From 1842e63591ae2101f8bd9847341d7dbb07666976 Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Sun, 10 Sep 2023 16:10:59 -0400 Subject: [PATCH] Add command line option for running 6S binary in subprocess --- src/sixs_bin/_cli.py | 15 +++++++++++++++ test/test_cli.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/sixs_bin/_cli.py b/src/sixs_bin/_cli.py index 1eedad3..9d43190 100644 --- a/src/sixs_bin/_cli.py +++ b/src/sixs_bin/_cli.py @@ -17,6 +17,7 @@ import argparse import pathlib +import subprocess import sys import sixs_bin @@ -45,6 +46,13 @@ def _make_parser() -> argparse.ArgumentParser: choices=sixs_bin._SIXS_BINARIES.keys(), help="Print the path to the specified 6S executable from this package's resources.", ) + command_group.add_argument( + "--exec", + choices=sixs_bin._SIXS_BINARIES.keys(), + help="Execute specified 6S executable in a subprocess, inheriting stdin and stdout. This option is provided" + " as a convenience, but its use is not recommended. Running 6S using this option is about 5%% slower than" + " executing the binary directly, due the overhead of starting the Python interpreter and subprocess.", + ) command_group.add_argument( "--test-wrapper", action="store_true", @@ -68,6 +76,13 @@ def main(cli_args: list[str]) -> None: print(_make_version()) return + if args.exec is not None: + proc_args = (sixs_bin.get_path(args.exec),) + ret_code = subprocess.Popen(proc_args).wait() + if ret_code: + raise subprocess.CalledProcessError(ret_code, proc_args) + return + if args.path is not None: print(sixs_bin.get_path(args.path)) return diff --git a/test/test_cli.py b/test/test_cli.py index 94e08ca..7b635e6 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -1,9 +1,27 @@ +import subprocess +import sys +from typing import Any + import pytest import sixs_bin import sixs_bin._cli as sixs_cli +def _run_self_subprocess( + cli_args: list[str], **kwargs: Any +) -> subprocess.CompletedProcess: + """ + Run the command line interface in a subprocess. + + Needed instead of calling the CLI entrypoint directly when more advanced control + over the process running the command line interface is required. + """ + return subprocess.run( + [sys.executable, "-m", sixs_bin.__name__, *cli_args], **kwargs + ) + + def test_version(capsys) -> None: sixs_cli.main(["--version"]) @@ -29,5 +47,31 @@ def test_bad_path() -> None: sixs_cli.main(["--path"]) +@pytest.mark.parametrize("version", sixs_bin._SIXS_BINARIES.keys()) +def test_exec_eof(version) -> None: + with pytest.raises(subprocess.CalledProcessError) as exec_info: + # Run in subprocess to reliably control stdin. + _run_self_subprocess( + ["--exec", version], + check=True, + capture_output=True, + text=True, + input="", # Empty stdin + ) + + assert exec_info.value.stdout == "" + assert ( + exec_info.value.stderr.splitlines()[1] == "Fortran runtime error: End of file" + ) + + +def test_exec_bad() -> None: + with pytest.raises(SystemExit): + sixs_cli.main(["--exec", "does-not-exist"]) + + with pytest.raises(SystemExit): + sixs_cli.main(["--exec"]) + + def test_test_wrapper() -> None: sixs_cli.main(["--test-wrapper"])