From 1a61903537764804b0e8b50a2bb065f4d01ca78b Mon Sep 17 00:00:00 2001 From: AttilaGombosER Date: Sun, 4 Aug 2024 16:59:27 +0200 Subject: [PATCH] Propagate return code of subprocess calls (#5) --- python/README.md | 40 +++++++++++++++--- python/common.py | 3 +- python/dh-virtualenv | 71 +++++++++++--------------------- python/fpm-deb | 42 +++++++------------ python/pack_python | 69 +++++++++++-------------------- python/tests/dhVirtualenvTest.py | 28 ++++++++++--- python/tests/fpmDebTest.py | 28 ++++++++++--- python/tests/packPythonTest.py | 20 ++++++++- python/tests/utils.py | 37 +++++++---------- python/tests/wheelTest.py | 24 ++++++++--- python/wheel | 32 +++++--------- 11 files changed, 208 insertions(+), 186 deletions(-) diff --git a/python/README.md b/python/README.md index f3d6a4b..92469f3 100644 --- a/python/README.md +++ b/python/README.md @@ -12,9 +12,37 @@ Packaging scripts to create installable packages from Python projects. ## Overview -The main entry point is `pack_python` script. It is calling the packaging-specific scripts and passing arguments to them. +The main entry point is `pack_python` script. +It is calling the packaging-specific scripts and passing arguments to them. Also parses the configuration from the target project's `setup.cfg` file if there is any. +## Requirements + +### python + +```bash +$ sudo apt-get install python3 python3-pip +``` + +### wheel + +```bash +$ pip install wheel +``` + +### fpm + +```bash +$ sudo apt-get install ruby ruby-dev rubygems build-essential +$ sudo gem install -N fpm +``` + +### dh-virtualenv + +```bash +sudo apt-get install debhelper devscripts equivs dh-virtualenv dh-python python3-virtualenv python3-all python3-stdeb +``` + ## Configuration The configuration is read from the `setup.cfg` file in the target project's root directory by default. @@ -78,19 +106,19 @@ $ ./pack_python tests/test-project --scripts "wheel fpm-deb" /home/attilagombos/EffectiveRange/packaging-tools/python/tests/test-project/dist/python3-test-project_1.0.0_all.deb ``` -## Scripts +## Packaging scripts - `wheel` - Create binary wheel package - `fpm-deb` - Create debian .deb package using [FPM](https://fpm.readthedocs.io/en/latest/index.html) - `dh-virtualenv` - Create debian .deb package using [dh-virtualenv](https://dh-virtualenv.readthedocs.io/en/latest/) -and [stdeb](https://github.com/astraw/stdeb) + and [stdeb](https://github.com/astraw/stdeb) ### wheel The `wheel` script is using the `bdist_wheel` setuptools command to create a binary wheel package. ```bash -$ scripts/wheel --help +$ wheel --help usage: wheel [-h] [-p PYTHON_BIN] [-o OUTPUT_DIR] workspace_dir positional arguments: @@ -109,7 +137,7 @@ options: The `fpm-deb` script is using the `fpm` to create a debian .deb package. ```bash -$ scripts/fpm-deb --help +$ fpm-deb --help usage: fpm-deb [-h] [-a ARGUMENTS] [-p PYTHON_BIN] [-o OUTPUT_DIR] workspace_dir positional arguments: @@ -131,7 +159,7 @@ The `dh-virtualenv` script is using `stdeb` and `dh-virtualenv` to create a debi with all Python dependecies pre-installed in a virtual environment. ```bash -$ scripts/dh-virtualenv --help +$ dh-virtualenv --help usage: dh-virtualenv [-h] [-a ARGUMENTS] [-p PYTHON_BIN] [-s SERVICE_FILE] [-e EXTRA_FILES] [-o OUTPUT_DIR] workspace_dir positional arguments: diff --git a/python/common.py b/python/common.py index 1fb2d75..df7b669 100644 --- a/python/common.py +++ b/python/common.py @@ -26,7 +26,7 @@ def run_command(workspace_dir: str, command: Union[str, list[str]], matcher: str first_match_only: bool = True) -> Generator[str, None, None]: command_line = command if isinstance(command, str) else ' '.join(command) - print(f'Running command "{command_line}"', file=sys.stderr) + print(f"Running command '{command_line}' with output matcher {matcher}", file=sys.stderr) with Popen(command_line, cwd=workspace_dir, shell=True, text=True, stdout=PIPE, stderr=PIPE) as process: pattern = re.compile(matcher) @@ -42,6 +42,7 @@ def run_command(workspace_dir: str, command: Union[str, list[str]], matcher: str return_code = process.poll() if return_code: + print(f"Command '{command_line}' failed with return code {return_code}", file=sys.stderr) exit(return_code) for line in output: diff --git a/python/dh-virtualenv b/python/dh-virtualenv index ff18432..64aff0c 100755 --- a/python/dh-virtualenv +++ b/python/dh-virtualenv @@ -5,18 +5,12 @@ # SPDX-License-Identifier: MIT import glob -import os -from posixpath import dirname import re import shutil -import sys from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Namespace from os.path import abspath -from pathlib import Path from typing import Generator -sys.path.insert(0, dirname(abspath(__file__))) - from common import check_workspace, run_command, get_absolute_path @@ -27,55 +21,48 @@ def main() -> None: check_workspace(workspace_dir) - output_dir = f"{workspace_dir}/dist" + output_dir = f'{workspace_dir}/dist' if arguments.output_dir: output_dir = abspath(arguments.output_dir) results = _create_sources(arguments, workspace_dir, output_dir) - build_dir = f"{output_dir}/{next(results)}" + build_dir = f'{output_dir}/{next(results)}' results = _build_package(arguments, workspace_dir, build_dir) for result in results: - print(f"{output_dir}/{result}") + print(f'{output_dir}/{result}') -def _create_sources( - arguments: Namespace, workspace_dir: str, output_dir: str -) -> Generator[str, None, None]: +def _create_sources(arguments: Namespace, workspace_dir: str, output_dir: str) -> Generator[str, None, None]: package_name = _extract_package_name(workspace_dir) command_arguments = [ - "--package3", - package_name, - "--dist-dir", - output_dir, - "--with-dh-virtualenv", - "--compat", - "10", + '--package3', package_name, + '--dist-dir', output_dir, + '--with-dh-virtualenv', + '--compat', '10', ] if arguments.arguments: command_arguments.extend(arguments.arguments.split()) if arguments.service_file: - command_arguments.append("--with-dh-systemd") + command_arguments.append('--with-dh-systemd') command = [ - arguments.python_bin, - "setup.py", - "--command-packages=stdeb.command", - "sdist_dsc", - *command_arguments, + arguments.python_bin, 'setup.py', + '--command-packages=stdeb.command', + 'sdist_dsc', *command_arguments, ] - return run_command(workspace_dir, command, r"copying setup.py -> (.+)") + return run_command(workspace_dir, command, r'copying setup.py -> (.+)') def _extract_package_name(workspace_dir: str) -> str: - with open(f"{workspace_dir}/setup.py", "r") as file: + with open(f'{workspace_dir}/setup.py', 'r') as file: setup_code = file.read() pattern = r'name\s*=\s*[\'"]([^\'"]+)[\'"]' @@ -85,13 +72,11 @@ def _extract_package_name(workspace_dir: str) -> str: if match: return match.group(1) else: - return workspace_dir.split("/")[-1] + return workspace_dir.split('/')[-1] -def _build_package( - arguments: Namespace, workspace_dir: str, build_dir: str -) -> Generator[str, None, None]: - debian_dir = f"{build_dir}/debian" +def _build_package(arguments: Namespace, workspace_dir: str, build_dir: str) -> Generator[str, None, None]: + debian_dir = f'{build_dir}/debian' if arguments.service_file: service_file = get_absolute_path(arguments.service_file, workspace_dir) @@ -103,27 +88,21 @@ def _build_package( for file in glob.glob(extra_files): shutil.copy(file, debian_dir) - command = ["dpkg-buildpackage", "-us", "-uc", "-ui", "-b"] + command = ['dpkg-buildpackage', '-us', '-uc', '-ui', '-b'] return run_command(build_dir, command, r".*'\.\./(.+\.deb)'") def _get_arguments() -> Namespace: parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) - parser.add_argument("-a", "--arguments", help="extra arguments passed to stdeb") - parser.add_argument( - "-p", "--python-bin", help="python executable to use", default="python3" - ) - parser.add_argument("-s", "--service-file", help="service unit file path") - parser.add_argument( - "-e", "--extra-files", help="add extra files into debian folder before build" - ) - parser.add_argument("-o", "--output-dir", help="package output directory") - parser.add_argument( - "workspace_dir", help="workspace directory where setup.py is located" - ) + parser.add_argument('-a', '--arguments', help='extra arguments passed to stdeb') + parser.add_argument('-p', '--python-bin', help='python executable to use', default='python3') + parser.add_argument('-s', '--service-file', help='service unit file path') + parser.add_argument('-e', '--extra-files', help='add extra files into debian folder before build') + parser.add_argument('-o', '--output-dir', help='package output directory') + parser.add_argument('workspace_dir', help='workspace directory where setup.py is located') return parser.parse_args() -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/python/fpm-deb b/python/fpm-deb index 4aa335b..1820739 100755 --- a/python/fpm-deb +++ b/python/fpm-deb @@ -5,13 +5,8 @@ # SPDX-License-Identifier: MIT import os -from os.path import dirname -import sys from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Namespace from os.path import abspath -from pathlib import Path - -sys.path.insert(0, dirname(abspath(__file__))) from common import check_workspace, run_command, get_absolute_path @@ -24,31 +19,26 @@ def main() -> None: check_workspace(workspace_dir) fpm_arguments = [ - "-s", - "python", - "-t", - "deb", - "--python-bin", - arguments.python_bin, - "--python-package-name-prefix", - "python3", - "--log", - "error", - "-f", + '-s', 'python', + '-t', 'deb', + '--python-bin', arguments.python_bin, + '--python-package-name-prefix', 'python3', + '--log', 'error', + '-f', ] if arguments.arguments: fpm_arguments.extend(arguments.arguments.split()) - output_dir = f"{workspace_dir}/dist" + output_dir = f'{workspace_dir}/dist' if arguments.output_dir: output_dir = abspath(arguments.output_dir) os.makedirs(output_dir, exist_ok=True) - fpm_arguments.extend(["--package", output_dir]) + fpm_arguments.extend(['--package', output_dir]) - command = ["fpm", *fpm_arguments, "setup.py"] + command = ['fpm', *fpm_arguments, 'setup.py'] results = run_command(workspace_dir, command, r'.*"(.+\.deb)"') @@ -59,16 +49,12 @@ def main() -> None: def _get_arguments() -> Namespace: parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) - parser.add_argument("-a", "--arguments", help="extra arguments passed to fpm") - parser.add_argument( - "-p", "--python-bin", help="python executable to use", default="python3" - ) - parser.add_argument("-o", "--output-dir", help="package output directory") - parser.add_argument( - "workspace_dir", help="workspace directory where setup.py is located" - ) + parser.add_argument('-a', '--arguments', help='extra arguments passed to fpm') + parser.add_argument('-p', '--python-bin', help='python executable to use', default='python3') + parser.add_argument('-o', '--output-dir', help='package output directory') + parser.add_argument('workspace_dir', help='workspace directory where setup.py is located') return parser.parse_args() -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/python/pack_python b/python/pack_python index 749c6c6..8f51dfa 100755 --- a/python/pack_python +++ b/python/pack_python @@ -15,13 +15,9 @@ from argparse import ( from configparser import ConfigParser from os.path import exists, dirname, abspath -import sys - -sys.path.insert(0, dirname(abspath(__file__))) - from common import check_workspace, get_absolute_path -DEFAULT_PACKAGING = "wheel" +DEFAULT_PACKAGING = 'wheel' def main() -> None: @@ -40,10 +36,10 @@ def main() -> None: else: configuration, packaging = _parse_config(arguments, config_file) - scripts_dir = f"{abspath(dirname(__file__))}" + scripts_dir = f'{abspath(dirname(__file__))}' for script in packaging: - script_file = f"{scripts_dir}/{script}" + script_file = f'{scripts_dir}/{script}' if exists(script_file): command = [script_file, workspace_dir] @@ -56,21 +52,21 @@ def main() -> None: def _run_script(arguments: Namespace, command: list[str]) -> None: if arguments.python_bin: - command.extend(["-p", arguments.python_bin]) + command.extend(['-p', arguments.python_bin]) if arguments.output_dir: - command.extend(["-o", arguments.output_dir]) + command.extend(['-o', arguments.output_dir]) result = subprocess.run(command, text=True, stdout=subprocess.PIPE) - if not result.returncode: - if result.stdout: - print(result.stdout.rstrip("\n")) + if result.returncode: + exit(result.returncode) + + if result.stdout: + print(result.stdout.rstrip('\n')) -def _parse_config( - arguments: Namespace, config_file: str -) -> tuple[dict[str, str], list[str]]: +def _parse_config(arguments: Namespace, config_file: str) -> tuple[dict[str, str], list[str]]: configuration = {} packaging = [DEFAULT_PACKAGING] @@ -78,15 +74,15 @@ def _parse_config( parser = ConfigParser() parser.read(config_file) - if parser.has_section("pack-python"): - configuration = dict(parser["pack-python"]) + if parser.has_section('pack-python'): + configuration = dict(parser['pack-python']) - packaging_options = configuration.get("packaging", "").split() + packaging_options = configuration.get('packaging', '').split() if arguments.all: packaging = packaging_options else: - default = configuration.get("default", DEFAULT_PACKAGING) + default = configuration.get('default', DEFAULT_PACKAGING) packaging = [default] return configuration, packaging @@ -94,38 +90,23 @@ def _parse_config( def _get_arguments() -> Namespace: parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) - parser.add_argument( - "-s", "--scripts", help="space separated packaging scripts to run" - ) - parser.add_argument( - "-a", - "--all", - help="run all configured packaging scripts", - action=BooleanOptionalAction, - default=False, - ) - parser.add_argument( - "-c", - "--config-file", - help="config file path relative to workspace directory", - default="setup.cfg", - ) - parser.add_argument( - "-p", "--python-bin", help="python executable to use", default="python3" - ) - parser.add_argument("-o", "--output-dir", help="package output directory") - parser.add_argument( - "workspace_dir", help="workspace directory where setup.py is located" - ) + parser.add_argument('-s', '--scripts', help='space separated packaging scripts to run') + parser.add_argument('-a', '--all', help='run all configured packaging scripts', + action=BooleanOptionalAction, default=False) + parser.add_argument('-c', '--config-file', help='config file path relative to workspace directory', + default='setup.cfg') + parser.add_argument('-p', '--python-bin', help='python executable to use', default='python3') + parser.add_argument('-o', '--output-dir', help='package output directory') + parser.add_argument('workspace_dir', help='workspace directory where setup.py is located') return parser.parse_args() def _split_arguments(arg_string: str) -> list[str]: # Split on spaces, but allow spaces inside double quotes - pattern = r"\"(.*?)\"|(\S+)" + pattern = r'\"(.*?)\"|(\S+)' matches = re.findall(pattern, arg_string) return [match[0] if match[0] else match[1] for match in matches] -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/python/tests/dhVirtualenvTest.py b/python/tests/dhVirtualenvTest.py index 85ab680..4363f5e 100644 --- a/python/tests/dhVirtualenvTest.py +++ b/python/tests/dhVirtualenvTest.py @@ -4,7 +4,7 @@ from unittest import TestCase from utils import TEST_PROJECT_ROOT, TEST_RESOURCE_ROOT, TEST_FILE_SYSTEM_ROOT, \ - delete_directory, check_file_is_in_deb, SCRIPTS_DIR, run_command, check_files_matches_in_deb + delete_directory, check_file_is_in_deb, RESOURCE_ROOT, run_command, check_files_matches_in_deb, check_files_exist class DhVirtualenvTest(TestCase): @@ -16,7 +16,7 @@ def setUp(self): def test_dh_virtualenv_when_no_output_dir_specified(self): # Given - command = [f'{SCRIPTS_DIR}/dh-virtualenv', TEST_PROJECT_ROOT] + command = [f'{RESOURCE_ROOT}/dh-virtualenv', TEST_PROJECT_ROOT] # When result = run_command(command) @@ -24,11 +24,12 @@ def test_dh_virtualenv_when_no_output_dir_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_PROJECT_ROOT}/dist/test-project_1.0.0-1_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_dh_virtualenv_when_relative_output_dir_specified(self): # Given output_dir = 'tests/test_root/etc/dist' if os.path.exists('tests') else 'test_root/etc/dist' - command = [f'{SCRIPTS_DIR}/dh-virtualenv', TEST_PROJECT_ROOT, '-o', output_dir] + command = [f'{RESOURCE_ROOT}/dh-virtualenv', TEST_PROJECT_ROOT, '-o', output_dir] # When result = run_command(command) @@ -36,10 +37,11 @@ def test_dh_virtualenv_when_relative_output_dir_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{os.getcwd()}/{output_dir}/test-project_1.0.0-1_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_dh_virtualenv_when_absolute_output_dir_specified(self): # Given - command = [f'{SCRIPTS_DIR}/dh-virtualenv', TEST_PROJECT_ROOT, '-o', f'{TEST_FILE_SYSTEM_ROOT}/etc/dist'] + command = [f'{RESOURCE_ROOT}/dh-virtualenv', TEST_PROJECT_ROOT, '-o', f'{TEST_FILE_SYSTEM_ROOT}/etc/dist'] # When result = run_command(command) @@ -47,10 +49,11 @@ def test_dh_virtualenv_when_absolute_output_dir_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_FILE_SYSTEM_ROOT}/etc/dist/test-project_1.0.0-1_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_dh_virtualenv_when_service_file_specified(self): # Given - command = [f'{SCRIPTS_DIR}/dh-virtualenv', TEST_PROJECT_ROOT, + command = [f'{RESOURCE_ROOT}/dh-virtualenv', TEST_PROJECT_ROOT, '-s', f'{TEST_PROJECT_ROOT}/service/test-project.service'] # When @@ -59,12 +62,13 @@ def test_dh_virtualenv_when_service_file_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_PROJECT_ROOT}/dist/test-project_1.0.0-1_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) self.assertTrue(check_file_is_in_deb(f'{TEST_PROJECT_ROOT}/dist/test-project_1.0.0-1_all.deb', 'lib/systemd/system/test-project.service')) def test_dh_virtualenv_when_extra_files_specified(self): # Given - command = [f'{SCRIPTS_DIR}/dh-virtualenv', TEST_PROJECT_ROOT, '-e', f'{TEST_PROJECT_ROOT}/scripts/*'] + command = [f'{RESOURCE_ROOT}/dh-virtualenv', TEST_PROJECT_ROOT, '-e', f'{TEST_PROJECT_ROOT}/scripts/*'] # When result = run_command(command) @@ -72,10 +76,22 @@ def test_dh_virtualenv_when_extra_files_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_PROJECT_ROOT}/dist/test-project_1.0.0-1_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) self.assertTrue(check_files_matches_in_deb(f'{TEST_PROJECT_ROOT}/dist/test-project_1.0.0-1_all.deb', [('postinst', 'test-project successfully installed'), ('postrm', 'test-project successfully removed')])) + def test_propagates_return_code_of_command(self): + # Given + command = [f'{RESOURCE_ROOT}/dh-virtualenv', TEST_PROJECT_ROOT, '-p', '/invalid/path'] + + # When + result = run_command(command) + + # Then + self.assertEqual(127, result.returncode) + self.assertEqual('', result.stdout) + if __name__ == '__main__': unittest.main() diff --git a/python/tests/fpmDebTest.py b/python/tests/fpmDebTest.py index d74542b..766cb2e 100644 --- a/python/tests/fpmDebTest.py +++ b/python/tests/fpmDebTest.py @@ -4,7 +4,7 @@ from unittest import TestCase from utils import TEST_PROJECT_ROOT, TEST_RESOURCE_ROOT, delete_directory, TEST_FILE_SYSTEM_ROOT, \ - SCRIPTS_DIR, run_command, check_file_is_in_deb, check_files_matches_in_deb + RESOURCE_ROOT, run_command, check_file_is_in_deb, check_files_matches_in_deb, check_files_exist class FpmDebTest(TestCase): @@ -16,7 +16,7 @@ def setUp(self): def test_fpm_deb_when_no_output_dir_specified(self): # Given - command = [f'{SCRIPTS_DIR}/fpm-deb', TEST_PROJECT_ROOT] + command = [f'{RESOURCE_ROOT}/fpm-deb', TEST_PROJECT_ROOT] # When result = run_command(command) @@ -24,11 +24,12 @@ def test_fpm_deb_when_no_output_dir_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_PROJECT_ROOT}/dist/python3-test-project_1.0.0_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_fpm_deb_when_relative_output_dir_specified(self): # Given output_dir = 'tests/test_root/etc/dist' if os.path.exists('tests') else 'test_root/etc/dist' - command = [f'{SCRIPTS_DIR}/fpm-deb', TEST_PROJECT_ROOT, '-o', output_dir] + command = [f'{RESOURCE_ROOT}/fpm-deb', TEST_PROJECT_ROOT, '-o', output_dir] # When result = run_command(command) @@ -36,10 +37,11 @@ def test_fpm_deb_when_relative_output_dir_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{os.getcwd()}/{output_dir}/python3-test-project_1.0.0_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_fpm_deb_when_absolute_output_dir_specified(self): # Given - command = [f'{SCRIPTS_DIR}/fpm-deb', TEST_PROJECT_ROOT, '-o', f'{TEST_FILE_SYSTEM_ROOT}/etc/dist'] + command = [f'{RESOURCE_ROOT}/fpm-deb', TEST_PROJECT_ROOT, '-o', f'{TEST_FILE_SYSTEM_ROOT}/etc/dist'] # When result = run_command(command) @@ -47,10 +49,11 @@ def test_fpm_deb_when_absolute_output_dir_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_FILE_SYSTEM_ROOT}/etc/dist/python3-test-project_1.0.0_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_fpm_deb_when_service_file_specified(self): # Given - command = [f'{SCRIPTS_DIR}/fpm-deb', TEST_PROJECT_ROOT, + command = [f'{RESOURCE_ROOT}/fpm-deb', TEST_PROJECT_ROOT, '-a', f'--deb-systemd {TEST_PROJECT_ROOT}/service/test-project.service'] # When @@ -59,12 +62,13 @@ def test_fpm_deb_when_service_file_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_PROJECT_ROOT}/dist/python3-test-project_1.0.0_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) self.assertTrue(check_file_is_in_deb(f'{TEST_PROJECT_ROOT}/dist/python3-test-project_1.0.0_all.deb', 'lib/systemd/system/test-project.service')) def test_fpm_deb_when_extra_files_specified(self): # Given - command = [f'{SCRIPTS_DIR}/fpm-deb', TEST_PROJECT_ROOT, + command = [f'{RESOURCE_ROOT}/fpm-deb', TEST_PROJECT_ROOT, '-a', f'--after-install {TEST_PROJECT_ROOT}/scripts/test-project.postinst'] # When @@ -73,9 +77,21 @@ def test_fpm_deb_when_extra_files_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_PROJECT_ROOT}/dist/python3-test-project_1.0.0_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) self.assertTrue(check_files_matches_in_deb(f'{TEST_PROJECT_ROOT}/dist/python3-test-project_1.0.0_all.deb', [('postinst', 'test-project successfully installed')])) + def test_propagates_return_code_of_command(self): + # Given + command = [f'{RESOURCE_ROOT}/fpm-deb', TEST_PROJECT_ROOT, '-p', '/invalid/path'] + + # When + result = run_command(command) + + # Then + self.assertEqual(1, result.returncode) + self.assertEqual('', result.stdout) + if __name__ == '__main__': unittest.main() diff --git a/python/tests/packPythonTest.py b/python/tests/packPythonTest.py index 1cc6e1a..efddda2 100644 --- a/python/tests/packPythonTest.py +++ b/python/tests/packPythonTest.py @@ -4,7 +4,7 @@ from unittest import TestCase from utils import TEST_PROJECT_ROOT, RESOURCE_ROOT, TEST_RESOURCE_ROOT, delete_directory, TEST_FILE_SYSTEM_ROOT, \ - run_command, create_file + run_command, create_file, check_files_exist class PackPythonTest(TestCase): @@ -24,6 +24,7 @@ def test_pack_python_when_packaging_default(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_PROJECT_ROOT}/dist/python3-test-project_1.0.0_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_pack_python_when_packaging_default_and_config_file_specified(self): # Given @@ -39,6 +40,7 @@ def test_pack_python_when_packaging_default_and_config_file_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_PROJECT_ROOT}/dist/test-project_1.0.0-1_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_pack_python_when_packaging_specified(self): # Given @@ -51,6 +53,7 @@ def test_pack_python_when_packaging_specified(self): self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_PROJECT_ROOT}/dist/test_project-1.0.0-py3-none-any.whl\n' f'{TEST_PROJECT_ROOT}/dist/python3-test-project_1.0.0_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_pack_python_when_packaging_all_and_no_output_dir_specified(self): # Given @@ -64,6 +67,7 @@ def test_pack_python_when_packaging_all_and_no_output_dir_specified(self): self.assertEqual(f'{TEST_PROJECT_ROOT}/dist/test_project-1.0.0-py3-none-any.whl\n' f'{TEST_PROJECT_ROOT}/dist/python3-test-project_1.0.0_all.deb\n' f'{TEST_PROJECT_ROOT}/dist/test-project_1.0.0-1_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_pack_python_when_packaging_all_and_relative_output_dir_specified(self): # Given @@ -78,6 +82,7 @@ def test_pack_python_when_packaging_all_and_relative_output_dir_specified(self): self.assertEqual(f'{os.getcwd()}/{output_dir}/test_project-1.0.0-py3-none-any.whl\n' f'{os.getcwd()}/{output_dir}/python3-test-project_1.0.0_all.deb\n' f'{os.getcwd()}/{output_dir}/test-project_1.0.0-1_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_pack_python_when_packaging_all_and_absolute_output_dir_specified(self): # Given @@ -92,6 +97,19 @@ def test_pack_python_when_packaging_all_and_absolute_output_dir_specified(self): self.assertEqual(f'{TEST_FILE_SYSTEM_ROOT}/etc/dist/test_project-1.0.0-py3-none-any.whl\n' f'{TEST_FILE_SYSTEM_ROOT}/etc/dist/python3-test-project_1.0.0_all.deb\n' f'{TEST_FILE_SYSTEM_ROOT}/etc/dist/test-project_1.0.0-1_all.deb\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) + + def test_propagates_return_code_of_command(self): + # Given + command = [f'{RESOURCE_ROOT}/pack_python', TEST_PROJECT_ROOT, '-o', f'{TEST_FILE_SYSTEM_ROOT}/etc/dist', + '--all', '-p', '/invalid/path'] + + # When + result = run_command(command) + + # Then + self.assertEqual(127, result.returncode) + self.assertEqual('', result.stdout) if __name__ == '__main__': diff --git a/python/tests/utils.py b/python/tests/utils.py index 4fc87f0..080ecce 100755 --- a/python/tests/utils.py +++ b/python/tests/utils.py @@ -4,12 +4,9 @@ from pathlib import Path TEST_RESOURCE_ROOT = str(Path(os.path.dirname(__file__)).absolute()) -TEST_FILE_SYSTEM_ROOT = str(Path(TEST_RESOURCE_ROOT).joinpath("test_root").absolute()) -TEST_PROJECT_ROOT = str( - Path(TEST_FILE_SYSTEM_ROOT).joinpath("etc").joinpath("test-project").absolute() -) +TEST_FILE_SYSTEM_ROOT = str(Path(TEST_RESOURCE_ROOT).joinpath('test_root').absolute()) +TEST_PROJECT_ROOT = str(Path(TEST_FILE_SYSTEM_ROOT).joinpath('etc').joinpath('test-project').absolute()) RESOURCE_ROOT = str(Path(TEST_RESOURCE_ROOT).parent.absolute()) -SCRIPTS_DIR = str(Path(RESOURCE_ROOT).absolute()) def delete_directory(directory: str) -> None: @@ -24,7 +21,7 @@ def create_directory(directory: str) -> None: def create_file(file: str, content: str) -> None: create_directory(os.path.dirname(file)) - with open(file, "w") as f: + with open(file, 'w') as f: f.write(content) @@ -33,40 +30,38 @@ def run_command(command: list[str]) -> subprocess.CompletedProcess[str]: command, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) - print("Return code:", result.returncode) + print('Return code:', result.returncode) if result.stdout: - print("Output:", result.stdout.rstrip("\n")) + print('Output:', result.stdout.rstrip('\n')) if result.stderr: - print("Error:", result.stderr.rstrip("\n")) + print('Error:', result.stderr.rstrip('\n')) print() return result +def check_files_exist(file_paths: str) -> bool: + for file_path in file_paths.splitlines(): + return os.path.isfile(file_path) + + def check_file_is_in_deb(deb_file_path: str, file_path: str) -> bool: - return ( - file_path - in subprocess.run( - ["dpkg", "-c", deb_file_path], text=True, stdout=subprocess.PIPE - ).stdout - ) + return file_path in subprocess.run(['dpkg', '-c', deb_file_path], text=True, stdout=subprocess.PIPE).stdout -def check_files_matches_in_deb( - deb_file_path: str, files_and_matchers: list[tuple[str, str]] -) -> bool: - temp_dir = f"{TEST_FILE_SYSTEM_ROOT}/tmp" +def check_files_matches_in_deb(deb_file_path: str, files_and_matchers: list[tuple[str, str]]) -> bool: + temp_dir = f'{TEST_FILE_SYSTEM_ROOT}/tmp' delete_directory(temp_dir) os.makedirs(temp_dir) - subprocess.run(["dpkg-deb", "-e", deb_file_path, temp_dir], check=True) + subprocess.run(['dpkg-deb', '-e', deb_file_path, temp_dir], check=True) all_matches = True for file_name, matcher in files_and_matchers: file_path = os.path.join(temp_dir, file_name) - with open(os.path.join(temp_dir, file_path), "r") as file: + with open(os.path.join(temp_dir, file_path), 'r') as file: content = file.read() matches = matcher in content all_matches = all_matches and matches diff --git a/python/tests/wheelTest.py b/python/tests/wheelTest.py index ee5c203..1d3051f 100644 --- a/python/tests/wheelTest.py +++ b/python/tests/wheelTest.py @@ -3,8 +3,8 @@ import unittest from unittest import TestCase -from utils import TEST_PROJECT_ROOT, TEST_RESOURCE_ROOT, TEST_FILE_SYSTEM_ROOT, SCRIPTS_DIR, delete_directory, \ - run_command +from utils import TEST_PROJECT_ROOT, TEST_RESOURCE_ROOT, TEST_FILE_SYSTEM_ROOT, RESOURCE_ROOT, delete_directory, \ + run_command, check_files_exist class WheelTest(TestCase): @@ -16,7 +16,7 @@ def setUp(self): def test_wheel_when_no_output_dir_specified(self): # Given - command = [f'{SCRIPTS_DIR}/wheel', TEST_PROJECT_ROOT] + command = [f'{RESOURCE_ROOT}/wheel', TEST_PROJECT_ROOT] # When result = run_command(command) @@ -24,11 +24,12 @@ def test_wheel_when_no_output_dir_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_PROJECT_ROOT}/dist/test_project-1.0.0-py3-none-any.whl\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_wheel_when_relative_output_dir_specified(self): # Given output_dir = 'tests/test_root/etc/dist' if os.path.exists('tests') else 'test_root/etc/dist' - command = [f'{SCRIPTS_DIR}/wheel', TEST_PROJECT_ROOT, '-o', output_dir] + command = [f'{RESOURCE_ROOT}/wheel', TEST_PROJECT_ROOT, '-o', output_dir] # When result = run_command(command) @@ -36,10 +37,11 @@ def test_wheel_when_relative_output_dir_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{os.getcwd()}/{output_dir}/test_project-1.0.0-py3-none-any.whl\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) def test_wheel_when_absolute_output_dir_specified(self): # Given - command = [f'{SCRIPTS_DIR}/wheel', TEST_PROJECT_ROOT, '-o', f'{TEST_FILE_SYSTEM_ROOT}/etc/dist'] + command = [f'{RESOURCE_ROOT}/wheel', TEST_PROJECT_ROOT, '-o', f'{TEST_FILE_SYSTEM_ROOT}/etc/dist'] # When result = run_command(command) @@ -47,6 +49,18 @@ def test_wheel_when_absolute_output_dir_specified(self): # Then self.assertEqual(0, result.returncode) self.assertEqual(f'{TEST_FILE_SYSTEM_ROOT}/etc/dist/test_project-1.0.0-py3-none-any.whl\n', result.stdout) + self.assertTrue(check_files_exist(result.stdout)) + + def test_propagates_return_code_of_command(self): + # Given + command = [f'{RESOURCE_ROOT}/wheel', TEST_PROJECT_ROOT, '-p', '/invalid/path'] + + # When + result = run_command(command) + + # Then + self.assertEqual(127, result.returncode) + self.assertEqual('', result.stdout) if __name__ == '__main__': diff --git a/python/wheel b/python/wheel index 20722ca..4f5f625 100755 --- a/python/wheel +++ b/python/wheel @@ -4,12 +4,8 @@ # SPDX-FileCopyrightText: 2024 Attila Gombos # SPDX-License-Identifier: MIT -import sys from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Namespace -from os.path import abspath, dirname -from pathlib import Path - -sys.path.insert(0, dirname(abspath(__file__))) +from os.path import abspath from common import check_workspace, run_command @@ -21,37 +17,29 @@ def main() -> None: check_workspace(workspace_dir) - command = [arguments.python_bin, "setup.py", "bdist_wheel"] + command = [arguments.python_bin, 'setup.py', 'bdist_wheel'] - output_dir = f"{workspace_dir}/dist" + output_dir = f'{workspace_dir}/dist' if arguments.output_dir: output_dir = abspath(arguments.output_dir) - command.extend(["--dist-dir", output_dir]) + command.extend(['--dist-dir', output_dir]) results = run_command(workspace_dir, command, r".*'(.+\.whl)'") for result in results: - if not result.startswith("/"): - result = ( - f"{output_dir}/{result}" - if arguments.output_dir - else f"{workspace_dir}/{result}" - ) + if not result.startswith('/'): + result = f'{output_dir}/{result}' if arguments.output_dir else f'{workspace_dir}/{result}' print(result) def _get_arguments() -> Namespace: parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) - parser.add_argument( - "-p", "--python-bin", help="python executable to use", default="python3" - ) - parser.add_argument("-o", "--output-dir", help="package output directory") - parser.add_argument( - "workspace_dir", help="workspace directory where setup.py is located" - ) + parser.add_argument('-p', '--python-bin', help='python executable to use', default='python3') + parser.add_argument('-o', '--output-dir', help='package output directory') + parser.add_argument('workspace_dir', help='workspace directory where setup.py is located') return parser.parse_args() -if __name__ == "__main__": +if __name__ == '__main__': main()