aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/test/lib
diff options
context:
space:
mode:
authorEgor Tensin <Egor.Tensin@gmail.com>2023-06-29 22:35:29 +0200
committerEgor Tensin <Egor.Tensin@gmail.com>2023-06-29 22:35:29 +0200
commit8c5c0138d71023079e9f0ac45f2f01a7e96784bc (patch)
treed15c4579c00fcb7d1f73a0d8f627b5ab398efbd1 /test/lib
parentlog: minor refactoring (diff)
downloadcimple-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__.py0
-rw-r--r--test/lib/process.py191
-rw-r--r--test/lib/test_repo.py48
-rwxr-xr-xtest/lib/test_repo/ci.sh24
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"