mirror of
https://github.com/yorukot/superfile.git
synced 2025-02-06 11:00:15 +00:00
This commit is contained in:
parent
cf6171d25c
commit
67a5619443
@ -7,6 +7,11 @@
|
||||
- Recommended to integrate your IDE with PEP8 to highlight PEP8 violations in real-time
|
||||
- Enforcing PEP8 via `pylint flake8 pycodestyle` and via pre commit hooks
|
||||
|
||||
## Writing New testcases
|
||||
- Just create a file ending with `_test.py` in `tests` directory
|
||||
- Any subclass of BaseTest with name ending with `Test` will be executed
|
||||
- see `run_tests` and `get_testcases` in `core/runner.py` for more info
|
||||
|
||||
## Setup
|
||||
Requires python 3.9 or later.
|
||||
|
||||
|
@ -1,6 +1,11 @@
|
||||
import logging
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from core.environment import Environment
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
import core.keys as keys
|
||||
import core.test_constants as tconst
|
||||
|
||||
|
||||
class BaseTest(ABC):
|
||||
@ -9,7 +14,7 @@ class BaseTest(ABC):
|
||||
And for each test to have full control on its environment, execution, and validation.
|
||||
"""
|
||||
def __init__(self, test_env : Environment):
|
||||
self.test_env = test_env
|
||||
self.env = test_env
|
||||
self.logger = logging.getLogger()
|
||||
|
||||
@abstractmethod
|
||||
@ -28,3 +33,71 @@ class BaseTest(ABC):
|
||||
Returns:
|
||||
bool: True if validation passed
|
||||
"""
|
||||
|
||||
class GenericTestImpl(BaseTest):
|
||||
def __init__(self, test_env : Environment,
|
||||
test_root : Path,
|
||||
start_dir : Path,
|
||||
test_dirs : list[Path],
|
||||
test_files : list[tuple[Path, str]],
|
||||
key_inputs : list[Union[keys.Keys,str]],
|
||||
validation_files : list[Path]):
|
||||
super().__init__(test_env)
|
||||
self.test_root = test_root
|
||||
self.start_dir = start_dir
|
||||
self.test_dirs = test_dirs
|
||||
self.test_files = test_files
|
||||
self.key_inputs = key_inputs
|
||||
self.validation_files = validation_files
|
||||
|
||||
def setup(self):
|
||||
for dir_path in self.test_dirs:
|
||||
self.env.fs_mgr.makedirs(dir_path)
|
||||
for file_path, data in self.test_files:
|
||||
self.env.fs_mgr.create_file(file_path, data)
|
||||
|
||||
self.logger.debug("Current file structure : \n%s",
|
||||
self.env.fs_mgr.tree(self.test_root))
|
||||
|
||||
|
||||
def test_execute(self):
|
||||
"""Execute the test
|
||||
"""
|
||||
# Start in DIR1
|
||||
self.env.spf_mgr.start_spf(self.env.fs_mgr.abspath(self.start_dir))
|
||||
|
||||
assert self.env.spf_mgr.is_spf_running()
|
||||
|
||||
for cur_input in self.key_inputs:
|
||||
if isinstance(cur_input, keys.Keys):
|
||||
self.env.spf_mgr.send_special_input(cur_input)
|
||||
else:
|
||||
assert isinstance(cur_input, str)
|
||||
self.env.spf_mgr.send_text_input(cur_input)
|
||||
time.sleep(tconst.KEY_DELAY)
|
||||
|
||||
time.sleep(tconst.OPERATION_DELAY)
|
||||
self.env.spf_mgr.send_special_input(keys.KEY_ESC)
|
||||
time.sleep(tconst.CLOSE_DELAY)
|
||||
self.logger.debug("Finished Execution")
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Validate that test passed. Log exception if failed.
|
||||
Returns:
|
||||
bool: True if validation passed
|
||||
"""
|
||||
self.logger.debug("tmux sessions : %s, Current file structure : \n%s",
|
||||
self.env.spf_mgr.server.sessions, self.env.fs_mgr.tree(self.test_root))
|
||||
try:
|
||||
assert not self.env.spf_mgr.is_spf_running()
|
||||
for file_path in self.validation_files:
|
||||
assert self.env.fs_mgr.check_exists(file_path)
|
||||
except AssertionError as ae:
|
||||
self.logger.debug("Test assertion failed : %s", ae)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}"
|
||||
|
||||
|
@ -6,9 +6,9 @@ class Environment:
|
||||
Manage cleanup of environment and other stuff at a single place
|
||||
"""
|
||||
def __init__(self, spf_manager : BaseSPFManager, fs_manager : TestFSManager ):
|
||||
self.spf_manager = spf_manager
|
||||
self.fs_manager = fs_manager
|
||||
self.spf_mgr = spf_manager
|
||||
self.fs_mgr = fs_manager
|
||||
|
||||
def cleanup(self):
|
||||
self.spf_manager.close_spf()
|
||||
self.fs_manager.cleanup()
|
||||
self.spf_mgr.close_spf()
|
||||
self.fs_mgr.cleanup()
|
@ -13,11 +13,17 @@ class TestFSManager:
|
||||
self.temp_dir_obj = TemporaryDirectory()
|
||||
self.temp_dir = Path(self.temp_dir_obj.name)
|
||||
|
||||
def makedirs(self, relative_path : Path):
|
||||
def abspath(self, relative_path : Path) -> Path:
|
||||
return self.temp_dir / relative_path
|
||||
|
||||
def check_exists(self, relative_path : Path) -> bool:
|
||||
return self.abspath(relative_path).exists()
|
||||
|
||||
def makedirs(self, relative_path : Path) -> None:
|
||||
# Overloaded '/' operator
|
||||
os.makedirs(self.temp_dir / relative_path, exist_ok=True)
|
||||
|
||||
def create_file(self, relative_path : Path, data : str = ""):
|
||||
def create_file(self, relative_path : Path, data : str = "") -> None:
|
||||
"""Create files
|
||||
Make sure directories exist
|
||||
Args:
|
||||
|
@ -31,9 +31,19 @@ KEY_CTRL_C : Keys = CtrlKeys('c')
|
||||
KEY_CTRL_M : Keys = CtrlKeys('m')
|
||||
KEY_CTRL_R : Keys = CtrlKeys('r')
|
||||
KEY_CTRL_V : Keys = CtrlKeys('v')
|
||||
KEY_CTRL_X : Keys = CtrlKeys('x')
|
||||
|
||||
# See https://vimdoc.sourceforge.net/htmldoc/digraph.html#digraph-table for key codes
|
||||
KEY_BACKSPACE : Keys = SpecialKeys(8 , "Backspace")
|
||||
KEY_ENTER : Keys = SpecialKeys(13, "Enter")
|
||||
KEY_ESC : Keys = SpecialKeys(27, "Esc")
|
||||
|
||||
|
||||
NO_ASCII = -1
|
||||
|
||||
# Some keys dont have ascii codes, they have to be handled separately
|
||||
# Make sure key name is the same string as key code for Tmux
|
||||
KEY_DOWN : Keys = SpecialKeys(NO_ASCII, "Down")
|
||||
KEY_UP : Keys = SpecialKeys(NO_ASCII, "Up")
|
||||
KEY_LEFT : Keys = SpecialKeys(NO_ASCII, "Left")
|
||||
KEY_RIGHT : Keys = SpecialKeys(NO_ASCII, "Right")
|
||||
|
@ -12,19 +12,19 @@ from pathlib import Path
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
def get_testcases() -> list[BaseTest]:
|
||||
def get_testcases(test_env : Environment) -> list[BaseTest]:
|
||||
res : list[BaseTest] = []
|
||||
test_dir = Path(__file__).parent / "tests"
|
||||
test_dir = Path(__file__).parent.parent / "tests"
|
||||
for test_file in test_dir.glob("*_test.py"):
|
||||
# Import dynamically
|
||||
module_name = test_file.stem
|
||||
logger.info(test_file)
|
||||
module = importlib.import_module(f"core.tests.{module_name}")
|
||||
module = importlib.import_module(f"tests.{module_name}")
|
||||
for attr_name in dir(module):
|
||||
attr = getattr(module, attr_name)
|
||||
if isinstance(attr, type) and attr_name.endswith("Test"):
|
||||
if isinstance(attr, type) and attr is not BaseTest and issubclass(attr, BaseTest) \
|
||||
and attr_name.endswith("Test"):
|
||||
logger.debug("Found a testcase %s, in module %s", attr_name, module_name)
|
||||
res.append(attr())
|
||||
res.append(attr(test_env))
|
||||
return res
|
||||
|
||||
|
||||
@ -41,13 +41,12 @@ def run_tests(spf_path : Path, stop_on_fail : bool = True) -> None:
|
||||
|
||||
test_env = Environment(spf_manager, fs_manager)
|
||||
|
||||
|
||||
|
||||
try:
|
||||
cnt_passed : int = 0
|
||||
cnt_executed : int = 0
|
||||
testcases = get_testcases()
|
||||
for t in []:
|
||||
testcases = get_testcases(test_env)
|
||||
logger.debug("Testcases : %s", testcases)
|
||||
for t in testcases:
|
||||
t.setup()
|
||||
t.test_execute()
|
||||
cnt_executed += 1
|
||||
@ -56,6 +55,8 @@ def run_tests(spf_path : Path, stop_on_fail : bool = True) -> None:
|
||||
cnt_passed += 1
|
||||
elif stop_on_fail:
|
||||
break
|
||||
|
||||
logger.info("Finised running %s test. %s passed", cnt_executed, cnt_passed)
|
||||
finally:
|
||||
# Make sure of cleanup
|
||||
# This is still not full proof, as if what happens when TestFSManager __init__ fails ?
|
||||
|
@ -1,9 +1,9 @@
|
||||
import libtmux
|
||||
import time
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from core.keys import Keys
|
||||
|
||||
import core.keys as keys
|
||||
|
||||
class BaseSPFManager(ABC):
|
||||
|
||||
@ -21,7 +21,7 @@ class BaseSPFManager(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def send_special_input(self, key : Keys) -> None:
|
||||
def send_special_input(self, key : keys.Keys) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
@ -47,6 +47,8 @@ class BaseSPFManager(ABC):
|
||||
class TmuxSPFManager(BaseSPFManager):
|
||||
"""
|
||||
Tmux based Manager
|
||||
After running spf, you can connect to the session via
|
||||
tmux -L superfile attach -t spf_session
|
||||
"""
|
||||
# Class variables
|
||||
SPF_START_DELAY = 0.1 # seconds
|
||||
@ -55,6 +57,7 @@ class TmuxSPFManager(BaseSPFManager):
|
||||
# Init should not allocate any resources
|
||||
def __init__(self, spf_path : str):
|
||||
super().__init__(spf_path)
|
||||
self.logger = logging.getLogger()
|
||||
self.server = libtmux.Server(socket_name=TmuxSPFManager.SPF_SOCKET_NAME)
|
||||
self.spf_session : libtmux.Session = None
|
||||
self.spf_pane : libtmux.Pane = None
|
||||
@ -78,9 +81,14 @@ class TmuxSPFManager(BaseSPFManager):
|
||||
for c in text:
|
||||
self._send_key(c)
|
||||
|
||||
def send_special_input(self, key : Keys) -> str:
|
||||
self._send_key(chr(key.ascii_code))
|
||||
|
||||
def send_special_input(self, key : keys.Keys) -> str:
|
||||
if key.ascii_code != keys.NO_ASCII:
|
||||
self._send_key(chr(key.ascii_code))
|
||||
elif isinstance(key, keys.SpecialKeys):
|
||||
self._send_key(key.key_name)
|
||||
else:
|
||||
raise Exception(f"Unknown key : {key}")
|
||||
|
||||
def get_rendered_output(self) -> str:
|
||||
return "[Not supported yet]"
|
||||
|
||||
@ -113,7 +121,7 @@ class PyAutoGuiSPFManager(BaseSPFManager):
|
||||
def send_text_input(self, text : str, all_at_once : bool = False) -> None:
|
||||
pass
|
||||
|
||||
def send_special_input(self, key : Keys) -> None:
|
||||
def send_special_input(self, key : keys.Keys) -> None:
|
||||
pass
|
||||
|
||||
def get_rendered_output(self) -> str:
|
||||
|
5
testsuite/core/test_constants.py
Normal file
5
testsuite/core/test_constants.py
Normal file
@ -0,0 +1,5 @@
|
||||
FILE_TEXT1 : str = "This is a sample Text\n"
|
||||
|
||||
KEY_DELAY : float = 0.05 # seconds
|
||||
OPERATION_DELAY : float = 0.3 # seconds
|
||||
CLOSE_DELAY : float = 0.3 # seconds
|
@ -1,19 +0,0 @@
|
||||
from core.base_test import BaseTest
|
||||
|
||||
class CopyTest(BaseTest):
|
||||
|
||||
def setup(self):
|
||||
"""Set up the required things for test
|
||||
"""
|
||||
|
||||
def test_execute(self):
|
||||
"""Execute the test
|
||||
"""
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Validate that test passed. Log exception if failed.
|
||||
Returns:
|
||||
bool: True if validation passed
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}"
|
@ -1 +0,0 @@
|
||||
FILE_TEXT1 : str = "This is a sample Text\n"
|
@ -25,6 +25,8 @@ def configure_logging(debug : bool = False) -> None:
|
||||
else:
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
logging.getLogger("libtmux").setLevel(logging.WARNING)
|
||||
|
||||
def main():
|
||||
# Setup argument parser
|
||||
parser = argparse.ArgumentParser(description='superfile testsuite')
|
||||
|
28
testsuite/tests/copy_test.py
Normal file
28
testsuite/tests/copy_test.py
Normal file
@ -0,0 +1,28 @@
|
||||
from pathlib import Path
|
||||
|
||||
from core.base_test import GenericTestImpl
|
||||
from core.environment import Environment
|
||||
import core.test_constants as tconst
|
||||
import core.keys as keys
|
||||
|
||||
TESTROOT = Path("copy_ops")
|
||||
DIR1 = TESTROOT / "dir1"
|
||||
DIR2 = TESTROOT / "dir2"
|
||||
FILE1 = DIR1 / "file1.txt"
|
||||
FILE1_COPY1 = DIR1 / "file1(1).txt"
|
||||
FILE1_COPY2 = DIR2 / "file1.txt"
|
||||
|
||||
|
||||
|
||||
class CopyTest(GenericTestImpl):
|
||||
|
||||
def __init__(self, test_env : Environment):
|
||||
super().__init__(
|
||||
test_env=test_env,
|
||||
test_root=TESTROOT,
|
||||
start_dir=DIR1,
|
||||
test_dirs=[DIR1, DIR2],
|
||||
test_files=[(FILE1, tconst.FILE_TEXT1)],
|
||||
key_inputs=[keys.KEY_CTRL_C, keys.KEY_CTRL_V],
|
||||
validation_files=[FILE1_COPY1]
|
||||
)
|
28
testsuite/tests/cut_test.py
Normal file
28
testsuite/tests/cut_test.py
Normal file
@ -0,0 +1,28 @@
|
||||
from pathlib import Path
|
||||
|
||||
from core.base_test import GenericTestImpl
|
||||
from core.environment import Environment
|
||||
import core.test_constants as tconst
|
||||
import core.keys as keys
|
||||
|
||||
TESTROOT = Path("cut_ops")
|
||||
DIR1 = TESTROOT / "dir1"
|
||||
DIR2 = TESTROOT / "dir2"
|
||||
FILE1 = DIR1 / "file1.txt"
|
||||
FILE1_CUT1 = DIR2 / "file1.txt"
|
||||
|
||||
|
||||
|
||||
class CutTest(GenericTestImpl):
|
||||
|
||||
def __init__(self, test_env : Environment):
|
||||
super().__init__(
|
||||
test_env=test_env,
|
||||
test_root=TESTROOT,
|
||||
start_dir=DIR1,
|
||||
test_dirs=[DIR1, DIR2],
|
||||
test_files=[(FILE1, tconst.FILE_TEXT1)],
|
||||
key_inputs=[keys.KEY_CTRL_X, keys.KEY_LEFT, keys.KEY_DOWN,
|
||||
keys.KEY_ENTER, keys.KEY_CTRL_V],
|
||||
validation_files=[FILE1_CUT1]
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user