diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 45636c8ec..dcc829b04 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -2,6 +2,16 @@ ## Summary +Updated the nox DB-version default to come from `BaseConfig` instead of the hardcoded `7.1.9`, +so ITDE-related test flows use the configured Exasol baseline and unit-test help no longer advertises `--db-version`. + +## Bug + +* #744: Updated nox DB-version handling to use `BaseConfig.minimum_exasol_version` instead hardcoded `7.1.9` + +## Refactoring + +* #744: Extracted shared minimum-version selection logic into `minimum_declared_version()` ## Security -* #867: Fixed zizmor linting results \ No newline at end of file +* #867: Fixed zizmor linting results diff --git a/exasol/toolbox/config.py b/exasol/toolbox/config.py index ca814d4d3..126a44587 100644 --- a/exasol/toolbox/config.py +++ b/exasol/toolbox/config.py @@ -93,6 +93,13 @@ def valid_version_string(version_string: str) -> str: return version_string +def minimum_declared_version(versions: tuple[str, ...]) -> str: + versioned = [Version.from_string(v) for v in versions] + min_version = min(versioned) + index_min_version = versioned.index(min_version) + return versions[index_min_version] + + ValidPluginHook = Annotated[type[Any], AfterValidator(validate_plugin_hook)] ValidVersionStr = Annotated[str, AfterValidator(valid_version_string)] @@ -230,10 +237,20 @@ def minimum_python_version(self) -> str: This is used in specific testing scenarios where it would be either costly to run the tests for all ``python_versions`` or we need a single metric. """ - versioned = [Version.from_string(v) for v in self.python_versions] - min_version = min(versioned) - index_min_version = versioned.index(min_version) - return self.python_versions[index_min_version] + return minimum_declared_version(self.python_versions) + + @computed_field # type: ignore[misc] + @property + def minimum_exasol_version(self) -> str | None: + """ + Minimum Exasol version declared from the ``exasol_versions`` list. + + This is used in scenarios where a single baseline Exasol version is + needed instead of iterating across the full configured matrix. + """ + if len(self.exasol_versions) == 0: + return None + return minimum_declared_version(self.exasol_versions) @computed_field # type: ignore[misc] @property diff --git a/exasol/toolbox/nox/_shared.py b/exasol/toolbox/nox/_shared.py index b80cfa724..7b3319a07 100644 --- a/exasol/toolbox/nox/_shared.py +++ b/exasol/toolbox/nox/_shared.py @@ -43,20 +43,49 @@ def _context_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter ) - parser.add_argument( - "--db-version", default="7.1.9", help="Specify the Exasol DB version to be used" - ) - parser.add_argument( - "--coverage", action="store_true", help="Enable the collection of coverage data" - ) return parser -def _context(session: Session, **kwargs: Any) -> MutableMapping[str, Any]: - parser = _context_parser() +def _context( + session: Session, + parser: argparse.ArgumentParser, + default_context: dict[str, Any], + **kwargs: Any, +) -> MutableMapping[str, Any]: namespace, args = parser.parse_known_args(session.posargs) cli_context: MutableMapping[str, Any] = vars(namespace) cli_context["fwd-args"] = args - default_context = {"db_version": "7.1.9", "coverage": False} # Note: ChainMap scans last to first return ChainMap(kwargs, cli_context, default_context) + + +def _unit_test_context(session: Session, **kwargs: Any) -> MutableMapping[str, Any]: + parser = _context_parser() + parser.add_argument( + "--coverage", action="store_true", help="Enable the collection of coverage data" + ) + return _context(session, parser, {"coverage": False}, **kwargs) + + +def _integration_test_context( + session: Session, + **kwargs: Any, +) -> MutableMapping[str, Any]: + parser = _context_parser() + parser.add_argument( + "--coverage", action="store_true", help="Enable the collection of coverage data" + ) + parser.add_argument( + "--db-version", + default=PROJECT_CONFIG.minimum_exasol_version, + help="Specify the Exasol DB version to be used", + ) + return _context( + session, + parser, + { + "coverage": False, + "db_version": PROJECT_CONFIG.minimum_exasol_version, + }, + **kwargs, + ) diff --git a/exasol/toolbox/nox/_test.py b/exasol/toolbox/nox/_test.py index 10b08c0c8..db6f9c4f8 100644 --- a/exasol/toolbox/nox/_test.py +++ b/exasol/toolbox/nox/_test.py @@ -11,7 +11,10 @@ from nox import Session from exasol.toolbox.config import BaseConfig -from exasol.toolbox.nox._shared import _context +from exasol.toolbox.nox._shared import ( + _integration_test_context, + _unit_test_context, +) from exasol.toolbox.nox.plugin import NoxTasks from noxconfig import ( PROJECT_CONFIG, @@ -76,7 +79,7 @@ def _coverage( @nox.session(name="test:unit", python=False) def unit_tests(session: Session) -> None: """Runs all unit tests""" - context = _context(session) + context = _unit_test_context(session) _unit_tests(session, PROJECT_CONFIG, context) @@ -90,12 +93,12 @@ def integration_tests(session: Session) -> None: * pre_integration_tests_hook(session: Session, config: Config, context: MutableMapping[str, Any]) -> bool: * post_integration_tests_hook(session: Session, config: Config, context: MutableMapping[str, Any]) -> bool: """ - context = _context(session) + context = _integration_test_context(session) _integration_tests(session, PROJECT_CONFIG, context) @nox.session(name="test:coverage", python=False) def coverage(session: Session) -> None: """Runs all tests (unit + integration) and reports the code coverage""" - context = _context(session, coverage=True) + context = _integration_test_context(session, coverage=True) _coverage(session, PROJECT_CONFIG, context) diff --git a/exasol/toolbox/nox/tasks.py b/exasol/toolbox/nox/tasks.py index 2b04dd8a4..6be048000 100644 --- a/exasol/toolbox/nox/tasks.py +++ b/exasol/toolbox/nox/tasks.py @@ -33,7 +33,7 @@ @nox.session(name="project:check", python=False) def check(session: Session) -> None: """Runs all available checks on the project""" - context = _context(session, coverage=True) + context = _integration_test_context(session, coverage=True) py_files = get_filtered_python_files(PROJECT_CONFIG.root_path) _code_format(session, Mode.Check, py_files) _pylint(session, py_files) @@ -61,7 +61,7 @@ def check(session: Session) -> None: from exasol.toolbox.nox._release import prepare_release from exasol.toolbox.nox._shared import ( Mode, - _context, + _integration_test_context, get_filtered_python_files, ) diff --git a/test/unit/config_test.py b/test/unit/config_test.py index 7a4433c6f..d87c0a97b 100644 --- a/test/unit/config_test.py +++ b/test/unit/config_test.py @@ -11,6 +11,7 @@ DEFAULT_EXCLUDED_PATHS, BaseConfig, DependencyManager, + minimum_declared_version, valid_version_string, warnings, ) @@ -65,6 +66,7 @@ def test_works_as_defined(tmp_path, test_project_config_factory): "merge_gate": False, }, }, + "minimum_exasol_version": "8.29.13", "minimum_python_version": "3.10", "os_version": "ubuntu-24.04", "sonar_token_name": "SONAR_TOKEN", @@ -153,6 +155,27 @@ def test_minimum_python_version(test_project_config_factory): assert conf.minimum_python_version == "1.10" +@pytest.mark.parametrize( + "declared_versions,expected", + [ + pytest.param(("3.11", "3.10", "3.12"), "3.10", id="python"), + pytest.param(("2025.1.8", "8.29.13", "9.9.9"), "8.29.13", id="exasol"), + ], +) +def test_minimum_declared_version(declared_versions, expected): + assert minimum_declared_version(declared_versions) == expected + + +def test_minimum_exasol_version(test_project_config_factory): + conf = test_project_config_factory(exasol_versions=("2025.1.8", "8.29.13", "9.9.9")) + assert conf.minimum_exasol_version == "8.29.13" + + +def test_minimum_exasol_version_when_empty(test_project_config_factory): + conf = test_project_config_factory(exasol_versions=()) + assert conf.minimum_exasol_version is None + + def test_sonar_token_name_can_be_overridden(tmp_path): config = AlternateSonarConfig(project_name="test", root_path=tmp_path) diff --git a/test/unit/nox/_shared_test.py b/test/unit/nox/_shared_test.py index f11773119..55083fc6d 100644 --- a/test/unit/nox/_shared_test.py +++ b/test/unit/nox/_shared_test.py @@ -4,6 +4,8 @@ import pytest from exasol.toolbox.nox._shared import ( + _integration_test_context, + _unit_test_context, get_filtered_python_files, ) @@ -74,3 +76,52 @@ def test_get_filtered_python_files( exceptions = [f for f in actual if f.endswith("sample.py")] assert len(exceptions) == 1 assert "toolbox/sample.py" in exceptions[0] + + +def test_unit_test_context_help_excludes_db_version(nox_session): + captured: dict[str, str] = {} + + def _capture_context(session, parser, default_context, **kwargs): + captured["help_text"] = parser.format_help() + return default_context + + with patch("exasol.toolbox.nox._shared._context", side_effect=_capture_context): + _unit_test_context(nox_session) + + help_text = captured["help_text"] + assert "--coverage" in help_text + assert "--db-version" not in help_text + + +def test_integration_test_context_help_includes_db_version(nox_session): + captured: dict[str, str] = {} + + def _capture_context(session, parser, default_context, **kwargs): + captured["help_text"] = parser.format_help() + return default_context + + with patch("exasol.toolbox.nox._shared._context", side_effect=_capture_context): + _integration_test_context(nox_session) + + help_text = captured["help_text"] + assert "--coverage" in help_text + assert "--db-version" in help_text + + +def test_unit_test_context_has_no_db_version(nox_session): + context = _unit_test_context(nox_session) + + assert context["coverage"] is False + assert "db_version" not in context + + +def test_integration_test_context_uses_minimum_exasol_version( + nox_session, test_project_config_factory +): + config = test_project_config_factory(exasol_versions=("2025.1.8", "8.29.13")) + + with patch("exasol.toolbox.nox._shared.PROJECT_CONFIG", config): + context = _integration_test_context(nox_session) + + assert context["coverage"] is False + assert context["db_version"] == "8.29.13" diff --git a/test/unit/nox/_test_test.py b/test/unit/nox/_test_test.py index cea538df4..c44a347cd 100644 --- a/test/unit/nox/_test_test.py +++ b/test/unit/nox/_test_test.py @@ -5,6 +5,7 @@ from exasol.toolbox.nox._test import ( _test_command, + coverage, integration_tests, unit_tests, ) @@ -16,7 +17,7 @@ def default_context(): """ The default context comes from `toolbox/nox/_shared.py::_context` """ - return {"db_version": "7.1.9", "coverage": False, "fwd-args": []} + return {"db_version": "8.29.13", "coverage": False, "fwd-args": []} class TestTestCommand: @@ -74,9 +75,16 @@ def test_unit_tests(nox_session, test_project_config_factory): test_filepath = test_directory / "dummy_test.py" test_filepath.write_text("def test_dummy():\n assert True") - with patch("exasol.toolbox.nox._test.PROJECT_CONFIG", new=config): + with ( + patch("exasol.toolbox.nox._test.PROJECT_CONFIG", new=config), + patch( + "exasol.toolbox.nox._test._unit_test_context", + wraps=lambda session: {"coverage": False, "fwd-args": []}, + ) as unit_context, + ): unit_tests(nox_session) + unit_context.assert_called_once_with(nox_session) assert (test_directory / "__pycache__").exists() @@ -95,7 +103,36 @@ def test_works_without_hooks(nox_session, test_project_config_factory): test_filepath = test_directory / "dummy_test.py" test_filepath.write_text("def test_dummy():\n assert True") - with patch("exasol.toolbox.nox._test.PROJECT_CONFIG", new=config): + with ( + patch("exasol.toolbox.nox._test.PROJECT_CONFIG", new=config), + patch( + "exasol.toolbox.nox._test._integration_test_context", + wraps=lambda session: { + "coverage": False, + "db_version": "8.29.13", + "fwd-args": [], + }, + ) as integration_context, + ): integration_tests(nox_session) + integration_context.assert_called_once_with(nox_session) assert (test_directory / "__pycache__").exists() + + +def test_coverage_uses_integration_test_context(nox_session): + with ( + patch( + "exasol.toolbox.nox._test._integration_test_context", + return_value={"coverage": True, "db_version": "8.29.13", "fwd-args": []}, + ) as integration_context, + patch("exasol.toolbox.nox._test._coverage") as coverage_impl, + ): + coverage(nox_session) + + integration_context.assert_called_once_with(nox_session, coverage=True) + coverage_impl.assert_called_once_with( + nox_session, + PROJECT_CONFIG, + {"coverage": True, "db_version": "8.29.13", "fwd-args": []}, + )