Skip to content

Move to nose2 only#6146

Open
klecki wants to merge 12 commits intoNVIDIA:mainfrom
klecki:nose2-only
Open

Move to nose2 only#6146
klecki wants to merge 12 commits intoNVIDIA:mainfrom
klecki:nose2-only

Conversation

@klecki
Copy link
Copy Markdown
Contributor

@klecki klecki commented Dec 22, 2025

Use nose2 as the only testing framework. Drop nose.

Category: Other

Description:

Remove the WARs used to keep nose alive.

nose2 supports the yield-style test discovery by default @attr has a different filtering syntax (-A) and just checks for presence of truthy test_foo.attribute_name. A decorator uses this mechanism for backward compatibility.

nose2 splits with_setup(setup, teardown) into two separate decorators, a backward compatible decorator is added.

nottest sets special attribute.

SkipTest from unittest is recommended to be used directly (with the same functionality).

Test scripts are adjusted with minimal changes to run through nose2. Followup cleanup can be used for renaming.

Replace unsupported -m regex by attributes

Additional information:

Affected modules and functionalities:

Key points relevant for the review:

Tests:

  • Existing tests apply
  • New tests added
    • Python tests
    • GTests
    • Benchmark
    • Other
  • N/A

Checklist

Documentation

  • Existing documentation applies
  • Documentation updated
    • Docstring
    • Doxygen
    • RST
    • Jupyter
    • Other
  • N/A

DALI team only

Requirements

  • Implements new requirements
  • Affects existing requirements
  • N/A

REQ IDs: N/A

JIRA TASK: N/A

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Dec 22, 2025

Greptile Summary

This PR completes the migration from nose to nose2 as the sole Python test runner, removing all nose compatibility shims, the nose_wrapper entry-point, and nose's with_setup/attr/nottest imports in favour of stdlib unittest, a custom attr decorator, and nose2's @params/@cartesian_params. MXNet/Gluon tests are also removed as part of the cleanup.

Key changes:

  • nose_utils.py — stripped down to stdlib/nose2 equivalents; attr, nottest, assert_raises, assert_warns rewritten using a shared unittest.TestCase instance; nose compatibility workarounds for Python 3.10–3.12 (imp, pkg_resources, collections.Callable) are deleted.
  • nose2_attrib_generators.py (new) — a custom nose2 plugin that monkey-patches Generators._testsFromGeneratorFunc to evaluate -A attribute filters before calling generator functions, preventing premature imports of optional heavy dependencies.
  • unittest*.cfg — updated to load nose2_attrib_generators ahead of nose2.plugins.attrib.
  • CI shell scripts — all invocations converted from python -m nose_wrapper --attr to python -m nose2 -A; .py extensions dropped from module paths.
  • Test files — yield-generator-style tests converted to @params-annotated methods in unittest.TestCase-compatible classes; @with_setup replaced by setUp/tearDown.
  • One minor inconsistency remains: !mxnet is still present in two attribute filters for test_dali_variable_batch_size in qa/TL0_python-self-test-core/test_body.sh even though all mxnet-tagged tests were removed (harmless, but inconsistent with the rest of the script).

Confidence Score: 4/5

  • PR is safe to merge; the single remaining issue is a benign stale !mxnet attribute filter that has no effect on test execution.
  • The migration is systematic and complete across all 53 files. Previous blocking issues (missing @attr("sanitizer_skip") decorators, --attr vs -A flag, and the _build_attribs_list comment) were all addressed. The only remaining finding is two residual !mxnet strings in attribute filters for test_dali_variable_batch_size — harmless no-ops since no tests carry that attribute anymore, but worth cleaning up for consistency.
  • qa/TL0_python-self-test-core/test_body.sh (lines 33–35): stale !mxnet filter fragments

Important Files Changed

Filename Overview
dali/test/python/nose2_attrib_generators.py New nose2 plugin that monkey-patches Generators._testsFromGeneratorFunc to apply -A attribute filters before calling generator functions; duplicated parsing logic from nose2 internals is acknowledged and documented.
dali/test/python/nose_utils.py Drops all nose imports and WAR shims; provides lightweight replacements for attr, nottest, assert_raises, and assert_warns built on stdlib unittest, with a module-level _test_case singleton for assertion helpers.
dali/test/python/unittest.cfg Adds nose2_attrib_generators plugin ahead of nose2.plugins.attrib so generator functions are attribute-filtered before being called.
qa/test_template_impl.sh Removes the nose wrapper runner and the dual-runner transition variables; python_new_invoke_test is now the single test runner used everywhere.
qa/nose_wrapper/main.py File deleted — the old nose wrapper entry-point that patched inspect.getargspec and imp for Python 3.11/3.12 compatibility is no longer needed.
qa/TL0_python-self-test-core/test_body.sh Fully migrated to python_new_invoke_test with -A filters; two attribute filter expressions for test_dali_variable_batch_size still redundantly include !mxnet.
dali/test/python/test_pool.py Refactored from yield-generator style with @with_setup/check_pool helper to class-based TestPoolOneCallback with setUp/tearDown and @params(*start_methods).
dali/test/python/test_external_source_parallel.py Refactored fork/setup tests into TestParallelFork class; CPU-only test renamed to _test_parallel_fork_cpu_only and invoked explicitly in CI scripts so it runs before any CUDA context is initialized.
dali/test/python/test_dali_tf_dataset_eager.py Generator-style tests converted to parameterized classes with setUp calling skip_inputs_for_incompatible_tf; random choice now seeded with random.Random(42) for determinism.
dali/test/python/test_fw_iterators.py All MXNet/Gluon iterator tests removed; remaining tests converted from yield generators to @params-based functions; large reduction in file size.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[nose2 CLI\npython -m nose2 -A filter] --> B[AttributeGeneratorFilter.handleArgs\nnose2_attrib_generators.py]
    B --> C{Generators plugin\nfound?}
    C -- Yes --> D[Monkey-patch\n_testsFromGeneratorFunc]
    C -- No --> E[Log warning\nno-op]
    D --> F[Test Discovery]
    F --> G{Is test a\ngenerator function?}
    G -- Yes --> H[patched_testsFromGeneratorFunc\nevent, obj]
    G -- No --> I[Normal nose2\ndiscovery]
    H --> J{_matches_attrib_filter\nobj, attrib_plugin}
    J -- No match --> K[Return empty list\nskip generator]
    J -- Match --> L[Call original\n_testsFromGeneratorFunc]
    L --> M[Yield parameterised\ntest cases]
    I --> N[Run tests]
    M --> N
    K --> O[Test skipped]
Loading

Reviews (14): Last reviewed commit: "Review fix" | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (1)

  1. dali/test/python/nose_utils.py, line 129-130 (link)

    style: creating a new TestCase instance for each assertion call is unconventional

    Consider creating a module-level instance once:

    Then define tc at module level (after imports) and reuse it in both assert_raises and assert_warns

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

25 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (2)

  1. dali/test/python/test_functional_api.py, line 22 (link)

    logic: This file still imports nose directly and uses nose.tools.eq_ on line 188. Since nose is being removed as a dependency, this will cause an import error.

  2. dali/test/python/test_functional_api.py, line 188 (link)

    logic: Replace nose.tools.eq_ with standard assertion or use assert_equals from nose_utils

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

28 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (1)

  1. dali/test/python/nose_utils.py, line 68 (link)

    syntax: missing self parameter - instance methods must have self as first parameter

29 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@klecki
Copy link
Copy Markdown
Contributor Author

klecki commented Dec 22, 2025

!build

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [40627092]: BUILD STARTED

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [40627092]: BUILD FAILED

@klecki
Copy link
Copy Markdown
Contributor Author

klecki commented Dec 22, 2025

!build

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [40628704]: BUILD STARTED

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (1)

  1. dali/test/python/nose_utils.py, line 83-85 (link)

    style: Consider using the module-level _test_case instance instead of creating a new empty_case() for each assertion:

    This is more efficient and aligns with how assert_raises and assert_warns are implemented.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

29 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@JanuszL JanuszL self-assigned this Dec 22, 2025
@JanuszL
Copy link
Copy Markdown
Contributor

JanuszL commented Dec 22, 2025

Do we need to touch unittest_failure.cfg, unittest_slow.cfg or unittest.cfg as well?

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [40628704]: BUILD FAILED

Copy link
Copy Markdown
Contributor

@mzient mzient left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way the tests are first discovered and then run will most certainly prevent parallel external source from working (see test job 247483858) was the primary reason for not pursuing full transition to nose2 to date.
Perhaps you can move the troublesome tests to separate files to guarantee proper execution order.

Also, some of the tests used to generate vast amounts of data in the "discovery" stage, because yielding in nose didn't cause data accumulation. Please make sure that this is not the case any more - the tests might work when run one by one but case out-of-memory condition when multiple test files are run.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (1)

  1. dali/test/python/nose_utils.py, line 74 (link)

    syntax: Missing self parameter in method definition

33 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Dec 30, 2025

Greptile's behavior is changing!

From now on, if a review finishes with no comments, we will not post an additional "statistics" comment to confirm that our review found nothing to comment on. However, you can confirm that we reviewed your changes in the status check section.

This feature can be toggled off in your Code Review Settings by deselecting "Create a status check for each PR".

expected_out,
)

def test_numba_func_with_cond(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing @attr("sanitizer_skip") decorator

test_numba_func_with_cond is missing the @attr("sanitizer_skip") decorator that was present on the original standalone function. Without this attribute, the test will not be excluded when running with sanitizers enabled (via the -A '!sanitizer_skip' filter used in CI), causing potential test failures or false positives in sanitizer runs.

Suggested change
def test_numba_func_with_cond(self):
@attr("sanitizer_skip")
def test_numba_func_with_cond(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

expected_out=[np.full((10, 10, 10), 255, dtype=np.uint8)],
enable_conditionals=True,
)
def test_numba_func_with_cond_do_not_convert(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing @attr("sanitizer_skip") decorator

test_numba_func_with_cond_do_not_convert is missing the @attr("sanitizer_skip") decorator that was present on the original standalone function. Without this attribute, the test will not be excluded when running with sanitizers enabled (via the -A '!sanitizer_skip' filter used in CI), causing potential test failures or false positives in sanitizer runs.

Suggested change
def test_numba_func_with_cond_do_not_convert(self):
@attr("sanitizer_skip")
def test_numba_func_with_cond_do_not_convert(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Signed-off-by: Janusz Lisiecki <jlisiecki@nvidia.com>
Comment on lines +46 to +82
def _build_attribs_list(self, attrib_plugin):
"""Build the attribs list from the attrib plugin's -A configuration.

This replicates the logic from AttributeSelector.moduleLoadedSuite
for -A filters only (not -E eval filters).
"""
attribs = []

# Handle -A (attribute) filters
for attr in attrib_plugin.attribs:
attr_group = []
for attrib in attr.strip().split(","):
if not attrib:
continue
items = attrib.split("=", 1)
if len(items) > 1:
# "name=value"
key, value = items
else:
key = items[0]
if key[0] == "!":
# "!name"
key = key[1:]
value = False
else:
# "name"
value = True
attr_group.append((key, value))
attribs.append(attr_group)

return attribs

def _matches_attrib_filter(self, test_func, attrib_plugin):
"""Check if test_func matches the attribute filter from attrib plugin."""
if not attrib_plugin:
return True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated internal attribute-parsing logic from nose2

_build_attribs_list manually replicates the parsing logic inside AttributeSelector.moduleLoadedSuite (including the comma-split, =-split, and !-negation handling). If nose2 changes how it parses -A expressions (e.g. to support quoting, ranges, or OR-groups within a single -A value), this copy will silently diverge and produce incorrect filter decisions for generator functions only.

Consider whether you can obtain the parsed attribs structure directly from the attrib_plugin instance after argument processing, rather than re-parsing from the raw string. For example, attrib_plugin.attribs may already be processed by the time handleArgs fires. If not, at minimum adding a comment explaining that this must be kept in sync with nose2's AttributeSelector internals would prevent silent future breakage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment explaining that.

@JanuszL
Copy link
Copy Markdown
Contributor

JanuszL commented Mar 10, 2026

!build

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [45822574]: BUILD STARTED

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [45743750]: BUILD PASSED

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [45822574]: BUILD FAILED

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [45822574]: BUILD PASSED

Signed-off-by: Janusz Lisiecki <jlisiecki@nvidia.com>
@JanuszL
Copy link
Copy Markdown
Contributor

JanuszL commented Mar 12, 2026

!build

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [45987009]: BUILD STARTED

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [45987009]: BUILD FAILED

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [45987009]: BUILD PASSED

Signed-off-by: Janusz Lisiecki <jlisiecki@nvidia.com>
@JanuszL JanuszL removed their assignment Mar 18, 2026
@JanuszL JanuszL requested review from JanuszL and removed request for JanuszL March 18, 2026 12:20
@JanuszL JanuszL requested review from JanuszL and removed request for JanuszL March 25, 2026 17:30
@JanuszL
Copy link
Copy Markdown
Contributor

JanuszL commented Mar 25, 2026

@greptileai please rereview and close adressed comments

@JanuszL
Copy link
Copy Markdown
Contributor

JanuszL commented Mar 25, 2026

!build

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [46987477]: BUILD STARTED

@dali-automaton
Copy link
Copy Markdown
Collaborator

CI MESSAGE: [46987477]: BUILD PASSED

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants