From 26b1d902614e9f865b81a20f5a67acb969f05d59 Mon Sep 17 00:00:00 2001 From: Yash Sangwan Date: Mon, 24 Aug 2026 18:40:46 +0530 Subject: [PATCH 1/2] [BUG] mute stderr in QuickTester.run_tests, and fix np.all generator checks Two fixes, both found while refactoring the sktime test framework to inherit from skbase. 1. `run_tests` muted stdout only, so anything a test wrote to stderr still reached the log at `verbose < 2`. It now mutes both streams, matching what `sktime` does downstream. `StderrMute` already shipped in `skbase.utils.stderr_mute` but was never used here. 2. Three checks passed a generator expression to `np.all` instead of a list. `np.all` receives the generator object rather than an array of booleans, and every generator object is truthy, so the checks always passed and their bodies never ran. - `_check_none_str_or_list_of_str` never rejected invalid input, so `tests_to_run` and friends were unvalidated. - the two checks in `test_create_test_instances_and_names` never ran. The second of those also passed `names`, a list of str, as the second argument to `isinstance`, which is not a type. Wrapping the comprehension alone would raise TypeError, so it is corrected to `str`, which is what the assert message already says it checks. Verified: full test suite is 1609 passed, 23 skipped, identical to main. An object returning non str names or non instances is now caught by `test_create_test_instances_and_names`, and was not before. At `verbose=0` stderr no longer leaks, and `verbose=2` still shows both streams. --- skbase/testing/test_all_objects.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/skbase/testing/test_all_objects.py b/skbase/testing/test_all_objects.py index d8a715e1..bd12f7a7 100644 --- a/skbase/testing/test_all_objects.py +++ b/skbase/testing/test_all_objects.py @@ -342,6 +342,7 @@ def run_tests( """ from _pytest.outcomes import Skipped + from skbase.utils.stderr_mute import StderrMute from skbase.utils.stdout_mute import StdoutMute tests_to_run = self._check_none_str_or_list_of_str( @@ -464,7 +465,10 @@ def print_if_verbose(msg): print_if_verbose(f"{key}") try: - with StdoutMute(active=verbose < 2): + with ( + StdoutMute(active=verbose < 2), + StderrMute(active=verbose < 2), + ): test_fun(**deepcopy(args)) results[key] = "PASSED" print_if_verbose("PASSED") @@ -541,7 +545,7 @@ def _check_none_str_or_list_of_str(obj, var_name="obj"): obj = [obj] if not isinstance(obj, list): raise ValueError(msg) - if not np.all(isinstance(x, str) for x in obj): + if not np.all([isinstance(x, str) for x in obj]): raise ValueError(msg) return obj @@ -715,12 +719,12 @@ def test_create_test_instances_and_names(self, object_class): f"found {type(names)}" ) - assert np.all(isinstance(est, object_class) for est in objects), ( + assert np.all([isinstance(est, object_class) for est in objects]), ( "list elements of first return returned by create_test_instances_and_names " "all must be an instance of the class" ) - assert np.all(isinstance(name, names) for name in names), ( + assert np.all([isinstance(name, str) for name in names]), ( "list elements of second return returned by create_test_instances_and_names" " all must be strings" ) From eca47f7438406f2912618f5b6fab8b4d7dd21a62 Mon Sep 17 00:00:00 2001 From: Yash Sangwan Date: Tue, 25 Aug 2026 10:11:33 +0530 Subject: [PATCH 2/2] [MNT] add regression tests for QuickTester input checks and stderr muting --- skbase/testing/tests/__init__.py | 2 + skbase/testing/tests/test_quicktester.py | 47 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 skbase/testing/tests/__init__.py create mode 100644 skbase/testing/tests/test_quicktester.py diff --git a/skbase/testing/tests/__init__.py b/skbase/testing/tests/__init__.py new file mode 100644 index 00000000..3b6c2e93 --- /dev/null +++ b/skbase/testing/tests/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +"""Tests for the skbase testing module.""" diff --git a/skbase/testing/tests/test_quicktester.py b/skbase/testing/tests/test_quicktester.py new file mode 100644 index 00000000..eecc5b14 --- /dev/null +++ b/skbase/testing/tests/test_quicktester.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +"""Regression tests for QuickTester input checks and output muting.""" + +import sys +from typing import List + +import pytest + +from skbase.testing import BaseFixtureGenerator, QuickTester +from skbase.tests.conftest import Parent + +__author__: List[str] = ["yash-sangwan"] + + +class _NoisyTester(BaseFixtureGenerator, QuickTester): + """Minimal test class that writes to both output streams.""" + + def test_writes_to_streams(self, object_class): + """Write a marker to stdout and to stderr.""" + sys.stdout.write("stdout-marker") + sys.stderr.write("stderr-marker") + + +@pytest.mark.parametrize("bad", [42, ["a", 5], {"a": 1}]) +def test_check_none_str_or_list_of_str_rejects_invalid(bad): + """Invalid input raises; a generator passed to np.all would never raise.""" + with pytest.raises(ValueError, match="must be None, str, or list of str"): + QuickTester._check_none_str_or_list_of_str(bad, var_name="x") + + +@pytest.mark.parametrize( + "good, expected", [(None, None), ("a", ["a"]), (["a", "b"], ["a", "b"])] +) +def test_check_none_str_or_list_of_str_accepts_valid(good, expected): + """Valid input is returned, coerced to list of str.""" + assert QuickTester._check_none_str_or_list_of_str(good, var_name="x") == expected + + +@pytest.mark.parametrize("verbose, muted", [(0, True), (2, False)]) +def test_run_tests_mutes_stdout_and_stderr(capsys, verbose, muted): + """Both streams are muted below verbose=2, and both are shown at verbose=2.""" + _NoisyTester().run_tests( + Parent, tests_to_run="test_writes_to_streams", verbose=verbose + ) + captured = capsys.readouterr() + assert ("stdout-marker" not in captured.out) is muted + assert ("stderr-marker" not in captured.err) is muted