From 2238f7a8abc3a391bce165b902ff16a6e38e571e Mon Sep 17 00:00:00 2001 From: helle Date: Thu, 17 Apr 2025 02:38:25 +0200 Subject: [PATCH] Replace existing external command/custom sub command tests with functional2 tests This removes the bash dependency issue that the previous test had, the tests are translated roughly 1 to 1 and do not address any of the TOD list items. Closes #800, also superceeds I81817b9fe4a5eeb019f7e04b5a60e091bbde26b2 Change-Id: Ie440cd0a602d4ca4a48edcaf517b7358ec572710 --- tests/functional/external-commands.sh | 68 ---------- tests/functional/meson.build | 1 - tests/functional2/commands/__init__.py | 0 .../commands/test_custom_sub_commands.py | 126 ++++++++++++++++++ 4 files changed, 126 insertions(+), 69 deletions(-) delete mode 100644 tests/functional/external-commands.sh create mode 100644 tests/functional2/commands/__init__.py create mode 100644 tests/functional2/commands/test_custom_sub_commands.py diff --git a/tests/functional/external-commands.sh b/tests/functional/external-commands.sh deleted file mode 100644 index 5891457fd..000000000 --- a/tests/functional/external-commands.sh +++ /dev/null @@ -1,68 +0,0 @@ -source common.sh - -cd "$TEST_ROOT" - -# To start off, we will produce some custom binaries that redirects to known functionality. -EXTRA_BINARIES_DIR=$(mktemp -d) -# trap "rm -rf $EXTRA_BINARIES_DIR" EXIT - -# Create an extra command as an alias of an existing one. -create_extra_command() { - local existing_command="$1" - local script_path="$EXTRA_BINARIES_DIR/lix-$existing_command" - - cat > "$script_path" <$TEST_ROOT/stdout 2>$TEST_ROOT/stderr - - # Test that an external subcommand can be run successfully. - expect 0 lix --extra-experimental-features 'lix-custom-sub-commands' copy-closure --version 1>/dev/null - expect 0 lix --extra-experimental-features 'lix-custom-sub-commands' collect-garbage --version 1>/dev/null - - # Test that the external subcommand can be run beyond `--help` processing successfully. - expect 0 lix --extra-experimental-features 'lix-custom-sub-commands' collect-garbage --dry-run 1>/dev/null - - # Test that an external subcommand without the experimental flag will fail - # without mentioning the experimental feature. - expect 1 lix copy-closure --help 1>$TEST_ROOT/stdout 2>$TEST_ROOT/stderr -} - -testSimpleExternalCommands -# Test that an external subcommand can be run successfully with a slightly modified PATH. -( - # Split the PATH into its first component and the rest - FIRST_PATH=${PATH%%:*} # The first directory - REST_PATH=${PATH#*:} # The rest of the PATH - - echo "modified PATH: $PATH" - # Rebuild PATH with the first directory moved to the second position - export PATH=$(echo $REST_PATH | cut -d: -f1):$FIRST_PATH:$(echo $REST_PATH | cut -d: -s -f2-) - - testSimpleExternalCommands -) - -# TODO: Test flags handling. - -# TODO: Short, long and multiple flags should be tested as well. -# TODO: `--` special flag? -# TODO: test positional arguments, but only `nix-copy-closure` implements some and it's pesky to test here. diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 1c15e079c..e5036e1a4 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -200,7 +200,6 @@ functional_tests_scripts = [ 'substitute-truncated-nar.sh', 'regression-484.sh', 'regression-reference-checks.sh', - 'external-commands.sh', 'redirected-filter-source.sh', ] diff --git a/tests/functional2/commands/__init__.py b/tests/functional2/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/functional2/commands/test_custom_sub_commands.py b/tests/functional2/commands/test_custom_sub_commands.py new file mode 100644 index 000000000..47a6c2002 --- /dev/null +++ b/tests/functional2/commands/test_custom_sub_commands.py @@ -0,0 +1,126 @@ +import os +import stat +import sys + +import pytest + +from pathlib import Path + +from textwrap import dedent +from functional2.testlib.fixtures import Nix + +COMMANDS = ["copy-closure", "collect-garbage"] + + +@pytest.fixture(scope="module") +def custom_sub_command_path(tmp_path_factory: pytest.TempPathFactory) -> Path: + return tmp_path_factory.mktemp(__name__) + + +@pytest.fixture(scope="module") +def failing_sub_command_path(tmp_path_factory: pytest.TempPathFactory) -> Path: + return tmp_path_factory.mktemp(__name__ + "-failing") + + +@pytest.fixture(scope="module", params=COMMANDS) +def custom_sub_command(request: pytest.FixtureRequest, custom_sub_command_path: Path) -> str: + # Create an external command that is an alias of an existing one + command = request.param + + executable = custom_sub_command_path / f"lix-{command}" + executable.write_text(dedent(f"""\ + #!{sys.executable} + import os, sys + # Start with args[0] set to the actual nix command used for testing + # as we are not making Lix variants of those. + os.execvp("nix-{command}", [ "nix-{command}" ] + sys.argv[1:]) + """)) + executable.chmod(stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR) + + return command + + +@pytest.fixture(scope="module") +def failing_sub_command(failing_sub_command_path: Path, custom_sub_command: str) -> str: + # Create an external command that will intentionally cause an error + executable = failing_sub_command_path / f"lix-{custom_sub_command}" + executable.write_text(dedent(f"""\ + #!{sys.executable} + import sys + sys.exit(42) + """)) + executable.chmod(stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR) + + return custom_sub_command + + +@pytest.fixture +def path(custom_sub_command_path: Path) -> str: + # Provide a modified search path + search_path = os.environ.get("PATH").split(":") + search_path += ["/some/incorrect", "/location/for/fun", str(custom_sub_command_path)] + + return ":".join(search_path) + + +@pytest.fixture +def path_with_failure(request: pytest.FixtureRequest, custom_sub_command_path: Path, + failing_sub_command_path: Path) -> str: + # Provide a search path with failing binaries inserted in the specified position + (first, fail) = request.param + search_path = os.environ.get("PATH").split(":") + + command_path = [str(custom_sub_command_path), str(failing_sub_command_path)] + if fail: + command_path.reverse() + + if first: + search_path = command_path + search_path + else: + search_path += command_path + + return ":".join(search_path) + + +@pytest.mark.parametrize("nix_exe, flag, expected", [("nix", False, 1), + ("lix", False, 1), + ("nix", True, 1), + ("lix", True, 0)]) +def test_sub_commands(nix: Nix, path: str, custom_sub_command: str, nix_exe: str, + flag: bool, expected: int): + # Test custom sub commands in various configurations + nix_command = nix.nix([custom_sub_command, "--version"], nix_exe=nix_exe) + nix_command.with_env(PATH=path) + if flag: + nix_command.settings.feature("lix-custom-sub-commands") + + nix_command.run().expect(expected) + + +@pytest.mark.parametrize("path_with_failure, expected", [((True, True), 42), + ((True, False), 0), + ((False, True), 42), + ((False, False), 0)], + indirect=["path_with_failure"]) +def test_sub_command_path_order(nix: Nix, path_with_failure: str, failing_sub_command: str, + expected: int): + # Test handling of the order of the path for custom sub commands + # Incidentally also tests passing through exit codes + nix_command = nix.nix([failing_sub_command, "--version"], nix_exe="lix") + nix_command.with_env(PATH=path_with_failure) + nix_command.settings.feature("lix-custom-sub-commands") + + nix_command.run().expect(expected) + + +@pytest.mark.skip(reason="TODO: we do not support auto completion for custom sub commands for now") +def test_custom_sub_commands_auto_completion(nix: Nix, tmp_path: Path): + pass + + +@pytest.mark.skip(reason="TODO: we do not test flag handling for custom sub commands for now") +def test_custom_sub_command_flag_handling(nix: Nix, tmp_path: Path): + # TODO: Short, long and multiple flags should be tested as well. + # TODO: `--` special flag + # TODO: test positional arguments, but only `nix-copy-closure` implements some and it's pesky to test here. + pass