diff options
author | Egor Tensin <Egor.Tensin@gmail.com> | 2023-06-29 22:35:29 +0200 |
---|---|---|
committer | Egor Tensin <Egor.Tensin@gmail.com> | 2023-06-29 22:35:29 +0200 |
commit | 8c5c0138d71023079e9f0ac45f2f01a7e96784bc (patch) | |
tree | d15c4579c00fcb7d1f73a0d8f627b5ab398efbd1 /test/lib | |
parent | log: minor refactoring (diff) | |
download | cimple-8c5c0138d71023079e9f0ac45f2f01a7e96784bc.tar.gz cimple-8c5c0138d71023079e9f0ac45f2f01a7e96784bc.zip |
test: shuffle files a bit
This should hopefully reduce clutter in the test/ directory.
Side note: if I leave the __init__.py file in the new py/ directory,
pytest fails with import errors. To make it work, I need to either
delete it or keep the __init__.py file in both test/ and py/. No idea
why.
Diffstat (limited to 'test/lib')
-rw-r--r-- | test/lib/__init__.py | 0 | ||||
-rw-r--r-- | test/lib/process.py | 191 | ||||
-rw-r--r-- | test/lib/test_repo.py | 48 | ||||
-rwxr-xr-x | test/lib/test_repo/ci.sh | 24 |
4 files changed, 0 insertions, 263 deletions
diff --git a/test/lib/__init__.py b/test/lib/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/test/lib/__init__.py +++ /dev/null diff --git a/test/lib/process.py b/test/lib/process.py deleted file mode 100644 index 3436280..0000000 --- a/test/lib/process.py +++ /dev/null @@ -1,191 +0,0 @@ -# Copyright (c) 2023 Egor Tensin <Egor.Tensin@gmail.com> -# This file is part of the "cimple" project. -# For details, see https://github.com/egor-tensin/cimple. -# Distributed under the MIT License. - -from contextlib import contextmanager -import logging -import os -import shutil -import subprocess -from threading import Event, Lock, Thread - - -class LoggingEvent(Event): - def __init__(self, timeout=10): - self.timeout = timeout - super().__init__() - - def log_line_matches(self, line): - return False - - def wait(self): - if not super().wait(self.timeout): - raise RuntimeError('timed out while waiting for an event') - - -class LoggingThread(Thread): - def __init__(self, process, events=None): - self.process = process - self.events_lock = Lock() - if events is None: - events = [] - self.events = events - - super().__init__(target=lambda: self.process_output_lines()) - self.start() - - def add_event(self, event): - with self.events_lock: - self.events.append(event) - - def process_output_lines(self): - for line in self.process.stdout: - line = line.removesuffix('\n') - logging.info('%s: %s', self.process.log_id, line) - with self.events_lock: - for event in self.events: - if event.is_set(): - continue - if not event.log_line_matches(line): - continue - event.set() - self.events = [event for event in self.events if not event.is_set()] - - -class CmdLine: - @staticmethod - def which(binary): - if os.path.split(binary)[0]: - # shutil.which('bin/bash') doesn't work. - return os.path.abspath(binary) - path = shutil.which(binary) - if path is None: - raise RuntimeError("couldn't find a binary: " + binary) - return path - - @staticmethod - def unbuffered(): - return CmdLine('stdbuf', '-o0') - - def __init__(self, binary, *args, name=None): - binary = self.which(binary) - argv = [binary] + list(args) - - self.binary = binary - self.argv = argv - - if name is None: - name = os.path.basename(binary) - self.process_name = name - - def log_line_means_process_ready(self, line): - return True - - @classmethod - def wrap(cls, outer, inner): - return cls(outer.argv[0], *outer.argv[1:], *inner.argv, name=inner.process_name) - - def run(self, *argv): - return Process.run(*self.argv, *argv) - - @contextmanager - def run_async(self, *argv): - with Process(self, *argv) as process: - yield process - - -class LoggingEventProcessReady(LoggingEvent): - def __init__(self, process): - self.process = process - super().__init__() - - def set(self): - logging.info('Process %s is ready', self.process.log_id) - super().set() - - def log_line_matches(self, line): - return self.process.cmd_line.log_line_means_process_ready(line) - - -class Process(subprocess.Popen): - _COMMON_ARGS = { - 'text': True, - 'stdin': subprocess.DEVNULL, - 'stdout': subprocess.PIPE, - 'stderr': subprocess.STDOUT, - } - - @staticmethod - def _log_process_start(argv): - logging.info('Executing command: %s', argv) - - @staticmethod - def _log_process_end(argv, ec, output): - log = logging.info - if ec: - log = logging.error - if ec: - log('Command %s exited with code %s', argv, ec) - else: - log('Command %s completed successfully', argv) - if output: - log('Output:\n%s', output) - - @staticmethod - def run(*args, **kwargs): - argv = list(args) - Process._log_process_start(argv) - try: - result = subprocess.run(argv, check=True, **Process._COMMON_ARGS, **kwargs) - ec, output = result.returncode, result.stdout - Process._log_process_end(argv, ec, output) - return output - except subprocess.CalledProcessError as e: - ec, output = e.returncode, e.stdout - Process._log_process_end(argv, ec, output) - raise - - def __init__(self, cmd_line, *args): - self.cmd_line = cmd_line - - argv = cmd_line.argv + list(args) - self._log_process_start(argv) - - super().__init__(argv, **Process._COMMON_ARGS) - logging.info('Process %s has started', self.log_id) - - ready_event = LoggingEventProcessReady(self) - self.logger = LoggingThread(self, [ready_event]) - ready_event.wait() - - @property - def log_id(self): - return f'{self.pid}/{self.cmd_line.process_name}' - - def __exit__(self, *args): - try: - self.shut_down() - self.logger.join() - except Exception as e: - logging.exception(e) - # Postpone closing the pipes until after the logging thread is finished - # so that it doesn't attempt to read from closed descriptors. - super().__exit__(*args) - - SHUT_DOWN_TIMEOUT_SEC = 3 - - def shut_down(self): - ec = self.poll() - if ec is not None: - return - logging.info('Terminating process %s', self.log_id) - self.terminate() - try: - self.wait(timeout=Process.SHUT_DOWN_TIMEOUT_SEC) - return - except subprocess.TimeoutExpired: - pass - logging.info('Process %s failed to terminate in time, killing it', self.log_id) - self.kill() - self.wait(timeout=Process.SHUT_DOWN_TIMEOUT_SEC) diff --git a/test/lib/test_repo.py b/test/lib/test_repo.py deleted file mode 100644 index 1922245..0000000 --- a/test/lib/test_repo.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (c) 2023 Egor Tensin <Egor.Tensin@gmail.com> -# This file is part of the "cimple" project. -# For details, see https://github.com/egor-tensin/cimple. -# Distributed under the MIT License. - -import logging -import os -import shutil - -from .process import Process - - -class Repo: - BRANCH = 'main' - - def __init__(self, path): - self.path = os.path.abspath(path) - os.makedirs(path, exist_ok=True) - self.run('git', 'init', '-q', f'--initial-branch={Repo.BRANCH}') - self.run('git', 'config', 'user.name', 'Test User') - self.run('git', 'config', 'user.email', 'test@example.com') - - def run(self, *args, **kwargs): - Process.run(*args, cwd=self.path, **kwargs) - - -class TestRepo(Repo): - # Prevent Pytest from discovering test cases in this class: - __test__ = False - - DATA_DIR = 'test_repo' - CI_SCRIPT = 'ci.sh' - OUTPUT_DIR = 'output' - - @staticmethod - def get_ci_script(): - return os.path.join(os.path.dirname(__file__), TestRepo.DATA_DIR, TestRepo.CI_SCRIPT) - - def __init__(self, path): - super().__init__(path) - shutil.copy(self.get_ci_script(), self.path) - self.run('git', 'add', '.') - self.run('git', 'commit', '-q', '-m', 'add CI script') - self.output_dir = os.path.join(self.path, TestRepo.OUTPUT_DIR) - os.makedirs(self.output_dir, exist_ok=True) - - def count_ci_output_files(self): - return len([name for name in os.listdir(self.output_dir) if os.path.isfile(os.path.join(self.output_dir, name))]) diff --git a/test/lib/test_repo/ci.sh b/test/lib/test_repo/ci.sh deleted file mode 100755 index 804cdb3..0000000 --- a/test/lib/test_repo/ci.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -o nounset -o pipefail -shopt -s inherit_errexit lastpipe - -script_dir="$( dirname -- "${BASH_SOURCE[0]}" )" -script_dir="$( cd -- "$script_dir" && pwd )" -readonly script_dir - -ci_output_dir="$( git remote get-url origin )" -ci_output_dir="$ci_output_dir/output" -readonly ci_output_dir - -mkdir -p -- "$ci_output_dir" - -readonly ci_output_template=ci_XXXXXX - -ci_output_path="$( mktemp --tmpdir="$ci_output_dir" "$ci_output_template" )" -readonly ci_output_path - -timestamp="$( date --iso-8601=ns )" -readonly timestamp - -echo "A CI run happened at $timestamp" | tee -- "$ci_output_path" |