Skip to content

Commit

Permalink
Working Copy and Paste test
Browse files Browse the repository at this point in the history
  • Loading branch information
lazysegtree committed Feb 1, 2025
1 parent cf6171d commit 67a5619
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 44 deletions.
5 changes: 5 additions & 0 deletions testsuite/ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
75 changes: 74 additions & 1 deletion testsuite/core/base_test.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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
Expand All @@ -28,3 +33,71 @@ def validate(self) -> bool:
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__}"

8 changes: 4 additions & 4 deletions testsuite/core/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
10 changes: 8 additions & 2 deletions testsuite/core/fs_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ def __init__(self):
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:
Expand Down
10 changes: 10 additions & 0 deletions testsuite/core/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,19 @@ def __init__(self, ascii_code : int, key_name : str):
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")
21 changes: 11 additions & 10 deletions testsuite/core/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@

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


Expand All @@ -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
Expand All @@ -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 ?
Expand Down
22 changes: 15 additions & 7 deletions testsuite/core/spf_manager.py
Original file line number Diff line number Diff line change
@@ -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):

Expand All @@ -21,7 +21,7 @@ def send_text_input(self, text : str, all_at_once : bool = False) -> None:
pass

@abstractmethod
def send_special_input(self, key : Keys) -> None:
def send_special_input(self, key : keys.Keys) -> None:
pass

@abstractmethod
Expand All @@ -47,6 +47,8 @@ def close_spf(self) -> None:
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
Expand All @@ -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
Expand All @@ -78,9 +81,14 @@ def send_text_input(self, text : str, all_at_once : bool = True) -> None:
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]"

Expand Down Expand Up @@ -113,7 +121,7 @@ def start_spf(self, start_dir : str = None) -> None:
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:
Expand Down
5 changes: 5 additions & 0 deletions testsuite/core/test_constants.py
Original file line number Diff line number Diff line change
@@ -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
19 changes: 0 additions & 19 deletions testsuite/core/tests/copy_test.py

This file was deleted.

1 change: 0 additions & 1 deletion testsuite/core/tests/test_constants.py

This file was deleted.

2 changes: 2 additions & 0 deletions testsuite/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
File renamed without changes.
28 changes: 28 additions & 0 deletions testsuite/tests/copy_test.py
Original file line number Diff line number Diff line change
@@ -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 changes: 28 additions & 0 deletions testsuite/tests/cut_test.py
Original file line number Diff line number Diff line change
@@ -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]
)

0 comments on commit 67a5619

Please sign in to comment.