From 1674abbfaa8226d47f708f31c9f4f8d489baa3ee Mon Sep 17 00:00:00 2001 From: Alyssa Cote Date: Tue, 9 Apr 2024 16:21:18 -0700 Subject: [PATCH 01/11] drop python 3.8 --- .github/workflows/release.yml | 2 +- .github/workflows/run_tests.yml | 4 +- doc/installation_instructions/basic.rst | 10 +- pyproject.toml | 2 +- setup.cfg | 3 +- smartsim/__init__.py | 4 +- smartsim/_core/_install/buildenv.py | 2 +- smartsim/_core/utils/helpers.py | 5 +- tests/test_cli.py | 860 ------------------------ 9 files changed, 14 insertions(+), 878 deletions(-) delete mode 100644 tests/test_cli.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ad9a55e03..ad711675a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -98,7 +98,7 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.8' + python-version: '3.9' - name: Build sdist run: | diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index b1f007319..a635537d4 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -57,12 +57,10 @@ jobs: os: [macos-12, macos-14, ubuntu-20.04] # Operating systems compiler: [8] # GNU compiler version rai: [1.2.7] # Redis AI versions - py_v: ["3.8", "3.9", "3.10", "3.11"] # Python versions + py_v: ["3.9", "3.10", "3.11"] # Python versions exclude: - os: macos-14 py_v: "3.9" - - os: macos-14 - py_v: "3.8" env: SMARTSIM_REDISAI: ${{ matrix.rai }} diff --git a/doc/installation_instructions/basic.rst b/doc/installation_instructions/basic.rst index 75b099ad5..905519f6f 100644 --- a/doc/installation_instructions/basic.rst +++ b/doc/installation_instructions/basic.rst @@ -20,7 +20,7 @@ Basic The base prerequisites to install SmartSim and SmartRedis are: - - Python 3.8-3.11 + - Python 3.9-3.11 - Pip - Cmake 3.13.x (or later) - C compiler @@ -74,11 +74,11 @@ Supported Versions * - MacOS - x86_64, aarch64 - Not supported - - 3.8 - 3.11 + - 3.9 - 3.11 * - Linux - x86_64 - Nvidia - - 3.8 - 3.11 + - 3.9 - 3.11 .. note:: @@ -256,9 +256,9 @@ SmartSim does. * - Platform - Python Versions * - MacOS - - 3.8 - 3.11 + - 3.9 - 3.11 * - Linux - - 3.8 - 3.11 + - 3.9 - 3.11 The Python client for SmartRedis is installed through ``pip`` as follows: diff --git a/pyproject.toml b/pyproject.toml index 72cc378d4..6ea070d67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 88 -target-version = ['py38', 'py39', 'py310'] +target-version = ['py39', 'py310'] exclude = ''' ( | \.egg diff --git a/setup.cfg b/setup.cfg index 5fdfa82ae..ba6606f7f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,7 +42,6 @@ contact_email = craylabs@hpe.com license = BSD 2-Clause License keywords = scientific, ai, workflow, hpc, analysis classifiers = - Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 @@ -56,7 +55,7 @@ setup_requires = setuptools>=39.2 cmake>=3.13 include_package_data = True -python_requires = >=3.8,<3.12 +python_requires = >=3.9,<3.12 [options.packages.find] include = diff --git a/smartsim/__init__.py b/smartsim/__init__.py index 7c1fa2fe0..5e24097a5 100644 --- a/smartsim/__init__.py +++ b/smartsim/__init__.py @@ -30,8 +30,8 @@ # pylint: disable-next=useless-import-alias from .version import __version__ as __version__ -if sys.version_info < (3, 8): # pragma: no cover - sys.exit("Python 3.8 or greater must be used with SmartSim.") +if sys.version_info < (3, 9): # pragma: no cover + sys.exit("Python 3.9 or greater must be used with SmartSim.") # Main API module # pylint: disable=wrong-import-position diff --git a/smartsim/_core/_install/buildenv.py b/smartsim/_core/_install/buildenv.py index cbf29c4b5..476d0374c 100644 --- a/smartsim/_core/_install/buildenv.py +++ b/smartsim/_core/_install/buildenv.py @@ -267,7 +267,7 @@ class Versioner: """ # compatible Python version - PYTHON_MIN = Version_("3.8.0") + PYTHON_MIN = Version_("3.9.0") # Versions SMARTSIM = Version_(get_env("SMARTSIM_VERSION", "0.6.2")) diff --git a/smartsim/_core/utils/helpers.py b/smartsim/_core/utils/helpers.py index b9e79e250..4a1e56e35 100644 --- a/smartsim/_core/utils/helpers.py +++ b/smartsim/_core/utils/helpers.py @@ -312,10 +312,9 @@ def decode_cmd(encoded_cmd: str) -> t.List[str]: return cleaned_cmd -# TODO: Remove the ``type: ignore`` comment here when Python 3.8 support is dropped -# ``collections.abc.Collection`` is not subscriptable until Python 3.9 + @t.final -class SignalInterceptionStack(collections.abc.Collection): # type: ignore[type-arg] +class SignalInterceptionStack(collections.abc.Collection): """Registers a stack of unique callables to be called when a signal is received before calling the original signal handler. """ diff --git a/tests/test_cli.py b/tests/test_cli.py deleted file mode 100644 index 710a9a659..000000000 --- a/tests/test_cli.py +++ /dev/null @@ -1,860 +0,0 @@ -# BSD 2-Clause License -# -# Copyright (c) 2021-2024, Hewlett Packard Enterprise -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import argparse -import logging -import os -import pathlib -import typing as t -from contextlib import contextmanager - -import pytest - -import smartsim -from smartsim._core._cli import build, cli, plugin -from smartsim._core._cli.build import configure_parser as build_parser -from smartsim._core._cli.build import execute as build_execute -from smartsim._core._cli.clean import configure_parser as clean_parser -from smartsim._core._cli.clean import execute as clean_execute -from smartsim._core._cli.clean import execute_all as clobber_execute -from smartsim._core._cli.dbcli import execute as dbcli_execute -from smartsim._core._cli.site import execute as site_execute -from smartsim._core._cli.utils import MenuItemConfig -from smartsim._core._cli.validate import configure_parser as validate_parser -from smartsim._core._cli.validate import execute as validate_execute - -# The tests in this file belong to the group_a group -pytestmark = pytest.mark.group_a - -_TEST_LOGGER = logging.getLogger(__name__) - -try: - import smartdashboard -except: - test_dash_plugin = False -else: - test_dash_plugin = True - - -def mock_execute_custom(msg: str = None, good: bool = True) -> int: - retval = 0 if good else 1 - print(msg) - return retval - - -def mock_execute_good( - _ns: argparse.Namespace, _unparsed: t.Optional[t.List[str]] = None -) -> int: - return mock_execute_custom("GOOD THINGS", good=True) - - -def mock_execute_fail( - _ns: argparse.Namespace, _unparsed: t.Optional[t.List[str]] = None -) -> int: - return mock_execute_custom("BAD THINGS", good=False) - - -def test_cli_default_args_parsing(capsys): - """Test default parser behaviors with no subparsers""" - menu: t.List[cli.MenuItemConfig] = [] - smart_cli = cli.SmartCli(menu) - - captured = capsys.readouterr() # throw away existing output - - with pytest.raises(SystemExit) as e: - # the parser shouldn't get the `smart` CLI argument - build_args = ["smart", "-h"] - smart_cli.parser.parse_args(build_args) - - captured = capsys.readouterr() - assert "invalid choice: 'smart'" in captured.err - assert e.value.code == 2 - - -def test_cli_invalid_command(capsys): - """Ensure the response when an unsupported command is given""" - exp_help = "this is my mock help text for build" - exp_cmd = "build" - actual_cmd = f"not{exp_cmd}" - menu = [ - cli.MenuItemConfig(exp_cmd, exp_help, mock_execute_good, build.configure_parser) - ] - smart_cli = cli.SmartCli(menu) - - captured = capsys.readouterr() # throw away existing output - with pytest.raises(SystemExit) as e: - build_args = [actual_cmd, "-h"] - smart_cli.parser.parse_args(build_args) - - captured = capsys.readouterr() # capture new output - - # show that the command was not recognized - assert "invalid choice" in captured.err - assert e.value.code == 2 - - -def test_cli_bad_default_args_parsing_bad_help(capsys): - """Test passing an argument name that is incorrect""" - menu: t.List[cli.MenuItemConfig] = [] - smart_cli = cli.SmartCli(menu) - - captured = capsys.readouterr() # throw away existing output - with pytest.raises(SystemExit) as e: - build_args = ["--halp"] # <-- HELP vs HALP - smart_cli.parser.parse_args(build_args) - - captured = capsys.readouterr() # capture new output - - assert "smart: error:" in captured.err - assert e.value.code == 2 - - -def test_cli_bad_default_args_parsing_good_help(capsys): - """Test passing an argument name that is correct""" - menu: t.List[cli.MenuItemConfig] = [] - smart_cli = cli.SmartCli(menu) - - captured = capsys.readouterr() # throw away existing output - with pytest.raises(SystemExit) as e: - build_args = ["-h"] - smart_cli.parser.parse_args(build_args) - - captured = capsys.readouterr() # capture new output - - assert "smart: error:" not in captured.out - assert "usage: smart" in captured.out - assert e.value.code == 0 - - -def test_cli_add_subparser(capsys): - """Test that passing configuration for a command causes the command - to be added to the CLI - """ - exp_help = "this is my mock help text for build" - exp_cmd = "build" - menu = [ - cli.MenuItemConfig(exp_cmd, exp_help, mock_execute_good, build.configure_parser) - ] - smart_cli = cli.SmartCli(menu) - - captured = capsys.readouterr() # throw away existing output - with pytest.raises(SystemExit) as e: - build_args = [exp_cmd, "-h"] # <--- -h only - smart_cli.parser.parse_args(build_args) - - captured = capsys.readouterr() # capture new output - - # show that -h showed the expected help text - assert exp_help in captured.out - assert e.value.code == 0 - - captured = capsys.readouterr() # throw away existing output - with pytest.raises(SystemExit) as e: - build_args = [exp_cmd, "--help"] - smart_cli.parser.parse_args(build_args) - - captured = capsys.readouterr() # capture new output - - # show that --help ALSO works - assert exp_help in captured.out - assert e.value.code == 0 - - -def test_cli_subparser_selection(capsys): - """Ensure the right subparser is selected""" - exp_a_help = "this is my mock help text for dbcli" - exp_a_cmd = "dbcli" - - exp_b_help = "this is my mock help text for build" - exp_b_cmd = "build" - - menu = [ - cli.MenuItemConfig( - exp_a_cmd, exp_a_help, mock_execute_good, build.configure_parser - ), - cli.MenuItemConfig( - exp_b_cmd, exp_b_help, mock_execute_good, build.configure_parser - ), - ] - smart_cli = cli.SmartCli(menu) - - captured = capsys.readouterr() # throw away existing output - with pytest.raises(SystemExit) as e: - build_args = [exp_a_cmd, "-h"] # <--- -h only - smart_cli.parser.parse_args(build_args) - - captured = capsys.readouterr() # capture new output - - # show that -h showed the expected help text for `smart dbcli -h` - assert exp_a_help in captured.out - assert e.value.code == 0 - - captured = capsys.readouterr() # throw away existing output - with pytest.raises(SystemExit) as e: - build_args = [exp_b_cmd, "--help"] - smart_cli.parser.parse_args(build_args) - - captured = capsys.readouterr() # capture new output - - # show that -h showed the expected help text for `smart build -h` - assert exp_b_help in captured.out - assert e.value.code == 0 - - -def test_cli_command_execution(capsys): - """Ensure the right command is executed""" - exp_a_help = "this is my mock help text for dbcli" - exp_a_cmd = "dbcli" - - exp_b_help = "this is my mock help text for build" - exp_b_cmd = "build" - - dbcli_exec = lambda x, y: mock_execute_custom(msg="Database", good=True) - build_exec = lambda x, y: mock_execute_custom(msg="Builder", good=True) - - menu = [ - cli.MenuItemConfig(exp_a_cmd, exp_a_help, dbcli_exec, lambda x: None), - cli.MenuItemConfig(exp_b_cmd, exp_b_help, build_exec, lambda x: None), - ] - smart_cli = cli.SmartCli(menu) - - captured = capsys.readouterr() # throw away existing output - - build_args = ["smart", exp_a_cmd] - ret_val = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - # show that `smart dbcli` calls the build parser and build execute function - assert "Database" in captured.out - assert ret_val == 0 - - build_args = ["smart", exp_b_cmd] - ret_val = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - # show that `smart build` calls the build parser and build execute function - assert "Builder" in captured.out - assert ret_val == 0 - - -def test_cli_default_cli(capsys): - """Ensure the default CLI supports expected top-level commands""" - smart_cli = cli.default_cli() - - captured = capsys.readouterr() # throw away existing output - - # execute with no argument, expect full help text - build_args = ["smart"] - ret_val = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - # show that `smart dbcli` calls the build parser and build execute function - assert "usage: smart [-h] " in captured.out - assert "Available commands" in captured.out - assert ret_val == os.EX_USAGE - - # execute with `build` argument, expect build-specific help text - with pytest.raises(SystemExit) as e: - build_args = ["smart", "build", "-h"] - ret_val = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - assert "usage: smart build [-h]" in captured.out - assert "Build SmartSim dependencies" in captured.out - assert "optional arguments:" in captured.out or "options:" in captured.out - assert ret_val == os.EX_USAGE - - # execute with `clean` argument, expect clean-specific help text - with pytest.raises(SystemExit) as e: - build_args = ["smart", "clean", "-h"] - ret_val = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - assert "usage: smart clean [-h]" in captured.out - assert "Remove previous ML runtime installation" in captured.out - assert "optional arguments:" in captured.out or "options:" in captured.out - assert "--clobber" in captured.out - assert ret_val == os.EX_USAGE - - # execute with `dbcli` argument, expect dbcli-specific help text - with pytest.raises(SystemExit) as e: - build_args = ["smart", "dbcli", "-h"] - ret_val = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - assert "usage: smart dbcli [-h]" in captured.out - assert "Print the path to the redis-cli binary" in captured.out - assert "optional arguments:" in captured.out or "options:" in captured.out - assert ret_val == os.EX_USAGE - - # execute with `site` argument, expect site-specific help text - with pytest.raises(SystemExit) as e: - build_args = ["smart", "site", "-h"] - ret_val = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - assert "usage: smart site [-h]" in captured.out - assert "Print the installation site of SmartSim" in captured.out - assert "optional arguments:" in captured.out or "options:" in captured.out - assert ret_val == os.EX_USAGE - - # execute with `clobber` argument, expect clobber-specific help text - with pytest.raises(SystemExit) as e: - build_args = ["smart", "clobber", "-h"] - ret_val = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - assert "usage: smart clobber [-h]" in captured.out - assert "Remove all previous dependency installations" in captured.out - assert "optional arguments:" in captured.out or "options:" in captured.out - # assert "--clobber" not in captured.out - assert ret_val == os.EX_USAGE - - -@pytest.mark.skipif(not test_dash_plugin, reason="plugin not found") -def test_cli_plugin_dashboard(capfd): - """Ensure expected dashboard CLI plugin commands are supported""" - smart_cli = cli.default_cli() - capfd.readouterr() # throw away existing output - - # execute with `dashboard` argument, expect dashboard-specific help text - build_args = ["smart", "dashboard", "-h"] - rc = smart_cli.execute(build_args) - - captured = capfd.readouterr() # capture new output - - assert "[-d DIRECTORY]" in captured.out - assert "[-p PORT]" in captured.out - - assert "optional arguments:" in captured.out - assert rc == 0 - - -def test_cli_plugin_invalid( - monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture -): - """Ensure unexpected CLI plugins are reported""" - import smartsim._core._cli.cli - import smartsim._core._cli.plugin - - plugin_module = "notinstalled.Experiment_Overview" - bad_plugins = [ - lambda: MenuItemConfig( - "dashboard", - "Start the SmartSim dashboard", - plugin.dynamic_execute(plugin_module, "Dashboard!"), - is_plugin=True, - ) - ] - monkeypatch.setattr(smartsim._core._cli.cli, "plugins", bad_plugins) - # Coloredlogs doesn't play nice with caplog - monkeypatch.setattr( - smartsim._core._cli.plugin, - "_LOGGER", - _TEST_LOGGER, - ) - - smart_cli = cli.default_cli() - - # execute with `dashboard` argument, expect failure to find dashboard plugin - build_args = ["smart", "dashboard", "-h"] - - rc = smart_cli.execute(build_args) - - assert plugin_module in caplog.text - assert "not found" in caplog.text - assert rc == os.EX_CONFIG - - -# fmt: off -@pytest.mark.parametrize( - "command,mock_location,exp_output", - [ - pytest.param("build", "build_execute", "mocked-build", id="ensure build action is executed"), - pytest.param("clean", "clean_execute", "mocked-clean", id="ensure clean action is executed"), - pytest.param("dbcli", "dbcli_execute", "mocked-dbcli", id="ensure dbcli action is executed"), - pytest.param("site", "site_execute", "mocked-site", id="ensure site action is executed"), - pytest.param("clobber", "clobber_execute", "mocked-clobber", id="ensure clobber action is executed"), - pytest.param("validate", "validate_execute", "mocked-validate", id="ensure validate action is executed"), - pytest.param("info", "info_execute", "mocked-validate", id="ensure info action is executed"), - ] -) -# fmt: on -def test_cli_action(capsys, monkeypatch, command, mock_location, exp_output): - """Ensure the default CLI executes the build action""" - - def mock_execute(ns: argparse.Namespace, _unparsed: t.Optional[t.List[str]] = None): - print(exp_output) - return 0 - - monkeypatch.setattr(smartsim._core._cli.cli, mock_location, mock_execute) - - smart_cli = cli.default_cli() - - captured = capsys.readouterr() # throw away existing output - - # execute with `` argument, expect -specific output text - build_args = ["smart", command] - ret_val = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - assert exp_output in captured.out - assert ret_val == 0 - - -# fmt: off -@pytest.mark.parametrize( - "command,mock_location,exp_output,optional_arg,exp_valid,exp_err_msg,check_prop,exp_prop_val", - [ - pytest.param("build", "build_execute", "verbose mocked-build", "-v", True, "", "v", True, id="verbose 'on'"), - pytest.param("build", "build_execute", "cpu mocked-build", "--device=cpu", True, "", "device", "cpu", id="device 'cpu'"), - pytest.param("build", "build_execute", "gpu mocked-build", "--device=gpu", True, "", "device", "gpu", id="device 'gpu'"), - pytest.param("build", "build_execute", "gpuX mocked-build", "--device=gpux", False, "invalid choice: 'gpux'", "", "", id="set bad device 'gpuX'"), - pytest.param("build", "build_execute", "no tensorflow mocked-build", "--no_tf", True, "", "no_tf", True, id="set no TF"), - pytest.param("build", "build_execute", "no torch mocked-build", "--no_pt", True, "", "no_pt", True, id="set no torch"), - pytest.param("build", "build_execute", "onnx mocked-build", "--onnx", True, "", "onnx", True, id="set w/onnx"), - pytest.param("build", "build_execute", "torch-dir mocked-build", "--torch_dir /foo/bar", True, "", "torch_dir", "/foo/bar", id="set torch dir"), - pytest.param("build", "build_execute", "bad-torch-dir mocked-build", "--torch_dir", False, "error: argument --torch_dir", "", "", id="set torch dir, no path"), - pytest.param("build", "build_execute", "keydb mocked-build", "--keydb", True, "", "keydb", True, id="keydb on"), - pytest.param("clean", "clean_execute", "clobbering mocked-clean", "--clobber", True, "", "clobber", True, id="clean w/clobber"), - pytest.param("validate", "validate_execute", "port mocked-validate", "--port=12345", True, "", "port", 12345, id="validate w/ manual port"), - pytest.param("validate", "validate_execute", "abbrv port mocked-validate", "-p 12345", True, "", "port", 12345, id="validate w/ manual abbreviated port"), - pytest.param("validate", "validate_execute", "cpu mocked-validate", "--device=cpu", True, "", "device", "cpu", id="validate: device 'cpu'"), - pytest.param("validate", "validate_execute", "gpu mocked-validate", "--device=gpu", True, "", "device", "gpu", id="validate: device 'gpu'"), - pytest.param("validate", "validate_execute", "gpuX mocked-validate", "--device=gpux", False, "invalid choice: 'gpux'", "", "", id="validate: set bad device 'gpuX'"), - ] -) -# fmt: on -def test_cli_optional_args( - capsys, - monkeypatch, - command: str, - mock_location: str, - exp_output: str, - optional_arg: str, - exp_valid: bool, - exp_err_msg: str, - check_prop: str, - exp_prop_val: t.Any, -): - """Ensure the parser for a command handles expected optional arguments""" - - def mock_execute(ns: argparse.Namespace, _unparsed: t.Optional[t.List[str]] = None): - print(exp_output) - return 0 - - monkeypatch.setattr(smartsim._core._cli.cli, mock_location, mock_execute) - - smart_cli = cli.default_cli() - - captured = capsys.readouterr() # throw away existing output - - build_args = ["smart", command] + optional_arg.split() - if exp_valid: - ret_val = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - assert exp_output in captured.out # did the expected execution method occur? - assert ret_val == 0 # is the retval is non-failure code? - else: - with pytest.raises(SystemExit) as e: - ret_val = smart_cli.execute(build_args) - assert ret_val > 0 - - captured = capsys.readouterr() # capture new output - assert exp_err_msg in captured.err - - -# fmt: off -@pytest.mark.parametrize( - "command,mock_location,mock_output,exp_output", - [ - pytest.param("build", "build_execute", "verbose mocked-build", "usage: smart build", id="build"), - pytest.param("clean", "clean_execute", "helpful mocked-clean", "usage: smart clean", id="clean"), - pytest.param("clobber", "clean_execute", "helpful mocked-clobber", "usage: smart clobber", id="clobber"), - pytest.param("dbcli", "clean_execute", "helpful mocked-dbcli", "usage: smart dbcli", id="dbcli"), - pytest.param("site", "clean_execute", "helpful mocked-site", "usage: smart site", id="site"), - pytest.param("validate", "validate_execute", "helpful mocked-validate", "usage: smart validate", id="validate"), - pytest.param("info", "info_execute", "helpful mocked-validate", "usage: smart info", id="info"), - ] -) -# fmt: on -def test_cli_help_support( - capsys, - monkeypatch, - command: str, - mock_location: str, - mock_output: str, - exp_output: str, -): - """Ensure the parser supports help optional for commands as expected""" - - def mock_execute(ns: argparse.Namespace, unparsed: t.Optional[t.List[str]] = None): - print(mock_output) - return 0 - - monkeypatch.setattr(smartsim._core._cli.cli, mock_location, mock_execute) - - smart_cli = cli.default_cli() - - captured = capsys.readouterr() # throw away existing output - - # execute with `` argument, expect -specific help text - build_args = ["smart", command] + ["-h"] - with pytest.raises(SystemExit) as e: - ret_val = smart_cli.execute(build_args) - assert ret_val == 0 - - captured = capsys.readouterr() # capture new output - assert exp_output in captured.out - - -# fmt: off -@pytest.mark.parametrize( - "command,mock_location,exp_output", - [ - pytest.param("build", "build_execute", "verbose mocked-build", id="build"), - pytest.param("clean", "clean_execute", "verbose mocked-clean", id="clean"), - pytest.param("clobber", "clobber_execute", "verbose mocked-clobber", id="clobber"), - pytest.param("dbcli", "dbcli_execute", "verbose mocked-dbcli", id="dbcli"), - pytest.param("site", "site_execute", "verbose mocked-site", id="site"), - pytest.param("validate", "validate_execute", "verbose mocked-validate", id="validate"), - pytest.param("info", "info_execute", "verbose mocked-validate", id="validate"), - ] -) -# fmt: on -def test_cli_invalid_optional_args( - capsys, monkeypatch, command: str, mock_location: str, exp_output: str -): - """Ensure the parser throws expected error for an invalid argument""" - - def mock_execute(ns: argparse.Namespace, unparsed: t.Optional[t.List[str]] = None): - print(exp_output) - return 0 - - monkeypatch.setattr(smartsim._core._cli.cli, mock_location, mock_execute) - - smart_cli = cli.default_cli() - - captured = capsys.readouterr() # throw away existing output - - # execute with `` argument, expect CLI to raise invalid arg error - build_args = ["smart", command] + ["-xyz"] - with pytest.raises(SystemExit) as e: - ret_val = smart_cli.execute(build_args) - assert ret_val > 0 - - captured = capsys.readouterr() # capture new output - assert "unrecognized argument" in captured.err - - -@pytest.mark.parametrize( - "command", - [ - pytest.param("build", id="build"), - pytest.param("clean", id="clean"), - pytest.param("clobber", id="clobber"), - pytest.param("dbcli", id="dbcli"), - pytest.param("site", id="site"), - pytest.param("validate", id="validate"), - pytest.param("info", id="info"), - ], -) -def test_cli_invalid_optional_args(capsys, command): - """Ensure the parser throws expected error for an invalid command""" - smart_cli = cli.default_cli() - - captured = capsys.readouterr() # throw away existing output - - # execute with `` argument, expect CLI to raise invalid arg error - build_args = ["smart", command] + ["-xyz"] - with pytest.raises(SystemExit) as e: - ret_val = smart_cli.execute(build_args) - assert ret_val > 0 - - captured = capsys.readouterr() # capture new output - assert "unrecognized argument" in captured.err - - -def test_cli_full_clean_execute(capsys, monkeypatch): - """Ensure that the execute method of clean is called""" - exp_retval = 0 - exp_output = "mocked-clean utility" - - # mock out the internal clean method so we don't actually delete anything - def mock_clean(core_path: pathlib.Path, _all: bool = False) -> int: - print(exp_output) - return exp_retval - - monkeypatch.setattr(smartsim._core._cli.clean, "clean", mock_clean) - - command = "clean" - cfg = MenuItemConfig( - command, f"test {command} help text", clean_execute, clean_parser - ) - menu = [cfg] - smart_cli = cli.SmartCli(menu) - - captured = capsys.readouterr() # throw away existing output - - build_args = ["smart", command] - actual_retval = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - assert exp_output in captured.out - assert actual_retval == exp_retval - - -def test_cli_full_clobber_execute(capsys, monkeypatch): - """Ensure that the execute method of clobber is called""" - exp_retval = 0 - exp_output = "mocked-clobber utility" - - def mock_operation(*args, **kwargs) -> int: - print(exp_output) - return exp_retval - - # mock out the internal clean method so we don't actually delete anything - monkeypatch.setattr(smartsim._core._cli.clean, "clean", mock_operation) - - command = "clobber" - cfg = MenuItemConfig(command, f"test {command} help text", clobber_execute) - menu = [cfg] - smart_cli = cli.SmartCli(menu) - - captured = capsys.readouterr() # throw away existing output - - build_args = ["smart", command] - actual_retval = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - assert exp_output in captured.out - assert actual_retval == exp_retval - - -def test_cli_full_dbcli_execute(capsys, monkeypatch): - """Ensure that the execute method of dbcli is called""" - exp_retval = 0 - exp_output = "mocked-get_db_path utility" - - def mock_operation(*args, **kwargs) -> int: - return exp_output - - # mock out the internal get_db_path method so we don't actually do file system ops - monkeypatch.setattr(smartsim._core._cli.dbcli, "get_db_path", mock_operation) - - command = "dbcli" - cfg = MenuItemConfig(command, f"test {command} help text", dbcli_execute) - menu = [cfg] - smart_cli = cli.SmartCli(menu) - - captured = capsys.readouterr() # throw away existing output - - build_args = ["smart", command] - actual_retval = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - assert exp_output in captured.out - assert actual_retval == exp_retval - - -def test_cli_full_site_execute(capsys, monkeypatch): - """Ensure that the execute method of site is called""" - exp_retval = 0 - exp_output = "mocked-get_install_path utility" - - def mock_operation(*args, **kwargs) -> int: - print(exp_output) - return exp_retval - - # mock out the internal get_db_path method so we don't actually do file system ops - monkeypatch.setattr(smartsim._core._cli.site, "get_install_path", mock_operation) - - command = "site" - cfg = MenuItemConfig(command, f"test {command} help text", site_execute) - menu = [cfg] - smart_cli = cli.SmartCli(menu) - - captured = capsys.readouterr() # throw away existing output - - build_args = ["smart", command] - actual_retval = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - assert exp_output in captured.out - assert actual_retval == exp_retval - - -def test_cli_full_build_execute(capsys, monkeypatch): - """Ensure that the execute method of build is called""" - exp_retval = 0 - exp_output = "mocked-execute-build utility" - - def mock_operation(*args, **kwargs) -> int: - print(exp_output) - return exp_retval - - # mock out the internal get_db_path method so we don't actually do file system ops - monkeypatch.setattr(smartsim._core._cli.build, "tabulate", mock_operation) - monkeypatch.setattr(smartsim._core._cli.build, "build_database", mock_operation) - monkeypatch.setattr(smartsim._core._cli.build, "build_redis_ai", mock_operation) - monkeypatch.setattr( - smartsim._core._cli.build, "check_py_torch_version", mock_operation - ) - monkeypatch.setattr( - smartsim._core._cli.build, "check_py_tf_version", mock_operation - ) - monkeypatch.setattr( - smartsim._core._cli.build, "check_py_onnx_version", mock_operation - ) - - command = "build" - cfg = MenuItemConfig( - command, f"test {command} help text", build_execute, build_parser - ) - menu = [cfg] - smart_cli = cli.SmartCli(menu) - - captured = capsys.readouterr() # throw away existing output - - build_args = ["smart", command] - actual_retval = smart_cli.execute(build_args) - - captured = capsys.readouterr() # capture new output - - assert exp_output in captured.out - assert actual_retval == exp_retval - - -def _good_build(*args, **kwargs): - _TEST_LOGGER.info("LGTM") - - -def _bad_build(*args, **kwargs): - raise Exception - - -@contextmanager -def _mock_temp_dir(*a, **kw): - yield "/a/mock/path/to/a/mock/temp/dir" - - -@pytest.mark.parametrize( - "mock_verify_fn, expected_stdout, expected_retval", - [ - pytest.param(_good_build, "LGTM", os.EX_OK, id="Configured Correctly"), - pytest.param( - _bad_build, - "SmartSim failed to run a simple experiment", - os.EX_SOFTWARE, - id="Configured Incorrectly", - ), - ], -) -def test_cli_validation_test_execute( - caplog, - monkeypatch, - mock_verify_fn, - expected_stdout, - expected_retval, -): - """Ensure the that the execute method of test target is called. This test will - stub out the actual test run by the cli (it will be tested elsewere), and simply - checks that if at any point the test raises an exception an appropriate error - code and error msg are returned. - """ - caplog.set_level(logging.INFO) - - # Mock out the verification tests/avoid file system ops - monkeypatch.setattr(smartsim._core._cli.validate, "test_install", mock_verify_fn) - monkeypatch.setattr( - smartsim._core._cli.validate, - "_VerificationTempDir", - _mock_temp_dir, - ) - # Coloredlogs doesn't play nice with caplog - monkeypatch.setattr( - smartsim._core._cli.validate, - "logger", - _TEST_LOGGER, - ) - - command = "validate" - cfg = MenuItemConfig( - command, f"test {command} help text", validate_execute, validate_parser - ) - menu = [cfg] - smart_cli = cli.SmartCli(menu) - - verify_args = ["smart", command] - actual_retval = smart_cli.execute(verify_args) - - assert expected_stdout in caplog.text - assert actual_retval == expected_retval - - -def test_validate_correctly_sets_and_restores_env(monkeypatch): - monkeypatch.setenv("FOO", "BAR") - monkeypatch.setenv("SPAM", "EGGS") - monkeypatch.delenv("TICK", raising=False) - monkeypatch.delenv("DNE", raising=False) - - assert os.environ["FOO"] == "BAR" - assert os.environ["SPAM"] == "EGGS" - assert "TICK" not in os.environ - assert "DNE" not in os.environ - - with smartsim._core._cli.validate._env_vars_set_to( - { - "FOO": "BAZ", # Redefine - "SPAM": None, # Delete - "TICK": "TOCK", # Add - "DNE": None, # Delete already missing - } - ): - assert os.environ["FOO"] == "BAZ" - assert "SPAM" not in os.environ - assert os.environ["TICK"] == "TOCK" - assert "DNE" not in os.environ - - assert os.environ["FOO"] == "BAR" - assert os.environ["SPAM"] == "EGGS" - assert "TICK" not in os.environ - assert "DNE" not in os.environ From f740664bd7c3f900356b31ecf41622bd7786b9af Mon Sep 17 00:00:00 2001 From: Alyssa Cote Date: Wed, 10 Apr 2024 09:17:14 -0700 Subject: [PATCH 02/11] restore deleted test file --- tests/test_cli.py | 860 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 860 insertions(+) create mode 100644 tests/test_cli.py diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 000000000..710a9a659 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,860 @@ +# BSD 2-Clause License +# +# Copyright (c) 2021-2024, Hewlett Packard Enterprise +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import argparse +import logging +import os +import pathlib +import typing as t +from contextlib import contextmanager + +import pytest + +import smartsim +from smartsim._core._cli import build, cli, plugin +from smartsim._core._cli.build import configure_parser as build_parser +from smartsim._core._cli.build import execute as build_execute +from smartsim._core._cli.clean import configure_parser as clean_parser +from smartsim._core._cli.clean import execute as clean_execute +from smartsim._core._cli.clean import execute_all as clobber_execute +from smartsim._core._cli.dbcli import execute as dbcli_execute +from smartsim._core._cli.site import execute as site_execute +from smartsim._core._cli.utils import MenuItemConfig +from smartsim._core._cli.validate import configure_parser as validate_parser +from smartsim._core._cli.validate import execute as validate_execute + +# The tests in this file belong to the group_a group +pytestmark = pytest.mark.group_a + +_TEST_LOGGER = logging.getLogger(__name__) + +try: + import smartdashboard +except: + test_dash_plugin = False +else: + test_dash_plugin = True + + +def mock_execute_custom(msg: str = None, good: bool = True) -> int: + retval = 0 if good else 1 + print(msg) + return retval + + +def mock_execute_good( + _ns: argparse.Namespace, _unparsed: t.Optional[t.List[str]] = None +) -> int: + return mock_execute_custom("GOOD THINGS", good=True) + + +def mock_execute_fail( + _ns: argparse.Namespace, _unparsed: t.Optional[t.List[str]] = None +) -> int: + return mock_execute_custom("BAD THINGS", good=False) + + +def test_cli_default_args_parsing(capsys): + """Test default parser behaviors with no subparsers""" + menu: t.List[cli.MenuItemConfig] = [] + smart_cli = cli.SmartCli(menu) + + captured = capsys.readouterr() # throw away existing output + + with pytest.raises(SystemExit) as e: + # the parser shouldn't get the `smart` CLI argument + build_args = ["smart", "-h"] + smart_cli.parser.parse_args(build_args) + + captured = capsys.readouterr() + assert "invalid choice: 'smart'" in captured.err + assert e.value.code == 2 + + +def test_cli_invalid_command(capsys): + """Ensure the response when an unsupported command is given""" + exp_help = "this is my mock help text for build" + exp_cmd = "build" + actual_cmd = f"not{exp_cmd}" + menu = [ + cli.MenuItemConfig(exp_cmd, exp_help, mock_execute_good, build.configure_parser) + ] + smart_cli = cli.SmartCli(menu) + + captured = capsys.readouterr() # throw away existing output + with pytest.raises(SystemExit) as e: + build_args = [actual_cmd, "-h"] + smart_cli.parser.parse_args(build_args) + + captured = capsys.readouterr() # capture new output + + # show that the command was not recognized + assert "invalid choice" in captured.err + assert e.value.code == 2 + + +def test_cli_bad_default_args_parsing_bad_help(capsys): + """Test passing an argument name that is incorrect""" + menu: t.List[cli.MenuItemConfig] = [] + smart_cli = cli.SmartCli(menu) + + captured = capsys.readouterr() # throw away existing output + with pytest.raises(SystemExit) as e: + build_args = ["--halp"] # <-- HELP vs HALP + smart_cli.parser.parse_args(build_args) + + captured = capsys.readouterr() # capture new output + + assert "smart: error:" in captured.err + assert e.value.code == 2 + + +def test_cli_bad_default_args_parsing_good_help(capsys): + """Test passing an argument name that is correct""" + menu: t.List[cli.MenuItemConfig] = [] + smart_cli = cli.SmartCli(menu) + + captured = capsys.readouterr() # throw away existing output + with pytest.raises(SystemExit) as e: + build_args = ["-h"] + smart_cli.parser.parse_args(build_args) + + captured = capsys.readouterr() # capture new output + + assert "smart: error:" not in captured.out + assert "usage: smart" in captured.out + assert e.value.code == 0 + + +def test_cli_add_subparser(capsys): + """Test that passing configuration for a command causes the command + to be added to the CLI + """ + exp_help = "this is my mock help text for build" + exp_cmd = "build" + menu = [ + cli.MenuItemConfig(exp_cmd, exp_help, mock_execute_good, build.configure_parser) + ] + smart_cli = cli.SmartCli(menu) + + captured = capsys.readouterr() # throw away existing output + with pytest.raises(SystemExit) as e: + build_args = [exp_cmd, "-h"] # <--- -h only + smart_cli.parser.parse_args(build_args) + + captured = capsys.readouterr() # capture new output + + # show that -h showed the expected help text + assert exp_help in captured.out + assert e.value.code == 0 + + captured = capsys.readouterr() # throw away existing output + with pytest.raises(SystemExit) as e: + build_args = [exp_cmd, "--help"] + smart_cli.parser.parse_args(build_args) + + captured = capsys.readouterr() # capture new output + + # show that --help ALSO works + assert exp_help in captured.out + assert e.value.code == 0 + + +def test_cli_subparser_selection(capsys): + """Ensure the right subparser is selected""" + exp_a_help = "this is my mock help text for dbcli" + exp_a_cmd = "dbcli" + + exp_b_help = "this is my mock help text for build" + exp_b_cmd = "build" + + menu = [ + cli.MenuItemConfig( + exp_a_cmd, exp_a_help, mock_execute_good, build.configure_parser + ), + cli.MenuItemConfig( + exp_b_cmd, exp_b_help, mock_execute_good, build.configure_parser + ), + ] + smart_cli = cli.SmartCli(menu) + + captured = capsys.readouterr() # throw away existing output + with pytest.raises(SystemExit) as e: + build_args = [exp_a_cmd, "-h"] # <--- -h only + smart_cli.parser.parse_args(build_args) + + captured = capsys.readouterr() # capture new output + + # show that -h showed the expected help text for `smart dbcli -h` + assert exp_a_help in captured.out + assert e.value.code == 0 + + captured = capsys.readouterr() # throw away existing output + with pytest.raises(SystemExit) as e: + build_args = [exp_b_cmd, "--help"] + smart_cli.parser.parse_args(build_args) + + captured = capsys.readouterr() # capture new output + + # show that -h showed the expected help text for `smart build -h` + assert exp_b_help in captured.out + assert e.value.code == 0 + + +def test_cli_command_execution(capsys): + """Ensure the right command is executed""" + exp_a_help = "this is my mock help text for dbcli" + exp_a_cmd = "dbcli" + + exp_b_help = "this is my mock help text for build" + exp_b_cmd = "build" + + dbcli_exec = lambda x, y: mock_execute_custom(msg="Database", good=True) + build_exec = lambda x, y: mock_execute_custom(msg="Builder", good=True) + + menu = [ + cli.MenuItemConfig(exp_a_cmd, exp_a_help, dbcli_exec, lambda x: None), + cli.MenuItemConfig(exp_b_cmd, exp_b_help, build_exec, lambda x: None), + ] + smart_cli = cli.SmartCli(menu) + + captured = capsys.readouterr() # throw away existing output + + build_args = ["smart", exp_a_cmd] + ret_val = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + # show that `smart dbcli` calls the build parser and build execute function + assert "Database" in captured.out + assert ret_val == 0 + + build_args = ["smart", exp_b_cmd] + ret_val = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + # show that `smart build` calls the build parser and build execute function + assert "Builder" in captured.out + assert ret_val == 0 + + +def test_cli_default_cli(capsys): + """Ensure the default CLI supports expected top-level commands""" + smart_cli = cli.default_cli() + + captured = capsys.readouterr() # throw away existing output + + # execute with no argument, expect full help text + build_args = ["smart"] + ret_val = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + # show that `smart dbcli` calls the build parser and build execute function + assert "usage: smart [-h] " in captured.out + assert "Available commands" in captured.out + assert ret_val == os.EX_USAGE + + # execute with `build` argument, expect build-specific help text + with pytest.raises(SystemExit) as e: + build_args = ["smart", "build", "-h"] + ret_val = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + assert "usage: smart build [-h]" in captured.out + assert "Build SmartSim dependencies" in captured.out + assert "optional arguments:" in captured.out or "options:" in captured.out + assert ret_val == os.EX_USAGE + + # execute with `clean` argument, expect clean-specific help text + with pytest.raises(SystemExit) as e: + build_args = ["smart", "clean", "-h"] + ret_val = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + assert "usage: smart clean [-h]" in captured.out + assert "Remove previous ML runtime installation" in captured.out + assert "optional arguments:" in captured.out or "options:" in captured.out + assert "--clobber" in captured.out + assert ret_val == os.EX_USAGE + + # execute with `dbcli` argument, expect dbcli-specific help text + with pytest.raises(SystemExit) as e: + build_args = ["smart", "dbcli", "-h"] + ret_val = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + assert "usage: smart dbcli [-h]" in captured.out + assert "Print the path to the redis-cli binary" in captured.out + assert "optional arguments:" in captured.out or "options:" in captured.out + assert ret_val == os.EX_USAGE + + # execute with `site` argument, expect site-specific help text + with pytest.raises(SystemExit) as e: + build_args = ["smart", "site", "-h"] + ret_val = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + assert "usage: smart site [-h]" in captured.out + assert "Print the installation site of SmartSim" in captured.out + assert "optional arguments:" in captured.out or "options:" in captured.out + assert ret_val == os.EX_USAGE + + # execute with `clobber` argument, expect clobber-specific help text + with pytest.raises(SystemExit) as e: + build_args = ["smart", "clobber", "-h"] + ret_val = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + assert "usage: smart clobber [-h]" in captured.out + assert "Remove all previous dependency installations" in captured.out + assert "optional arguments:" in captured.out or "options:" in captured.out + # assert "--clobber" not in captured.out + assert ret_val == os.EX_USAGE + + +@pytest.mark.skipif(not test_dash_plugin, reason="plugin not found") +def test_cli_plugin_dashboard(capfd): + """Ensure expected dashboard CLI plugin commands are supported""" + smart_cli = cli.default_cli() + capfd.readouterr() # throw away existing output + + # execute with `dashboard` argument, expect dashboard-specific help text + build_args = ["smart", "dashboard", "-h"] + rc = smart_cli.execute(build_args) + + captured = capfd.readouterr() # capture new output + + assert "[-d DIRECTORY]" in captured.out + assert "[-p PORT]" in captured.out + + assert "optional arguments:" in captured.out + assert rc == 0 + + +def test_cli_plugin_invalid( + monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture +): + """Ensure unexpected CLI plugins are reported""" + import smartsim._core._cli.cli + import smartsim._core._cli.plugin + + plugin_module = "notinstalled.Experiment_Overview" + bad_plugins = [ + lambda: MenuItemConfig( + "dashboard", + "Start the SmartSim dashboard", + plugin.dynamic_execute(plugin_module, "Dashboard!"), + is_plugin=True, + ) + ] + monkeypatch.setattr(smartsim._core._cli.cli, "plugins", bad_plugins) + # Coloredlogs doesn't play nice with caplog + monkeypatch.setattr( + smartsim._core._cli.plugin, + "_LOGGER", + _TEST_LOGGER, + ) + + smart_cli = cli.default_cli() + + # execute with `dashboard` argument, expect failure to find dashboard plugin + build_args = ["smart", "dashboard", "-h"] + + rc = smart_cli.execute(build_args) + + assert plugin_module in caplog.text + assert "not found" in caplog.text + assert rc == os.EX_CONFIG + + +# fmt: off +@pytest.mark.parametrize( + "command,mock_location,exp_output", + [ + pytest.param("build", "build_execute", "mocked-build", id="ensure build action is executed"), + pytest.param("clean", "clean_execute", "mocked-clean", id="ensure clean action is executed"), + pytest.param("dbcli", "dbcli_execute", "mocked-dbcli", id="ensure dbcli action is executed"), + pytest.param("site", "site_execute", "mocked-site", id="ensure site action is executed"), + pytest.param("clobber", "clobber_execute", "mocked-clobber", id="ensure clobber action is executed"), + pytest.param("validate", "validate_execute", "mocked-validate", id="ensure validate action is executed"), + pytest.param("info", "info_execute", "mocked-validate", id="ensure info action is executed"), + ] +) +# fmt: on +def test_cli_action(capsys, monkeypatch, command, mock_location, exp_output): + """Ensure the default CLI executes the build action""" + + def mock_execute(ns: argparse.Namespace, _unparsed: t.Optional[t.List[str]] = None): + print(exp_output) + return 0 + + monkeypatch.setattr(smartsim._core._cli.cli, mock_location, mock_execute) + + smart_cli = cli.default_cli() + + captured = capsys.readouterr() # throw away existing output + + # execute with `` argument, expect -specific output text + build_args = ["smart", command] + ret_val = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + assert exp_output in captured.out + assert ret_val == 0 + + +# fmt: off +@pytest.mark.parametrize( + "command,mock_location,exp_output,optional_arg,exp_valid,exp_err_msg,check_prop,exp_prop_val", + [ + pytest.param("build", "build_execute", "verbose mocked-build", "-v", True, "", "v", True, id="verbose 'on'"), + pytest.param("build", "build_execute", "cpu mocked-build", "--device=cpu", True, "", "device", "cpu", id="device 'cpu'"), + pytest.param("build", "build_execute", "gpu mocked-build", "--device=gpu", True, "", "device", "gpu", id="device 'gpu'"), + pytest.param("build", "build_execute", "gpuX mocked-build", "--device=gpux", False, "invalid choice: 'gpux'", "", "", id="set bad device 'gpuX'"), + pytest.param("build", "build_execute", "no tensorflow mocked-build", "--no_tf", True, "", "no_tf", True, id="set no TF"), + pytest.param("build", "build_execute", "no torch mocked-build", "--no_pt", True, "", "no_pt", True, id="set no torch"), + pytest.param("build", "build_execute", "onnx mocked-build", "--onnx", True, "", "onnx", True, id="set w/onnx"), + pytest.param("build", "build_execute", "torch-dir mocked-build", "--torch_dir /foo/bar", True, "", "torch_dir", "/foo/bar", id="set torch dir"), + pytest.param("build", "build_execute", "bad-torch-dir mocked-build", "--torch_dir", False, "error: argument --torch_dir", "", "", id="set torch dir, no path"), + pytest.param("build", "build_execute", "keydb mocked-build", "--keydb", True, "", "keydb", True, id="keydb on"), + pytest.param("clean", "clean_execute", "clobbering mocked-clean", "--clobber", True, "", "clobber", True, id="clean w/clobber"), + pytest.param("validate", "validate_execute", "port mocked-validate", "--port=12345", True, "", "port", 12345, id="validate w/ manual port"), + pytest.param("validate", "validate_execute", "abbrv port mocked-validate", "-p 12345", True, "", "port", 12345, id="validate w/ manual abbreviated port"), + pytest.param("validate", "validate_execute", "cpu mocked-validate", "--device=cpu", True, "", "device", "cpu", id="validate: device 'cpu'"), + pytest.param("validate", "validate_execute", "gpu mocked-validate", "--device=gpu", True, "", "device", "gpu", id="validate: device 'gpu'"), + pytest.param("validate", "validate_execute", "gpuX mocked-validate", "--device=gpux", False, "invalid choice: 'gpux'", "", "", id="validate: set bad device 'gpuX'"), + ] +) +# fmt: on +def test_cli_optional_args( + capsys, + monkeypatch, + command: str, + mock_location: str, + exp_output: str, + optional_arg: str, + exp_valid: bool, + exp_err_msg: str, + check_prop: str, + exp_prop_val: t.Any, +): + """Ensure the parser for a command handles expected optional arguments""" + + def mock_execute(ns: argparse.Namespace, _unparsed: t.Optional[t.List[str]] = None): + print(exp_output) + return 0 + + monkeypatch.setattr(smartsim._core._cli.cli, mock_location, mock_execute) + + smart_cli = cli.default_cli() + + captured = capsys.readouterr() # throw away existing output + + build_args = ["smart", command] + optional_arg.split() + if exp_valid: + ret_val = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + assert exp_output in captured.out # did the expected execution method occur? + assert ret_val == 0 # is the retval is non-failure code? + else: + with pytest.raises(SystemExit) as e: + ret_val = smart_cli.execute(build_args) + assert ret_val > 0 + + captured = capsys.readouterr() # capture new output + assert exp_err_msg in captured.err + + +# fmt: off +@pytest.mark.parametrize( + "command,mock_location,mock_output,exp_output", + [ + pytest.param("build", "build_execute", "verbose mocked-build", "usage: smart build", id="build"), + pytest.param("clean", "clean_execute", "helpful mocked-clean", "usage: smart clean", id="clean"), + pytest.param("clobber", "clean_execute", "helpful mocked-clobber", "usage: smart clobber", id="clobber"), + pytest.param("dbcli", "clean_execute", "helpful mocked-dbcli", "usage: smart dbcli", id="dbcli"), + pytest.param("site", "clean_execute", "helpful mocked-site", "usage: smart site", id="site"), + pytest.param("validate", "validate_execute", "helpful mocked-validate", "usage: smart validate", id="validate"), + pytest.param("info", "info_execute", "helpful mocked-validate", "usage: smart info", id="info"), + ] +) +# fmt: on +def test_cli_help_support( + capsys, + monkeypatch, + command: str, + mock_location: str, + mock_output: str, + exp_output: str, +): + """Ensure the parser supports help optional for commands as expected""" + + def mock_execute(ns: argparse.Namespace, unparsed: t.Optional[t.List[str]] = None): + print(mock_output) + return 0 + + monkeypatch.setattr(smartsim._core._cli.cli, mock_location, mock_execute) + + smart_cli = cli.default_cli() + + captured = capsys.readouterr() # throw away existing output + + # execute with `` argument, expect -specific help text + build_args = ["smart", command] + ["-h"] + with pytest.raises(SystemExit) as e: + ret_val = smart_cli.execute(build_args) + assert ret_val == 0 + + captured = capsys.readouterr() # capture new output + assert exp_output in captured.out + + +# fmt: off +@pytest.mark.parametrize( + "command,mock_location,exp_output", + [ + pytest.param("build", "build_execute", "verbose mocked-build", id="build"), + pytest.param("clean", "clean_execute", "verbose mocked-clean", id="clean"), + pytest.param("clobber", "clobber_execute", "verbose mocked-clobber", id="clobber"), + pytest.param("dbcli", "dbcli_execute", "verbose mocked-dbcli", id="dbcli"), + pytest.param("site", "site_execute", "verbose mocked-site", id="site"), + pytest.param("validate", "validate_execute", "verbose mocked-validate", id="validate"), + pytest.param("info", "info_execute", "verbose mocked-validate", id="validate"), + ] +) +# fmt: on +def test_cli_invalid_optional_args( + capsys, monkeypatch, command: str, mock_location: str, exp_output: str +): + """Ensure the parser throws expected error for an invalid argument""" + + def mock_execute(ns: argparse.Namespace, unparsed: t.Optional[t.List[str]] = None): + print(exp_output) + return 0 + + monkeypatch.setattr(smartsim._core._cli.cli, mock_location, mock_execute) + + smart_cli = cli.default_cli() + + captured = capsys.readouterr() # throw away existing output + + # execute with `` argument, expect CLI to raise invalid arg error + build_args = ["smart", command] + ["-xyz"] + with pytest.raises(SystemExit) as e: + ret_val = smart_cli.execute(build_args) + assert ret_val > 0 + + captured = capsys.readouterr() # capture new output + assert "unrecognized argument" in captured.err + + +@pytest.mark.parametrize( + "command", + [ + pytest.param("build", id="build"), + pytest.param("clean", id="clean"), + pytest.param("clobber", id="clobber"), + pytest.param("dbcli", id="dbcli"), + pytest.param("site", id="site"), + pytest.param("validate", id="validate"), + pytest.param("info", id="info"), + ], +) +def test_cli_invalid_optional_args(capsys, command): + """Ensure the parser throws expected error for an invalid command""" + smart_cli = cli.default_cli() + + captured = capsys.readouterr() # throw away existing output + + # execute with `` argument, expect CLI to raise invalid arg error + build_args = ["smart", command] + ["-xyz"] + with pytest.raises(SystemExit) as e: + ret_val = smart_cli.execute(build_args) + assert ret_val > 0 + + captured = capsys.readouterr() # capture new output + assert "unrecognized argument" in captured.err + + +def test_cli_full_clean_execute(capsys, monkeypatch): + """Ensure that the execute method of clean is called""" + exp_retval = 0 + exp_output = "mocked-clean utility" + + # mock out the internal clean method so we don't actually delete anything + def mock_clean(core_path: pathlib.Path, _all: bool = False) -> int: + print(exp_output) + return exp_retval + + monkeypatch.setattr(smartsim._core._cli.clean, "clean", mock_clean) + + command = "clean" + cfg = MenuItemConfig( + command, f"test {command} help text", clean_execute, clean_parser + ) + menu = [cfg] + smart_cli = cli.SmartCli(menu) + + captured = capsys.readouterr() # throw away existing output + + build_args = ["smart", command] + actual_retval = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + assert exp_output in captured.out + assert actual_retval == exp_retval + + +def test_cli_full_clobber_execute(capsys, monkeypatch): + """Ensure that the execute method of clobber is called""" + exp_retval = 0 + exp_output = "mocked-clobber utility" + + def mock_operation(*args, **kwargs) -> int: + print(exp_output) + return exp_retval + + # mock out the internal clean method so we don't actually delete anything + monkeypatch.setattr(smartsim._core._cli.clean, "clean", mock_operation) + + command = "clobber" + cfg = MenuItemConfig(command, f"test {command} help text", clobber_execute) + menu = [cfg] + smart_cli = cli.SmartCli(menu) + + captured = capsys.readouterr() # throw away existing output + + build_args = ["smart", command] + actual_retval = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + assert exp_output in captured.out + assert actual_retval == exp_retval + + +def test_cli_full_dbcli_execute(capsys, monkeypatch): + """Ensure that the execute method of dbcli is called""" + exp_retval = 0 + exp_output = "mocked-get_db_path utility" + + def mock_operation(*args, **kwargs) -> int: + return exp_output + + # mock out the internal get_db_path method so we don't actually do file system ops + monkeypatch.setattr(smartsim._core._cli.dbcli, "get_db_path", mock_operation) + + command = "dbcli" + cfg = MenuItemConfig(command, f"test {command} help text", dbcli_execute) + menu = [cfg] + smart_cli = cli.SmartCli(menu) + + captured = capsys.readouterr() # throw away existing output + + build_args = ["smart", command] + actual_retval = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + assert exp_output in captured.out + assert actual_retval == exp_retval + + +def test_cli_full_site_execute(capsys, monkeypatch): + """Ensure that the execute method of site is called""" + exp_retval = 0 + exp_output = "mocked-get_install_path utility" + + def mock_operation(*args, **kwargs) -> int: + print(exp_output) + return exp_retval + + # mock out the internal get_db_path method so we don't actually do file system ops + monkeypatch.setattr(smartsim._core._cli.site, "get_install_path", mock_operation) + + command = "site" + cfg = MenuItemConfig(command, f"test {command} help text", site_execute) + menu = [cfg] + smart_cli = cli.SmartCli(menu) + + captured = capsys.readouterr() # throw away existing output + + build_args = ["smart", command] + actual_retval = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + assert exp_output in captured.out + assert actual_retval == exp_retval + + +def test_cli_full_build_execute(capsys, monkeypatch): + """Ensure that the execute method of build is called""" + exp_retval = 0 + exp_output = "mocked-execute-build utility" + + def mock_operation(*args, **kwargs) -> int: + print(exp_output) + return exp_retval + + # mock out the internal get_db_path method so we don't actually do file system ops + monkeypatch.setattr(smartsim._core._cli.build, "tabulate", mock_operation) + monkeypatch.setattr(smartsim._core._cli.build, "build_database", mock_operation) + monkeypatch.setattr(smartsim._core._cli.build, "build_redis_ai", mock_operation) + monkeypatch.setattr( + smartsim._core._cli.build, "check_py_torch_version", mock_operation + ) + monkeypatch.setattr( + smartsim._core._cli.build, "check_py_tf_version", mock_operation + ) + monkeypatch.setattr( + smartsim._core._cli.build, "check_py_onnx_version", mock_operation + ) + + command = "build" + cfg = MenuItemConfig( + command, f"test {command} help text", build_execute, build_parser + ) + menu = [cfg] + smart_cli = cli.SmartCli(menu) + + captured = capsys.readouterr() # throw away existing output + + build_args = ["smart", command] + actual_retval = smart_cli.execute(build_args) + + captured = capsys.readouterr() # capture new output + + assert exp_output in captured.out + assert actual_retval == exp_retval + + +def _good_build(*args, **kwargs): + _TEST_LOGGER.info("LGTM") + + +def _bad_build(*args, **kwargs): + raise Exception + + +@contextmanager +def _mock_temp_dir(*a, **kw): + yield "/a/mock/path/to/a/mock/temp/dir" + + +@pytest.mark.parametrize( + "mock_verify_fn, expected_stdout, expected_retval", + [ + pytest.param(_good_build, "LGTM", os.EX_OK, id="Configured Correctly"), + pytest.param( + _bad_build, + "SmartSim failed to run a simple experiment", + os.EX_SOFTWARE, + id="Configured Incorrectly", + ), + ], +) +def test_cli_validation_test_execute( + caplog, + monkeypatch, + mock_verify_fn, + expected_stdout, + expected_retval, +): + """Ensure the that the execute method of test target is called. This test will + stub out the actual test run by the cli (it will be tested elsewere), and simply + checks that if at any point the test raises an exception an appropriate error + code and error msg are returned. + """ + caplog.set_level(logging.INFO) + + # Mock out the verification tests/avoid file system ops + monkeypatch.setattr(smartsim._core._cli.validate, "test_install", mock_verify_fn) + monkeypatch.setattr( + smartsim._core._cli.validate, + "_VerificationTempDir", + _mock_temp_dir, + ) + # Coloredlogs doesn't play nice with caplog + monkeypatch.setattr( + smartsim._core._cli.validate, + "logger", + _TEST_LOGGER, + ) + + command = "validate" + cfg = MenuItemConfig( + command, f"test {command} help text", validate_execute, validate_parser + ) + menu = [cfg] + smart_cli = cli.SmartCli(menu) + + verify_args = ["smart", command] + actual_retval = smart_cli.execute(verify_args) + + assert expected_stdout in caplog.text + assert actual_retval == expected_retval + + +def test_validate_correctly_sets_and_restores_env(monkeypatch): + monkeypatch.setenv("FOO", "BAR") + monkeypatch.setenv("SPAM", "EGGS") + monkeypatch.delenv("TICK", raising=False) + monkeypatch.delenv("DNE", raising=False) + + assert os.environ["FOO"] == "BAR" + assert os.environ["SPAM"] == "EGGS" + assert "TICK" not in os.environ + assert "DNE" not in os.environ + + with smartsim._core._cli.validate._env_vars_set_to( + { + "FOO": "BAZ", # Redefine + "SPAM": None, # Delete + "TICK": "TOCK", # Add + "DNE": None, # Delete already missing + } + ): + assert os.environ["FOO"] == "BAZ" + assert "SPAM" not in os.environ + assert os.environ["TICK"] == "TOCK" + assert "DNE" not in os.environ + + assert os.environ["FOO"] == "BAR" + assert os.environ["SPAM"] == "EGGS" + assert "TICK" not in os.environ + assert "DNE" not in os.environ From 2f4f67b78e276476eaf4eea3458bc907cdcd73a1 Mon Sep 17 00:00:00 2001 From: Alyssa Cote Date: Wed, 10 Apr 2024 09:57:28 -0700 Subject: [PATCH 03/11] changelog --- doc/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/changelog.rst b/doc/changelog.rst index 167266fa3..c0493be31 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -18,6 +18,7 @@ To be released at some future point in time Description +- Drop Python 3.8 support. - Historical output files stored under .smartsim directory - Add option to build Torch backend without the Intel Math Kernel Library - Fix ReadTheDocs build issue @@ -38,6 +39,8 @@ Description Detailed Notes +- Python 3.8 is reaching its end-of-life in October, 2024, so it will + no longer continue to be supported. (SmartSim-PR544_) - The dashboard needs to display historical logs, so log files are written out under the .smartsim directory and files under the experiment directory are symlinked to them. (SmartSim-PR532_) @@ -93,6 +96,7 @@ Detailed Notes handler. SmartSim will now attempt to kill any launched jobs before calling the previously registered signal handler. (SmartSim-PR535_) +.. _SmartSim-PR544: https://github.com/CrayLabs/SmartSim/pull/544 .. _SmartSim-PR532: https://github.com/CrayLabs/SmartSim/pull/532 .. _SmartSim-PR538: https://github.com/CrayLabs/SmartSim/pull/538 .. _SmartSim-PR537: https://github.com/CrayLabs/SmartSim/pull/537 From 6da596db519894d10d505b6e64a6f25a17585e3a Mon Sep 17 00:00:00 2001 From: Alyssa Cote Date: Wed, 10 Apr 2024 10:13:53 -0700 Subject: [PATCH 04/11] changelog consistency --- doc/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index c0493be31..91b87b807 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -18,7 +18,7 @@ To be released at some future point in time Description -- Drop Python 3.8 support. +- Drop Python 3.8 support - Historical output files stored under .smartsim directory - Add option to build Torch backend without the Intel Math Kernel Library - Fix ReadTheDocs build issue From a3396352775927267bf05aea953203441af48220 Mon Sep 17 00:00:00 2001 From: Alyssa Cote Date: Wed, 10 Apr 2024 11:33:02 -0700 Subject: [PATCH 05/11] testing mypy changes --- smartsim/_core/utils/helpers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/smartsim/_core/utils/helpers.py b/smartsim/_core/utils/helpers.py index 4a1e56e35..b9e79e250 100644 --- a/smartsim/_core/utils/helpers.py +++ b/smartsim/_core/utils/helpers.py @@ -312,9 +312,10 @@ def decode_cmd(encoded_cmd: str) -> t.List[str]: return cleaned_cmd - +# TODO: Remove the ``type: ignore`` comment here when Python 3.8 support is dropped +# ``collections.abc.Collection`` is not subscriptable until Python 3.9 @t.final -class SignalInterceptionStack(collections.abc.Collection): +class SignalInterceptionStack(collections.abc.Collection): # type: ignore[type-arg] """Registers a stack of unique callables to be called when a signal is received before calling the original signal handler. """ From 1be225bded04691141d8c6ff45fd870d1882e883 Mon Sep 17 00:00:00 2001 From: Alyssa Cote Date: Thu, 11 Apr 2024 12:27:57 -0700 Subject: [PATCH 06/11] remove type ignore comment --- smartsim/_core/utils/helpers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/smartsim/_core/utils/helpers.py b/smartsim/_core/utils/helpers.py index b9e79e250..0e51f9c6d 100644 --- a/smartsim/_core/utils/helpers.py +++ b/smartsim/_core/utils/helpers.py @@ -312,10 +312,8 @@ def decode_cmd(encoded_cmd: str) -> t.List[str]: return cleaned_cmd -# TODO: Remove the ``type: ignore`` comment here when Python 3.8 support is dropped -# ``collections.abc.Collection`` is not subscriptable until Python 3.9 @t.final -class SignalInterceptionStack(collections.abc.Collection): # type: ignore[type-arg] +class SignalInterceptionStack(collections.abc.Collection): """Registers a stack of unique callables to be called when a signal is received before calling the original signal handler. """ From 45b69aad2fc451fcc47e2c595dbfe5d4bdc88f0d Mon Sep 17 00:00:00 2001 From: Alyssa Cote Date: Thu, 11 Apr 2024 14:30:56 -0700 Subject: [PATCH 07/11] add type parameter --- smartsim/_core/utils/helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/smartsim/_core/utils/helpers.py b/smartsim/_core/utils/helpers.py index 0e51f9c6d..aac818885 100644 --- a/smartsim/_core/utils/helpers.py +++ b/smartsim/_core/utils/helpers.py @@ -313,7 +313,9 @@ def decode_cmd(encoded_cmd: str) -> t.List[str]: @t.final -class SignalInterceptionStack(collections.abc.Collection): +class SignalInterceptionStack( + collections.abc.Collection[t.Callable[[int, FrameType | None], object]] +): """Registers a stack of unique callables to be called when a signal is received before calling the original signal handler. """ From 93c15b6daf8affa55884dfc01bea834d3682a155 Mon Sep 17 00:00:00 2001 From: Alyssa Cote Date: Thu, 11 Apr 2024 14:45:17 -0700 Subject: [PATCH 08/11] tweak type parameter --- smartsim/_core/utils/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartsim/_core/utils/helpers.py b/smartsim/_core/utils/helpers.py index aac818885..4c232ab3f 100644 --- a/smartsim/_core/utils/helpers.py +++ b/smartsim/_core/utils/helpers.py @@ -314,7 +314,7 @@ def decode_cmd(encoded_cmd: str) -> t.List[str]: @t.final class SignalInterceptionStack( - collections.abc.Collection[t.Callable[[int, FrameType | None], object]] + collections.abc.Collection[_TSignalHandlerFn] ): """Registers a stack of unique callables to be called when a signal is received before calling the original signal handler. From 9223802b7bbe3cfb701b0edfef8d4bcd03f18778 Mon Sep 17 00:00:00 2001 From: Alyssa Cote Date: Thu, 11 Apr 2024 14:49:42 -0700 Subject: [PATCH 09/11] style --- smartsim/_core/utils/helpers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/smartsim/_core/utils/helpers.py b/smartsim/_core/utils/helpers.py index 4c232ab3f..680618e32 100644 --- a/smartsim/_core/utils/helpers.py +++ b/smartsim/_core/utils/helpers.py @@ -313,9 +313,7 @@ def decode_cmd(encoded_cmd: str) -> t.List[str]: @t.final -class SignalInterceptionStack( - collections.abc.Collection[_TSignalHandlerFn] -): +class SignalInterceptionStack(collections.abc.Collection[_TSignalHandlerFn]): """Registers a stack of unique callables to be called when a signal is received before calling the original signal handler. """ From bbf6e8864786da5f9ebad17899c915c2986db9fa Mon Sep 17 00:00:00 2001 From: Alyssa Cote Date: Thu, 11 Apr 2024 14:55:15 -0700 Subject: [PATCH 10/11] remove unique in docstring --- smartsim/_core/utils/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smartsim/_core/utils/helpers.py b/smartsim/_core/utils/helpers.py index 680618e32..d9e7c513f 100644 --- a/smartsim/_core/utils/helpers.py +++ b/smartsim/_core/utils/helpers.py @@ -314,7 +314,7 @@ def decode_cmd(encoded_cmd: str) -> t.List[str]: @t.final class SignalInterceptionStack(collections.abc.Collection[_TSignalHandlerFn]): - """Registers a stack of unique callables to be called when a signal is + """Registers a stack of callables to be called when a signal is received before calling the original signal handler. """ From 5e7c175c925f74f4a2bc72e11c01deaf43832d43 Mon Sep 17 00:00:00 2001 From: Alyssa Cote Date: Tue, 16 Apr 2024 14:49:40 -0700 Subject: [PATCH 11/11] add py311 to pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6ea070d67..fe87141de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 88 -target-version = ['py39', 'py310'] +target-version = ['py39', 'py310', 'py311'] exclude = ''' ( | \.egg