1
0
mirror of https://github.com/yorukot/superfile.git synced 2025-02-06 11:00:15 +00:00

Working Copy and Paste test
All checks were successful
Go / build (push) Successful in 10m14s

This commit is contained in:
Nitin Kumar 2025-02-01 16:25:38 +05:30
parent cf6171d25c
commit 67a5619443
14 changed files with 190 additions and 44 deletions

View File

@ -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.

View File

@ -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__}"

View File

@ -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()

View File

@ -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:

View File

@ -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")

View File

@ -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 ?

View File

@ -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:

View 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

View File

@ -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__}"

View File

@ -1 +0,0 @@
FILE_TEXT1 : str = "This is a sample Text\n"

View File

@ -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')

View 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]
)

View 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]
)