├── nose2 ├── tests │ ├── unit │ │ ├── __init__.py │ │ ├── test_collect_plugin.py │ │ ├── test_result.py │ │ ├── test_decorators.py │ │ ├── test_config.py │ │ ├── test_dundertest_plugin.py │ │ ├── test_prof_plugin.py │ │ ├── test_session.py │ │ ├── test_plugin_api.py │ │ ├── test_collector.py │ │ ├── test_failfast.py │ │ ├── test_logcapture_plugin.py │ │ ├── test_attrib_plugin.py │ │ ├── test_outcomes_plugin.py │ │ ├── test_debugger_plugin.py │ │ ├── test_functions_loader.py │ │ ├── test_generators_plugin.py │ │ ├── test_testcase_loader.py │ │ ├── test_mp_plugin.py │ │ ├── test_doctest_plugin.py │ │ └── test_buffer_plugin.py │ ├── functional │ │ ├── __init__.py │ │ ├── support │ │ │ ├── cfg │ │ │ │ ├── b.cfg │ │ │ │ └── a.cfg │ │ │ ├── scenario │ │ │ │ ├── layers_and_non_layers │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── test_such_with_has_setup.py │ │ │ │ │ ├── test_such_with_uses_decorator.py │ │ │ │ │ ├── test_layers.py │ │ │ │ │ └── common.py │ │ │ │ ├── module_import_err │ │ │ │ │ ├── pkg │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── test_attribute_err.py │ │ │ │ │ │ └── test_import_err.py │ │ │ │ │ └── test_import_err.py │ │ │ │ ├── test_with_module │ │ │ │ │ ├── lib │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ └── mod1.py │ │ │ │ │ └── test_coverage.py │ │ │ │ ├── colliding_test_modules │ │ │ │ │ └── tests │ │ │ │ │ │ ├── test.py │ │ │ │ │ │ └── more_tests │ │ │ │ │ │ └── test.py │ │ │ │ ├── doctests │ │ │ │ │ ├── doctests_pkg1 │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── docs1.rst │ │ │ │ │ │ ├── docs1.py │ │ │ │ │ │ └── docs1.txt │ │ │ │ │ ├── docs.rst │ │ │ │ │ ├── docs.py │ │ │ │ │ └── docs.txt │ │ │ │ ├── junitxml │ │ │ │ │ ├── empty_properties │ │ │ │ │ │ ├── properties.json │ │ │ │ │ │ ├── unittest.cfg │ │ │ │ │ │ └── test_junitxml_empty_properties.py │ │ │ │ │ ├── non_default_path │ │ │ │ │ │ ├── unittest.cfg │ │ │ │ │ │ └── test_junitxml_non_default_path.py │ │ │ │ │ ├── skip_reason │ │ │ │ │ │ ├── unittest.cfg │ │ │ │ │ │ └── test_junitxml_skip_reason.py │ │ │ │ │ ├── fail_to_write │ │ │ │ │ │ ├── unittest.cfg │ │ │ │ │ │ └── test_junitxml_fail_to_write.py │ │ │ │ │ ├── with_properties │ │ │ │ │ │ ├── unittest.cfg │ │ │ │ │ │ └── test_junitxml_with_properties.py │ │ │ │ │ ├── missing_properties │ │ │ │ │ │ ├── unittest.cfg │ │ │ │ │ │ └── test_junitxml_missing_properties.py │ │ │ │ │ ├── happyday │ │ │ │ │ │ └── test_junitxml_happyday.py │ │ │ │ │ └── chdir │ │ │ │ │ │ └── test_junitxml_chdir.py │ │ │ │ ├── no_tests │ │ │ │ │ └── a.py │ │ │ │ ├── pretty_asserts │ │ │ │ │ ├── ignore_passing │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ └── test_prettyassert_ignore_passing.py │ │ │ │ │ ├── unittest_assertion │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ └── test_prettyassert_unittestassertion.py │ │ │ │ │ ├── conf_on │ │ │ │ │ │ ├── nose2.cfg │ │ │ │ │ │ └── test_conf_on.py │ │ │ │ │ ├── simple_global │ │ │ │ │ │ └── test_simple_global.py │ │ │ │ │ ├── multiline_statement │ │ │ │ │ │ └── test_multiline_statement.py │ │ │ │ │ ├── attribute_resolution2 │ │ │ │ │ │ └── test_prettyassert_attribute_resolution2.py │ │ │ │ │ ├── assign_after_assert │ │ │ │ │ │ └── test_assign_after_assert.py │ │ │ │ │ ├── attribute_resolution │ │ │ │ │ │ └── test_prettyassert_attribute_resolution.py │ │ │ │ │ └── multiline_funcdef │ │ │ │ │ │ └── test_multiline_funcdef.py │ │ │ │ ├── tests_in_package │ │ │ │ │ ├── pkg1 │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── test │ │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ │ └── test_things.py │ │ │ │ │ │ └── mod1.py │ │ │ │ │ ├── docs.rst │ │ │ │ │ ├── docs.txt │ │ │ │ │ ├── unittest.cfg │ │ │ │ │ └── setup.py │ │ │ │ ├── coverage_config_fail_under │ │ │ │ │ ├── covered_lib │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ └── mod1.py │ │ │ │ │ ├── .coveragerc │ │ │ │ │ └── test_mod.py │ │ │ │ ├── coverage_multiprocessing_with_combine │ │ │ │ │ ├── lib │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ └── mod1.py │ │ │ │ │ ├── .coveragerc │ │ │ │ │ ├── nose2.cfg │ │ │ │ │ └── test_lib.py │ │ │ │ ├── tests_in_zipped_eggs │ │ │ │ │ ├── docs.rst │ │ │ │ │ ├── docs.txt │ │ │ │ │ ├── unittest.cfg │ │ │ │ │ ├── pkgegg-0.0.0-py2.7.egg │ │ │ │ │ └── setup.py │ │ │ │ ├── coverage_config_fail_under2 │ │ │ │ │ ├── part_covered_lib │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ └── mod1.py │ │ │ │ │ ├── .coveragerc │ │ │ │ │ ├── nose2.cfg │ │ │ │ │ └── test_part_covered_mod.py │ │ │ │ ├── coverage_multiprocessing_without_combine │ │ │ │ │ ├── lib │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ └── mod1.py │ │ │ │ │ ├── .coveragerc │ │ │ │ │ ├── nose2.cfg │ │ │ │ │ └── test_lib.py │ │ │ │ ├── tests_in_unzipped_eggs │ │ │ │ │ ├── docs.rst │ │ │ │ │ ├── pkgunegg-0.0.0-py2.7.egg │ │ │ │ │ │ ├── EGG-INFO │ │ │ │ │ │ │ ├── zip-safe │ │ │ │ │ │ │ ├── dependency_links.txt │ │ │ │ │ │ │ ├── top_level.txt │ │ │ │ │ │ │ ├── PKG-INFO │ │ │ │ │ │ │ └── SOURCES.txt │ │ │ │ │ │ └── pkgunegg │ │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ │ ├── test │ │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ │ └── test_things.py │ │ │ │ │ │ │ └── mod1.py │ │ │ │ │ ├── docs.txt │ │ │ │ │ ├── unittest.cfg │ │ │ │ │ └── setup.py │ │ │ │ ├── test_coverage_config │ │ │ │ │ ├── nose2cfg │ │ │ │ │ │ ├── covered_lib_nose2cfg │ │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ │ └── mod1.py │ │ │ │ │ │ ├── nose2.cfg │ │ │ │ │ │ └── test_nose2cfg.py │ │ │ │ │ └── coveragerc │ │ │ │ │ │ ├── covered_lib_coveragerc │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ └── mod1.py │ │ │ │ │ │ ├── .coveragerc │ │ │ │ │ │ └── test_coveragerc.py │ │ │ │ ├── load_tests_pkg │ │ │ │ │ ├── ltpkg │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ └── tests │ │ │ │ │ │ │ ├── test_find_these.py │ │ │ │ │ │ │ └── __init__.py │ │ │ │ │ ├── ltpkg2 │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ └── tests │ │ │ │ │ │ │ ├── test_skip_these.py │ │ │ │ │ │ │ └── __init__.py │ │ │ │ │ └── unittest.cfg │ │ │ │ ├── many_tests_socket │ │ │ │ │ ├── nose2.cfg │ │ │ │ │ └── test_gen_many_socket_func.py │ │ │ │ ├── many_tests │ │ │ │ │ └── test_gen_many_func.py │ │ │ │ ├── logging_config │ │ │ │ │ ├── nose2.cfg │ │ │ │ │ └── logging_config.py │ │ │ │ ├── dundertest_attribute │ │ │ │ │ └── test.py │ │ │ │ ├── package_in_lib │ │ │ │ │ ├── lib │ │ │ │ │ │ └── pkg2 │ │ │ │ │ │ │ └── __init__.py │ │ │ │ │ └── tests.py │ │ │ │ ├── coverage_of_imports │ │ │ │ │ ├── lib20171102 │ │ │ │ │ │ ├── mod1.py │ │ │ │ │ │ └── __init__.py │ │ │ │ │ └── test_import_coverage.py │ │ │ │ ├── one_test │ │ │ │ │ └── tests.py │ │ │ │ ├── layers_hooks │ │ │ │ │ ├── test_simple_such.py │ │ │ │ │ └── test_layers_simple.py │ │ │ │ ├── module_fixtures │ │ │ │ │ ├── test_mf_func.py │ │ │ │ │ ├── test_mf_gen_func.py │ │ │ │ │ ├── test_mf_param_func.py │ │ │ │ │ └── test_mf_testcase.py │ │ │ │ ├── layers_with_errors │ │ │ │ │ ├── test_layer_setup_fail.py │ │ │ │ │ ├── test_such_setup_fail.py │ │ │ │ │ ├── test_such_teardown_fail.py │ │ │ │ │ ├── test_layer_teardown_fail.py │ │ │ │ │ ├── test_layers_with_errors.py │ │ │ │ │ └── test_such_with_errors.py │ │ │ │ ├── load_tests │ │ │ │ │ ├── test_filter.py │ │ │ │ │ └── test_simple.py │ │ │ │ ├── test_class_fail │ │ │ │ │ └── test_class_fail.py │ │ │ │ ├── test_classes │ │ │ │ │ ├── test_classes.py │ │ │ │ │ └── test_fixtures.py │ │ │ │ ├── test_classes_mp │ │ │ │ │ ├── test_classes_mp.py │ │ │ │ │ └── test_fixtures_mp.py │ │ │ │ ├── expected_failures │ │ │ │ │ └── expected_failures.py │ │ │ │ ├── logging │ │ │ │ │ └── logging_keeps_copies_of_mutable_objects.py │ │ │ │ ├── slow │ │ │ │ │ └── test_slow.py │ │ │ │ ├── such_with_params │ │ │ │ │ └── such_with_params.py │ │ │ │ ├── decorators │ │ │ │ │ └── test_decorators.py │ │ │ │ ├── class_fixtures │ │ │ │ │ └── test_cf_testcase.py │ │ │ │ ├── layers_and_attributes │ │ │ │ │ └── test_layers_and_attributes.py │ │ │ │ ├── subtests │ │ │ │ │ └── test_subtests.py │ │ │ │ ├── layers_with_inheritance │ │ │ │ │ └── test_layers_with_inheritance.py │ │ │ │ └── layers_setups │ │ │ │ │ ├── higher_layer_setup.py │ │ │ │ │ ├── higher_layer_testsetup_no_test.py │ │ │ │ │ ├── higher_layer_testsetup_with_test.py │ │ │ │ │ └── higher_layer_testsetup_3layers.py │ │ │ ├── toml │ │ │ │ ├── b │ │ │ │ │ └── pyproject.toml │ │ │ │ └── a │ │ │ │ │ └── pyproject.toml │ │ │ ├── lib │ │ │ │ ├── plugin_a.py │ │ │ │ └── layer_hooks_plugin.py │ │ │ └── such │ │ │ │ ├── output.txt │ │ │ │ ├── test_regression_same_havings.py │ │ │ │ ├── test_such_without_layers.py │ │ │ │ └── test_such_timing.py │ │ ├── test_dundertest_plugin.py │ │ ├── test_collect_plugin.py │ │ ├── test_decorators.py │ │ ├── test_util.py │ │ ├── test_main.py │ │ ├── test_printhooks_plugin.py │ │ ├── test_logcapture_plugin.py │ │ ├── test_loadtests_plugin.py │ │ ├── test_doctests_plugin.py │ │ ├── test_attrib_plugin.py │ │ ├── test_eggdiscovery_loader.py │ │ └── test_discovery_loader.py │ └── __init__.py ├── plugins │ ├── loader │ │ ├── __init__.py │ │ ├── loadtests.py │ │ └── eggdiscovery.py │ ├── __init__.py │ ├── _constants.py │ ├── failfast.py │ ├── dundertest.py │ ├── collect.py │ ├── doctests.py │ ├── debugger.py │ ├── printhooks.py │ ├── outcomes.py │ └── prof.py ├── __init__.py ├── tools │ ├── __init__.py │ ├── decorators.py │ └── params.py ├── __main__.py ├── exceptions.py ├── _toml.py ├── runner.py ├── config.py └── collector.py ├── changelog.rst ├── docs ├── dev │ ├── contributing.rst │ ├── main.rst │ ├── utils.rst │ ├── loader.rst │ ├── result.rst │ ├── runner.rst │ ├── exceptions.rst │ ├── event_reference.rst │ ├── internals.rst │ ├── plugin_class_reference.rst │ ├── session_reference.rst │ └── documenting_plugins.rst ├── index.rst ├── plugins │ ├── prof.rst │ ├── testid.rst │ ├── doctests.rst │ ├── buffer.rst │ ├── result.rst │ ├── testcases.rst │ ├── debugger.rst │ ├── functions.rst │ ├── testclasses.rst │ ├── discovery.rst │ ├── generators.rst │ ├── parameters.rst │ ├── loadtests.rst │ ├── outcomes.rst │ ├── dundertests.rst │ ├── collect.rst │ ├── failfast.rst │ ├── logcapture.rst │ ├── attrib_example.py │ ├── eggdiscovery.rst │ ├── coverage.rst │ └── prettyassert.rst ├── params.rst ├── tools.rst ├── decorators.rst ├── contents.rst.inc ├── conf.py ├── getting_started.rst └── plugins.rst ├── setup.py ├── unittest.cfg ├── .flake8 ├── RELEASING.md ├── AUTHORS ├── .github ├── dependabot.yml └── workflows │ ├── test-on-downstream-projects.yaml │ └── build.yaml ├── .coveragerc ├── .readthedocs.yml ├── MANIFEST.in ├── .gitignore ├── Makefile ├── tox.ini ├── .pre-commit-config.yaml ├── contributing.rst ├── pyproject.toml └── LICENSE /nose2/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /changelog.rst: -------------------------------------------------------------------------------- 1 | docs/changelog.rst -------------------------------------------------------------------------------- /nose2/plugins/loader/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/dev/contributing.rst: -------------------------------------------------------------------------------- 1 | ../../contributing.rst -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /nose2/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit and functional tests.""" 2 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/cfg/b.cfg: -------------------------------------------------------------------------------- 1 | [b] 2 | b = 4 3 | 5 4 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_and_non_layers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/module_import_err/pkg/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_with_module/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/colliding_test_modules/tests/test.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/doctests/doctests_pkg1/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /unittest.cfg: -------------------------------------------------------------------------------- 1 | [log-capture] 2 | always-on = True 3 | clear-handlers = true 4 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/doctests/docs.rst: -------------------------------------------------------------------------------- 1 | >>> 1 == 1 2 | True 3 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/empty_properties/properties.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/no_tests/a.py: -------------------------------------------------------------------------------- 1 | """An empty module.""" 2 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/pretty_asserts/ignore_passing/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_package/pkg1/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/colliding_test_modules/tests/more_tests/test.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/pretty_asserts/unittest_assertion/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_package/pkg1/test/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_config_fail_under/covered_lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_multiprocessing_with_combine/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_package/docs.rst: -------------------------------------------------------------------------------- 1 | >>> 1 == 1 2 | True 3 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_zipped_eggs/docs.rst: -------------------------------------------------------------------------------- 1 | >>> 1 == 1 2 | True 3 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_config_fail_under2/part_covered_lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_multiprocessing_without_combine/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/doctests/docs.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> 2 == 2 3 | True 4 | """ 5 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/doctests/doctests_pkg1/docs1.rst: -------------------------------------------------------------------------------- 1 | >>> 1 == 1 2 | True 3 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/docs.rst: -------------------------------------------------------------------------------- 1 | >>> 1 == 1 2 | True 3 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | .. include :: ../README.rst 4 | .. include :: contents.rst.inc 5 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/cfg/a.cfg: -------------------------------------------------------------------------------- 1 | [a] 2 | a = 1 3 | 4 | [unittest] 5 | plugins = plugin_a 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_coverage_config/nose2cfg/covered_lib_nose2cfg/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/toml/b/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.nose2.b] 2 | b = """ 3 | 4 4 | 5 5 | """ 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/doctests/docs.txt: -------------------------------------------------------------------------------- 1 | >>> 2 == 2 2 | True 3 | >>> 3 == 2 4 | False 5 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_coverage_config/coveragerc/covered_lib_coveragerc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/pkgunegg-0.0.0-py2.7.egg/EGG-INFO/zip-safe: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/doctests/doctests_pkg1/docs1.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> 2 == 2 3 | True 4 | """ 5 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/non_default_path/unittest.cfg: -------------------------------------------------------------------------------- 1 | [junit-xml] 2 | path = a.xml 3 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/load_tests_pkg/ltpkg/__init__.py: -------------------------------------------------------------------------------- 1 | def gt(a, b): 2 | return a > b 3 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/load_tests_pkg/ltpkg2/__init__.py: -------------------------------------------------------------------------------- 1 | def lt(a, b): 2 | return a < b 3 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/load_tests_pkg/unittest.cfg: -------------------------------------------------------------------------------- 1 | [unittest] 2 | test-file-pattern = test* 3 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/pretty_asserts/conf_on/nose2.cfg: -------------------------------------------------------------------------------- 1 | [pretty-assert] 2 | always-on = True 3 | -------------------------------------------------------------------------------- /docs/dev/main.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | nose2.main 3 | ========== 4 | 5 | .. automodule :: nose2.main 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/dev/utils.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | nose2.util 3 | ========== 4 | 5 | .. automodule :: nose2.util 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/plugins/prof.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Profiling 3 | ========= 4 | 5 | .. autoplugin :: nose2.plugins.prof.Profiler 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/many_tests_socket/nose2.cfg: -------------------------------------------------------------------------------- 1 | [multiprocess] 2 | bind_address = 127.0.0.1 3 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_package/docs.txt: -------------------------------------------------------------------------------- 1 | >>> 2 == 2 2 | True 3 | >>> 3 == 2 4 | False 5 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/pkgunegg-0.0.0-py2.7.egg/EGG-INFO/dependency_links.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/pkgunegg-0.0.0-py2.7.egg/pkgunegg/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_zipped_eggs/docs.txt: -------------------------------------------------------------------------------- 1 | >>> 2 == 2 2 | True 3 | >>> 3 == 2 4 | False 5 | -------------------------------------------------------------------------------- /nose2/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | from nose2.plugins._constants import DEFAULT_PLUGINS 2 | 3 | __all__ = ("DEFAULT_PLUGINS",) 4 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/doctests/doctests_pkg1/docs1.txt: -------------------------------------------------------------------------------- 1 | >>> 2 == 2 2 | True 3 | >>> 3 == 2 4 | False 5 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_coverage_config/coveragerc/.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | show_missing = True 3 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/docs.txt: -------------------------------------------------------------------------------- 1 | >>> 2 == 2 2 | True 3 | >>> 3 == 2 4 | False 5 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/pkgunegg-0.0.0-py2.7.egg/EGG-INFO/top_level.txt: -------------------------------------------------------------------------------- 1 | pkgunegg 2 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/pkgunegg-0.0.0-py2.7.egg/pkgunegg/test/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /docs/dev/loader.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | nose2.loader 3 | ============ 4 | 5 | .. automodule :: nose2.loader 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/dev/result.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | nose2.result 3 | ============ 4 | 5 | .. automodule :: nose2.result 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/dev/runner.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | nose2.runner 3 | ============ 4 | 5 | 6 | .. automodule :: nose2.runner 7 | :members: 8 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_multiprocessing_with_combine/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | concurrency = multiprocessing 3 | -------------------------------------------------------------------------------- /docs/plugins/testid.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Using Test IDs 3 | ============== 4 | 5 | .. autoplugin :: nose2.plugins.testid.TestId 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_config_fail_under/.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | show_missing = True 3 | fail_under = 80 4 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_config_fail_under2/.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | show_missing = True 3 | fail_under = 100 4 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_multiprocessing_without_combine/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | concurrency = multiprocessing 3 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/toml/a/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.nose2.a] 2 | a = 1 3 | 4 | [tool.nose2.unittest] 5 | plugins = "plugin_a" 6 | -------------------------------------------------------------------------------- /docs/dev/exceptions.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | nose2.exceptions 3 | ================ 4 | 5 | .. automodule :: nose2.exceptions 6 | :members: 7 | -------------------------------------------------------------------------------- /nose2/__init__.py: -------------------------------------------------------------------------------- 1 | from nose2.main import discover, main 2 | 3 | __version__ = "0.15.1" 4 | 5 | __all__ = ("__version__", "discover", "main") 6 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = .git,.tox,__pycache__,.eggs,dist,.venv*,docs,build 3 | max-line-length = 88 4 | extend-ignore = W503,W504,E203,B011 5 | -------------------------------------------------------------------------------- /docs/plugins/doctests.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Loader: Doctests 3 | ================ 4 | 5 | .. autoplugin :: nose2.plugins.doctests.DocTestLoader 6 | -------------------------------------------------------------------------------- /docs/plugins/buffer.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Buffering test output 3 | ===================== 4 | 5 | .. autoplugin :: nose2.plugins.buffer.OutputBufferPlugin 6 | -------------------------------------------------------------------------------- /docs/plugins/result.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Reporting test results 3 | ====================== 4 | 5 | .. autoplugin :: nose2.plugins.result.ResultReporter 6 | -------------------------------------------------------------------------------- /docs/plugins/testcases.rst: -------------------------------------------------------------------------------- 1 | ================== 2 | Loader: Test Cases 3 | ================== 4 | 5 | .. autoplugin :: nose2.plugins.loader.testcases.TestCaseLoader 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/skip_reason/unittest.cfg: -------------------------------------------------------------------------------- 1 | [unittest] 2 | plugins = nose2.plugins.junitxml 3 | 4 | [junit-xml] 5 | always-on = True 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/fail_to_write/unittest.cfg: -------------------------------------------------------------------------------- 1 | [junit-xml] 2 | always-on = False 3 | keep_restricted = False 4 | path = /does/not/exist.xml 5 | -------------------------------------------------------------------------------- /docs/plugins/debugger.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Dropping Into the Debugger 3 | ========================== 4 | 5 | .. autoplugin :: nose2.plugins.debugger.Debugger 6 | -------------------------------------------------------------------------------- /docs/plugins/functions.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Loader: Test Functions 3 | ====================== 4 | 5 | .. autoplugin :: nose2.plugins.loader.functions.Functions 6 | -------------------------------------------------------------------------------- /docs/plugins/testclasses.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Loader: Test Classes 3 | ==================== 4 | 5 | .. autoplugin :: nose2.plugins.loader.testclasses.TestClassLoader 6 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | Releasing nose2 2 | --------------- 3 | 4 | - Update the version in `nose2/__init__.py` 5 | - Update `changelog.rst` 6 | - Commit changes 7 | - Run `make release` 8 | -------------------------------------------------------------------------------- /docs/params.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Parameterized tests 3 | =================== 4 | 5 | .. autofunction :: nose2.tools.params 6 | 7 | See also: :doc:`plugins/parameters` 8 | -------------------------------------------------------------------------------- /docs/plugins/discovery.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Loader: Test discovery 3 | ====================== 4 | 5 | .. autoplugin :: nose2.plugins.loader.discovery.DiscoveryLoader 6 | -------------------------------------------------------------------------------- /docs/plugins/generators.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Loader: Test Generators 3 | ======================= 4 | 5 | .. autoplugin :: nose2.plugins.loader.generators.Generators 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_config_fail_under2/nose2.cfg: -------------------------------------------------------------------------------- 1 | [coverage] 2 | always-on = True 3 | coverage = part_covered_lib 4 | coverage-config = .coveragerc 5 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_multiprocessing_without_combine/nose2.cfg: -------------------------------------------------------------------------------- 1 | [coverage] 2 | always-on = True 3 | coverage = lib 4 | coverage-config = .coveragerc 5 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/empty_properties/unittest.cfg: -------------------------------------------------------------------------------- 1 | [junit-xml] 2 | always-on = False 3 | keep_restricted = False 4 | test_properties = properties.json 5 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/with_properties/unittest.cfg: -------------------------------------------------------------------------------- 1 | [junit-xml] 2 | always-on = False 3 | keep_restricted = False 4 | test_properties = properties.json 5 | -------------------------------------------------------------------------------- /nose2/tools/__init__.py: -------------------------------------------------------------------------------- 1 | from . import decorators, such 2 | from .params import cartesian_params, params 3 | 4 | __all__ = ["cartesian_params", "params", "such", "decorators"] 5 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/missing_properties/unittest.cfg: -------------------------------------------------------------------------------- 1 | [junit-xml] 2 | always-on = False 3 | keep_restricted = False 4 | test_properties = properties.json 5 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/many_tests/test_gen_many_func.py: -------------------------------------------------------------------------------- 1 | def check(_): 2 | pass 3 | 4 | 5 | def test(): 6 | for i in range(0, 600): 7 | yield check, i 8 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_coverage_config/nose2cfg/nose2.cfg: -------------------------------------------------------------------------------- 1 | [coverage] 2 | always-on = True 3 | coverage-report = term-missing 4 | coverage = covered_lib_nose2cfg/ 5 | -------------------------------------------------------------------------------- /docs/plugins/parameters.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Loader: Parameterized Tests 3 | =========================== 4 | 5 | .. autoplugin :: nose2.plugins.loader.parameters.Parameters 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/happyday/test_junitxml_happyday.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Test(unittest.TestCase): 5 | def test(self): 6 | pass 7 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/load_tests_pkg/ltpkg/tests/test_find_these.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Test(unittest.TestCase): 5 | def test(self): 6 | pass 7 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/pretty_asserts/conf_on/test_conf_on.py: -------------------------------------------------------------------------------- 1 | myglob = 1 2 | 3 | 4 | def test_w_global(): 5 | global myglob # noqa: F824 6 | assert myglob == 2 7 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Jason Pellerin 2 | Augie Fackler 3 | Arve Knudsen 4 | Wouter Overmeire 5 | Omer Katz 6 | Ilya Kurnosov 7 | Philip Thiem 8 | Aloys Baillet 9 | Stephen Rosen 10 | Stefan Holek 11 | -------------------------------------------------------------------------------- /docs/plugins/loadtests.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Loader: load_tests protocol 3 | =========================== 4 | 5 | .. autoplugin :: nose2.plugins.loader.loadtests.LoadTestsLoader 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_package/unittest.cfg: -------------------------------------------------------------------------------- 1 | [outcomes] 2 | treat-as-skip = 3 | IOError 4 | TodoError 5 | TypeError 6 | treat-as-fail = 7 | GlormpError 8 | -------------------------------------------------------------------------------- /docs/plugins/outcomes.rst: -------------------------------------------------------------------------------- 1 | =================================== 2 | Mapping exceptions to test outcomes 3 | =================================== 4 | 5 | .. autoplugin :: nose2.plugins.outcomes.Outcomes 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/fail_to_write/test_junitxml_fail_to_write.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Test(unittest.TestCase): 5 | def test(self): 6 | pass 7 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/with_properties/test_junitxml_with_properties.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Test(unittest.TestCase): 5 | def test(self): 6 | pass 7 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/many_tests_socket/test_gen_many_socket_func.py: -------------------------------------------------------------------------------- 1 | def check(_): 2 | pass 3 | 4 | 5 | def test(): 6 | for i in range(0, 600): 7 | yield check, i 8 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/pretty_asserts/simple_global/test_simple_global.py: -------------------------------------------------------------------------------- 1 | myglob = 1 2 | 3 | 4 | def test_w_global(): 5 | global myglob # noqa: F824 6 | assert myglob == 2 7 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/unittest.cfg: -------------------------------------------------------------------------------- 1 | [outcomes] 2 | treat-as-skip = 3 | IOError 4 | TodoError 5 | TypeError 6 | treat-as-fail = 7 | GlormpError 8 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_zipped_eggs/unittest.cfg: -------------------------------------------------------------------------------- 1 | [outcomes] 2 | treat-as-skip = 3 | IOError 4 | TodoError 5 | TypeError 6 | treat-as-fail = 7 | GlormpError 8 | -------------------------------------------------------------------------------- /docs/plugins/dundertests.rst: -------------------------------------------------------------------------------- 1 | ================================ 2 | Default filter: :attr:`__test__` 3 | ================================ 4 | 5 | .. autoplugin :: nose2.plugins.dundertest.DunderTestFilter 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_multiprocessing_with_combine/nose2.cfg: -------------------------------------------------------------------------------- 1 | [coverage] 2 | always-on = True 3 | coverage = lib 4 | coverage-config = .coveragerc 5 | coverage-combine = True 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/empty_properties/test_junitxml_empty_properties.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Test(unittest.TestCase): 5 | def test(self): 6 | pass 7 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/non_default_path/test_junitxml_non_default_path.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Test(unittest.TestCase): 5 | def test(self): 6 | pass 7 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/logging_config/nose2.cfg: -------------------------------------------------------------------------------- 1 | [log-capture] 2 | always-on = True 3 | format = "[%%(name)s] [%%(levelname)s] %%(message)s" 4 | 5 | [pretty-assert] 6 | always-on = True 7 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_package/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | setup(name="pkg1", packages=find_packages(), test_suite="nose2.collector.collector") 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | # check for github-actions updates weekly 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | -------------------------------------------------------------------------------- /docs/plugins/collect.rst: -------------------------------------------------------------------------------- 1 | ===================================== 2 | Collecting tests without running them 3 | ===================================== 4 | 5 | .. autoplugin :: nose2.plugins.collect.CollectOnly 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/missing_properties/test_junitxml_missing_properties.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Test(unittest.TestCase): 5 | def test(self): 6 | pass 7 | -------------------------------------------------------------------------------- /docs/dev/event_reference.rst: -------------------------------------------------------------------------------- 1 | Event reference 2 | =============== 3 | 4 | .. automodule :: nose2.events 5 | :members: 6 | :undoc-members: 7 | :exclude-members: Hook, Plugin, PluginInterface, PluginMeta 8 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/dundertest_attribute/test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestDunderTest(unittest.TestCase): 5 | __test__ = False 6 | 7 | def test_a(self): 8 | pass 9 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_zipped_eggs/pkgegg-0.0.0-py2.7.egg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nose-devs/nose2/HEAD/nose2/tests/functional/support/scenario/tests_in_zipped_eggs/pkgegg-0.0.0-py2.7.egg -------------------------------------------------------------------------------- /docs/plugins/failfast.rst: -------------------------------------------------------------------------------- 1 | ========================================= 2 | Stopping After the First Error or Failure 3 | ========================================= 4 | 5 | .. autoplugin :: nose2.plugins.failfast.FailFast 6 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/package_in_lib/lib/pkg2/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger(__name__) 4 | 5 | 6 | def get_one(): 7 | log.debug("Returning %s", 1) 8 | return 1 9 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/skip_reason/test_junitxml_skip_reason.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Test(unittest.TestCase): 5 | @unittest.skip("ohai") 6 | def test(self): 7 | pass 8 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_of_imports/lib20171102/mod1.py: -------------------------------------------------------------------------------- 1 | # statement run at import time should be covered 2 | foo = 1 3 | 4 | 5 | # so should an ordinary function body 6 | def func(): 7 | return 2 8 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/load_tests_pkg/ltpkg2/tests/test_skip_these.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Test(unittest.TestCase): 5 | def test(self): 6 | raise Exception("this should not execute") 7 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/lib/plugin_a.py: -------------------------------------------------------------------------------- 1 | from nose2 import events 2 | 3 | 4 | class PluginA(events.Plugin): 5 | configSection = "a" 6 | 7 | def __init__(self) -> None: 8 | self.a = self.config.as_int("a", 0) 9 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_with_module/lib/mod1.py: -------------------------------------------------------------------------------- 1 | def covered_func(): 2 | a = 1 3 | a = a + 8 4 | return a 5 | 6 | 7 | def uncovered_func(): 8 | b = 1 9 | b = b + 8 10 | return b 11 | -------------------------------------------------------------------------------- /docs/plugins/logcapture.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Capturing log messages 3 | ====================== 4 | 5 | .. todo :: 6 | 7 | Document all the things. 8 | 9 | 10 | .. autoplugin :: nose2.plugins.logcapture.LogCapture 11 | -------------------------------------------------------------------------------- /docs/tools.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Tools and Helpers 3 | ================= 4 | 5 | Tools for Test Authors 6 | ====================== 7 | 8 | .. toctree :: 9 | :maxdepth: 2 10 | 11 | decorators 12 | params 13 | such_dsl 14 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/module_import_err/pkg/test_attribute_err.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | def test_foo(): 5 | pass 6 | 7 | 8 | class TestFoo(unittest.TestCase): 9 | def test_foo(self): 10 | pass 11 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_with_module/test_coverage.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from lib import mod1 4 | 5 | 6 | class TestLib(unittest.TestCase): 7 | def test1(self): 8 | self.assertEqual(mod1.covered_func(), 9) 9 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_config_fail_under/covered_lib/mod1.py: -------------------------------------------------------------------------------- 1 | def covered_func(): 2 | a = 1 3 | a = a + 8 4 | return a 5 | 6 | 7 | def uncovered_func(): 8 | b = 1 9 | b = b + 8 10 | return b 11 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_config_fail_under/test_mod.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from covered_lib import mod1 4 | 5 | 6 | class TestLib(unittest.TestCase): 7 | def test1(self): 8 | self.assertEqual(mod1.covered_func(), 9) 9 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_config_fail_under2/part_covered_lib/mod1.py: -------------------------------------------------------------------------------- 1 | def covered_func(): 2 | a = 1 3 | a = a + 8 4 | return a 5 | 6 | 7 | def uncovered_func(): 8 | b = 1 9 | b = b + 8 10 | return b 11 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/one_test/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import nose2 4 | 5 | 6 | class Test(unittest.TestCase): 7 | def test(self): 8 | pass 9 | 10 | 11 | if __name__ == "__main__": 12 | nose2.main() 13 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/pretty_asserts/multiline_statement/test_multiline_statement.py: -------------------------------------------------------------------------------- 1 | def test_demo(): 2 | x = 1 3 | y = 2 4 | # fmt: off 5 | assert (x 6 | > 7 | y), "oh noez, x <= y" 8 | # fmt: on 9 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_package/pkg1/mod1.py: -------------------------------------------------------------------------------- 1 | def some_other_func(): 2 | """This is a function with an inline doctest. 3 | 4 | >>> a = 1 5 | >>> b = 2 6 | >>> a == b 7 | False 8 | """ 9 | pass 10 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_multiprocessing_with_combine/test_lib.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from lib.mod1 import method 4 | 5 | 6 | class TestLib(unittest.TestCase): 7 | def test1(self): 8 | self.assertEqual(method(), True) 9 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_multiprocessing_without_combine/test_lib.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from lib.mod1 import method 4 | 5 | 6 | class TestLib(unittest.TestCase): 7 | def test1(self): 8 | self.assertEqual(method(), True) 9 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_coverage_config/nose2cfg/covered_lib_nose2cfg/mod1.py: -------------------------------------------------------------------------------- 1 | def covered_func(): 2 | a = 1 3 | a = a + 8 4 | return a 5 | 6 | 7 | def uncovered_func(): 8 | b = 1 9 | b = b + 8 10 | return b 11 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | setup( 4 | name="pkg1", 5 | packages=find_packages(), 6 | zip_safe=True, 7 | test_suite="nose2.collector.collector", 8 | ) 9 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_zipped_eggs/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | setup( 4 | name="pkg1", 5 | packages=find_packages(), 6 | zip_safe=True, 7 | test_suite="nose2.collector.collector", 8 | ) 9 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_hooks/test_simple_such.py: -------------------------------------------------------------------------------- 1 | from nose2.tools import such 2 | 3 | with such.A("system") as it: 4 | 5 | @it.should("do something") 6 | def test(): 7 | pass 8 | 9 | 10 | it.createTests(globals()) 11 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/module_import_err/test_import_err.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | raise ImportError("booms") 4 | 5 | 6 | def test(): 7 | pass 8 | 9 | 10 | class Test(unittest.TestCase): 11 | def test(self): 12 | pass 13 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/pretty_asserts/unittest_assertion/test_prettyassert_unittestassertion.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestFoo(unittest.TestCase): 5 | def test_old_assertion(self): 6 | x = False 7 | self.assertTrue(x) 8 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_coverage_config/coveragerc/covered_lib_coveragerc/mod1.py: -------------------------------------------------------------------------------- 1 | def covered_func(): 2 | a = 1 3 | a = a + 8 4 | return a 5 | 6 | 7 | def uncovered_func(): 8 | b = 1 9 | b = b + 8 10 | return b 11 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_config_fail_under2/test_part_covered_mod.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from part_covered_lib import mod1 4 | 5 | 6 | class TestLib(unittest.TestCase): 7 | def test1(self): 8 | self.assertEqual(mod1.covered_func(), 9) 9 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_coverage_config/nose2cfg/test_nose2cfg.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from covered_lib_nose2cfg import mod1 4 | 5 | 6 | class TestLib(unittest.TestCase): 7 | def test1(self): 8 | self.assertEqual(mod1.covered_func(), 9) 9 | -------------------------------------------------------------------------------- /nose2/__main__.py: -------------------------------------------------------------------------------- 1 | """Main entry point""" 2 | 3 | import sys 4 | 5 | if sys.argv[0].endswith("__main__.py"): 6 | sys.argv[0] = "nose2" 7 | 8 | __unittest = True 9 | 10 | 11 | if __name__ == "__main__": 12 | from nose2 import discover 13 | 14 | discover() 15 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/module_import_err/pkg/test_import_err.py: -------------------------------------------------------------------------------- 1 | raise ValueError("booms") 2 | 3 | import unittest # noqa: E402 4 | 5 | 6 | def test(): 7 | pass 8 | 9 | 10 | class Test(unittest.TestCase): 11 | def test(self): 12 | pass 13 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_coverage_config/coveragerc/test_coveragerc.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from covered_lib_coveragerc import mod1 4 | 5 | 6 | class TestLib(unittest.TestCase): 7 | def test1(self): 8 | self.assertEqual(mod1.covered_func(), 9) 9 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = nose2/tests/* 4 | include = nose2/* 5 | 6 | [report] 7 | show_missing = True 8 | skip_covered = True 9 | exclude_lines = pragma: no cover 10 | raise NotImplementedError 11 | include = nose2/* 12 | omit = nose2/tests/* 13 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | sphinx: 4 | configuration: docs/conf.py 5 | 6 | build: 7 | os: ubuntu-20.04 8 | tools: 9 | python: "3.10" 10 | 11 | python: 12 | install: 13 | - method: pip 14 | path: . 15 | extra_requirements: 16 | - dev 17 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/pkgunegg-0.0.0-py2.7.egg/pkgunegg/mod1.py: -------------------------------------------------------------------------------- 1 | def some_other_func(): 2 | """This is a function with an inline doctest. 3 | 4 | >>> a = 1 5 | >>> b = 2 6 | >>> a == b 7 | False 8 | """ 9 | pass 10 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_multiprocessing_with_combine/lib/mod1.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Process 2 | 3 | 4 | def method2(): 5 | return 6 | 7 | 8 | def method(): 9 | p = Process(target=method2) 10 | p.start() 11 | p.join() 12 | return True 13 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_multiprocessing_without_combine/lib/mod1.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Process 2 | 3 | 4 | def method2(): 5 | return 6 | 7 | 8 | def method(): 9 | p = Process(target=method2) 10 | p.start() 11 | p.join() 12 | return True 13 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/module_fixtures/test_mf_func.py: -------------------------------------------------------------------------------- 1 | THINGS = [] 2 | 3 | 4 | def setUpModule(): 5 | THINGS.append(1) 6 | 7 | 8 | def tearDownModule(): 9 | while THINGS: 10 | THINGS.pop() 11 | 12 | 13 | def test(): 14 | assert THINGS, "setup didn't run I think" 15 | -------------------------------------------------------------------------------- /docs/decorators.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Decorators 3 | ========== 4 | 5 | nose2 ships with various decorators that assist you to write your tests. 6 | 7 | Setup & Teardown 8 | ================ 9 | 10 | .. autofunction :: nose2.tools.decorators.with_setup 11 | .. autofunction :: nose2.tools.decorators.with_teardown 12 | -------------------------------------------------------------------------------- /docs/dev/internals.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Internals 3 | ========= 4 | 5 | Reference material for things you probably only need to care about if 6 | you want to contribute to nose2. 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | main 12 | exceptions 13 | loader 14 | result 15 | runner 16 | utils 17 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_of_imports/lib20171102/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The __init__ file is loaded *before* the testsuite starts running in test 3 | scenarios. 4 | Therefore, even though it would be *great* if we could check that it gets 5 | counted correctly by coverage, it's better to leave it out. 6 | """ 7 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/pkgunegg-0.0.0-py2.7.egg/EGG-INFO/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: pkgunegg 3 | Version: 0.0.0 4 | Summary: UNKNOWN 5 | Home-page: UNKNOWN 6 | Author: UNKNOWN 7 | Author-email: UNKNOWN 8 | License: UNKNOWN 9 | Description: UNKNOWN 10 | Platform: UNKNOWN 11 | -------------------------------------------------------------------------------- /nose2/tests/functional/test_dundertest_plugin.py: -------------------------------------------------------------------------------- 1 | from nose2.tests._common import FunctionalTestCase 2 | 3 | 4 | class TestDunderTestPlugin(FunctionalTestCase): 5 | def test_dunder(self): 6 | proc = self.runIn("scenario/dundertest_attribute", "-v") 7 | self.assertTestRunOutputMatches(proc, stderr="Ran 0 tests") 8 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/coverage_of_imports/test_import_coverage.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from lib20171102.mod1 import foo, func 4 | 5 | 6 | class TestCase(unittest.TestCase): 7 | def test1(self): 8 | self.assertEqual(foo, 1) 9 | 10 | def test2(self): 11 | self.assertEqual(func(), 2) 12 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/pretty_asserts/attribute_resolution2/test_prettyassert_attribute_resolution2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestFoo(unittest.TestCase): 5 | def test_ohnoez(self): 6 | self.x = 1 7 | 8 | def foo(): 9 | return self 10 | 11 | assert foo().x != self.x 12 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_with_errors/test_layer_setup_fail.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Layer: 5 | @classmethod 6 | def setUp(cls): 7 | raise RuntimeError("Bad Error in Layer setUp!") 8 | 9 | 10 | class Test(unittest.TestCase): 11 | layer = Layer 12 | 13 | def testPass(self): 14 | pass 15 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/pretty_asserts/assign_after_assert/test_assign_after_assert.py: -------------------------------------------------------------------------------- 1 | def test_demo(): 2 | """ 3 | Assign a value to `x` after an assert 4 | Testsuite will want to ensure that we print `x = 1`, which was the value at 5 | the time of the assert 6 | """ 7 | x = 1 8 | assert x == 2 9 | x = 2 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include tox.ini 3 | include unittest.cfg 4 | include README.rst 5 | include license.txt 6 | recursive-include nose2/tests/functional/support *.py *.txt *.toml *.cfg *.rst *.json *.egg .coveragerc 7 | recursive-include docs *.inc *.py *.rst Makefile 8 | global-exclude __pycache__ 9 | global-exclude *~ 10 | global-exclude *.pyc 11 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/module_fixtures/test_mf_gen_func.py: -------------------------------------------------------------------------------- 1 | THINGS = [] 2 | 3 | 4 | def setUpModule(): 5 | THINGS.append(1) 6 | 7 | 8 | def tearDownModule(): 9 | while THINGS: 10 | THINGS.pop() 11 | 12 | 13 | def check(_): 14 | assert THINGS, "setup didn't run I think" 15 | 16 | 17 | def test(): 18 | yield check, 1 19 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/pretty_asserts/attribute_resolution/test_prettyassert_attribute_resolution.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestFoo(unittest.TestCase): 5 | x = 1 6 | 7 | def test_ohnoez(self): 8 | x = self 9 | # fmt: off 10 | assert x.x != (self 11 | ).x 12 | # fmt: on 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | *.pyo 4 | .DS_Store 5 | .nose 6 | *.egg-info 7 | *.bak 8 | *.rej 9 | *$py.class 10 | *.orig 11 | example.cfg 12 | .coverage* 13 | .idea 14 | nosetests.xml 15 | xunit.xml 16 | nose2-junit.xml 17 | _env 18 | docs/_build 19 | htmlcov 20 | cover/* 21 | dist 22 | build 23 | .noseids 24 | .tox 25 | .*.sw[nop] 26 | .dir-locals.el 27 | six-*.egg 28 | .venv-* 29 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/module_fixtures/test_mf_param_func.py: -------------------------------------------------------------------------------- 1 | THINGS = [] 2 | 3 | 4 | def setUpModule(): 5 | THINGS.append(1) 6 | 7 | 8 | def tearDownModule(): 9 | while THINGS: 10 | THINGS.pop() 11 | 12 | 13 | def test(p): 14 | assert THINGS, "setup didn't run I think" 15 | 16 | 17 | test.paramList = (1,) # type: ignore[attr-defined] 18 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/load_tests/test_filter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestCase(unittest.TestCase): 5 | def test_a(self): 6 | pass 7 | 8 | def test_b(self): 9 | pass 10 | 11 | def test_c(self): 12 | pass 13 | 14 | 15 | def load_tests(loader, tests, pattern): 16 | del tests._tests[0]._tests[1] 17 | return tests 18 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/pkgunegg-0.0.0-py2.7.egg/EGG-INFO/SOURCES.txt: -------------------------------------------------------------------------------- 1 | setup.py 2 | pkgunegg/__init__.py 3 | pkgunegg/mod1.py 4 | pkgunegg.egg-info/PKG-INFO 5 | pkgunegg.egg-info/SOURCES.txt 6 | pkgunegg.egg-info/dependency_links.txt 7 | pkgunegg.egg-info/top_level.txt 8 | pkgunegg.egg-info/zip-safe 9 | pkgunegg/test/__init__.py 10 | pkgunegg/test/test_things.py 11 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/load_tests_pkg/ltpkg/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def load_tests(loader, standard_tests, pattern): 5 | # top level directory cached on loader instance 6 | this_dir = os.path.dirname(__file__) 7 | package_tests = loader.discover(start_dir=this_dir, pattern=pattern) 8 | standard_tests.addTests(package_tests) 9 | return standard_tests 10 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/load_tests_pkg/ltpkg2/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | def load_tests(loader, standard_tests, pattern): 5 | suite = loader.suiteClass() 6 | 7 | class Test(unittest.TestCase): 8 | def test(self): 9 | import ltpkg2 10 | 11 | assert ltpkg2.lt(1, 2) 12 | 13 | suite.addTest(Test("test")) 14 | return suite 15 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/logging_config/logging_config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import unittest 3 | 4 | import nose2 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | 9 | class Test(unittest.TestCase): 10 | def test_logging_config(self): 11 | log.debug("foo") 12 | log.info("bar") 13 | assert False 14 | 15 | 16 | if __name__ == "__main__": 17 | nose2.main() 18 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_class_fail/test_class_fail.py: -------------------------------------------------------------------------------- 1 | class Test: 2 | def __init__(self) -> None: 3 | raise RuntimeError( 4 | "Something bad happened but other tests should still be run!" 5 | ) 6 | 7 | def test(self): 8 | raise RuntimeError( 9 | "Something bad happened but other tests should still be run! RUNNING" 10 | ) 11 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_classes/test_classes.py: -------------------------------------------------------------------------------- 1 | class Test: 2 | def test(self): 3 | pass 4 | 5 | def test_gen(self): 6 | def check(a): 7 | pass 8 | 9 | for i in range(0, 5): 10 | yield check, i 11 | 12 | def test_params(self, a): 13 | pass 14 | 15 | test_params.paramList = (1, 2) # type: ignore[attr-defined] 16 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_classes_mp/test_classes_mp.py: -------------------------------------------------------------------------------- 1 | class Test: 2 | def test(self): 3 | pass 4 | 5 | def test_gen(self): 6 | def check(a): 7 | pass 8 | 9 | for i in range(0, 5): 10 | yield check, i 11 | 12 | def test_params(self, a): 13 | pass 14 | 15 | test_params.paramList = (1, 2) # type: ignore[attr-defined] 16 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_with_errors/test_such_setup_fail.py: -------------------------------------------------------------------------------- 1 | from nose2.tools import such 2 | 3 | with such.A("test scenario with errors") as it: 4 | 5 | @it.has_setup 6 | def setup_fail(): 7 | raise RuntimeError("Bad Error in such setUp!") 8 | 9 | @it.should("check that value == 1") 10 | def test_passes(case): 11 | pass 12 | 13 | 14 | it.createTests(globals()) 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: lint test docs build release clean 2 | 3 | NOSE2_VERSION=$(shell grep '^__version__' nose2/__init__.py | cut -d '"' -f2) 4 | 5 | lint: 6 | tox -e lint 7 | test: 8 | tox 9 | docs: 10 | tox -e docs 11 | 12 | build: 13 | tox -e build 14 | 15 | release: 16 | git tag -s "$(NOSE2_VERSION)" -m "v$(NOSE2_VERSION)" 17 | tox -e build,publish 18 | 19 | 20 | clean: 21 | $(MAKE) -C docs/ clean 22 | rm -rf .tox 23 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_with_errors/test_such_teardown_fail.py: -------------------------------------------------------------------------------- 1 | from nose2.tools import such 2 | 3 | with such.A("test scenario with errors") as it: 4 | 5 | @it.has_teardown 6 | def teardown_fail(): 7 | raise RuntimeError("Bad Error in such tearDown!") 8 | 9 | @it.should("check that value == 1") 10 | def test_passes(case): 11 | pass 12 | 13 | 14 | it.createTests(globals()) 15 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/pretty_asserts/multiline_funcdef/test_multiline_funcdef.py: -------------------------------------------------------------------------------- 1 | from nose2.tools.params import params 2 | 3 | # fmt: off 4 | 5 | 6 | # multiline function definition 7 | def test_demo( 8 | ): 9 | x = 1 10 | y = 2 11 | assert x > y, "oh noez, x <= y" 12 | 13 | 14 | @params(('foo',), 15 | ('bar',)) 16 | def test_multiline_deco(value): 17 | assert not value 18 | # fmt: on 19 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_with_errors/test_layer_teardown_fail.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Layer: 5 | @classmethod 6 | def setUp(cls): 7 | pass 8 | 9 | @classmethod 10 | def testTearDown(cls): 11 | raise RuntimeError("Bad Error in Layer testTearDown!") 12 | 13 | 14 | class Test(unittest.TestCase): 15 | layer = Layer 16 | 17 | def testPass(self): 18 | pass 19 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/module_fixtures/test_mf_testcase.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | THINGS = [] 4 | 5 | 6 | def setUpModule(): 7 | THINGS.append(1) 8 | 9 | 10 | def tearDownModule(): 11 | while THINGS: 12 | THINGS.pop() 13 | 14 | 15 | class Test(unittest.TestCase): 16 | def test_1(self): 17 | assert THINGS, "setup didn't run" 18 | 19 | def test_2(self): 20 | assert THINGS, "setup didn't run" 21 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/expected_failures/expected_failures.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestWithExpectedFailures(unittest.TestCase): 5 | @unittest.expectedFailure 6 | def test_should_fail(self): 7 | assert False 8 | 9 | @unittest.expectedFailure 10 | def test_should_pass(self): 11 | assert True 12 | 13 | def test_whatever(self): 14 | assert True 15 | 16 | def test_fails(self): 17 | assert False 18 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/pretty_asserts/ignore_passing/test_prettyassert_ignore_passing.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | import unittest 3 | 4 | 5 | class TestFailAssert(unittest.TestCase): 6 | def test_failing_assert(self): 7 | x = True 8 | y = False 9 | # fmt: off 10 | assert x; assert y 11 | # fmt: on 12 | 13 | def test_failing_assert2(self): 14 | p = 1 15 | q = 0 16 | assert p 17 | assert q 18 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/logging/logging_keeps_copies_of_mutable_objects.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import unittest 3 | 4 | import nose2 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | 9 | class Test(unittest.TestCase): 10 | def test_logging_keeps_copies_of_mutable_objects(self): 11 | d = {} 12 | log.debug("foo: %s", d) 13 | d["bar"] = "baz" 14 | self.assertTrue(False) 15 | 16 | 17 | if __name__ == "__main__": 18 | nose2.main() 19 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/load_tests/test_simple.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestCase(unittest.TestCase): 5 | def test_a(self): 6 | pass 7 | 8 | def test_b(self): 9 | pass 10 | 11 | def test_c(self): 12 | pass 13 | 14 | 15 | def load_tests(loader, tests, pattern): 16 | class InnerTest(unittest.TestCase): 17 | def test_d(self): 18 | pass 19 | 20 | tests.addTest(InnerTest("test_d")) 21 | return tests 22 | -------------------------------------------------------------------------------- /nose2/exceptions.py: -------------------------------------------------------------------------------- 1 | # This module contains some code copied from unittest2/ and other code 2 | # developed in reference to unittest2. 3 | # unittest2 is Copyright (c) 2001-2010 Python Software Foundation; All 4 | # Rights Reserved. See: http://docs.python.org/license.html 5 | __unittest = True 6 | 7 | 8 | class TestNotFoundError(Exception): 9 | """Raised when a named test cannot be found""" 10 | 11 | 12 | class LoadTestsFailure(Exception): 13 | """Raised when a test cannot be loaded""" 14 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/junitxml/chdir/test_junitxml_chdir.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import shutil 3 | import tempfile 4 | import unittest 5 | 6 | 7 | class Test(unittest.TestCase): 8 | def setUp(self): 9 | super().setUp() 10 | 11 | self.temp_dir = tempfile.mkdtemp() 12 | 13 | def tearDown(self): 14 | super().tearDown() 15 | 16 | shutil.rmtree(self.temp_dir, ignore_errors=True) 17 | 18 | def test_chdir(self): 19 | os.chdir(self.temp_dir) 20 | -------------------------------------------------------------------------------- /docs/dev/plugin_class_reference.rst: -------------------------------------------------------------------------------- 1 | Plugin class reference 2 | ====================== 3 | 4 | The plugin system in nose2 is based on the plugin system in 5 | unittest2's ``plugins`` branch. 6 | 7 | 8 | Plugin base class 9 | ----------------- 10 | 11 | .. autoclass :: nose2.events.Plugin 12 | :members: 13 | 14 | 15 | Plugin interface classes 16 | ------------------------ 17 | 18 | .. autoclass :: nose2.events.PluginInterface 19 | :members: 20 | 21 | .. autoclass :: nose2.events.Hook 22 | :members: 23 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/package_in_lib/tests.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import unittest 3 | 4 | from pkg2 import get_one 5 | 6 | log = logging.getLogger(__name__) 7 | log.debug("module imported") 8 | 9 | 10 | def test(): 11 | log.debug("test run") 12 | assert get_one() == 1 13 | 14 | 15 | def test_fail(): 16 | log.debug("test_fail run") 17 | assert get_one() == 2 18 | 19 | 20 | class Tests(unittest.TestCase): 21 | def test_fail2(self): 22 | log.debug("test_fail2 run") 23 | self.assertEqual(get_one(), 4) 24 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_with_errors/test_layers_with_errors.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Layer: 5 | description = "fixture with a value" 6 | 7 | @classmethod 8 | def setUp(cls): 9 | cls.value = 1 10 | 11 | 12 | class Test(unittest.TestCase): 13 | layer = Layer 14 | 15 | def test_ok(self): 16 | self.assertEqual(self.layer.value, 1) 17 | 18 | def test_fail(self): 19 | self.assertEqual(self.layer.value, 2) 20 | 21 | def test_err(self): 22 | self.assertEqual(self.layer.mulch, "pine") 23 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/such/output.txt: -------------------------------------------------------------------------------- 1 | test (test_such.NormalTest) ... ok 2 | A system with complex setup 3 | should do something ... ok 4 | having an expensive fixture 5 | should do more things ... ok 6 | having another precondition 7 | should do that not this ... ok 8 | should do this not that ... ok 9 | having a different precondition 10 | should do something else ... ok 11 | should have another test ... ok 12 | 13 | ---------------------------------------------------------------------- 14 | Ran 7 tests in 0.002s 15 | 16 | OK 17 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/slow/test_slow.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | import unittest 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | class TestSlow(unittest.TestCase): 9 | def test_ok(self): 10 | print("hide this") 11 | time.sleep(2) 12 | 13 | def test_fail(self): 14 | print("show this") 15 | log.debug("hola") 16 | time.sleep(2) 17 | self.assertEqual(1, 2) 18 | 19 | def test_err(self): 20 | print("show this too") 21 | log.debug("ciao") 22 | time.sleep(2) 23 | {}["x"] 24 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/such_with_params/such_with_params.py: -------------------------------------------------------------------------------- 1 | from nose2.tools import such 2 | from nose2.tools.params import params 3 | 4 | with such.A("foo") as it: 5 | 6 | @it.should("do bar") 7 | @params(1, 2, 3) 8 | def test(case, bar): 9 | case.assertTrue(isinstance(bar, int)) 10 | 11 | @it.should("do bar and extra") 12 | @params((1, 2), (3, 4), (5, 6)) 13 | def testExtraArg(case, bar, foo): 14 | case.assertTrue(isinstance(bar, int)) 15 | case.assertTrue(isinstance(foo, int)) 16 | 17 | 18 | it.createTests(globals()) 19 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/such/test_regression_same_havings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from nose2.tools import such 4 | 5 | with such.A("system") as it: 6 | with it.having("something"): 7 | 8 | @it.should("do stuff") 9 | def test_should_do_stuff(case): 10 | pass 11 | 12 | it.createTests(globals()) 13 | 14 | with such.A("another system") as it: 15 | with it.having("something"): 16 | 17 | @it.should("do stuff") # noqa: F811 18 | def test_should_do_stuff(case): # noqa: F811 19 | pass 20 | 21 | it.createTests(globals()) 22 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_collect_plugin.py: -------------------------------------------------------------------------------- 1 | from nose2 import session 2 | from nose2.plugins import collect 3 | from nose2.tests._common import FakeStartTestRunEvent, TestCase 4 | 5 | 6 | class TestCollectOnly(TestCase): 7 | tags = ["unit"] 8 | 9 | def setUp(self): 10 | self.session = session.Session() 11 | self.plugin = collect.CollectOnly(session=self.session) 12 | 13 | def test_startTestRun_sets_executeTests(self): 14 | event = FakeStartTestRunEvent() 15 | self.plugin.startTestRun(event) 16 | self.assertEqual(event.executeTests, self.plugin.collectTests) 17 | -------------------------------------------------------------------------------- /nose2/plugins/_constants.py: -------------------------------------------------------------------------------- 1 | DEFAULT_PLUGINS = ( 2 | "nose2.plugins.loader.discovery", 3 | "nose2.plugins.loader.testcases", 4 | "nose2.plugins.loader.functions", 5 | "nose2.plugins.loader.testclasses", 6 | "nose2.plugins.loader.generators", 7 | "nose2.plugins.loader.parameters", 8 | "nose2.plugins.loader.loadtests", 9 | "nose2.plugins.dundertest", 10 | "nose2.plugins.coverage", 11 | "nose2.plugins.result", 12 | "nose2.plugins.logcapture", 13 | "nose2.plugins.buffer", 14 | "nose2.plugins.failfast", 15 | "nose2.plugins.debugger", 16 | "nose2.plugins.prettyassert", 17 | ) 18 | -------------------------------------------------------------------------------- /nose2/_toml.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import types 4 | 5 | TOML_ENABLED: bool = False 6 | toml: types.ModuleType | None = None 7 | 8 | try: 9 | import tomllib as toml 10 | 11 | TOML_ENABLED = True 12 | except ImportError: 13 | try: 14 | import tomli as toml 15 | 16 | TOML_ENABLED = True 17 | except ImportError: 18 | toml = None 19 | TOML_ENABLED = False 20 | 21 | 22 | def load_toml(file: str) -> dict: 23 | if toml is None: 24 | raise RuntimeError("toml library not found. Please install 'tomli'.") 25 | with open(file, "rb") as fp: 26 | return toml.load(fp) 27 | -------------------------------------------------------------------------------- /docs/dev/session_reference.rst: -------------------------------------------------------------------------------- 1 | Session reference 2 | ================= 3 | 4 | Session 5 | ------- 6 | 7 | In nose2, all configuration for a test run is encapsulated in a 8 | ``Session`` instance. Plugins always have the session available as 9 | ``self.session``. 10 | 11 | .. autoclass :: nose2.session.Session 12 | :members: 13 | 14 | 15 | Config 16 | ------ 17 | 18 | Configuration values loaded from config file sections are made 19 | available to plugins in ``Config`` instances. Plugins that set 20 | ``configSection`` will have a ``Config`` instance available as 21 | ``self.config``. 22 | 23 | .. autoclass :: nose2.config.Config 24 | :members: 25 | -------------------------------------------------------------------------------- /nose2/tests/functional/test_collect_plugin.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from nose2.tests._common import FunctionalTestCase 4 | 5 | 6 | class CollectOnlyFunctionalTest(FunctionalTestCase): 7 | def test_collect_tests_in_package(self): 8 | self.assertTestRunOutputMatches( 9 | self.runIn( 10 | "scenario/tests_in_package", 11 | "-v", 12 | "--collect-only", 13 | "--plugin=nose2.plugins.collect", 14 | ), 15 | stderr=EXPECT_LAYOUT1, 16 | ) 17 | 18 | 19 | # expectations 20 | EXPECT_LAYOUT1 = re.compile( 21 | r"""Ran 25 tests in \d.\d+s 22 | 23 | OK""" 24 | ) 25 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/decorators/test_decorators.py: -------------------------------------------------------------------------------- 1 | from nose2.tools.decorators import with_setup, with_teardown 2 | 3 | setup_performed = False 4 | teardown_performed = False 5 | 6 | 7 | def setup(): 8 | global setup_performed 9 | setup_performed = True 10 | 11 | 12 | def teardown(): 13 | global teardown_performed 14 | teardown_performed = True 15 | 16 | 17 | @with_setup(setup) 18 | def test_with_setup(): 19 | assert setup_performed, "Setup not performed." 20 | 21 | 22 | @with_teardown(teardown) 23 | def test_with_teardown(): 24 | pass 25 | 26 | 27 | def test_teardown_ran(): 28 | assert teardown_performed, "Teardown not performed." 29 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/class_fixtures/test_cf_testcase.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Test(unittest.TestCase): 5 | @classmethod 6 | def setUpClass(cls): 7 | cls.x = 1 8 | 9 | def test_1(self): 10 | assert self.x 11 | 12 | def test_2(self): 13 | assert self.x 14 | 15 | 16 | class Test2(unittest.TestCase): 17 | def setUp(self): 18 | self.x = 1 19 | 20 | def test_1(self): 21 | assert self.x 22 | 23 | def test_2(self): 24 | assert self.x 25 | 26 | 27 | class Test3(Test): 28 | # this has class setup by virtue of inheriting from Test 29 | 30 | def test_3(self): 31 | assert self.x 32 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_with_errors/test_such_with_errors.py: -------------------------------------------------------------------------------- 1 | from nose2.tools import such 2 | 3 | with such.A("test scenario with errors") as it: 4 | 5 | @it.has_setup 6 | def set_value(): 7 | it.value = 1 8 | 9 | @it.should("check that value == 1") 10 | def test_passes(case): 11 | case.assertEqual(it.value, 1) 12 | 13 | @it.should("check that value == 2 and fail") 14 | def test_fails(case): 15 | case.assertEqual(it.value, 2) 16 | 17 | @it.should("check for an attribute that does not exist and raise an error") 18 | def test_err(case): 19 | case.assertEqual(it.mulch, "pine") 20 | 21 | 22 | it.createTests(globals()) 23 | -------------------------------------------------------------------------------- /docs/plugins/attrib_example.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Test(unittest.TestCase): 5 | def test_fast(self): 6 | pass 7 | 8 | test_fast.fast = 1 9 | test_fast.layer = 2 10 | test_fast.flags = ["blue", "green"] 11 | 12 | def test_faster(self): 13 | pass 14 | 15 | test_faster.fast = 1 16 | test_faster.layer = 1 17 | test_faster.flags = ["red", "green"] 18 | 19 | def test_slow(self): 20 | pass 21 | 22 | test_slow.fast = 0 23 | test_slow.slow = 1 24 | test_slow.layer = 2 25 | 26 | def test_slower(self): 27 | pass 28 | 29 | test_slower.slow = 1 30 | test_slower.layer = 3 31 | test_slower.flags = ["blue", "red"] 32 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_result.py: -------------------------------------------------------------------------------- 1 | from nose2 import result, session 2 | from nose2.tests._common import TestCase 3 | 4 | 5 | class TestPluggableTestResult(TestCase): 6 | def setUp(self): 7 | self.session = session.Session() 8 | self.result = result.PluggableTestResult(self.session) 9 | 10 | def test_skip_reason_not_discarded(self): 11 | class Test(TestCase): 12 | def test(self): 13 | pass 14 | 15 | plugin = FakePlugin() 16 | self.session.hooks.register("testOutcome", plugin) 17 | self.result.addSkip(Test("test"), "because") 18 | self.assertEqual(plugin.reason, "because") 19 | 20 | 21 | class FakePlugin: 22 | def testOutcome(self, event): 23 | self.reason = event.reason 24 | -------------------------------------------------------------------------------- /docs/contents.rst.inc: -------------------------------------------------------------------------------- 1 | User's Guide 2 | ============ 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | getting_started 8 | usage 9 | configuration 10 | differences 11 | plugins 12 | tools 13 | changelog 14 | 15 | Plugin Developer's Guide 16 | ======================== 17 | 18 | .. toctree :: 19 | :maxdepth: 2 20 | 21 | dev/writing_plugins 22 | dev/documenting_plugins 23 | dev/event_reference 24 | dev/hook_reference 25 | dev/session_reference 26 | dev/plugin_class_reference 27 | 28 | Developer's Guide 29 | ================= 30 | 31 | .. toctree:: 32 | :maxdepth: 2 33 | 34 | dev/contributing 35 | dev/internals 36 | 37 | Indices and tables 38 | ================== 39 | 40 | * :ref:`genindex` 41 | * :ref:`modindex` 42 | * :ref:`search` 43 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/such/test_such_without_layers.py: -------------------------------------------------------------------------------- 1 | from nose2.tools import such 2 | 3 | with such.A("system with complex setup") as it: 4 | 5 | @it.has_setup 6 | def setup(): 7 | it.things = [1] 8 | 9 | @it.has_teardown 10 | def teardown(): 11 | it.things = [] 12 | 13 | @it.should("do something") 14 | def test(): 15 | assert it.things 16 | it.assertEqual(len(it.things), 1) 17 | 18 | with it.having("an expensive fixture"): 19 | 20 | @it.has_setup # noqa: F811 21 | def setup(): # noqa: F811 22 | it.things.append(2) 23 | 24 | @it.should("do more things") # noqa: F811 25 | def test(case): # noqa: F811 26 | case.assertEqual(it.things[-1], 2) 27 | 28 | 29 | it.createTests(globals()) 30 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_and_non_layers/test_such_with_has_setup.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from nose2.tools import such 4 | 5 | from .common import NormalTest, NormalTestTwo, UniqueResource # noqa: F401 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | with such.A("system with setup") as it: 11 | 12 | @it.has_setup 13 | def setup(): 14 | log.info("Called setup in such test") 15 | it.unique_resource = UniqueResource() 16 | it.unique_resource.lock() 17 | 18 | @it.has_teardown 19 | def teardown(): 20 | log.info("Called teardown in such test") 21 | it.unique_resource.unlock() 22 | 23 | @it.should("do something") 24 | def test(case): 25 | it.assertTrue(it.unique_resource.used) 26 | 27 | 28 | it.createTests(globals()) 29 | -------------------------------------------------------------------------------- /nose2/tests/functional/test_decorators.py: -------------------------------------------------------------------------------- 1 | from nose2.tests._common import FunctionalTestCase 2 | 3 | 4 | class DecoratorsTests(FunctionalTestCase): 5 | def test_with_setup(self): 6 | process = self.runIn("scenario/decorators", "test_decorators.test_with_setup") 7 | 8 | self.assertTestRunOutputMatches(process, stderr="Ran 1 test") 9 | self.assertEqual(process.poll(), 0, process.stderr.getvalue()) 10 | 11 | def test_with_teardown(self): 12 | process = self.runIn( 13 | "scenario/decorators", 14 | "test_decorators.test_with_teardown", 15 | "test_decorators.test_teardown_ran", 16 | ) 17 | 18 | self.assertTestRunOutputMatches(process, stderr="Ran 2 test") 19 | self.assertEqual(process.poll(), 0, process.stderr.getvalue()) 20 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_classes/test_fixtures.py: -------------------------------------------------------------------------------- 1 | class Test: 2 | @classmethod 3 | def setUpClass(cls): 4 | cls.setup = 1 5 | 6 | @classmethod 7 | def tearDownClass(cls): 8 | del cls.setup 9 | 10 | def setUp(self): 11 | self.test_setup = 1 12 | 13 | def tearDown(self): 14 | del self.test_setup 15 | 16 | def test(self): 17 | assert self.test_setup 18 | assert self.setup 19 | 20 | def test_gen(self): 21 | def check(a): 22 | assert self.test_setup 23 | assert self.setup 24 | 25 | for i in range(0, 2): 26 | yield check, i 27 | 28 | def test_params(self, a): 29 | assert self.test_setup 30 | assert self.setup 31 | 32 | test_params.paramList = (1, 2) # type: ignore[attr-defined] 33 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/test_classes_mp/test_fixtures_mp.py: -------------------------------------------------------------------------------- 1 | class Test: 2 | @classmethod 3 | def setUpClass(cls): 4 | cls.setup = 1 5 | 6 | @classmethod 7 | def tearDownClass(cls): 8 | del cls.setup 9 | 10 | def setUp(self): 11 | self.test_setup = 1 12 | 13 | def tearDown(self): 14 | del self.test_setup 15 | 16 | def test(self): 17 | assert self.test_setup 18 | assert self.setup 19 | 20 | def test_gen(self): 21 | def check(a): 22 | assert self.test_setup 23 | assert self.setup 24 | 25 | for i in range(0, 2): 26 | yield check, i 27 | 28 | def test_params(self, a): 29 | assert self.test_setup 30 | assert self.setup 31 | 32 | test_params.paramList = (1, 2) # type: ignore[attr-defined] 33 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_and_attributes/test_layers_and_attributes.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | STATE = {} 4 | 5 | 6 | class L1: 7 | @classmethod 8 | def setUp(cls): 9 | STATE["L1"] = "setup" 10 | 11 | @classmethod 12 | def tearDown(cls): 13 | del STATE["L1"] 14 | 15 | 16 | class L2: 17 | @classmethod 18 | def setUp(cls): 19 | STATE["L2"] = "setup" 20 | 21 | @classmethod 22 | def tearDown(cls): 23 | del STATE["L2"] 24 | 25 | 26 | class LayerAndAttributesA(unittest.TestCase): 27 | layer = L1 28 | a = 1 29 | 30 | def test(self): 31 | self.assertEqual(STATE.get("L1"), "setup") 32 | 33 | 34 | class LayerAndAttributesB(unittest.TestCase): 35 | layer = L2 36 | b = 1 37 | 38 | def test(self): 39 | self.assertEqual(STATE.get("L2"), "setup") 40 | -------------------------------------------------------------------------------- /nose2/plugins/failfast.py: -------------------------------------------------------------------------------- 1 | """ 2 | Stop the test run after the first error or failure. 3 | 4 | This plugin implements :func:`testOutcome` and sets 5 | ``event.result.shouldStop`` if it sees an outcome with exc_info that 6 | is not expected. 7 | 8 | """ 9 | 10 | from nose2 import events 11 | 12 | __unittest = True 13 | 14 | 15 | class FailFast(events.Plugin): 16 | """Stop the test run after error or failure""" 17 | 18 | commandLineSwitch = ( 19 | "F", 20 | "fail-fast", 21 | "Stop the test run after the first error or failure", 22 | ) 23 | 24 | def resultCreated(self, event): 25 | """Mark new result""" 26 | if hasattr(event.result, "failfast"): 27 | event.result.failfast = True 28 | 29 | def testOutcome(self, event): 30 | """Stop on unexpected error or failure""" 31 | if event.exc_info and not event.expected: 32 | event.result.shouldStop = True 33 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_decorators.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for nose2 decorators. 3 | """ 4 | 5 | from nose2.tests._common import TestCase 6 | from nose2.tools.decorators import with_setup, with_teardown 7 | 8 | 9 | class WithSetupDecoratorTests(TestCase): 10 | def fake_setup(self): 11 | pass 12 | 13 | class fake_test: 14 | setup = None 15 | 16 | def test_setup_injection(self): 17 | sut = self.fake_test() 18 | expected = with_setup(self.fake_setup)(sut).setup 19 | 20 | self.assertEqual(expected, self.fake_setup) 21 | 22 | 23 | class WithTeardownDecoratorTests(TestCase): 24 | def fake_teardown(self): 25 | pass 26 | 27 | class fake_test: 28 | teardown = None 29 | 30 | def test_teardown_injection(self): 31 | sut = self.fake_test() 32 | expected = with_teardown(self.fake_teardown)(sut).tearDownFunc 33 | 34 | self.assertEqual(expected, self.fake_teardown) 35 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.insert(0, os.path.abspath("..")) 5 | import nose2 # noqa:E402 6 | 7 | extensions = [ 8 | "sphinx.ext.autodoc", 9 | "sphinx.ext.doctest", 10 | "sphinx.ext.intersphinx", 11 | "sphinx.ext.todo", 12 | "sphinx.ext.coverage", 13 | "sphinx.ext.ifconfig", 14 | "sphinx.ext.viewcode", 15 | "sphinx_issues", 16 | "nose2.sphinxext", 17 | ] 18 | 19 | source_suffix = ".rst" 20 | master_doc = "index" 21 | project = "nose2" 22 | copyright = "2010-2022, Jason Pellerin, Stephen Rosen" 23 | version = release = nose2.__version__ 24 | exclude_patterns = ["_build"] 25 | templates_path = ["_templates"] 26 | 27 | # theme 28 | html_theme = "sphinx_rtd_theme" 29 | 30 | # sphinx-issues 31 | github_user = "nose-devs" 32 | github_repo = "nose2" 33 | issues_github_path = f"{github_user}/{github_repo}" 34 | # intersphinx 35 | intersphinx_mapping = { 36 | "python": ("http://docs.python.org/", None), 37 | } 38 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/subtests/test_subtests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Case(unittest.TestCase): 5 | def test_subtest_success(self): 6 | for i in range(3): 7 | with self.subTest(i=i): 8 | self.assertTrue(i < 3) 9 | 10 | def test_subtest_failure(self): 11 | for i in range(6): 12 | with self.subTest(i=i): 13 | self.assertEqual(i % 2, 0) 14 | 15 | def test_subtest_error(self): 16 | for i in range(3): 17 | with self.subTest(i=i): 18 | raise RuntimeError(i) 19 | 20 | @unittest.expectedFailure 21 | def test_subtest_expected_failure(self): 22 | for i in range(6): 23 | with self.subTest(i=i): 24 | self.assertEqual(i % 2, 0) 25 | 26 | def test_subtest_message(self): 27 | for i in range(6): 28 | with self.subTest("msg", i=i): 29 | self.assertEqual(i % 2, 0) 30 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_with_inheritance/test_layers_with_inheritance.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class L1: 5 | @classmethod 6 | def setUp(cls): 7 | print("L1 setUp") 8 | 9 | @classmethod 10 | def testSetUp(cls): 11 | print("L1 testSetUp") 12 | 13 | @classmethod 14 | def tearDown(cls): 15 | print("L1 tearDown") 16 | 17 | @classmethod 18 | def testTearDown(cls): 19 | print("L1 testTearDown") 20 | 21 | 22 | class L2(L1): 23 | @classmethod 24 | def setUp(cls): 25 | print("L2 setUp") 26 | 27 | @classmethod 28 | def testSetUp(cls): 29 | print("L2 testSetUp") 30 | 31 | @classmethod 32 | def testTearDown(cls): 33 | print("L2 testTearDown") 34 | 35 | 36 | # L1 tearDown should only run once 37 | class T1(unittest.TestCase): 38 | layer = L2 39 | 40 | def test1(self): 41 | print("Run test1") 42 | 43 | def test2(self): 44 | print("Run test2") 45 | -------------------------------------------------------------------------------- /nose2/tests/functional/test_util.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from nose2 import util 4 | from nose2.tests._common import TestCase, support_file 5 | 6 | 7 | class UtilTests(TestCase): 8 | def test_name_from_path(self): 9 | test_module = support_file("scenario/tests_in_package/pkg1/test/test_things.py") 10 | test_package_path = support_file("scenario/tests_in_package") 11 | self.assertEqual( 12 | util.name_from_path(test_module), 13 | ("pkg1.test.test_things", test_package_path), 14 | ) 15 | 16 | def test_non_ascii_output(self): 17 | class D: 18 | def __init__(self) -> None: 19 | self.out: list[str] = [] 20 | 21 | def write(self, arg): 22 | self.out.append(arg) 23 | 24 | stream = D() 25 | decorated = util._WritelnDecorator(stream) 26 | string = "\u00dcnic\u00f6de" 27 | decorated.write(string) 28 | str("".join(stream.out)) 29 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_setups/higher_layer_setup.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from nose2.tools import such 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | with such.A("foo") as it: 9 | it.upper_run = 0 10 | it.lower_run = 0 11 | 12 | @it.has_setup 13 | def upper_test_setup(case): 14 | log.error("foo::setUp") 15 | it.upper_run += 1 16 | 17 | with it.having("some bar"): 18 | 19 | @it.has_setup 20 | def lower_test_setup(case): 21 | log.error("foo some bar::setUp") 22 | it.lower_run += 1 23 | 24 | @it.should("run all setups") 25 | def test_run_all_setups(case): 26 | case.assertEqual(it.upper_run, 1) 27 | case.assertEqual(it.lower_run, 1) 28 | 29 | @it.should("run all setups just once") 30 | def test_run_all_setups_just_once(case): 31 | case.assertEqual(it.upper_run, 1) 32 | case.assertEqual(it.lower_run, 1) 33 | 34 | 35 | it.createTests(globals()) 36 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_setups/higher_layer_testsetup_no_test.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from nose2.tools import such 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | with such.A("foo") as it: 9 | it.upper_run = 0 10 | it.lower_run = 0 11 | 12 | @it.has_test_setup 13 | def upper_test_setup(case): 14 | log.error("foo::setUp") 15 | it.upper_run += 1 16 | 17 | with it.having("some bar"): 18 | 19 | @it.has_test_setup 20 | def lower_test_setup(case): 21 | log.error("foo some bar::setUp") 22 | it.lower_run += 1 23 | 24 | @it.should("run all setups") 25 | def test_run_all_setups(case): 26 | case.assertEqual(it.upper_run, 1) 27 | case.assertEqual(it.lower_run, 1) 28 | 29 | @it.should("run all setups again") 30 | def test_run_all_setups_again(case): 31 | case.assertEqual(it.upper_run, 2) 32 | case.assertEqual(it.lower_run, 2) 33 | 34 | 35 | it.createTests(globals()) 36 | -------------------------------------------------------------------------------- /nose2/tools/decorators.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides decorators that assist the test author to write tests. 3 | """ 4 | 5 | 6 | def with_setup(setup): 7 | """ 8 | A decorator that sets the :func:`setup` method to be executed before the test. 9 | 10 | It currently works only for function test cases. 11 | 12 | :param setup: The method to be executed before the test. 13 | :type setup: function 14 | """ 15 | 16 | def decorator(testcase): 17 | testcase.setup = setup 18 | 19 | return testcase 20 | 21 | return decorator 22 | 23 | 24 | def with_teardown(teardown): 25 | """ 26 | A decorator that sets the :func:`teardown` method to be after before the test. 27 | 28 | It currently works only for function test cases. 29 | 30 | :param teardown: The method to be executed after the test. 31 | :type teardown: function 32 | """ 33 | 34 | def decorator(testcase): 35 | testcase.tearDownFunc = teardown 36 | 37 | return testcase 38 | 39 | return decorator 40 | -------------------------------------------------------------------------------- /nose2/plugins/dundertest.py: -------------------------------------------------------------------------------- 1 | """ 2 | This plugin implements :func:`startTestRun`, which excludes all test objects 3 | that define a ``__test__`` attribute that evaluates to ``False``. 4 | """ 5 | 6 | from unittest import TestSuite 7 | 8 | from nose2 import events 9 | 10 | __unittest = True 11 | 12 | 13 | class DunderTestFilter(events.Plugin): 14 | """ 15 | Exclude all tests defining a ``__test__`` attribute that evaluates to ``False``. 16 | """ 17 | 18 | alwaysOn = True 19 | 20 | def startTestRun(self, event): 21 | """ 22 | Recurse :attr:`event.suite` and remove all test suites and test cases 23 | that define a ``__test__`` attribute that evaluates to ``False``. 24 | """ 25 | self.removeNonTests(event.suite) 26 | 27 | def removeNonTests(self, suite): 28 | for test in list(suite): 29 | if not getattr(test, "__test__", True): 30 | suite._tests.remove(test) 31 | elif isinstance(test, TestSuite): 32 | self.removeNonTests(test) 33 | -------------------------------------------------------------------------------- /nose2/tests/functional/test_main.py: -------------------------------------------------------------------------------- 1 | from nose2.tests._common import FunctionalTestCase 2 | 3 | 4 | class TestPluggableTestProgram(FunctionalTestCase): 5 | def test_run_in_empty_dir_succeeds(self): 6 | proc = self.runIn("scenario/no_tests") 7 | stdout, stderr = proc.communicate() 8 | self.assertEqual(proc.poll(), 0, stderr) 9 | 10 | def test_extra_hooks(self): 11 | class Check: 12 | ran = False 13 | 14 | def startTestRun(self, event): 15 | self.ran = True 16 | 17 | check = Check() 18 | proc = self.runIn("scenario/no_tests", extraHooks=[("startTestRun", check)]) 19 | stdout, stderr = proc.communicate() 20 | self.assertEqual(proc.poll(), 0, stderr) 21 | assert check.ran, "Extra hook did not execute" 22 | 23 | def test_run_in_module_from_its_main(self): 24 | proc = self.runModuleAsMain("scenario/one_test/tests.py") 25 | self.assertTestRunOutputMatches(proc, stderr="Ran 1 test") 26 | self.assertEqual(proc.poll(), 0) 27 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/such/test_such_timing.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from nose2.tools import such 4 | 5 | 6 | def slow_blocking_init(): 7 | print("YEAH2") 8 | time.sleep(1) 9 | print("a second elapsed") 10 | time.sleep(1) 11 | print("a second elapsed") 12 | return True 13 | 14 | 15 | class Layer1: 16 | description = "Layer1 description" 17 | 18 | @classmethod 19 | def setUp(cls): 20 | print("YEAH") 21 | it.obj = False 22 | 23 | 24 | class Layer2: 25 | description = "Layer2 description" 26 | 27 | @classmethod 28 | def setUp(cls): 29 | it.obj = slow_blocking_init() 30 | 31 | 32 | with such.A("system with a fast initial setup layer") as it: 33 | it.uses(Layer1) 34 | 35 | @it.should("not have obj initialized") 36 | def test(): 37 | assert not it.obj 38 | 39 | with it.having("a second slow setup layer"): 40 | it.uses(Layer2) 41 | 42 | @it.should("have obj initialized") 43 | def test2(): 44 | assert it.obj 45 | 46 | 47 | it.createTests(globals()) 48 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_config.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from nose2 import config 4 | 5 | 6 | class TestConfig(unittest.TestCase): 7 | def setUp(self): 8 | self.conf = config.Config( 9 | [("a", " 1 "), ("b", " x\n y "), ("c", "0"), ("d", "123")] 10 | ) 11 | 12 | def test_as_int(self): 13 | self.assertEqual(self.conf.as_int("a"), 1) 14 | 15 | def test_as_str(self): 16 | self.assertEqual(self.conf.as_str("a"), "1") 17 | self.assertEqual(self.conf.as_str("b"), "x\n y") 18 | self.assertEqual(self.conf.as_str("missing", "default"), "default") 19 | 20 | def test_as_bool(self): 21 | self.assertEqual(self.conf.as_bool("a"), True) 22 | self.assertEqual(self.conf.as_bool("c"), False) 23 | 24 | def test_as_float(self): 25 | self.assertAlmostEqual(self.conf.as_float("a"), 1.0) 26 | 27 | def test_as_list(self): 28 | self.assertEqual(self.conf.as_list("b"), ["x", "y"]) 29 | self.assertEqual(self.conf.as_list("a"), ["1"]) 30 | self.assertEqual(self.conf.as_list("d"), ["123"]) 31 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_dundertest_plugin.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from nose2 import session 4 | from nose2.plugins import dundertest 5 | from nose2.tests._common import TestCase 6 | 7 | 8 | class TestDunderTestPlugin(TestCase): 9 | tags = ["unit"] 10 | 11 | def setUp(self): 12 | class DummyCase(TestCase): 13 | def test_a(self): 14 | pass 15 | 16 | self.suite = unittest.TestSuite() 17 | self.caseClass = DummyCase 18 | self.session = session.Session() 19 | self.plugin = dundertest.DunderTestFilter(session=self.session) 20 | self.plugin.register() 21 | 22 | def test_undefined_dunder_test_attribute_keeps_test(self): 23 | self.suite.addTest(self.caseClass("test_a")) 24 | self.plugin.removeNonTests(self.suite) 25 | self.assertEqual(len(list(self.suite)), 1) 26 | 27 | def test_false_dunder_test_attribute_removes_test(self): 28 | dummyTest = self.caseClass("test_a") 29 | dummyTest.__test__ = False 30 | self.suite.addTest(dummyTest) 31 | self.plugin.removeNonTests(self.suite) 32 | self.assertEqual(len(list(self.suite)), 0) 33 | -------------------------------------------------------------------------------- /docs/plugins/eggdiscovery.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Loader: Egg Test discovery 3 | ========================== 4 | 5 | 6 | What is Egg Discovery 7 | --------------------- 8 | 9 | Sometimes Python Eggs are marked as zip-safe and they can be installed zipped, 10 | instead of unzipped in an ``.egg`` folder. See http://peak.telecommunity.com/DevCenter/PythonEggs for more details. 11 | The normal ``nose2.plugins.loader.discovery`` plugin ignores modules located inside zip files. 12 | 13 | The Egg Discovery plugin allows nose2 to discover tests within these zipped egg files. 14 | 15 | This plugin requires ``pkg_resources`` (from ``setuptools``) to work correctly. 16 | 17 | 18 | Usage 19 | ----- 20 | 21 | To activate the plugin, include the plugin module in the plugins list 22 | in ``[unittest]`` section in a config file:: 23 | 24 | [unittest] 25 | plugins = nose2.plugins.loader.eggdiscovery 26 | 27 | Or pass the module with the :option:`--plugin` command-line option:: 28 | 29 | nose2 --plugin=nose2.plugins.loader.eggdiscovery module_in_egg 30 | 31 | 32 | Reference 33 | --------- 34 | 35 | .. autoplugin :: nose2.plugins.loader.eggdiscovery.EggDiscoveryLoader 36 | -------------------------------------------------------------------------------- /docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | Getting started with nose2 2 | ========================== 3 | 4 | Installation 5 | ------------ 6 | 7 | The recommended way to install nose2 is with `pip`_ :: 8 | 9 | pip install nose2 10 | 11 | Running tests 12 | ------------- 13 | 14 | To run tests in a project, use the ``nose2`` script that is installed 15 | with nose2:: 16 | 17 | nose2 18 | 19 | This will find and run tests in all packages in the current working 20 | directory, and any sub-directories of the current working directory 21 | whose names start with 'test'. 22 | 23 | To find tests, nose2 looks for modules whose names start with 24 | 'test'. In those modules, nose2 will load tests from all 25 | :class:`unittest.TestCase` subclasses, as well as functions whose 26 | names start with 'test'. 27 | 28 | .. todo :: 29 | 30 | ... and other classes whose names start with 'Test'. 31 | 32 | 33 | The ``nose2`` script supports a number of command-line options, as 34 | well as extensive configuration via config files. For more information 35 | see :doc:`usage` and :doc:`configuration`. 36 | 37 | .. _pip : http://pypi.python.org/pypi/pip/1.0.2 38 | .. _pypi : http://pypi.python.org/pypi 39 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/lib/layer_hooks_plugin.py: -------------------------------------------------------------------------------- 1 | from nose2 import events 2 | 3 | 4 | class PrintFixture(events.Plugin): 5 | alwaysOn = True 6 | 7 | def startLayerSetup(self, event): 8 | print(f"StartLayerSetup: {event.layer}") 9 | 10 | def stopLayerSetup(self, event): 11 | print(f"StopLayerSetup: {event.layer}") 12 | 13 | def startLayerSetupTest(self, event): 14 | log = "StartLayerSetupTest: {0}:{1}" 15 | print(log.format(event.layer, event.test)) 16 | 17 | def stopLayerSetupTest(self, event): 18 | log = "StopLayerSetupTest: {0}:{1}" 19 | print(log.format(event.layer, event.test)) 20 | 21 | def startLayerTeardownTest(self, event): 22 | log = "StartLayerTeardownTest: {0}:{1}" 23 | print(log.format(event.layer, event.test)) 24 | 25 | def stopLayerTeardownTest(self, event): 26 | log = "StopLayerTeardownTest: {0}:{1}" 27 | print(log.format(event.layer, event.test)) 28 | 29 | def startLayerTeardown(self, event): 30 | print(f"StartLayerTeardown: {event.layer}") 31 | 32 | def stopLayerTeardown(self, event): 33 | print(f"StopLayerTeardown: {event.layer}") 34 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_prof_plugin.py: -------------------------------------------------------------------------------- 1 | from nose2 import session 2 | from nose2.events import StartTestRunEvent 3 | from nose2.plugins import prof 4 | from nose2.tests._common import Stub, TestCase 5 | 6 | 7 | class TestProfPlugin(TestCase): 8 | tags = ["unit"] 9 | 10 | def setUp(self): 11 | self.plugin = prof.Profiler(session=session.Session()) 12 | # stub out and save the cProfile and pstats modules 13 | self.cProfile = prof.cProfile 14 | self.pstats = prof.pstats 15 | prof.cProfile = Stub() 16 | prof.pstats = Stub() 17 | 18 | def tearDown(self): 19 | prof.cProfile = self.cProfile 20 | prof.pstats = self.pstats 21 | 22 | def test_startTestRun_sets_executeTests(self): 23 | _prof = Stub() 24 | _prof.runcall = object() 25 | 26 | def _profile_call() -> Stub: 27 | return _prof 28 | 29 | prof.cProfile.Profile = _profile_call 30 | event = StartTestRunEvent( 31 | runner=None, suite=None, result=None, startTime=None, executeTests=None 32 | ) 33 | self.plugin.startTestRun(event) 34 | assert event.executeTests is _prof.runcall, "executeTests was not replaced" 35 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py{38,39,310,311,312,313}{,-nocov},pypy,docs,lint 3 | 4 | [testenv] 5 | passenv = CI 6 | extras = dev 7 | deps = 8 | !nocov: coverage 9 | py{38,39,310}-toml: tomli 10 | setenv = PYTHONPATH={toxinidir} 11 | commands = 12 | nocov: nose2 -v --pretty-assert {posargs} 13 | !nocov: coverage erase 14 | !nocov: coverage run nose2 -v --pretty-assert {posargs} 15 | !nocov: coverage report 16 | 17 | [testenv:lint] 18 | deps = pre-commit~=2.9.3 19 | skip_install = true 20 | commands = pre-commit run --all-files 21 | 22 | [testenv:mypy] 23 | extras = dev 24 | deps = 25 | mypy==1.10.0 26 | types-setuptools 27 | types-docutils 28 | tomli 29 | coverage 30 | commands = 31 | mypy nose2/ {posargs} 32 | 33 | [testenv:docs] 34 | extras = dev 35 | changedir = docs 36 | commands = sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html 37 | 38 | [testenv:build] 39 | deps = 40 | build 41 | twine 42 | skip_install = true 43 | allowlist_externals = rm 44 | commands_pre = rm -rf dist/ 45 | commands = 46 | python -m build 47 | twine check dist/* 48 | 49 | [testenv:publish] 50 | deps = twine 51 | skip_install = true 52 | commands = twine upload dist/* 53 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_setups/higher_layer_testsetup_with_test.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from nose2.tools import such 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | with such.A("foo") as it: 9 | it.upper_run = 0 10 | it.lower_run = 0 11 | 12 | @it.has_test_setup 13 | def upper_test_setup(case): 14 | log.error("foo::setUp") 15 | it.upper_run += 1 16 | 17 | @it.should("run upper setups") 18 | def test_run_upper_setups(case): 19 | case.assertEqual(it.upper_run, 1) 20 | case.assertEqual(it.lower_run, 0) 21 | 22 | with it.having("some bar"): 23 | 24 | @it.has_test_setup 25 | def lower_test_setup(case): 26 | log.error("foo some bar::setUp") 27 | it.lower_run += 1 28 | 29 | @it.should("run all setups") 30 | def test_run_all_setups(case): 31 | case.assertEqual(it.upper_run, 2) 32 | case.assertEqual(it.lower_run, 1) 33 | 34 | @it.should("run all setups again") 35 | def test_run_all_setups_again(case): 36 | case.assertEqual(it.upper_run, 3) 37 | case.assertEqual(it.lower_run, 2) 38 | 39 | 40 | it.createTests(globals()) 41 | -------------------------------------------------------------------------------- /nose2/tests/functional/test_printhooks_plugin.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from nose2.tests._common import FunctionalTestCase 4 | 5 | 6 | class TestPrintHooksPlugin(FunctionalTestCase): 7 | def test_invocation_by_double_dash_option(self): 8 | proc = self.runIn( 9 | "scenario/no_tests", "--plugin=nose2.plugins.printhooks", "--print-hooks" 10 | ) 11 | match = re.compile("\nhandleArgs: CommandLineArgsEvent\\(handled=False, args=") 12 | self.assertTestRunOutputMatches(proc, stderr=match) 13 | self.assertEqual(proc.poll(), 0) 14 | 15 | def test_invocation_by_single_dash_option(self): 16 | proc = self.runIn( 17 | "scenario/no_tests", "--plugin=nose2.plugins.printhooks", "-P" 18 | ) 19 | match = re.compile("\nhandleArgs: CommandLineArgsEvent\\(handled=False, args=") 20 | self.assertTestRunOutputMatches(proc, stderr=match) 21 | self.assertEqual(proc.poll(), 0) 22 | 23 | def test_nested_hooks_are_indented(self): 24 | proc = self.runIn( 25 | "scenario/no_tests", "--plugin=nose2.plugins.printhooks", "--print-hooks" 26 | ) 27 | match = re.compile("\n handleFile: ") 28 | self.assertTestRunOutputMatches(proc, stderr=match) 29 | self.assertEqual(proc.poll(), 0) 30 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_session.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from nose2 import events, session 4 | 5 | 6 | class SessionUnitTests(unittest.TestCase): 7 | def test_can_create_session(self): 8 | session.Session() 9 | 10 | def test_load_plugins_from_module_can_load_plugins(self): 11 | class fakemod: 12 | pass 13 | 14 | f = fakemod() 15 | 16 | class A(events.Plugin): 17 | pass 18 | 19 | f.A = A 20 | s = session.Session() 21 | s.loadPluginsFromModule(f) 22 | assert s.plugins 23 | a = s.plugins[0] 24 | self.assertEqual(a.session, s) 25 | 26 | def test_load_plugins_from_module_does_not_load_plain_Plugins(self): 27 | class fakemod: 28 | pass 29 | 30 | f = fakemod() 31 | 32 | f.A = events.Plugin 33 | s = session.Session() 34 | s.loadPluginsFromModule(f) 35 | self.assertEqual(len(s.plugins), 0) 36 | 37 | def test_load_plugins_from_module_does_not_duplicate_always_on_plugins(self): 38 | class fakemod: 39 | pass 40 | 41 | f = fakemod() 42 | 43 | class A(events.Plugin): 44 | alwaysOn = True 45 | 46 | f.A = A 47 | s = session.Session() 48 | s.loadPluginsFromModule(f) 49 | self.assertEqual(len(s.plugins), 1) 50 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_plugin_api.py: -------------------------------------------------------------------------------- 1 | from nose2 import events, session 2 | from nose2.tests._common import TestCase 3 | 4 | 5 | class Example(events.Plugin): 6 | commandLineSwitch = ("X", "xxx", "triple x") 7 | 8 | def testOutcome(self, event): 9 | pass 10 | 11 | 12 | class TestPluginApi(TestCase): 13 | def setUp(self): 14 | self.session = session.Session() 15 | self.plug = Example(session=self.session) 16 | super(TestCase, self).setUp() 17 | 18 | def test_add_option_adds_option(self): 19 | helpt = self.session.argparse.format_help() 20 | assert "-X, --xxx" in helpt, ( 21 | "commandLineSwitch arg not found in help text: %s" % helpt 22 | ) 23 | 24 | def test_short_opt_registers_plugin(self): 25 | args, argv = self.session.argparse.parse_known_args(["-X"]) 26 | assert self.plug in self.session.plugins 27 | assert ( 28 | self.plug in self.session.hooks.testOutcome.plugins 29 | ), "short opt did not register plugin" 30 | 31 | def test_long_opt_registers_plugin(self): 32 | args, argv = self.session.argparse.parse_known_args(["--xxx"]) 33 | assert self.plug in self.session.plugins 34 | assert ( 35 | self.plug in self.session.hooks.testOutcome.plugins 36 | ), "long opt did not register plugin" 37 | -------------------------------------------------------------------------------- /nose2/tests/functional/test_logcapture_plugin.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from nose2.tests._common import FunctionalTestCase 4 | 5 | 6 | class LogCaptureFunctionalTest(FunctionalTestCase): 7 | def test_package_in_lib(self): 8 | match = re.compile(">> begin captured logging <<") 9 | self.assertTestRunOutputMatches( 10 | self.runIn("scenario/package_in_lib", "--log-capture"), stderr=match 11 | ) 12 | 13 | def test_logging_keeps_copies_of_mutable_objects(self): 14 | proc = self.runIn( 15 | "scenario/logging", 16 | "-v", 17 | "--log-capture", 18 | "logging_keeps_copies_of_mutable_objects", 19 | ) 20 | self.assertTestRunOutputMatches(proc, stderr="Ran 1 test in") 21 | self.assertTestRunOutputMatches(proc, stderr="FAILED") 22 | self.assertTestRunOutputMatches(proc, stderr="foo: {}") 23 | 24 | def test_logging_config_interpolation(self): 25 | proc = self.runIn("scenario/logging_config", "-v", "logging_config") 26 | self.assertTestRunOutputMatches(proc, stderr="Ran 1 test in") 27 | self.assertTestRunOutputMatches(proc, stderr="FAILED") 28 | self.assertTestRunOutputMatches( 29 | proc, stderr=r"\[logging_config\] \[DEBUG\] foo" 30 | ) 31 | self.assertTestRunOutputMatches(proc, stderr=r"\[logging_config\] \[INFO\] bar") 32 | -------------------------------------------------------------------------------- /docs/plugins/coverage.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Test coverage reporting 3 | ======================= 4 | 5 | .. autoplugin :: nose2.plugins.coverage.Coverage 6 | 7 | Differences From coverage 8 | ------------------------- 9 | 10 | The ``coverage`` tool is the basis for nose2's coverage reporting. nose2 will 11 | seek to emulate ``coverage`` behavior whenever possible, but there are known 12 | cases where this is not feasible. 13 | 14 | If you need the exact behaviors of ``coverage``, consider having ``coverage`` 15 | invoke ``nose2``. 16 | 17 | Otherwise, please be aware of the following known differences: 18 | 19 | - The ``fail_under`` parameter results in an exit status of 2 for ``coverage``, 20 | but an exit status of 1 for ``nose2`` 21 | 22 | Compatibility with mp plugin 23 | ---------------------------- 24 | 25 | The ``coverage`` and ``mp`` plugins may be used in conjunction to enable 26 | multiprocess testing with coverage reporting. 27 | 28 | Special instructions: 29 | 30 | - Due to the way the plugin is reloaded in subprocesses, command-line options 31 | for the ``coverage`` plugin have no effect. If you need to change any 32 | ``coverage`` plugin options, use a configuration file. 33 | - Do *not* use the ``concurrency`` option within a ``.coveragerc`` file ; this 34 | interferes with the ``coverage`` plugin, which automatically handles 35 | multiprocess coverage reporting. 36 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_and_non_layers/test_such_with_uses_decorator.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from nose2.tools import such 4 | 5 | from .common import NormalTest, NormalTestTwo, UniqueResource # noqa: F401 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | class Layer1: 11 | description = "Layer1" 12 | 13 | @classmethod 14 | def setUp(cls): 15 | log.info("Called setup in layer 1") 16 | it.unique_resource = UniqueResource() 17 | it.unique_resource.lock() 18 | 19 | @classmethod 20 | def tearDown(cls): 21 | log.info("Called teardown in layer 2") 22 | it.unique_resource.unlock() 23 | 24 | 25 | class Layer2: 26 | description = "Layer2" 27 | 28 | @classmethod 29 | def setUp(cls): 30 | log.info("Called setup in layer 2") 31 | 32 | @classmethod 33 | def tearDown(cls): 34 | log.info("Called teardown in layer 2") 35 | 36 | 37 | with such.A("system with setup") as it: 38 | it.uses(Layer1) 39 | 40 | @it.should("do something") 41 | def test(case): 42 | it.assertTrue(it.unique_resource.used) 43 | 44 | with it.having("another setup"): 45 | it.uses(Layer2) 46 | 47 | @it.should("do something else") # noqa: F811 48 | def test(case): # noqa: F811 49 | it.assertTrue(it.unique_resource.used) 50 | 51 | 52 | it.createTests(globals()) 53 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_setups/higher_layer_testsetup_3layers.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from nose2.tools import such 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | 8 | with such.A("foo") as it: 9 | it.upper_run = 0 10 | it.mid_run = 0 11 | it.lower_run = 0 12 | 13 | @it.has_test_setup 14 | def upper_test_setup(case): 15 | log.error("foo::setUp") 16 | it.upper_run += 1 17 | 18 | with it.having("some bar"): 19 | 20 | @it.has_test_setup 21 | def middle_test_setup(case): 22 | log.error("foo some bar::setUp") 23 | it.mid_run += 1 24 | 25 | with it.having("and more"): 26 | 27 | @it.has_test_setup 28 | def lower_test_setup(case): 29 | log.error("foo some bar and more::setUp") 30 | it.lower_run += 1 31 | 32 | @it.should("run all setups") 33 | def test_run_all_setups(case): 34 | case.assertEqual(it.upper_run, 1) 35 | case.assertEqual(it.mid_run, 1) 36 | case.assertEqual(it.lower_run, 1) 37 | 38 | @it.should("run all setups again") 39 | def test_run_all_setups_again(case): 40 | case.assertEqual(it.upper_run, 2) 41 | case.assertEqual(it.mid_run, 2) 42 | case.assertEqual(it.lower_run, 2) 43 | 44 | 45 | it.createTests(globals()) 46 | -------------------------------------------------------------------------------- /docs/dev/documenting_plugins.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Documenting plugins 3 | =================== 4 | 5 | You should do it. Nobody will use your plugins if you don't. Or if 6 | they do use them, they will curse you whenever things go wrong. 7 | 8 | One easy way to document your plugins is to use nose2's `Sphinx`_ 9 | extension, which provides an ``autoplugin`` directive that will 10 | produce decent reference documentation from your plugin classes. 11 | 12 | To use it, add ``nose2.sphinxext`` to the ``extensions`` list in the 13 | ``conf.py`` file in your docs directory. 14 | 15 | Then add an ``autoplugin`` directive to a ``*.rst`` file, like this:: 16 | 17 | .. autoplugin :: mypackage.plugins.PluginClass 18 | 19 | This will produce output that includes the config vars your plugin 20 | loads in ``__init__``, as well as any command line options your plugin 21 | registers. This is why you *really* should extract config vars and 22 | register command-line options in ``__init__``. 23 | 24 | The output will also include an ``autoclass`` section for your plugin 25 | class, so you can put more narrative documentation in the plugin's 26 | docstring for users to read. 27 | 28 | Of course you can, and should, write some words before the reference 29 | docs explaining what your plugin does and how to use it. You can put 30 | those words in the ``*.rst`` file itself, or in the docstring of the module 31 | where your plugin lives. 32 | 33 | .. _Sphinx : http://sphinx.pocoo.org/ 34 | -------------------------------------------------------------------------------- /docs/plugins/prettyassert.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | Use assert statements in tests 3 | ============================== 4 | 5 | .. autoplugin :: nose2.plugins.prettyassert.PrettyAssert 6 | 7 | assert statement inspection 8 | --------------------------- 9 | 10 | The prettyassert plugin works by inspecting the stack frame which raised an 11 | `AssertionError`. Unlike pytest's assertion rewriting code, it does not modify 12 | the built-in `AssertionError`. 13 | 14 | As a result, it is somewhat limited in its capabilities -- it 15 | can only report the *bound* values from that stack frame. That means that this 16 | type of statement works well: 17 | 18 | .. code-block:: python 19 | 20 | x = f() 21 | y = g() 22 | assert x == y 23 | 24 | but this type of statement does not: 25 | 26 | .. code-block:: python 27 | 28 | assert f() == g() 29 | 30 | It will still run, but the prettyassert will tell you that `f` and `g` are 31 | functions, not what they evaluated to. This is probably not what you want. 32 | 33 | attribute resolution 34 | -------------------- 35 | 36 | The assertion inspection will resolve attributes, so that expressions like this 37 | will work as well: 38 | 39 | .. code-block:: python 40 | 41 | assert x.foo == 1 42 | 43 | But note that the attribute `x.foo` will be resolved *twice* in this case, if 44 | the assertion fails. Once when the assertion is evaluated, and again when it is 45 | inspected. 46 | 47 | As a result, properties with dynamic values may not behave as expected under 48 | prettyassert inspection. 49 | -------------------------------------------------------------------------------- /nose2/plugins/collect.py: -------------------------------------------------------------------------------- 1 | """ 2 | This plugin implements :func:`startTestRun`, setting a test executor 3 | (``event.executeTests``) that just collects tests without executing 4 | them. To do so it calls result.startTest, result.addSuccess and 5 | result.stopTest for each test, without calling the test itself. 6 | """ 7 | 8 | import unittest 9 | 10 | from nose2.events import Plugin 11 | 12 | __unittest = True 13 | 14 | 15 | class CollectOnly(Plugin): 16 | """Collect but don't run tests""" 17 | 18 | configSection = "collect-only" 19 | commandLineSwitch = ( 20 | None, 21 | "collect-only", 22 | "Collect but do not run tests. With '-v', this will output test names", 23 | ) 24 | _mpmode = False 25 | 26 | def registerInSubprocess(self, event): 27 | event.pluginClasses.append(self.__class__) 28 | self._mpmode = True 29 | 30 | def startTestRun(self, event): 31 | """Replace ``event.executeTests``""" 32 | if self._mpmode: 33 | return 34 | event.executeTests = self.collectTests 35 | 36 | def startSubprocess(self, event): 37 | event.executeTests = self.collectTests 38 | 39 | def collectTests(self, suite, result): 40 | """Collect tests, but don't run them""" 41 | for test in suite: 42 | if isinstance(test, unittest.BaseTestSuite): 43 | self.collectTests(test, result) 44 | continue 45 | result.startTest(test) 46 | result.addSuccess(test) 47 | result.stopTest(test) 48 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks.git 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-merge-conflict 6 | - id: check-toml 7 | - id: check-yaml 8 | - id: end-of-file-fixer 9 | - id: trailing-whitespace 10 | - repo: https://github.com/python-jsonschema/check-jsonschema 11 | rev: 0.33.0 12 | hooks: 13 | - id: check-github-workflows 14 | - id: check-dependabot 15 | - id: check-readthedocs 16 | - repo: https://github.com/psf/black 17 | rev: 25.1.0 18 | hooks: 19 | - id: black 20 | language_version: python3 21 | - repo: https://github.com/PyCQA/isort 22 | rev: 6.0.1 23 | hooks: 24 | - id: isort 25 | - repo: https://github.com/asottile/pyupgrade 26 | rev: v3.19.1 27 | hooks: 28 | - id: pyupgrade 29 | args: ["--py37-plus"] 30 | - repo: https://github.com/PyCQA/flake8 31 | rev: 7.2.0 32 | hooks: 33 | - id: flake8 34 | additional_dependencies: 35 | - 'flake8-bugbear==24.12.12' 36 | - 'flake8-comprehensions==3.16.0' 37 | - 'flake8-typing-as-t==1.0.0' 38 | - repo: https://github.com/sirosen/slyp 39 | rev: 0.8.2 40 | hooks: 41 | - id: slyp 42 | - repo: https://github.com/codespell-project/codespell 43 | rev: v2.4.1 44 | hooks: 45 | - id: codespell 46 | additional_dependencies: 47 | - tomli 48 | args: 49 | - "-L" 50 | - "assertIn" 51 | - repo: https://github.com/tox-dev/pyproject-fmt 52 | rev: v2.5.1 53 | hooks: 54 | - id: pyproject-fmt 55 | additional_dependencies: ["tox>=4.9"] 56 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_collector.py: -------------------------------------------------------------------------------- 1 | import re 2 | from textwrap import dedent 3 | from unittest import mock 4 | 5 | from nose2 import collector 6 | from nose2.tests._common import RedirectStdStreams, TestCase 7 | 8 | 9 | class TestCollector(TestCase): 10 | _RUN_IN_TEMP = True 11 | tags = ["unit"] 12 | 13 | def test_collector_completes_with_no_tests(self): 14 | with open("unittest.cfg", "w") as ut_file: 15 | ut_file.write( 16 | dedent( 17 | """ 18 | [unittest] 19 | quiet = true 20 | """ 21 | ) 22 | ) 23 | test = collector.collector() 24 | with RedirectStdStreams() as redir: 25 | self.assertRaises(SystemExit, test.run, None) 26 | self.assertEqual("", redir.stdout.getvalue()) 27 | self.assertTrue( 28 | re.match( 29 | r"\n-+\nRan 0 tests in \d.\d\d\ds\n\nOK\n", redir.stderr.getvalue() 30 | ) 31 | ) 32 | 33 | def test_collector_sets_testLoader_in_session(self): 34 | """ 35 | session.testLoader needs to be set so that plugins that use this 36 | field (like Layers) dont break. 37 | """ 38 | test = collector.collector() 39 | mock_session = mock.MagicMock() 40 | mock_loader = mock.MagicMock() 41 | mock_runner = mock.MagicMock() 42 | test._get_objects = mock.Mock( 43 | return_value=(mock_session, mock_loader, mock_runner) 44 | ) 45 | test._collector(None) 46 | self.assertTrue(mock_session.testLoader is mock_loader) 47 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_and_non_layers/test_layers.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import unittest 3 | 4 | from .common import NormalTest, NormalTestTwo, UniqueResource # noqa: F401 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | 9 | class Layer1: 10 | @classmethod 11 | def setUp(cls): 12 | log.info("Called setup in layer 1") 13 | cls.unique_resource = UniqueResource() 14 | cls.unique_resource.lock() 15 | 16 | @classmethod 17 | def tearDown(cls): 18 | log.info("Called teardown in layer 2") 19 | cls.unique_resource.unlock() 20 | 21 | 22 | class Layer2: 23 | @classmethod 24 | def setUp(cls): 25 | log.info("Called setup in layer 2") 26 | cls.unique_resource = UniqueResource() 27 | cls.unique_resource.lock() 28 | 29 | @classmethod 30 | def tearDown(cls): 31 | log.info("Called teardown in layer 2") 32 | cls.unique_resource.unlock() 33 | 34 | 35 | class Layer3(Layer2): 36 | @classmethod 37 | def setUp(cls): 38 | log.info("Called setup in layer 3") 39 | 40 | @classmethod 41 | def tearDown(cls): 42 | log.info("Called teardown in layer 3") 43 | 44 | 45 | class LayerTest1(unittest.TestCase): 46 | layer = Layer1 47 | 48 | def test(self): 49 | self.assertTrue(self.layer.unique_resource.used) 50 | 51 | 52 | class LayerTest2(unittest.TestCase): 53 | layer = Layer2 54 | 55 | def test(self): 56 | self.assertTrue(self.layer.unique_resource.used) 57 | 58 | 59 | class LayerTest3(unittest.TestCase): 60 | layer = Layer2 61 | 62 | def test(self): 63 | self.assertTrue(self.layer.unique_resource.used) 64 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_hooks/test_layers_simple.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class Layer1: 5 | layer_setup = 0 6 | test_setup = 0 7 | test_teardown = 0 8 | layer_teardown = 0 9 | tests_count = 0 10 | 11 | @classmethod 12 | def setUp(cls): 13 | if cls.layer_setup >= 1: 14 | raise Exception("layer_setup already ran") 15 | cls.layer_setup += 1 16 | 17 | @classmethod 18 | def testSetUp(cls): 19 | if cls.test_setup >= 2: 20 | raise Exception("test_setup already ran twice") 21 | cls.test_setup += 1 22 | 23 | @classmethod 24 | def testTearDown(cls): 25 | if cls.test_teardown >= 2: 26 | raise Exception("test_teardown already ran twice") 27 | cls.test_teardown += 1 28 | 29 | @classmethod 30 | def tearDown(cls): 31 | if cls.layer_teardown >= 1: 32 | raise Exception("layer_teardown already ran") 33 | cls.layer_teardown += 1 34 | 35 | 36 | class TestSimple(unittest.TestCase): 37 | layer = Layer1 38 | 39 | def test_1(self): 40 | assert self.layer.layer_setup == 1 41 | assert self.layer.test_setup == self.layer.tests_count + 1 42 | assert self.layer.test_teardown == self.layer.tests_count 43 | assert self.layer.layer_teardown == 0 44 | self.layer.tests_count += 1 45 | 46 | def test_2(self): 47 | assert self.layer.layer_setup == 1 48 | assert self.layer.test_setup == self.layer.tests_count + 1 49 | assert self.layer.test_teardown == self.layer.tests_count 50 | assert self.layer.layer_teardown == 0 51 | self.layer.tests_count += 1 52 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/layers_and_non_layers/common.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import unittest 3 | 4 | log = logging.getLogger(__name__) 5 | 6 | 7 | class UniqueResource: 8 | _instance = None 9 | used = False 10 | 11 | def __new__(cls, *args, **kwargs): 12 | if not cls._instance: 13 | cls._instance = super().__new__(cls, *args, **kwargs) 14 | return cls._instance 15 | 16 | def lock(self): 17 | if not self.used: 18 | self.used = True 19 | else: 20 | raise Exception("Resource already used") 21 | 22 | def unlock(self): 23 | if self.used: 24 | self.used = False 25 | else: 26 | raise Exception("Resource already unlocked") 27 | 28 | 29 | class NormalTest(unittest.TestCase): 30 | @classmethod 31 | def setUpClass(cls): 32 | log.info("Called setUpClass in NormalTest") 33 | cls.unique_resource = UniqueResource() 34 | cls.unique_resource.lock() 35 | 36 | @classmethod 37 | def tearDownClass(cls): 38 | log.info("Called tearDownClass in NormalTest") 39 | cls.unique_resource.unlock() 40 | 41 | def test(self): 42 | self.assertTrue(self.unique_resource.used) 43 | 44 | 45 | class NormalTestTwo(unittest.TestCase): 46 | @classmethod 47 | def setUpClass(cls): 48 | log.info("Called setUpClass in NormalTestTwo") 49 | cls.unique_resource = UniqueResource() 50 | cls.unique_resource.lock() 51 | 52 | @classmethod 53 | def tearDownClass(cls): 54 | log.info("Called tearDownClass in NormalTestTwo") 55 | cls.unique_resource.unlock() 56 | 57 | def test(self): 58 | self.assertTrue(self.unique_resource.used) 59 | -------------------------------------------------------------------------------- /contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing to nose2 2 | ===================== 3 | 4 | Please do! 5 | 6 | The Basics 7 | ---------- 8 | 9 | nose2 is hosted on `github`_ and uses GitHub for issue tracking. 10 | 11 | Please report issues and make feature requests here: 12 | https://github.com/nose-devs/nose2/issues 13 | 14 | Code Contributions 15 | ------------------ 16 | 17 | The main rule is: *code changes should include tests.* 18 | 19 | If you aren't sure how to add tests, or you don't know why existing tests fail 20 | on your changes, that's okay! Submit your patch and ask for help testing it. 21 | 22 | Local Dev Requirements 23 | ++++++++++++++++++++++ 24 | 25 | To run the tests you must have `tox`_ installed. 26 | 27 | Optional but useful tools include ``make`` and `pre-commit`_. 28 | 29 | Running Tests 30 | +++++++++++++ 31 | 32 | To run all tests: :: 33 | 34 | $ tox 35 | 36 | To run linting checks: :: 37 | 38 | $ tox -e lint 39 | 40 | You can also use ``make test`` and ``make lint`` for these. 41 | 42 | Linting 43 | +++++++ 44 | 45 | nose2 uses `black`_ and `ruff`_ to enforce code formatting and linting and 46 | `pre-commit`_ to run these tools. 47 | 48 | For the best development experience, we recommend setting up integrations with 49 | your editor and git. 50 | 51 | Running ``pre-commit`` as a git hook is optional. To configure it, you must 52 | have ``pre-commit`` installed and run: 53 | 54 | .. code-block:: bash 55 | 56 | $ pre-commit install 57 | 58 | .. note:: 59 | If you need to bypass pre-commit hooks after setting this up, you can commit 60 | with ``--no-verify`` 61 | 62 | .. _black: https://black.readthedocs.io/ 63 | .. _github: https://github.com/nose-devs/nose2 64 | .. _pre-commit: https://pre-commit.com/ 65 | .. _ruff: https://beta.ruff.rs/docs/ 66 | .. _tox: http://pypi.python.org/pypi/tox 67 | -------------------------------------------------------------------------------- /nose2/plugins/doctests.py: -------------------------------------------------------------------------------- 1 | """ 2 | Load tests from doctests. 3 | 4 | This plugin implements :func:`handleFile` to load doctests from text files 5 | and python modules. 6 | 7 | To disable loading doctests from text files, configure an empty extensions list: 8 | 9 | .. code-block :: ini 10 | 11 | [doctest] 12 | extensions = 13 | 14 | """ 15 | 16 | import doctest 17 | import os 18 | 19 | from nose2 import util 20 | from nose2.events import Plugin 21 | 22 | __unittest = True 23 | 24 | 25 | class DocTestLoader(Plugin): 26 | configSection = "doctest" 27 | commandLineSwitch = ( 28 | None, 29 | "with-doctest", 30 | "Load doctests from text files and modules", 31 | ) 32 | 33 | def __init__(self) -> None: 34 | self.extensions = self.config.as_list("extensions", [".txt", ".rst"]) 35 | 36 | def handleFile(self, event): 37 | """Load doctests from text files and modules""" 38 | path = event.path 39 | _root, ext = os.path.splitext(path) 40 | if ext in self.extensions: 41 | suite = doctest.DocFileTest(path, module_relative=False) 42 | event.extraTests.append(suite) 43 | return 44 | elif not util.valid_module_name(os.path.basename(path)): 45 | return 46 | 47 | name, package_path = util.name_from_path(path) 48 | # ignore top-level setup.py which cannot be imported 49 | if name == "setup": 50 | return 51 | util.ensure_importable(package_path) 52 | try: 53 | module = util.module_from_name(name) 54 | except Exception: 55 | # XXX log warning here? 56 | return 57 | if hasattr(module, "__test__") and not module.__test__: 58 | return 59 | suite = doctest.DocTestSuite(module) 60 | event.extraTests.append(suite) 61 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_failfast.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from nose2 import result, session 4 | from nose2.plugins import failfast 5 | from nose2.tests._common import TestCase 6 | 7 | 8 | class TestFailFast(TestCase): 9 | tags = ["unit"] 10 | 11 | def setUp(self): 12 | self.session = session.Session() 13 | self.result = result.PluggableTestResult(self.session) 14 | self.plugin = failfast.FailFast(session=self.session) 15 | self.plugin.register() 16 | 17 | class Test(TestCase): 18 | def test(self): 19 | pass 20 | 21 | def test_err(self): 22 | raise Exception("oops") 23 | 24 | def test_fail(self): 25 | assert False 26 | 27 | @unittest.expectedFailure 28 | def test_fail_expected(self): 29 | assert False 30 | 31 | @unittest.skipIf(True, "Always skip") 32 | def test_skip(self): 33 | pass 34 | 35 | self.case = Test 36 | 37 | def test_sets_shouldstop_on_unexpected_error(self): 38 | test = self.case("test_err") 39 | test(self.result) 40 | assert self.result.shouldStop 41 | 42 | def test_sets_shouldstop_on_unexpected_fail(self): 43 | test = self.case("test_fail") 44 | test(self.result) 45 | assert self.result.shouldStop 46 | 47 | def test_does_not_set_shouldstop_on_expected_fail(self): 48 | test = self.case("test_fail_expected") 49 | test(self.result) 50 | assert not self.result.shouldStop 51 | 52 | def test_does_not_set_shouldstop_on_success(self): 53 | test = self.case("test") 54 | test(self.result) 55 | assert not self.result.shouldStop 56 | 57 | def test_does_not_set_shouldstop_on_skip(self): 58 | test = self.case("test_skip") 59 | test(self.result) 60 | assert not self.result.shouldStop 61 | -------------------------------------------------------------------------------- /.github/workflows/test-on-downstream-projects.yaml: -------------------------------------------------------------------------------- 1 | name: test latest on downstream projects 2 | on: 3 | push: 4 | branches: 5 | - main 6 | # a dedicated branch for working on this workflow 7 | - downstream-test 8 | # build weekly at 4:00 AM UTC 9 | schedule: 10 | - cron: '0 4 * * 1' 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-python@v5 19 | with: 20 | python-version: "3.x" 21 | - run: python -m pip install tox 22 | - name: build 23 | run: make build 24 | - name: upload latest builds 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: latest-build 28 | path: dist/* 29 | 30 | test-mailman: 31 | strategy: 32 | matrix: 33 | pkg-extension: ["whl", "tar.gz"] 34 | needs: build 35 | runs-on: ubuntu-latest 36 | steps: 37 | - run: git clone "https://gitlab.com/mailman/mailman/" . 38 | - name: download sdist build 39 | uses: actions/download-artifact@v4 40 | with: 41 | name: latest-build 42 | path: nose2-build 43 | # NOTE: be sure to use a python version supported by mailman tests 44 | # at time of writing, this can be seen in the mailman test matrix: 45 | # https://gitlab.com/mailman/mailman/-/blob/master/tox.ini 46 | - uses: actions/setup-python@v5 47 | with: 48 | python-version: "3.11" 49 | - run: python -m pip install tox 50 | - name: setup testenv 51 | run: tox -e py-nocov --devenv testenv 52 | - name: install nose2 latest build 53 | run: | 54 | source testenv/bin/activate 55 | pip install --force-reinstall -v ./nose2-build/*.${{ matrix.pkg-extension }} 56 | - name: run tests 57 | run: | 58 | source testenv/bin/activate 59 | python -m nose2 -v 60 | -------------------------------------------------------------------------------- /nose2/plugins/debugger.py: -------------------------------------------------------------------------------- 1 | """ 2 | Start a :func:`pdb.post_mortem` on errors and failures. 3 | 4 | This plugin implements :func:`testOutcome` and will drop into pdb 5 | whenever it sees a test outcome that includes exc_info. 6 | 7 | It fires :func:`beforeInteraction` before launching pdb and 8 | :func:`afterInteraction` after. Other plugins may implement 9 | :func:`beforeInteraction` to return ``False`` and set ``event.handled`` to 10 | prevent this plugin from launching pdb. 11 | 12 | """ 13 | 14 | import logging 15 | import pdb 16 | 17 | from nose2 import events 18 | 19 | __unittest = True 20 | log = logging.getLogger(__name__) 21 | 22 | 23 | class Debugger(events.Plugin): 24 | """Enter pdb on test error or failure 25 | 26 | .. attribute :: pdb 27 | 28 | For ease of mocking and using different pdb implementations, pdb 29 | is aliased as a class attribute. 30 | 31 | """ 32 | 33 | configSection = "debugger" 34 | commandLineSwitch = ("D", "debugger", "Enter pdb on test fail or error") 35 | # allow easy mocking and replacement of pdb 36 | pdb = pdb 37 | 38 | def __init__(self) -> None: 39 | self.errorsOnly = self.config.as_bool("errors-only", default=False) 40 | 41 | def testOutcome(self, event): 42 | """Drop into pdb on unexpected errors or failures""" 43 | if not event.exc_info or event.expected: 44 | # skipped tests, unexpected successes, expected failures 45 | return 46 | 47 | value, tb = event.exc_info[1:] 48 | test = event.test 49 | if self.errorsOnly and isinstance(value, test.failureException): 50 | return 51 | evt = events.UserInteractionEvent() 52 | result = self.session.hooks.beforeInteraction(evt) 53 | try: 54 | if not result and evt.handled: 55 | log.warning("Skipping pdb for %s, user interaction not allowed", event) 56 | return 57 | self.pdb.post_mortem(tb) 58 | finally: 59 | self.session.hooks.afterInteraction(evt) 60 | -------------------------------------------------------------------------------- /nose2/tests/functional/test_loadtests_plugin.py: -------------------------------------------------------------------------------- 1 | from nose2.tests._common import FunctionalTestCase 2 | 3 | 4 | class TestLoadTestsPlugin(FunctionalTestCase): 5 | def test_simple(self): 6 | proc = self.runIn( 7 | "scenario/load_tests", "-v", "--plugin=nose2.plugins.loader.loadtests" 8 | ) 9 | self.assertTestRunOutputMatches(proc, stderr="Ran 6 tests") 10 | self.assertTestRunOutputMatches(proc, stderr="test_a..test_simple") 11 | self.assertTestRunOutputMatches(proc, stderr="test_b..test_simple") 12 | self.assertTestRunOutputMatches(proc, stderr="test_c..test_simple") 13 | self.assertTestRunOutputMatches(proc, stderr="test_d..test_simple") 14 | self.assertTestRunOutputMatches(proc, stderr="test_a..test_filter") 15 | self.assertTestRunOutputMatches(proc, stderr="test_c..test_filter") 16 | self.assertEqual(proc.poll(), 0) 17 | 18 | def test_package(self): 19 | proc = self.runIn( 20 | "scenario/load_tests_pkg", 21 | "-v", 22 | "-c=nose2/tests/functional/support/scenario/load_tests_pkg/unittest.cfg", 23 | "--plugin=nose2.plugins.loader.loadtests", 24 | ) 25 | self.assertTestRunOutputMatches(proc, stderr="Ran 2 tests") 26 | self.assertTestRunOutputMatches( 27 | proc, stderr="test..ltpkg.tests.test_find_these.Test" 28 | ) 29 | self.assertTestRunOutputMatches( 30 | proc, stderr="test..ltpkg2.tests.load_tests..Test" 31 | ) 32 | 33 | def test_project_directory_inside_package(self): 34 | proc = self.runIn( 35 | "scenario/load_tests_pkg/ltpkg/tests", 36 | "-v", 37 | "-c=nose2/tests/functional/support/scenario/load_tests_pkg/unittest.cfg", 38 | "--plugin=nose2.plugins.loader.loadtests", 39 | ) 40 | self.assertTestRunOutputMatches(proc, stderr="Ran 1 test") 41 | self.assertTestRunOutputMatches( 42 | proc, stderr="test..ltpkg.tests.test_find_these.Test" 43 | ) 44 | -------------------------------------------------------------------------------- /nose2/tools/params.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains some code copied from :mod:`unittest2` and other 3 | code developed in reference to :mod:`unittest2`. 4 | 5 | unittest2 is Copyright (c) 2001-2010 Python Software Foundation; All 6 | Rights Reserved. See: http://docs.python.org/license.html 7 | 8 | """ 9 | 10 | import itertools 11 | 12 | __unittest = True 13 | 14 | 15 | def cartesian_params(*paramList): 16 | """Make a test function or method parameterized by cartesian product 17 | of parameters 18 | 19 | .. code-block :: python 20 | 21 | import unittest 22 | 23 | from nose2.tools import cartesian_params 24 | 25 | 26 | @cartesian_params((1, 2, 3), ('a', 'b')) 27 | def test_nums(num, char): 28 | assert num < ord(char) 29 | 30 | 31 | class Test(unittest.TestCase): 32 | 33 | @cartesian_params((1, 2, 3), ('a', 'b')) 34 | def test_less_than(self, num, char): 35 | self.assertLess(num, ord(char)) 36 | 37 | Parameters in the list must be defined as iterable objects (such as 38 | ``tuple`` or ``list``). 39 | 40 | """ 41 | 42 | def decorator(func): 43 | func.paramList = itertools.product(*paramList) 44 | return func 45 | 46 | return decorator 47 | 48 | 49 | def params(*paramList): 50 | """Make a test function or method parameterized by parameters. 51 | 52 | .. code-block :: python 53 | 54 | import unittest 55 | 56 | from nose2.tools import params 57 | 58 | 59 | @params(1, 2, 3) 60 | def test_nums(num): 61 | assert num < 4 62 | 63 | 64 | class Test(unittest.TestCase): 65 | 66 | @params((1, 2), (2, 3), (4, 5)) 67 | def test_less_than(self, a, b): 68 | assert a < b 69 | 70 | Parameters in the list may be defined as simple values, or as 71 | tuples. To pass a tuple as a simple value, wrap it in another tuple. 72 | 73 | """ 74 | 75 | def decorator(func): 76 | func.paramList = paramList 77 | return func 78 | 79 | return decorator 80 | -------------------------------------------------------------------------------- /nose2/plugins/printhooks.py: -------------------------------------------------------------------------------- 1 | """ 2 | This plugin is primarily useful for plugin authors who want to debug 3 | their plugins. 4 | 5 | It prints each hook that is called to stderr, along with details of 6 | the event that was passed to the hook. 7 | 8 | To do that, this plugin overrides :meth:`nose2.events.Plugin.register` 9 | and, after registration, replaces all existing 10 | :class:`nose2.events.Hook` instances in ``session.hooks`` with 11 | instances of a :class:`~nose2.events.Hook` subclass that prints information about 12 | each call. 13 | """ 14 | 15 | from __future__ import annotations 16 | 17 | import sys 18 | 19 | from nose2 import events 20 | 21 | INDENT: list[str] = [] 22 | __unittest = True 23 | 24 | 25 | class PrintHooks(events.Plugin): 26 | """Print hooks as they are called""" 27 | 28 | configSection = "print-hooks" 29 | commandLineSwitch = ( 30 | "P", 31 | "print-hooks", 32 | "Print names of hooks in order of execution", 33 | ) 34 | 35 | def register(self): 36 | """Override to inject noisy hook instances. 37 | 38 | Replaces :class:`~nose2.events.Hook` instances in ``self.session.hooks.hooks`` 39 | with noisier objects. 40 | 41 | """ 42 | super().register() 43 | # now we can be sure that all other plugins have loaded 44 | # and this plugin is active, patch in our hook class 45 | self.session.hooks.hookClass = NoisyHook 46 | for attr, hook in self.session.hooks.hooks.items(): 47 | newhook = NoisyHook(attr) 48 | newhook.plugins = hook.plugins 49 | self.session.hooks.hooks[attr] = newhook 50 | 51 | 52 | class NoisyHook(events.Hook): 53 | def __call__(self, event): 54 | _report(self.method, event) 55 | _indent() 56 | try: 57 | return super().__call__(event) 58 | finally: 59 | _dedent() 60 | 61 | 62 | def _report(method, event): 63 | sys.stderr.write("\n{}{}: {}".format("".join(INDENT), method, event)) 64 | 65 | 66 | def _indent(): 67 | INDENT.append(" ") 68 | 69 | 70 | def _dedent(): 71 | if INDENT: 72 | INDENT.pop() 73 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "setuptools.build_meta" 3 | requires = [ 4 | "setuptools", 5 | ] 6 | 7 | [project] 8 | name = "nose2" 9 | description = "unittest with plugins" 10 | readme = "README.rst" 11 | keywords = [ 12 | "testing", 13 | "tests", 14 | "unittest", 15 | ] 16 | license = { text = "BSD-2-Clause" } 17 | authors = [ 18 | { name = "Stephen Rosen", email = "dev@nose2.io" }, 19 | ] 20 | requires-python = ">=3.8" 21 | classifiers = [ 22 | "Development Status :: 4 - Beta", 23 | "Environment :: Console", 24 | "Intended Audience :: Developers", 25 | "License :: OSI Approved :: BSD License", 26 | "Operating System :: OS Independent", 27 | "Programming Language :: Python", 28 | "Programming Language :: Python :: 3 :: Only", 29 | "Programming Language :: Python :: 3.8", 30 | "Programming Language :: Python :: 3.9", 31 | "Programming Language :: Python :: 3.10", 32 | "Programming Language :: Python :: 3.11", 33 | "Programming Language :: Python :: 3.12", 34 | "Programming Language :: Python :: 3.13", 35 | "Programming Language :: Python :: Implementation :: CPython", 36 | "Programming Language :: Python :: Implementation :: PyPy", 37 | "Topic :: Software Development :: Libraries", 38 | "Topic :: Software Development :: Libraries :: Python Modules", 39 | "Topic :: Software Development :: Testing", 40 | ] 41 | dynamic = [ 42 | "version", 43 | ] 44 | optional-dependencies.coverage_plugin = [ 45 | "coverage", 46 | ] 47 | optional-dependencies.dev = [ 48 | "sphinx", 49 | "sphinx-issues", 50 | "sphinx-rtd-theme", 51 | ] 52 | urls.changelog = "https://docs.nose2.io/en/latest/changelog.html" 53 | urls.documentation = "https://docs.nose2.io/" 54 | urls.repository = "https://github.com/nose-devs/nose2" 55 | scripts.nose2 = "nose2:discover" 56 | 57 | [tool.setuptools.dynamic] 58 | version = { attr = "nose2.__version__" } 59 | 60 | [tool.isort] 61 | profile = "black" 62 | known_third_party = [ 63 | "coverage", 64 | "mock", 65 | ] 66 | 67 | [tool.mypy] 68 | # strict = true 69 | sqlite_cache = true 70 | ignore_missing_imports = true 71 | disallow_subclassing_any = false 72 | files = [ 73 | "nose2", 74 | ] 75 | -------------------------------------------------------------------------------- /docs/plugins.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Plugins for nose2 3 | ================= 4 | 5 | Built in and Loaded by Default 6 | ============================== 7 | 8 | These plugins are loaded by default. To exclude one of these plugins 9 | from loading, add the plugin's fully qualified module name to the 10 | ``exclude-plugins`` list in a config file's ``[unittest]`` section, 11 | or pass the plugin module with the ``--exclude-plugin`` argument 12 | on the command line. You can also pass plugin module names to exclude to a 13 | :class:`nose2.main.PluggableTestProgram` using the ``excludePlugins`` 14 | keyword argument. 15 | 16 | .. toctree:: 17 | :maxdepth: 2 18 | 19 | plugins/discovery 20 | plugins/functions 21 | plugins/generators 22 | plugins/parameters 23 | plugins/testcases 24 | plugins/testclasses 25 | plugins/loadtests 26 | plugins/dundertests 27 | plugins/result 28 | plugins/buffer 29 | plugins/debugger 30 | plugins/failfast 31 | plugins/logcapture 32 | plugins/coverage 33 | plugins/prettyassert 34 | 35 | 36 | Built in but *not* Loaded by Default 37 | ==================================== 38 | 39 | These plugins are available as part of the nose2 package but *are not 40 | loaded by default*. To load one of these plugins, add the plugin module 41 | name (as dot-separated, fully qualified name) to the ``plugins`` list 42 | in a config file's ``[unittest]`` 43 | section, or pass the plugin module with the ``--plugin`` argument on 44 | the command line. You can also pass plugin module names to a 45 | :class:`nose2.main.PluggableTestProgram` using the ``plugins`` keyword 46 | argument. 47 | 48 | .. toctree:: 49 | :maxdepth: 2 50 | 51 | plugins/junitxml 52 | plugins/attrib 53 | plugins/mp 54 | plugins/layers 55 | plugins/doctests 56 | plugins/outcomes 57 | plugins/collect 58 | plugins/testid 59 | plugins/prof 60 | plugins/printhooks 61 | plugins/eggdiscovery 62 | 63 | 64 | Third-party Plugins 65 | =================== 66 | 67 | If you are a plugin author, please add your plugin to the list on the 68 | `nose2 wiki`_. If you are looking for more plugins, check that list! 69 | 70 | .. _nose2 wiki : https://github.com/nose-devs/nose2/wiki/Plugins 71 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | pull_request: 5 | # build weekly at 4:00 AM UTC 6 | schedule: 7 | - cron: '0 4 * * 1' 8 | jobs: 9 | # this job ensures that tests can run from the packaged version, which means 10 | # that nose2 is correctly packaging and distributing its tests 11 | test-sdist: 12 | name: run tests from packaged source 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-python@v5 17 | with: 18 | python-version: "3.12" 19 | - name: install build prereqs 20 | run: pip install build 21 | - name: test 22 | run: | 23 | python -m build --sdist 24 | version="$(cat nose2/__init__.py | grep '^__version__' | cut -d '"' -f2)" 25 | cd dist 26 | tar -xzf "nose2-${version}.tar.gz" 27 | cd "nose2-${version}" 28 | pip install -e '.[dev]' 29 | nose2 -v --pretty-assert 30 | 31 | typing: 32 | name: 'typing (mypy)' 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | - uses: actions/setup-python@v5 37 | with: 38 | python-version: "3.12" 39 | - run: pip install tox 40 | - run: tox run -e mypy 41 | 42 | test: 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | # for the matrix, linux on all pythons 47 | # 48 | # any additional builds for windows and macos 49 | # handled via `include` to avoid an over-large test matrix 50 | os: [ubuntu-latest] 51 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.9"] 52 | include: 53 | - os: windows-latest 54 | python-version: "3.x" 55 | - os: macos-latest 56 | python-version: "3.x" 57 | name: "python=${{ matrix.python-version }} os=${{ matrix.os }}" 58 | runs-on: ${{ matrix.os }} 59 | steps: 60 | - uses: actions/checkout@v4 61 | - uses: actions/setup-python@v5 62 | with: 63 | python-version: ${{ matrix.python-version }} 64 | allow-prereleases: true 65 | - name: install tox 66 | run: python -m pip install -U tox 67 | - name: test 68 | run: python -m tox -e py 69 | -------------------------------------------------------------------------------- /nose2/plugins/outcomes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Map exceptions to test outcomes. 3 | 4 | This plugin implements :func:`setTestOutcome` to enable simple mapping 5 | of exception classes to existing test outcomes. 6 | 7 | By setting a list of exception classes in a nose2 config file, you can 8 | configure exceptions that would otherwise be treated as test errors, 9 | to be treated as failures or skips instead: 10 | 11 | .. code-block :: ini 12 | 13 | [outcomes] 14 | always-on = True 15 | treat-as-fail = NotImplementedError 16 | treat-as-skip = TodoError 17 | IOError 18 | 19 | """ 20 | 21 | from nose2.events import Plugin 22 | 23 | __unittest = True 24 | 25 | 26 | class Outcomes(Plugin): 27 | """Map exceptions to other test outcomes""" 28 | 29 | configSection = "outcomes" 30 | commandLineSwitch = ( 31 | None, 32 | "set-outcomes", 33 | "Treat some configured exceptions as failure or skips", 34 | ) 35 | 36 | def __init__(self) -> None: 37 | self.treatAsFail = set(self.config.as_list("treat-as-fail", [])) 38 | self.treatAsSkip = set(self.config.as_list("treat-as-skip", [])) 39 | 40 | def setTestOutcome(self, event): 41 | """Update outcome, exc_info and reason based on configured mappings""" 42 | if event.exc_info: 43 | ec, ev, tb = event.exc_info 44 | classname = ec.__name__ 45 | if classname in self.treatAsFail: 46 | short, long_ = self.labels(classname) 47 | self._setOutcome(event, "failed", short, long_) 48 | elif classname in self.treatAsSkip: 49 | short, long_ = self.labels(classname, upper=False) 50 | self._setOutcome(event, "skipped", short, f"{long_}: '{ev}'", str(ev)) 51 | 52 | def labels(self, label, upper=True): 53 | if upper: 54 | label = label.upper() 55 | else: 56 | label = label.lower() 57 | short = label[0] 58 | return short, label 59 | 60 | def _setOutcome(self, event, outcome, shortLabel, longLabel, reason=None): 61 | event.outcome = outcome 62 | event.shortLabel = shortLabel 63 | event.longLabel = longLabel 64 | if reason: 65 | event.exc_info = None 66 | event.reason = reason 67 | -------------------------------------------------------------------------------- /nose2/runner.py: -------------------------------------------------------------------------------- 1 | # This module contains some code copied from unittest2/runner.py and other 2 | # code developed in reference to that module and others within unittest2. 3 | # unittest2 is Copyright (c) 2001-2010 Python Software Foundation; All 4 | # Rights Reserved. See: http://docs.python.org/license.html 5 | 6 | import time 7 | 8 | from nose2 import events, result 9 | 10 | __unittest = True 11 | 12 | 13 | class PluggableTestRunner: 14 | """Test runner that defers most work to plugins. 15 | 16 | :param session: Test run session 17 | 18 | .. attribute :: resultClass 19 | 20 | Class to instantiate to create test result. Default: 21 | :class:`nose2.result.PluggableTestResult`. 22 | 23 | """ 24 | 25 | resultClass = result.PluggableTestResult 26 | 27 | def __init__(self, session) -> None: 28 | self.session = session 29 | 30 | def run(self, test): 31 | """Run tests. 32 | 33 | :param test: A unittest :class:`TestSuite`` or :class:`TestClass`. 34 | :returns: Test result 35 | 36 | Fires :func:`startTestRun` and :func:`stopTestRun` hooks. 37 | 38 | """ 39 | result = self._makeResult() 40 | 41 | def executor(suite, result): 42 | return suite(result) 43 | 44 | startTime = time.time() 45 | event = events.StartTestRunEvent(self, test, result, startTime, executor) 46 | self.session.hooks.startTestRun(event) 47 | 48 | # allows startTestRun to modify test suite 49 | test = event.suite 50 | # ... and test execution 51 | executor = event.executeTests 52 | try: 53 | if not event.handled: 54 | executor(test, result) 55 | finally: 56 | stopTime = time.time() 57 | timeTaken = stopTime - startTime 58 | event = events.StopTestRunEvent(self, result, stopTime, timeTaken) 59 | self.session.hooks.stopTestRun(event) 60 | self.session.hooks.afterTestRun(event) 61 | return result 62 | 63 | def _makeResult(self): 64 | result = self.resultClass(self.session) 65 | event = events.ResultCreatedEvent(result) 66 | self.session.hooks.resultCreated(event) 67 | self.session.testResult = event.result 68 | return event.result 69 | 70 | def __repr__(self): 71 | return "<%s>" % self.__class__.__name__ 72 | -------------------------------------------------------------------------------- /nose2/plugins/prof.py: -------------------------------------------------------------------------------- 1 | """ 2 | Profile test execution using cProfile. 3 | 4 | This plugin implements :func:`startTestRun` and replaces 5 | ``event.executeTests`` with :meth:`cProfile.Profile.runcall`. It 6 | implements :func:`beforeSummaryReport` to output profiling information 7 | before the final test summary time. Config file options ``filename``, 8 | ``sort`` and ``restrict`` can be used to change where profiling 9 | information is saved and how it is presented. 10 | 11 | Load this plugin by running nose2 with the 12 | `--plugin=nose2.plugins.prof` option and activate it 13 | with the `--profile` option,or put the corresponding 14 | entries (`plugin` and `always_on`) in the respective 15 | sections of the configuration file. 16 | 17 | """ 18 | 19 | import cProfile 20 | import logging 21 | import pstats 22 | 23 | from nose2 import events, util 24 | 25 | log = logging.getLogger(__name__) 26 | __unittest = True 27 | 28 | 29 | class Profiler(events.Plugin): 30 | """Profile the test run""" 31 | 32 | configSection = "profiler" 33 | commandLineSwitch = ("P", "profile", "Run tests under profiler") 34 | 35 | def __init__(self) -> None: 36 | self.pfile = self.config.as_str("filename", "") 37 | self.sort = self.config.as_str("sort", "cumulative") 38 | self.restrict = self.config.as_list("restrict", []) 39 | self.fileno = None 40 | 41 | def startTestRun(self, event): 42 | """Set up the profiler""" 43 | self.prof = cProfile.Profile() 44 | event.executeTests = self.prof.runcall 45 | 46 | def beforeSummaryReport(self, event): 47 | """Output profiling results""" 48 | 49 | # write prof output to stream 50 | class Stream: 51 | def write(self, *msg): 52 | for m in msg: 53 | event.stream.write(m) 54 | event.stream.write(" ") 55 | event.stream.flush() 56 | 57 | stream = Stream() 58 | prof_stats = pstats.Stats(self.prof, stream=stream) 59 | prof_stats.sort_stats(self.sort) 60 | event.stream.writeln(util.ln("Profiling results")) 61 | if self.restrict: 62 | prof_stats.print_stats(*self.restrict) 63 | else: 64 | prof_stats.print_stats() 65 | 66 | if self.pfile: 67 | prof_stats.dump_stats(self.pfile) 68 | 69 | self.prof.disable() 70 | event.stream.writeln("") 71 | -------------------------------------------------------------------------------- /nose2/tests/functional/test_doctests_plugin.py: -------------------------------------------------------------------------------- 1 | from nose2.tests._common import FunctionalTestCase, support_file 2 | 3 | 4 | class TestDoctestsPlugin(FunctionalTestCase): 5 | def test_simple(self): 6 | proc = self.runIn( 7 | "scenario/doctests", 8 | "-v", 9 | "--plugin=nose2.plugins.doctests", 10 | "--with-doctest", 11 | ) 12 | self.assertTestRunOutputMatches(proc, stderr="Ran 6 tests") 13 | self.assertTestRunOutputMatches(proc, stderr="Doctest: docs ... ok") 14 | self.assertTestRunOutputMatches(proc, stderr="Doctest: docs.rst ... ok") 15 | self.assertTestRunOutputMatches(proc, stderr="Doctest: docs.txt ... ok") 16 | self.assertTestRunOutputMatches( 17 | proc, stderr="Doctest: doctests_pkg1.docs1 ... ok" 18 | ) 19 | self.assertTestRunOutputMatches(proc, stderr="Doctest: docs1.rst ... ok") 20 | self.assertTestRunOutputMatches(proc, stderr="Doctest: docs1.txt ... ok") 21 | self.assertEqual(proc.poll(), 0) 22 | 23 | def test_start_directory_inside_package(self): 24 | proc = self.runIn( 25 | "scenario/doctests/doctests_pkg1", 26 | "-v", 27 | "--plugin=nose2.plugins.doctests", 28 | "--with-doctest", 29 | "-t", 30 | support_file("scenario/doctests"), 31 | ) 32 | self.assertTestRunOutputMatches(proc, stderr="Ran 3 tests") 33 | self.assertTestRunOutputMatches( 34 | proc, stderr="Doctest: doctests_pkg1.docs1 ... ok" 35 | ) 36 | self.assertTestRunOutputMatches(proc, stderr="Doctest: docs1.rst ... ok") 37 | self.assertTestRunOutputMatches(proc, stderr="Doctest: docs1.txt ... ok") 38 | self.assertEqual(proc.poll(), 0) 39 | 40 | def test_project_directory_inside_package(self): 41 | proc = self.runIn( 42 | "scenario/doctests/doctests_pkg1", 43 | "-v", 44 | "--plugin=nose2.plugins.doctests", 45 | "--with-doctest", 46 | ) 47 | self.assertTestRunOutputMatches(proc, stderr="Ran 3 tests") 48 | self.assertTestRunOutputMatches( 49 | proc, stderr="Doctest: doctests_pkg1.docs1 ... ok" 50 | ) 51 | self.assertTestRunOutputMatches(proc, stderr="Doctest: docs1.rst ... ok") 52 | self.assertTestRunOutputMatches(proc, stderr="Doctest: docs1.txt ... ok") 53 | self.assertEqual(proc.poll(), 0) 54 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_logcapture_plugin.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | 5 | from nose2 import session 6 | from nose2.plugins import logcapture 7 | from nose2.tests._common import TestCase 8 | 9 | log = logging.getLogger(__name__) 10 | 11 | 12 | class StubLogging: 13 | def __init__(self, name=None) -> None: 14 | self.name = name 15 | self.handlers: list[logging.Handler] = [] 16 | self.level = None 17 | 18 | def getLogger(self, _name=None): 19 | return self 20 | 21 | def addHandler(self, handler): 22 | self.handlers.append(handler) 23 | 24 | def setLevel(self, level): 25 | self.level = level 26 | 27 | def debug(self, message, *arg): 28 | # import pdb; pdb.set_trace() 29 | for handler in self.handlers: 30 | handler.emit(StubRecord(message % arg)) 31 | 32 | 33 | class StubRecord: 34 | def __init__(self, message) -> None: 35 | self.message = message 36 | self.name = "stub" 37 | self.levelname = "stub" 38 | self.exc_info = None 39 | self.exc_text = None 40 | self.stack_info = None 41 | 42 | def getMessage(self): 43 | return self.message 44 | 45 | 46 | class LogCaptureUnitTest(TestCase): 47 | tags = ["unit"] 48 | 49 | def setUp(self): 50 | self.session = session.Session() 51 | self.plugin = logcapture.LogCapture(session=self.session) 52 | self.logging = logcapture.logging 53 | logcapture.logging = StubLogging() 54 | 55 | def tearDown(self): 56 | logcapture.logging = self.logging 57 | 58 | def event(self, error=True, failed=False): 59 | e = Event() 60 | e.metadata = {} 61 | return e 62 | 63 | def test_buffer_cleared_after_each_test(self): 64 | self.plugin.startTestRun(None) 65 | self.plugin.startTest(None) 66 | logcapture.logging.getLogger("test").debug("hello") 67 | assert self.plugin.handler.buffer 68 | self.plugin.setTestOutcome(self.event()) 69 | assert self.plugin.handler.buffer 70 | self.plugin.stopTest(None) 71 | assert not self.plugin.handler.buffer 72 | 73 | def test_buffered_logs_attached_to_event(self): 74 | self.plugin.startTestRun(None) 75 | self.plugin.startTest(None) 76 | logcapture.logging.getLogger("test").debug("hello") 77 | assert self.plugin.handler.buffer 78 | e = self.event() 79 | self.plugin.setTestOutcome(e) 80 | assert "logs" in e.metadata, "No log in %s" % e.metadata 81 | 82 | 83 | class Event: 84 | pass 85 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_attrib_plugin.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from nose2 import events, session 4 | from nose2.plugins import attrib 5 | from nose2.tests._common import TestCase 6 | 7 | 8 | class TestAttribPlugin(TestCase): 9 | tags = ["unit"] 10 | 11 | def setUp(self): 12 | class TC_1(TestCase): 13 | tags = ["a", "b"] 14 | 15 | def test_a(self): 16 | pass 17 | 18 | test_a.a = 1 19 | test_a.c = 0 20 | 21 | def test_b(self): 22 | pass 23 | 24 | test_b.b = 1 25 | 26 | self.TC_1 = TC_1 27 | self.session = session.Session() 28 | self.plugin = attrib.AttributeSelector(session=self.session) 29 | self.plugin.register() 30 | 31 | def test_validate_attribs_with_simple_values(self): 32 | assert self.plugin.validateAttrib(self.TC_1("test_a"), [[("a", "1")]]) 33 | assert self.plugin.validateAttrib(self.TC_1("test_a"), [[("a", True)]]) 34 | assert self.plugin.validateAttrib(self.TC_1("test_a"), [[("c", False)]]) 35 | assert self.plugin.validateAttrib(self.TC_1("test_b"), [[("b", "1")]]) 36 | assert not self.plugin.validateAttrib(self.TC_1("test_a"), [[("a", False)]]) 37 | assert not self.plugin.validateAttrib(self.TC_1("test_a"), [[("c", True)]]) 38 | assert not self.plugin.validateAttrib(self.TC_1("test_a"), [[("a", "2")]]) 39 | assert not self.plugin.validateAttrib(self.TC_1("test_a"), [[("b", "1")]]) 40 | 41 | def test_validate_attribs_with_callable(self): 42 | assert self.plugin.validateAttrib( 43 | self.TC_1("test_a"), [[("a", lambda key, test: True)]] 44 | ) 45 | assert not self.plugin.validateAttrib( 46 | self.TC_1("test_a"), [[("a", lambda key, test: False)]] 47 | ) 48 | 49 | def test_validate_attribs_against_list(self): 50 | assert self.plugin.validateAttrib(self.TC_1("test_a"), [[("tags", "a")]]) 51 | assert self.plugin.validateAttrib(self.TC_1("test_a"), [[("tags", "b")]]) 52 | assert not self.plugin.validateAttrib(self.TC_1("test_a"), [[("tags", "c")]]) 53 | 54 | def test_module_loaded_suite_filters_suite(self): 55 | self.plugin.attribs = ["a"] 56 | suite = unittest.TestSuite() 57 | suite.addTest(self.TC_1("test_a")) 58 | suite.addTest(self.TC_1("test_b")) 59 | event = events.ModuleSuiteEvent(None, None, suite) 60 | self.session.hooks.moduleLoadedSuite(event) 61 | self.assertEqual(len(event.suite._tests), 1) 62 | self.assertEqual(event.suite._tests[0]._testMethodName, "test_a") 63 | -------------------------------------------------------------------------------- /nose2/config.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | TRUE_VALS = {"1", "t", "true", "on", "yes", "y"} 4 | __unittest = True 5 | 6 | 7 | class Config: 8 | """Configuration for a plugin or other entities. 9 | 10 | Encapsulates configuration for a single plugin or other element. 11 | Corresponds to a :class:`ConfigParser.Section` but provides an 12 | extended interface for extracting items as a certain type. 13 | 14 | """ 15 | 16 | def __init__(self, items) -> None: 17 | self._items = items 18 | self._mvd: dict[str, list[str]] = {} 19 | for k, v in items: 20 | self._mvd.setdefault(k, []).append(v) 21 | 22 | def __getitem__(self, key): 23 | return self._mvd[key] 24 | 25 | def as_bool(self, key, default=None): 26 | """Get key value as boolean 27 | 28 | 1, t, true, on, yes and y (case insensitive) are accepted as ``True`` 29 | values. All other values are ``False``. 30 | 31 | """ 32 | try: 33 | val = self._mvd[key][0].strip() 34 | except KeyError: 35 | return default 36 | except IndexError: 37 | # setting = -> False 38 | return False 39 | return val.lower() in TRUE_VALS 40 | 41 | def as_int(self, key, default=None): 42 | """Get key value as integer""" 43 | return self._cast(key, int, default) 44 | 45 | def as_float(self, key, default=None): 46 | """Get key value as float""" 47 | return self._cast(key, float, default) 48 | 49 | def as_str(self, key, default=None): 50 | """Get key value as str""" 51 | return self._cast(key, str, default) 52 | 53 | def as_list(self, key, default=None): 54 | """Get key value as list. 55 | 56 | The value is split into lines and returned as a list. Lines 57 | are stripped of whitespace, and lines beginning with # are 58 | skipped. 59 | 60 | """ 61 | lines = [] 62 | try: 63 | vlist = self[key] 64 | except KeyError: 65 | return default 66 | for val in vlist: 67 | lines.extend( 68 | line.strip() 69 | for line in val.splitlines() 70 | if line.strip() and not line.strip().startswith("#") 71 | ) 72 | return lines 73 | 74 | def get(self, key, default=None): 75 | """Get key value""" 76 | return self.as_str(key, default) 77 | 78 | def _cast(self, key, type_, default): 79 | try: 80 | return type_(self._mvd[key][0].strip()) 81 | except (KeyError, IndexError): 82 | return default 83 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_outcomes_plugin.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from nose2 import events, result, session 4 | from nose2.plugins import outcomes 5 | from nose2.tests._common import TestCase 6 | 7 | 8 | class TestOutComesPlugin(TestCase): 9 | tags = ["unit"] 10 | 11 | def setUp(self): 12 | self.session = session.Session() 13 | self.result = result.PluggableTestResult(self.session) 14 | self.plugin = outcomes.Outcomes(session=self.session) 15 | self.plugin.register() 16 | 17 | class Test(TestCase): 18 | def test_e1(self): 19 | raise KeyError("k") 20 | 21 | def test_e2(self): 22 | raise TypeError("x") 23 | 24 | def test_e3(self): 25 | raise OSError("o") 26 | 27 | self.case = Test 28 | 29 | class Watcher(events.Plugin): 30 | def __init__(self) -> None: 31 | self.outcomes: dict[str, list[events.TestOutcomeEvent]] = {} 32 | 33 | def testOutcome(self, event): 34 | self.outcomes.setdefault(event.outcome, []).append(event) 35 | 36 | self.watcher = Watcher(session=self.session) 37 | self.watcher.register() 38 | 39 | def test_labels_upper(self): 40 | self.assertEqual(self.plugin.labels("xxx"), ("X", "XXX")) 41 | 42 | def test_can_do_nothing_when_not_configured(self): 43 | test = self.case("test_e1") 44 | test(self.result) 45 | assert self.watcher.outcomes["error"] 46 | assert "failed" not in self.watcher.outcomes 47 | 48 | def test_can_treat_as_fail(self): 49 | self.plugin.treatAsFail.add("KeyError") 50 | test = self.case("test_e1") 51 | test(self.result) 52 | assert self.watcher.outcomes["failed"] 53 | assert "error" not in self.watcher.outcomes 54 | 55 | def test_can_treat_as_skip(self): 56 | self.plugin.treatAsSkip.add("KeyError") 57 | test = self.case("test_e1") 58 | test(self.result) 59 | assert self.watcher.outcomes["skipped"] 60 | assert "error" not in self.watcher.outcomes 61 | 62 | def test_can_handle_multiple_events_cleanly(self): 63 | self.plugin.treatAsSkip.add("KeyError") 64 | self.plugin.treatAsFail.add("TypeError") 65 | test = self.case("test_e1") 66 | test(self.result) 67 | test = self.case("test_e2") 68 | test(self.result) 69 | test = self.case("test_e3") 70 | test(self.result) 71 | self.assertEqual(len(self.watcher.outcomes["skipped"]), 1) 72 | self.assertEqual(len(self.watcher.outcomes["error"]), 1) 73 | self.assertEqual(len(self.watcher.outcomes["failed"]), 1) 74 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_debugger_plugin.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from nose2 import events, result, session 4 | from nose2.plugins import debugger 5 | from nose2.tests._common import TestCase 6 | 7 | 8 | class NullHandler(logging.Handler): 9 | def emit(self, record): 10 | pass 11 | 12 | 13 | class StubPdb: 14 | def __init__(self) -> None: 15 | self.called = False 16 | self.tb = None 17 | 18 | def post_mortem(self, tb): 19 | self.called = True 20 | self.tb = tb 21 | 22 | 23 | class NoInteraction(events.Plugin): 24 | def beforeInteraction(self, event): 25 | event.handled = True 26 | return False 27 | 28 | 29 | class TestDebugger(TestCase): 30 | tags = ["unit"] 31 | 32 | def setUp(self): 33 | self.session = session.Session() 34 | self.plugin = debugger.Debugger(session=self.session) 35 | self.result = result.PluggableTestResult(self.session) 36 | 37 | class Test(TestCase): 38 | def test(self): 39 | pass 40 | 41 | def test_err(self): 42 | raise Exception("oops") 43 | 44 | def test_fail(self): 45 | assert False 46 | 47 | self.case = Test 48 | 49 | self.pdb = self.plugin.pdb 50 | self.plugin.pdb = StubPdb() 51 | 52 | self.plugin.register() 53 | 54 | super(TestCase, self).setUp() 55 | 56 | def tearDown(self): 57 | self.plugin.pdb = self.pdb 58 | super(TestCase, self).tearDown() 59 | 60 | def test_does_not_call_pdb_on_success(self): 61 | test = self.case("test") 62 | test(self.result) 63 | assert not self.plugin.pdb.called, "pdb was called on success" 64 | 65 | def test_does_call_pdb_on_error(self): 66 | test = self.case("test_err") 67 | test(self.result) 68 | assert self.plugin.pdb.called, "pdb was not called on error" 69 | 70 | def test_does_call_pdb_on_failure(self): 71 | test = self.case("test_fail") 72 | test(self.result) 73 | assert self.plugin.pdb.called, "pdb was not called on failure" 74 | 75 | def test_does_not_call_pdb_on_failure_if_config_set(self): 76 | self.plugin.errorsOnly = True 77 | test = self.case("test_fail") 78 | test(self.result) 79 | assert ( 80 | not self.plugin.pdb.called 81 | ), "pdb was called on failure when errorsOnly set" 82 | 83 | def test_other_plugins_can_prevent_interaction(self): 84 | # prevent 'no logger for x' warnings 85 | debugger.log.addHandler(NullHandler()) 86 | nono = NoInteraction(session=self.session) 87 | nono.register() 88 | test = self.case("test_err") 89 | test(self.result) 90 | assert ( 91 | not self.plugin.pdb.called 92 | ), "pdb was called despite beforeInteraction returning False" 93 | -------------------------------------------------------------------------------- /nose2/tests/functional/test_attrib_plugin.py: -------------------------------------------------------------------------------- 1 | from nose2.tests._common import FunctionalTestCase 2 | 3 | 4 | class TestAttribPlugin(FunctionalTestCase): 5 | def test_simple_true(self): 6 | proc = self.runIn( 7 | "scenario/tests_in_package", 8 | "-v", 9 | "--plugin=nose2.plugins.attrib", 10 | "-A", 11 | "a", 12 | ) 13 | self.assertTestRunOutputMatches(proc, stderr="Ran 4 tests") 14 | self.assertTestRunOutputMatches(proc, stderr="test_params_method") 15 | self.assertTestRunOutputMatches(proc, stderr="test_func") 16 | 17 | def test_simple_false(self): 18 | proc = self.runIn( 19 | "scenario/tests_in_package", 20 | "-v", 21 | "--plugin=nose2.plugins.attrib", 22 | "-A", 23 | "!a", 24 | ) 25 | self.assertTestRunOutputMatches(proc, stderr="Ran 21 tests") 26 | 27 | def test_simple_value(self): 28 | proc = self.runIn( 29 | "scenario/tests_in_package", 30 | "-v", 31 | "--plugin=nose2.plugins.attrib", 32 | "-A", 33 | "b=2", 34 | ) 35 | self.assertTestRunOutputMatches(proc, stderr="Ran 2 tests") 36 | 37 | def test_list_value(self): 38 | proc = self.runIn( 39 | "scenario/tests_in_package", 40 | "-v", 41 | "--plugin=nose2.plugins.attrib", 42 | "-A", 43 | "tags=func", 44 | ) 45 | self.assertTestRunOutputMatches(proc, stderr="Ran 8 tests") 46 | self.assertTestRunOutputMatches(proc, stderr="test_params_func") 47 | self.assertTestRunOutputMatches(proc, stderr="test_func") 48 | self.assertTestRunOutputMatches(proc, stderr="test_gen") 49 | 50 | def test_list_value_negation(self): 51 | proc = self.runIn( 52 | "scenario/tests_in_package", 53 | "-v", 54 | "--plugin=nose2.plugins.attrib", 55 | "-A", 56 | "!tags=func", 57 | ) 58 | self.assertTestRunOutputMatches(proc, stderr="Ran 8 tests") 59 | self.assertTestRunOutputMatches(proc, stderr="test_gen_method") 60 | self.assertTestRunOutputMatches(proc, stderr="test_params_method") 61 | self.assertTestRunOutputMatches(proc, stderr="test_ok") 62 | self.assertTestRunOutputMatches(proc, stderr="test_failed") 63 | self.assertTestRunOutputMatches(proc, stderr="test_skippy") 64 | self.assertTestRunOutputMatches(proc, stderr="test_typeerr") 65 | 66 | def test_eval_expr(self): 67 | proc = self.runIn( 68 | "scenario/tests_in_package", 69 | "-v", 70 | "--plugin=nose2.plugins.attrib", 71 | "-E", 72 | "a == b and a", 73 | ) 74 | self.assertTestRunOutputMatches(proc, stderr="Ran 1 test") 75 | self.assertTestRunOutputMatches(proc, stderr="skippy") 76 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_package/pkg1/test/test_things.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class SomeTests(unittest.TestCase): 5 | tags = ["case"] 6 | 7 | def test_ok(self): 8 | pass 9 | 10 | test_ok.tags = ["method", "pass"] # type: ignore[attr-defined] 11 | test_ok.a = 0 # type: ignore[attr-defined] 12 | test_ok.b = 1 # type: ignore[attr-defined] 13 | 14 | def test_typeerr(self): 15 | raise TypeError("oops") 16 | 17 | test_typeerr.tags = ["method"] # type: ignore[attr-defined] 18 | 19 | def test_failed(self): 20 | print("Hello stdout") 21 | assert False, "I failed" 22 | 23 | test_failed.tags = ["method"] # type: ignore[attr-defined] 24 | 25 | def test_skippy(self): 26 | raise unittest.SkipTest("I wanted to skip") 27 | 28 | test_skippy.a = 1 # type: ignore[attr-defined] 29 | test_skippy.b = 1 # type: ignore[attr-defined] 30 | 31 | def test_gen_method(self): 32 | def check(x): 33 | assert x == 1 34 | 35 | check.b = 2 # type: ignore[attr-defined] 36 | yield check, 1 37 | yield check, 2 38 | 39 | test_gen_method.a = 1 # type: ignore[attr-defined] 40 | test_gen_method.b = 1 # type: ignore[attr-defined] 41 | 42 | def test_params_method(self, a): 43 | self.assertEqual(a, 1) 44 | 45 | test_params_method.paramList = (1, 2) # type: ignore[attr-defined] 46 | test_params_method.a = 1 # type: ignore[attr-defined] 47 | 48 | 49 | def test_func(): 50 | assert 1 == 1 51 | 52 | 53 | test_func.a = 1 # type: ignore[attr-defined] 54 | test_func.b = 0 # type: ignore[attr-defined] 55 | test_func.tags = ["func", "pass"] # type: ignore[attr-defined] 56 | 57 | 58 | def test_gen(): 59 | def check(a, b): 60 | assert a == b 61 | 62 | check.tags = ["func"] # type: ignore[attr-defined] 63 | for i in range(0, 5): 64 | yield check, (i, i) 65 | 66 | 67 | test_gen.testGenerator = True # type: ignore[attr-defined] 68 | test_gen.tags = ["func"] # type: ignore[attr-defined] 69 | 70 | 71 | def test_gen_nose_style(): 72 | def check(a, b): 73 | assert a == b 74 | 75 | for i in range(0, 5): 76 | yield check, i, i 77 | 78 | 79 | did_setup = False 80 | 81 | 82 | def setup(): 83 | global did_setup 84 | did_setup = True 85 | 86 | 87 | def test_fixt(): 88 | assert did_setup 89 | 90 | 91 | test_fixt.setup = setup # type: ignore[attr-defined] 92 | 93 | 94 | def test_params_func(a): 95 | assert a == 1 96 | 97 | 98 | test_params_func.paramList = (1, 2) # type: ignore[attr-defined] 99 | test_params_func.tags = ["func"] # type: ignore[attr-defined] 100 | 101 | 102 | def test_params_func_multi_arg(a, b): 103 | assert a == b 104 | 105 | 106 | test_params_func_multi_arg.paramList = ((1, 1), (1, 2), (2, 2)) # type: ignore[attr-defined] # noqa: E501 107 | -------------------------------------------------------------------------------- /nose2/tests/functional/support/scenario/tests_in_unzipped_eggs/pkgunegg-0.0.0-py2.7.egg/pkgunegg/test/test_things.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class SomeTests(unittest.TestCase): 5 | tags = ["case"] 6 | 7 | def test_ok(self): 8 | pass 9 | 10 | test_ok.tags = ["method", "pass"] # type: ignore[attr-defined] 11 | test_ok.a = 0 # type: ignore[attr-defined] 12 | test_ok.b = 1 # type: ignore[attr-defined] 13 | 14 | def test_typeerr(self): 15 | raise TypeError("oops") 16 | 17 | test_typeerr.tags = ["method"] # type: ignore[attr-defined] 18 | 19 | def test_failed(self): 20 | print("Hello stdout") 21 | assert False, "I failed" 22 | 23 | test_failed.tags = ["method"] # type: ignore[attr-defined] 24 | 25 | def test_skippy(self): 26 | raise unittest.SkipTest("I wanted to skip") 27 | 28 | test_skippy.a = 1 # type: ignore[attr-defined] 29 | test_skippy.b = 1 # type: ignore[attr-defined] 30 | 31 | def test_gen_method(self): 32 | def check(x): 33 | assert x == 1 34 | 35 | check.b = 2 # type: ignore[attr-defined] 36 | yield check, 1 37 | yield check, 2 38 | 39 | test_gen_method.a = 1 # type: ignore[attr-defined] 40 | test_gen_method.b = 1 # type: ignore[attr-defined] 41 | 42 | def test_params_method(self, a): 43 | self.assertEqual(a, 1) 44 | 45 | test_params_method.paramList = (1, 2) # type: ignore[attr-defined] 46 | test_params_method.a = 1 # type: ignore[attr-defined] 47 | 48 | 49 | def test_func(): 50 | assert 1 == 1 51 | 52 | 53 | test_func.a = 1 # type: ignore[attr-defined] 54 | test_func.b = 0 # type: ignore[attr-defined] 55 | test_func.tags = ["func", "pass"] # type: ignore[attr-defined] 56 | 57 | 58 | def test_gen(): 59 | def check(a, b): 60 | assert a == b 61 | 62 | check.tags = ["func"] # type: ignore[attr-defined] 63 | for i in range(0, 5): 64 | yield check, (i, i) 65 | 66 | 67 | test_gen.testGenerator = True # type: ignore[attr-defined] 68 | test_gen.tags = ["func"] # type: ignore[attr-defined] 69 | 70 | 71 | def test_gen_nose_style(): 72 | def check(a, b): 73 | assert a == b 74 | 75 | for i in range(0, 5): 76 | yield check, i, i 77 | 78 | 79 | did_setup = False 80 | 81 | 82 | def setup(): 83 | global did_setup 84 | did_setup = True 85 | 86 | 87 | def test_fixt(): 88 | assert did_setup 89 | 90 | 91 | test_fixt.setup = setup # type: ignore[attr-defined] 92 | 93 | 94 | def test_params_func(a): 95 | assert a == 1 96 | 97 | 98 | test_params_func.paramList = (1, 2) # type: ignore[attr-defined] 99 | test_params_func.tags = ["func"] # type: ignore[attr-defined] 100 | 101 | 102 | def test_params_func_multi_arg(a, b): 103 | assert a == b 104 | 105 | 106 | test_params_func_multi_arg.paramList = ((1, 1), (1, 2), (2, 2)) # type: ignore[attr-defined] # noqa: E501 107 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_functions_loader.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import mock 3 | 4 | from nose2 import events, loader, session 5 | from nose2.plugins.loader import functions 6 | from nose2.tests._common import TestCase 7 | 8 | 9 | class TestFunctionLoader(TestCase): 10 | def setUp(self): 11 | self.session = session.Session() 12 | self.loader = loader.PluggableTestLoader(self.session) 13 | self.plugin = functions.Functions(session=self.session) 14 | 15 | def test_can_load_test_functions_from_module(self): 16 | class Mod: 17 | pass 18 | 19 | def test(): 20 | pass 21 | 22 | m = Mod() 23 | m.test = test 24 | event = events.LoadFromModuleEvent(self.loader, m) 25 | self.session.hooks.loadTestsFromModule(event) 26 | self.assertEqual(len(event.extraTests), 1) 27 | assert isinstance(event.extraTests[0], unittest.FunctionTestCase) 28 | 29 | def test_ignores_generator_functions(self): 30 | class Mod: 31 | pass 32 | 33 | def test(): 34 | yield 35 | 36 | m = Mod() 37 | m.test = test 38 | event = events.LoadFromModuleEvent(self.loader, m) 39 | self.session.hooks.loadTestsFromModule(event) 40 | self.assertEqual(len(event.extraTests), 0) 41 | 42 | def test_ignores_functions_that_take_args(self): 43 | class Mod: 44 | pass 45 | 46 | def test(a): 47 | pass 48 | 49 | m = Mod() 50 | m.test = test 51 | event = events.LoadFromModuleEvent(self.loader, m) 52 | self.session.hooks.loadTestsFromModule(event) 53 | self.assertEqual(len(event.extraTests), 0) 54 | 55 | def test_can_load_test_functions_from_name(self): 56 | event = events.LoadFromNameEvent(self.loader, __name__ + ".func", None) 57 | suite = self.session.hooks.loadTestsFromName(event) 58 | self.assertNotEqual(suite, None) 59 | 60 | def test_ignores_test_methods_from_name(self): 61 | # Should ignore test methods even when specified directly 62 | event = events.LoadFromNameEvent( 63 | self.loader, __name__ + ".Case.test_method", None 64 | ) 65 | suite = self.session.hooks.loadTestsFromName(event) 66 | self.assertEqual(suite, None) 67 | 68 | def test_ignores_decorated_test_methods_from_name(self): 69 | # Should ignore test methods even when they are of FunctionType 70 | event = events.LoadFromNameEvent( 71 | self.loader, __name__ + ".Case.test_patched", None 72 | ) 73 | suite = self.session.hooks.loadTestsFromName(event) 74 | self.assertEqual(suite, None) 75 | 76 | 77 | def func(): 78 | pass 79 | 80 | 81 | def dummy(): 82 | pass 83 | 84 | 85 | class Case(unittest.TestCase): 86 | __test__ = False # do not run this 87 | 88 | def test_method(self): 89 | pass 90 | 91 | @mock.patch(__name__ + ".dummy") 92 | def test_patched(self, mock): 93 | pass 94 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_generators_plugin.py: -------------------------------------------------------------------------------- 1 | from nose2 import events, loader, session, util 2 | from nose2.plugins.loader import generators, testcases 3 | from nose2.tests._common import TestCase 4 | 5 | 6 | class TestGeneratorUnpack(TestCase): 7 | tags = ["unit"] 8 | 9 | def setUp(self): 10 | self.session = session.Session() 11 | self.loader = loader.PluggableTestLoader(self.session) 12 | self.expect = [ 13 | (0, ("call", (0, 1))), 14 | (1, ("call", (1, 2))), 15 | (2, ("call", (2, 3))), 16 | ] 17 | self.plugin = generators.Generators(session=self.session) 18 | # need testcase loader to make the initial response to load from module 19 | self.tcl = testcases.TestCaseLoader(session=self.session) 20 | 21 | def test_unpack_handles_nose_style_generators(self): 22 | def gen(): 23 | for i in range(0, 3): 24 | yield "call", i, i + 1 25 | 26 | out = list(self.plugin.unpack(gen())) 27 | self.assertEqual(out, self.expect) 28 | 29 | def test_unpack_handles_unittest2_style_generators(self): 30 | def gen(): 31 | for i in range(0, 3): 32 | yield "call", (i, i + 1) 33 | 34 | out = list(self.plugin.unpack(gen())) 35 | self.assertEqual(out, self.expect) 36 | 37 | def test_ignores_ordinary_functions(self): 38 | class Mod: 39 | pass 40 | 41 | def test(): 42 | pass 43 | 44 | m = Mod() 45 | m.test = test 46 | event = events.LoadFromModuleEvent(self.loader, m) 47 | self.session.hooks.loadTestsFromModule(event) 48 | self.assertEqual(len(event.extraTests), 0) 49 | 50 | def test_can_load_tests_from_generator_functions(self): 51 | class Mod: 52 | __name__ = "themod" 53 | 54 | def check(x): 55 | assert x == 1 56 | 57 | def test(): 58 | yield check, 1 59 | yield check, 2 60 | 61 | m = Mod() 62 | m.test = test 63 | test.__module__ = m.__name__ 64 | event = events.LoadFromModuleEvent(self.loader, m) 65 | self.session.hooks.loadTestsFromModule(event) 66 | self.assertEqual(len(event.extraTests), 2) 67 | # check that test names are sensible 68 | self.assertEqual(util.test_name(event.extraTests[0]), "themod.test:1") 69 | self.assertEqual(util.test_name(event.extraTests[1]), "themod.test:2") 70 | 71 | def test_can_load_tests_from_generator_methods(self): 72 | class Mod: 73 | pass 74 | 75 | def check(x): 76 | return x == 1 77 | 78 | class Test(TestCase): 79 | def test(self): 80 | yield check, 1 81 | yield check, 2 82 | 83 | m = Mod() 84 | m.Test = Test 85 | event = events.LoadFromModuleEvent(self.loader, m) 86 | self.session.hooks.loadTestsFromModule(event) 87 | self.assertEqual(len(event.extraTests), 1) 88 | self.assertEqual(len(event.extraTests[0]._tests), 2) 89 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_testcase_loader.py: -------------------------------------------------------------------------------- 1 | from nose2 import events, loader, session 2 | from nose2.plugins.loader.testcases import TestCaseLoader 3 | from nose2.tests._common import TestCase 4 | 5 | 6 | class TestTestCaseLoader(TestCase): 7 | def setUp(self): 8 | self.session = session.Session() 9 | self.loader = loader.PluggableTestLoader(session=self.session) 10 | self.plugin = TestCaseLoader(session=self.session) 11 | 12 | class Mod: 13 | pass 14 | 15 | self.module = Mod() 16 | 17 | class A(TestCase): 18 | def test(self): 19 | pass 20 | 21 | class B(TestCase): 22 | def runTest(self): 23 | pass 24 | 25 | class C(TestCase): 26 | def foo(self): 27 | pass 28 | 29 | class Test: 30 | def test(self): 31 | pass 32 | 33 | self.module.A = A 34 | self.module.B = B 35 | self.module.C = C 36 | self.module.Test = Test 37 | 38 | def test_can_find_testcases_in_module(self): 39 | event = events.LoadFromModuleEvent(self.loader, self.module) 40 | result = self.session.hooks.loadTestsFromModule(event) 41 | self.assertEqual(result, None) 42 | self.assertEqual(len(event.extraTests), 3) 43 | self.assertEqual(len(event.extraTests[0]._tests), 1) # A 44 | self.assertEqual(len(event.extraTests[1]._tests), 1) # B 45 | self.assertEqual(len(event.extraTests[2]._tests), 0) # C 46 | 47 | def test_get_testcase_names_can_override_name_selection(self): 48 | class FooIsOnlyTest(events.Plugin): 49 | def getTestCaseNames(self, event): 50 | event.handled = True 51 | return ["foo"] if "foo" in dir(event.testCase) else [] 52 | 53 | foo = FooIsOnlyTest(session=self.session) 54 | foo.register() 55 | event = events.LoadFromModuleEvent(self.loader, self.module) 56 | result = self.session.hooks.loadTestsFromModule(event) 57 | self.assertEqual(result, None) 58 | self.assertEqual(len(event.extraTests), 3) 59 | self.assertEqual(len(event.extraTests[0]._tests), 0) # A 60 | self.assertEqual(len(event.extraTests[1]._tests), 1) # B (runTest) 61 | self.assertEqual(len(event.extraTests[2]._tests), 1) # C 62 | 63 | def test_plugins_can_exclude_test_names(self): 64 | class Excluder(events.Plugin): 65 | def getTestCaseNames(self, event): 66 | event.excludedNames.append("test") 67 | 68 | excl = Excluder(session=self.session) 69 | excl.register() 70 | event = events.LoadFromModuleEvent(self.loader, self.module) 71 | result = self.session.hooks.loadTestsFromModule(event) 72 | self.assertEqual(result, None) 73 | self.assertEqual(len(event.extraTests), 3) 74 | self.assertEqual(len(event.extraTests[0]._tests), 0) # A 75 | self.assertEqual(len(event.extraTests[1]._tests), 1) # B (runTest) 76 | self.assertEqual(len(event.extraTests[2]._tests), 0) # C 77 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_mp_plugin.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import sys 3 | 4 | from nose2 import session 5 | from nose2.plugins import mp 6 | from nose2.tests._common import Conn, TestCase 7 | 8 | 9 | class TestMPPlugin(TestCase): 10 | def setUp(self): 11 | self.session = session.Session() 12 | self.plugin = mp.MultiProcess(session=self.session) 13 | 14 | def test_gentests(self): 15 | conn = Conn([1, 2, 3]) 16 | res = [] 17 | for x in mp.gentests(conn): 18 | res.append(x) 19 | self.assertEqual(res, [1, 2, 3]) 20 | 21 | def test_recording_plugin_interface(self): 22 | rpi = mp.RecordingPluginInterface() 23 | # this one should record 24 | rpi.setTestOutcome(None) 25 | # none of these should record 26 | rpi.getTestCaseNames(None) 27 | rpi.startSubprocess(None) 28 | rpi.stopSubprocess(None) 29 | rpi.registerInSubprocess(None) 30 | rpi.loadTestsFromModule(None) 31 | rpi.loadTestsFromTestCase(None) 32 | rpi.moduleLoadedSuite(None) 33 | rpi.getTestMethodNames(None) 34 | self.assertEqual(rpi.flush(), [("setTestOutcome", None)]) 35 | 36 | def test_address(self): 37 | platform = sys.platform 38 | try: 39 | sys.platform = "linux" 40 | host = "1.2.3.4" 41 | port = 245 42 | self.plugin.setAddress(host) 43 | self.assertEqual((self.plugin.bind_host, self.plugin.bind_port), (host, 0)) 44 | self.plugin.setAddress("%s:%i" % (host, port)) 45 | self.assertEqual( 46 | (self.plugin.bind_host, self.plugin.bind_port), (host, port) 47 | ) 48 | self.plugin.setAddress(None) 49 | self.assertEqual((self.plugin.bind_host, self.plugin.bind_port), (None, 0)) 50 | sys.platform = "win32" 51 | self.plugin.setAddress(host) 52 | self.assertEqual((self.plugin.bind_host, self.plugin.bind_port), (host, 0)) 53 | self.plugin.setAddress("%s:%i" % (host, port)) 54 | self.assertEqual( 55 | (self.plugin.bind_host, self.plugin.bind_port), (host, port) 56 | ) 57 | self.plugin.setAddress(None) 58 | self.assertEqual( 59 | (self.plugin.bind_host, self.plugin.bind_port), ("127.116.157.163", 0) 60 | ) 61 | finally: 62 | sys.platform = platform 63 | 64 | def test_session_import(self): 65 | config = configparser.ConfigParser() 66 | config.add_section(mp.MultiProcess.configSection) 67 | export_session = { 68 | "config": config, 69 | "verbosity": None, 70 | "startDir": "", 71 | "topLevelDir": "", 72 | "pluginClasses": [mp.MultiProcess], 73 | } 74 | import logging 75 | 76 | session = mp.import_session(logging.root, export_session) 77 | self.assertIn("registerInSubprocess", session.hooks.methods) 78 | self.assertIn("startSubprocess", session.hooks.methods) 79 | self.assertIn("stopSubprocess", session.hooks.methods) 80 | pass 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2023, Jason Pellerin and contributors 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the 14 | distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | --- 29 | 30 | Portions derived from unittest2. The unittest2 license and copyright is 31 | reproduced below. 32 | 33 | Copyright (c) 2010, Michael Foord 34 | All rights reserved. 35 | E-mail : fuzzyman AT voidspace DOT org DOT uk 36 | 37 | Redistribution and use in source and binary forms, with or without 38 | modification, are permitted provided that the following conditions are 39 | met: 40 | 41 | 42 | * Redistributions of source code must retain the above copyright 43 | notice, this list of conditions and the following disclaimer. 44 | 45 | * Redistributions in binary form must reproduce the above 46 | copyright notice, this list of conditions and the following 47 | disclaimer in the documentation and/or other materials provided 48 | with the distribution. 49 | 50 | * Neither the name of Michael Foord nor the name of Voidspace 51 | may be used to endorse or promote products derived from this 52 | software without specific prior written permission. 53 | 54 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 55 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 56 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 57 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 58 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 59 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 60 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 61 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 62 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 63 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 64 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 65 | -------------------------------------------------------------------------------- /nose2/collector.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | from nose2 import events, loader, runner, session 6 | from nose2.main import PluggableTestProgram 7 | 8 | __unittest = True 9 | 10 | 11 | def collector(): 12 | """ 13 | This is the entry point used by setuptools, as in:: 14 | 15 | python setup.py test 16 | 17 | """ 18 | 19 | class Test(unittest.TestCase): 20 | def run(self, result_): 21 | ok = self._collector(result_) 22 | sys.exit(not ok) 23 | 24 | def _get_objects(self): 25 | ssn = session.Session() 26 | ldr = loader.PluggableTestLoader(ssn) 27 | rnr = runner.PluggableTestRunner(ssn) 28 | return ssn, ldr, rnr 29 | 30 | def _collector(self, result_): 31 | ssn, ldr, rnr = self._get_objects() 32 | 33 | ssn.testLoader = ldr 34 | ssn.loadConfigFiles( 35 | "unittest.cfg", 36 | "nose2.cfg", 37 | "setup.cfg", 38 | os.path.expanduser("~/.unittest.cfg"), 39 | os.path.expanduser("~/.nose2.cfg"), 40 | ) 41 | ssn.setStartDir() 42 | ssn.prepareSysPath() 43 | ssn.loadPlugins(PluggableTestProgram.defaultPlugins) 44 | 45 | # TODO: refactor argument parsing to make it possible to feed CLI 46 | # args to plugins via this path (currently done in 47 | # PluggableTestProgram) 48 | # in order to do this, it seems like features in 49 | # PluggableTestProgram need to be factored out into some source 50 | # from which both it and this dummy test case can invoke them 51 | # 52 | # this is the disabled feature: 53 | # ssn.hooks.handleArgs(events.CommandLineArgsEvent(...)) 54 | # 55 | # this means that there may be plugins which don't work under 56 | # setuptools invocation because they expect to get handleArgs 57 | # triggered (e.g. older versions of the coverage plugin) 58 | 59 | # FIXME: this is all a great-big DRY violation when compared with 60 | # PluggableTestProgram 61 | 62 | # create the testsuite, and make sure the createTests event gets 63 | # triggered, as some plugins expect it 64 | # just doing `ldr.loadTestsFromNames` works, but leaves some 65 | # plugins in the lurch 66 | event = events.CreateTestsEvent(ldr, [], None) 67 | result = ssn.hooks.createTests(event) 68 | if event.handled: 69 | test = event 70 | else: 71 | test = ldr.loadTestsFromNames([], None) 72 | 73 | # fire the "createdTestSuite" event for plugins to handle 74 | # as above, we can get away without this, but some plugins will 75 | # expect it 76 | event = events.CreatedTestSuiteEvent(test) 77 | result = ssn.hooks.createdTestSuite(event) 78 | if event.handled: 79 | test = result 80 | 81 | rslt = rnr.run(test) 82 | return rslt.wasSuccessful() 83 | 84 | return Test("_collector") 85 | -------------------------------------------------------------------------------- /nose2/plugins/loader/loadtests.py: -------------------------------------------------------------------------------- 1 | """ 2 | Loader that implements the ``load_tests`` protocol. 3 | 4 | This plugin implements the ``load_tests`` protocol as detailed in the 5 | documentation for :mod:`unittest2`. 6 | 7 | See the `load_tests protocol`_ documentation for more information. 8 | 9 | .. warning :: 10 | 11 | Test suites using the ``load_tests`` protocol do not work correctly 12 | with the :mod:`multiprocess` plugin as of nose2 04. This will be 13 | fixed in a future release. 14 | 15 | .. _load_tests protocol: 16 | http://docs.python.org/library/unittest.html#load-tests-protocol 17 | """ 18 | 19 | import logging 20 | from fnmatch import fnmatch 21 | 22 | from nose2 import events, util 23 | 24 | log = logging.getLogger(__name__) 25 | 26 | 27 | class LoadTestsLoader(events.Plugin): 28 | """Loader plugin that implements load_tests.""" 29 | 30 | alwaysOn = True 31 | configSection = "load_tests" 32 | _loading = False 33 | 34 | def registerInSubprocess(self, event): 35 | event.pluginClasses.append(self.__class__) 36 | 37 | def moduleLoadedSuite(self, event): 38 | """Run ``load_tests`` in a module. 39 | 40 | May add to or filter tests loaded in module. 41 | 42 | """ 43 | module = event.module 44 | load_tests = getattr(module, "load_tests", None) 45 | if not load_tests: 46 | return 47 | try: 48 | event.suite = load_tests( 49 | event.loader, event.suite, self.session.testFilePattern 50 | ) 51 | except Exception as exc: 52 | log.exception("Failed to load tests from %s via load_tests", module) 53 | suite = event.loader.suiteClass() 54 | suite.addTest(event.loader.failedLoadTests(module.__name__, exc)) 55 | event.handled = True 56 | return suite 57 | 58 | def handleDir(self, event): 59 | """Run ``load_tests`` in packages. 60 | 61 | If a package itself matches the test file pattern, run 62 | ``load_tests`` in its :file:`__init__.py`, and stop default test 63 | discovery for that package. 64 | 65 | """ 66 | if self._loading: 67 | return 68 | 69 | if self._match(event.name, event.pattern) and util.ispackage(event.path): 70 | name, _package_path = util.name_from_path(event.path) 71 | module = util.module_from_name(name) 72 | 73 | load_tests = getattr(module, "load_tests", None) 74 | if not load_tests: 75 | return 76 | self._loading = True 77 | try: 78 | suite = event.loader.suiteClass() 79 | try: 80 | suite = load_tests(event.loader, suite, event.pattern) 81 | except Exception as exc: 82 | log.exception("Failed to load tests from %s via load_tests", module) 83 | suite.addTest(event.loader.failedLoadTests(module.__name__, exc)) 84 | 85 | event.handled = True 86 | return suite 87 | finally: 88 | self._loading = False 89 | 90 | def _match(self, filename, pattern): 91 | return fnmatch(filename, pattern) 92 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_doctest_plugin.py: -------------------------------------------------------------------------------- 1 | """Test doctests plugin.""" 2 | 3 | import doctest 4 | from textwrap import dedent 5 | 6 | from nose2 import events, loader, session 7 | from nose2.plugins import doctests 8 | from nose2.tests._common import TestCase 9 | 10 | 11 | class UnitTestDocTestLoader(TestCase): 12 | """Test class DocTestLoader.""" 13 | 14 | tags = ["unit"] 15 | 16 | _RUN_IN_TEMP = True 17 | 18 | def setUp(self): 19 | self.session = session.Session() 20 | self.loader = loader.PluggableTestLoader(self.session) 21 | self.plugin = doctests.DocTestLoader(session=self.session) 22 | super().setUp() 23 | 24 | def test___init__(self): 25 | """Test the __init__ method.""" 26 | self.assertEqual(self.plugin.extensions, [".txt", ".rst"]) 27 | 28 | def test_handle_file(self): 29 | """Test method handleFile.""" 30 | # Create doctest files of supported types 31 | doc_test = """\ 32 | >>> 2 == 2 33 | True 34 | """ 35 | txt_event = self._handle_file("docs.txt", doc_test) 36 | rst_event = self._handle_file("docs.rst", doc_test) 37 | # Exercise loading of doctests from Python code 38 | py_event = self._handle_file( 39 | "docs.py", 40 | """\ 41 | \"\"\" 42 | >>> 2 == 2 43 | True 44 | \"\"\" 45 | """, 46 | ) 47 | for event, ext in [(txt_event, "txt"), (rst_event, "rst")]: 48 | (test,) = event.extraTests 49 | self.assertTrue(isinstance(test, doctest.DocFileCase)) 50 | self.assertEqual(repr(test), "docs.%s" % ext) 51 | 52 | (testsuite,) = py_event.extraTests 53 | (test,) = list(testsuite) 54 | self.assertEqual(repr(test), "docs ()") 55 | 56 | def test_handle_file_python_without_doctests(self): 57 | """Test calling handleFile for a Python module without doctests.""" 58 | event = self._handle_file( 59 | "mod.py", 60 | """\ 61 | def func(): 62 | pass 63 | """, 64 | ) 65 | self.assertEqual(event.extraTests, [doctest.DocTestSuite()]) 66 | 67 | def test_handle_file_python_setup_py(self): 68 | # Test calling handleFile on a top-level setup.py file. 69 | # The file should be ignored by the plugin as it cannot safely be 70 | # imported. 71 | 72 | setup_py = dedent( 73 | """\ 74 | ''' 75 | >>> never executed 76 | ''' 77 | from setuptools import setup 78 | setup(name='foo') 79 | """ 80 | ) 81 | event = self._handle_file("setup.py", setup_py) 82 | self.assertEqual(event.extraTests, []) 83 | 84 | def _handle_file(self, fpath, content): 85 | """Have plugin handle a file with certain content. 86 | 87 | The file is created, then a plugin is instantiated and its handleFile 88 | method is called for the file. 89 | """ 90 | fh = open(fpath, "w") 91 | try: 92 | fh.write(content) 93 | finally: 94 | fh.close() 95 | 96 | event = events.HandleFileEvent(self.loader, fh.name, fpath, None, None) 97 | self.plugin.handleFile(event) 98 | return event 99 | -------------------------------------------------------------------------------- /nose2/tests/functional/test_eggdiscovery_loader.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | # This unused import is not very elegant, but it allows eggdiscovery to be found in 4 | # Travis (or when run with PYTHONPATH=.) 5 | import nose2.plugins.loader.eggdiscovery # noqa: F401 6 | from nose2.tests._common import FunctionalTestCase, support_file 7 | 8 | try: 9 | import pkg_resources # noqa: F401 10 | except ImportError: 11 | pass 12 | else: 13 | 14 | class EggDiscoveryFunctionalTest(FunctionalTestCase): 15 | def setUp(self): 16 | for m in [m for m in sys.modules if m.startswith("pkgegg")]: 17 | del sys.modules[m] 18 | self.egg_path = support_file( 19 | "scenario/tests_in_zipped_eggs/pkgegg-0.0.0-py2.7.egg" 20 | ) 21 | sys.path.append(self.egg_path) 22 | 23 | def tearDown(self): 24 | if self.egg_path in sys.path: 25 | sys.path.remove(self.egg_path) 26 | for m in [m for m in sys.modules if m.startswith("pkgegg")]: 27 | del sys.modules[m] 28 | 29 | def test_non_egg_discoverer_does_not_fail_when_looking_in_egg(self): 30 | proc = self.runIn("scenario/tests_in_zipped_eggs", "-v", "pkgegg") 31 | self.assertTestRunOutputMatches(proc, stderr="Ran 0 tests in") 32 | 33 | def test_can_discover_test_modules_in_zipped_eggs(self): 34 | proc = self.runIn( 35 | "scenario/tests_in_zipped_eggs", 36 | "-v", 37 | "--plugin=nose2.plugins.loader.eggdiscovery", 38 | "pkgegg", 39 | ) 40 | self.assertTestRunOutputMatches( 41 | proc, stderr=r"FAILED \(failures=5, errors=1, skipped=1\)" 42 | ) 43 | 44 | def test_eggdiscovery_failure_does_not_exist(self): 45 | proc = self.runIn( 46 | "scenario", 47 | "-v", 48 | "--plugin=nose2.plugins.loader.eggdiscovery", 49 | "--exclude-plugin=nose2.plugins.loader.discovery", 50 | "-s", 51 | "tests_in_zipped_eggs_BAD", 52 | ) 53 | self.assertTestRunOutputMatches( 54 | proc, stderr="tests_in_zipped_eggs_BAD does not exist" 55 | ) 56 | 57 | class UnzippedEggDiscoveryFunctionalTest(FunctionalTestCase): 58 | def setUp(self): 59 | for m in [m for m in sys.modules if m.startswith("pkgegg")]: 60 | del sys.modules[m] 61 | self.egg_path = support_file( 62 | "scenario/tests_in_unzipped_eggs/pkgunegg-0.0.0-py2.7.egg" 63 | ) 64 | sys.path.append(self.egg_path) 65 | 66 | def tearDown(self): 67 | if self.egg_path in sys.path: 68 | sys.path.remove(self.egg_path) 69 | for m in [m for m in sys.modules if m.startswith("pkgunegg")]: 70 | del sys.modules[m] 71 | 72 | def test_eggdiscovery_ignores_unzipped_eggs(self): 73 | proc = self.runIn( 74 | "scenario/tests_in_unzipped_eggs", 75 | "-v", 76 | "--plugin=nose2.plugins.loader.eggdiscovery", 77 | "pkgunegg", 78 | ) 79 | self.assertTestRunOutputMatches( 80 | proc, stderr=r"FAILED \(failures=5, errors=1, skipped=1\)" 81 | ) 82 | -------------------------------------------------------------------------------- /nose2/tests/unit/test_buffer_plugin.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import io 4 | import sys 5 | 6 | from nose2 import events, result, session, util 7 | from nose2.plugins import buffer 8 | from nose2.tests._common import TestCase 9 | 10 | 11 | class TestBufferPlugin(TestCase): 12 | tags = ["unit"] 13 | 14 | def setUp(self): 15 | self.session = session.Session() 16 | self.result = result.PluggableTestResult(self.session) 17 | self.plugin = buffer.OutputBufferPlugin(session=self.session) 18 | self.plugin.register() 19 | 20 | class Test(TestCase): 21 | printed_nonascii_str = util.safe_decode("test 日本").encode("utf-8") 22 | printed_unicode = "hello" 23 | 24 | def test_out(self): 25 | print("hello") 26 | raise {}["oops"] 27 | 28 | def test_err(self): 29 | print("goodbye", file=sys.stderr) 30 | 31 | def test_mixed_unicode_and_nonascii_str(self): 32 | print(self.printed_nonascii_str) 33 | print(self.printed_unicode) 34 | print(self.printed_nonascii_str, file=sys.stderr) 35 | print(self.printed_unicode, file=sys.stderr) 36 | raise {}["oops"] 37 | 38 | self.case = Test 39 | 40 | class Watcher(events.Plugin): 41 | def __init__(self) -> None: 42 | self.events: list[events.TestOutcomeEvent] = [] 43 | 44 | def testOutcome(self, event): 45 | self.events.append(event) 46 | 47 | self.watcher = Watcher(session=self.session) 48 | self.watcher.register() 49 | 50 | def test_captures_stdout(self): 51 | out = sys.stdout 52 | buf = io.StringIO() 53 | sys.stdout = buf 54 | try: 55 | test = self.case("test_out") 56 | test(self.result) 57 | assert "hello" not in buf.getvalue() 58 | assert "hello" in self.watcher.events[0].metadata["stdout"] 59 | finally: 60 | sys.stdout = out 61 | 62 | def test_captures_stderr_when_configured(self): 63 | self.plugin.captureStderr = True 64 | err = sys.stderr 65 | buf = io.StringIO() 66 | sys.stderr = buf 67 | try: 68 | test = self.case("test_err") 69 | test(self.result) 70 | assert "goodbye" not in buf.getvalue() 71 | assert "goodbye" in self.watcher.events[0].metadata["stderr"] 72 | finally: 73 | sys.stderr = err 74 | 75 | def test_does_not_crash_with_mixed_unicode_and_nonascii_str(self): 76 | self.plugin.captureStderr = True 77 | test = self.case("test_mixed_unicode_and_nonascii_str") 78 | test(self.result) 79 | evt = events.OutcomeDetailEvent(self.watcher.events[0]) 80 | self.session.hooks.outcomeDetail(evt) 81 | extraDetail = "".join(evt.extraDetail) 82 | for string in [ 83 | repr(self.case.printed_nonascii_str), 84 | self.case.printed_unicode, 85 | ]: 86 | assert string in extraDetail, "Output not found in error message" 87 | 88 | def test_decorates_outcome_detail(self): 89 | test = self.case("test_out") 90 | test(self.result) 91 | evt = events.OutcomeDetailEvent(self.watcher.events[0]) 92 | self.session.hooks.outcomeDetail(evt) 93 | assert "hello" in "".join(evt.extraDetail) 94 | -------------------------------------------------------------------------------- /nose2/tests/functional/test_discovery_loader.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from nose2 import events, loader, session 4 | from nose2.plugins.loader.discovery import DiscoveryLoader 5 | from nose2.tests._common import FunctionalTestCase, TestCase, support_file 6 | 7 | 8 | class Watcher(events.Plugin): 9 | def __init__(self) -> None: 10 | self.called: list[events.LoadFromModuleEvent] = [] 11 | 12 | def loadTestsFromModule(self, event): 13 | self.called.append(event) 14 | 15 | 16 | class DiscoveryFunctionalTest(FunctionalTestCase): 17 | def setUp(self): 18 | self.session = session.Session() 19 | self.plug = DiscoveryLoader(session=self.session) 20 | self.loader = loader.PluggableTestLoader(self.session) 21 | self.watcher = Watcher(session=self.session) 22 | self.watcher.register() 23 | 24 | def test_can_discover_test_modules_in_packages(self): 25 | self.session.startDir = support_file("scenario/tests_in_package") 26 | event = events.LoadFromNamesEvent(self.loader, [], None) 27 | result = self.session.hooks.loadTestsFromNames(event) 28 | assert isinstance(result, self.loader.suiteClass) 29 | self.assertEqual(len(result._tests), 1) 30 | self.assertEqual(len(self.watcher.called), 1) 31 | self.assertEqual( 32 | self.watcher.called[0].module.__name__, "pkg1.test.test_things" 33 | ) 34 | 35 | def test_discovery_supports_code_in_lib_dir(self): 36 | self.session.startDir = support_file("scenario/package_in_lib") 37 | event = events.LoadFromNamesEvent(self.loader, [], None) 38 | result = self.session.hooks.loadTestsFromNames(event) 39 | assert isinstance(result, self.loader.suiteClass) 40 | self.assertEqual(len(result._tests), 1) 41 | self.assertEqual(len(self.watcher.called), 1) 42 | self.assertEqual(self.watcher.called[0].module.__name__, "tests") 43 | 44 | def test_match_path_event_can_prevent_discovery(self): 45 | class NoTestsForYou(events.Plugin): 46 | def matchPath(self, event): 47 | event.handled = True 48 | return False 49 | 50 | mp = NoTestsForYou(session=self.session) 51 | mp.register() 52 | self.session.startDir = support_file("scenario/tests_in_package") 53 | event = events.LoadFromNamesEvent(self.loader, [], None) 54 | result = self.session.hooks.loadTestsFromNames(event) 55 | assert isinstance(result, self.loader.suiteClass) 56 | self.assertEqual(len(result._tests), 0) 57 | self.assertEqual(len(self.watcher.called), 0) 58 | 59 | def test_handle_file_event_can_add_tests(self): 60 | class TextTest(TestCase): 61 | def test(self): 62 | pass 63 | 64 | class TestsInText(events.Plugin): 65 | def handleFile(self, event): 66 | if event.path.endswith(".txt"): 67 | event.extraTests.append(TextTest("test")) 68 | 69 | mp = TestsInText(session=self.session) 70 | mp.register() 71 | self.session.startDir = support_file("scenario/tests_in_package") 72 | event = events.LoadFromNamesEvent(self.loader, [], None) 73 | result = self.session.hooks.loadTestsFromNames(event) 74 | assert isinstance(result, self.loader.suiteClass) 75 | self.assertEqual(len(result._tests), 2) 76 | self.assertEqual(len(self.watcher.called), 1) 77 | -------------------------------------------------------------------------------- /nose2/plugins/loader/eggdiscovery.py: -------------------------------------------------------------------------------- 1 | """ 2 | Egg-based discovery test loader. 3 | 4 | This plugin implements nose2's automatic test module discovery inside Egg Files. 5 | It looks for test modules in packages whose names start 6 | with ``test``, then fires the :func:`loadTestsFromModule` hook for each 7 | one to allow other plugins to load the actual tests. 8 | 9 | It also fires :func:`handleFile` for every file that it sees, and 10 | :func:`matchPath` for every Python module, to allow other plugins to 11 | load tests from other kinds of files and to influence which modules 12 | are examined for tests. 13 | 14 | """ 15 | 16 | from __future__ import annotations 17 | 18 | import logging 19 | import os 20 | 21 | from nose2 import events 22 | from nose2.plugins.loader import discovery 23 | 24 | __unittest = True 25 | log = logging.getLogger(__name__) 26 | 27 | try: 28 | import pkg_resources 29 | 30 | _has_pkg_resources = True 31 | except ImportError: 32 | _has_pkg_resources = False 33 | 34 | 35 | class EggDiscoveryLoader(events.Plugin, discovery.Discoverer): 36 | """Loader plugin that can discover tests inside Egg Files""" 37 | 38 | alwaysOn = True 39 | configSection = "discovery" 40 | 41 | def registerInSubprocess(self, event): 42 | event.pluginClasses.append(self.__class__) 43 | 44 | def loadTestsFromName(self, event): 45 | """Load tests from module named by event.name""" 46 | return discovery.Discoverer.loadTestsFromName(self, event) 47 | 48 | def loadTestsFromNames(self, event): 49 | """Discover tests if no test names specified""" 50 | return discovery.Discoverer.loadTestsFromNames(self, event) 51 | 52 | def _checkIfPathIsOK(self, start_dir): 53 | if not os.path.exists(os.path.abspath(start_dir)): 54 | raise OSError("%s does not exist" % os.path.abspath(start_dir)) 55 | 56 | def _find_tests_in_egg_dir(self, event, rel_path, dist): 57 | log.debug( 58 | "find in egg dir %s %s (%s)", dist.location, rel_path, dist.project_name 59 | ) 60 | full_path = os.path.join(dist.location, rel_path) 61 | dir_handler = discovery.DirectoryHandler(self.session) 62 | yield from dir_handler.handle_dir(event, full_path, dist.location) 63 | if dir_handler.event_handled: 64 | return 65 | for path in dist.resource_listdir(rel_path): 66 | # on setuptools==38.2.5 , resource_listdir() can yield "" 67 | # if that happens, skip processing it to avoid infinite recursion 68 | if path == "": 69 | continue 70 | 71 | entry_path = os.path.join(rel_path, path) 72 | if dist.resource_isdir(entry_path): 73 | yield from self._find_tests_in_egg_dir(event, entry_path, dist) 74 | else: 75 | modname = os.path.splitext(entry_path)[0].replace(os.sep, ".") 76 | yield from self._find_tests_in_file( 77 | event, 78 | path, 79 | os.path.join(dist.location, entry_path), 80 | dist.location, 81 | modname, 82 | ) 83 | 84 | def _find_tests_in_dir(self, event, full_path, top_level): 85 | if os.path.exists(full_path): 86 | return 87 | elif _has_pkg_resources and full_path.find(".egg") != -1: 88 | egg_path = full_path.split(".egg")[0] + ".egg" 89 | for dist in pkg_resources.find_distributions(egg_path): 90 | for modname in dist._get_metadata("top_level.txt"): 91 | yield from self._find_tests_in_egg_dir(event, modname, dist) 92 | --------------------------------------------------------------------------------