From 45a0794fb1f03f568c82bbdb5cd2322ba0807c72 Mon Sep 17 00:00:00 2001 From: Anas Nashif Date: Fri, 9 Nov 2012 12:21:03 -0800 Subject: [PATCH] Imported Upstream version 1.1.2 --- AUTHORS | 23 + CHANGELOG | 647 ++++++ NEWS | 5 + PKG-INFO | 38 + README.txt | 482 +++++ bin/nosetests | 6 + distribute_setup.py | 485 +++++ doc/.static/nose.css | 74 + doc/.templates/index.html | 52 + doc/.templates/indexsidebar.html | 56 + doc/.templates/layout.html | 16 + doc/.templates/page.html | 7 + doc/Makefile | 89 + doc/api.rst | 20 + doc/api/commands.rst | 2 + doc/api/config.rst | 5 + doc/api/core.rst | 5 + doc/api/importer.rst | 5 + doc/api/inspector.rst | 5 + doc/api/loader.rst | 2 + doc/api/plugin_manager.rst | 2 + doc/api/proxy.rst | 2 + doc/api/result.rst | 2 + doc/api/selector.rst | 2 + doc/api/suite.rst | 2 + doc/api/test_cases.rst | 8 + doc/api/twistedtools.rst | 2 + doc/api/util.rst | 5 + doc/conf.py | 237 ++ doc/contributing.rst | 58 + doc/developing.rst | 30 + .../test_addplugins/support/test$py.class | Bin 0 -> 2628 bytes doc/doc_tests/test_addplugins/support/test.py | 2 + doc/doc_tests/test_addplugins/support/test.pyc | Bin 0 -> 266 bytes doc/doc_tests/test_addplugins/test_addplugins.rst | 80 + doc/doc_tests/test_allmodules/support/mod$py.class | Bin 0 -> 3239 bytes doc/doc_tests/test_allmodules/support/mod.py | 5 + doc/doc_tests/test_allmodules/support/mod.pyc | Bin 0 -> 435 bytes .../test_allmodules/support/test$py.class | Bin 0 -> 2628 bytes doc/doc_tests/test_allmodules/support/test.py | 2 + doc/doc_tests/test_allmodules/support/test.pyc | Bin 0 -> 266 bytes doc/doc_tests/test_allmodules/test_allmodules.rst | 67 + doc/doc_tests/test_coverage_html/coverage_html.rst | 57 + .../test_coverage_html/coverage_html.rst.py3.patch | 16 + .../coverage_html_fixtures$py.class | Bin 0 -> 5318 bytes .../test_coverage_html/coverage_html_fixtures.py | 26 + .../test_coverage_html/coverage_html_fixtures.pyc | Bin 0 -> 1236 bytes doc/doc_tests/test_coverage_html/support/blah.py | 6 + doc/doc_tests/test_coverage_html/support/blah.pyc | Bin 0 -> 405 bytes .../support/tests/test_covered.py | 4 + .../support/tests/test_covered.pyc | Bin 0 -> 340 bytes .../test_doctest_fixtures/doctest_fixtures.rst | 122 ++ .../doctest_fixtures_fixtures$py.class | Bin 0 -> 4513 bytes .../doctest_fixtures_fixtures.py | 17 + .../doctest_fixtures_fixtures.pyc | Bin 0 -> 846 bytes doc/doc_tests/test_init_plugin/example.cfg | 3 + doc/doc_tests/test_init_plugin/init_plugin.rst | 164 ++ .../test_init_plugin/init_plugin.rst.py3.patch | 10 + .../support/unwanted_package/__init__$py.class | Bin 0 -> 2181 bytes .../support/unwanted_package/__init__.py | 1 + .../support/unwanted_package/__init__.pyc | Bin 0 -> 189 bytes .../support/unwanted_package/test_spam$py.class | Bin 0 -> 3051 bytes .../support/unwanted_package/test_spam.py | 3 + .../support/unwanted_package/test_spam.pyc | Bin 0 -> 333 bytes .../support/wanted_package/__init__$py.class | Bin 0 -> 2175 bytes .../support/wanted_package/__init__.py | 1 + .../support/wanted_package/__init__.pyc | Bin 0 -> 187 bytes .../support/wanted_package/test_eggs$py.class | Bin 0 -> 3045 bytes .../support/wanted_package/test_eggs.py | 3 + .../support/wanted_package/test_eggs.pyc | Bin 0 -> 331 bytes doc/doc_tests/test_issue089/unwanted_package.rst | 70 + .../test_issue097/plugintest_environment.rst | 160 ++ doc/doc_tests/test_issue107/plugin_exceptions.rst | 149 ++ .../test_issue107/support/test_spam$py.class | Bin 0 -> 3210 bytes doc/doc_tests/test_issue107/support/test_spam.py | 5 + doc/doc_tests/test_issue107/support/test_spam.pyc | Bin 0 -> 419 bytes doc/doc_tests/test_issue119/empty_plugin.rst | 57 + doc/doc_tests/test_issue119/test_zeronine$py.class | Bin 0 -> 5731 bytes doc/doc_tests/test_issue119/test_zeronine.py | 26 + doc/doc_tests/test_issue119/test_zeronine.pyc | Bin 0 -> 1362 bytes doc/doc_tests/test_issue142/errorclass_failure.rst | 124 ++ .../support/errorclass_failing_test$py.class | Bin 0 -> 3473 bytes .../support/errorclass_failing_test.py | 7 + .../support/errorclass_failing_test.pyc | Bin 0 -> 489 bytes .../support/errorclass_failure_plugin$py.class | Bin 0 -> 4198 bytes .../support/errorclass_failure_plugin.py | 16 + .../support/errorclass_failure_plugin.pyc | Bin 0 -> 972 bytes .../support/errorclass_tests$py.class | Bin 0 -> 4631 bytes .../test_issue142/support/errorclass_tests.py | 20 + .../test_issue142/support/errorclass_tests.pyc | Bin 0 -> 1102 bytes doc/doc_tests/test_issue145/imported_tests.rst | 117 + .../support/package1/__init__$py.class | Bin 0 -> 2758 bytes .../test_issue145/support/package1/__init__.py | 2 + .../test_issue145/support/package1/__init__.pyc | Bin 0 -> 302 bytes .../support/package1/test_module$py.class | Bin 0 -> 4162 bytes .../test_issue145/support/package1/test_module.py | 12 + .../test_issue145/support/package1/test_module.pyc | Bin 0 -> 807 bytes .../support/package2c/__init__$py.class | Bin 0 -> 2762 bytes .../test_issue145/support/package2c/__init__.py | 2 + .../test_issue145/support/package2c/__init__.pyc | Bin 0 -> 304 bytes .../support/package2c/test_module$py.class | Bin 0 -> 2512 bytes .../test_issue145/support/package2c/test_module.py | 1 + .../support/package2c/test_module.pyc | Bin 0 -> 298 bytes .../support/package2f/__init__$py.class | Bin 0 -> 2762 bytes .../test_issue145/support/package2f/__init__.py | 2 + .../test_issue145/support/package2f/__init__.pyc | Bin 0 -> 304 bytes .../support/package2f/test_module$py.class | Bin 0 -> 2481 bytes .../test_issue145/support/package2f/test_module.py | 1 + .../support/package2f/test_module.pyc | Bin 0 -> 269 bytes doc/doc_tests/test_multiprocess/multiprocess.rst | 269 +++ .../multiprocess_fixtures$py.class | Bin 0 -> 4364 bytes .../test_multiprocess/multiprocess_fixtures.py | 16 + .../test_multiprocess/multiprocess_fixtures.pyc | Bin 0 -> 769 bytes .../test_multiprocess/support/test_can_split.py | 30 + .../test_multiprocess/support/test_can_split.pyc | Bin 0 -> 2152 bytes .../test_multiprocess/support/test_not_shared.py | 30 + .../test_multiprocess/support/test_not_shared.pyc | Bin 0 -> 2150 bytes .../test_multiprocess/support/test_shared.py | 49 + .../test_multiprocess/support/test_shared.pyc | Bin 0 -> 3083 bytes .../restricted_plugin_options.rst | 89 + .../restricted_plugin_options.rst.py3.patch | 9 + .../test_restricted_plugin_options/support/bad.cfg | 2 + .../support/start.cfg | 2 + .../support/test$py.class | Bin 0 -> 2643 bytes .../test_restricted_plugin_options/support/test.py | 2 + .../support/test.pyc | Bin 0 -> 281 bytes .../test_selector_plugin/selector_plugin.rst | 119 + .../test_selector_plugin/support/mymodule$py.class | Bin 0 -> 3007 bytes .../test_selector_plugin/support/mymodule.py | 2 + .../test_selector_plugin/support/mymodule.pyc | Bin 0 -> 327 bytes .../support/mypackage/__init__$py.class | Bin 0 -> 2167 bytes .../support/mypackage/__init__.py | 1 + .../support/mypackage/__init__.pyc | Bin 0 -> 189 bytes .../support/mypackage/math/__init__$py.class | Bin 0 -> 2310 bytes .../support/mypackage/math/__init__.py | 1 + .../support/mypackage/math/__init__.pyc | Bin 0 -> 237 bytes .../support/mypackage/math/basic$py.class | Bin 0 -> 3109 bytes .../support/mypackage/math/basic.py | 5 + .../support/mypackage/math/basic.pyc | Bin 0 -> 433 bytes .../support/mypackage/strings$py.class | Bin 0 -> 3089 bytes .../support/mypackage/strings.py | 2 + .../support/mypackage/strings.pyc | Bin 0 -> 324 bytes .../support/tests/math/basic$py.class | Bin 0 -> 5138 bytes .../support/tests/math/basic.py | 17 + .../support/tests/math/basic.pyc | Bin 0 -> 1103 bytes .../support/tests/mymodule/my_function$py.class | Bin 0 -> 4329 bytes .../support/tests/mymodule/my_function.py | 7 + .../support/tests/mymodule/my_function.pyc | Bin 0 -> 640 bytes .../support/tests/strings/cat$py.class | Bin 0 -> 4612 bytes .../support/tests/strings/cat.py | 12 + .../support/tests/strings/cat.pyc | Bin 0 -> 830 bytes .../support/tests/testlib$py.class | Bin 0 -> 3151 bytes .../test_selector_plugin/support/tests/testlib.py | 6 + .../test_selector_plugin/support/tests/testlib.pyc | Bin 0 -> 436 bytes .../test_xunit_plugin/support/nosetests.xml | 25 + .../test_xunit_plugin/support/test_skip$py.class | Bin 0 -> 4186 bytes .../test_xunit_plugin/support/test_skip.py | 13 + .../test_xunit_plugin/support/test_skip.pyc | Bin 0 -> 766 bytes doc/doc_tests/test_xunit_plugin/test_skips.rst | 40 + doc/docstring.py | 25 + doc/finding_tests.rst | 32 + doc/further_reading.rst | 34 + doc/index.html | 8 + doc/index.rst | 74 + doc/man.rst | 24 + doc/manbuilder.py | 24 + doc/manbuilder.pyc | Bin 0 -> 1308 bytes doc/manpage.py | 1119 ++++++++++ doc/manpage.pyc | Bin 0 -> 51841 bytes doc/more_info.rst | 48 + doc/news.rst | 4 + doc/plugins.rst | 70 + doc/plugins/allmodules.rst | 4 + doc/plugins/attrib.rst | 4 + doc/plugins/builtin.rst | 30 + doc/plugins/capture.rst | 5 + doc/plugins/collect.rst | 4 + doc/plugins/cover.rst | 14 + doc/plugins/debug.rst | 4 + doc/plugins/deprecated.rst | 4 + doc/plugins/doctests.rst | 4 + doc/plugins/documenting.rst | 62 + doc/plugins/errorclasses.rst | 7 + doc/plugins/failuredetail.rst | 4 + doc/plugins/interface.rst | 122 ++ doc/plugins/isolate.rst | 4 + doc/plugins/logcapture.rst | 4 + doc/plugins/multiprocess.rst | 5 + doc/plugins/other.rst | 6 + doc/plugins/prof.rst | 4 + doc/plugins/skip.rst | 5 + doc/plugins/testid.rst | 4 + doc/plugins/testing.rst | 7 + doc/plugins/writing.rst | 1 + doc/plugins/xunit.rst | 4 + doc/rtd-requirements.txt | 3 + doc/setuptools_integration.rst | 38 + doc/testing.rst | 55 + doc/testing_tools.rst | 11 + doc/usage.rst | 42 + doc/writing_tests.rst | 172 ++ examples/attrib_plugin.py | 82 + examples/html_plugin/htmlplug.py | 92 + examples/html_plugin/setup.py | 24 + examples/plugin/plug.py | 4 + examples/plugin/setup.py | 27 + .../test_addplugins/support/test$py.class | Bin 0 -> 2628 bytes .../doc_tests/test_addplugins/support/test.py | 2 + .../doc_tests/test_addplugins/support/test.pyc | Bin 0 -> 266 bytes .../doc_tests/test_addplugins/test_addplugins.rst | 80 + .../doc_tests/test_allmodules/support/mod$py.class | Bin 0 -> 3239 bytes .../doc_tests/test_allmodules/support/mod.py | 5 + .../doc_tests/test_allmodules/support/mod.pyc | Bin 0 -> 435 bytes .../test_allmodules/support/test$py.class | Bin 0 -> 2628 bytes .../doc_tests/test_allmodules/support/test.py | 2 + .../doc_tests/test_allmodules/support/test.pyc | Bin 0 -> 266 bytes .../doc_tests/test_allmodules/test_allmodules.rst | 67 + .../doc_tests/test_coverage_html/coverage_html.rst | 57 + .../test_coverage_html/coverage_html.rst.py3.patch | 16 + .../coverage_html_fixtures$py.class | Bin 0 -> 5318 bytes .../test_coverage_html/coverage_html_fixtures.py | 26 + .../test_coverage_html/coverage_html_fixtures.pyc | Bin 0 -> 1236 bytes .../doc_tests/test_coverage_html/support/blah.py | 6 + .../doc_tests/test_coverage_html/support/blah.pyc | Bin 0 -> 405 bytes .../support/tests/test_covered.py | 4 + .../support/tests/test_covered.pyc | Bin 0 -> 340 bytes .../test_doctest_fixtures/doctest_fixtures.rst | 122 ++ .../doctest_fixtures_fixtures$py.class | Bin 0 -> 4513 bytes .../doctest_fixtures_fixtures.py | 17 + .../doctest_fixtures_fixtures.pyc | Bin 0 -> 846 bytes .../doc_tests/test_init_plugin/example.cfg | 3 + .../doc_tests/test_init_plugin/init_plugin.rst | 164 ++ .../test_init_plugin/init_plugin.rst.py3.patch | 10 + .../support/unwanted_package/__init__$py.class | Bin 0 -> 2181 bytes .../support/unwanted_package/__init__.py | 1 + .../support/unwanted_package/__init__.pyc | Bin 0 -> 189 bytes .../support/unwanted_package/test_spam$py.class | Bin 0 -> 3051 bytes .../support/unwanted_package/test_spam.py | 3 + .../support/unwanted_package/test_spam.pyc | Bin 0 -> 333 bytes .../support/wanted_package/__init__$py.class | Bin 0 -> 2175 bytes .../support/wanted_package/__init__.py | 1 + .../support/wanted_package/__init__.pyc | Bin 0 -> 187 bytes .../support/wanted_package/test_eggs$py.class | Bin 0 -> 3045 bytes .../support/wanted_package/test_eggs.py | 3 + .../support/wanted_package/test_eggs.pyc | Bin 0 -> 331 bytes .../doc_tests/test_issue089/unwanted_package.rst | 70 + .../test_issue097/plugintest_environment.rst | 160 ++ .../doc_tests/test_issue107/plugin_exceptions.rst | 149 ++ .../test_issue107/support/test_spam$py.class | Bin 0 -> 3210 bytes .../doc_tests/test_issue107/support/test_spam.py | 5 + .../doc_tests/test_issue107/support/test_spam.pyc | Bin 0 -> 419 bytes .../doc_tests/test_issue119/empty_plugin.rst | 57 + .../doc_tests/test_issue119/test_zeronine$py.class | Bin 0 -> 5731 bytes .../doc_tests/test_issue119/test_zeronine.py | 26 + .../doc_tests/test_issue119/test_zeronine.pyc | Bin 0 -> 1362 bytes .../doc_tests/test_issue142/errorclass_failure.rst | 124 ++ .../support/errorclass_failing_test$py.class | Bin 0 -> 3473 bytes .../support/errorclass_failing_test.py | 7 + .../support/errorclass_failing_test.pyc | Bin 0 -> 489 bytes .../support/errorclass_failure_plugin$py.class | Bin 0 -> 4198 bytes .../support/errorclass_failure_plugin.py | 16 + .../support/errorclass_failure_plugin.pyc | Bin 0 -> 972 bytes .../support/errorclass_tests$py.class | Bin 0 -> 4631 bytes .../test_issue142/support/errorclass_tests.py | 20 + .../test_issue142/support/errorclass_tests.pyc | Bin 0 -> 1102 bytes .../doc_tests/test_issue145/imported_tests.rst | 117 + .../support/package1/__init__$py.class | Bin 0 -> 2758 bytes .../test_issue145/support/package1/__init__.py | 2 + .../test_issue145/support/package1/__init__.pyc | Bin 0 -> 302 bytes .../support/package1/test_module$py.class | Bin 0 -> 4162 bytes .../test_issue145/support/package1/test_module.py | 12 + .../test_issue145/support/package1/test_module.pyc | Bin 0 -> 807 bytes .../support/package2c/__init__$py.class | Bin 0 -> 2762 bytes .../test_issue145/support/package2c/__init__.py | 2 + .../test_issue145/support/package2c/__init__.pyc | Bin 0 -> 304 bytes .../support/package2c/test_module$py.class | Bin 0 -> 2512 bytes .../test_issue145/support/package2c/test_module.py | 1 + .../support/package2c/test_module.pyc | Bin 0 -> 298 bytes .../support/package2f/__init__$py.class | Bin 0 -> 2762 bytes .../test_issue145/support/package2f/__init__.py | 2 + .../test_issue145/support/package2f/__init__.pyc | Bin 0 -> 304 bytes .../support/package2f/test_module$py.class | Bin 0 -> 2481 bytes .../test_issue145/support/package2f/test_module.py | 1 + .../support/package2f/test_module.pyc | Bin 0 -> 269 bytes .../doc_tests/test_multiprocess/multiprocess.rst | 269 +++ .../multiprocess_fixtures$py.class | Bin 0 -> 4364 bytes .../test_multiprocess/multiprocess_fixtures.py | 16 + .../test_multiprocess/multiprocess_fixtures.pyc | Bin 0 -> 769 bytes .../test_multiprocess/support/test_can_split.py | 30 + .../test_multiprocess/support/test_can_split.pyc | Bin 0 -> 2152 bytes .../test_multiprocess/support/test_not_shared.py | 30 + .../test_multiprocess/support/test_not_shared.pyc | Bin 0 -> 2150 bytes .../test_multiprocess/support/test_shared.py | 49 + .../test_multiprocess/support/test_shared.pyc | Bin 0 -> 3083 bytes .../restricted_plugin_options.rst | 89 + .../restricted_plugin_options.rst.py3.patch | 9 + .../test_restricted_plugin_options/support/bad.cfg | 2 + .../support/start.cfg | 2 + .../support/test$py.class | Bin 0 -> 2643 bytes .../test_restricted_plugin_options/support/test.py | 2 + .../support/test.pyc | Bin 0 -> 281 bytes .../test_selector_plugin/selector_plugin.rst | 119 + .../test_selector_plugin/support/mymodule$py.class | Bin 0 -> 3007 bytes .../test_selector_plugin/support/mymodule.py | 2 + .../test_selector_plugin/support/mymodule.pyc | Bin 0 -> 327 bytes .../support/mypackage/__init__$py.class | Bin 0 -> 2167 bytes .../support/mypackage/__init__.py | 1 + .../support/mypackage/__init__.pyc | Bin 0 -> 189 bytes .../support/mypackage/math/__init__$py.class | Bin 0 -> 2310 bytes .../support/mypackage/math/__init__.py | 1 + .../support/mypackage/math/__init__.pyc | Bin 0 -> 237 bytes .../support/mypackage/math/basic$py.class | Bin 0 -> 3109 bytes .../support/mypackage/math/basic.py | 5 + .../support/mypackage/math/basic.pyc | Bin 0 -> 433 bytes .../support/mypackage/strings$py.class | Bin 0 -> 3089 bytes .../support/mypackage/strings.py | 2 + .../support/mypackage/strings.pyc | Bin 0 -> 324 bytes .../support/tests/math/basic$py.class | Bin 0 -> 5138 bytes .../support/tests/math/basic.py | 17 + .../support/tests/math/basic.pyc | Bin 0 -> 1103 bytes .../support/tests/mymodule/my_function$py.class | Bin 0 -> 4329 bytes .../support/tests/mymodule/my_function.py | 7 + .../support/tests/mymodule/my_function.pyc | Bin 0 -> 640 bytes .../support/tests/strings/cat$py.class | Bin 0 -> 4612 bytes .../support/tests/strings/cat.py | 12 + .../support/tests/strings/cat.pyc | Bin 0 -> 830 bytes .../support/tests/testlib$py.class | Bin 0 -> 3151 bytes .../test_selector_plugin/support/tests/testlib.py | 6 + .../test_selector_plugin/support/tests/testlib.pyc | Bin 0 -> 436 bytes .../test_xunit_plugin/support/nosetests.xml | 25 + .../test_xunit_plugin/support/test_skip$py.class | Bin 0 -> 4186 bytes .../test_xunit_plugin/support/test_skip.py | 13 + .../test_xunit_plugin/support/test_skip.pyc | Bin 0 -> 766 bytes .../doc_tests/test_xunit_plugin/test_skips.rst | 40 + functional_tests/support/att/test_attr$py.class | Bin 0 -> 10453 bytes functional_tests/support/att/test_attr.py | 96 + functional_tests/support/att/test_attr.pyc | Bin 0 -> 3713 bytes .../support/ctx/mod_import_skip$py.class | Bin 0 -> 3835 bytes functional_tests/support/ctx/mod_import_skip.py | 9 + functional_tests/support/ctx/mod_import_skip.pyc | Bin 0 -> 562 bytes .../support/ctx/mod_setup_fails$py.class | Bin 0 -> 3769 bytes functional_tests/support/ctx/mod_setup_fails.py | 12 + functional_tests/support/ctx/mod_setup_fails.pyc | Bin 0 -> 613 bytes .../support/ctx/mod_setup_skip$py.class | Bin 0 -> 3763 bytes functional_tests/support/ctx/mod_setup_skip.py | 14 + functional_tests/support/ctx/mod_setup_skip.pyc | Bin 0 -> 646 bytes functional_tests/support/dir1/mod$py.class | Bin 0 -> 2114 bytes functional_tests/support/dir1/mod.py | 1 + functional_tests/support/dir1/mod.pyc | Bin 0 -> 148 bytes .../support/dir1/pak/__init__$py.class | Bin 0 -> 2123 bytes functional_tests/support/dir1/pak/__init__.py | 1 + functional_tests/support/dir1/pak/__init__.pyc | Bin 0 -> 157 bytes functional_tests/support/dir1/pak/mod$py.class | Bin 0 -> 2126 bytes functional_tests/support/dir1/pak/mod.py | 1 + functional_tests/support/dir1/pak/mod.pyc | Bin 0 -> 152 bytes .../support/dir1/pak/sub/__init__$py.class | Bin 0 -> 2135 bytes functional_tests/support/dir1/pak/sub/__init__.py | 1 + functional_tests/support/dir1/pak/sub/__init__.pyc | Bin 0 -> 161 bytes functional_tests/support/dir2/mod$py.class | Bin 0 -> 2114 bytes functional_tests/support/dir2/mod.py | 1 + functional_tests/support/dir2/mod.pyc | Bin 0 -> 148 bytes .../support/dir2/pak/__init__$py.class | Bin 0 -> 2123 bytes functional_tests/support/dir2/pak/__init__.py | 1 + functional_tests/support/dir2/pak/__init__.pyc | Bin 0 -> 157 bytes functional_tests/support/dir2/pak/mod$py.class | Bin 0 -> 2126 bytes functional_tests/support/dir2/pak/mod.py | 1 + functional_tests/support/dir2/pak/mod.pyc | Bin 0 -> 152 bytes .../support/dir2/pak/sub/__init__$py.class | Bin 0 -> 2135 bytes functional_tests/support/dir2/pak/sub/__init__.py | 1 + functional_tests/support/dir2/pak/sub/__init__.pyc | Bin 0 -> 161 bytes functional_tests/support/dtt/docs/doc.txt | 6 + functional_tests/support/dtt/docs/errdoc.txt | 7 + functional_tests/support/dtt/docs/nodoc.txt | 1 + functional_tests/support/dtt/some_mod$py.class | Bin 0 -> 3170 bytes functional_tests/support/dtt/some_mod.py | 17 + functional_tests/support/dtt/some_mod.pyc | Bin 0 -> 407 bytes functional_tests/support/empty/.hidden | 0 .../support/ep/Some_plugin.egg-info/PKG-INFO | 10 + .../support/ep/Some_plugin.egg-info/SOURCES.txt | 6 + .../ep/Some_plugin.egg-info/dependency_links.txt | 1 + .../ep/Some_plugin.egg-info/entry_points.txt | 3 + .../support/ep/Some_plugin.egg-info/top_level.txt | 1 + functional_tests/support/ep/setup.py | 10 + functional_tests/support/ep/someplugin.py | 4 + functional_tests/support/fdp/test_fdp$py.class | Bin 0 -> 4150 bytes functional_tests/support/fdp/test_fdp.py | 10 + functional_tests/support/fdp/test_fdp.pyc | Bin 0 -> 602 bytes .../support/fdp/test_fdp_no_capt$py.class | Bin 0 -> 4064 bytes functional_tests/support/fdp/test_fdp_no_capt.py | 9 + functional_tests/support/fdp/test_fdp_no_capt.pyc | Bin 0 -> 593 bytes functional_tests/support/gen/test$py.class | Bin 0 -> 4711 bytes functional_tests/support/gen/test.py | 12 + functional_tests/support/gen/test.pyc | Bin 0 -> 650 bytes functional_tests/support/id_fails/test_a$py.class | Bin 0 -> 2364 bytes functional_tests/support/id_fails/test_a.py | 1 + functional_tests/support/id_fails/test_a.pyc | Bin 0 -> 197 bytes functional_tests/support/id_fails/test_b$py.class | Bin 0 -> 3177 bytes functional_tests/support/id_fails/test_b.py | 5 + functional_tests/support/id_fails/test_b.pyc | Bin 0 -> 397 bytes functional_tests/support/idp/exm$py.class | Bin 0 -> 3233 bytes functional_tests/support/idp/exm.py | 21 + functional_tests/support/idp/exm.pyc | Bin 0 -> 476 bytes functional_tests/support/idp/tests$py.class | Bin 0 -> 7650 bytes functional_tests/support/idp/tests.py | 38 + functional_tests/support/idp/tests.pyc | Bin 0 -> 1786 bytes .../support/ipt/test1/ipthelp$py.class | Bin 0 -> 2803 bytes functional_tests/support/ipt/test1/ipthelp.py | 4 + functional_tests/support/ipt/test1/ipthelp.pyc | Bin 0 -> 301 bytes functional_tests/support/ipt/test1/tests$py.class | Bin 0 -> 3348 bytes functional_tests/support/ipt/test1/tests.py | 7 + functional_tests/support/ipt/test1/tests.pyc | Bin 0 -> 383 bytes .../support/ipt/test2/ipthelp$py.class | Bin 0 -> 3038 bytes functional_tests/support/ipt/test2/ipthelp.py | 5 + functional_tests/support/ipt/test2/ipthelp.pyc | Bin 0 -> 313 bytes functional_tests/support/ipt/test2/tests$py.class | Bin 0 -> 3397 bytes functional_tests/support/ipt/test2/tests.py | 8 + functional_tests/support/ipt/test2/tests.pyc | Bin 0 -> 391 bytes functional_tests/support/issue038/test$py.class | Bin 0 -> 3400 bytes functional_tests/support/issue038/test.py | 9 + functional_tests/support/issue038/test.pyc | Bin 0 -> 461 bytes functional_tests/support/issue072/test$py.class | Bin 0 -> 3422 bytes functional_tests/support/issue072/test.py | 4 + functional_tests/support/issue072/test.pyc | Bin 0 -> 338 bytes .../support/issue082/_mypackage/__init__.py | 1 + .../support/issue082/_mypackage/_eggs.py | 8 + .../support/issue082/_mypackage/bacon.py | 8 + .../issue082/mypublicpackage/__init__$py.class | Bin 0 -> 2163 bytes .../support/issue082/mypublicpackage/__init__.py | 1 + .../support/issue082/mypublicpackage/__init__.pyc | Bin 0 -> 173 bytes .../support/issue082/mypublicpackage/_foo$py.class | Bin 0 -> 2334 bytes .../support/issue082/mypublicpackage/_foo.py | 8 + .../support/issue082/mypublicpackage/_foo.pyc | Bin 0 -> 210 bytes .../support/issue082/mypublicpackage/bar$py.class | Bin 0 -> 2331 bytes .../support/issue082/mypublicpackage/bar.py | 8 + .../support/issue082/mypublicpackage/bar.pyc | Bin 0 -> 209 bytes functional_tests/support/issue130/test$py.class | Bin 0 -> 3468 bytes functional_tests/support/issue130/test.py | 5 + functional_tests/support/issue130/test.pyc | Bin 0 -> 413 bytes .../support/issue143/not-a-package/__init__.py | 1 + .../support/issue143/not-a-package/test.py | 2 + .../support/issue191/UNKNOWN.egg-info/PKG-INFO | 10 + .../support/issue191/UNKNOWN.egg-info/SOURCES.txt | 6 + .../issue191/UNKNOWN.egg-info/dependency_links.txt | 1 + .../issue191/UNKNOWN.egg-info/top_level.txt | 1 + functional_tests/support/issue191/setup.cfg | 2 + functional_tests/support/issue191/setup.py | 3 + functional_tests/support/issue191/test$py.class | Bin 0 -> 2611 bytes functional_tests/support/issue191/test.py | 2 + functional_tests/support/issue191/test.pyc | Bin 0 -> 249 bytes .../support/issue269/test_bad_class$py.class | Bin 0 -> 3809 bytes .../support/issue269/test_bad_class.py | 5 + .../support/issue269/test_bad_class.pyc | Bin 0 -> 571 bytes .../support/issue279/test_mod_setup_fails$py.class | Bin 0 -> 3299 bytes .../support/issue279/test_mod_setup_fails.py | 5 + .../support/issue279/test_mod_setup_fails.pyc | Bin 0 -> 456 bytes functional_tests/support/issue408/nosetests.xml | 0 functional_tests/support/issue408/test$py.class | Bin 0 -> 5133 bytes functional_tests/support/issue408/test.py | 16 + functional_tests/support/issue408/test.pyc | Bin 0 -> 1134 bytes functional_tests/support/ltfn/state$py.class | Bin 0 -> 2438 bytes functional_tests/support/ltfn/state.py | 1 + functional_tests/support/ltfn/state.pyc | Bin 0 -> 172 bytes functional_tests/support/ltfn/test_mod$py.class | Bin 0 -> 3713 bytes functional_tests/support/ltfn/test_mod.py | 10 + functional_tests/support/ltfn/test_mod.pyc | Bin 0 -> 649 bytes .../support/ltfn/test_pak1/__init__$py.class | Bin 0 -> 4037 bytes .../support/ltfn/test_pak1/__init__.py | 13 + .../support/ltfn/test_pak1/__init__.pyc | Bin 0 -> 826 bytes .../support/ltfn/test_pak1/test_mod$py.class | Bin 0 -> 3807 bytes .../support/ltfn/test_pak1/test_mod.py | 12 + .../support/ltfn/test_pak1/test_mod.pyc | Bin 0 -> 705 bytes .../support/ltfn/test_pak2/__init__$py.class | Bin 0 -> 4037 bytes .../support/ltfn/test_pak2/__init__.py | 13 + .../support/ltfn/test_pak2/__init__.pyc | Bin 0 -> 826 bytes functional_tests/support/ltftc/tests$py.class | Bin 0 -> 4065 bytes functional_tests/support/ltftc/tests.py | 9 + functional_tests/support/ltftc/tests.pyc | Bin 0 -> 627 bytes .../namespace_pkg/namespace_pkg/__init__$py.class | Bin 0 -> 2730 bytes .../namespace_pkg/namespace_pkg/__init__.py | 2 + .../namespace_pkg/namespace_pkg/__init__.pyc | Bin 0 -> 269 bytes .../namespace_pkg/namespace_pkg/example$py.class | Bin 0 -> 2322 bytes .../support/namespace_pkg/namespace_pkg/example.py | 1 + .../namespace_pkg/namespace_pkg/example.pyc | Bin 0 -> 214 bytes .../namespace_pkg/namespace_pkg/test_pkg$py.class | Bin 0 -> 3625 bytes .../namespace_pkg/namespace_pkg/test_pkg.py | 6 + .../namespace_pkg/namespace_pkg/test_pkg.pyc | Bin 0 -> 535 bytes .../site-packages/namespace_pkg/__init__.py | 2 + .../site-packages/namespace_pkg/example2$py.class | Bin 0 -> 2346 bytes .../site-packages/namespace_pkg/example2.py | 1 + .../site-packages/namespace_pkg/example2.pyc | Bin 0 -> 236 bytes .../site-packages/namespace_pkg/test_pkg2$py.class | Bin 0 -> 3644 bytes .../site-packages/namespace_pkg/test_pkg2.py | 6 + .../site-packages/namespace_pkg/test_pkg2.pyc | Bin 0 -> 551 bytes functional_tests/support/package1/example$py.class | Bin 0 -> 3047 bytes functional_tests/support/package1/example.py | 8 + functional_tests/support/package1/example.pyc | Bin 0 -> 352 bytes .../package1/tests/test_example_function$py.class | Bin 0 -> 5193 bytes .../package1/tests/test_example_function.py | 15 + .../package1/tests/test_example_function.pyc | Bin 0 -> 1054 bytes functional_tests/support/package2/maths$py.class | Bin 0 -> 3533 bytes functional_tests/support/package2/maths.py | 11 + functional_tests/support/package2/maths.pyc | Bin 0 -> 641 bytes .../support/package2/test_pak/__init__$py.class | Bin 0 -> 3558 bytes .../support/package2/test_pak/__init__.py | 11 + .../support/package2/test_pak/__init__.pyc | Bin 0 -> 514 bytes .../support/package2/test_pak/test_mod$py.class | Bin 0 -> 5313 bytes .../support/package2/test_pak/test_mod.py | 20 + .../support/package2/test_pak/test_mod.pyc | Bin 0 -> 1136 bytes .../package2/test_pak/test_sub/__init__$py.class | Bin 0 -> 3489 bytes .../support/package2/test_pak/test_sub/__init__.py | 9 + .../package2/test_pak/test_sub/__init__.pyc | Bin 0 -> 543 bytes .../package2/test_pak/test_sub/test_mod$py.class | Bin 0 -> 7596 bytes .../support/package2/test_pak/test_sub/test_mod.py | 36 + .../package2/test_pak/test_sub/test_mod.pyc | Bin 0 -> 2256 bytes functional_tests/support/package3/lib/a$py.class | Bin 0 -> 2600 bytes functional_tests/support/package3/lib/a.py | 2 + functional_tests/support/package3/lib/a.pyc | Bin 0 -> 247 bytes functional_tests/support/package3/src/b$py.class | Bin 0 -> 2600 bytes functional_tests/support/package3/src/b.py | 2 + functional_tests/support/package3/src/b.pyc | Bin 0 -> 247 bytes .../support/package3/tests/test_a$py.class | Bin 0 -> 3002 bytes functional_tests/support/package3/tests/test_a.py | 4 + functional_tests/support/package3/tests/test_a.pyc | Bin 0 -> 296 bytes .../support/package3/tests/test_b$py.class | Bin 0 -> 3002 bytes functional_tests/support/package3/tests/test_b.py | 4 + functional_tests/support/package3/tests/test_b.pyc | Bin 0 -> 296 bytes functional_tests/support/pass/test$py.class | Bin 0 -> 2607 bytes functional_tests/support/pass/test.py | 2 + functional_tests/support/pass/test.pyc | Bin 0 -> 245 bytes functional_tests/support/test.cfg | 2 + .../support/test_buggy_generators$py.class | Bin 0 -> 7309 bytes functional_tests/support/test_buggy_generators.py | 29 + functional_tests/support/test_buggy_generators.pyc | Bin 0 -> 1588 bytes .../support/todo/test_with_todo$py.class | Bin 0 -> 3474 bytes functional_tests/support/todo/test_with_todo.py | 7 + functional_tests/support/todo/test_with_todo.pyc | Bin 0 -> 479 bytes functional_tests/support/todo/todoplug$py.class | Bin 0 -> 3644 bytes functional_tests/support/todo/todoplug.py | 7 + functional_tests/support/todo/todoplug.pyc | Bin 0 -> 670 bytes functional_tests/support/twist/test_twisted.py | 15 + functional_tests/support/xunit.xml | 27 + .../support/xunit/test_xunit_as_suite$py.class | Bin 0 -> 5802 bytes .../support/xunit/test_xunit_as_suite.py | 24 + .../support/xunit/test_xunit_as_suite.pyc | Bin 0 -> 1325 bytes functional_tests/test_attribute_plugin$py.class | Bin 0 -> 19670 bytes functional_tests/test_attribute_plugin.py | 181 ++ functional_tests/test_attribute_plugin.pyc | Bin 0 -> 8217 bytes functional_tests/test_buggy_generators$py.class | Bin 0 -> 7112 bytes functional_tests/test_buggy_generators.py | 36 + functional_tests/test_buggy_generators.pyc | Bin 0 -> 1849 bytes functional_tests/test_cases$py.class | Bin 0 -> 7732 bytes functional_tests/test_cases.py | 38 + functional_tests/test_cases.pyc | Bin 0 -> 2072 bytes functional_tests/test_collector$py.class | Bin 0 -> 7738 bytes functional_tests/test_collector.py | 46 + functional_tests/test_collector.pyc | Bin 0 -> 1975 bytes functional_tests/test_commands$py.class | Bin 0 -> 7688 bytes functional_tests/test_commands.py | 47 + functional_tests/test_commands.pyc | Bin 0 -> 1977 bytes functional_tests/test_config_files$py.class | Bin 0 -> 6905 bytes functional_tests/test_config_files.py | 41 + functional_tests/test_config_files.pyc | Bin 0 -> 1817 bytes functional_tests/test_doctest_plugin$py.class | Bin 0 -> 7655 bytes functional_tests/test_doctest_plugin.py | 44 + functional_tests/test_doctest_plugin.pyc | Bin 0 -> 1947 bytes functional_tests/test_entrypoints$py.class | Bin 0 -> 5165 bytes functional_tests/test_entrypoints.py | 17 + functional_tests/test_entrypoints.pyc | Bin 0 -> 931 bytes .../test_failuredetail_plugin$py.class | Bin 0 -> 7060 bytes functional_tests/test_failuredetail_plugin.py | 50 + functional_tests/test_failuredetail_plugin.pyc | Bin 0 -> 1795 bytes functional_tests/test_generator_fixtures$py.class | Bin 0 -> 8553 bytes functional_tests/test_generator_fixtures.py | 58 + functional_tests/test_generator_fixtures.pyc | Bin 0 -> 2217 bytes functional_tests/test_id_plugin$py.class | Bin 0 -> 21960 bytes functional_tests/test_id_plugin.py | 261 +++ functional_tests/test_id_plugin.pyc | Bin 0 -> 9442 bytes functional_tests/test_importer$py.class | Bin 0 -> 16447 bytes functional_tests/test_importer.py | 168 ++ functional_tests/test_importer.pyc | Bin 0 -> 6458 bytes functional_tests/test_isolate_plugin$py.class | Bin 0 -> 8005 bytes functional_tests/test_isolate_plugin.py | 57 + functional_tests/test_isolate_plugin.pyc | Bin 0 -> 2373 bytes .../test_issue120/support/some_test$py.class | Bin 0 -> 2641 bytes .../test_issue120/support/some_test.py | 3 + .../test_issue120/support/some_test.pyc | Bin 0 -> 264 bytes .../test_issue120/test_named_test_with_doctest.rst | 25 + functional_tests/test_issue_072$py.class | Bin 0 -> 7141 bytes functional_tests/test_issue_072.py | 45 + functional_tests/test_issue_072.pyc | Bin 0 -> 1889 bytes functional_tests/test_issue_082$py.class | Bin 0 -> 9681 bytes functional_tests/test_issue_082.py | 74 + functional_tests/test_issue_082.pyc | Bin 0 -> 3101 bytes functional_tests/test_issue_408$py.class | Bin 0 -> 5998 bytes functional_tests/test_issue_408.py | 25 + functional_tests/test_issue_408.pyc | Bin 0 -> 1194 bytes .../test_load_tests_from_test_case$py.class | Bin 0 -> 7962 bytes functional_tests/test_load_tests_from_test_case.py | 56 + .../test_load_tests_from_test_case.pyc | Bin 0 -> 2334 bytes functional_tests/test_loader$py.class | Bin 0 -> 32386 bytes functional_tests/test_loader.py | 451 ++++ functional_tests/test_loader.pyc | Bin 0 -> 17408 bytes .../test_multiprocessing/support/nameerror.py | 4 + .../test_multiprocessing/support/nameerror.pyc | Bin 0 -> 204 bytes .../test_multiprocessing/support/timeout.py | 6 + .../test_multiprocessing/test_nameerror$py.class | Bin 0 -> 6464 bytes .../test_multiprocessing/test_nameerror.py | 31 + .../test_multiprocessing/test_nameerror.pyc | Bin 0 -> 1511 bytes .../test_process_timeout$py.class | Bin 0 -> 7196 bytes .../test_multiprocessing/test_process_timeout.py | 37 + .../test_multiprocessing/test_process_timeout.pyc | Bin 0 -> 1937 bytes functional_tests/test_namespace_pkg$py.class | Bin 0 -> 8608 bytes functional_tests/test_namespace_pkg.py | 58 + functional_tests/test_namespace_pkg.pyc | Bin 0 -> 2552 bytes functional_tests/test_plugin_api$py.class | Bin 0 -> 7898 bytes functional_tests/test_plugin_api.py | 45 + functional_tests/test_plugin_api.pyc | Bin 0 -> 2048 bytes functional_tests/test_plugins$py.class | Bin 0 -> 10200 bytes functional_tests/test_plugins.py | 71 + functional_tests/test_plugins.pyc | Bin 0 -> 3160 bytes functional_tests/test_plugintest$py.class | Bin 0 -> 9339 bytes functional_tests/test_plugintest.py | 51 + functional_tests/test_plugintest.pyc | Bin 0 -> 2939 bytes functional_tests/test_program$py.class | Bin 0 -> 15837 bytes functional_tests/test_program.py | 189 ++ functional_tests/test_program.pyc | Bin 0 -> 6646 bytes functional_tests/test_result$py.class | Bin 0 -> 6055 bytes functional_tests/test_result.py | 32 + functional_tests/test_result.pyc | Bin 0 -> 1445 bytes functional_tests/test_selector$py.class | Bin 0 -> 5333 bytes functional_tests/test_selector.py | 17 + functional_tests/test_selector.pyc | Bin 0 -> 1026 bytes .../test_skip_pdb_interaction$py.class | Bin 0 -> 7904 bytes functional_tests/test_skip_pdb_interaction.py | 49 + functional_tests/test_skip_pdb_interaction.pyc | Bin 0 -> 2491 bytes functional_tests/test_success$py.class | Bin 0 -> 6176 bytes functional_tests/test_success.py | 43 + functional_tests/test_success.pyc | Bin 0 -> 1601 bytes functional_tests/test_suite$py.class | Bin 0 -> 7698 bytes functional_tests/test_suite.py | 47 + functional_tests/test_suite.pyc | Bin 0 -> 2210 bytes functional_tests/test_withid_failures.rst | 50 + functional_tests/test_xunit$py.class | Bin 0 -> 9468 bytes functional_tests/test_xunit.py | 61 + functional_tests/test_xunit.pyc | Bin 0 -> 2827 bytes install-rpm.sh | 3 + lgpl.txt | 504 +++++ nose/__init__.py | 15 + nose/case.py | 397 ++++ nose/commands.py | 146 ++ nose/config.py | 638 ++++++ nose/core.py | 324 +++ nose/exc.py | 9 + nose/ext/__init__.py | 3 + nose/ext/dtcompat.py | 2272 ++++++++++++++++++++ nose/failure.py | 39 + nose/importer.py | 154 ++ nose/inspector.py | 207 ++ nose/loader.py | 595 +++++ nose/plugins/__init__.py | 190 ++ nose/plugins/allmodules.py | 45 + nose/plugins/attrib.py | 286 +++ nose/plugins/base.py | 728 +++++++ nose/plugins/builtin.py | 34 + nose/plugins/capture.py | 128 ++ nose/plugins/collect.py | 94 + nose/plugins/cover.py | 308 +++ nose/plugins/debug.py | 62 + nose/plugins/deprecated.py | 45 + nose/plugins/doctests.py | 428 ++++ nose/plugins/errorclass.py | 210 ++ nose/plugins/failuredetail.py | 43 + nose/plugins/isolate.py | 103 + nose/plugins/logcapture.py | 236 ++ nose/plugins/manager.py | 446 ++++ nose/plugins/multiprocess.py | 798 +++++++ nose/plugins/plugintest.py | 416 ++++ nose/plugins/prof.py | 154 ++ nose/plugins/skip.py | 56 + nose/plugins/testid.py | 306 +++ nose/plugins/xunit.py | 253 +++ nose/proxy.py | 191 ++ nose/pyversion.py | 130 ++ nose/result.py | 200 ++ nose/selector.py | 251 +++ nose/sphinx/__init__.py | 1 + nose/sphinx/pluginopts.py | 186 ++ nose/suite.py | 607 ++++++ nose/tools.py | 194 ++ nose/twistedtools.py | 168 ++ nose/usage.txt | 110 + nose/util.py | 663 ++++++ nosetests.1 | 480 +++++ patch.py | 639 ++++++ selftest.py | 60 + setup.cfg | 11 + setup.py | 121 ++ setup3lib.py | 140 ++ unit_tests/helpers$py.class | Bin 0 -> 3230 bytes unit_tests/helpers.py | 6 + unit_tests/helpers.pyc | Bin 0 -> 346 bytes unit_tests/mock$py.class | Bin 0 -> 14001 bytes unit_tests/mock.py | 107 + unit_tests/mock.pyc | Bin 0 -> 5524 bytes unit_tests/support/bug101/tests.py | 9 + unit_tests/support/bug105/tests$py.class | Bin 0 -> 6973 bytes unit_tests/support/bug105/tests.py | 49 + unit_tests/support/bug105/tests.pyc | Bin 0 -> 1845 bytes unit_tests/support/config_defaults/a.cfg | 2 + unit_tests/support/config_defaults/b.cfg | 2 + unit_tests/support/config_defaults/invalid.cfg | 1 + .../support/config_defaults/invalid_value.cfg | 2 + unit_tests/support/doctest/err_doctests$py.class | Bin 0 -> 3427 bytes unit_tests/support/doctest/err_doctests.py | 12 + unit_tests/support/doctest/err_doctests.pyc | Bin 0 -> 449 bytes unit_tests/support/doctest/no_doctests$py.class | Bin 0 -> 3368 bytes unit_tests/support/doctest/no_doctests.py | 9 + unit_tests/support/doctest/no_doctests.pyc | Bin 0 -> 396 bytes unit_tests/support/foo/__init__$py.class | Bin 0 -> 2825 bytes unit_tests/support/foo/__init__.py | 7 + unit_tests/support/foo/__init__.pyc | Bin 0 -> 339 bytes unit_tests/support/foo/bar/__init__$py.class | Bin 0 -> 2124 bytes unit_tests/support/foo/bar/__init__.py | 1 + unit_tests/support/foo/bar/__init__.pyc | Bin 0 -> 150 bytes unit_tests/support/foo/bar/buz$py.class | Bin 0 -> 2907 bytes unit_tests/support/foo/bar/buz.py | 8 + unit_tests/support/foo/bar/buz.pyc | Bin 0 -> 344 bytes unit_tests/support/foo/doctests.txt | 7 + unit_tests/support/foo/test_foo.py | 1 + unit_tests/support/foo/tests/dir_test_file.py | 3 + unit_tests/support/issue006/tests$py.class | Bin 0 -> 6086 bytes unit_tests/support/issue006/tests.py | 19 + unit_tests/support/issue006/tests.pyc | Bin 0 -> 1120 bytes unit_tests/support/issue065/tests$py.class | Bin 0 -> 3641 bytes unit_tests/support/issue065/tests.py | 5 + unit_tests/support/issue065/tests.pyc | Bin 0 -> 481 bytes unit_tests/support/issue270/__init__.py | 2 + unit_tests/support/issue270/__init__.pyc | Bin 0 -> 321 bytes unit_tests/support/issue270/foo_test.py | 7 + unit_tests/support/issue270/foo_test.pyc | Bin 0 -> 723 bytes unit_tests/support/other/file.txt | 1 + unit_tests/support/pkgorg/lib/modernity.py | 1 + unit_tests/support/pkgorg/tests/test_mod.py | 4 + unit_tests/support/script.py | 3 + unit_tests/support/test-dir/test.py | 1 + unit_tests/support/test.py | 13 + unit_tests/test_attribute_plugin$py.class | Bin 0 -> 7867 bytes unit_tests/test_attribute_plugin.py | 53 + unit_tests/test_attribute_plugin.pyc | Bin 0 -> 2199 bytes unit_tests/test_bug105$py.class | Bin 0 -> 6259 bytes unit_tests/test_bug105.py | 32 + unit_tests/test_bug105.pyc | Bin 0 -> 1267 bytes unit_tests/test_capture_plugin$py.class | Bin 0 -> 11382 bytes unit_tests/test_capture_plugin.py | 100 + unit_tests/test_capture_plugin.pyc | Bin 0 -> 3624 bytes unit_tests/test_cases$py.class | Bin 0 -> 28661 bytes unit_tests/test_cases.py | 274 +++ unit_tests/test_cases.pyc | Bin 0 -> 12159 bytes unit_tests/test_config$py.class | Bin 0 -> 15661 bytes unit_tests/test_config.py | 141 ++ unit_tests/test_config.pyc | Bin 0 -> 6104 bytes unit_tests/test_config_defaults.rst | 146 ++ unit_tests/test_core$py.class | Bin 0 -> 11906 bytes unit_tests/test_core.py | 96 + unit_tests/test_core.pyc | Bin 0 -> 3865 bytes unit_tests/test_deprecated_plugin$py.class | Bin 0 -> 14121 bytes unit_tests/test_deprecated_plugin.py | 131 ++ unit_tests/test_deprecated_plugin.pyc | Bin 0 -> 5342 bytes unit_tests/test_doctest_error_handling$py.class | Bin 0 -> 8020 bytes unit_tests/test_doctest_error_handling.py | 40 + unit_tests/test_doctest_error_handling.pyc | Bin 0 -> 2031 bytes unit_tests/test_doctest_munging.rst | 105 + unit_tests/test_id_plugin$py.class | Bin 0 -> 5368 bytes unit_tests/test_id_plugin.py | 20 + unit_tests/test_id_plugin.pyc | Bin 0 -> 1006 bytes unit_tests/test_importer$py.class | Bin 0 -> 7923 bytes unit_tests/test_importer.py | 55 + unit_tests/test_importer.pyc | Bin 0 -> 2249 bytes unit_tests/test_inspector$py.class | Bin 0 -> 14522 bytes unit_tests/test_inspector.py | 149 ++ unit_tests/test_inspector.pyc | Bin 0 -> 5018 bytes unit_tests/test_isolation_plugin$py.class | Bin 0 -> 2869 bytes unit_tests/test_isolation_plugin.py | 2 + unit_tests/test_isolation_plugin.pyc | Bin 0 -> 296 bytes unit_tests/test_issue155.rst | 46 + unit_tests/test_issue270.rst | 22 + unit_tests/test_issue270_fixtures$py.class | Bin 0 -> 4246 bytes unit_tests/test_issue270_fixtures.py | 11 + unit_tests/test_issue270_fixtures.pyc | Bin 0 -> 685 bytes unit_tests/test_issue_006$py.class | Bin 0 -> 6406 bytes unit_tests/test_issue_006.py | 31 + unit_tests/test_issue_006.pyc | Bin 0 -> 1466 bytes unit_tests/test_issue_064$py.class | Bin 0 -> 2956 bytes unit_tests/test_issue_064.py | 2 + unit_tests/test_issue_064.pyc | Bin 0 -> 369 bytes unit_tests/test_issue_065$py.class | Bin 0 -> 5452 bytes unit_tests/test_issue_065.py | 20 + unit_tests/test_issue_065.pyc | Bin 0 -> 1064 bytes unit_tests/test_issue_100.rst | 12 + unit_tests/test_issue_100.rst.py3.patch | 8 + unit_tests/test_issue_101$py.class | Bin 0 -> 6689 bytes unit_tests/test_issue_101.py | 27 + unit_tests/test_issue_101.pyc | Bin 0 -> 1516 bytes unit_tests/test_issue_159.rst | 6 + unit_tests/test_issue_227$py.class | Bin 0 -> 4138 bytes unit_tests/test_issue_227.py | 12 + unit_tests/test_issue_227.pyc | Bin 0 -> 581 bytes unit_tests/test_issue_230$py.class | Bin 0 -> 5837 bytes unit_tests/test_issue_230.py | 21 + unit_tests/test_issue_230.pyc | Bin 0 -> 1041 bytes unit_tests/test_lazy_suite$py.class | Bin 0 -> 6186 bytes unit_tests/test_lazy_suite.py | 21 + unit_tests/test_lazy_suite.pyc | Bin 0 -> 1219 bytes unit_tests/test_loader$py.class | Bin 0 -> 46239 bytes unit_tests/test_loader.py | 501 +++++ unit_tests/test_loader.pyc | Bin 0 -> 19006 bytes unit_tests/test_logcapture_plugin$py.class | Bin 0 -> 20179 bytes unit_tests/test_logcapture_plugin.py | 205 ++ unit_tests/test_logcapture_plugin.pyc | Bin 0 -> 8208 bytes unit_tests/test_logging$py.class | Bin 0 -> 6034 bytes unit_tests/test_logging.py | 40 + unit_tests/test_logging.pyc | Bin 0 -> 1523 bytes unit_tests/test_ls_tree.rst | 50 + unit_tests/test_multiprocess$py.class | Bin 0 -> 8185 bytes unit_tests/test_multiprocess.py | 62 + unit_tests/test_multiprocess.pyc | Bin 0 -> 2430 bytes unit_tests/test_multiprocess_runner$py.class | Bin 0 -> 12967 bytes unit_tests/test_multiprocess_runner.py | 120 ++ unit_tests/test_multiprocess_runner.pyc | Bin 0 -> 4873 bytes unit_tests/test_pdb_plugin$py.class | Bin 0 -> 12008 bytes unit_tests/test_pdb_plugin.py | 117 + unit_tests/test_pdb_plugin.pyc | Bin 0 -> 4287 bytes unit_tests/test_plugin$py.class | Bin 0 -> 6626 bytes unit_tests/test_plugin.py | 33 + unit_tests/test_plugin.pyc | Bin 0 -> 1450 bytes unit_tests/test_plugin_interfaces$py.class | Bin 0 -> 7809 bytes unit_tests/test_plugin_interfaces.py | 45 + unit_tests/test_plugin_interfaces.pyc | Bin 0 -> 1813 bytes unit_tests/test_plugin_manager$py.class | Bin 0 -> 11828 bytes unit_tests/test_plugin_manager.py | 74 + unit_tests/test_plugin_manager.pyc | Bin 0 -> 4110 bytes unit_tests/test_plugins$py.class | Bin 0 -> 35733 bytes unit_tests/test_plugins.py | 415 ++++ unit_tests/test_plugins.pyc | Bin 0 -> 14821 bytes unit_tests/test_result_proxy$py.class | Bin 0 -> 20146 bytes unit_tests/test_result_proxy.py | 188 ++ unit_tests/test_result_proxy.pyc | Bin 0 -> 8064 bytes unit_tests/test_selector$py.class | Bin 0 -> 20951 bytes unit_tests/test_selector.py | 200 ++ unit_tests/test_selector.pyc | Bin 0 -> 8041 bytes unit_tests/test_selector_plugins$py.class | Bin 0 -> 6449 bytes unit_tests/test_selector_plugins.py | 30 + unit_tests/test_selector_plugins.pyc | Bin 0 -> 1505 bytes unit_tests/test_skip_plugin$py.class | Bin 0 -> 14017 bytes unit_tests/test_skip_plugin.py | 130 ++ unit_tests/test_skip_plugin.pyc | Bin 0 -> 5216 bytes unit_tests/test_suite$py.class | Bin 0 -> 29016 bytes unit_tests/test_suite.py | 301 +++ unit_tests/test_suite.pyc | Bin 0 -> 10967 bytes unit_tests/test_tools$py.class | Bin 0 -> 19415 bytes unit_tests/test_tools.py | 207 ++ unit_tests/test_tools.pyc | Bin 0 -> 7169 bytes unit_tests/test_twisted$py.class | Bin 0 -> 9594 bytes unit_tests/test_twisted.py | 93 + unit_tests/test_twisted.pyc | Bin 0 -> 2980 bytes unit_tests/test_twisted_testcase$py.class | Bin 0 -> 4255 bytes unit_tests/test_twisted_testcase.py | 11 + unit_tests/test_twisted_testcase.pyc | Bin 0 -> 641 bytes unit_tests/test_utils$py.class | Bin 0 -> 20175 bytes unit_tests/test_utils.py | 180 ++ unit_tests/test_utils.pyc | Bin 0 -> 7435 bytes unit_tests/test_xunit$py.class | Bin 0 -> 28086 bytes unit_tests/test_xunit.py | 343 +++ unit_tests/test_xunit.pyc | Bin 0 -> 13037 bytes 874 files changed, 32207 insertions(+) create mode 100644 AUTHORS create mode 100644 CHANGELOG create mode 100644 NEWS create mode 100644 PKG-INFO create mode 100644 README.txt create mode 100755 bin/nosetests create mode 100644 distribute_setup.py create mode 100644 doc/.static/nose.css create mode 100644 doc/.templates/index.html create mode 100644 doc/.templates/indexsidebar.html create mode 100644 doc/.templates/layout.html create mode 100644 doc/.templates/page.html create mode 100644 doc/Makefile create mode 100644 doc/api.rst create mode 100644 doc/api/commands.rst create mode 100644 doc/api/config.rst create mode 100644 doc/api/core.rst create mode 100644 doc/api/importer.rst create mode 100644 doc/api/inspector.rst create mode 100644 doc/api/loader.rst create mode 100644 doc/api/plugin_manager.rst create mode 100644 doc/api/proxy.rst create mode 100644 doc/api/result.rst create mode 100644 doc/api/selector.rst create mode 100644 doc/api/suite.rst create mode 100644 doc/api/test_cases.rst create mode 100644 doc/api/twistedtools.rst create mode 100644 doc/api/util.rst create mode 100644 doc/conf.py create mode 100644 doc/contributing.rst create mode 100644 doc/developing.rst create mode 100644 doc/doc_tests/test_addplugins/support/test$py.class create mode 100644 doc/doc_tests/test_addplugins/support/test.py create mode 100644 doc/doc_tests/test_addplugins/support/test.pyc create mode 100644 doc/doc_tests/test_addplugins/test_addplugins.rst create mode 100644 doc/doc_tests/test_allmodules/support/mod$py.class create mode 100644 doc/doc_tests/test_allmodules/support/mod.py create mode 100644 doc/doc_tests/test_allmodules/support/mod.pyc create mode 100644 doc/doc_tests/test_allmodules/support/test$py.class create mode 100644 doc/doc_tests/test_allmodules/support/test.py create mode 100644 doc/doc_tests/test_allmodules/support/test.pyc create mode 100644 doc/doc_tests/test_allmodules/test_allmodules.rst create mode 100644 doc/doc_tests/test_coverage_html/coverage_html.rst create mode 100644 doc/doc_tests/test_coverage_html/coverage_html.rst.py3.patch create mode 100644 doc/doc_tests/test_coverage_html/coverage_html_fixtures$py.class create mode 100644 doc/doc_tests/test_coverage_html/coverage_html_fixtures.py create mode 100644 doc/doc_tests/test_coverage_html/coverage_html_fixtures.pyc create mode 100644 doc/doc_tests/test_coverage_html/support/blah.py create mode 100644 doc/doc_tests/test_coverage_html/support/blah.pyc create mode 100644 doc/doc_tests/test_coverage_html/support/tests/test_covered.py create mode 100644 doc/doc_tests/test_coverage_html/support/tests/test_covered.pyc create mode 100644 doc/doc_tests/test_doctest_fixtures/doctest_fixtures.rst create mode 100644 doc/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures$py.class create mode 100644 doc/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.py create mode 100644 doc/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.pyc create mode 100644 doc/doc_tests/test_init_plugin/example.cfg create mode 100644 doc/doc_tests/test_init_plugin/init_plugin.rst create mode 100644 doc/doc_tests/test_init_plugin/init_plugin.rst.py3.patch create mode 100644 doc/doc_tests/test_issue089/support/unwanted_package/__init__$py.class create mode 100644 doc/doc_tests/test_issue089/support/unwanted_package/__init__.py create mode 100644 doc/doc_tests/test_issue089/support/unwanted_package/__init__.pyc create mode 100644 doc/doc_tests/test_issue089/support/unwanted_package/test_spam$py.class create mode 100644 doc/doc_tests/test_issue089/support/unwanted_package/test_spam.py create mode 100644 doc/doc_tests/test_issue089/support/unwanted_package/test_spam.pyc create mode 100644 doc/doc_tests/test_issue089/support/wanted_package/__init__$py.class create mode 100644 doc/doc_tests/test_issue089/support/wanted_package/__init__.py create mode 100644 doc/doc_tests/test_issue089/support/wanted_package/__init__.pyc create mode 100644 doc/doc_tests/test_issue089/support/wanted_package/test_eggs$py.class create mode 100644 doc/doc_tests/test_issue089/support/wanted_package/test_eggs.py create mode 100644 doc/doc_tests/test_issue089/support/wanted_package/test_eggs.pyc create mode 100644 doc/doc_tests/test_issue089/unwanted_package.rst create mode 100644 doc/doc_tests/test_issue097/plugintest_environment.rst create mode 100644 doc/doc_tests/test_issue107/plugin_exceptions.rst create mode 100644 doc/doc_tests/test_issue107/support/test_spam$py.class create mode 100644 doc/doc_tests/test_issue107/support/test_spam.py create mode 100644 doc/doc_tests/test_issue107/support/test_spam.pyc create mode 100644 doc/doc_tests/test_issue119/empty_plugin.rst create mode 100644 doc/doc_tests/test_issue119/test_zeronine$py.class create mode 100644 doc/doc_tests/test_issue119/test_zeronine.py create mode 100644 doc/doc_tests/test_issue119/test_zeronine.pyc create mode 100644 doc/doc_tests/test_issue142/errorclass_failure.rst create mode 100644 doc/doc_tests/test_issue142/support/errorclass_failing_test$py.class create mode 100644 doc/doc_tests/test_issue142/support/errorclass_failing_test.py create mode 100644 doc/doc_tests/test_issue142/support/errorclass_failing_test.pyc create mode 100644 doc/doc_tests/test_issue142/support/errorclass_failure_plugin$py.class create mode 100644 doc/doc_tests/test_issue142/support/errorclass_failure_plugin.py create mode 100644 doc/doc_tests/test_issue142/support/errorclass_failure_plugin.pyc create mode 100644 doc/doc_tests/test_issue142/support/errorclass_tests$py.class create mode 100644 doc/doc_tests/test_issue142/support/errorclass_tests.py create mode 100644 doc/doc_tests/test_issue142/support/errorclass_tests.pyc create mode 100644 doc/doc_tests/test_issue145/imported_tests.rst create mode 100644 doc/doc_tests/test_issue145/support/package1/__init__$py.class create mode 100644 doc/doc_tests/test_issue145/support/package1/__init__.py create mode 100644 doc/doc_tests/test_issue145/support/package1/__init__.pyc create mode 100644 doc/doc_tests/test_issue145/support/package1/test_module$py.class create mode 100644 doc/doc_tests/test_issue145/support/package1/test_module.py create mode 100644 doc/doc_tests/test_issue145/support/package1/test_module.pyc create mode 100644 doc/doc_tests/test_issue145/support/package2c/__init__$py.class create mode 100644 doc/doc_tests/test_issue145/support/package2c/__init__.py create mode 100644 doc/doc_tests/test_issue145/support/package2c/__init__.pyc create mode 100644 doc/doc_tests/test_issue145/support/package2c/test_module$py.class create mode 100644 doc/doc_tests/test_issue145/support/package2c/test_module.py create mode 100644 doc/doc_tests/test_issue145/support/package2c/test_module.pyc create mode 100644 doc/doc_tests/test_issue145/support/package2f/__init__$py.class create mode 100644 doc/doc_tests/test_issue145/support/package2f/__init__.py create mode 100644 doc/doc_tests/test_issue145/support/package2f/__init__.pyc create mode 100644 doc/doc_tests/test_issue145/support/package2f/test_module$py.class create mode 100644 doc/doc_tests/test_issue145/support/package2f/test_module.py create mode 100644 doc/doc_tests/test_issue145/support/package2f/test_module.pyc create mode 100644 doc/doc_tests/test_multiprocess/multiprocess.rst create mode 100644 doc/doc_tests/test_multiprocess/multiprocess_fixtures$py.class create mode 100644 doc/doc_tests/test_multiprocess/multiprocess_fixtures.py create mode 100644 doc/doc_tests/test_multiprocess/multiprocess_fixtures.pyc create mode 100644 doc/doc_tests/test_multiprocess/support/test_can_split.py create mode 100644 doc/doc_tests/test_multiprocess/support/test_can_split.pyc create mode 100644 doc/doc_tests/test_multiprocess/support/test_not_shared.py create mode 100644 doc/doc_tests/test_multiprocess/support/test_not_shared.pyc create mode 100644 doc/doc_tests/test_multiprocess/support/test_shared.py create mode 100644 doc/doc_tests/test_multiprocess/support/test_shared.pyc create mode 100644 doc/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst create mode 100644 doc/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst.py3.patch create mode 100644 doc/doc_tests/test_restricted_plugin_options/support/bad.cfg create mode 100644 doc/doc_tests/test_restricted_plugin_options/support/start.cfg create mode 100644 doc/doc_tests/test_restricted_plugin_options/support/test$py.class create mode 100644 doc/doc_tests/test_restricted_plugin_options/support/test.py create mode 100644 doc/doc_tests/test_restricted_plugin_options/support/test.pyc create mode 100644 doc/doc_tests/test_selector_plugin/selector_plugin.rst create mode 100644 doc/doc_tests/test_selector_plugin/support/mymodule$py.class create mode 100644 doc/doc_tests/test_selector_plugin/support/mymodule.py create mode 100644 doc/doc_tests/test_selector_plugin/support/mymodule.pyc create mode 100644 doc/doc_tests/test_selector_plugin/support/mypackage/__init__$py.class create mode 100644 doc/doc_tests/test_selector_plugin/support/mypackage/__init__.py create mode 100644 doc/doc_tests/test_selector_plugin/support/mypackage/__init__.pyc create mode 100644 doc/doc_tests/test_selector_plugin/support/mypackage/math/__init__$py.class create mode 100644 doc/doc_tests/test_selector_plugin/support/mypackage/math/__init__.py create mode 100644 doc/doc_tests/test_selector_plugin/support/mypackage/math/__init__.pyc create mode 100644 doc/doc_tests/test_selector_plugin/support/mypackage/math/basic$py.class create mode 100644 doc/doc_tests/test_selector_plugin/support/mypackage/math/basic.py create mode 100644 doc/doc_tests/test_selector_plugin/support/mypackage/math/basic.pyc create mode 100644 doc/doc_tests/test_selector_plugin/support/mypackage/strings$py.class create mode 100644 doc/doc_tests/test_selector_plugin/support/mypackage/strings.py create mode 100644 doc/doc_tests/test_selector_plugin/support/mypackage/strings.pyc create mode 100644 doc/doc_tests/test_selector_plugin/support/tests/math/basic$py.class create mode 100644 doc/doc_tests/test_selector_plugin/support/tests/math/basic.py create mode 100644 doc/doc_tests/test_selector_plugin/support/tests/math/basic.pyc create mode 100644 doc/doc_tests/test_selector_plugin/support/tests/mymodule/my_function$py.class create mode 100644 doc/doc_tests/test_selector_plugin/support/tests/mymodule/my_function.py create mode 100644 doc/doc_tests/test_selector_plugin/support/tests/mymodule/my_function.pyc create mode 100644 doc/doc_tests/test_selector_plugin/support/tests/strings/cat$py.class create mode 100644 doc/doc_tests/test_selector_plugin/support/tests/strings/cat.py create mode 100644 doc/doc_tests/test_selector_plugin/support/tests/strings/cat.pyc create mode 100644 doc/doc_tests/test_selector_plugin/support/tests/testlib$py.class create mode 100644 doc/doc_tests/test_selector_plugin/support/tests/testlib.py create mode 100644 doc/doc_tests/test_selector_plugin/support/tests/testlib.pyc create mode 100644 doc/doc_tests/test_xunit_plugin/support/nosetests.xml create mode 100644 doc/doc_tests/test_xunit_plugin/support/test_skip$py.class create mode 100644 doc/doc_tests/test_xunit_plugin/support/test_skip.py create mode 100644 doc/doc_tests/test_xunit_plugin/support/test_skip.pyc create mode 100644 doc/doc_tests/test_xunit_plugin/test_skips.rst create mode 100644 doc/docstring.py create mode 100644 doc/finding_tests.rst create mode 100644 doc/further_reading.rst create mode 100644 doc/index.html create mode 100644 doc/index.rst create mode 100644 doc/man.rst create mode 100644 doc/manbuilder.py create mode 100644 doc/manbuilder.pyc create mode 100644 doc/manpage.py create mode 100644 doc/manpage.pyc create mode 100644 doc/more_info.rst create mode 100644 doc/news.rst create mode 100644 doc/plugins.rst create mode 100644 doc/plugins/allmodules.rst create mode 100644 doc/plugins/attrib.rst create mode 100644 doc/plugins/builtin.rst create mode 100644 doc/plugins/capture.rst create mode 100644 doc/plugins/collect.rst create mode 100644 doc/plugins/cover.rst create mode 100644 doc/plugins/debug.rst create mode 100644 doc/plugins/deprecated.rst create mode 100644 doc/plugins/doctests.rst create mode 100644 doc/plugins/documenting.rst create mode 100644 doc/plugins/errorclasses.rst create mode 100644 doc/plugins/failuredetail.rst create mode 100644 doc/plugins/interface.rst create mode 100644 doc/plugins/isolate.rst create mode 100644 doc/plugins/logcapture.rst create mode 100644 doc/plugins/multiprocess.rst create mode 100644 doc/plugins/other.rst create mode 100644 doc/plugins/prof.rst create mode 100644 doc/plugins/skip.rst create mode 100644 doc/plugins/testid.rst create mode 100644 doc/plugins/testing.rst create mode 100644 doc/plugins/writing.rst create mode 100644 doc/plugins/xunit.rst create mode 100644 doc/rtd-requirements.txt create mode 100644 doc/setuptools_integration.rst create mode 100644 doc/testing.rst create mode 100644 doc/testing_tools.rst create mode 100644 doc/usage.rst create mode 100644 doc/writing_tests.rst create mode 100644 examples/attrib_plugin.py create mode 100644 examples/html_plugin/htmlplug.py create mode 100644 examples/html_plugin/setup.py create mode 100644 examples/plugin/plug.py create mode 100644 examples/plugin/setup.py create mode 100644 functional_tests/doc_tests/test_addplugins/support/test$py.class create mode 100644 functional_tests/doc_tests/test_addplugins/support/test.py create mode 100644 functional_tests/doc_tests/test_addplugins/support/test.pyc create mode 100644 functional_tests/doc_tests/test_addplugins/test_addplugins.rst create mode 100644 functional_tests/doc_tests/test_allmodules/support/mod$py.class create mode 100644 functional_tests/doc_tests/test_allmodules/support/mod.py create mode 100644 functional_tests/doc_tests/test_allmodules/support/mod.pyc create mode 100644 functional_tests/doc_tests/test_allmodules/support/test$py.class create mode 100644 functional_tests/doc_tests/test_allmodules/support/test.py create mode 100644 functional_tests/doc_tests/test_allmodules/support/test.pyc create mode 100644 functional_tests/doc_tests/test_allmodules/test_allmodules.rst create mode 100644 functional_tests/doc_tests/test_coverage_html/coverage_html.rst create mode 100644 functional_tests/doc_tests/test_coverage_html/coverage_html.rst.py3.patch create mode 100644 functional_tests/doc_tests/test_coverage_html/coverage_html_fixtures$py.class create mode 100644 functional_tests/doc_tests/test_coverage_html/coverage_html_fixtures.py create mode 100644 functional_tests/doc_tests/test_coverage_html/coverage_html_fixtures.pyc create mode 100644 functional_tests/doc_tests/test_coverage_html/support/blah.py create mode 100644 functional_tests/doc_tests/test_coverage_html/support/blah.pyc create mode 100644 functional_tests/doc_tests/test_coverage_html/support/tests/test_covered.py create mode 100644 functional_tests/doc_tests/test_coverage_html/support/tests/test_covered.pyc create mode 100644 functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures.rst create mode 100644 functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures$py.class create mode 100644 functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.py create mode 100644 functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.pyc create mode 100644 functional_tests/doc_tests/test_init_plugin/example.cfg create mode 100644 functional_tests/doc_tests/test_init_plugin/init_plugin.rst create mode 100644 functional_tests/doc_tests/test_init_plugin/init_plugin.rst.py3.patch create mode 100644 functional_tests/doc_tests/test_issue089/support/unwanted_package/__init__$py.class create mode 100644 functional_tests/doc_tests/test_issue089/support/unwanted_package/__init__.py create mode 100644 functional_tests/doc_tests/test_issue089/support/unwanted_package/__init__.pyc create mode 100644 functional_tests/doc_tests/test_issue089/support/unwanted_package/test_spam$py.class create mode 100644 functional_tests/doc_tests/test_issue089/support/unwanted_package/test_spam.py create mode 100644 functional_tests/doc_tests/test_issue089/support/unwanted_package/test_spam.pyc create mode 100644 functional_tests/doc_tests/test_issue089/support/wanted_package/__init__$py.class create mode 100644 functional_tests/doc_tests/test_issue089/support/wanted_package/__init__.py create mode 100644 functional_tests/doc_tests/test_issue089/support/wanted_package/__init__.pyc create mode 100644 functional_tests/doc_tests/test_issue089/support/wanted_package/test_eggs$py.class create mode 100644 functional_tests/doc_tests/test_issue089/support/wanted_package/test_eggs.py create mode 100644 functional_tests/doc_tests/test_issue089/support/wanted_package/test_eggs.pyc create mode 100644 functional_tests/doc_tests/test_issue089/unwanted_package.rst create mode 100644 functional_tests/doc_tests/test_issue097/plugintest_environment.rst create mode 100644 functional_tests/doc_tests/test_issue107/plugin_exceptions.rst create mode 100644 functional_tests/doc_tests/test_issue107/support/test_spam$py.class create mode 100644 functional_tests/doc_tests/test_issue107/support/test_spam.py create mode 100644 functional_tests/doc_tests/test_issue107/support/test_spam.pyc create mode 100644 functional_tests/doc_tests/test_issue119/empty_plugin.rst create mode 100644 functional_tests/doc_tests/test_issue119/test_zeronine$py.class create mode 100644 functional_tests/doc_tests/test_issue119/test_zeronine.py create mode 100644 functional_tests/doc_tests/test_issue119/test_zeronine.pyc create mode 100644 functional_tests/doc_tests/test_issue142/errorclass_failure.rst create mode 100644 functional_tests/doc_tests/test_issue142/support/errorclass_failing_test$py.class create mode 100644 functional_tests/doc_tests/test_issue142/support/errorclass_failing_test.py create mode 100644 functional_tests/doc_tests/test_issue142/support/errorclass_failing_test.pyc create mode 100644 functional_tests/doc_tests/test_issue142/support/errorclass_failure_plugin$py.class create mode 100644 functional_tests/doc_tests/test_issue142/support/errorclass_failure_plugin.py create mode 100644 functional_tests/doc_tests/test_issue142/support/errorclass_failure_plugin.pyc create mode 100644 functional_tests/doc_tests/test_issue142/support/errorclass_tests$py.class create mode 100644 functional_tests/doc_tests/test_issue142/support/errorclass_tests.py create mode 100644 functional_tests/doc_tests/test_issue142/support/errorclass_tests.pyc create mode 100644 functional_tests/doc_tests/test_issue145/imported_tests.rst create mode 100644 functional_tests/doc_tests/test_issue145/support/package1/__init__$py.class create mode 100644 functional_tests/doc_tests/test_issue145/support/package1/__init__.py create mode 100644 functional_tests/doc_tests/test_issue145/support/package1/__init__.pyc create mode 100644 functional_tests/doc_tests/test_issue145/support/package1/test_module$py.class create mode 100644 functional_tests/doc_tests/test_issue145/support/package1/test_module.py create mode 100644 functional_tests/doc_tests/test_issue145/support/package1/test_module.pyc create mode 100644 functional_tests/doc_tests/test_issue145/support/package2c/__init__$py.class create mode 100644 functional_tests/doc_tests/test_issue145/support/package2c/__init__.py create mode 100644 functional_tests/doc_tests/test_issue145/support/package2c/__init__.pyc create mode 100644 functional_tests/doc_tests/test_issue145/support/package2c/test_module$py.class create mode 100644 functional_tests/doc_tests/test_issue145/support/package2c/test_module.py create mode 100644 functional_tests/doc_tests/test_issue145/support/package2c/test_module.pyc create mode 100644 functional_tests/doc_tests/test_issue145/support/package2f/__init__$py.class create mode 100644 functional_tests/doc_tests/test_issue145/support/package2f/__init__.py create mode 100644 functional_tests/doc_tests/test_issue145/support/package2f/__init__.pyc create mode 100644 functional_tests/doc_tests/test_issue145/support/package2f/test_module$py.class create mode 100644 functional_tests/doc_tests/test_issue145/support/package2f/test_module.py create mode 100644 functional_tests/doc_tests/test_issue145/support/package2f/test_module.pyc create mode 100644 functional_tests/doc_tests/test_multiprocess/multiprocess.rst create mode 100644 functional_tests/doc_tests/test_multiprocess/multiprocess_fixtures$py.class create mode 100644 functional_tests/doc_tests/test_multiprocess/multiprocess_fixtures.py create mode 100644 functional_tests/doc_tests/test_multiprocess/multiprocess_fixtures.pyc create mode 100644 functional_tests/doc_tests/test_multiprocess/support/test_can_split.py create mode 100644 functional_tests/doc_tests/test_multiprocess/support/test_can_split.pyc create mode 100644 functional_tests/doc_tests/test_multiprocess/support/test_not_shared.py create mode 100644 functional_tests/doc_tests/test_multiprocess/support/test_not_shared.pyc create mode 100644 functional_tests/doc_tests/test_multiprocess/support/test_shared.py create mode 100644 functional_tests/doc_tests/test_multiprocess/support/test_shared.pyc create mode 100644 functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst create mode 100644 functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst.py3.patch create mode 100644 functional_tests/doc_tests/test_restricted_plugin_options/support/bad.cfg create mode 100644 functional_tests/doc_tests/test_restricted_plugin_options/support/start.cfg create mode 100644 functional_tests/doc_tests/test_restricted_plugin_options/support/test$py.class create mode 100644 functional_tests/doc_tests/test_restricted_plugin_options/support/test.py create mode 100644 functional_tests/doc_tests/test_restricted_plugin_options/support/test.pyc create mode 100644 functional_tests/doc_tests/test_selector_plugin/selector_plugin.rst create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mymodule$py.class create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mymodule.py create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mymodule.pyc create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mypackage/__init__$py.class create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mypackage/__init__.py create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mypackage/__init__.pyc create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/__init__$py.class create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/__init__.py create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/__init__.pyc create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/basic$py.class create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/basic.py create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/basic.pyc create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mypackage/strings$py.class create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mypackage/strings.py create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/mypackage/strings.pyc create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/tests/math/basic$py.class create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/tests/math/basic.py create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/tests/math/basic.pyc create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/tests/mymodule/my_function$py.class create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/tests/mymodule/my_function.py create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/tests/mymodule/my_function.pyc create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/tests/strings/cat$py.class create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/tests/strings/cat.py create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/tests/strings/cat.pyc create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/tests/testlib$py.class create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/tests/testlib.py create mode 100644 functional_tests/doc_tests/test_selector_plugin/support/tests/testlib.pyc create mode 100644 functional_tests/doc_tests/test_xunit_plugin/support/nosetests.xml create mode 100644 functional_tests/doc_tests/test_xunit_plugin/support/test_skip$py.class create mode 100644 functional_tests/doc_tests/test_xunit_plugin/support/test_skip.py create mode 100644 functional_tests/doc_tests/test_xunit_plugin/support/test_skip.pyc create mode 100644 functional_tests/doc_tests/test_xunit_plugin/test_skips.rst create mode 100644 functional_tests/support/att/test_attr$py.class create mode 100644 functional_tests/support/att/test_attr.py create mode 100644 functional_tests/support/att/test_attr.pyc create mode 100644 functional_tests/support/ctx/mod_import_skip$py.class create mode 100644 functional_tests/support/ctx/mod_import_skip.py create mode 100644 functional_tests/support/ctx/mod_import_skip.pyc create mode 100644 functional_tests/support/ctx/mod_setup_fails$py.class create mode 100644 functional_tests/support/ctx/mod_setup_fails.py create mode 100644 functional_tests/support/ctx/mod_setup_fails.pyc create mode 100644 functional_tests/support/ctx/mod_setup_skip$py.class create mode 100644 functional_tests/support/ctx/mod_setup_skip.py create mode 100644 functional_tests/support/ctx/mod_setup_skip.pyc create mode 100644 functional_tests/support/dir1/mod$py.class create mode 100644 functional_tests/support/dir1/mod.py create mode 100644 functional_tests/support/dir1/mod.pyc create mode 100644 functional_tests/support/dir1/pak/__init__$py.class create mode 100644 functional_tests/support/dir1/pak/__init__.py create mode 100644 functional_tests/support/dir1/pak/__init__.pyc create mode 100644 functional_tests/support/dir1/pak/mod$py.class create mode 100644 functional_tests/support/dir1/pak/mod.py create mode 100644 functional_tests/support/dir1/pak/mod.pyc create mode 100644 functional_tests/support/dir1/pak/sub/__init__$py.class create mode 100644 functional_tests/support/dir1/pak/sub/__init__.py create mode 100644 functional_tests/support/dir1/pak/sub/__init__.pyc create mode 100644 functional_tests/support/dir2/mod$py.class create mode 100644 functional_tests/support/dir2/mod.py create mode 100644 functional_tests/support/dir2/mod.pyc create mode 100644 functional_tests/support/dir2/pak/__init__$py.class create mode 100644 functional_tests/support/dir2/pak/__init__.py create mode 100644 functional_tests/support/dir2/pak/__init__.pyc create mode 100644 functional_tests/support/dir2/pak/mod$py.class create mode 100644 functional_tests/support/dir2/pak/mod.py create mode 100644 functional_tests/support/dir2/pak/mod.pyc create mode 100644 functional_tests/support/dir2/pak/sub/__init__$py.class create mode 100644 functional_tests/support/dir2/pak/sub/__init__.py create mode 100644 functional_tests/support/dir2/pak/sub/__init__.pyc create mode 100644 functional_tests/support/dtt/docs/doc.txt create mode 100644 functional_tests/support/dtt/docs/errdoc.txt create mode 100644 functional_tests/support/dtt/docs/nodoc.txt create mode 100644 functional_tests/support/dtt/some_mod$py.class create mode 100644 functional_tests/support/dtt/some_mod.py create mode 100644 functional_tests/support/dtt/some_mod.pyc create mode 100644 functional_tests/support/empty/.hidden create mode 100644 functional_tests/support/ep/Some_plugin.egg-info/PKG-INFO create mode 100644 functional_tests/support/ep/Some_plugin.egg-info/SOURCES.txt create mode 100644 functional_tests/support/ep/Some_plugin.egg-info/dependency_links.txt create mode 100644 functional_tests/support/ep/Some_plugin.egg-info/entry_points.txt create mode 100644 functional_tests/support/ep/Some_plugin.egg-info/top_level.txt create mode 100644 functional_tests/support/ep/setup.py create mode 100644 functional_tests/support/ep/someplugin.py create mode 100644 functional_tests/support/fdp/test_fdp$py.class create mode 100644 functional_tests/support/fdp/test_fdp.py create mode 100644 functional_tests/support/fdp/test_fdp.pyc create mode 100644 functional_tests/support/fdp/test_fdp_no_capt$py.class create mode 100644 functional_tests/support/fdp/test_fdp_no_capt.py create mode 100644 functional_tests/support/fdp/test_fdp_no_capt.pyc create mode 100644 functional_tests/support/gen/test$py.class create mode 100644 functional_tests/support/gen/test.py create mode 100644 functional_tests/support/gen/test.pyc create mode 100644 functional_tests/support/id_fails/test_a$py.class create mode 100644 functional_tests/support/id_fails/test_a.py create mode 100644 functional_tests/support/id_fails/test_a.pyc create mode 100644 functional_tests/support/id_fails/test_b$py.class create mode 100644 functional_tests/support/id_fails/test_b.py create mode 100644 functional_tests/support/id_fails/test_b.pyc create mode 100644 functional_tests/support/idp/exm$py.class create mode 100644 functional_tests/support/idp/exm.py create mode 100644 functional_tests/support/idp/exm.pyc create mode 100644 functional_tests/support/idp/tests$py.class create mode 100644 functional_tests/support/idp/tests.py create mode 100644 functional_tests/support/idp/tests.pyc create mode 100644 functional_tests/support/ipt/test1/ipthelp$py.class create mode 100644 functional_tests/support/ipt/test1/ipthelp.py create mode 100644 functional_tests/support/ipt/test1/ipthelp.pyc create mode 100644 functional_tests/support/ipt/test1/tests$py.class create mode 100644 functional_tests/support/ipt/test1/tests.py create mode 100644 functional_tests/support/ipt/test1/tests.pyc create mode 100644 functional_tests/support/ipt/test2/ipthelp$py.class create mode 100644 functional_tests/support/ipt/test2/ipthelp.py create mode 100644 functional_tests/support/ipt/test2/ipthelp.pyc create mode 100644 functional_tests/support/ipt/test2/tests$py.class create mode 100644 functional_tests/support/ipt/test2/tests.py create mode 100644 functional_tests/support/ipt/test2/tests.pyc create mode 100644 functional_tests/support/issue038/test$py.class create mode 100644 functional_tests/support/issue038/test.py create mode 100644 functional_tests/support/issue038/test.pyc create mode 100644 functional_tests/support/issue072/test$py.class create mode 100644 functional_tests/support/issue072/test.py create mode 100644 functional_tests/support/issue072/test.pyc create mode 100644 functional_tests/support/issue082/_mypackage/__init__.py create mode 100644 functional_tests/support/issue082/_mypackage/_eggs.py create mode 100644 functional_tests/support/issue082/_mypackage/bacon.py create mode 100644 functional_tests/support/issue082/mypublicpackage/__init__$py.class create mode 100644 functional_tests/support/issue082/mypublicpackage/__init__.py create mode 100644 functional_tests/support/issue082/mypublicpackage/__init__.pyc create mode 100644 functional_tests/support/issue082/mypublicpackage/_foo$py.class create mode 100644 functional_tests/support/issue082/mypublicpackage/_foo.py create mode 100644 functional_tests/support/issue082/mypublicpackage/_foo.pyc create mode 100644 functional_tests/support/issue082/mypublicpackage/bar$py.class create mode 100644 functional_tests/support/issue082/mypublicpackage/bar.py create mode 100644 functional_tests/support/issue082/mypublicpackage/bar.pyc create mode 100644 functional_tests/support/issue130/test$py.class create mode 100644 functional_tests/support/issue130/test.py create mode 100644 functional_tests/support/issue130/test.pyc create mode 100644 functional_tests/support/issue143/not-a-package/__init__.py create mode 100644 functional_tests/support/issue143/not-a-package/test.py create mode 100644 functional_tests/support/issue191/UNKNOWN.egg-info/PKG-INFO create mode 100644 functional_tests/support/issue191/UNKNOWN.egg-info/SOURCES.txt create mode 100644 functional_tests/support/issue191/UNKNOWN.egg-info/dependency_links.txt create mode 100644 functional_tests/support/issue191/UNKNOWN.egg-info/top_level.txt create mode 100644 functional_tests/support/issue191/setup.cfg create mode 100644 functional_tests/support/issue191/setup.py create mode 100644 functional_tests/support/issue191/test$py.class create mode 100644 functional_tests/support/issue191/test.py create mode 100644 functional_tests/support/issue191/test.pyc create mode 100644 functional_tests/support/issue269/test_bad_class$py.class create mode 100644 functional_tests/support/issue269/test_bad_class.py create mode 100644 functional_tests/support/issue269/test_bad_class.pyc create mode 100644 functional_tests/support/issue279/test_mod_setup_fails$py.class create mode 100644 functional_tests/support/issue279/test_mod_setup_fails.py create mode 100644 functional_tests/support/issue279/test_mod_setup_fails.pyc create mode 100644 functional_tests/support/issue408/nosetests.xml create mode 100644 functional_tests/support/issue408/test$py.class create mode 100644 functional_tests/support/issue408/test.py create mode 100644 functional_tests/support/issue408/test.pyc create mode 100644 functional_tests/support/ltfn/state$py.class create mode 100644 functional_tests/support/ltfn/state.py create mode 100644 functional_tests/support/ltfn/state.pyc create mode 100644 functional_tests/support/ltfn/test_mod$py.class create mode 100644 functional_tests/support/ltfn/test_mod.py create mode 100644 functional_tests/support/ltfn/test_mod.pyc create mode 100644 functional_tests/support/ltfn/test_pak1/__init__$py.class create mode 100644 functional_tests/support/ltfn/test_pak1/__init__.py create mode 100644 functional_tests/support/ltfn/test_pak1/__init__.pyc create mode 100644 functional_tests/support/ltfn/test_pak1/test_mod$py.class create mode 100644 functional_tests/support/ltfn/test_pak1/test_mod.py create mode 100644 functional_tests/support/ltfn/test_pak1/test_mod.pyc create mode 100644 functional_tests/support/ltfn/test_pak2/__init__$py.class create mode 100644 functional_tests/support/ltfn/test_pak2/__init__.py create mode 100644 functional_tests/support/ltfn/test_pak2/__init__.pyc create mode 100644 functional_tests/support/ltftc/tests$py.class create mode 100644 functional_tests/support/ltftc/tests.py create mode 100644 functional_tests/support/ltftc/tests.pyc create mode 100644 functional_tests/support/namespace_pkg/namespace_pkg/__init__$py.class create mode 100644 functional_tests/support/namespace_pkg/namespace_pkg/__init__.py create mode 100644 functional_tests/support/namespace_pkg/namespace_pkg/__init__.pyc create mode 100644 functional_tests/support/namespace_pkg/namespace_pkg/example$py.class create mode 100644 functional_tests/support/namespace_pkg/namespace_pkg/example.py create mode 100644 functional_tests/support/namespace_pkg/namespace_pkg/example.pyc create mode 100644 functional_tests/support/namespace_pkg/namespace_pkg/test_pkg$py.class create mode 100644 functional_tests/support/namespace_pkg/namespace_pkg/test_pkg.py create mode 100644 functional_tests/support/namespace_pkg/namespace_pkg/test_pkg.pyc create mode 100644 functional_tests/support/namespace_pkg/site-packages/namespace_pkg/__init__.py create mode 100644 functional_tests/support/namespace_pkg/site-packages/namespace_pkg/example2$py.class create mode 100644 functional_tests/support/namespace_pkg/site-packages/namespace_pkg/example2.py create mode 100644 functional_tests/support/namespace_pkg/site-packages/namespace_pkg/example2.pyc create mode 100644 functional_tests/support/namespace_pkg/site-packages/namespace_pkg/test_pkg2$py.class create mode 100644 functional_tests/support/namespace_pkg/site-packages/namespace_pkg/test_pkg2.py create mode 100644 functional_tests/support/namespace_pkg/site-packages/namespace_pkg/test_pkg2.pyc create mode 100644 functional_tests/support/package1/example$py.class create mode 100644 functional_tests/support/package1/example.py create mode 100644 functional_tests/support/package1/example.pyc create mode 100644 functional_tests/support/package1/tests/test_example_function$py.class create mode 100644 functional_tests/support/package1/tests/test_example_function.py create mode 100644 functional_tests/support/package1/tests/test_example_function.pyc create mode 100644 functional_tests/support/package2/maths$py.class create mode 100644 functional_tests/support/package2/maths.py create mode 100644 functional_tests/support/package2/maths.pyc create mode 100644 functional_tests/support/package2/test_pak/__init__$py.class create mode 100644 functional_tests/support/package2/test_pak/__init__.py create mode 100644 functional_tests/support/package2/test_pak/__init__.pyc create mode 100644 functional_tests/support/package2/test_pak/test_mod$py.class create mode 100644 functional_tests/support/package2/test_pak/test_mod.py create mode 100644 functional_tests/support/package2/test_pak/test_mod.pyc create mode 100644 functional_tests/support/package2/test_pak/test_sub/__init__$py.class create mode 100644 functional_tests/support/package2/test_pak/test_sub/__init__.py create mode 100644 functional_tests/support/package2/test_pak/test_sub/__init__.pyc create mode 100644 functional_tests/support/package2/test_pak/test_sub/test_mod$py.class create mode 100644 functional_tests/support/package2/test_pak/test_sub/test_mod.py create mode 100644 functional_tests/support/package2/test_pak/test_sub/test_mod.pyc create mode 100644 functional_tests/support/package3/lib/a$py.class create mode 100644 functional_tests/support/package3/lib/a.py create mode 100644 functional_tests/support/package3/lib/a.pyc create mode 100644 functional_tests/support/package3/src/b$py.class create mode 100644 functional_tests/support/package3/src/b.py create mode 100644 functional_tests/support/package3/src/b.pyc create mode 100644 functional_tests/support/package3/tests/test_a$py.class create mode 100644 functional_tests/support/package3/tests/test_a.py create mode 100644 functional_tests/support/package3/tests/test_a.pyc create mode 100644 functional_tests/support/package3/tests/test_b$py.class create mode 100644 functional_tests/support/package3/tests/test_b.py create mode 100644 functional_tests/support/package3/tests/test_b.pyc create mode 100644 functional_tests/support/pass/test$py.class create mode 100644 functional_tests/support/pass/test.py create mode 100644 functional_tests/support/pass/test.pyc create mode 100644 functional_tests/support/test.cfg create mode 100644 functional_tests/support/test_buggy_generators$py.class create mode 100644 functional_tests/support/test_buggy_generators.py create mode 100644 functional_tests/support/test_buggy_generators.pyc create mode 100644 functional_tests/support/todo/test_with_todo$py.class create mode 100644 functional_tests/support/todo/test_with_todo.py create mode 100644 functional_tests/support/todo/test_with_todo.pyc create mode 100644 functional_tests/support/todo/todoplug$py.class create mode 100644 functional_tests/support/todo/todoplug.py create mode 100644 functional_tests/support/todo/todoplug.pyc create mode 100644 functional_tests/support/twist/test_twisted.py create mode 100644 functional_tests/support/xunit.xml create mode 100644 functional_tests/support/xunit/test_xunit_as_suite$py.class create mode 100644 functional_tests/support/xunit/test_xunit_as_suite.py create mode 100644 functional_tests/support/xunit/test_xunit_as_suite.pyc create mode 100644 functional_tests/test_attribute_plugin$py.class create mode 100644 functional_tests/test_attribute_plugin.py create mode 100644 functional_tests/test_attribute_plugin.pyc create mode 100644 functional_tests/test_buggy_generators$py.class create mode 100644 functional_tests/test_buggy_generators.py create mode 100644 functional_tests/test_buggy_generators.pyc create mode 100644 functional_tests/test_cases$py.class create mode 100644 functional_tests/test_cases.py create mode 100644 functional_tests/test_cases.pyc create mode 100644 functional_tests/test_collector$py.class create mode 100644 functional_tests/test_collector.py create mode 100644 functional_tests/test_collector.pyc create mode 100644 functional_tests/test_commands$py.class create mode 100644 functional_tests/test_commands.py create mode 100644 functional_tests/test_commands.pyc create mode 100644 functional_tests/test_config_files$py.class create mode 100644 functional_tests/test_config_files.py create mode 100644 functional_tests/test_config_files.pyc create mode 100644 functional_tests/test_doctest_plugin$py.class create mode 100644 functional_tests/test_doctest_plugin.py create mode 100644 functional_tests/test_doctest_plugin.pyc create mode 100644 functional_tests/test_entrypoints$py.class create mode 100644 functional_tests/test_entrypoints.py create mode 100644 functional_tests/test_entrypoints.pyc create mode 100644 functional_tests/test_failuredetail_plugin$py.class create mode 100644 functional_tests/test_failuredetail_plugin.py create mode 100644 functional_tests/test_failuredetail_plugin.pyc create mode 100644 functional_tests/test_generator_fixtures$py.class create mode 100644 functional_tests/test_generator_fixtures.py create mode 100644 functional_tests/test_generator_fixtures.pyc create mode 100644 functional_tests/test_id_plugin$py.class create mode 100644 functional_tests/test_id_plugin.py create mode 100644 functional_tests/test_id_plugin.pyc create mode 100644 functional_tests/test_importer$py.class create mode 100644 functional_tests/test_importer.py create mode 100644 functional_tests/test_importer.pyc create mode 100644 functional_tests/test_isolate_plugin$py.class create mode 100644 functional_tests/test_isolate_plugin.py create mode 100644 functional_tests/test_isolate_plugin.pyc create mode 100644 functional_tests/test_issue120/support/some_test$py.class create mode 100644 functional_tests/test_issue120/support/some_test.py create mode 100644 functional_tests/test_issue120/support/some_test.pyc create mode 100644 functional_tests/test_issue120/test_named_test_with_doctest.rst create mode 100644 functional_tests/test_issue_072$py.class create mode 100644 functional_tests/test_issue_072.py create mode 100644 functional_tests/test_issue_072.pyc create mode 100644 functional_tests/test_issue_082$py.class create mode 100644 functional_tests/test_issue_082.py create mode 100644 functional_tests/test_issue_082.pyc create mode 100644 functional_tests/test_issue_408$py.class create mode 100644 functional_tests/test_issue_408.py create mode 100644 functional_tests/test_issue_408.pyc create mode 100644 functional_tests/test_load_tests_from_test_case$py.class create mode 100644 functional_tests/test_load_tests_from_test_case.py create mode 100644 functional_tests/test_load_tests_from_test_case.pyc create mode 100644 functional_tests/test_loader$py.class create mode 100644 functional_tests/test_loader.py create mode 100644 functional_tests/test_loader.pyc create mode 100644 functional_tests/test_multiprocessing/support/nameerror.py create mode 100644 functional_tests/test_multiprocessing/support/nameerror.pyc create mode 100644 functional_tests/test_multiprocessing/support/timeout.py create mode 100644 functional_tests/test_multiprocessing/test_nameerror$py.class create mode 100644 functional_tests/test_multiprocessing/test_nameerror.py create mode 100644 functional_tests/test_multiprocessing/test_nameerror.pyc create mode 100644 functional_tests/test_multiprocessing/test_process_timeout$py.class create mode 100644 functional_tests/test_multiprocessing/test_process_timeout.py create mode 100644 functional_tests/test_multiprocessing/test_process_timeout.pyc create mode 100644 functional_tests/test_namespace_pkg$py.class create mode 100644 functional_tests/test_namespace_pkg.py create mode 100644 functional_tests/test_namespace_pkg.pyc create mode 100644 functional_tests/test_plugin_api$py.class create mode 100644 functional_tests/test_plugin_api.py create mode 100644 functional_tests/test_plugin_api.pyc create mode 100644 functional_tests/test_plugins$py.class create mode 100644 functional_tests/test_plugins.py create mode 100644 functional_tests/test_plugins.pyc create mode 100644 functional_tests/test_plugintest$py.class create mode 100644 functional_tests/test_plugintest.py create mode 100644 functional_tests/test_plugintest.pyc create mode 100644 functional_tests/test_program$py.class create mode 100644 functional_tests/test_program.py create mode 100644 functional_tests/test_program.pyc create mode 100644 functional_tests/test_result$py.class create mode 100644 functional_tests/test_result.py create mode 100644 functional_tests/test_result.pyc create mode 100644 functional_tests/test_selector$py.class create mode 100644 functional_tests/test_selector.py create mode 100644 functional_tests/test_selector.pyc create mode 100644 functional_tests/test_skip_pdb_interaction$py.class create mode 100644 functional_tests/test_skip_pdb_interaction.py create mode 100644 functional_tests/test_skip_pdb_interaction.pyc create mode 100644 functional_tests/test_success$py.class create mode 100644 functional_tests/test_success.py create mode 100644 functional_tests/test_success.pyc create mode 100644 functional_tests/test_suite$py.class create mode 100644 functional_tests/test_suite.py create mode 100644 functional_tests/test_suite.pyc create mode 100644 functional_tests/test_withid_failures.rst create mode 100644 functional_tests/test_xunit$py.class create mode 100644 functional_tests/test_xunit.py create mode 100644 functional_tests/test_xunit.pyc create mode 100755 install-rpm.sh create mode 100644 lgpl.txt create mode 100644 nose/__init__.py create mode 100644 nose/case.py create mode 100644 nose/commands.py create mode 100644 nose/config.py create mode 100644 nose/core.py create mode 100644 nose/exc.py create mode 100644 nose/ext/__init__.py create mode 100644 nose/ext/dtcompat.py create mode 100644 nose/failure.py create mode 100644 nose/importer.py create mode 100644 nose/inspector.py create mode 100644 nose/loader.py create mode 100644 nose/plugins/__init__.py create mode 100644 nose/plugins/allmodules.py create mode 100644 nose/plugins/attrib.py create mode 100644 nose/plugins/base.py create mode 100644 nose/plugins/builtin.py create mode 100644 nose/plugins/capture.py create mode 100644 nose/plugins/collect.py create mode 100644 nose/plugins/cover.py create mode 100644 nose/plugins/debug.py create mode 100644 nose/plugins/deprecated.py create mode 100644 nose/plugins/doctests.py create mode 100644 nose/plugins/errorclass.py create mode 100644 nose/plugins/failuredetail.py create mode 100644 nose/plugins/isolate.py create mode 100644 nose/plugins/logcapture.py create mode 100644 nose/plugins/manager.py create mode 100644 nose/plugins/multiprocess.py create mode 100644 nose/plugins/plugintest.py create mode 100644 nose/plugins/prof.py create mode 100644 nose/plugins/skip.py create mode 100644 nose/plugins/testid.py create mode 100644 nose/plugins/xunit.py create mode 100644 nose/proxy.py create mode 100644 nose/pyversion.py create mode 100644 nose/result.py create mode 100644 nose/selector.py create mode 100644 nose/sphinx/__init__.py create mode 100644 nose/sphinx/pluginopts.py create mode 100644 nose/suite.py create mode 100644 nose/tools.py create mode 100644 nose/twistedtools.py create mode 100644 nose/usage.txt create mode 100644 nose/util.py create mode 100644 nosetests.1 create mode 100644 patch.py create mode 100755 selftest.py create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 setup3lib.py create mode 100644 unit_tests/helpers$py.class create mode 100644 unit_tests/helpers.py create mode 100644 unit_tests/helpers.pyc create mode 100644 unit_tests/mock$py.class create mode 100644 unit_tests/mock.py create mode 100644 unit_tests/mock.pyc create mode 100644 unit_tests/support/bug101/tests.py create mode 100644 unit_tests/support/bug105/tests$py.class create mode 100644 unit_tests/support/bug105/tests.py create mode 100644 unit_tests/support/bug105/tests.pyc create mode 100644 unit_tests/support/config_defaults/a.cfg create mode 100644 unit_tests/support/config_defaults/b.cfg create mode 100644 unit_tests/support/config_defaults/invalid.cfg create mode 100644 unit_tests/support/config_defaults/invalid_value.cfg create mode 100644 unit_tests/support/doctest/err_doctests$py.class create mode 100644 unit_tests/support/doctest/err_doctests.py create mode 100644 unit_tests/support/doctest/err_doctests.pyc create mode 100644 unit_tests/support/doctest/no_doctests$py.class create mode 100644 unit_tests/support/doctest/no_doctests.py create mode 100644 unit_tests/support/doctest/no_doctests.pyc create mode 100644 unit_tests/support/foo/__init__$py.class create mode 100644 unit_tests/support/foo/__init__.py create mode 100644 unit_tests/support/foo/__init__.pyc create mode 100644 unit_tests/support/foo/bar/__init__$py.class create mode 100644 unit_tests/support/foo/bar/__init__.py create mode 100644 unit_tests/support/foo/bar/__init__.pyc create mode 100644 unit_tests/support/foo/bar/buz$py.class create mode 100644 unit_tests/support/foo/bar/buz.py create mode 100644 unit_tests/support/foo/bar/buz.pyc create mode 100644 unit_tests/support/foo/doctests.txt create mode 100644 unit_tests/support/foo/test_foo.py create mode 100644 unit_tests/support/foo/tests/dir_test_file.py create mode 100644 unit_tests/support/issue006/tests$py.class create mode 100644 unit_tests/support/issue006/tests.py create mode 100644 unit_tests/support/issue006/tests.pyc create mode 100644 unit_tests/support/issue065/tests$py.class create mode 100644 unit_tests/support/issue065/tests.py create mode 100644 unit_tests/support/issue065/tests.pyc create mode 100644 unit_tests/support/issue270/__init__.py create mode 100644 unit_tests/support/issue270/__init__.pyc create mode 100644 unit_tests/support/issue270/foo_test.py create mode 100644 unit_tests/support/issue270/foo_test.pyc create mode 100644 unit_tests/support/other/file.txt create mode 100644 unit_tests/support/pkgorg/lib/modernity.py create mode 100644 unit_tests/support/pkgorg/tests/test_mod.py create mode 100755 unit_tests/support/script.py create mode 100644 unit_tests/support/test-dir/test.py create mode 100644 unit_tests/support/test.py create mode 100644 unit_tests/test_attribute_plugin$py.class create mode 100644 unit_tests/test_attribute_plugin.py create mode 100644 unit_tests/test_attribute_plugin.pyc create mode 100644 unit_tests/test_bug105$py.class create mode 100644 unit_tests/test_bug105.py create mode 100644 unit_tests/test_bug105.pyc create mode 100644 unit_tests/test_capture_plugin$py.class create mode 100644 unit_tests/test_capture_plugin.py create mode 100644 unit_tests/test_capture_plugin.pyc create mode 100644 unit_tests/test_cases$py.class create mode 100644 unit_tests/test_cases.py create mode 100644 unit_tests/test_cases.pyc create mode 100644 unit_tests/test_config$py.class create mode 100644 unit_tests/test_config.py create mode 100644 unit_tests/test_config.pyc create mode 100644 unit_tests/test_config_defaults.rst create mode 100644 unit_tests/test_core$py.class create mode 100644 unit_tests/test_core.py create mode 100644 unit_tests/test_core.pyc create mode 100644 unit_tests/test_deprecated_plugin$py.class create mode 100644 unit_tests/test_deprecated_plugin.py create mode 100644 unit_tests/test_deprecated_plugin.pyc create mode 100644 unit_tests/test_doctest_error_handling$py.class create mode 100644 unit_tests/test_doctest_error_handling.py create mode 100644 unit_tests/test_doctest_error_handling.pyc create mode 100644 unit_tests/test_doctest_munging.rst create mode 100644 unit_tests/test_id_plugin$py.class create mode 100644 unit_tests/test_id_plugin.py create mode 100644 unit_tests/test_id_plugin.pyc create mode 100644 unit_tests/test_importer$py.class create mode 100644 unit_tests/test_importer.py create mode 100644 unit_tests/test_importer.pyc create mode 100644 unit_tests/test_inspector$py.class create mode 100644 unit_tests/test_inspector.py create mode 100644 unit_tests/test_inspector.pyc create mode 100644 unit_tests/test_isolation_plugin$py.class create mode 100644 unit_tests/test_isolation_plugin.py create mode 100644 unit_tests/test_isolation_plugin.pyc create mode 100644 unit_tests/test_issue155.rst create mode 100644 unit_tests/test_issue270.rst create mode 100644 unit_tests/test_issue270_fixtures$py.class create mode 100644 unit_tests/test_issue270_fixtures.py create mode 100644 unit_tests/test_issue270_fixtures.pyc create mode 100644 unit_tests/test_issue_006$py.class create mode 100644 unit_tests/test_issue_006.py create mode 100644 unit_tests/test_issue_006.pyc create mode 100644 unit_tests/test_issue_064$py.class create mode 100644 unit_tests/test_issue_064.py create mode 100644 unit_tests/test_issue_064.pyc create mode 100644 unit_tests/test_issue_065$py.class create mode 100644 unit_tests/test_issue_065.py create mode 100644 unit_tests/test_issue_065.pyc create mode 100644 unit_tests/test_issue_100.rst create mode 100644 unit_tests/test_issue_100.rst.py3.patch create mode 100644 unit_tests/test_issue_101$py.class create mode 100644 unit_tests/test_issue_101.py create mode 100644 unit_tests/test_issue_101.pyc create mode 100644 unit_tests/test_issue_159.rst create mode 100644 unit_tests/test_issue_227$py.class create mode 100644 unit_tests/test_issue_227.py create mode 100644 unit_tests/test_issue_227.pyc create mode 100644 unit_tests/test_issue_230$py.class create mode 100644 unit_tests/test_issue_230.py create mode 100644 unit_tests/test_issue_230.pyc create mode 100644 unit_tests/test_lazy_suite$py.class create mode 100644 unit_tests/test_lazy_suite.py create mode 100644 unit_tests/test_lazy_suite.pyc create mode 100644 unit_tests/test_loader$py.class create mode 100644 unit_tests/test_loader.py create mode 100644 unit_tests/test_loader.pyc create mode 100644 unit_tests/test_logcapture_plugin$py.class create mode 100644 unit_tests/test_logcapture_plugin.py create mode 100644 unit_tests/test_logcapture_plugin.pyc create mode 100644 unit_tests/test_logging$py.class create mode 100644 unit_tests/test_logging.py create mode 100644 unit_tests/test_logging.pyc create mode 100644 unit_tests/test_ls_tree.rst create mode 100644 unit_tests/test_multiprocess$py.class create mode 100644 unit_tests/test_multiprocess.py create mode 100644 unit_tests/test_multiprocess.pyc create mode 100644 unit_tests/test_multiprocess_runner$py.class create mode 100644 unit_tests/test_multiprocess_runner.py create mode 100644 unit_tests/test_multiprocess_runner.pyc create mode 100644 unit_tests/test_pdb_plugin$py.class create mode 100644 unit_tests/test_pdb_plugin.py create mode 100644 unit_tests/test_pdb_plugin.pyc create mode 100644 unit_tests/test_plugin$py.class create mode 100644 unit_tests/test_plugin.py create mode 100644 unit_tests/test_plugin.pyc create mode 100644 unit_tests/test_plugin_interfaces$py.class create mode 100644 unit_tests/test_plugin_interfaces.py create mode 100644 unit_tests/test_plugin_interfaces.pyc create mode 100644 unit_tests/test_plugin_manager$py.class create mode 100644 unit_tests/test_plugin_manager.py create mode 100644 unit_tests/test_plugin_manager.pyc create mode 100644 unit_tests/test_plugins$py.class create mode 100644 unit_tests/test_plugins.py create mode 100644 unit_tests/test_plugins.pyc create mode 100644 unit_tests/test_result_proxy$py.class create mode 100644 unit_tests/test_result_proxy.py create mode 100644 unit_tests/test_result_proxy.pyc create mode 100644 unit_tests/test_selector$py.class create mode 100644 unit_tests/test_selector.py create mode 100644 unit_tests/test_selector.pyc create mode 100644 unit_tests/test_selector_plugins$py.class create mode 100644 unit_tests/test_selector_plugins.py create mode 100644 unit_tests/test_selector_plugins.pyc create mode 100644 unit_tests/test_skip_plugin$py.class create mode 100644 unit_tests/test_skip_plugin.py create mode 100644 unit_tests/test_skip_plugin.pyc create mode 100644 unit_tests/test_suite$py.class create mode 100644 unit_tests/test_suite.py create mode 100644 unit_tests/test_suite.pyc create mode 100644 unit_tests/test_tools$py.class create mode 100644 unit_tests/test_tools.py create mode 100644 unit_tests/test_tools.pyc create mode 100644 unit_tests/test_twisted$py.class create mode 100644 unit_tests/test_twisted.py create mode 100644 unit_tests/test_twisted.pyc create mode 100644 unit_tests/test_twisted_testcase$py.class create mode 100644 unit_tests/test_twisted_testcase.py create mode 100644 unit_tests/test_twisted_testcase.pyc create mode 100644 unit_tests/test_utils$py.class create mode 100644 unit_tests/test_utils.py create mode 100644 unit_tests/test_utils.pyc create mode 100644 unit_tests/test_xunit$py.class create mode 100644 unit_tests/test_xunit.py create mode 100644 unit_tests/test_xunit.pyc diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..aa962ea --- /dev/null +++ b/AUTHORS @@ -0,0 +1,23 @@ +Jason Pellerin +Kumar McMillan +Mika Eloranta +Jay Parlar +Scot Doyle +James Casbon +Antoine Pitrou +John J Lee +Allen Bierbaum +Pam Zerbinos +Augie Fackler +Peter Fein +Kevin Mitchell +Alex Stewart +Timothee Peignier +Thomas Kluyver +Heng Liu +Rosen Diankov +Buck Golemon +Bobby Impollonia +Takafumi Arakaki +Peter Bengtsson +Gary Donovan diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..a736d3d --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,647 @@ +1.1.2 + +- Fixed regression where the .coverage file was not saved (#439). + Patch by Timothée Peignier. + +1.1.1 + +- Fixed missing nose.sphinx module in source distribution (#436). + +1.1.0 + +- Revised multiprocessing implementation so that it works for test generators + (#399). Thanks to Rosen Diankov for the patch. +- More fixes to multiprocessing implemented by Buck Golemon and Gary Donovan + (also part of #399). +- Lots of improvements to the attrib plugin by Bobby Impollonia (#412, #411, + #324 and #381) +- Code coverage plugin now uses native HTML generation when coverage 3 is + installed (#264). Thanks to Timothée Peignier for the patch. +- Xunit plugin now shows test run time in fractions of a second (#317) +- @attr (from nose.plugins.attrib) can now be used as a class decorator (#292) +- Fixes Xunit plugin to handle non-UTF8 characters (#395) +- Fixes Xunit plugin for reporting generator tests (#369) +- Fixed problems with SkipTest in Python 3.2 (#389) +- Fixed bug in doctest plugin under python 3. Thanks to Thomas Kluyver + for the patch. (#391) +- Fixes mishandling of custom exceptions during failures (#405) +- Fixed subtle bug in :option:`--first-package-wins` that made it + unpredictable (#293) +- Fixes case where teardown_class() was called more than once (#408). Thanks + to Heng Liu for the patch. +- Fixes coverage module names -- 'cal' used to also match calendar which was a + bug (#433) +- Fixes capture plugin when exception message contains non-ascii chars (#402) +- Fixed bug in tests for twisted tools. Thanks to Thomas Kluyver + for the patch. +- Makes :option:`--plugins` more succinct when there are no options (#235) + +1.0.0 + +- Made nose compatible with python 3. **Huge** thanks to Alex "foogod" + Stewart! + +0.11.4 + +- Made nose compatible with Python 2.7. + +0.11.3 + +- Fixed default plugin manager's use of plugin overriding. Thanks to + rob.daylife for the bug report and patch. (#323). + +0.11.2 + +- Changed plugin loading so that external plugins loaded via extension + points can override builtin plugins with the same name. +- Updated multiprocess plugin and nose's packaging to allow multiprocess + plugin to work on Windows (#265). +- Fixed bug in xunit plugin's interaction with suites and errors in + module-level setup. Thanks to Mark McCague for the bug report (#279). +- Fixed bug in nose.loader.TestLoader that allowed Test classes that raise + exceptions in __init__ to crash the test run (#269). +- Fixed bugs in nose's test suite that caused spurious failures on Windows. +- Fixed bug in twisted tools: delayed calls were not shut down on + reactor stop. Thanks to abbeyj for the patch (#278). +- Fixed bug where root log handlers were not cleared. For example, this was + emitting unwanted messages when testing Google App Engine websites. +- Fixed bug in test names output by xunit plugin. Thanks to Philip + Jenvey for the bug report and patch (#280). +- Fixed bug in profile plugin that caused stats to fail to print under Python + 2.5 and later. Thanks to djs at n-cube dot org for the bug report (#285). +- Improved logcapture filtering, with default setting to filter out log + messages from nose itself. Thanks to gfxmonk for the patch (#277). +- The xunit plugin now tags skipped tests with a testcase tag, and + prevents the XML from containing invalid control characters. +- Updated nose to be compatible with python 2.7 (#305). +- Updated loading of usage document to allow nose to run from within + an egg archive (#288). +- Fixed IronPython checks to make nose compatible with more versions + of IronPython. Thanks to Kevin Mitchell for the patch (#316). + +0.11.1 + +- Fixed bug in xunit plugin xml escaping. Thanks to Nat Williams for the bug + report (#266). +- Fixed bug in xunit plugin that could cause test run to crash after certain + types of errors or actions by other plugins. +- Fixed bug in testid plugin that could cause test run to crash after certain + types of errors or actions by other plugins. +- Fixed bug in collect only plugin that caused it to fail when collecting from + test generators. +- Fixed some broken html in docs. + +0.11 + +- **All new documentation!** nose's documentation is now generated by + Sphinx. And thanks to Pam Zerbinos, it is much better organized and easier + to use and read. +- Two new core commandline options can help with testing namespace + packages. :option:`--first-package-wins` is useful when you want to test one + part of a namespace package that uses another part; in previous versions of + nose, the other part of the namespace package would be evicted from + sys.modules when the 2nd loaded. :option:`--traverse-namespace` is useful if + you want nose to discover tests across entries in a package's + __path__. (This was formerly the default behavior). +- To make it easier to use custom plugins without needing setuptools, + :func:`nose.core.main` and :func:`nose.core.run` now support an + :doc:`addplugins ` keyword + argument that takes a list of additional plugins to make available. **Note** + that adding a plugin to this list **does not** activate or enable the + plugin, only makes it available to be enabled via command-line or + config file settings. +- Thanks to Kevin Mitchell, nose is now more compatible with + IronPython. IronPython is still not fully supported, but it should work. If + you'd like to improve nose's support for IronPython further, please join the + nose developer's list and volunteer to become the IronPython maintainer for + nose! +- Added multiprocess plugin that allows tests to be run in parallel + across multiple processes. +- Added logcapture plugin that captures logging messages and prints them with + failing tests. Thanks to Max Ischenko for the implementation. +- Added optional HTML coverage reports to coverage plugin. Thanks to Augie + Fackler for the patch. +- Added plugin that enables collection of tests in all modules. Thanks to + Peter Fein for the patch (#137). +- Added --failed option to testid plugin. When this option is in effect, if + any tests failed in the previous test run (so long as testid was active for + that test run) only the failed tests will run. +- Made it possible to 'yield test' in addition to 'yield test,' from test + generators. Thanks to Chad Whitacre for the patch (#230). +- Fixed bug that caused traceback inspector to fail when source code file + could not be found. Thanks to Philip Jenvey for the bug report and patch + (#236). +- Fixed some issues limiting compatibility with IronPython. Thanks to Kevin + Mitchell for the patch. +- Added support for module and test case fixtures in doctest files (#60). +- Added --traverse-namespace commandline option that restores old default + behavior of following all package __path__ entries when loading tests from + packages. Thanks to Philip Jenvey for the patch (#167). +- Added --first-package-wins commandline option to better support testing + parts of namespace packages. Thanks to Jason Coombs for the bug report + (#197). +- Added versioned nosetests scripts (#123). +- Fixed bug that would cause context teardown to fail to run in some + cases. Thanks to John Shaw for the bug report and patch (#234). +- Enabled doctest plugin to use variable other than "_" as the default result + variable. Thanks to Matt Good for the patch (#163). +- Fixed bug that would cause unicode output to crash output capture. Thanks to + schickb for the bug report (#227). +- Added setUp and tearDown as valid names for module-level fixtures. Thanks to + AgilityNerd for the patch (#211). +- Fixed bug in list of valid names for package-level fixtures. Thanks to + Philip Jenvey for the patch (#237). +- Updated man page generation using hacked up manpage writer from + docutils sandbox. Thanks grubert@users.sourceforge.net for the + original module. + +0.10.4 + +- nose is now compatible with python 2.6. + +0.10.3 + +- Fixed bug in nosetests setup command that caused an exception to be raised + if run with options. Thanks to Philip Jenvey for the bug report (#191). +- Raised score of coverage plugin to 200, so that it will execute before + default-score plugins, and so be able to catch more import-time code. Thanks + to Ned Batchelder for the bug report and patch (#190). + +0.10.2 + +- nose now runs under jython (jython svn trunk only at this time). Thanks to + Philip Jenvey, Pam Zerbinos and the other pycon sprinters (#160). +- Fixed bugs in loader, default plugin manager, and other modules that + caused plugin exceptions to be swallowed (#152, #155). Thanks to John J + Lee for the bug report and patch. +- Added selftest.py script, used to test a non-installed distribution of + nose (#49). Thanks to Antoine Pitrou and John J Lee for the bug report and + patches. +- Fixed bug in nose.importer that caused errors with namespace + packages. Thanks to Philip Jenvey for the bug report and patch (#164). +- Fixed bug in nose.tools.with_setup that prevented use of multiple + @with_setup decorators. Thanks to tlesher for the bug report (#151). +- Fixed bugs in handling of context fixtures for tests imported into a + package. Thanks to Gary Bernhardt for the bug report (#145). +- Fixed bugs in handling of config files and config file options for plugins + excluded by a RestrictedPluginManager. Thanks to John J Lee and Philip + Jenvey for the bug reports and patches (#158, #166). +- Updated ErrorClass exception reporting to be shorter and more clear. Thanks + to John J Lee for the patch (#142). +- Allowed plugins to load tests from modules starting with '_'. Thanks to John + J Lee for the patch (#82). +- Updated documentation about building as rpm (#127). +- Updated config to make including executable files the default on + IronPython as well as on Windows. Thanks to sanxiyn for the bug + report and patch (#183). +- Fixed a python 2.3 incompatibility in errorclass_failure.rst + (#173). Thanks to Philip Jenvey for the bug report and patch. +- Classes with metaclasses can now be collected as tests (#153). +- Made sure the document tree in the selector plugin test is accurate + and tested (#144). Thanks to John J Lee for the bug report and + patch. +- Fixed stack level used when dropping into pdb in a doctest + (#154). Thanks to John J Lee for the bug report and patch. +- Fixed bug in ErrorClassPlugin that made some missing keyword + argument errors obscure (#159). Thanks to Philip Jenvey for the bug + report and patch. + +0.10.1 + +- Fixed bug in capture plugin that caused it to record captured output + on the test in the wrong attribute (#113). +- Fixed bug in result proxy that caused tests to fail if they accessed + certain result attibutes directly (#114). Thanks to Neilen Marais + for the bug report. +- Fixed bug in capture plugin that caused other error formatters + changes to be lost if no output was captured (#124). Thanks to + someone at ilorentz.org for the bug report. +- Fixed several bugs in the nosetests setup command that made some + options unusable and the command itself unusable when no options + were set (#125, #126, #128). Thanks to Alain Poirier for the bug + reports. +- Fixed bug in handling of string errors (#130). Thanks to schl... at + uni-oldenburg.de for the bug report. +- Fixed bug in coverage plugin option handling that prevented + --cover-package=mod1,mod2 from working (#117). Thanks to Allen + Bierbaum for the patch. +- Fixed bug in profiler plugin that prevented output from being + produced when output capture was enabled on python 2.5 + (#129). Thanks to James Casbon for the patch. +- Fixed bug in adapting 0.9 plugins to 0.10 (#119 part one). Thanks to + John J Lee for the bug report and tests. +- Fixed bug in handling of argv in config and plugin test utilities + (#119 part two). Thanks to John J Lee for the bug report and patch. +- Fixed bug where Failure cases due to invalid test name + specifications were passed to plugins makeTest (#120). Thanks to + John J Lee for the bug report and patch. +- Fixed bugs in doc css that mangled display in small windows. Thanks + to Ben Hoyt for the bug report and Michal Kwiatkowski for the fix. +- Made it possible to pass a list or comma-separated string as + defaultTest to main(). Thanks to Allen Bierbaum for the suggestion + and patch. +- Fixed a bug in nose.selector and nose.util.getpackage that caused + directories with names that are not legal python identifiers to be + collected as packages (#143). Thanks to John J Lee for the bug + report. + +0.10.0 + +- Fixed bug that broke plugins with names containing underscores or + hyphens. Thanks to John J Lee for the bug report and patch (Issue + #81). +- Fixed typo in nose.__all__. Thanks to John J Lee for the bug report. +- Fixed handling of test descriptions that are multiline + docstrings. Thanks to James Casbon for the patch (Issue #50). +- Improved documentation of doctest plugin to make it clear that + entities may have doctests, or themselves be tests, but not + both. Thanks to John J Lee for the bug report and patch (Issue #84). +- Made __file__ available in non-python-module doctests. +- Fixed bug that made it impossible for plugins to exclude package + directories from test discovery (Issue #89). Thanks to John J Lee + for the bug report and patch. +- Fixed bug that swallowed TypeError and AttributeError exceptions + raised in some plugin calls (Issue #95). Thanks to John J Lee for + the bug report. +- Fixed inconsistencies in many interfaces and docs. Thanks to John J + Lee for many bug reports. +- Fixed bugs in rpm generation (Issue #96). Thanks to Mike Verdone for + the bug report and http://bugs.python.org/issue644744 for the fix. +- Fixed inconsisent use of os.environ in plugin testing + utilities. Thanks to John J Lee for the bug report and patch (Issue + #97). +- Fixed bug in test_address that prevented use of nose.case.Test in + doctests (Issue #100). Thanks to John J Lee for the bug report. +- Fixed bug in error class plugin that caused string exceptions to be + masked (#Issue 101). Thanks to depaula for the bug report. +- Fixed bugs in tests and the profiler plugin when running under + Windows (Issue #103). Thanks to Sidnei Da Silva for the bug report. +- Fixed bugs in plugin calls that caused exception swallowing (Issue + #107). Thanks to John L Lee for the bug report and patch. +- Added more plugin example doctests. Thanks to Kumar McMillan and + John L Lee for patches and lots of help. +- Changed default location of id file for TestId plugin from ~/.noseids to + .noseids in the configured working directory. + +0.10.0b1 + +- Added support for a description attribute on tests in function and + method test cases. Most useful for generators: set the description + attribute on the yielded function. +- Fixed incorrect signature of addSuccess() method in + IPluginInterface. Thanks to nosexunit for the bug report. (Issue + #68). +- Fixed isclass() function in nose.util so that it will not raise an + exception when examining instances that have no accessible __bases__ + attribute. (Issue #65). +- Fixed passing of tests to result; the test itself and not the + wrapper was being passed, resulting in test description plugin hooks + not being called. (Issue #71). +- Fixed bugs in FailureDetail and Capture plugins, and plugin manager + and proxy uses of chainable plugin calls. Thanks to Ian Bicking for + the bug report (Issue #72). +- Improved documentation. + +0.10.0a2 + +- Changed entry point name to nose.plugins.0.10 -- dashes and other + non-word characters besides . are not allowed in entry point names. + (Issue #67) +- Fixed loading of plugins from that entry point. +- Fixed backwards-compatibility issue in nose.util (is_generator was + renamed isgenerator). (Issue #64) +- Fixed bug in --logging-config option. Thanks to anton_kr... at yahoo + com for the bug report. (Issue #62) +- Fixed bug in handling of --where argument: first --where was not + passed to loader as workingDir. Thanks to nosexunit for the bug + report. (Issue #63). + +0.10.0a1 + +- Rewrote test loader to be more drop-in compatible with + unittest.TestLoader and to support a more user-friendly command + line. +- Rewrote test runner and result classes to reduce duplication of effort. +- Revised configuration system to be less convoluted. +- Added nose.case.TestCase as universal wrapper for all + testcases. Plugins always see instances of this class. +- Added a management layer to the plugin system, allowing for easy use + of different plugin loading schemes. The default manager loads + builtin plugins, 0.10 plugins under the setuptools entrypoint + nose.plugins.0-10 and provides limited support for legacy plugins + loaded under the entrypoint nose.plugins. +- Added plugin hooks in all phases of discovery, running and description. +- Converted several formely core features to plugins: output capture, + assert introspection, pdb, and skipped and deprecated test support. +- Added id plugin that allows for easier specification of tests on the + command line. +- Added ErrorClassPlugin base class to allow for easier authoring of + plugins that handle errors, like the builtin skipped and deprecated + test plugins. +- Added support for loading doctests from non-module files for all + supported python versions. +- Added score property to plugins to allow plugins to execute in a + defined order (higher score execute first). +- Expanded nose's own test suite to include a variety of functional tests. +- Fixed many bugs. + +0.9.3 + +- Added support for user configuration files. Thanks to Antoine Pitrou for the + patch. +- Fixed bug that caused profiler plugin to leak 0-byte temp files. Thanks to + Antoine Pitrou for the patch. +- Made usage of temp files in profiler plugin more sensible. Thanks to Michael + Sclenker for the bug report. +- Fixed bug that stopped loading of twisted TestCase tests. Thanks to Kumar + McMillan for the bug report. +- Corrected man page location. Thanks to luke macken for the bug report and + patch. +- Added with_setup to nose.tools.__all__. Thanks to Allen Bierbaum for the bug + report. +- Altered plugin loading so that builtin plugins can be loaded without + setuptools. Thanks to Allen Bierbaum for the suggestion. +- Fixed a bug in the doctests plugin that caused an error when multiple + exclude arguments were specified. Thanks to mbeachy for the bug report and + patch. + +0.9.2 + +- Added nosetests setuptools command. Now you can run python setup.py + nosetests and have access to all nose features and plugins. Thanks to James + Casbon for the patch. +- Added make_decorator function to nose.tools. Used to construct decorator + functions that are well-behaved and preserve as much of the original + function's metadata as possible. Thanks to Antoine Pitrou for the patch. +- Added nose.twistedtools, contributed by Antoine Pitrou. This module adds + @deferred decorator that makes it simple to write deferred tests, with or + without timeouts. +- Added monkeypatch to doctests that keeps doctest from stepping on coverage + when the two plugins are used together. Thanks to David Avraamides for the + bug report. +- Added isolation plugin. Use this plugin to automatically restore sys.modules + after each test module or package. Thanks to Michal Kwiatkowski for the + feature request. +- Fixed bug where -vvvv turned off verbose logging instead of making it even + more verbose. Thanks to Ian Bicking for the bug report. +- Fixed bug where assert inspection would fail when the trailing """ of a + docstring was one of the inspected lines. Thanks to cito at online dot de + for the bug report. +- Updated attrib plugin to allow selection of test methods by attributes of + the test case class. Thanks to Jason Hildebrand for the patch. +- Improved compatibility with python 2.2. Thanks to Chad Whitacre for the + patch. +- Fixed bug in handling of options from setup.cfg. Thanks to Kumar McMillan for + the patch. +- Fixed bug in generator methods, where a generator method using an inline + funciton would result in an AttributeError. Thanks to Antoine Pitrou for the + bug report. +- Updated coverage plugin to ignore lines tagged with #pragma: no cover, + matching the behavior of coverage.py on the command line. Thanks to Bill + Zingler for the bug report. +- Added a man page for nosetests. Thanks to Gustavo Noronha Silva for the + request and providing an example. + +0.9.1 + +- New function nose.runmodule() finds and runs tests only in a + single module, which defaults to __main__ (like unittest.main() or + doctest.runmodule()). Thanks Greg Wilson for the suggestion. +- Multiple -w (--where) arguments can now be used in one command line, + to find and run tests in multiple locations. Thanks Titus Brown for + the suggestion. +- Multiple --include and --exclude arguments are now accepted in one command + line. Thanks Michal Kwiatkowski for the feature request. +- Coverage will now include modules not imported by any test when + using the new --cover-inclusive switch. Thanks James Casbon for the + patch. +- module:TestClass test selections now properly select all tests in the test + class. +- startTest and stopTest are now called in plugins at the beginning and end of + test suites, including test modules, as well as individual tests. Thanks + Michal Kwiatkowski for the suggestion. +- Fix bug in test selection when run as ``python setup.py test``: 'test' was + passing through and being used as the test name selection. Thanks Kumar + McMillan for the bug report. +- Fix bug in handling of -x/--stop option where the test run would stop on + skipped or deprecated tests. Thanks Kumar McMillan for the bug report. +- Fix bug in loading tests from projects with layouts that place modules in + /lib or /src dirs and tests in a parallel /tests dir. +- Fix bug in python version detection. Thanks Kevin Dangoor for the bug report + and fix. +- Fix log message in selector that could raise IndexError. Thanks Kumar + McMillan for the bug report and patch. +- Fix bug in handling doctest extension arguments specified in environment and + on command line. Thanks Ian Bicking for the bug report. +- Fix bug in running fixtures (setup/teardown) that are not functions, and + report a better error message when a fixture is not callable. Thanks Ian + Bicking for the bug report. + +0.9.0 + +- More unit tests and better test coverage. Numerous bugfixes deriving from + same. +- Make --exe option do what it says, and turn it on by default on + Windows. Add --noexe option so windows users can turn if off.Thanks + richard at artsalliancemedia dot com for the bug reports. +- Handle a working directory that happens to be in the middle of a package + more gracefully. Thanks Max Ischenko for the bug report and test case. +- Fix bugs in test name comparison when a test module is specified whose name + overlaps that of a non-test module. Thanks Max Ischenko for the bug report + and test case. +- Fix warning spam when a non-existent test file is requested on the command + line. Thanks Max Ischenko for the bug report. + +0.9.0b2 + +- Allow --debug to set any logger to DEBUG. Thanks to casbon at gmail dot com + for the patch. +- Fix doctest help, which was missing notes about the environment variables + that it accepts. Thanks to Kumar McMillan for the patch. +- Restore sys.stdout after run() in nose.core. Thanks to Titus Brown for the + bug report. +- Correct handling of trailing comma in attrib plugin args. Thanks Titus Brown + for the patch. + +0.9.0b1 + +- Fix bug in handling of OR conditions in attrib plugin. Thanks to Titus + Brown for the bug report. +- Fix bug in nose.importer that would cause an attribute error when a local + module shadowed a builtin, or other object in sys.modules, without a + __file__ attribute. Thanks to casbon at gmail dot com for the bug report. +- Fix bug in nose.tools decorators that would cause decorated tests to appear + with incorrect names in result output. + +0.9.0a2 + +- In TestLoader, use inspect's isfunction() and ismethod() to filter functions + and methods, instead of callable(). Thanks to Kumar McMillan for reporting + the bug. +- Fix doctest plugin: return an empty iterable when no tests are found in a + directory instead of None. Thanks to Kumar McMillan for the bug report and + patch. +- Ignore executable python modules, unless run with --exe file. This is a + partial defense against nose causing trouble by loading python modules that + are not import-safe. The full defense: don't write modules that aren't + import safe! +- Catch and warn about errors on plugin load instead of dying. +- Renamed builtin profile module from nose.plugins.profile to + nose.plugins.prof to avoid shadowing stdlib profile.py module. + +0.9.0a1 + +- Add support for plugins, with hooks for selecting, loading and reporting on + tests. Doctest and coverage are now plugins. +- Add builtin plugins for profiling with hotshot, selecting tests by + attribute (contributed by Mika Eloranta), and warning of missed tests + specified on command line. +- Change command line test selection syntax to match unittest. Thanks to Titus + Brown for the suggestion. +- Option to drop into pdb on error or failure. +- Option to stop running on first error or failure. Thanks to Kevin Dangoor + for the suggestion. +- Support for doctests in files other than python modules (python 2.4 only) +- Reimplement base test selection as single self-contained class. +- Reimplement test loading as unittest-compatible TestLoader class. +- Remove all monkeypatching. +- Reimplement output capture and assert introspection support in + unittest-compatible Result class. +- Better support for multiline constructions in assert introspection. +- More context output with assert introspections. +- Refactor setuptools test command support to use proxied result, which + enables output capture and assert introspection support without + monkeypatching. Thanks to Philip J. Eby for the suggestion and skeleton + implementation. +- Add support for generators in test classes. Thanks to Jay Parlar for the + suggestion and patch. +- Add nose.tools package with some helpful test-composition functions and + decorators, including @raises, contributed by Scot Doyle. +- Reimplement nose.main (TestProgram) to have unittest-compatible signature. +- All-new import path handling. You can even turn it off! (If you don't, + nose will ensure that all directories from which it imports anything are on + sys.path before the import.) +- Logging package used for verbose logging. +- Support for skipped and deprecated tests. +- Configuration is no longer global. + +0.8.7 + +- Add support for py.test-style test generators. Thanks to Jay Parlar for + the suggestion. +- Fix bug in doctest discovery. Thanks to Richard Cooper for the bug report. +- Fix bug in output capture being appended to later exceptions. Thanks to + Titus Brown for the patch that uncovered the bug. +- Fix bug(?) in Exception patch that caused masked hasattr/__getattr__ loops + to either become actual infinite loops, or at least take so long to finally + error out that they might as well be infinite. +- Add -m option to restrict test running to only tests in a particular package + or module. Like the -f option, -m does not restrict test *loading*, only + test *execution*. +- When loading and running a test module, ensure that the module's path is in + sys.path for the duration of the run, not just while importing the module. +- Add id() method to all callable test classes, for greater unittest + compatibility. + +0.8.6 + +- Fix bug with coverage output when sys.modules contains entries without + __file__ attributes +- Added -p (--cover-packages) switch that may be used to restrict coverage + report to modules in the indicated package(s) + +0.8.5 + +- Output capture and verbose assertion errors now work when run like + 'python setup.py test', as advertised. +- Code coverage improvements: now coverage will be output for all modules + imported by any means that were not in sys.modules at the start of the test + run. By default, test modules will be excluded from the coverage report, but + you can include them with the -t (--cover-tests) option. + +0.8.4 + +- Fix bugs in handling of setup/teardown fixtures that could cause TypeError + exceptions in fixtures to be silently ignored, or multiple fixtures of the + same type to run. Thanks to Titus Brown for the bug report. + +0.8.3 + +- Add -V (--version) switch to nosetests +- Fix bug where sys.path would not be set up correctly when running some + tests, producing spurious import errors (Thanks to Titus Brown and Mike + Thomson for the bug reports) +- For test classses not derived from unittest.TestCase, output (module.Class) + "doc string" as test description, when method doc string is available + (Thanks to David Keeney for the suggestion, even if this isn't quite what he + meant) + +0.8.2 + +- Revise import to bypass sys.path and manipulate sys.modules more + intelligently, ensuring that the test module we think we are loading is the + module we actually load, and that modules loaded by other imports are not + reloaded without cause +- Allow test directories inside of packages. Formerly directories matching + testMatch but lacking an __init__.py would cause an ImportError when located + inside of packages +- Fix bugs in different handling of -f switch in combination with -w and -o + +0.8.1 + +- Fix bug in main() that resulted in incorrect exit status for nosetests + script when tests fail +- Add missing test files to MANIFEST.in +- Miscellaneous pylint cleanups + +0.8 + +- Add doctest support +- Add optional code coverage support, using Ned Batchelder's coverage.py; + activate with --coverage switch or NOSE_COVERAGE environment variable +- More informative error message on import error +- Fix bug where module setup could be called twice and teardown skipped + for certain setup method names. +- main() returns success value, does not exit. run_exit() added to support + old behavior; nosetests script now calls nose.run_exit() + +0.7.5 + +- Fix bus error on exit +- Discover tests inside of non-TestCase classes that match testMatch +- Reorganize selftest: now selftest tests the output of a full nose run +- Add test_with_setup.py contributed by Kumar McMillan + +0.7.2 + +- Refactor and correct bugs in discovery and test loading +- Reorganize and expand documentation +- Add -f (run this test file only) switch + +0.7.1 + +- Bugfix release: test files in root of working directory were not being + stripped of file extension before import. + +0.7 + +- Change license to LGPL +- Major rework of output capture and assert introspection +- Improve test discovery: now finds tests in packages +- Replace -n switch ('no cwd') with -w switch ('look here') + +0.6 + +- New nosetests script +- Allow specification of names on command line that are loadable but not + directly loadable as modules (eg nosetests -o path/to/tests.py) +- Add optional py.test-like assert introspection. Thanks to Kevin Dangoor + for the suggestion. +- Improvements to selftest + +0.5.1 + +- Increased compatibility with python 2.3 (and maybe earlier) +- Increased compatibility with tests written for py.test: now calls + module.setup_module(module) if module.setup_module() fails + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..6bee1ee --- /dev/null +++ b/NEWS @@ -0,0 +1,5 @@ +1.0! +---- + +nose version 1.0 adds support for python 3. The thanks of a grateful +nation go out to Alex Stewart, aka foogod, for all of his great work. diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..9e816f2 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,38 @@ +Metadata-Version: 1.0 +Name: nose +Version: 1.1.2 +Summary: nose extends unittest to make testing easier +Home-page: http://readthedocs.org/docs/nose/ +Author: Jason Pellerin +Author-email: jpellerin+nose@gmail.com +License: GNU LGPL +Description: nose extends the test loading and running features of unittest, making + it easier to write, find and run tests. + + By default, nose will run tests in files or directories under the current + working directory whose names include "test" or "Test" at a word boundary + (like "test_this" or "functional_test" or "TestClass" but not + "libtest"). Test output is similar to that of unittest, but also includes + captured stdout output from failing tests, for easy print-style debugging. + + These features, and many more, are customizable through the use of + plugins. Plugins included with nose provide support for doctest, code + coverage and profiling, flexible attribute-based test selection, + output capture and more. More information about writing plugins may be + found on in the nose API documentation, here: + http://somethingaboutorange.com/mrl/projects/nose/ + + If you have recently reported a bug marked as fixed, or have a craving for + the very latest, you may want the unstable development version instead: + http://bitbucket.org/jpellerin/nose/get/tip.gz#egg=nose-dev + +Keywords: test unittest doctest automatic discovery +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Topic :: Software Development :: Testing diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..1b578da --- /dev/null +++ b/README.txt @@ -0,0 +1,482 @@ + +Basic usage +*********** + +Use the nosetests script (after installation by setuptools): + + nosetests [options] [(optional) test files or directories] + +In addition to passing command-line options, you may also put +configuration options in a .noserc or nose.cfg file in your home +directory. These are standard .ini-style config files. Put your +nosetests configuration in a [nosetests] section, with the -- prefix +removed: + + [nosetests] + verbosity=3 + with-doctest=1 + +There are several other ways to use the nose test runner besides the +*nosetests* script. You may use nose in a test script: + + import nose + nose.main() + +If you don't want the test script to exit with 0 on success and 1 on +failure (like unittest.main), use nose.run() instead: + + import nose + result = nose.run() + +*result* will be true if the test run succeeded, or false if any test +failed or raised an uncaught exception. Lastly, you can run nose.core +directly, which will run nose.main(): + + python /path/to/nose/core.py + +Please see the usage message for the nosetests script for information +about how to control which tests nose runs, which plugins are loaded, +and the test output. + + +Extended usage +============== + +nose collects tests automatically from python source files, +directories and packages found in its working directory (which +defaults to the current working directory). Any python source file, +directory or package that matches the testMatch regular expression (by +default: *(?:^|[b_.-])[Tt]est)* will be collected as a test (or source +for collection of tests). In addition, all other packages found in the +working directory will be examined for python source files or +directories that match testMatch. Package discovery descends all the +way down the tree, so package.tests and package.sub.tests and +package.sub.sub2.tests will all be collected. + +Within a test directory or package, any python source file matching +testMatch will be examined for test cases. Within a test module, +functions and classes whose names match testMatch and TestCase +subclasses with any name will be loaded and executed as tests. Tests +may use the assert keyword or raise AssertionErrors to indicate test +failure. TestCase subclasses may do the same or use the various +TestCase methods available. + + +Selecting Tests +--------------- + +To specify which tests to run, pass test names on the command line: + + nosetests only_test_this.py + +Test names specified may be file or module names, and may optionally +indicate the test case to run by separating the module or file name +from the test case name with a colon. Filenames may be relative or +absolute. Examples: + + nosetests test.module + nosetests another.test:TestCase.test_method + nosetests a.test:TestCase + nosetests /path/to/test/file.py:test_function + +You may also change the working directory where nose looks for tests +by using the -w switch: + + nosetests -w /path/to/tests + +Note, however, that support for multiple -w arguments is now +deprecated and will be removed in a future release. As of nose 0.10, +you can get the same behavior by specifying the target directories +*without* the -w switch: + + nosetests /path/to/tests /another/path/to/tests + +Further customization of test selection and loading is possible +through the use of plugins. + +Test result output is identical to that of unittest, except for the +additional features (error classes, and plugin-supplied features such +as output capture and assert introspection) detailed in the options +below. + + +Configuration +------------- + +In addition to passing command-line options, you may also put +configuration options in your project's *setup.cfg* file, or a .noserc +or nose.cfg file in your home directory. In any of these standard +.ini-style config files, you put your nosetests configuration in a +``[nosetests]`` section. Options are the same as on the command line, +with the -- prefix removed. For options that are simple switches, you +must supply a value: + + [nosetests] + verbosity=3 + with-doctest=1 + +All configuration files that are found will be loaded and their +options combined. You can override the standard config file loading +with the ``-c`` option. + + +Using Plugins +------------- + +There are numerous nose plugins available via easy_install and +elsewhere. To use a plugin, just install it. The plugin will add +command line options to nosetests. To verify that the plugin is +installed, run: + + nosetests --plugins + +You can add -v or -vv to that command to show more information about +each plugin. + +If you are running nose.main() or nose.run() from a script, you can +specify a list of plugins to use by passing a list of plugins with the +plugins keyword argument. + + +0.9 plugins +----------- + +nose 1.0 can use SOME plugins that were written for nose 0.9. The +default plugin manager inserts a compatibility wrapper around 0.9 +plugins that adapts the changed plugin api calls. However, plugins +that access nose internals are likely to fail, especially if they +attempt to access test case or test suite classes. For example, +plugins that try to determine if a test passed to startTest is an +individual test or a suite will fail, partly because suites are no +longer passed to startTest and partly because it's likely that the +plugin is trying to find out if the test is an instance of a class +that no longer exists. + + +0.10 and 0.11 plugins +--------------------- + +All plugins written for nose 0.10 and 0.11 should work with nose 1.0. + + +Options +------- + +-V, --version + + Output nose version and exit + +-p, --plugins + + Output list of available plugins and exit. Combine with higher + verbosity for greater detail + +-v=DEFAULT, --verbose=DEFAULT + + Be more verbose. [NOSE_VERBOSE] + +--verbosity=VERBOSITY + + Set verbosity; --verbosity=2 is the same as -v + +-q=DEFAULT, --quiet=DEFAULT + + Be less verbose + +-c=FILES, --config=FILES + + Load configuration from config file(s). May be specified multiple + times; in that case, all config files will be loaded and combined + +-w=WHERE, --where=WHERE + + Look for tests in this directory. May be specified multiple times. + The first directory passed will be used as the working directory, + in place of the current working directory, which is the default. + Others will be added to the list of tests to execute. [NOSE_WHERE] + +--py3where=PY3WHERE + + Look for tests in this directory under Python 3.x. Functions the + same as 'where', but only applies if running under Python 3.x or + above. Note that, if present under 3.x, this option completely + replaces any directories specified with 'where', so the 'where' + option becomes ineffective. [NOSE_PY3WHERE] + +-m=REGEX, --match=REGEX, --testmatch=REGEX + + Files, directories, function names, and class names that match this + regular expression are considered tests. Default: + (?:^|[b_./-])[Tt]est [NOSE_TESTMATCH] + +--tests=NAMES + + Run these tests (comma-separated list). This argument is useful + mainly from configuration files; on the command line, just pass the + tests to run as additional arguments with no switch. + +-l=DEFAULT, --debug=DEFAULT + + Activate debug logging for one or more systems. Available debug + loggers: nose, nose.importer, nose.inspector, nose.plugins, + nose.result and nose.selector. Separate multiple names with a + comma. + +--debug-log=FILE + + Log debug messages to this file (default: sys.stderr) + +--logging-config=FILE, --log-config=FILE + + Load logging config from this file -- bypasses all other logging + config settings. + +-I=REGEX, --ignore-files=REGEX + + Completely ignore any file that matches this regular expression. + Takes precedence over any other settings or plugins. Specifying + this option will replace the default setting. Specify this option + multiple times to add more regular expressions [NOSE_IGNORE_FILES] + +-e=REGEX, --exclude=REGEX + + Don't run tests that match regular expression [NOSE_EXCLUDE] + +-i=REGEX, --include=REGEX + + This regular expression will be applied to files, directories, + function names, and class names for a chance to include additional + tests that do not match TESTMATCH. Specify this option multiple + times to add more regular expressions [NOSE_INCLUDE] + +-x, --stop + + Stop running tests after the first error or failure + +-P, --no-path-adjustment + + Don't make any changes to sys.path when loading tests [NOSE_NOPATH] + +--exe + + Look for tests in python modules that are executable. Normal + behavior is to exclude executable modules, since they may not be + import-safe [NOSE_INCLUDE_EXE] + +--noexe + + DO NOT look for tests in python modules that are executable. (The + default on the windows platform is to do so.) + +--traverse-namespace + + Traverse through all path entries of a namespace package + +--first-package-wins, --first-pkg-wins, --1st-pkg-wins + + nose's importer will normally evict a package from sys.modules if + it sees a package with the same name in a different location. Set + this option to disable that behavior. + +-a=ATTR, --attr=ATTR + + Run only tests that have attributes specified by ATTR [NOSE_ATTR] + +-A=EXPR, --eval-attr=EXPR + + Run only tests for whose attributes the Python expression EXPR + evaluates to True [NOSE_EVAL_ATTR] + +-s, --nocapture + + Don't capture stdout (any stdout output will be printed + immediately) [NOSE_NOCAPTURE] + +--nologcapture + + Disable logging capture plugin. Logging configurtion will be left + intact. [NOSE_NOLOGCAPTURE] + +--logging-format=FORMAT + + Specify custom format to print statements. Uses the same format as + used by standard logging handlers. [NOSE_LOGFORMAT] + +--logging-datefmt=FORMAT + + Specify custom date/time format to print statements. Uses the same + format as used by standard logging handlers. [NOSE_LOGDATEFMT] + +--logging-filter=FILTER + + Specify which statements to filter in/out. By default, everything + is captured. If the output is too verbose, use this option to + filter out needless output. Example: filter=foo will capture + statements issued ONLY to foo or foo.what.ever.sub but not foobar + or other logger. Specify multiple loggers with comma: + filter=foo,bar,baz. If any logger name is prefixed with a minus, eg + filter=-foo, it will be excluded rather than included. Default: + exclude logging messages from nose itself (-nose). [NOSE_LOGFILTER] + +--logging-clear-handlers + + Clear all other logging handlers + +--with-coverage + + Enable plugin Coverage: Activate a coverage report using Ned + Batchelder's coverage module. [NOSE_WITH_COVERAGE] + +--cover-package=PACKAGE + + Restrict coverage output to selected packages [NOSE_COVER_PACKAGE] + +--cover-erase + + Erase previously collected coverage statistics before run + +--cover-tests + + Include test modules in coverage report [NOSE_COVER_TESTS] + +--cover-inclusive + + Include all python files under working directory in coverage + report. Useful for discovering holes in test coverage if not all + files are imported by the test suite. [NOSE_COVER_INCLUSIVE] + +--cover-html + + Produce HTML coverage information + +--cover-html-dir=DIR + + Produce HTML coverage information in dir + +--pdb + + Drop into debugger on errors + +--pdb-failures + + Drop into debugger on failures + +--no-deprecated + + Disable special handling of DeprecatedTest exceptions. + +--with-doctest + + Enable plugin Doctest: Activate doctest plugin to find and run + doctests in non-test modules. [NOSE_WITH_DOCTEST] + +--doctest-tests + + Also look for doctests in test modules. Note that classes, methods + and functions should have either doctests or non-doctest tests, not + both. [NOSE_DOCTEST_TESTS] + +--doctest-extension=EXT + + Also look for doctests in files with this extension + [NOSE_DOCTEST_EXTENSION] + +--doctest-result-variable=VAR + + Change the variable name set to the result of the last interpreter + command from the default '_'. Can be used to avoid conflicts with + the _() function used for text translation. + [NOSE_DOCTEST_RESULT_VAR] + +--doctest-fixtures=SUFFIX + + Find fixtures for a doctest file in module with this name appended + to the base name of the doctest file + +--with-isolation + + Enable plugin IsolationPlugin: Activate the isolation plugin to + isolate changes to external modules to a single test module or + package. The isolation plugin resets the contents of sys.modules + after each test module or package runs to its state before the + test. PLEASE NOTE that this plugin should not be used with the + coverage plugin, or in any other case where module reloading may + produce undesirable side-effects. [NOSE_WITH_ISOLATION] + +-d, --detailed-errors, --failure-detail + + Add detail to error output by attempting to evaluate failed asserts + [NOSE_DETAILED_ERRORS] + +--with-profile + + Enable plugin Profile: Use this plugin to run tests using the + hotshot profiler. [NOSE_WITH_PROFILE] + +--profile-sort=SORT + + Set sort order for profiler output + +--profile-stats-file=FILE + + Profiler stats file; default is a new temp file on each run + +--profile-restrict=RESTRICT + + Restrict profiler output. See help for pstats.Stats for details + +--no-skip + + Disable special handling of SkipTest exceptions. + +--with-id + + Enable plugin TestId: Activate to add a test id (like #1) to each + test name output. Activate with --failed to rerun failing tests + only. [NOSE_WITH_ID] + +--id-file=FILE + + Store test ids found in test runs in this file. Default is the file + .noseids in the working directory. + +--failed + + Run the tests that failed in the last test run. + +--processes=NUM + + Spread test run among this many processes. Set a number equal to + the number of processors or cores in your machine for best results. + [NOSE_PROCESSES] + +--process-timeout=SECONDS + + Set timeout for return of results from each test runner process. + [NOSE_PROCESS_TIMEOUT] + +--process-restartworker + + If set, will restart each worker process once their tests are done, + this helps control memory leaks from killing the system. + [NOSE_PROCESS_RESTARTWORKER] + +--with-xunit + + Enable plugin Xunit: This plugin provides test results in the + standard XUnit XML format. [NOSE_WITH_XUNIT] + +--xunit-file=FILE + + Path to xml file to store the xunit report in. Default is + nosetests.xml in the working directory [NOSE_XUNIT_FILE] + +--all-modules + + Enable plugin AllModules: Collect tests from all python modules. + [NOSE_ALL_MODULES] + +--collect-only + + Enable collect-only: Collect and output test names only, don't run + any tests. [COLLECT_ONLY] diff --git a/bin/nosetests b/bin/nosetests new file mode 100755 index 0000000..36e0ee9 --- /dev/null +++ b/bin/nosetests @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +from nose import main + +if __name__ == '__main__': + main() diff --git a/distribute_setup.py b/distribute_setup.py new file mode 100644 index 0000000..3ea2e66 --- /dev/null +++ b/distribute_setup.py @@ -0,0 +1,485 @@ +#!python +"""Bootstrap distribute installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from distribute_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import os +import sys +import time +import fnmatch +import tempfile +import tarfile +from distutils import log + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +try: + import subprocess + + def _python_cmd(*args): + args = (sys.executable,) + args + return subprocess.call(args) == 0 + +except ImportError: + # will be used for python 2.3 + def _python_cmd(*args): + args = (sys.executable,) + args + # quoting arguments if windows + if sys.platform == 'win32': + def quote(arg): + if ' ' in arg: + return '"%s"' % arg + return arg + args = [quote(arg) for arg in args] + return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 + +DEFAULT_VERSION = "0.6.14" +DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" +SETUPTOOLS_FAKED_VERSION = "0.6c11" + +SETUPTOOLS_PKG_INFO = """\ +Metadata-Version: 1.0 +Name: setuptools +Version: %s +Summary: xxxx +Home-page: xxx +Author: xxx +Author-email: xxx +License: xxx +Description: xxx +""" % SETUPTOOLS_FAKED_VERSION + + +def _install(tarball): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # installing + log.warn('Installing Distribute') + if not _python_cmd('setup.py', 'install'): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') + finally: + os.chdir(old_wd) + + +def _build_egg(egg, tarball, to_dir): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # building an egg + log.warn('Building a Distribute egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + + finally: + os.chdir(old_wd) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +def _do_download(version, download_base, to_dir, download_delay): + egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + tarball = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, tarball, to_dir) + sys.path.insert(0, egg) + import setuptools + setuptools.bootstrap_install_from = egg + + +def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, download_delay=15, no_fake=True): + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + was_imported = 'pkg_resources' in sys.modules or \ + 'setuptools' in sys.modules + try: + try: + import pkg_resources + if not hasattr(pkg_resources, '_distribute'): + if not no_fake: + _fake_setuptools() + raise ImportError + except ImportError: + return _do_download(version, download_base, to_dir, download_delay) + try: + pkg_resources.require("distribute>="+version) + return + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] + if was_imported: + sys.stderr.write( + "The required version of distribute (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U distribute'." + "\n\n(Currently using %r)\n" % (version, e.args[0])) + sys.exit(2) + else: + del pkg_resources, sys.modules['pkg_resources'] # reload ok + return _do_download(version, download_base, to_dir, + download_delay) + except pkg_resources.DistributionNotFound: + return _do_download(version, download_base, to_dir, + download_delay) + finally: + if not no_fake: + _create_fake_setuptools_pkg_info(to_dir) + +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, delay=15): + """Download distribute from a specified location and return its filename + + `version` should be a valid distribute version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download + attempt. + """ + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + try: + from urllib.request import urlopen + except ImportError: + from urllib2 import urlopen + tgz_name = "distribute-%s.tar.gz" % version + url = download_base + tgz_name + saveto = os.path.join(to_dir, tgz_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + log.warn("Downloading %s", url) + src = urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = src.read() + dst = open(saveto, "wb") + dst.write(data) + finally: + if src: + src.close() + if dst: + dst.close() + return os.path.realpath(saveto) + +def _no_sandbox(function): + def __no_sandbox(*args, **kw): + try: + from setuptools.sandbox import DirectorySandbox + if not hasattr(DirectorySandbox, '_old'): + def violation(*args): + pass + DirectorySandbox._old = DirectorySandbox._violation + DirectorySandbox._violation = violation + patched = True + else: + patched = False + except ImportError: + patched = False + + try: + return function(*args, **kw) + finally: + if patched: + DirectorySandbox._violation = DirectorySandbox._old + del DirectorySandbox._old + + return __no_sandbox + +def _patch_file(path, content): + """Will backup the file then patch it""" + existing_content = open(path).read() + if existing_content == content: + # already patched + log.warn('Already patched.') + return False + log.warn('Patching...') + _rename_path(path) + f = open(path, 'w') + try: + f.write(content) + finally: + f.close() + return True + +_patch_file = _no_sandbox(_patch_file) + +def _same_content(path, content): + return open(path).read() == content + +def _rename_path(path): + new_name = path + '.OLD.%s' % time.time() + log.warn('Renaming %s into %s', path, new_name) + os.rename(path, new_name) + return new_name + +def _remove_flat_installation(placeholder): + if not os.path.isdir(placeholder): + log.warn('Unkown installation at %s', placeholder) + return False + found = False + for file in os.listdir(placeholder): + if fnmatch.fnmatch(file, 'setuptools*.egg-info'): + found = True + break + if not found: + log.warn('Could not locate setuptools*.egg-info') + return + + log.warn('Removing elements out of the way...') + pkg_info = os.path.join(placeholder, file) + if os.path.isdir(pkg_info): + patched = _patch_egg_dir(pkg_info) + else: + patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) + + if not patched: + log.warn('%s already patched.', pkg_info) + return False + # now let's move the files out of the way + for element in ('setuptools', 'pkg_resources.py', 'site.py'): + element = os.path.join(placeholder, element) + if os.path.exists(element): + _rename_path(element) + else: + log.warn('Could not find the %s element of the ' + 'Setuptools distribution', element) + return True + +_remove_flat_installation = _no_sandbox(_remove_flat_installation) + +def _after_install(dist): + log.warn('After install bootstrap.') + placeholder = dist.get_command_obj('install').install_purelib + _create_fake_setuptools_pkg_info(placeholder) + +def _create_fake_setuptools_pkg_info(placeholder): + if not placeholder or not os.path.exists(placeholder): + log.warn('Could not find the install location') + return + pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) + setuptools_file = 'setuptools-%s-py%s.egg-info' % \ + (SETUPTOOLS_FAKED_VERSION, pyver) + pkg_info = os.path.join(placeholder, setuptools_file) + if os.path.exists(pkg_info): + log.warn('%s already exists', pkg_info) + return + + log.warn('Creating %s', pkg_info) + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + + pth_file = os.path.join(placeholder, 'setuptools.pth') + log.warn('Creating %s', pth_file) + f = open(pth_file, 'w') + try: + f.write(os.path.join(os.curdir, setuptools_file)) + finally: + f.close() + +_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info) + +def _patch_egg_dir(path): + # let's check if it's already patched + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + if os.path.exists(pkg_info): + if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): + log.warn('%s already patched.', pkg_info) + return False + _rename_path(path) + os.mkdir(path) + os.mkdir(os.path.join(path, 'EGG-INFO')) + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + return True + +_patch_egg_dir = _no_sandbox(_patch_egg_dir) + +def _before_install(): + log.warn('Before install bootstrap.') + _fake_setuptools() + + +def _under_prefix(location): + if 'install' not in sys.argv: + return True + args = sys.argv[sys.argv.index('install')+1:] + for index, arg in enumerate(args): + for option in ('--root', '--prefix'): + if arg.startswith('%s=' % option): + top_dir = arg.split('root=')[-1] + return location.startswith(top_dir) + elif arg == option: + if len(args) > index: + top_dir = args[index+1] + return location.startswith(top_dir) + if arg == '--user' and USER_SITE is not None: + return location.startswith(USER_SITE) + return True + + +def _fake_setuptools(): + log.warn('Scanning installed packages') + try: + import pkg_resources + except ImportError: + # we're cool + log.warn('Setuptools or Distribute does not seem to be installed.') + return + ws = pkg_resources.working_set + try: + setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools', + replacement=False)) + except TypeError: + # old distribute API + setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools')) + + if setuptools_dist is None: + log.warn('No setuptools distribution found') + return + # detecting if it was already faked + setuptools_location = setuptools_dist.location + log.warn('Setuptools installation detected at %s', setuptools_location) + + # if --root or --preix was provided, and if + # setuptools is not located in them, we don't patch it + if not _under_prefix(setuptools_location): + log.warn('Not patching, --root or --prefix is installing Distribute' + ' in another location') + return + + # let's see if its an egg + if not setuptools_location.endswith('.egg'): + log.warn('Non-egg installation') + res = _remove_flat_installation(setuptools_location) + if not res: + return + else: + log.warn('Egg installation') + pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') + if (os.path.exists(pkg_info) and + _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): + log.warn('Already patched.') + return + log.warn('Patching...') + # let's create a fake egg replacing setuptools one + res = _patch_egg_dir(setuptools_location) + if not res: + return + log.warn('Patched done.') + _relaunch() + + +def _relaunch(): + log.warn('Relaunching...') + # we have to relaunch the process + # pip marker to avoid a relaunch bug + if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']: + sys.argv[0] = 'setup.py' + args = [sys.executable] + sys.argv + sys.exit(subprocess.call(args)) + + +def _extractall(self, path=".", members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + import copy + import operator + from tarfile import ExtractError + directories = [] + + if members is None: + members = self + + for tarinfo in members: + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 448 # decimal for oct 0700 + self.extract(tarinfo, path) + + # Reverse sort directories. + if sys.version_info < (2, 4): + def sorter(dir1, dir2): + return cmp(dir1.name, dir2.name) + directories.sort(sorter) + directories.reverse() + else: + directories.sort(key=operator.attrgetter('name'), reverse=True) + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError: + e = sys.exc_info()[1] + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + +def main(argv, version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + tarball = download_setuptools() + _install(tarball) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/doc/.static/nose.css b/doc/.static/nose.css new file mode 100644 index 0000000..38602e2 --- /dev/null +++ b/doc/.static/nose.css @@ -0,0 +1,74 @@ +@import url(default.css); + +body { + padding-left: 20px; + background-color: #fff; + font: x-small Georgia,Serif; + font-size/* */:/**/small; + font-size: /**/small; +} + +div.body { border-right: 1px solid #ccc; } + +div.body h1 { margin-top: 0; font-size: 130%; margin-right: 0px; } +div.body h2 { font-size: 120%; } +div.body h3 { font-size: 115%; } +div.body h4 { font-size: 110%; } +div.body h5 { font-size: 106%; } +div.body h6 { font-size: 103%; } + +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + border: none; + background-color: #fff; +} + +.new { + color: #f00; + font-weight: bold; +} + +pre, tt { + background-color: #ffe; +} + +div.body h1.big { + font-family: courier, "courier new", monospace; + font-weight: bold; + font-size: 800%; + background-color: #fff; + margin: 0px -20px 0px -25px; + padding: 0px 0px 0px 10px; + border: none; + color: #000; +} + +div.body h2.big { + font-weight: bold; + margin: -10px -20px 0px -20px; +} + +p.big { + font-family: 'Trebuchet MS', sans-serif; + font-weight: bold; + font-size: 200%; + margin-left: -20px; + padding-left: 10px; +} + +span.biglink { + font-size: 1.3em +} + +table.contentstable td { + vertical-align: top + padding-left: 0px; + padding-right: 10px; +} + +table.contentstable td p { + text-align: left; +} \ No newline at end of file diff --git a/doc/.templates/index.html b/doc/.templates/index.html new file mode 100644 index 0000000..affb29e --- /dev/null +++ b/doc/.templates/index.html @@ -0,0 +1,52 @@ +

nose

+

is nicer testing for python

+ +

nose extends unittest to make testing easier.

+ +{{ body }} + +

Documentation

+ + + + + + + + + + + + + + +
+ + Testing with nose +

Find out how to write, find and run tests using nose.
+ More >

+
+ + Developing with nose +

Find out how to write your own plugins, and about nose + internals.
More >

+
+ + News +

What's new in this release?
+ More >

+
+ + Further reading +

Plugin recipes and usage examples, trivia and other + uncategorizable items.
+ More >

+
+ Indices and tables + +
diff --git a/doc/.templates/indexsidebar.html b/doc/.templates/indexsidebar.html new file mode 100644 index 0000000..cc1aea8 --- /dev/null +++ b/doc/.templates/indexsidebar.html @@ -0,0 +1,56 @@ +

Download

+ + +

Install

+
    +
  • This release:
    + easy_install nose=={{ release }}
  • +
  • Development (unstable):
    + easy_install nose==dev +
  • +
+ +

Community

+
    +
  • + Announcement list +
    • Sign up to receive email announcements + of new releases
    +
  • +
  • + Users' discussion list +
    • Talk about using nose. Get help. Give help!
    +
  • +
  • + TIP list +
    • The Testing In Python list features wide-ranging + discussions of all topics of interest to python + testers.
    +
  • +
+ +

Tracker

+
  • Report bugs, request features, wik the wiki, browse source.
+ +

Other links

+ + +

Older versions

+ diff --git a/doc/.templates/layout.html b/doc/.templates/layout.html new file mode 100644 index 0000000..e49c1e9 --- /dev/null +++ b/doc/.templates/layout.html @@ -0,0 +1,16 @@ +{% extends "!layout.html" %} + +{%- block relbar1 %} +{% if pagename != 'index' %}{{ super() }}{% endif %} +{% endblock %} + +{%- block footer %} +{{ super() }} + + +{%- endblock %} diff --git a/doc/.templates/page.html b/doc/.templates/page.html new file mode 100644 index 0000000..3416437 --- /dev/null +++ b/doc/.templates/page.html @@ -0,0 +1,7 @@ +{% extends "!page.html" %} +{% block body %} +{% if pagename == 'index' %} +{% include "index.html" %} +{% else %} +{{ super() }} +{% endif %}{% endblock %} diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..2a1d23b --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,89 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html web pickle htmlhelp latex changes linkcheck man readme + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview over all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + +clean: + -rm -rf .build/* + +html: + mkdir -p .build/html .build/doctrees + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html + @echo + @echo "Build finished. The HTML pages are in .build/html." + +pickle: + mkdir -p .build/pickle .build/doctrees + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +web: pickle + +json: + mkdir -p .build/json .build/doctrees + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) .build/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + mkdir -p .build/htmlhelp .build/doctrees + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in .build/htmlhelp." + +latex: + mkdir -p .build/latex .build/doctrees + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex + @echo + @echo "Build finished; the LaTeX files are in .build/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + mkdir -p .build/changes .build/doctrees + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes + @echo + @echo "The overview file is in .build/changes." + +linkcheck: + mkdir -p .build/linkcheck .build/doctrees + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in .build/linkcheck/output.txt." + +man: + mkdir -p .build/man ./build/doctrees + $(SPHINXBUILD) -b manpage $(ALLSPHINXOPTS) .build/man man.rst + cp .build/man/man.man ../nosetests.1 + @echo + @echo "Generated man page copied to ../nosetests.1" + +readme: + mkdir -p .build/text .build/doctrees$ + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) .build/text usage.rst + cp .build/text/usage.txt ../README.txt + @echo + @echo "Updated ../README.txt" diff --git a/doc/api.rst b/doc/api.rst new file mode 100644 index 0000000..b2dd665 --- /dev/null +++ b/doc/api.rst @@ -0,0 +1,20 @@ +nose internals +============== + +.. toctree :: + :maxdepth: 2 + + api/core + api/loader + api/selector + api/config + api/test_cases + api/suite + api/result + api/proxy + api/plugin_manager + api/importer + api/commands + api/twistedtools + api/inspector + api/util diff --git a/doc/api/commands.rst b/doc/api/commands.rst new file mode 100644 index 0000000..c9ae14a --- /dev/null +++ b/doc/api/commands.rst @@ -0,0 +1,2 @@ +.. automodule :: nose.commands + :members: \ No newline at end of file diff --git a/doc/api/config.rst b/doc/api/config.rst new file mode 100644 index 0000000..077cf9b --- /dev/null +++ b/doc/api/config.rst @@ -0,0 +1,5 @@ +Configuration +============= + +.. automodule :: nose.config + :members: \ No newline at end of file diff --git a/doc/api/core.rst b/doc/api/core.rst new file mode 100644 index 0000000..6e58329 --- /dev/null +++ b/doc/api/core.rst @@ -0,0 +1,5 @@ +Test runner and main() +====================== + +.. automodule :: nose.core + :members: diff --git a/doc/api/importer.rst b/doc/api/importer.rst new file mode 100644 index 0000000..956fdb3 --- /dev/null +++ b/doc/api/importer.rst @@ -0,0 +1,5 @@ +Importer +======== + +.. automodule :: nose.importer + :members: \ No newline at end of file diff --git a/doc/api/inspector.rst b/doc/api/inspector.rst new file mode 100644 index 0000000..e204985 --- /dev/null +++ b/doc/api/inspector.rst @@ -0,0 +1,5 @@ +Traceback inspector +=================== + +.. automodule :: nose.inspector + :members: \ No newline at end of file diff --git a/doc/api/loader.rst b/doc/api/loader.rst new file mode 100644 index 0000000..741dd22 --- /dev/null +++ b/doc/api/loader.rst @@ -0,0 +1,2 @@ +.. automodule :: nose.loader + :members: \ No newline at end of file diff --git a/doc/api/plugin_manager.rst b/doc/api/plugin_manager.rst new file mode 100644 index 0000000..5c1e393 --- /dev/null +++ b/doc/api/plugin_manager.rst @@ -0,0 +1,2 @@ +.. automodule :: nose.plugins.manager + :members: \ No newline at end of file diff --git a/doc/api/proxy.rst b/doc/api/proxy.rst new file mode 100644 index 0000000..1802074 --- /dev/null +++ b/doc/api/proxy.rst @@ -0,0 +1,2 @@ +.. automodule :: nose.proxy + :members: \ No newline at end of file diff --git a/doc/api/result.rst b/doc/api/result.rst new file mode 100644 index 0000000..75b110d --- /dev/null +++ b/doc/api/result.rst @@ -0,0 +1,2 @@ +.. automodule :: nose.result + :members: \ No newline at end of file diff --git a/doc/api/selector.rst b/doc/api/selector.rst new file mode 100644 index 0000000..d3de5a4 --- /dev/null +++ b/doc/api/selector.rst @@ -0,0 +1,2 @@ +.. automodule :: nose.selector + :members: \ No newline at end of file diff --git a/doc/api/suite.rst b/doc/api/suite.rst new file mode 100644 index 0000000..9b764b0 --- /dev/null +++ b/doc/api/suite.rst @@ -0,0 +1,2 @@ +.. automodule :: nose.suite + :members: \ No newline at end of file diff --git a/doc/api/test_cases.rst b/doc/api/test_cases.rst new file mode 100644 index 0000000..2508f54 --- /dev/null +++ b/doc/api/test_cases.rst @@ -0,0 +1,8 @@ +Test Cases +========== + +.. automodule :: nose.case + :members: + +.. autoclass :: nose.failure.Failure + :members: diff --git a/doc/api/twistedtools.rst b/doc/api/twistedtools.rst new file mode 100644 index 0000000..584d9c7 --- /dev/null +++ b/doc/api/twistedtools.rst @@ -0,0 +1,2 @@ +.. automodule :: nose.twistedtools + :members: \ No newline at end of file diff --git a/doc/api/util.rst b/doc/api/util.rst new file mode 100644 index 0000000..f4b683e --- /dev/null +++ b/doc/api/util.rst @@ -0,0 +1,5 @@ +Utility functions +================= + +.. automodule :: nose.util + :members: \ No newline at end of file diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..3fb4326 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,237 @@ +# -*- coding: utf-8 -*- +# +# nose documentation build configuration file, created by +# sphinx-quickstart on Thu Mar 26 16:49:00 2009. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If your extensions are in another directory, add it here. If the directory +# is relative to the documentation root, use os.path.abspath to make it +# absolute, like shown here. + +# need to be brutal because of easy_install's pth hacks: +sys.path.insert(0, + os.path.join(os.path.dirname(__file__), '..')) +sys.path.insert(0, os.path.abspath('.')) + + +# General configuration +# --------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', + 'nose.sphinx.pluginopts', 'manbuilder'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['.templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'nose' +copyright = u'2009, Jason Pellerin' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.1' +# The full version, including alpha/beta/rc tags. +release = '1.1.2' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['.build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'trac' + + +# Options for HTML output +# ----------------------- + +# The style sheet to use for HTML and HTML Help pages. A file of that name +# must exist either in Sphinx' static/ path, or in one of the custom paths +# given in html_static_path. +html_style = 'nose.css' + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['.static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +html_sidebars = { + 'index': 'indexsidebar.html' + } + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {'index': 'index.html'} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, the reST sources are included in the HTML build as _sources/. +#html_copy_source = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'nosedoc' + +""" +footerbgcolor (CSS color): Background color for the footer line. +footertextcolor (CSS color): Text color for the footer line. +sidebarbgcolor (CSS color): Background color for the sidebar. +sidebartextcolor (CSS color): Text color for the sidebar. +sidebarlinkcolor (CSS color): Link color for the sidebar. +relbarbgcolor (CSS color): Background color for the relation bar. +relbartextcolor (CSS color): Text color for the relation bar. +relbarlinkcolor (CSS color): Link color for the relation bar. +bgcolor (CSS color): Body background color. +textcolor (CSS color): Body text color. +linkcolor (CSS color): Body link color. +headbgcolor (CSS color): Background color for headings. +headtextcolor (CSS color): Text color for headings. +headlinkcolor (CSS color): Link color for headings. +codebgcolor (CSS color): Background color for code blocks. +codetextcolor (CSS color): Default text color for code blocks, if not set differently by the highlighting style. +bodyfont (CSS font-family): Font for normal text. +headfont (CSS font-family): Font for headings. +""" +html_theme_options = { + 'rightsidebar': 'true', + 'sidebarbgcolor': '#fff', + 'sidebartextcolor': '#20435c', + 'sidebarlinkcolor': '#355f7c', + 'bgcolor': '#fff', + 'codebgcolor': '#ffe', + 'headbgcolor': '#fff', + 'relbarbgcolor': '#fff', + 'relbartextcolor': '#20435c', + 'relbarlinkcolor': '#355f7c', +} + +# the css mostly overrides this: +html_theme = 'default' + +# Options for LaTeX output +# ------------------------ + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', 'nose.tex', ur'nose Documentation', + ur'Jason Pellerin', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/dev': None} diff --git a/doc/contributing.rst b/doc/contributing.rst new file mode 100644 index 0000000..1a74ca8 --- /dev/null +++ b/doc/contributing.rst @@ -0,0 +1,58 @@ +Contributing to nose +==================== + +You'd like to contribute to nose? Great! Now that nose is hosted under +`Mercurial `__, contributing is even easier. + +Get the code! +------------- + +Start by getting a local working copy of nose, either stable, from google code:: + + hg clone http://python-nose.googlecode.com/hg/ nose-stable + +or unstable, from bitbucket:: + + hg clone http://bitbucket.org/jpellerin/nose/ nose-unstable + +If you plan to submit changes back to the core repository, you should set up a +public repository of your own somewhere. `Bitbucket `__ +is a good place to do that. Once you've set up your bitbucket nose repository, +if working from **stable**, pull from your working copy of nose-stable, and push +to bitbucket. That (with occasional merging) will be your normal practice for +keeping your repository up to date. If you're on bitbucket and working from +**unstable**, just **fork** http://bitbucket.org/jpellerin/nose/. + +Running nose's tests +-------------------- + +nose runs its own test suite with `tox +`. You don't have to install tox to run +nose's test suite, but you should, because tox makes it easy to run +all tests on all supported python versions. You'll also need python +2.4, 2.5, 2.6, 2.7, 3.1 and jython installed somewhere in your $PATH. + +Discuss +------- + +Join the `nose developer list +`__ at google groups. It's +low-traffic and mostly signal. + +What to work on? +---------------- + +You can find a list of open issues at nose's `google code repository +`__. If you'd like to +work on an issue, leave a comment on the issue detailing how you plan +to fix it, and where to find the Mercurial repository where you will +publish your changes. + +I have a great idea for a plugin... +----------------------------------- + +Great! :doc:`Write it `. Release it on `pypi +`__. If it gains a large following, and +becomes stable enough to work with nose's 6+ month release cycles, it +may be a good candidate for inclusion in nose's builtin plugins. + diff --git a/doc/developing.rst b/doc/developing.rst new file mode 100644 index 0000000..3302ddf --- /dev/null +++ b/doc/developing.rst @@ -0,0 +1,30 @@ +Developing with nose +==================== + +Get the code +------------ + +The stable branch of nose is hosted at `google code +`__. You should clone this +branch if you're developing a plugin or working on bug fixes for nose:: + + hg clone http://python-nose.googlecode.com/hg/ nose-stable + +The **unstable** branch of nose is hosted at `bitbucket +`__. You should **fork** this branch if +you are developing new features for nose. Then clone your fork, and submit +your changes as a pull request. If you just want to use unstable, you can +clone the branch:: + + hg clone http://bitbucket.org/jpellerin/nose/ nose-unstable + + +Read +---- + +.. toctree :: + :maxdepth: 2 + + plugins + api + contributing diff --git a/doc/doc_tests/test_addplugins/support/test$py.class b/doc/doc_tests/test_addplugins/support/test$py.class new file mode 100644 index 0000000000000000000000000000000000000000..f4dea0b03aaef3375b7a6b45852200243e06b3c6 GIT binary patch literal 2628 zcmbtV-Bue_6#fngOcIBdM%q%K6fDv}fH5fA8bm2Ani5h3Oa;_9Bqw3$WM-V1(8ixj zpQB!Tg`mK)OCP|8a=FiB$SP#g8!s~FKWwpL!t5u$EA#Cj0TrI7;s>{>kE!5TeYBkB z!imgyUPdRnVhBTGh_}i~h%(3ztCs7lcNhk?+Z&hh2IMGW=w|3|Rku_#imqlF5-u}D z%Y}+=?x;HH?0=ogD~LzQ!#5eaT<*A|DT<<8&{fCP7+6NeHCz`AeH2s9G`Ng@ArZp> zF?teKUBXRPv<}^cd=Na~bYU3yQH7RIkBjDXvCaUljZpYAr zQ3^qL9KjevM3l-bsyc&`$nL58s-mk#MOk-k&8V~jqzM6`FoAJV)(M8I0kY$HhFk6H zv!U+jT*4H=G|})*iD4Q9*s#O(GSx2Y=ao`7gLg&h-ec$}5JylceUBNo6wB833Bih6 zwG_j2c){j6SLsYmr>19=vX2l|F9-{cQZkGFP5dvYrIMxBDw^RaPR+7R+f~H-v{jGb zE>*ZSriN_^9}F1HO{Vwes}OhB1@?`6#4qZrbdm+?M65HWwk&~vda67Ey! z=Bj3?rt|p-9uNo({!HME;YA{|9Z<=OI?}B?nXRn=*UlQsD_A;6cYwJ}CTM*4jj)7I zW7ImI$smM?7$1@U74EL6nnA@)v=t?6R6U++i2$49`3PN_Gs6V|ZT74AHO;N(^6MPv%9-9=KT{9Z7 z&9>@0FBTYll~=5=LYSE zMbn@>)QYZYuko@@lb_~llbvDaRd6rTuyg{J#(7U<6j6#{2L#k^8asO7tQ#5D6PY0R z{jW_WAl4y3po#hG-JS-(VYPmi4GC3-;q!h}ElP;3EUaeo+;%7jl+ez7Rj=_7t=fKC zSAqO18yfA{PJpF;a(;PEY!{30%DR+G%ge43yNgVqK(cQlewaU;Vd7oEdk@>q%P4D=!Pb^F6U3_@;dlB4CUpljke8R&m-5C} zzPUels)MC6o(JxjGZ=sez==r)O|fF-$9ELp`r~T~xl{!M`~mR?fMM#dnk>>7(D`WF z$qrWYtjem~;(2x*_1@{BB^xQC@!i5?Z?!RDm@mDH80QfDL@0V0ZX^kWOMoaLAyX+$ SEy@>@r>> import os +>>> support = os.path.join(os.path.dirname(__file__), 'support') +>>> from nose.plugins.plugintest import run_buffered as run +>>> argv = [__file__, '-v', support] # --collect-only +>>> run(argv=argv) +test.test ... ok + +---------------------------------------------------------------------- +Ran 1 test in ...s + +OK + +Without '-v', we get a dot. + +>>> run(argv=[__file__, support]) +. +---------------------------------------------------------------------- +Ran 1 test in ...s + +OK + +The plugin is simple. It captures and wraps the test result output stream and +replaces 'ok' with 'maybe' and '.' with '?'. + +>>> from nose.plugins.base import Plugin +>>> class Maybe(Plugin): +... def setOutputStream(self, stream): +... self.stream = stream +... return self +... def flush(self): +... self.stream.flush() +... def writeln(self, out=""): +... self.write(out + "\n") +... def write(self, out): +... if out == "ok\n": +... out = "maybe\n" +... elif out == ".": +... out = "?" +... self.stream.write(out) + +To activate the plugin, we pass an instance in the addplugins list. + +>>> run(argv=argv + ['--with-maybe'], addplugins=[Maybe()]) +test.test ... maybe + +---------------------------------------------------------------------- +Ran 1 test in ...s + +OK + +>>> run(argv=[__file__, support, '--with-maybe'], addplugins=[Maybe()]) +? +---------------------------------------------------------------------- +Ran 1 test in ...s + +OK + diff --git a/doc/doc_tests/test_allmodules/support/mod$py.class b/doc/doc_tests/test_allmodules/support/mod$py.class new file mode 100644 index 0000000000000000000000000000000000000000..63226274a79b0fe50b87aca00d7f20d8defc60b4 GIT binary patch literal 3239 zcmbtW>r)$56#re4up!;H5c{CCrNPEF5MT?oYNetS!KMX@fGD)OF3C+;*zCsL4K204 z-|yG>Xmv(E`N2<>DrM}b-~FQ;&)sYzGz;U5nc3XCd+vGM-}#+$?_Yoa_6L9-e8&*V zCyy~qq(&^epp=%~dCOF?md%y%drK8C%U zn3ND=kR0wBy2%;BsZ9H{j0oyu2t#6s*Xl`VV33E3CD%9aFzmmuxpNuYAU7a}?F=oo z=7!5=*3~Uj!cK;Uyjn19_X%wwova~GWAVYJ2lR+!T`(*6Kp$4=fK?DJpe<>Np_v6`(zSK7P|J@h1BAg8 z+C^47D0{-7n%8v0N%j)zs+!|-<$|h4(M7KX?pCtE&>i4q3!=$Ta7;L%d^FeQy0SY7 z_cLr$Rns!BaNANlcD5W{gi$PgJO3h*dSGSsPLD~JpJo@Mmmlt|Pg3_AuK zhueZpL$+<%Q9R19y{Iknq03ob65kmnx5(iZ2?-EYjS^V~a5_fp8kEslq0JCO>|d0l zc!FVf5SG&nhiZYkQxsF$oWUiW^$_wnF%rW$j1ngtZsds&BNZ}J2+N(Ku8qdTqjTwR~fV$<~*sD=BWV zq?ne&Rht`Jqp_#Er@L3l`&F+Qs)*B3a#q%#d8lcIK^rR@+)71wdsUAl)ow(bQ+EkSoQS{dANd@?9pN`_7XepT(*|cOV zU{TCo2x!S7k`YVGaa#hl;oVs}Vq89{7fS{|ZCS44+FFU>WKbP>yQqy(e6FezRm)h4 z!Nn!on_V_5dpl8UZ=+h^wqz{hia_i|;sW8cL4~0$0I&^9=x97#TYeK|(-f6gjakAg z4DDopSU0#yNAaL#5)S38YuOV#Z_pu2&851Wp?7_)57N2h1f0(sBa!hMUXS5byg^Ni zTD+m&DH74NGC}nFUzY+wu{uG5cf?ue&E6C3%bR@=K#cL_JGe;rh#|2tqGE}lv6X@G z%rv(hLW7`v_rf;W90|J00upI1;`jo(tHh$&JvB zWvpDBU6?3B2W_F_F|E7&o18;vvR zU$Cod1#RpW?i)+Tk9Msh&BoGF?UG@C$r*o1-LKfUf&(|mB*QoKA0t;{4st_PS#^Ab zW&+_I=?Ga(*XY%IIwDL;E&YpNv!o$(j;EtSC*Hk^-c>ww4F}Wlhb!~zXj(ykI{p~F zs%v=s&-Iq;yof56Be+JEn#gh*AqlM#!V z{`j-rsD?tnLmTZ($FreOXay4?#zIYvSVQ3^^s4Y^*b|05A@hW7RpA-Q6GqkvhkvFx zL?qPi{74#U^gvvJF2DsEV4&zr6BMP8?z)ZEnYeiirB&E#xZJUZs})-fcnL52tK;5U zpg!KZhBqtL7~aC$zWhf@mEc5oM`zdmpM)>aC9Luj$vi+$f}Z3m-o3Gk_v!iY2CJl8 zfSM=mgn+Y^`KYb~IJuc1wV9xEGeI4$leaot#J4o=qS+_3gHJQ~44;e1S$y#i{QFk= literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_allmodules/support/mod.py b/doc/doc_tests/test_allmodules/support/mod.py new file mode 100644 index 0000000..e136d56 --- /dev/null +++ b/doc/doc_tests/test_allmodules/support/mod.py @@ -0,0 +1,5 @@ +def test(): + pass + +def test_fails(): + assert False, "This test fails" diff --git a/doc/doc_tests/test_allmodules/support/mod.pyc b/doc/doc_tests/test_allmodules/support/mod.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17ebf6e33886edca4d43ecc61663689ffea0b28e GIT binary patch literal 435 zcmZ8dO-sZu5S?`OU_m|Ud5=9ctaw)uJ@(Lg(Mt(!77J+;GE+gki9f>M=`V1mEeqR1 zUfz7YylMY;o__t_77Z+);`^QtUNf`+KLBq`)I=1t2~^pJOGg8bGH@~vRGCN=83U|< zUOA^j>^=-xO?}XVM_Whhtl@i?@A4bHABToIKNwe&MMB;BW?>T7#<||NqqC@S48cdz z9OvPa#DKKO``VPZdV%fAS zidA=aO+zV~Hdj{bi?&+jnU;t3U7M?=ysNrAlWciitv}w`<3%^aaJ`L52_Xi_;jXS3 zoFSaZCO2esqAP|lB!+mao`fia{IF`dzIlgXV7t9@8E-(2B8F~;{#J8~HKXWirXk@n zL$q9|=;n^9lg<9unY@B{ln}nj(B*Q+9Zgdd<$|s{uExN!GOppe5a^?ra;Cv$^b3s` z21wA;u<8M7{l*IC$y00p_YE+cGYimZOl^{(D2!jbEMOnugt_J8PHyCcU zug|)=qjL$9MAJkAof5+oh_GRY>t(9lvY%H<-89}6se6y1qd*ctrSuUqY$=wl?Gu9) zw`wVd>F|Qhb*|Exo=Q(mD`lS{s$LKt9HnFy{hRo|pz3gSl$d}r`RHZL;-eTcn3M57J`gc~!q9WEE)woj z>1G=!=OTDOBsBOl!83*ziR^YjB`?~@w8~_+wgRA?HI!GdOpxsWciC*v`0yKH5ue7W zbv~0p3=uItBK#HZuBe(p#Z9zTC2mwbnQMsvpUI5~ZVH-D#K`97GM2F-Zux*YA&@Ma zYKhwtz99Yfn7?zA}d?hmWHF1IRbWVj~Bp_kuL}*^$ZYAwn%`ij{HZe<38IojwQPa6W`(eQ} zC=a!wYuanPtkdMDx!M$Gn0}Sq3p6a9fTeLBiHssjG303BBAXW5WYWf(q>qiRt?Y-N5myTNUTazF{~+*kD)57Daar*#!5 zzp}2;j_m|k`X}d?=OlKq2(PSL2{mH3NEXB8Wm=xOT6KrpUei&p(@HKrT@jRw<;|L1 zq-!yDRQNXvpblj7r1jL683f(t>#P5x&zGn5;dhflMZ|lVyfi zy1PFQWeaO1GEdT!3UmMufQyg>>TDy*kMFF$?Z;PVdRb-y@CPg(0EVew3|r(O;ONOV zlXcd}Eb=1X<8>Ze?)_+7!71#+pF61|SUyNg2~r Ss(dkhirFe<(SM9by1oIWr81`g literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_allmodules/test_allmodules.rst b/doc/doc_tests/test_allmodules/test_allmodules.rst new file mode 100644 index 0000000..b541987 --- /dev/null +++ b/doc/doc_tests/test_allmodules/test_allmodules.rst @@ -0,0 +1,67 @@ +Finding tests in all modules +============================ + +Normally, nose only looks for tests in modules whose names match testMatch. By +default that means modules with 'test' or 'Test' at the start of the name +after an underscore (_) or dash (-) or other non-alphanumeric character. + +If you want to collect tests from all modules, use the ``--all-modules`` +command line argument to activate the :doc:`allmodules plugin +<../../plugins/allmodules>`. + +.. Note :: + + The function :func:`nose.plugins.plugintest.run` reformats test result + output to remove timings, which will vary from run to run, and + redirects the output to stdout. + + >>> from nose.plugins.plugintest import run_buffered as run + +.. + + >>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> argv = [__file__, '-v', support] + +The target directory contains a test module and a normal module. + + >>> support_files = [d for d in os.listdir(support) + ... if not d.startswith('.') + ... and d.endswith('.py')] + >>> support_files.sort() + >>> support_files + ['mod.py', 'test.py'] + +When run without ``--all-modules``, only the test module is examined for tests. + + >>> run(argv=argv) + test.test ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + +When ``--all-modules`` is active, both modules are examined. + + >>> from nose.plugins.allmodules import AllModules + >>> argv = [__file__, '-v', '--all-modules', support] + >>> run(argv=argv, plugins=[AllModules()]) # doctest: +REPORT_NDIFF + mod.test ... ok + mod.test_fails ... FAIL + test.test ... ok + + ====================================================================== + FAIL: mod.test_fails + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + AssertionError: This test fails + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + FAILED (failures=1) + + + diff --git a/doc/doc_tests/test_coverage_html/coverage_html.rst b/doc/doc_tests/test_coverage_html/coverage_html.rst new file mode 100644 index 0000000..95f9e8a --- /dev/null +++ b/doc/doc_tests/test_coverage_html/coverage_html.rst @@ -0,0 +1,57 @@ +Generating HTML Coverage with nose +---------------------------------- + +.. Note :: + + HTML coverage requires Ned Batchelder's `coverage.py`_ module. +.. + +Console coverage output is useful but terse. For a more browseable view of +code coverage, the coverage plugin supports basic HTML coverage output. + +.. hide this from the actual documentation: + >>> from nose.plugins.plugintest import run_buffered as run + >>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> cover_html_dir = os.path.join(support, 'cover') + >>> cover_file = os.path.join(os.getcwd(), '.coverage') + >>> if os.path.exists(cover_file): + ... os.unlink(cover_file) + ... + + +The console coverage output is printed, as normal. + + >>> from nose.plugins.cover import Coverage + >>> cover_html_dir = os.path.join(support, 'cover') + >>> run(argv=[__file__, '-v', '--with-coverage', '--cover-package=blah', + ... '--cover-html', '--cover-html-dir=' + cover_html_dir, + ... support, ], + ... plugins=[Coverage()]) # doctest: +REPORT_NDIFF + test_covered.test_blah ... hi + ok + + Name Stmts Miss Cover Missing + ------------------------------------- + blah 4 1 75% 6 + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + +The html coverage reports are saved to disk in the directory specified by the +``--cover-html-dir`` option. In that directory you'll find ``index.html`` +which links to a detailed coverage report for each module in the report. The +detail pages show the module source, colorized to indicated which lines are +covered and which are not. There is an example of this HTML output in the +`coverage.py`_ docs. + +.. hide this from the actual documentation: + >>> os.path.exists(cover_file) + True + >>> os.path.exists(os.path.join(cover_html_dir, 'index.html')) + True + >>> os.path.exists(os.path.join(cover_html_dir, 'blah.html')) + True + +.. _`coverage.py`: http://nedbatchelder.com/code/coverage/ diff --git a/doc/doc_tests/test_coverage_html/coverage_html.rst.py3.patch b/doc/doc_tests/test_coverage_html/coverage_html.rst.py3.patch new file mode 100644 index 0000000..f325a01 --- /dev/null +++ b/doc/doc_tests/test_coverage_html/coverage_html.rst.py3.patch @@ -0,0 +1,16 @@ +--- coverage_html.rst.orig 2010-08-31 23:13:33.000000000 -0700 ++++ coverage_html.rst 2010-08-31 23:14:25.000000000 -0700 +@@ -78,11 +78,11 @@ + +
+
1
def dostuff():
+-
2
    print 'hi'
++
2
    print('hi')
+ + +
5
def notcov():
+-
6
    print 'not covered'
++
6
    print('not covered')
+ +
+ diff --git a/doc/doc_tests/test_coverage_html/coverage_html_fixtures$py.class b/doc/doc_tests/test_coverage_html/coverage_html_fixtures$py.class new file mode 100644 index 0000000000000000000000000000000000000000..79f80150725b572813a21ca4b3c2b43e9ec30a77 GIT binary patch literal 5318 zcmbtY3v?7$8UAiIu#@c+mgN-mL!EjwFIjaNnK0=B?to5>9Cn33%fJx%xoYi zR;#x7tdD9%tF2n0tuGqdB^Fz>KC0FFe!t)E_uHe^e)rDol4N&t@VMv9-n;ky^Uwc( z_xm5Sk34q&g8){G27$=H@=FA^w{$!1U`JuZA9C!DjN@7z+eW%vGjDZPZR{9wEwjJR zH+`$KwQ6_U$ku)Pt&HC(Fu#tCw+Y)GONw@CQF%8pWXhf7Ic_W^NCV|25W(s?CT!N#mT1 znG@(ZKlP#0(vew6NJnN1G&uBQuE2C}sOV>N8s-bkvK`NAFXW1YS=(#(4rB`&76`;A z`w+z<+R?}T9hT>7SS%0=yzX+HyudXz?ArT3Adzm}TmS5IEJadcu}mN_!QhNDY`GdP zrfr+#)Eu-lJdtNTK`L<1SB5`HU~#@^4_by4F`S}bDEh{L;~M==CJ<>Q33j+eYD$q9 zgO+cTcmnM;T%7U%+t4nN?+}=4<#~oTWV%+rp^@@POeWQ~kti+|&^PA` zen?(VV9}KDtixqZSc@kMEUZ@7RkSmH*0H0woKzh!26N6nGe?~Zr$W3F8=7eRQw3&_ zeZ_*2clwJtYdKBU6zU4hvN)AwD& z(6L==xEg&7b|Y>vjXplw+#Cw>qAUol?;*^#I$k4_mHd7*yNRx<|)u?2)^BrK@Xn zJU8&>c|_DO7~MH3*;oDO@foiE!>6R;wJ_zA_X%8>HxF2w4`r-^Gd@&kzH`vrm#dMxr^hMy$EQa-Xd2oc4FxPM+YlCl@ZohNTF30{F`c^RgAq}tOY`>GWHN2WQ z^k-d}A~n2LQF$PnvkYdp*GVYX>Ucu{AJ^;qRDY)ynL{9S zT#L8J_&CO6unuKCpDE;R^o1!RthrR2C)toLT@f|XdqPSC~6m@=g$8Z6tU!Ln`1pqeCY z$4|0b%I2h78a~8uRU#VB5J{7DYnaOq2y|`=%X=~?=E3ODeGWU5YFUp zqinw_U~Ei-mSbW$YcGswcu2=b1D=1JJlYVrdxeHi5_#J?D5GECww83Q9_d`Z2_dZR zqtbi!)L`QTl62P6UE^`-n#2y%)2HxhnRp-3Ap$CYmi>ToJMfv2uiwJX$;@l1VMpsu4Vz^`IuRp7zpCR4_@bPBy+%_b)pa{&zvV{pO~z4%uK>Q) z$A&a#UFkT!=euU1PHj`BRNE+*E4MOIzK!q1@GX3ozPUv^@NHdnzM6PjO}BJ>A3u;N z{*Zk&ahh<8z_J>EO%Ul6xVSnYY%kij44R6~QT$Y(mHNAweq}n|38#uvbUJuXSphGGZXMVHFKaxk~7xl z6y1y^Pm7%~&Gz|}omnr-L)~aIF`DwcJS?O8cM*rbAckg{z-el;tf^{bo1#V*RW-6h zRwH{$H8KgQk>RUG_8V$sW#Xt&FA?VTA9}Y7)A`y9u{70Kf|iJ-#t`pKC1PzQ%n`k* zX>xI1xHvCdoGure!^P%sF)9}qHa>uhdfOt2C4FtFhA}*W^EK-mQ;8)DsD6&%RFiVc z%W^9w$*ri!#nn?<%7QB=39dXR7^B8iVpR#3(C{^(IdPq%9BSl62ni&3T;MA!FqhIK zZwPV|<)qZnRD6bXe`A1MgwJ>eL5|N>WM(I^T z**Al;nY`k>X5+v7-b&AU(HKQnRDSNw0*H90a)G|I*rxNh8vyjjRd z4q(Bt%Qxy=KC#kqq z-4Q9_c0!#NxW*aQ1q+?J zlmFpJR@}PsG~T!J6iy~a$1rvZHz)2_<8b1`YP==!pc=O%KB7h=@i8@iA`)Q?_0ai; zBaz4`PDX@?%!*?SpE-p`BQhR(Rz(sU`zUcVt}Bs7B_fo_^%arNOOfO8xk_Z(IFUQ< zp?&fwSZzbA;~XU?8b|S{rW>|5P_1D1H3z_PJo<~q#V!3<@GjN>TRs5x50lV`DwsILQdvz_E&oFw>18af5^!e G{PS_F*xKv> literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_coverage_html/coverage_html_fixtures.py b/doc/doc_tests/test_coverage_html/coverage_html_fixtures.py new file mode 100644 index 0000000..6829dc2 --- /dev/null +++ b/doc/doc_tests/test_coverage_html/coverage_html_fixtures.py @@ -0,0 +1,26 @@ +import sys +import os +import shutil +from nose.plugins.skip import SkipTest +from nose.plugins.cover import Coverage +from nose.plugins.plugintest import munge_nose_output_for_doctest + +# This fixture is not reentrant because we have to cleanup the files that +# coverage produces once all tests have finished running. +_multiprocess_shared_ = True + +def setup_module(): + try: + import coverage + if 'active' in Coverage.status: + raise SkipTest("Coverage plugin is active. Skipping tests of " + "plugin itself.") + except ImportError: + raise SkipTest("coverage module not available") + +def teardown_module(): + # Clean up the files produced by coverage + cover_html_dir = os.path.join(os.path.dirname(__file__), 'support', 'cover') + if os.path.exists(cover_html_dir): + shutil.rmtree(cover_html_dir) + diff --git a/doc/doc_tests/test_coverage_html/coverage_html_fixtures.pyc b/doc/doc_tests/test_coverage_html/coverage_html_fixtures.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00eee1b23141080fc82c93e028079bb617e3dc9b GIT binary patch literal 1236 zcmah}O>fgc5S_KtG)X^5X%Pqs;RA|jC!qHm8txOB3}6E=ZyW)0;UfXe z1IPqyLdbhC?}5ky*z7>whk2hSA#8RbAHaM7LJeU)f~*I>XrDdEcD%FC`LopU7!J)Vjl6m$*z9$C(v=^^+LLj9`I}(*e41ol0Q+@aboH9qG z@h<2=I~OdnS1_avXOyCD>Wb+>CfN5q8S-KLAs9y|08}Ziy>n@F>DOf1mT93p)spb# zN=iKJHt-xss`09*D@~d=0&j_lSkdux7K7IbL@l-x8&h?V0X#k`}^AeZ;Z(s P(!Cjq1OD~;!EpE&wZ0JU literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_coverage_html/support/blah.py b/doc/doc_tests/test_coverage_html/support/blah.py new file mode 100644 index 0000000..ef6657c --- /dev/null +++ b/doc/doc_tests/test_coverage_html/support/blah.py @@ -0,0 +1,6 @@ +def dostuff(): + print 'hi' + + +def notcov(): + print 'not covered' diff --git a/doc/doc_tests/test_coverage_html/support/blah.pyc b/doc/doc_tests/test_coverage_html/support/blah.pyc new file mode 100644 index 0000000000000000000000000000000000000000..923a02c3c937b02e1e4b27596c9735f3935ae7c0 GIT binary patch literal 405 zcma)2Jx{|h5Iq+tDpglz#!Rt-*g@(5Q-*Xysv;9RBvKMv@ud>#+8@#1$uHo}iCVQ2 zmi+GB`QClF{aromk1uHf(-CCf==w(j4R8SXBq9P4pl7g&Zs_Skux7!61p?kbNVSP( z57K;GFGIel@C!-4EVV2W>qLWyxE49tOm}2KP%!?JPcp4n|)MAKZ+K;(Sb` gygOo3k2I{Gal?#1Yjl}~*tyzRgA2W$MqJ$f0Rm@5*#H0l literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_coverage_html/support/tests/test_covered.py b/doc/doc_tests/test_coverage_html/support/tests/test_covered.py new file mode 100644 index 0000000..c669c5c --- /dev/null +++ b/doc/doc_tests/test_coverage_html/support/tests/test_covered.py @@ -0,0 +1,4 @@ +import blah + +def test_blah(): + blah.dostuff() diff --git a/doc/doc_tests/test_coverage_html/support/tests/test_covered.pyc b/doc/doc_tests/test_coverage_html/support/tests/test_covered.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4d933c4a4d3c380971ba795f1e4a63fbd702aa1 GIT binary patch literal 340 zcmZ8c%?iRW3{IyY3VQSzc3PQuSMcs(-n@*ubvoSEvSxyK)Q9jneFBpz3@!A_m*mS| z{hCg0&*e6Q?-OL#^xd2w2RH-li3oTQ5ggft0i}K`He4|yK!8%f9YA;`ciVbB4v8Kr zb;1#m+&JutA|whuAPoa)+w!7NORTMIjMHhWE3L@JQ5-E~(KQ*CrcqUj+M$!V$-a#J zG&2|7s-sR%SXa_@)|wXON7@T?9@#67X`YYqVgqs>> something + 'Something?' + +The ``count`` variable is injected by the test-level fixture. + + >>> count + 1 + +.. warning :: + + This whole file is one doctest test. setup_test doesn't do what you think! + It exists to give you access to the test case and examples, but it runs + *once*, before all of them, not before each. + + >>> count + 1 + + Thus, ``count`` stays 1 throughout the test, no matter how many examples it + includes. \ No newline at end of file diff --git a/doc/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures$py.class b/doc/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures$py.class new file mode 100644 index 0000000000000000000000000000000000000000..eeeff8a716e6378441020bbf8366332b9bd5a164 GIT binary patch literal 4513 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 literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.py b/doc/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.py new file mode 100644 index 0000000..72fbfd7 --- /dev/null +++ b/doc/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.py @@ -0,0 +1,17 @@ +called = [] + +def globs(globs): + globs['something'] = 'Something?' + return globs + +def setup_module(module): + module.called[:] = [] + +def setup_test(test): + called.append(test) + test.globs['count'] = len(called) +setup_test.__test__ = False + +def teardown_test(test): + pass +teardown_test.__test__ = False diff --git a/doc/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.pyc b/doc/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf8a880a70efd96a8ad9e7852b8e190b862d8a73 GIT binary patch literal 846 zcmZuv%}(1u5T3P7+5`eG!NI3kAdW~NA#wDbpR!|U)!yhs^f~(k%r_2L zMNPD`^S|GG5VtH7J-1e>KZ*{l3;U$va%@i#d4R=ld z7}PA_OXW<0ThX#%Uq6@ZGj4~h>w~v@-}qAQi_OL6GJ6`kitD~B8_Pa$$Xs6?G}u;5 zy`SpuJnp^5|8dy&$Y%JV0DwItKw(zqV_FdTF|80OW7dXJA#6J7CMkw&qz_MND*-}Gsv4b z_+RI>MA?^n*S~bSxifJbNXSz#qSzH*fe)s@>pI?j*$5^ywOVOyaV*9(Ftj;;DhII7 UlL7S&M8Gx{(K1=a@{g9$8(EKvJpcdz literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_init_plugin/example.cfg b/doc/doc_tests/test_init_plugin/example.cfg new file mode 100644 index 0000000..b02ac0e --- /dev/null +++ b/doc/doc_tests/test_init_plugin/example.cfg @@ -0,0 +1,3 @@ +[DEFAULT] +can_frobnicate = 1 +likes_cheese = 0 diff --git a/doc/doc_tests/test_init_plugin/init_plugin.rst b/doc/doc_tests/test_init_plugin/init_plugin.rst new file mode 100644 index 0000000..6c64029 --- /dev/null +++ b/doc/doc_tests/test_init_plugin/init_plugin.rst @@ -0,0 +1,164 @@ +Running Initialization Code Before the Test Run +----------------------------------------------- + +Many applications, especially those using web frameworks like Pylons_ +or Django_, can't be tested without first being configured or +otherwise initialized. Plugins can fulfill this requirement by +implementing :meth:`begin() `. + +In this example, we'll use a very simple example: a widget class that +can't be tested without a configuration. + +Here's the widget class. It's configured at the class or instance +level by setting the ``cfg`` attribute to a dictionary. + + >>> class ConfigurableWidget(object): + ... cfg = None + ... def can_frobnicate(self): + ... return self.cfg.get('can_frobnicate', True) + ... def likes_cheese(self): + ... return self.cfg.get('likes_cheese', True) + +The tests verify that the widget's methods can be called without +raising any exceptions. + + >>> import unittest + >>> class TestConfigurableWidget(unittest.TestCase): + ... longMessage = False + ... def setUp(self): + ... self.widget = ConfigurableWidget() + ... def test_can_frobnicate(self): + ... """Widgets can frobnicate (or not)""" + ... self.widget.can_frobnicate() + ... def test_likes_cheese(self): + ... """Widgets might like cheese""" + ... self.widget.likes_cheese() + ... def shortDescription(self): # 2.7 compat + ... try: + ... doc = self._testMethodDoc + ... except AttributeError: + ... # 2.4 compat + ... doc = self._TestCase__testMethodDoc + ... return doc and doc.split("\n")[0].strip() or None + +The tests are bundled into a suite that we can pass to the test runner. + + >>> def suite(): + ... return unittest.TestSuite([ + ... TestConfigurableWidget('test_can_frobnicate'), + ... TestConfigurableWidget('test_likes_cheese')]) + +When we run tests without first configuring the ConfigurableWidget, +the tests fail. + +.. Note :: + + The function :func:`nose.plugins.plugintest.run` reformats test result + output to remove timings, which will vary from run to run, and + redirects the output to stdout. + + >>> from nose.plugins.plugintest import run_buffered as run + +.. + + >>> argv = [__file__, '-v'] + >>> run(argv=argv, suite=suite()) # doctest: +REPORT_NDIFF + Widgets can frobnicate (or not) ... ERROR + Widgets might like cheese ... ERROR + + ====================================================================== + ERROR: Widgets can frobnicate (or not) + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + AttributeError: 'NoneType' object has no attribute 'get' + + ====================================================================== + ERROR: Widgets might like cheese + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + AttributeError: 'NoneType' object has no attribute 'get' + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + FAILED (errors=2) + +To configure the widget system before running tests, write a plugin +that implements :meth:`begin() ` +and initializes the system with a hard-coded configuration. (Later, we'll +write a better plugin that accepts a command-line argument specifying the +configuration file.) + + >>> from nose.plugins import Plugin + >>> class ConfiguringPlugin(Plugin): + ... enabled = True + ... def configure(self, options, conf): + ... pass # always on + ... def begin(self): + ... ConfigurableWidget.cfg = {} + +Now configure and execute a new test run using the plugin, which will +inject the hard-coded configuration. + + >>> run(argv=argv, suite=suite(), + ... plugins=[ConfiguringPlugin()]) # doctest: +REPORT_NDIFF + Widgets can frobnicate (or not) ... ok + Widgets might like cheese ... ok + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + OK + +This time the tests pass, because the widget class is configured. + +But the ConfiguringPlugin is pretty lame -- the configuration it +installs is hard coded. A better plugin would allow the user to +specify a configuration file on the command line: + + >>> class BetterConfiguringPlugin(Plugin): + ... def options(self, parser, env={}): + ... parser.add_option('--widget-config', action='store', + ... dest='widget_config', default=None, + ... help='Specify path to widget config file') + ... def configure(self, options, conf): + ... if options.widget_config: + ... self.load_config(options.widget_config) + ... self.enabled = True + ... def begin(self): + ... ConfigurableWidget.cfg = self.cfg + ... def load_config(self, path): + ... from ConfigParser import ConfigParser + ... p = ConfigParser() + ... p.read([path]) + ... self.cfg = dict(p.items('DEFAULT')) + +To use the plugin, we need a config file. + + >>> import os + >>> cfg_file = os.path.join(os.path.dirname(__file__), 'example.cfg') + >>> bytes = open(cfg_file, 'w').write("""\ + ... [DEFAULT] + ... can_frobnicate = 1 + ... likes_cheese = 0 + ... """) + +Now we can execute a test run using that configuration, after first +resetting the widget system to an unconfigured state. + + >>> ConfigurableWidget.cfg = None + >>> argv = [__file__, '-v', '--widget-config', cfg_file] + >>> run(argv=argv, suite=suite(), + ... plugins=[BetterConfiguringPlugin()]) # doctest: +REPORT_NDIFF + Widgets can frobnicate (or not) ... ok + Widgets might like cheese ... ok + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + OK + +.. _Pylons: http://pylonshq.com/ +.. _Django: http://www.djangoproject.com/ diff --git a/doc/doc_tests/test_init_plugin/init_plugin.rst.py3.patch b/doc/doc_tests/test_init_plugin/init_plugin.rst.py3.patch new file mode 100644 index 0000000..90a0a44 --- /dev/null +++ b/doc/doc_tests/test_init_plugin/init_plugin.rst.py3.patch @@ -0,0 +1,10 @@ +--- init_plugin.rst.orig 2010-08-31 10:36:54.000000000 -0700 ++++ init_plugin.rst 2010-08-31 10:37:30.000000000 -0700 +@@ -143,6 +143,7 @@ + ... can_frobnicate = 1 + ... likes_cheese = 0 + ... """) ++ 46 + + Now we can execute a test run using that configuration, after first + resetting the widget system to an unconfigured state. diff --git a/doc/doc_tests/test_issue089/support/unwanted_package/__init__$py.class b/doc/doc_tests/test_issue089/support/unwanted_package/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..1ccf3e42355b66cbf34722a8818f192e5c953635 GIT binary patch literal 2181 zcmbtVZBrXn6n<_tupurj4U|$S1zM~L&@5nEi@_>I(X9zZf@rDZcG=vd+irGu_Jx`c z{ucf0rwUV=(a|5^k8(UWiL@chj59Jr?%jLOJ?A;keeOAb{r%gY0G7ZRr0VPZHC>;pTa69hsPjV4#?Dip8nbmT;3i{BWMiMe@Rdw44LW_ZE3I>oKp!IhcGQIv}43dLQSNIt}8;gST7#hGi zTwv&5wHzzF$8a%Qd}bUNs%!kpo!toFF~g8$2kwzn4pqDupX zINvehG%fChwDM8t{Vk_SlkY2FSP<#oCj2!XmJG`wDP+$)N^+~WY(}#&F>|{qp{!wy zq{-*1f)8+4%t-R%r>|2WDY{05`!YTu`I_{!LteJ(p3U#OZWx5V;W4blm4{Xqy%@#v z$`&dt74WCkOe&n=syWQ)r`&evR3O4Y$VC)Oh0`b&!gkdIzur3pl(Dl;u>X_O~s8$!HGG;^4q#!r_vR~)WvQj3O4aD zjS{vP22I1Z^?%-zvBC z!l~Do&r3px>9QO7CKoruaG9up;SjH=WifkOcYsyIwq=2 zYozB!V?XT^EA;<_zHZP=By?^*d5qM2;uVxbWM0w=gF(N-TFj}IV}yR6(AS3;Y@Nx^ zy~gFaBTQwkH<3HSyd?dBd}(1mvnWZ@A*Lk8qzgl6;?@!F{7NTuK_&3mmTX&%a-tFCY$)G?Tz|7 z%ANfR_0lAHyE?aUbK(~fOEB~1Z{!$Xzhv#a_i6bS@iK;_j7cVoL5D>1&h*Mz8wr?n YHi2uX&^SV~Zz=vG4SRSjCJ*r4KN9jycmMzZ literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue089/support/unwanted_package/__init__.py b/doc/doc_tests/test_issue089/support/unwanted_package/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/doc/doc_tests/test_issue089/support/unwanted_package/__init__.py @@ -0,0 +1 @@ +pass diff --git a/doc/doc_tests/test_issue089/support/unwanted_package/__init__.pyc b/doc/doc_tests/test_issue089/support/unwanted_package/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3e47f02cc4bf9f6c6cce5c94a35251acafbadae GIT binary patch literal 189 zcmZ9GK?=e!5Je-n61+!O9V;$$WlzwJ2N*&+O0Z2rW}=7}@tmH(33TO$|Ni{Hn)$ok zyg!G*nMEtjUd?O8Xw0Rl%g`vyA7s`IQuYrk1TrTGkqI+RWHr0CYxi(1!HJK-PAJ5} sFh;jrwWDV)v^$=FOG+^dl<>4cXvAdQ-QEZgeei literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue089/support/unwanted_package/test_spam$py.class b/doc/doc_tests/test_issue089/support/unwanted_package/test_spam$py.class new file mode 100644 index 0000000000000000000000000000000000000000..2998cfd98c34b2771f8ef4f2b42ae6ff77deabef GIT binary patch literal 3051 zcmbtWTXz#x6#h<=c9Jka8@ZGgq)5f2X*)E6fZRosp7$~g``bWD z2r)=5_YB?S4B_I$A7Cy$X!{e@}y0%sx93uc#E%4<^NmV1*N1r9` z)d3ks6S);_O;ZfbEGhGzqnoAeIFr$f1A>J&!|xNEWE7X_+^4HgIyQS6JVp}C&Uz_Kz%Fe+$O7!Fm;>ze8Df@*8|RjtI8 z|9G~m5{T>Gfb%D;0w?^vcshzSPB3(x(M{bu%doPIBipE_-5CMn4OWDJpTV;+bmJ5$ zplNml&oM-V)UCW`Fem|f*dFx^o)>~3XZ8l{O)fGVZWYP{eJ^Os2A44IE5tW(B8GD~ zPr7iqQKY@OZ7C;Kr|^b$cYtP{}AdRyj+O za!g)$^mJ@^Bx4q@#Bd37GA`hv;9-HG`{9h1Kv9UCDO-h#!OuqU8Y$f5*M%$@eoSPq zw$^}08Obg!1xRB{Xfr|B0mo&tLCM+3n_d|ofx20v$6p6fZ9 z&2Ty>oqaD_Z4|@Jt)$~s7%{A%Ouji4v+mo;R=lm-0^O2f!4{0YO@ z4Y+EyS|Z~*Zp5&LDqR9ppbYgvhKfrd8zjH~^(GJ!>kuNiCCatmdp|%9v(*P|NcfOp zV5=Qvn+S1~@!9MmcU z-B(h-V$aArPO^JAJ(G%OMrwG0&7?Yh!|`<-yF=?4zM|hQYDJ%ts)?9Ki2i^RsZK&n z#rN0HU&E2_aVQl(TJQgW9)d~56ZENWVEC`i$Q_%IJMc9j^$>mvAqi;-VF_ROM^fg~ zuyElrcKQci9J+_ep}WY&r#CQv7q5mwf8g@;a4LQ!6bh{)8)7Wf(~S+haTjWHG86VE z!~P`mCr|!Du);4YDvd+BsTID4*P#mkfhd-dZ_Eym0b+K~eWb6%3-?g0;o1hwp$#~7 ztSDS~jn#2~EwL9*Z{TJfjo}@<+nB#i5fL#tHZ(la|Fbv?81R3EO(L?u!5ZGXQ^N;$ zSUnnor)jd{pkOG?8bod4Y@pr5V7rM9d_$le$l+UR_fYR+I{uR^ZsSwYxq#391vN%R Am;e9( literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue089/support/unwanted_package/test_spam.py b/doc/doc_tests/test_issue089/support/unwanted_package/test_spam.py new file mode 100644 index 0000000..cfd1cc1 --- /dev/null +++ b/doc/doc_tests/test_issue089/support/unwanted_package/test_spam.py @@ -0,0 +1,3 @@ +def test_spam(): + assert True + diff --git a/doc/doc_tests/test_issue089/support/unwanted_package/test_spam.pyc b/doc/doc_tests/test_issue089/support/unwanted_package/test_spam.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5598905df4e7b69edbbccf3c7f8360f3db15b005 GIT binary patch literal 333 zcmY*U!Ab)`49#dkEYw54Voy7^c+is;@w$iBQ!mS~8)dOOGfXlS>8b8#2av!Y^gmuCVI*GG#R?h8P_KhxZ zo0^fBY#W*b(!B17GTz=lSjsVm#5Vg+=Na2Ny5_^Z%VMQbbbT4W_*%X^7W8UTonWd# W!tzq^3DVYZ?^0PJ7~pXj(#9W=u||&o literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue089/support/wanted_package/__init__$py.class b/doc/doc_tests/test_issue089/support/wanted_package/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..bb74c143e21d796fb276c82074ac10b27d279f1a GIT binary patch literal 2175 zcmbtVZBrXn6n<_tut{862$WJN1zN00pjp7S7K2raqFV!v1f!*n+hub@w`_KIb~n^~ z@VDq^KUJ7gMn`{uKg#jkB+`Z~GtS5ixp(h9_nhZE=k2e*fBO@_A~=Ion_Xbo$`oCH zPxYGNGuKf~*XL@fx$YZvUg){le&%zdS`Lkn7qUIKrRJmE=iCen442PQDMMnA10LFz zLkQv-En890hyE1ekm;(X+eQ#t3`|pyL@G(=?=Yl$^~o3@1)HwWGk!i61s5@x#03m7 z^siWs72apKlqo(p4h+>coISN1`j)d-$W|Dpdd;ji95b|BXWQ7dxr|FhTSj0W#^n@7 zF-8~yZr2!wi!Tf(%za~z&w5QFVyNd(##II5m`Gw0*BD|t#UKiQ2NuJ=>iO0I zQLct{Pj%dY>pr)+LF;0EF~6kNIuH$84|xydFng7%8-wuG9nxucNE;jdnvq&jDl&*2oiE+ zG2+3RTjfOH;MqXQxW$lIsk_yN&F?3mQr0;9g%D@>DWg5<4eft9(ROwsKy*dF5T!f9 zoTf$9M#~?CDsL%8+4#N!hI!%s9m2oI!wti7C=oK}KBYvfw`4{$5jL}xgvhI5jHF2C zih>VtPwXh!$IpgSASt>=mHRS2p<*zps6$@1>YmLXxNaDPzTq(}M>U3)7rhw8@#)io zf(KYjVHKZ}Z+^pR`*x>SU-z>}w-l`7bHUgblncaD*As@B2*SEVD1~mEC7{&YYB-K4 z=u^xxiVRuOzi!#wq2{;dI>bZ63|)VV*KBHQ)C5k&8J6D8`!#A>K}1LFMxtO7k5bq` ziDAGrY+L`gav91QEh_!~R~r^7786OZ9gzfYIFLb|Z@Sxh-6I-&b+x2bxF3+`L}TB9 zVK=x$(_@@rB2s>1+oG}22e8E-gP(sOv9r{_H9=&2PdP0pVz^kOd9c~2?{dG@XsNmY|M_F4J16 z+-U8mdt!zDpVHS&+6jj)%*Bt9n2WuFa)|UxI$<#AS4fLJb$5)=?^F8v5Cg3@`P^$< z$sJ)Ted7e#Bg{$CAINXa&!rb6Njk)o#F#WRh!fmC!rfo#rO-rW+0ipdtI!@l#9d(# zH13v3d)vx1*$}rQzhm-oy6_qw9pmE@e3mmTmz>iq@N`kA$L##z?gWS-20^s>CH0eY1*^>uU34(J literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue089/support/wanted_package/test_eggs$py.class b/doc/doc_tests/test_issue089/support/wanted_package/test_eggs$py.class new file mode 100644 index 0000000000000000000000000000000000000000..b32d6903b0c2804c0166e8244c46654583d46cfc GIT binary patch literal 3045 zcmbtWTXz#x6#h<=c9Jka8@ZGgq)5f2X*)E6fZRosp7$~g``bWD z2r)=5_YB?S4B_I$A7Cy$X!{e@}@>0ni93uc#E%4<^NmV1*N1r9` z)d3ks6S);_O;ZfbEGhGzqnoAeIFr$f1A>J&!|xNEWE7X_+^4HgIyQS6JVp}C&Uz_Kz%Fe+$OD5BRj)8hrz*7B=bi7WrH zY*!@^)4c(wPgn&`=zH;W6lt7b=sKgDx_6dgWg9oP(N4QF0>&Gh2>CvPXJhEbDT;s7 z>KQyQ z;c}zMa45G8;l$|_UJ{(X%+R4ykqAnThNc+|#dh>HstJl$wiVNIx$1C(Yt)X7j*X_3 zVk74?Llp$MO2NuEdOl&g>sI*jQ>PWTV%wJEDeY_%z9uq0<`1@1_odayT7cZ$MT7KS4Mgl$|j!R9fAD zOJ=JjGOpuB3~Q)T`KRJ!s1Nc}l>cmy{QlQ#KuD}Zh~Sne)qd~&06EN7AFv_eLxzE^ zc9d-*#8Jj)vy0qui31|Eb4@cUJVehKx-i7-E*KzC)t!Ma(e!Hj@eM+h(Vjdy-15Ttm2{9Gl zUqgQlN503QRQzbY{{wmmCKXT6r@DdRzcwRxY(nn9*M!tV_$h=Wq$PwUeBmESnNP#Q zh0ECKA9!)-9wvwGA{(FH!2Dgj8Vdb^%hSWD_?1v7w2o|uu~1JpHt@z>sLjbt*q;ph zlgyty`3u1czo@7*4(X;=_!?e^D*OkcSVq1vJ3t1A***7>z7j9oL$QWy8!(49;MB3A zaN#vp$Nja$UOc^ln{_mXckpgw{x(HK#N^n}@JRp9;w)gm{}ncg$N~pzc<)XPAKYQ} yXb7IB$%=!5p)_j{wTZKVb`yi`COYs9fp#E=Z>ilwy^rbmPqMg;PetbfKKmEb=|Zvq literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue089/support/wanted_package/test_eggs.py b/doc/doc_tests/test_issue089/support/wanted_package/test_eggs.py new file mode 100644 index 0000000..bb65550 --- /dev/null +++ b/doc/doc_tests/test_issue089/support/wanted_package/test_eggs.py @@ -0,0 +1,3 @@ +def test_eggs(): + assert True + diff --git a/doc/doc_tests/test_issue089/support/wanted_package/test_eggs.pyc b/doc/doc_tests/test_issue089/support/wanted_package/test_eggs.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c12ec5f30cd8c6f66d6d42c8bc1e1321f651c16d GIT binary patch literal 331 zcmY*Uy=uci4A!NAkU~22DLna-(4n2$Qo4FbGG#G`tr}Cucbw#uk}dQh`kZ-!NG9ax zK+-4alTP2}FuC9TKV=1+o{{(_MiX%*z#qU`fPrN)kjf43LwS$l7$A(muz|e+BU8&n z_hkJD4+XAbhx{Pmod_e>zWt2R$3!Y{aok91L#(%s(eftP))VFt8*~z9=`5YwudXe0 zfopO`VzQ+#PDt~-ChBnd{9>sK!AG_~u4OFq;EHco$yz00RZ$v#c`P5E3wkrDN-$Y3 WU~wn-3TdmqcA+eh3-H>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> support_files = [d for d in os.listdir(support) + ... if not d.startswith('.')] + >>> support_files.sort() + >>> support_files + ['unwanted_package', 'wanted_package'] + +When we run nose normally, tests are loaded from both packages. + +.. Note :: + + The function :func:`nose.plugins.plugintest.run` reformats test result + output to remove timings, which will vary from run to run, and + redirects the output to stdout. + + >>> from nose.plugins.plugintest import run_buffered as run + +.. + + >>> argv = [__file__, '-v', support] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + unwanted_package.test_spam.test_spam ... ok + wanted_package.test_eggs.test_eggs ... ok + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + OK + +To exclude the tests in the unwanted package, we can write a simple +plugin that implements :meth:`IPluginInterface.wantDirectory()` and returns ``False`` if +the basename of the directory is ``"unwanted_package"``. This will +prevent nose from descending into the unwanted package. + + >>> from nose.plugins import Plugin + >>> class UnwantedPackagePlugin(Plugin): + ... # no command line arg needed to activate plugin + ... enabled = True + ... name = "unwanted-package" + ... + ... def configure(self, options, conf): + ... pass # always on + ... + ... def wantDirectory(self, dirname): + ... want = None + ... if os.path.basename(dirname) == "unwanted_package": + ... want = False + ... return want + +In the next test run we use the plugin, and the unwanted package is +not discovered. + + >>> run(argv=argv, + ... plugins=[UnwantedPackagePlugin()]) # doctest: +REPORT_NDIFF + wanted_package.test_eggs.test_eggs ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK \ No newline at end of file diff --git a/doc/doc_tests/test_issue097/plugintest_environment.rst b/doc/doc_tests/test_issue097/plugintest_environment.rst new file mode 100644 index 0000000..99b37cf --- /dev/null +++ b/doc/doc_tests/test_issue097/plugintest_environment.rst @@ -0,0 +1,160 @@ +nose.plugins.plugintest, os.environ and sys.argv +------------------------------------------------ + +:class:`nose.plugins.plugintest.PluginTester` and +:func:`nose.plugins.plugintest.run` are utilities for testing nose +plugins. When testing plugins, it should be possible to control the +environment seen plugins under test, and that environment should never +be affected by ``os.environ`` or ``sys.argv``. + + >>> import os + >>> import sys + >>> import unittest + >>> import nose.config + >>> from nose.plugins import Plugin + >>> from nose.plugins.builtin import FailureDetail, Capture + >>> from nose.plugins.plugintest import PluginTester + +Our test plugin takes no command-line arguments and simply prints the +environment it's given by nose. + + >>> class PrintEnvPlugin(Plugin): + ... name = "print-env" + ... + ... # no command line arg needed to activate plugin + ... enabled = True + ... def configure(self, options, conf): + ... if not self.can_configure: + ... return + ... self.conf = conf + ... + ... def options(self, parser, env={}): + ... print "env:", env + +To test the argv, we use a config class that prints the argv it's +given by nose. We need to monkeypatch nose.config.Config, so that we +can test the cases where that is used as the default. + + >>> old_config = nose.config.Config + >>> class PrintArgvConfig(old_config): + ... + ... def configure(self, argv=None, doc=None): + ... print "argv:", argv + ... old_config.configure(self, argv, doc) + >>> nose.config.Config = PrintArgvConfig + +The class under test, PluginTester, is designed to be used by +subclassing. + + >>> class Tester(PluginTester): + ... activate = "-v" + ... plugins = [PrintEnvPlugin(), + ... FailureDetail(), + ... Capture(), + ... ] + ... + ... def makeSuite(self): + ... return unittest.TestSuite(tests=[]) + +For the purposes of this test, we need a known ``os.environ`` and +``sys.argv``. + + >>> old_environ = os.environ + >>> old_argv = sys.argv + >>> os.environ = {"spam": "eggs"} + >>> sys.argv = ["spamtests"] + +PluginTester always uses the [nosetests, self.activate] as its argv. +If ``env`` is not overridden, the default is an empty ``env``. + + >>> tester = Tester() + >>> tester.setUp() + argv: ['nosetests', '-v'] + env: {} + +An empty ``env`` is respected... + + >>> class EmptyEnvTester(Tester): + ... env = {} + >>> tester = EmptyEnvTester() + >>> tester.setUp() + argv: ['nosetests', '-v'] + env: {} + +... as is a non-empty ``env``. + + >>> class NonEmptyEnvTester(Tester): + ... env = {"foo": "bar"} + >>> tester = NonEmptyEnvTester() + >>> tester.setUp() + argv: ['nosetests', '-v'] + env: {'foo': 'bar'} + + +``nose.plugins.plugintest.run()`` should work analogously. + + >>> from nose.plugins.plugintest import run_buffered as run + >>> run(suite=unittest.TestSuite(tests=[]), + ... plugins=[PrintEnvPlugin()]) # doctest: +REPORT_NDIFF + argv: ['nosetests', '-v'] + env: {} + + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + >>> run(env={}, + ... suite=unittest.TestSuite(tests=[]), + ... plugins=[PrintEnvPlugin()]) # doctest: +REPORT_NDIFF + argv: ['nosetests', '-v'] + env: {} + + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + >>> run(env={"foo": "bar"}, + ... suite=unittest.TestSuite(tests=[]), + ... plugins=[PrintEnvPlugin()]) # doctest: +REPORT_NDIFF + argv: ['nosetests', '-v'] + env: {'foo': 'bar'} + + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + +An explicit argv parameter is honoured: + + >>> run(argv=["spam"], + ... suite=unittest.TestSuite(tests=[]), + ... plugins=[PrintEnvPlugin()]) # doctest: +REPORT_NDIFF + argv: ['spam'] + env: {} + + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + +An explicit config parameter with an env is honoured: + + >>> from nose.plugins.manager import PluginManager + >>> manager = PluginManager(plugins=[PrintEnvPlugin()]) + >>> config = PrintArgvConfig(env={"foo": "bar"}, plugins=manager) + >>> run(config=config, + ... suite=unittest.TestSuite(tests=[])) # doctest: +REPORT_NDIFF + argv: ['nosetests', '-v'] + env: {'foo': 'bar'} + + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + + +Clean up. + + >>> os.environ = old_environ + >>> sys.argv = old_argv + >>> nose.config.Config = old_config diff --git a/doc/doc_tests/test_issue107/plugin_exceptions.rst b/doc/doc_tests/test_issue107/plugin_exceptions.rst new file mode 100644 index 0000000..2c595f0 --- /dev/null +++ b/doc/doc_tests/test_issue107/plugin_exceptions.rst @@ -0,0 +1,149 @@ +When Plugins Fail +----------------- + +Plugin methods should not fail silently. When a plugin method raises +an exception before or during the execution of a test, the exception +will be wrapped in a :class:`nose.failure.Failure` instance and appear as a +failing test. Exceptions raised at other times, such as in the +preparation phase with ``prepareTestLoader`` or ``prepareTestResult``, +or after a test executes, in ``afterTest`` will stop the entire test +run. + + >>> import os + >>> import sys + >>> from nose.plugins import Plugin + >>> from nose.plugins.plugintest import run_buffered as run + +Our first test plugins take no command-line arguments and raises +AttributeError in beforeTest and afterTest. + + >>> class EnabledPlugin(Plugin): + ... """Plugin that takes no command-line arguments""" + ... + ... enabled = True + ... + ... def configure(self, options, conf): + ... pass + ... def options(self, parser, env={}): + ... pass + >>> class FailBeforePlugin(EnabledPlugin): + ... name = "fail-before" + ... + ... def beforeTest(self, test): + ... raise AttributeError() + >>> class FailAfterPlugin(EnabledPlugin): + ... name = "fail-after" + ... + ... def afterTest(self, test): + ... raise AttributeError() + +Running tests with the fail-before plugin enabled will result in all +tests failing. + + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> suitepath = os.path.join(support, 'test_spam.py') + >>> run(argv=['nosetests', suitepath], + ... plugins=[FailBeforePlugin()]) + EE + ====================================================================== + ERROR: test_spam.test_spam + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + AttributeError + + ====================================================================== + ERROR: test_spam.test_eggs + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + AttributeError + + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + FAILED (errors=2) + +But with the fail-after plugin, the entire test run will fail. + + >>> run(argv=['nosetests', suitepath], + ... plugins=[FailAfterPlugin()]) + Traceback (most recent call last): + ... + AttributeError + +Likewise, since the next plugin fails in a preparatory method, outside +of test execution, the entire test run fails when the plugin is used. + + >>> class FailPreparationPlugin(EnabledPlugin): + ... name = "fail-prepare" + ... + ... def prepareTestLoader(self, loader): + ... raise TypeError("That loader is not my type") + >>> run(argv=['nosetests', suitepath], + ... plugins=[FailPreparationPlugin()]) + Traceback (most recent call last): + ... + TypeError: That loader is not my type + + +Even AttributeErrors and TypeErrors are not silently suppressed as +they used to be for some generative plugin methods (issue152). + +These methods caught TypeError and AttributeError and did not record +the exception, before issue152 was fixed: .loadTestsFromDir(), +.loadTestsFromModule(), .loadTestsFromTestCase(), +loadTestsFromTestClass, and .makeTest(). Now, the exception is +caught, but logged as a Failure. + + >>> class FailLoadPlugin(EnabledPlugin): + ... name = "fail-load" + ... + ... def loadTestsFromModule(self, module): + ... # we're testing exception handling behaviour during + ... # iteration, so be a generator function, without + ... # actually yielding any tests + ... if False: + ... yield None + ... raise TypeError("bug in plugin") + >>> run(argv=['nosetests', suitepath], + ... plugins=[FailLoadPlugin()]) + ..E + ====================================================================== + ERROR: Failure: TypeError (bug in plugin) + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + TypeError: bug in plugin + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + FAILED (errors=1) + + +Also, before issue152 was resolved, .loadTestsFromFile() and +.loadTestsFromName() didn't catch these errors at all, so the +following test would crash nose: + + >>> class FailLoadFromNamePlugin(EnabledPlugin): + ... name = "fail-load-from-name" + ... + ... def loadTestsFromName(self, name, module=None, importPath=None): + ... if False: + ... yield None + ... raise TypeError("bug in plugin") + >>> run(argv=['nosetests', suitepath], + ... plugins=[FailLoadFromNamePlugin()]) + E + ====================================================================== + ERROR: Failure: TypeError (bug in plugin) + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + TypeError: bug in plugin + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + FAILED (errors=1) diff --git a/doc/doc_tests/test_issue107/support/test_spam$py.class b/doc/doc_tests/test_issue107/support/test_spam$py.class new file mode 100644 index 0000000000000000000000000000000000000000..a06d9e90ff6f652104923b95b5f50fe50477a5f6 GIT binary patch literal 3210 zcmbtW>sK2^6#q>EY)H2)#6BprmRhWV09#^ft<)-|P*Z_YK(y4lF3BV;YQ9-~HV?^Y=f${|R6K-!eq= zT}K(Fl4F)rQ0%g|V3|tRa=0>C9(A-LAFL6~EI3@tO?#Th2fJ&eljRHZi#+QMGVHCR zQbLqLa=B;dCTD0!X1Zr(G@>bv21pEv+H(?`8RU_o?FH&xhK|eijmy{uxfyY6XK1fg zH(D~Yo^F{Eb}}^Q)q-KoYX<3T-%4dG63yh{eGJvBf~KjJu+6T?Fc+l$%~AGwz0 zwpeGF4r4=**7^X2aiOYoP)0X;;^ae$a-UZX&GmE!mXXniegUgO3H}dOyDWjAZ3{6s zY~?tq+l_~tk;XBGrc=79d#4!|xA1!lvedH?vL1jS`1%M=#L%KJ%^O_8 zsLvZelBeQ0hYYdBnIC?^HOSWw}o>FzD-!8{+F|;%) z<|RxMi}H?D%$Oc`OrE><5)1;6@eF3;n8C9$E?`oGeTkvv?kbjWnKFH_=I$)rDjuKK zi?+efSeEB{j%G8Q46A8BidrAVa`h{Mp1dXE%O=f1f;}RMKZR4_Llz5b$G<0uvgzg`o(djMI z2(W=0?vK}K+uiBd@T`&W8Sz}e#;|jY9w`&0;yic!zDZS2PoM14xFBbI+A29&F0#e2 zkKFdi%B=3{l++>9wCIgQ9=bS0;h!TMKA=}lo9L$GYMW>;{zxV0k94E?BlUV3W3-=; zqwfp)wS*?R7Jv(>hE>E?uw&v5S}vu2#jd^;9A~$2aw3%&>|4bXY$7GqZaEWdIU8(= z{Dz||IC7IzGJHw@<77(AL1w5LtA?|P(jRav)ks=XiM^}nSjD05a3Ga9T$z85R#KHp zBgjNi%5^qOc>meiV1@oR8<_U zsA{C$)MkP+9=7{u7J6>ue9tXRB&OEz+%3#SqkrJ}@!nM8g=jRof{7?&(bg8MVg43! zRpDTRFKqCI%oiT73eQTuuyKR%%rE4h0E^mN;6V$GK6oq02~dG%6tECTJIOmC-E{}) zO9}lpmR3<*gWI!)s})_%xQ24DJK^sIniA}PlAMyj3v-ymz{|q-o%gaJN<&rY(rvk zVQ1gG-Ff?F+TY8I&!78s1;a9OE@jh=gc{%*;6;Fmz(75NO){6OlY%ELxR$^G{u~S& z_%dLme3Pi)I+^L^nS^cVk*@@-h%j*Dy5|sFNQ8kY@T;WOhvvOxwA^~zIl>~~9!oj1 zG)wR7Ti;bGQ0@yxVzRZX2Bdk>5cQbdK3M9#cY$qW$(L>FKX@TOjD(sb^$6yFV`}?P qEBb138c`nG8x?W4BP|*fkKN}wwOr{qjEEvPX&q(M%j0NhF+Ttp!b!XU literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue119/empty_plugin.rst b/doc/doc_tests/test_issue119/empty_plugin.rst new file mode 100644 index 0000000..6194c19 --- /dev/null +++ b/doc/doc_tests/test_issue119/empty_plugin.rst @@ -0,0 +1,57 @@ +Minimal plugin +-------------- + +Plugins work as long as they implement the minimal interface required +by nose.plugins.base. They do not have to derive from +nose.plugins.Plugin. + + >>> class NullPlugin(object): + ... + ... enabled = True + ... name = "null" + ... score = 100 + ... + ... def options(self, parser, env): + ... pass + ... + ... def configure(self, options, conf): + ... pass + >>> import unittest + >>> from nose.plugins.plugintest import run_buffered as run + >>> run(suite=unittest.TestSuite(tests=[]), + ... plugins=[NullPlugin()]) # doctest: +REPORT_NDIFF + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + +Plugins can derive from nose.plugins.base and do nothing except set a +name. + + >>> import os + >>> from nose.plugins import Plugin + >>> class DerivedNullPlugin(Plugin): + ... + ... name = "derived-null" + +Enabled plugin that's otherwise empty + + >>> class EnabledDerivedNullPlugin(Plugin): + ... + ... enabled = True + ... name = "enabled-derived-null" + ... + ... def options(self, parser, env=os.environ): + ... pass + ... + ... def configure(self, options, conf): + ... if not self.can_configure: + ... return + ... self.conf = conf + >>> run(suite=unittest.TestSuite(tests=[]), + ... plugins=[DerivedNullPlugin(), EnabledDerivedNullPlugin()]) + ... # doctest: +REPORT_NDIFF + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK diff --git a/doc/doc_tests/test_issue119/test_zeronine$py.class b/doc/doc_tests/test_issue119/test_zeronine$py.class new file mode 100644 index 0000000000000000000000000000000000000000..8138d8c1bf1a228529d81b5b88fe408e67e483fe GIT binary patch literal 5731 zcmbtY3wRt=75;BF$!?Y@-3)0dZGj+xCf#nfDG#MVu#GJS(F}bt%o_p>&=RfDa zXZqk{cijV^N3|)$#yU1C?CczH-0^I2+MjUj?1`Ut~q7(E?U?%;acYCkZ<}{ zZ`Y#L!RZTzC#?~`SK-(?D&vSL#68O|)wD84o{R?FGd7T53<~}o9FzxZ|kniU0@x>-reOba>4a;zJ5*n5(B*Zpn#4IRe zk4$~VXYr#Iy7+Mn)s*bK?^~XqzzPM;c08-QSSXF>ZTfetLTaJmaU7@6JQ#>1utp&n zI=bC;rW7uYFy*5E!ukHLi|V7L;dq=N?w?59YAvl;9J+B5-3ZOwMYk+Bfs+;5=7~?4 zwmB{WNd3eAGRw@{jXL>sITUD{Ur^X@f7i=U+TM5lp;;wsl;L1RMDTVX2A}HWi=BW zbSG&TQ_g594E$gac!5M<*vah#am)xR(oyC=IcCbd#Of=U9#6a?B2$;BrFzFk%pXZipNVHBaWR;KAz_sKd?CBVbjbJp1VXy`hQpbI!$IKg7ST4V6kVzn~(7IrB&vy9A?Fh~6SKHdijI0|PiopVcqw;9io{BadlF2-db@A+}~q{y@LWLB2S3G{if zT#cvuaA^`HT*iDXR+;y5vHvs;&kgOrl307Cyl;hsa5X6|2tlZY=&xDg3=Pl63tEWI z3t5tjE!keO85zfp%or~?!)Adxqsq5pwuYDBr7g_hmnobQ5P&7c6VQMmJYbAE zmS@q#0bqetF0A8uo7RtshF9Q~QXa2j6rGY^EU`deBhlf-=@)E;;}@e> zrPN-z-d=~-OE$iN{usunHC!5(B7GBo(uUAK4Q~mV`nrIDZO0Z06B3PecJ0;hR=iC> ze!Jjsx`rD=9B!gT;#HnToVk<{OL_MWyi-)ai&c?xY)iv?B$gNBy}XRJc%J178rxjg z;RW@6(vTGSw#!DWqRdB<{G9XkkBy4=oKlj_2k=1&*{uvSSNl`4V{;VO+p;5%cq^T1NW}{+TqetFw%-y38;xkEn8lNSnt*fEMb?r{j;rge9~7R52?}li^wgi{xwcboX>`%8pgo z!YmjP1}{76j8xu&u;e|jWcBo%m7N!)?&5R;4{7*fSVmvwH4zdt8!U? z1K*VT`IZJ1X5V)bxQ{N7aCVqN7po&Xf?zjOYbuagIq!O`c+j;wfvu8_q zCh;hKMc>?#9r||0VnUd|Eh3u2)4#!QC1}56IxtT2wkVtw5wLk8{CYciaUJh0*|wDM zoXv6kNui7Sx3g!KhON)B8Hdt{@3=dyu>!vX$*XhX3Y%(4Bj~OoJ|X8@qTw(2YZ8CP z-^kZNH(sq1;p_gW4E(>oTP4dHBnkc{ZDR2DZ{a<8Q%blxc;hE*#D!mtP+T=Bw9mI~ zs>mR@*)4_K{0=NSY>xoRT|Y32q9<(QKcm?9O|6!n?)hHEarU z9!_E)?txOxNMe;xF-=`1tzR(m>JCP7PcZVsBN*9Q1taS<7};(IBX5sjlUsyNuKH zGx%pi?Zo55iL)qC#uNEJT{*X6b*g2-?k#n8AJ$({ckZ0J8Jt(f`BXYkRjN@bE#Jtw zwG_)3F=I&2WoEEf&EnY-k6hLM3}?pDspZ_!6z=G#?nvt^`KnLQqKwf<4U^PxsH$Od zzJ`W7Ff)Vq?I)H>rTF($V#;?u4SXc68~G4b#QDgY#%DPtSXm+N$Wlfmk4@`OOY1{{ zhi&Q9GKoL3nM z;hhzkd0y#({Sv%ZK02Q^dPtI~cz7*?BSE|{omwGw#L9TJ{#wE_i#MiI1dFD5p=-zY+~74|aU;cFlCO<3ctlc7vh)Y|VP&K(DdT6e`1Sf({65sxfEP4Jhl%;vem3TO8u&EsSE5e^$Xv~e>L4b(k1EjApwwA!VMD!zjrA5z fueZ>kBu^XgkUEBA8yaYbQi*=mqLT7*zB=kZvA;6k literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue119/test_zeronine.py b/doc/doc_tests/test_issue119/test_zeronine.py new file mode 100644 index 0000000..6a4f450 --- /dev/null +++ b/doc/doc_tests/test_issue119/test_zeronine.py @@ -0,0 +1,26 @@ +import os +import unittest +from nose.plugins import Plugin +from nose.plugins.plugintest import PluginTester +from nose.plugins.manager import ZeroNinePlugin + +here = os.path.abspath(os.path.dirname(__file__)) + +support = os.path.join(os.path.dirname(os.path.dirname(here)), 'support') + + +class EmptyPlugin(Plugin): + pass + +class TestEmptyPlugin(PluginTester, unittest.TestCase): + activate = '--with-empty' + plugins = [ZeroNinePlugin(EmptyPlugin())] + suitepath = os.path.join(here, 'empty_plugin.rst') + + def test_empty_zero_nine_does_not_crash(self): + print self.output + assert "'EmptyPlugin' object has no attribute 'loadTestsFromPath'" \ + not in self.output + + + diff --git a/doc/doc_tests/test_issue119/test_zeronine.pyc b/doc/doc_tests/test_issue119/test_zeronine.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e1e946c56efe7c7c6f2e1903f674967b05e1675 GIT binary patch literal 1362 zcmZux%Wl(95S{D1)0UPB$__u6I|db4Q9goi z;j{Px=3J*qK_v0@%-q*GGiS2;z1RKv^Tl*V>rY+YujR47WQd8DL_uXJ3KYePmL4TF z3TmWckCt^x8Wc3dsnN1YNsEFODNSVBWID7qx}F>4bnB5%hgT#L3B%W5$sdkA}te&%mK|kirbsHhd_Nk=m`AjK!Hh9hO?x+GHK=S zZv2nhvM#FV1BnK@$fhRM)oN~GcbVr|!Mr2(=_==&YHpJ+-0&9b+A^>lp~ydXCr$?* zLWqZ98m)8~a@RSlEG`pq2jbCo;0tlbd0{?9tjD~{$7yDDSm;DY@}7()qvzv!na`8F%Diq-E+S)(zAF(P%8dU6HNMB3|?dAk9E)buwqDCM!8FnIVlg*6IR@KP`%^ zl8lcf+w%F?$^h(AUvWbLLR27(Gi}2(<8W3)cJWA-S!^KHRs$r*2L<0@fMg=6GIZcB z|67-?l1`je=By+_uv&*b7b?!U&P?izg)TIs9c*b>W8R0i1@0_|-fzeRbm7$8R84iL z9(#jBiTHamI5C&0VK8K%hC)YoFt&}xPyU%(ui5OYx(Ef3tRflr4;rnaG`iA-kEZjH zeaAMsw2mFxGByQTYTaFPgLd*EDIf5Cd8kSga*1nBq=g(k_s`(t%IbJW&aHRbkpZ{U eKY|6;+e}p@hCp9oa2oEYj@rZ5d+H5qN3}oQ#}+F9 literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue142/errorclass_failure.rst b/doc/doc_tests/test_issue142/errorclass_failure.rst new file mode 100644 index 0000000..c4ce287 --- /dev/null +++ b/doc/doc_tests/test_issue142/errorclass_failure.rst @@ -0,0 +1,124 @@ +Failure of Errorclasses +----------------------- + +Errorclasses (skips, deprecations, etc.) define whether or not they +represent test failures. + + >>> import os + >>> import sys + >>> from nose.plugins.plugintest import run_buffered as run + >>> from nose.plugins.skip import Skip + >>> from nose.plugins.deprecated import Deprecated + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> sys.path.insert(0, support) + >>> from errorclass_failure_plugin import Todo, TodoPlugin, \ + ... NonFailureTodoPlugin + >>> todo_test = os.path.join(support, 'errorclass_failing_test.py') + >>> misc_test = os.path.join(support, 'errorclass_tests.py') + +nose.plugins.errorclass.ErrorClass has an argument ``isfailure``. With a +true isfailure, when the errorclass' exception is raised by a test, +tracebacks are printed. + + >>> run(argv=["nosetests", "-v", "--with-todo", todo_test], + ... plugins=[TodoPlugin()]) # doctest: +REPORT_NDIFF + errorclass_failing_test.test_todo ... TODO: fix me + errorclass_failing_test.test_2 ... ok + + ====================================================================== + TODO: errorclass_failing_test.test_todo + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + Todo: fix me + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + FAILED (TODO=1) + + +Also, ``--stop`` stops the test run. + + >>> run(argv=["nosetests", "-v", "--with-todo", "--stop", todo_test], + ... plugins=[TodoPlugin()]) # doctest: +REPORT_NDIFF + errorclass_failing_test.test_todo ... TODO: fix me + + ====================================================================== + TODO: errorclass_failing_test.test_todo + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + Todo: fix me + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + FAILED (TODO=1) + + +With a false .isfailure, errorclass exceptions raised by tests are +treated as "ignored errors." For ignored errors, tracebacks are not +printed, and the test run does not stop. + + >>> run(argv=["nosetests", "-v", "--with-non-failure-todo", "--stop", + ... todo_test], + ... plugins=[NonFailureTodoPlugin()]) # doctest: +REPORT_NDIFF + errorclass_failing_test.test_todo ... TODO: fix me + errorclass_failing_test.test_2 ... ok + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + OK (TODO=1) + + +Exception detail strings of errorclass errors are always printed when +-v is in effect, regardless of whether the error is ignored. Note +that exception detail strings may have more than one line. + + >>> run(argv=["nosetests", "-v", "--with-todo", misc_test], + ... plugins=[TodoPlugin(), Skip(), Deprecated()]) + ... # doctest: +REPORT_NDIFF + errorclass_tests.test_todo ... TODO: fix me + errorclass_tests.test_2 ... ok + errorclass_tests.test_3 ... SKIP: skipety-skip + errorclass_tests.test_4 ... SKIP + errorclass_tests.test_5 ... DEPRECATED: spam + eggs + + spam + errorclass_tests.test_6 ... DEPRECATED: spam + + ====================================================================== + TODO: errorclass_tests.test_todo + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + Todo: fix me + + ---------------------------------------------------------------------- + Ran 6 tests in ...s + + FAILED (DEPRECATED=2, SKIP=2, TODO=1) + +Without -v, the exception detail strings are only displayed if the +error is not ignored (otherwise, there's no traceback). + + >>> run(argv=["nosetests", "--with-todo", misc_test], + ... plugins=[TodoPlugin(), Skip(), Deprecated()]) + ... # doctest: +REPORT_NDIFF + T.SSDD + ====================================================================== + TODO: errorclass_tests.test_todo + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + Todo: fix me + + ---------------------------------------------------------------------- + Ran 6 tests in ...s + + FAILED (DEPRECATED=2, SKIP=2, TODO=1) + +>>> sys.path.remove(support) diff --git a/doc/doc_tests/test_issue142/support/errorclass_failing_test$py.class b/doc/doc_tests/test_issue142/support/errorclass_failing_test$py.class new file mode 100644 index 0000000000000000000000000000000000000000..cf2de6b471729fc4d4fafd09c772d8ef52ccc2fd GIT binary patch literal 3473 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 literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue142/support/errorclass_tests.py b/doc/doc_tests/test_issue142/support/errorclass_tests.py new file mode 100644 index 0000000..4981224 --- /dev/null +++ b/doc/doc_tests/test_issue142/support/errorclass_tests.py @@ -0,0 +1,20 @@ +from errorclass_failure_plugin import Todo +from nose import SkipTest, DeprecatedTest + +def test_todo(): + raise Todo('fix me') + +def test_2(): + pass + +def test_3(): + raise SkipTest('skipety-skip') + +def test_4(): + raise SkipTest() + +def test_5(): + raise DeprecatedTest('spam\neggs\n\nspam') + +def test_6(): + raise DeprecatedTest('spam') diff --git a/doc/doc_tests/test_issue142/support/errorclass_tests.pyc b/doc/doc_tests/test_issue142/support/errorclass_tests.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e95e34c1ccfa7f451db897a691869053cb3f665 GIT binary patch literal 1102 zcmbVLO>fgc5S@+lX$T8=##fdHN8c}hh{0M$$e*yEx z#V!SCtyr^C}2W*Bw$K_JsfunD{ZP=nkyMc#vul@syF40Uavc-zQ_va5M<}f7 z{42AB?pTGV(wc2;3X}4CxmYe%;jTGlE-z1Mv2~CZ>N2}WSZ{M{8?#)mLfceTS-a5G zby;UcYHb^~sBRpJmT1TILXAbuq`InbAyO5bENdgeqh4!|V#GR>C>d81Mvv1W`%{V(Rq4?; z`@Qvjz_ze|N9w$S516lKD3D?6Xk&EeJkiICo+t`l@WQ=i^I(j2X>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> from nose.util import ls_tree + >>> print ls_tree(support) # doctest: +REPORT_NDIFF + |-- package1 + | |-- __init__.py + | `-- test_module.py + |-- package2c + | |-- __init__.py + | `-- test_module.py + `-- package2f + |-- __init__.py + `-- test_module.py + +In these packages, the tests are all defined in package1, and are imported +into package2f and package2c. + +.. Note :: + + The run() function in :mod:`nose.plugins.plugintest` reformats test result + output to remove timings, which will vary from run to run, and + redirects the output to stdout. + + >>> from nose.plugins.plugintest import run_buffered as run + +.. + +package1 has fixtures, which we can see by running all of the tests. Note +below that the test names reflect the modules into which the tests are +imported, not the source modules. + + >>> argv = [__file__, '-v', support] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + package1 setup + test (package1.test_module.TestCase) ... ok + package1.test_module.TestClass.test_class ... ok + package1.test_module.test_function ... ok + package2c setup + test (package2c.test_module.TestCase) ... ok + package2c.test_module.TestClass.test_class ... ok + package2f setup + package2f.test_module.test_function ... ok + + ---------------------------------------------------------------------- + Ran 6 tests in ...s + + OK + +When tests are run in package2f or package2c, only the fixtures from those +packages are executed. + + >>> argv = [__file__, '-v', os.path.join(support, 'package2f')] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + package2f setup + package2f.test_module.test_function ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + >>> argv = [__file__, '-v', os.path.join(support, 'package2c')] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + package2c setup + test (package2c.test_module.TestCase) ... ok + package2c.test_module.TestClass.test_class ... ok + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + OK + +This also applies when only the specific tests are selected via the +command-line. + + >>> argv = [__file__, '-v', + ... os.path.join(support, 'package2c', 'test_module.py') + + ... ':TestClass.test_class'] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + package2c setup + package2c.test_module.TestClass.test_class ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + >>> argv = [__file__, '-v', + ... os.path.join(support, 'package2c', 'test_module.py') + + ... ':TestCase.test'] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + package2c setup + test (package2c.test_module.TestCase) ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + >>> argv = [__file__, '-v', + ... os.path.join(support, 'package2f', 'test_module.py') + + ... ':test_function'] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + package2f setup + package2f.test_module.test_function ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK diff --git a/doc/doc_tests/test_issue145/support/package1/__init__$py.class b/doc/doc_tests/test_issue145/support/package1/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..1186513d2dcf58eb8944267b1d9b3e3423f1f8aa GIT binary patch literal 2758 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 literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue145/support/package1/test_module.py b/doc/doc_tests/test_issue145/support/package1/test_module.py new file mode 100644 index 0000000..0c5ac78 --- /dev/null +++ b/doc/doc_tests/test_issue145/support/package1/test_module.py @@ -0,0 +1,12 @@ +import unittest + +def test_function(): + pass + +class TestClass: + def test_class(self): + pass + +class TestCase(unittest.TestCase): + def test(self): + pass diff --git a/doc/doc_tests/test_issue145/support/package1/test_module.pyc b/doc/doc_tests/test_issue145/support/package1/test_module.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d033ce29b804b5f18b792a947f39e48bdaac479 GIT binary patch literal 807 zcmcIh%TB{E5Zokfp+!9)ej=w@LFEjh_a0m>^kP|w15xu*?I0nJ@DY3`U%-yjHVBC$ zkuq7Y*OQrX^);WpeJAks&G~ z%6WTy+K6y^6Hl0CU=Mh;0{f_E-^A#wV+Q&K?wNa{-4`#~8R4o{)QvT2Z%U*2n=F#W zwaB|VbwyL_QaNLt6m3knAOc>(l9D3Q2BaoL#3FI=+7z$ACj3)|B}WgX9jm z830~V{QV45y{s4#-8q9&wXTd(epad8l~NLv1D&rtrvw2m#xqRlTx;cqzh!vm5)HU|%}=fZSX7Y?Knv3jc#vwpx=68Aam4(z=s P9J>Wr2$+V`=xX#0ukCk+ literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue145/support/package2c/__init__$py.class b/doc/doc_tests/test_issue145/support/package2c/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..ddc721a778f24420dd771409666fe7330eb03f05 GIT binary patch literal 2762 zcmbtWU0WMP6n=*Umc-D~NPz;iV6i3y*al0rrma$1YAR49hyv=mB$KeP*$c$>FZ9 z8Jr=U$R?L#XhmBTVMq+|#(feZ46z4g%dNS082Z=AXwKb!Z ziJ=#lBIrRM`LXDgs~ZfLgD#-Pq+n+Nm!lNUE3~Jm=&Ivt3@jVNAch2{SBb`X)8H`- z3y@K~#?TuO%d}bGgy14x7YGszZMQW;bMG>2o&n$tqMMx&@cvjdBS=NjiBX1Dl7VNn zW1OK~D8kIEI)j{;-Bh<#SyzpcT;uI*kJphF>X~4;7_c|F%y6Z-43^Y&olBUcY)A;Q zNAPA8H}Mvc>TtctaBlYaH4}kvBO?gB&2YYM8LWjuWcD_mRgFTzyQG<-ZI-i!%WZ=f z7{-GD2Si)f&pk{>F@^g=Ym~c1nN+k**(keZOEyf0D>m1;N@IFFJw729Yid_@#pRCc z$OSWBn~DF5<~SZt-uesY*A?(0TfTB)m^|c)M&C zJe}Wd#~ex3;7^3&7=BJ<*8;Bew2w^Vn(XRo02fCI?B84_$albT*=$g`s-?tZEQkdA zAO==VjvV1Had%GDC_VcUO}COr%Kn<}M8KKJ<#u!nMcs&!URGoH5X)lrNkCl^NM=p7 zz-&cu&_#Dp{n3@9jA6(nvO)6uU!74% ztVM{x7M0kaxdC!ms2s5&VVhy_B#yF0gxK=bLUx(k4sk$)wr;Ds$3xWGskih8uAf`d zsHe9AEd7o1%Tro*`Vx+{cM0E;x&>KO=Vqx{&wJ%{Zu?0`xlW$|`K>u2$!N~>>^v8D z#BhmVyL8I3=4f=4Q-)#Ew*nz_FhoJ$r0Abex9=52l%V#Ce&~->T>Oy|&>yKw(J0Y+ z;){OI=-+*`QJDuuQ{jEI@8aD23v{leen;2XE|Tm3WAmxFJhqQF*nFzx4_w{FwH;c| z@I5`-XcTiwuDZujnhDCfQmwwn{zF`8wo{;>RHo-M+M%Gs-8<}rR{zmQvP^uQS!#Zgc z;KRFE5&;1be1wl{)%FdL`S^m5N6dI zgM}|@_UA;gAo<$J=ve=+;w)g$e+o?E%7B4=I6M3Bc373(AL!j2p%_3|LJyO~@Et-l luT2M=8HSq~TJR$|ZNWqQL}M4tp3?D^EWX57V)6)I{|6Wx;Bf!| literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue145/support/package2c/__init__.py b/doc/doc_tests/test_issue145/support/package2c/__init__.py new file mode 100644 index 0000000..106401f --- /dev/null +++ b/doc/doc_tests/test_issue145/support/package2c/__init__.py @@ -0,0 +1,2 @@ +def setup(): + print 'package2c setup' diff --git a/doc/doc_tests/test_issue145/support/package2c/__init__.pyc b/doc/doc_tests/test_issue145/support/package2c/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..394ffd84d8c26c972e2cacf731b005befd87c09c GIT binary patch literal 304 zcmZ8cu?oUK3{3?G1@RXgJN2mOtl*$i2i;tb<9f{ zlANET;q5WYEOam8yyTA~4mrRbV9y|eUEE{k$F~ySKztFbH*9`;G90rOj6Ip7o7xFF zYC_Bs`~^C&rb6v6hMb~vP-TTq*RF7ebDE^dR2`bqlCMfrXmUuX^2&Bdxb}#ROJ{S1 uO$b#@>N8kr?MqKuC*ewc9@Q=)2cnz;Q5Fl?O87!jY8TxKBB?IgCZR9azdhst literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue145/support/package2c/test_module$py.class b/doc/doc_tests/test_issue145/support/package2c/test_module$py.class new file mode 100644 index 0000000000000000000000000000000000000000..4a98b218fc406b60d38984ff694cb48b5a099a28 GIT binary patch literal 2512 zcmbtVZC4vb6n-Wf*d#73O=-(ZZK+)~}DGz}g?m|^rlJJM7`Giz$WwRN*5;|+%J zHf>uoG{*^Hgdx=V)Eqi;mEn3n2)*8rag8BD6U%n9wpC|%7F+Lko!K2+^2xSKQZByh zWAm6$a2;b2L~(<_iL|Yh21|4IhZ{BE#LmFxjSAc6H0#*7gi8;~i2~ zOkjS1cmy%b5(bAGRe`~~-ube6;eKZtEt1UmWi}Zp1#?J-p@Q@pDA7N^Y`U@_PrBN$ z^&`@#>ed^oX*s-PbAyvCO6gQOHLq4Zz?xAKFdVgFmAzR9mhL!Bo?ci~on}J_MfHW2 zYP3RFAmD?HoGJ8u2Bk_W%b6~>O8g3s}Vm~E2J{)-bx z)-A2VZ5eqgfiiVFmlvp882piCxsGdV4Tgt)9kY|gAV#r#el(+C3xx>2!Zzh*H_dKt zi-Z1pkNKvhponh-W8YG9B%XSXFx>MYtY-^}<$gZ_`PNp`G({PoW0vuZAx{3Yy1`9) z71k`1cxaYg%iiKugZe#{{`q!>`HKqgv}T{Dq8EvR=O{(+9lmE6p$2J`&MK6l5X5xnL=2KB7z-strP(O|2q`CO6P4rNX>4jpMmlS}lhPBKjS?%&wf z={+3+So&w@*Pm!xKlK-$Rv9GtprDB1>N-7?o6Y(jw>#~XDx99*^5KRMVx(X-?J^fP z!!SWt=TelS?&x%ZtEOquJ52~3YVRubVTKwcjEh!EP{&0->5NovG!B!USfO8ozMj*Y zLTHgq1Wyr)Muz@^vYC{kR|?5sbhMC61lqVs3-e3CMD)r8ZJK0D!DO)WJ$m+?2>gya z$C!CRo)~`cqKG*Y1?k0F!U*94QnRm+m_0!{y4XhM1RqHfMctT7L_d)v=@@B=G3nYU z+W7PY*_U)ec%pLl{EX7bXf1e*tcVDNk;7xpI!!Tzb?i^5PorPH!p13{wDI+98&A8w z!q~yCw>#R|ORPlIHngrchCP%$dxiK3lD9jvbIGaSL@dGR>%Xy6@E5F`w@u3n#OyeN nGE^pu!Gc6{Z+iPOLjVq)4bW+q#xa`hQ+z##100IUV;KJdPZ6>H literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue145/support/package2c/test_module.py b/doc/doc_tests/test_issue145/support/package2c/test_module.py new file mode 100644 index 0000000..6affbf1 --- /dev/null +++ b/doc/doc_tests/test_issue145/support/package2c/test_module.py @@ -0,0 +1 @@ +from package1.test_module import TestClass, TestCase diff --git a/doc/doc_tests/test_issue145/support/package2c/test_module.pyc b/doc/doc_tests/test_issue145/support/package2c/test_module.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2313273820073cdc9eefecec58889cf817c5399 GIT binary patch literal 298 zcmY+8!HU8_42CoHq6^*xU!kWC75A**y$9WsmoiMVOX;*TG^rrIh|lR0Y^GMwf&ZI7 zkmOJMSbp8^+rq;168ZNcS_>+G2EZveD{u*%6Ht|cQyQB_&_dh~5+9r)qWP;bghe`& zidTYQY-9c~&saLk)>nf={+Q(V$#L^BW*n6u6+OYVI34=>Vi+~IL3f_8?9rhFmU)(E v8~r$c$>FZ9 z8Jr=U$R?L#XhmBTVMq+|#(feZ46z4g%dNS082Z=AXwKb!Z ziJ=#lBIrRM`LXDgs~ZfLgD#-Pq+n+Nm!lNUE3~Jm=&Ivt3@jVNAch2{SBb`X)8H`- z3y@K~#?TuO%d}bGgy14x7YGszZMQW;bMG>2o&n$tqMMx&@cvjdBS=NjiBX1Dl7VNn zW1OK~D8kIEI)j{;-Bh<#SyzpcT;uI*kJphF>X~4;7_c|F%y6Z-43^Y&olBUcY)A;Q zNAPA8H}Mvc>TtctaBlYaH4}kvBO?gB&2YYM8LWjuWcD_mRgFTzyQG<-ZI-i!%WZ=f z7{-GD2Si)f&pk{>F@^g=Ym~c1nN+k**(keZOEyf0D>m1;N@IFFJw729Yid_@#pRCc z$OSWBn~DF5<~SZt-uesY*A?(0TfTB)m^|c)M&C zJe}Wd#~ex3;7^3&7=BJ<*8;Bew2w^Vn(XRo02fCI?B84_$albT*=$g`s-?tZEQkdA zAO==VjvV1Had%GDC_VcUO}COr%Kn<}M8KKJ<#u!nMcs&!URGoH5X)lrNkCl^NM=p7 zz-&cu&_#Dp{n3@9jA6(nvO)6uU!74% ztVM{x7M0kaxdC!ms2s5&VVhy_B#yF0gxK=bLUx(k4sk$)wr;Ds$3xWGskih8uAf`d zsHe9AEd7o1%Tro*`Vx+{cM0E;x&>KO=Vqx{&wJ%{Zu?0`xlW$|`K>u2$!N~>>^v8D z#BhmVyL8I3=4f=4Q-)#Ew*nz_FhoJ$r0Abex9=52l%V#Ce&~->T>Oy|&>yKw(J0Y+ z;){OI=-+*`QJDuuQ{jEI@8aD23v{leen;2XE|Tm3WAmxFJhqQF*nFzx4_w{FwH;c| z@I5`-XcTiwuDZujnhDCfQmwwn{zF`8wo{;>RHo-M+M%Gs-8<}rR{zmQvP^uQS!#Zgc z;KRFE5&;1be1wl{)%FdL`S^m5N6dI zgM}|@_UA;gAo<$J=ve=+;w)g$e+o?E%7B4=I6M3Bc373(AL!j2p%_3|LJyO~@Et-l luT2M=8HSq~TJR$|ZNWqQL}M4tp3?D^EWX57V)6)I{|9w&;C%o9 literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue145/support/package2f/__init__.py b/doc/doc_tests/test_issue145/support/package2f/__init__.py new file mode 100644 index 0000000..fc203eb --- /dev/null +++ b/doc/doc_tests/test_issue145/support/package2f/__init__.py @@ -0,0 +1,2 @@ +def setup(): + print 'package2f setup' diff --git a/doc/doc_tests/test_issue145/support/package2f/__init__.pyc b/doc/doc_tests/test_issue145/support/package2f/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28350da5d26f18a45e3f178ca41f858b093f273a GIT binary patch literal 304 zcmZ8cy$ZrW3{C|Hh2kqXcIr{7vx0+89dvU!j(U~D{v1szh^r6bbNU1(RYX0woRc_-XR7GxoLp0zQj)2mt$fNp2SI!d#2~xeGYyL~ zgyLFaSHU2Lq6k4|7_I5W7kY)KQyzD{Le(zSO&&&sVdO|Z)>TutDr(+y467pJ4Tj;? zwQ}7mdWLOaJ%nFipWYCL;8Gk|EZeZLT5XO+t3d z764E5x3AzX?nQ71Qw&4PhGlpw3^xKoOzbk;?RB&6gTA95m|Vu&#HYB3c^dCTaUXGt z!R2OI#NZb-Q&ulkb*9!JqKx&^$(UC#i@6ArNHGi)h^j%^w8=o047KJM$HYw4tJYM@ zc6q_!CfDgqr_!m6dd?EvEQl~%wPY84sTG#tx^(0%1fX~EkSny{WGf}enM8PLm7t#jV>@J*$WW&}=+>xIJjFKU=G3irZi~J4I-domrCM+m2mCk#^ofOT9UvP^dakZWw$ElafVMa(kx7!qWE-7vXDyI{?>2#0#nvz={T zHfhS!yuG;2kh#q8H5yfJz=G(EM8R`>6U9Ehr8!Om(kz^JC__H31<~(+Z6!gm0YQR; z01~{$Kn6AJy-x4d8o}VGt2u3#J1%8TFb*E;W}Qp4OeYv71MhF{7_>zP0ha#B{pClx z)(!oog(`zqL`XoyaCL*$<5sxuKh0o8h*+hP}iLctWBKR85{M)hCT>>DMY@*P%#_%0Up+eu@EnPj3g1fkWpg QNoDe1(QpVu2v2bIAHUVCzW@LL literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_issue145/support/package2f/test_module.py b/doc/doc_tests/test_issue145/support/package2f/test_module.py new file mode 100644 index 0000000..e353c62 --- /dev/null +++ b/doc/doc_tests/test_issue145/support/package2f/test_module.py @@ -0,0 +1 @@ +from package1.test_module import test_function diff --git a/doc/doc_tests/test_issue145/support/package2f/test_module.pyc b/doc/doc_tests/test_issue145/support/package2f/test_module.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a84068dc90b4a5552ce8191d10a1376ea801cdae GIT binary patch literal 269 zcmZWkK?=e!5ZrhadJud>PYEgYtS{(6Z(d4BqJ*YRXf}fQ5#Q+xOkxGWz|Jn2-JRL- zRjnV-wv({DRmz7HVUs`s3;>qEnE+7)PIabhlKCnD>VW+vP7;lqx>> from nose.plugins.plugintest import run_buffered as run + +.. + + >>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> test_not_shared = os.path.join(support, 'test_not_shared.py') + >>> test_shared = os.path.join(support, 'test_shared.py') + >>> test_can_split = os.path.join(support, 'test_can_split.py') + +The module with shared fixtures passes. + + >>> run(argv=['nosetests', '-v', test_shared]) #doctest: +REPORT_NDIFF + setup called + test_shared.TestMe.test_one ... ok + test_shared.test_a ... ok + test_shared.test_b ... ok + teardown called + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + OK + +As does the module with no fixture annotations. + + >>> run(argv=['nosetests', '-v', test_not_shared]) #doctest: +REPORT_NDIFF + setup called + test_not_shared.TestMe.test_one ... ok + test_not_shared.test_a ... ok + test_not_shared.test_b ... ok + teardown called + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + OK + +And the module that marks its fixtures as re-entrant. + + >>> run(argv=['nosetests', '-v', test_can_split]) #doctest: +REPORT_NDIFF + setup called + test_can_split.TestMe.test_one ... ok + test_can_split.test_a ... ok + test_can_split.test_b ... ok + teardown called + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + OK + +However, when run with the ``--processes=2`` switch, each test module +behaves differently. + + >>> from nose.plugins.multiprocess import MultiProcess + +The module marked ``_multiprocess_shared_`` executes correctly, although as with +any use of the multiprocess plugin, the order in which the tests execute is +indeterminate. + +First we have to reset all of the test modules. + + >>> import sys + >>> sys.modules['test_not_shared'].called[:] = [] + >>> sys.modules['test_can_split'].called[:] = [] + +Then we can run the tests again with the multiprocess plugin active. + + >>> run(argv=['nosetests', '-v', '--processes=2', test_shared], + ... plugins=[MultiProcess()]) #doctest: +ELLIPSIS + setup called + test_shared.... ok + teardown called + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + OK + +As does the one not marked -- however in this case, ``--processes=2`` +will do *nothing at all*: since the tests are in a module with +unmarked fixtures, the entire test module will be dispatched to a +single runner process. + +However, the module marked ``_multiprocess_can_split_`` will fail, since +the fixtures *are not reentrant*. A module such as this *must not* be +marked ``_multiprocess_can_split_``, or tests will fail in one or more +runner processes as fixtures are re-executed. + +We have to reset all of the test modules again. + + >>> import sys + >>> sys.modules['test_not_shared'].called[:] = [] + >>> sys.modules['test_can_split'].called[:] = [] + +Then we can run again and see the failures. + + >>> run(argv=['nosetests', '-v', '--processes=2', test_can_split], + ... plugins=[MultiProcess()]) #doctest: +ELLIPSIS + setup called + teardown called + test_can_split.... + ... + FAILED (failures=...) + +Other differences in test running +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The main difference between using the multiprocess plugin and not doing so +is obviously that tests run concurrently under multiprocess. However, there +are a few other differences that may impact your test suite: + +* More tests may be found + + Because tests are dispatched to worker processes by name, a worker + process may find and run tests in a module that would not be found during a + normal test run. For instance, if a non-test module contains a test-like + function, that function would be discovered as a test in a worker process + if the entire module is dispatched to the worker. This is because worker + processes load tests in *directed* mode -- the same way that nose loads + tests when you explicitly name a module -- rather than in *discovered* mode, + the mode nose uses when looking for tests in a directory. + +* Out-of-order output + + Test results are collected by workers and returned to the master process for + output. Since different processes may complete their tests at different + times, test result output order is not determinate. + +* Plugin interaction warning + + The multiprocess plugin does not work well with other plugins that expect to + wrap or gain control of the test-running process. Examples from nose's + builtin plugins include coverage and profiling: a test run using + both multiprocess and either of those is likely to fail in some + confusing and spectacular way. + +* Python 2.6 warning + + This is unlikely to impact you unless you are writing tests for nose itself, + but be aware that under python 2.6, the multiprocess plugin is not + re-entrant. For example, when running nose with the plugin active, you can't + use subprocess to launch another copy of nose that also uses the + multiprocess plugin. This is why this test is skipped under python 2.6 when + run with the ``--processes`` switch. diff --git a/doc/doc_tests/test_multiprocess/multiprocess_fixtures$py.class b/doc/doc_tests/test_multiprocess/multiprocess_fixtures$py.class new file mode 100644 index 0000000000000000000000000000000000000000..c373f1708ec2c2364f4c690773cea9bc6ca9579c GIT binary patch literal 4364 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 literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_multiprocess/support/test_can_split.py b/doc/doc_tests/test_multiprocess/support/test_can_split.py new file mode 100644 index 0000000..a7ae6e7 --- /dev/null +++ b/doc/doc_tests/test_multiprocess/support/test_can_split.py @@ -0,0 +1,30 @@ +import sys +called = [] + +_multiprocess_can_split_ = 1 + +def setup(): + print >> sys.stderr, "setup called" + called.append('setup') + + +def teardown(): + print >> sys.stderr, "teardown called" + called.append('teardown') + + +def test_a(): + assert len(called) == 1, "len(%s) !=1" % called + + +def test_b(): + assert len(called) == 1, "len(%s) !=1" % called + + +class TestMe: + def setup_class(cls): + cls._setup = True + setup_class = classmethod(setup_class) + + def test_one(self): + assert self._setup, "Class was not set up" diff --git a/doc/doc_tests/test_multiprocess/support/test_can_split.pyc b/doc/doc_tests/test_multiprocess/support/test_can_split.pyc new file mode 100644 index 0000000000000000000000000000000000000000..311493e24a8b9fd61d5f9718fd4c60ed3e9668b2 GIT binary patch literal 2152 zcmd5-!EVz)5S?|Lv?*=jfDjx&;ZS5QwNh~e1XLVAA_u1zdRZCo77UJUdDm1GoG2f` zZ}BVq0L&YwNf97%Kq*dkJ+nLBoq79a_UpH;jSpY$O){E4A-^ASb5rgN{}sI>IwX3{ zy&@CPVL+xwpIPmZ2}KCW^hM~C8Hg|-vqok}W*zD&(aS^D6-}cJUN`^F&m>nYE;jD5 zvd_)k;?A>;qL4obo;~*g<-Aour-~H2G>u?8ahtHVzO6@DS`^m!{@FkjVhvci6Xyqb zdt;kM#JMp>)4H~$iCCg#=Z;v7>n4AndK>#$9ha4}y0L{#`90np@9xD%ZJGJJD$_!H z>%5CimCYsSde#;`ubV2f&c&{+>#Fgw%w%b)U0vjUw?0X*v$zL`No}bNZ#-~*pWENV zaha{XO&e1kmuHBqpBocU5@4PL=&(bAeV3t&w95E#VwbqNKgYkr(@Y1iaQsY&w*l4L zq?i=U2p+xmDLBsxA(g|Lg)O5UcXxFA!B_|*eBeO|+;6kRW9MvxQ+(PqRdaDL2rY#i z=@o#1!%>J3g4$(cop0ECGH)caUybey_TS@{tMI%;cy zAVFD8gcev^5OKCRx|qn<;Q|V3Roa^@&%=&TI6Nk`F4LLS+T&i)dRCdXU^PL03vPng z%=$N#NswL3I7C|EF(z;k$K#NG12qgbgU%ZS(p6>@We=O5ZV> sys.stderr, "setup called" + called.append('setup') + + +def teardown(): + print >> sys.stderr, "teardown called" + called.append('teardown') + + +def test_a(): + assert len(called) == 1, "len(%s) !=1" % called + + +def test_b(): + assert len(called) == 1, "len(%s) !=1" % called + + +class TestMe: + def setup_class(cls): + cls._setup = True + setup_class = classmethod(setup_class) + + def test_one(self): + assert self._setup, "Class was not set up" diff --git a/doc/doc_tests/test_multiprocess/support/test_not_shared.pyc b/doc/doc_tests/test_multiprocess/support/test_not_shared.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d8a9eea7e61f61a435726b281313e9892956297 GIT binary patch literal 2150 zcmd5-!EVz)5S?|Lv?*=jfDjx&;ZS5QwTd_b0wN9|k%Q96IJM;F<`0KZ=jSpY$PcoW6A&-x_xhZ#s|BBub9T2_Y zUXcmtARyDD&#d;ygd&7w`Xcnn3`7`^StBzfvkvu?=+y!1il)&9@0)+;XOb%x&kpXf zGUn!Pap&1aQOGX@&tLd}ay}}cQ$>nhnntjlxJ_7F-`1lnEedOVe=!k-*aKGX#Q6b6 zZ*0?uI5);@TGzHT5lgh}97jrV-Q@36Z(~2J79M%H|SuJ!=b}*G-jK=VI5^b=CM-R?5n2ca%2P?A9jta{_GBKq0J6`i^wOTC1VDKTlRMnmt{#z;n}BSO ztW*e;Cnb|1CDRD+WZ*m+6v?SA7MM~Wi8NCRB?Ze9vG!o%e$!fUK}&^^WuZ@2xFuGr z%oHzKf^w&`B6aQtd%2*=FY#UHMGh_}sy8@OByG;J1Sm`#upVQh(Q)cVT$M%~>8Py* zg9NoT5n^C(fyCM3@Pa~LhYu*LRcUXsJdZm<@$gvGx=d$QYmcvs*0ajA1*-{iT!0hE zX4W57CP9KN0}*MJ7n#6ET#!Qw4%9H%3_5oZNmrjynx#Pf^dZsmxkwOoBDO> sys.stderr, "setup called" + _log('setup') + + +def teardown(): + print >> sys.stderr, "teardown called" + _clear() + + +def test_a(): + assert len(logged()) == 1, "len(%s) !=1" % called + + +def test_b(): + assert len(logged()) == 1, "len(%s) !=1" % called + + +class TestMe: + def setup_class(cls): + cls._setup = True + setup_class = classmethod(setup_class) + + def test_one(self): + assert self._setup, "Class was not set up" diff --git a/doc/doc_tests/test_multiprocess/support/test_shared.pyc b/doc/doc_tests/test_multiprocess/support/test_shared.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d48dca893e977dca4ef825429d92d81825f85ef0 GIT binary patch literal 3083 zcmc&$TW{k;6h3w?O|ogb+r4lV3PM28qJ|PiTg!pW0+YuAWMJTf!F)f+(#I)rZcIb=gs4x)IRpFYL z9TkRRdMfOS>8r3OW}w2p7`%r0A%_^vnLhGxgV~?^#k?6VLPHGz&0J z*@b8=v|}{)Av%x$fZs#Gr-3{Ph~o)14kTy>RDLyR@w@;WoQ%22)N#AyuH(*wahuz* zD=S;5z_UI1JFhcuRZ8>H*)hNK+~QG^dk*IFF?!L?rI037ojppto%lsHDez5P+uSDj zJvux(e19@u7OBt5BFSTKop%#crYni#c(KfVR@G%{otwC2Rh6}$=t{%OVf9#5jPvre zkMmrQ=Lh@2F8-*1K2+@(%~3H>1tLKYGzTN)4GB2aUX6nSEGajnV>%{o#a22!sY?8r z?_x4@^DMWKa=`Jj$g|?%2LIKPKn>y))YjJ_IoeNxn+*k2krQhQPBDI}Ost*ei~GCk zI6~h!_~DlPMEBsAQ@kTms~k~O$c^o}Yy92OePssl?FRmpnV-mE{ZnghLsDOsjplZxOH{eB_a&EK<9-p#f5(0CJp9$ZZlABfb4+<&tZ*yZ=l~1RO?KpEOCWvk zl6tQ}{tvKJ5n$o5fQaCRr5}V+?IFa~+OXf9pTo~hFy8i#kO2i;jN}@ZIUO=~X_}{<1 zfv;w{huZOtO);wv-TZSj_ZB+TcvO1C{4)rlSQL9Ljga9koNPukRktpjhO<# zGLdS4fYIn#2Ma%%x?AXvxzXr6aigO2BcL2DtEM=-idFRrB}Hq3v-viz1U+b?fl(Ir zJ;*D}O)=+U92d#L#<3shU{RW7j$y?7qSr2^YhnFaX`&G(x8&o|%Q*Wknq#bYgMK&+ z8&#P4BSLAiG%r7%rO0G8p=s8PV_s2!lI7vd)>efFWmYuT7RK^lu2}Kvwy8x;lYSHG tdKir#(Mckf9Mx<~tSGpT8l!zeKXz1W7!1RM;C9#z`#b$kzm3Me{U;1$%ZdO1 literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst b/doc/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst new file mode 100644 index 0000000..9513fdf --- /dev/null +++ b/doc/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst @@ -0,0 +1,89 @@ +Restricted Plugin Managers +-------------------------- + +In some cases, such as running under the ``python setup.py test`` command, +nose is not able to use all available plugins. In those cases, a +`nose.plugins.manager.RestrictedPluginManager` is used to exclude plugins that +implement API methods that nose is unable to call. + +Support files for this test are in the support directory. + + >>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + +For this test, we'll use a simple plugin that implements the ``startTest`` +method. + + >>> from nose.plugins.base import Plugin + >>> from nose.plugins.manager import RestrictedPluginManager + >>> class StartPlugin(Plugin): + ... def startTest(self, test): + ... print "started %s" % test + +.. Note :: + + The run() function in :mod:`nose.plugins.plugintest` reformats test result + output to remove timings, which will vary from run to run, and + redirects the output to stdout. + + >>> from nose.plugins.plugintest import run_buffered as run + +.. + +When run with a normal plugin manager, the plugin executes. + + >>> argv = ['plugintest', '-v', '--with-startplugin', support] + >>> run(argv=argv, plugins=[StartPlugin()]) # doctest: +REPORT_NDIFF + started test.test + test.test ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + +However, when run with a restricted plugin manager configured to exclude +plugins implementing `startTest`, an exception is raised and nose exits. + + >>> restricted = RestrictedPluginManager( + ... plugins=[StartPlugin()], exclude=('startTest',), load=False) + >>> run(argv=argv, plugins=restricted) #doctest: +REPORT_NDIFF +ELLIPSIS + Traceback (most recent call last): + ... + SystemExit: ... + +Errors are only raised when options defined by excluded plugins are used. + + >>> argv = ['plugintest', '-v', support] + >>> run(argv=argv, plugins=restricted) # doctest: +REPORT_NDIFF + test.test ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + +When a disabled option appears in a configuration file, instead of on the +command line, a warning is raised instead of an exception. + + >>> argv = ['plugintest', '-v', '-c', os.path.join(support, 'start.cfg'), + ... support] + >>> run(argv=argv, plugins=restricted) # doctest: +ELLIPSIS + RuntimeWarning: Option 'with-startplugin' in config file '...start.cfg' ignored: excluded by runtime environment + test.test ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + +However, if an option appears in a configuration file that is not recognized +either as an option defined by nose, or by an active or excluded plugin, an +error is raised. + + >>> argv = ['plugintest', '-v', '-c', os.path.join(support, 'bad.cfg'), + ... support] + >>> run(argv=argv, plugins=restricted) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ConfigError: Error reading config file '...bad.cfg': no such option 'with-meltedcheese' diff --git a/doc/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst.py3.patch b/doc/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst.py3.patch new file mode 100644 index 0000000..51a09b4 --- /dev/null +++ b/doc/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst.py3.patch @@ -0,0 +1,9 @@ +--- restricted_plugin_options.rst.orig 2010-08-31 10:57:04.000000000 -0700 ++++ restricted_plugin_options.rst 2010-08-31 10:57:51.000000000 -0700 +@@ -86,5 +86,5 @@ + >>> run(argv=argv, plugins=restricted) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... +- ConfigError: Error reading config file '...bad.cfg': no such option 'with-meltedcheese' ++ nose.config.ConfigError: Error reading config file '...bad.cfg': no such option 'with-meltedcheese' + diff --git a/doc/doc_tests/test_restricted_plugin_options/support/bad.cfg b/doc/doc_tests/test_restricted_plugin_options/support/bad.cfg new file mode 100644 index 0000000..c050ec4 --- /dev/null +++ b/doc/doc_tests/test_restricted_plugin_options/support/bad.cfg @@ -0,0 +1,2 @@ +[nosetests] +with-meltedcheese=1 \ No newline at end of file diff --git a/doc/doc_tests/test_restricted_plugin_options/support/start.cfg b/doc/doc_tests/test_restricted_plugin_options/support/start.cfg new file mode 100644 index 0000000..ea1e289 --- /dev/null +++ b/doc/doc_tests/test_restricted_plugin_options/support/start.cfg @@ -0,0 +1,2 @@ +[nosetests] +with-startplugin=1 \ No newline at end of file diff --git a/doc/doc_tests/test_restricted_plugin_options/support/test$py.class b/doc/doc_tests/test_restricted_plugin_options/support/test$py.class new file mode 100644 index 0000000000000000000000000000000000000000..2336360216122f6374d26c7c1b119b01de772d75 GIT binary patch literal 2643 zcmbtVT~`}b6y28uhQy(zk+u{l1&cHxz!(&54Wg75O$jLyOa;_9BsXE`WM-V1(8iBP ze@A`x2|A z5RPXO1sR>_iXse&A=auVA;KU(tXZya-eDNrYVTae8;~Q2qK9Fi)!b6uD7l(xNVv=p zsT8ZaxvlDCbKrF*uOJqo2;XGra=GJ*Kl13^ixhb)8H}&ghmvD zBAxE)0= z#wZ05aW}>px<#qXlBzQ(@$9a;rz*N?RF%AIYeuz|AZHX12IEMGvQ98u4bV*#7;d$% z&$_y;a|u&K(|9vFC5mYfVZ#pBD^$Czk1IhpgLegV?=f@~Ng}9}e#8u#V%gdrF<5bH zmSUI=FWOw^DxIn6)bxx}@fo7(Md86w%4W&GiQgpcQ1&I4my4EOuWClov;?CZ#i?7C zX}gLLo3nat*9Kxn7^<<&18WIMoJ zCKEJG{JvPir%~#p&two`M2?Rr{wjA@RL!7T$J?qBRcfBhwRnKfM4=lu1?wlHWb<fhNYWcY7K@ht^#EO@1575vN9aDlrQA#OZX|z4-_!v{*)iVEC1ZD{j*w)zWXB)4 zae$$Hl4p2M-(7TyJ2lmr#c{d`s4gWt$znSl;h&n9zC{#I(BtVWPaGpPafsR2y%T(V zh=ow-FD$N1CSwmnq0j+lLyU#4^x_1WLoA;?-U)k;VegT7kE6elSrHn|1>Z?8og&=u z0hUDyKm<8F_FqRR0P))Q62lv@)nlw3Vf_S~6DRnlX)A&vzV)SJo-6?n8$Lm$X^o+Z z9sm7j0#(2|J~25p_^a>*jCkK7CQ%$<=m@*}N6`0K6VL|zw?a`2AuM5-N#fW;i0=M% hq}{_ZPewa%7eCP1M>mt)TN!)@TU;K&`47k7!Px)+ literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_restricted_plugin_options/support/test.py b/doc/doc_tests/test_restricted_plugin_options/support/test.py new file mode 100644 index 0000000..f174823 --- /dev/null +++ b/doc/doc_tests/test_restricted_plugin_options/support/test.py @@ -0,0 +1,2 @@ +def test(): + pass diff --git a/doc/doc_tests/test_restricted_plugin_options/support/test.pyc b/doc/doc_tests/test_restricted_plugin_options/support/test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46c102a9b86e031ebe63a8483a5a5303f176d1b9 GIT binary patch literal 281 zcmZ9G!3x4K42CoDB)#^HN4vrLe7~$prE0L-?FNfl1v#E%eWyyn6vZ%4}cS!4BBGFlOOIV-1@_{giFs`qAb(ld<|ogv%nFw7S>X&;0~g>wi#k`k;8H>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + +In this example, the project to be tested consists of a module and +package and associated tests, laid out like this:: + + >>> from nose.util import ls_tree + >>> print ls_tree(support) + |-- mymodule.py + |-- mypackage + | |-- __init__.py + | |-- strings.py + | `-- math + | |-- __init__.py + | `-- basic.py + `-- tests + |-- testlib.py + |-- math + | `-- basic.py + |-- mymodule + | `-- my_function.py + `-- strings + `-- cat.py + +Because the test modules do not include ``test`` in their names, +nose's default selector is unable to discover this project's tests. + +.. Note :: + + The run() function in :mod:`nose.plugins.plugintest` reformats test result + output to remove timings, which will vary from run to run, and + redirects the output to stdout. + + >>> from nose.plugins.plugintest import run_buffered as run + +.. + + >>> argv = [__file__, '-v', support] + >>> run(argv=argv) + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + +The tests for the example project follow a few basic conventions: + +* The are all located under the tests/ directory. +* Test modules are organized into groups under directories named for + the module or package they test. +* testlib is *not* a test module, but it must be importable by the + test modules. +* Test modules contain unitest.TestCase classes that are tests, and + may contain other functions or classes that are NOT tests, no matter + how they are named. + +We can codify those conventions in a selector class. + + >>> from nose.selector import Selector + >>> import unittest + >>> class MySelector(Selector): + ... def wantDirectory(self, dirname): + ... # we want the tests directory and all directories + ... # beneath it, and no others + ... parts = dirname.split(os.path.sep) + ... return 'tests' in parts + ... def wantFile(self, filename): + ... # we want python modules under tests/, except testlib + ... parts = filename.split(os.path.sep) + ... base, ext = os.path.splitext(parts[-1]) + ... return 'tests' in parts and ext == '.py' and base != 'testlib' + ... def wantModule(self, module): + ... # wantDirectory and wantFile above will ensure that + ... # we never see an unwanted module + ... return True + ... def wantFunction(self, function): + ... # never collect functions + ... return False + ... def wantClass(self, cls): + ... # only collect TestCase subclasses + ... return issubclass(cls, unittest.TestCase) + +To use our selector class, we need a plugin that can inject it into +the test loader. + + >>> from nose.plugins import Plugin + >>> class UseMySelector(Plugin): + ... enabled = True + ... def configure(self, options, conf): + ... pass # always on + ... def prepareTestLoader(self, loader): + ... loader.selector = MySelector(loader.config) + +Now we can execute a test run using the custom selector, and the +project's tests will be collected. + + >>> run(argv=argv, plugins=[UseMySelector()]) + test_add (basic.TestBasicMath) ... ok + test_sub (basic.TestBasicMath) ... ok + test_tuple_groups (my_function.MyFunction) ... ok + test_cat (cat.StringsCat) ... ok + + ---------------------------------------------------------------------- + Ran 4 tests in ...s + + OK diff --git a/doc/doc_tests/test_selector_plugin/support/mymodule$py.class b/doc/doc_tests/test_selector_plugin/support/mymodule$py.class new file mode 100644 index 0000000000000000000000000000000000000000..86c6620c81e1df8472738783c02bc828d882a469 GIT binary patch literal 3007 zcmbtWYg-#d6n=*dY?7vyMrfh*LM_%n61G&Uw$xTii>5+~1W{-;F3BWay4g+lLXBGQ z_s6?GKtKDbQUxA)>JRWo`FLitiKJQh;)i5rXU@KJ&U@Z-X8-=@&%XeS;U@;EG<1q# zE-`IdWu;nkZkmQtFfFdk)}}1A!pGY-=5JbDE#@56;p4+?yR)@x%PYL#j58eE0aAv< zAluy0HG?yR5}DzJ7&_4vMF=uOy!|~H5r){6O4aegMuVlTs?cDO0m zC^(vF$k@jaDdo$$xvc7hd1yDt{fI}%!$%qRR%-c@k1;gH(AR!Modp>O7{X-=)hwtw z!x4&gwZ+=^j;1RU zmZjEg8IKY9B~2&45`^RJ>#9*!{0z3Q2*e>g9>ocqq!gC&x@tQb1Ixsaz_4Ivgkevm zRxyjN&WEZs87ZPCAmmB2$j4*o#b^X+C=4q>67)UUCCHti1J-@8IxS;SbmO$=Y%`T% zJVn_O!mMXYNo=Da=UAFiZZC(X;05l}ct+&!St2%T8a#$`LNifNHS`964KFYpYu8Rg zp7ZLmNI2zLO4Pl6K8lN&AatASCDP&aHmZruNn93eUSa6SQ&k74*$;!Epj0hwm5Nw# zDpkcWZJxKd&Q%)6M#n}^D-D6Gdfwr-V=G0o;LpT!o?M_hH?4eCcgvcg*lx9IT8`3i zX|!4kBNJF0bth!JL`7Y)%u2>^xMlDn!)Opo0lD#`U)IBLT zPRdbY&(~FXA&gf@yavA`G|2FCBC}omK5{_j|0@vRCbP5@;95)e#)Gf}j>`o01;0pM z#cNR#(P9ilkcja*m7E&J>ohEfk*A>$hDt$ErN6f5si)%E9B3(eKc?Y@ zFjAs#JQt;dZpKhRQOs5X8mWMrHq|1xWKa|7Ezrf_@SIkmK6S}79owam?^$7_8pfL~EQ7HmPILjob5T3%r|9za-&36=1P_EMd5 z4MS9E(`OlPF$@#_l%{ioF3?HSARb&%DRaCeN`@|;ra8mu?TVkIhH3}gs;!>H@HXCw zqK0>=1<<{sH#-TpU91x+MtE%pDiM~bX#v2H6mHE3Nk(&~YZbW25ko(@?a(O;nypbPCk(@+j~nvP#&HVzB1Qix zHT6ExUdh!y(P_Mqs?i(i5~EQjJK~9c-_p}%bkRFNDj8Zwcnyzax6r+q{2e{%HJoA_ zcru%epGmLdEXyW4{y=gK=kJj{!*}%GMWdKgQ8f{7L!yr)U@_TAh@B5`Fq@RtF_6wn z`J8w>7ZPU;^oPWPFcQ;HKDUJ?H?)rI8I>8o7_D_|;9!-p8CIJ;3}-Dj8ppBxwy(5@XW-Zfs)dK5le%d7D!Fja(`e ze>0crj4$WN_DsmL4SBZY1KFPXjnGBV)N%YXyJ-|egw}9Fcn3tl$&?zFAgIlBh6$nd;^K*{&aAsjiH@3I`9L5b|8ZvY3!ld RXLS7M8GL~+#pEi!`WNFfEl~gf literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_selector_plugin/support/mymodule.py b/doc/doc_tests/test_selector_plugin/support/mymodule.py new file mode 100644 index 0000000..66b3c16 --- /dev/null +++ b/doc/doc_tests/test_selector_plugin/support/mymodule.py @@ -0,0 +1,2 @@ +def my_function(a, b, c): + return (a, (b, c)) diff --git a/doc/doc_tests/test_selector_plugin/support/mymodule.pyc b/doc/doc_tests/test_selector_plugin/support/mymodule.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6320e75acdd1bc7e991f3c1a638c2c089d418e1e GIT binary patch literal 327 zcmYjM%?iRW49?V(GVn2WTA6rP@V-O6c`382GFaELX2Q^;58-qA1SZ9aCHeB@r|DPw zPM7!Bw$$)F3B^5mNC|3yC%}nF0>^Ad6+I`I{J?W-f(Kawj~QH|9W&liW;};XtBB`_ zI+i6>L2^&>+Kkf(sEG;RH@6CnK4f>FI`O+~TDi+m$4L#MHEUDYZy0fVTW{xZ&0 zplOY+SH2ytO((+Oz3Wlf(YktQP3}ir(K~ij(<+t{SPCR%lrI;={}{KTLizjFE?(ls E2lBB#?*IS* literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_selector_plugin/support/mypackage/__init__$py.class b/doc/doc_tests/test_selector_plugin/support/mypackage/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..34dc2b3c222b672774f2cadf4389a68b662cafeb GIT binary patch literal 2167 zcmbtVZBrXn6n<_tut{7fO({*G6lk#~fo2P~)f%i)6y2K8NHALJxLuN)blc7D&h7@y z2Y-ux_EUu^&FJV4@JBhGn?y>;GUJTQkbC#ubI*CsbKd^?`?o&Udl&wKqMZ$@4=OJI_3B)XISo@O)z3gMCIizH#F2>8`CAOhVSO^jNWqpP^h}?PMZq~t#4(QZ z45MqNZ3g!lE~X35jeSG447;J01JAS@`AmgjZrIFb%dQ5dWA7My7MF36XiE#s7jP+o zDWnL4&#gMcc%j*Ljp_@d!57^&aiE<1mT^VFG-l$M#Z`ugPRxg)?t@`ys;+156VYnW zbXD8&x$bd`8?>(ER&uLqy${T=^nm+;uhyJuep8a_!!?G~u=hPUG8W05y5}@CJK&zpYYe$C3PZxscSykvypzD&NGq7ff}kNw z7DFCvI5ke}O`HypjGGLxwWd>RS^RzsDrJk!zZc>RKc%%N!y)}IC))0A2#A5G=fiY| znA5bdn&|qYP{pk#C=1_Jz_2XbzeD&NJSZBbO{tJR^C=}*vnw-|4zZc3#6)IYz+{4S zt|@pA?~5HJ`Sj^<3M2)`sBurm$5aGWD&~Nf&8BPd2aXf?foHf3_re-N*Nb6{;`sP! zUcm!wB(RQ8$TzQL_k6oEtZ(pHs9Op)@tI)kbIJwcY2XRNLI`04B9ubcPZLmTZ?|k) z6!I}<83l$6>EARhZd23SaBSkCB`Rl|*F_0Y@gIvbte(vK4Qf_?NGBXbqF@V;5-6g? zFjh4zOaHfa8OmubEdBmh`xPk`5lOHUk_2x!kU<@9e$aZ;B^o?+y`)vR=ac6|5b(@~T`!hT$@WE+8uv(>E!A>$dIC7$Sr|7Rc2p^7(5>NQov(P*bAIbXO`j zT1V-gSfT%?^mT)F!lCh{=n-N|kylU-kbFre39_vHM@Ls<`-d0F!Sbb@(5qOWIez4==e7Al0sC*ER)5eL!y0ud*!T)2&(ipf@`SJ TI!U{4$o^vuyLcit5Ap3kyzWR+ literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_selector_plugin/support/mypackage/__init__.py b/doc/doc_tests/test_selector_plugin/support/mypackage/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/doc/doc_tests/test_selector_plugin/support/mypackage/__init__.py @@ -0,0 +1 @@ +pass diff --git a/doc/doc_tests/test_selector_plugin/support/mypackage/__init__.pyc b/doc/doc_tests/test_selector_plugin/support/mypackage/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8730b7e5453962623a73d715de2676d99239a2f GIT binary patch literal 189 zcmZ9GF$%*l3`Je&ROmf2MWl3ZPmrYt5XNzXX>1Ei4uoE$=j;h8L#7J;`_ktRir@3; z{ke|L>~m1RtN5sxjCph|89RmfgG9}MN?5IUkVud+dn|tOR(|k{4z!W literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_selector_plugin/support/mypackage/math/__init__$py.class b/doc/doc_tests/test_selector_plugin/support/mypackage/math/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..f40b42726d981eebc68001989e41580e6aee8c2c GIT binary patch literal 2310 zcmbtVZC4vb6n=&cY!bH?(zK<36fD>z&}_k0ZG%*bqFV!v1f!+ux+IgZu-V)2}5@wi%QuX#q37y1sic0F#?%7GE^LbmU;)PB6P$E!hs;p#aiWk?LN&jZV} zIYTU?Wh)8>F_c6MGDEuAc8%)3(ct-}5$xu74BxEAkzlygYtd}FGTvfHkl=X1ie)k6 zGsQjQz)&s2Zm8wJGwnuy5U-*Wo-g3-BotgEP<7oh{J>;jnt~Ce5`^dyL#o$rsV(C& zLEmsh1Y_s3px`P-6S#tF3`0w%Z3fE>mm?5nD-75B-K@3jYG69{ma$`T8ShZSG6M5B zt|u{xDZ=1$t4`Du&sb9hYcp<}QZUlDDPu;#ySS0SEZ$=n&?);-_(S3tJk|Bg14_0U zG+otpe6D-k;s&h?`Gx$VS`Q%_mL70F@YR}A4JGld6F#a3$J1S_)i7<WCS?P){}_Sz`)-_f?WBS3T&K_SX^ zggZ@(BA^qGf_u21q@pY+V7Mc~|CsPMc(87mHq}GsTve*KX6Me8OoY#DB`(Tq91}^h z`Kf{j_(Vu+WV1J&0!h&^YTT3Y1@%Ogrb@ue)W{Zp=s1BNc!tYxFKRM%qUgscw$D^4 zwnz9f2@OSx&1>1GvEA*r*R2*AmVymD7L1js7Ko=V6^7{u!n&?d3Ek)=pw!-M*|zBG zGt4r!7_wx4&9t~p(|6UeiHDYGpG{sDeMGb1>^j5Zo0DXfW|to^lDm;8sNkC8~1?p`6j8%I|-@1ERzRL=k)!Q3S6!knsbIj*_!9=KepWZH^na@4P zbnXZ@Q>43ag!d)s1wL4xpG$otNzx&1N{mS(!#Kgx5tjcT4Pl9zGqf{IE2F*GA(n+l zAb}M;3{R)Xhd53DjoBxu)#v!^7@wbDJ$HhyPHiQyiE?;5)wxSCre;s@`&#DBY>n$U)IfUR1KWD8uxLddPWG&hrMCpFUdv literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_selector_plugin/support/mypackage/math/basic$py.class b/doc/doc_tests/test_selector_plugin/support/mypackage/math/basic$py.class new file mode 100644 index 0000000000000000000000000000000000000000..dca938c40b4dc135b01f852c690974948940134f GIT binary patch literal 3109 zcmbtWTXz#x6#h<=bdoqoiCjtx(n_jH(`IND1cC@uiorJ2rd9zlPLq>#+R03unG~Yn z{err@`>szu_=H76mn{7O{wSCGOs0i4lfL+noRge={r0!_Isg3o=idOv@FPQ@96!P^ zo0u}Kic)Jhi>9FzO^YitjY&(b@=V*o+@i(RQr=M=o=LWy&NMD9Eb*d~Vc6Hfq=W#2 zWOGN?49*ZtWRvqULgcj3K0!O7Ssl6Wkh0iLc`x>f^m-Y3 z+v90LCZUheD3EQ2V}1qkklohL0UV5?ACD8l<$|u-j>f>UG6paxf`5X_Qf<`K;<8%d zN>z0hl?Bz-it$=QLY#v0$zj4Q@r;Z;crt<^JjKv`N;5R)G{e$1QEpS6PCk78Ur(6` zlEiQnJxCE}38FAY34cMN7XC_NYDryD6nxWYBTFtZ^#V@>ctr5mW zUw*d&k}yS0T(-<=)^NCGaN>E|4?Q0qcqz%q;ZhXSn33@!vI2-%hMujJmoP`xvB4 zBH-)P!)h3B&_XPHld_?~Gp%X{)j#1k4qQV-r>ZNnH{laOaz2c>;NIydp;(czfTGyZ zyl9)4!rYXpmbfKhnJ8bR8^GattyW-}+pOlBmW;Px3H)fD_Yh8-R2T+*0NW%&H`n3z-kq%*hG^T3m?d0iNRs_Y zP3H#PloO^wIMhYE%<{768@gdO#2LmPruqbpX4~ib+#HFF2HuY12Hs)VLD!64SZ`Q{ zd?M>d#{YF^3&M2?61*qIy}SEBOhI?|kq zHngJZbsnJS2VHjkzVg#^8ofJ002{ky|9peQwwB|eCy0cvi01+}hMiONpvcv$3*2%? zEX@FVrWBW_1v#U6vu+i+s1^-a3fmzs^O~(uQO_ENN$(ko(8eI8e}-`QlwKsgV%k!u zy<*t7D>bCM(q-tbbVJY@CV4`RKHt!c$Pn;|REp`%)pYnhG?r zUxcu~i9@-Rurm6EFf7XkXbj6q} zmhcffDY;^yhs2KmM*amhG|jxIdT4dATE#VyKM+9)+&dkhh{fsd`xv?$E8oLn6H9BT z4X?pz*@~c!6;C?m$^zZ7p*7rWS!1|`cfIpp2!B!f^zg`N|F6OqkZ3J#%?{9hfIjgi z-oMkthxGaQ4r^8AJNmy&+5-qm7-EuGzD9ud-ZtLpAkpbyq|-qcz9(;8xQHKU-A%jC QD1+Nse2y=~<|4lQ4-)(^y8r+H literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_selector_plugin/support/mypackage/math/basic.py b/doc/doc_tests/test_selector_plugin/support/mypackage/math/basic.py new file mode 100644 index 0000000..6cddd28 --- /dev/null +++ b/doc/doc_tests/test_selector_plugin/support/mypackage/math/basic.py @@ -0,0 +1,5 @@ +def add(a, b): + return a + b + +def sub(a, b): + return a - b diff --git a/doc/doc_tests/test_selector_plugin/support/mypackage/math/basic.pyc b/doc/doc_tests/test_selector_plugin/support/mypackage/math/basic.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f434d6c01b1210288cd3f64d1fe09f84bef52bf GIT binary patch literal 433 zcmaJ-u};J=40Y}p0Erd&gif^%v4g~ZLvCZK+%y~&Z4)&v(g{XBg74%Du+sxN5hc&h zcH;QC|GGGT`@G#cI4&dhUQW6cXaGI{9)*}p3^Xg)6>CYkmB7z=&ymkuaKT+39Q40|hbdxnmiKQ)+OOb+U({wEgf(5h`iorINrdA4K+@_PTWwX2PZVFKm zZ+O2x-uwS*i$Ls0Qb8XEXm74$%`+ScQeF+BX|RJ(73>CyS1#lX&GmE!mX6^No)B4j zl6Vc^YSfzNME-Uh5#&F`u+ZfEChTkm9q@fE7@yB$3fk~Al^rQ4g5#84Q3uuZs)-Gp z)pXJv?DO;0bqSI40{{JwpI<(+MH0x7MRl(GEaMhKAK{Pb1~=)R z9<@y3p)52!#S22rbib{eGaTAjVWZS9-GIxx-jf*K#ybk$!n+Jx=mIiwYgNRMO{9aw z_`hBZLU1iY1Rsb7?aw|6ki%qUjSU%}F!a>>QL>2;M;)0=&v3^j4v5h3qGpu2L{A>N zZMy^QC#H3J>x2O|bkqIi2AMsWgbmLg8DEpS1z8MR$LSH1DVOHB&DG7n%6vDrWAYJ3i}=mnG>wu1pLh z<2gx^mXMJcleV>C75Tg1VRD8RrFbzr5Q^(rGCL9S%|gBzc|vAKf1?9LT-01@$F|WZ zVht^U3kQKH43uiCJ>-&DZNG=k*|@m^dl}9uZuGC>jVe|YH}Pi8I_}#Nd-2XyyjMkI zxQ+K~>$htR9_$}Tb^q!ghMwx+@9d)au4R09XBi*gVO6@mr*9MF#V&+obTU~C-$0^y lZQ8ThLhoh^E%<>zTW}FS(%4S3&*=Ei)A#~kipe>A^)J~wM^*p; literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_selector_plugin/support/mypackage/strings.py b/doc/doc_tests/test_selector_plugin/support/mypackage/strings.py new file mode 100644 index 0000000..8ffc4cc --- /dev/null +++ b/doc/doc_tests/test_selector_plugin/support/mypackage/strings.py @@ -0,0 +1,2 @@ +def cat(a, b): + return "%s%s" % (a, b) diff --git a/doc/doc_tests/test_selector_plugin/support/mypackage/strings.pyc b/doc/doc_tests/test_selector_plugin/support/mypackage/strings.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e18782f30afb790bb7dbc14a1366d711613be532 GIT binary patch literal 324 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

f(;OuvF@%IJ%0e_T}efZOV0O5ZM A-T(jq literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_selector_plugin/support/tests/strings/cat.py b/doc/doc_tests/test_selector_plugin/support/tests/strings/cat.py new file mode 100644 index 0000000..3b410e8 --- /dev/null +++ b/doc/doc_tests/test_selector_plugin/support/tests/strings/cat.py @@ -0,0 +1,12 @@ +import testlib +from mypackage import strings + +class StringsCat(testlib.Base): + + def test_cat(self): + self.assertEqual(strings.cat('one', 'two'), 'onetwo') + + +def test_helper_function(): + raise Exception( + "This test helper function should not be collected") diff --git a/doc/doc_tests/test_selector_plugin/support/tests/strings/cat.pyc b/doc/doc_tests/test_selector_plugin/support/tests/strings/cat.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80be4e79d5280fc9d8ddc5b1ec82bcd37e94e86c GIT binary patch literal 830 zcmZuv!H&}~5FMx8!Y(QTi3_(Jb81w?5ec-_UVBKn(2M0w+%~Ekhj_xWVo#Kh;5+vV zcAnD$2O`aQCLTY(dE>+5^yR^V&Q|hifB)?mO#;7kxOWukg5{0MD>U^ zimDO)B03U%S&yk2BUxYI+$y^efMuqD^$d=?y4eQ4l6W=53&VfzMcp;ZUW;Qv5o{IV z5-L_%B7c`9ieO{0g;+?%JqQ;?oPd!W5q8G}yu_DKkzy2(S|w!Y(hC_YJ;3xipE&(Gz;cuI0~6Ce_) z3?$>G){Qw>t)u2zAAHq2NsCJ<_)9!S1;7fisBJ`=Bqefu0A7fNSK?$iBJ)wvPZ1k~ zjABxD?0oO4wDFwY+O+fzecCDt$>4MIy|gW)`5!TUgL@&+ zfUD2AcueQEAwe7#3SE60VGsAMDfeb;i@1%EUCTIPr^QsVIQ$q5|4>%E#D>`5qBe|o O_Yp3j0MdQ%L>CxGZXJhyE^yYbI-lI|NQ&s-v9>jGefM< zeVAc7HSW0cTB+hMIJTB^T&_)3#$2Pwhnf~<7F=%Rv%cZ;p`NDIsmj^8%RJ`~G3?p^ zQbLSD^0;rAHfM;ZGCi{jHlbBU91=sKY@5E%JwJgqhVAQ7%wkExW`;JJIIe$IV4O;g zUp5vE%`)tHE$h3cJ>TqjBcgjatzZitkfC5Jxgk7sa)!mA-5-1yxrk;PR1wW~hR*f= zHyDzzgJ_uNzD+bU3`E$tr>DEnDNO7pPiKg%QN!a2>}A-j>jY}}zN_mB9u&KF;vwSs zWUwR2&?;u#gAB5+7oB|B;<}!|0Rqx>;f#2CgjPikEOV~ARFQCqp+%=p_C#bnBwRrc zdfSjfnw%gP3VJ}F$FMJT&q(?)AR^I-tO)c`C(nlzJd8nseS|@6W>mtXB#5GMg^yZ> zM;5O~uz4v0JWN@{N?8X|NC~Y!k8v4iFhM?e+$u0A<15Oc z%$>!Q$lMD;LDXv&w32HsQuomOVo9?dkLxbCI7w0;92gupq7_0rhNTM}Ps=;Ga3-F0 zGC~c;arKf_o;PjHE0;tg(CV8*(*vbS0yB~2SC^{~hO&XS!4164rlk}IsD-C_DYFiRsr8*q0 zm*rCl%u&W|epRTE;g?iqwPG5LZbdO0zR6s?7$Mj-86Aqkjxd+WL=|&brFn3n{DJ~j zuh)fokDD+|o5UH}2ZB#YJjGzo&NW352Glc~z#*Zuj7&;$6kNs?F?)?L*9%p^jXOr3 zyAr4q?8wn^naMG&@T3mvlu))O#QnJ(M5DAHx0CA`JZL-=E+#YKxB zb!_6HEXrt_7eqZ#VJ~lIII_Ae1U)t4tY7Pif_Lz)jJNR~!xlO=Exl354B1pBN`C+A zf*~Zp0zcCdf4?5n zvuaoW5{@pRuZH6%;%T*OH+|p1PQ?3J4Ns?A{=n!GPTeA8hVSWDCW~SY#ORl@6xtsq z`-0$PTJ27&2gwG(q!VP8u+OL4g#+m-4zEWNKDQDEHG&XHryH9<%J$yDWbbWEtLJLC zbQ>?LdNB6IVs~Lo^rh8YEEZeBbd0gswszDoe;elVVpnUh7+R4^Kfj7h5YJf!HsHqy>ucI;C z#0TN}O`<4H77q6I_3!;HFotA(2)^v3IpyV}TUC5=i`9wxf!Mw?w%%qJ8GA zMfW!Q_#_WH;s#@S z+c{%sk%}%hQ;_m7l})UTDL920wBu3_qkpmFG;MmhF%Vl|~+o`X~$KD2$ zY_&}K$%F=3T~i#*5T+;JYn+B5j-+Qv@Y`~Dt;Aavf;r \ No newline at end of file diff --git a/doc/doc_tests/test_xunit_plugin/support/test_skip$py.class b/doc/doc_tests/test_xunit_plugin/support/test_skip$py.class new file mode 100644 index 0000000000000000000000000000000000000000..7dc7e85e37b2b10b441655ff1565a4373e10866f GIT binary patch literal 4186 zcmbtW3wIkw75>Juy|TJ?6*bMnq12_O$d)a|PC_3Jfh3JV#db|>40*t^mPd|aX;-XP zNn9x9RbJ)MmRDOIEpI5V)M*{lkhZ)J{F43(4&ThKbmV9iI0v6Io>|>{XYT#(H}}rJ z{{8A}04DGcfmm^Pzre}#jO(rDs_Xta*U1%JPv+*=r#-tOr`iOI=R9ezEcmuBr$*YO z^Xm^UpO*!HN?><~Ee*s3jGFYzB}WQ$r}HCACVJ4DKsOA5xZ~DjR$eN^(I=2RZ(p!; zW!qWJE%;u^Sv7DAbr#l2)kRtJ78>OX<}3v4I)GLwNZ`v8?G{-ae+M%ryX}! zk)i4V;fuhqH5x<9KLAo78Stoxp>5uRwprUXx8Y1nzG?uUR0XaA=w` zygVLCkTLH!abG}!S%%WUJ&uP3ZqZ*wyHp-NK-?{BMK0G@Eh~0hc`1^KjjH_!uYQq* zIPdt_m1lR}5>OUA9z2wcLtZ2v<77OLdkRt6HwFmYiIzURC`g*Ni<|U612& z6Q2*V@I_McaK&Azm*tT-zRW7&$cw6U2|S<9M>UtW4%FU&@~2NnkZN0|rlR(aI4+-$ zB4JotpTJiY|Gs8|eM;r>8}y&WYt}9~EIsLtrX)p0>+DKLoH?=-$GEDTPJ(-W$HY^3 zTCJXmXhF)88P{Hso`L5`+yb8#zFc6}F3V%C>(_kGu68IRI*N85)z3{+^7Q-oK?2{y z4;hM;MZC+ldt&03_*DYGz>BQe zx;Zqig(`OWDEj?hk3mJTE=7Xhsg|x+msMTYs|{5E_3C9+)bwgIf-Ljv4Xg}o2@Gxb zvQi~nUhe38eo1;YB1E`*F4*O|jPb2I$ahkt{_J9jkK7(WOgvh9ffa1ujVWqrkOtl$ z_Z5@^x6bhOI#;hOOHUV1Rsz0>3v08Au!#k??iHlUpuk?b?emnSQmw>9A9YwSZI#0s zpHf*?S%E(wfjz3E($zhx#Os+&NzZKbdS=nlGq0#T)3l~AYa6^v|P>@s5z z_zVB<((wqcRDyds`ZyHR$gnW#d$>~4fsDZuGHrYGQV)D@3B|?GCPxcpvpbX2MyUrj zab#=*$2Rf4EzG=5QQ$yeZ19Wp4UJbq<3pKbLYjeJF!*+1%F~kgH4Rjm$lRlrl)JCu1VrqR0k6l42^%*^%O?^(!$5LO=^Os^V@@94{lln?57TdtZm=LiY zNo?WkSMX$0cyF&3c59)~!ilDEJ{d@RTBLJLsZ921+ujywp((8-O)WKAq-!q`5tS46 znJ}y*XPwv$JgEqysPRpFE0hi~WF_5s4cSwvZ(qfCH}UKio*&)9PlCPr@KgLO)J|z# z5-gS7!moqf1b&0xhVnN_pKk7UWOQtN-;3%jkW9g!`hA!q%`wKYpX1;ret&rrFL7Mq zc!lHYWf9O?rTZCDIjs_%6=A+xQa{#7u)mYwU?;(SodkzF3A*q%`qzcW@psNUxq6it PT+8E+xULot;kEw&U+qrx literal 0 HcmV?d00001 diff --git a/doc/doc_tests/test_xunit_plugin/support/test_skip.py b/doc/doc_tests/test_xunit_plugin/support/test_skip.py new file mode 100644 index 0000000..cb26c41 --- /dev/null +++ b/doc/doc_tests/test_xunit_plugin/support/test_skip.py @@ -0,0 +1,13 @@ +from nose.exc import SkipTest + +def test_ok(): + pass + +def test_err(): + raise Exception("oh no") + +def test_fail(): + assert False, "bye" + +def test_skip(): + raise SkipTest("not me") diff --git a/doc/doc_tests/test_xunit_plugin/support/test_skip.pyc b/doc/doc_tests/test_xunit_plugin/support/test_skip.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2cf730bbb61efda5d15c1205eac6a18e1d5ad484 GIT binary patch literal 766 zcmaJ`fzGkRnT-dlaUZ+l1 zI-_+M4Wr9=*3>zcrp~GqiO0u<$$t_1>9VOyOl{RXmv!tLYmLKLBA%p0c7?~pA{%Dj zBIvmXpf-Zp6~X5OkTfg!Q1GtS;yF6@9kS+SP#Y-*yX20Rxwb59r>KNtfZQ74)Xs6k z_L5$3B7p6ILZT$%fM&sM6J}(^Pb)1RvW$nU@*2+w-FdGaD;POv+`lqdsK}UQWyQ{3 z9S4eM{k6ftLa(dm+Wy9Vn3X4Rx@8^9-I3n#pd-D^6Fw~AJCksw3C}8Fp&{40-jkF~ VK$5q#>vr=2H*Dli_JeMV-ya<#d`>> import os +>>> from nose.plugins.xunit import Xunit +>>> from nose.plugins.skip import SkipTest, Skip +>>> support = os.path.join(os.path.dirname(__file__), 'support') +>>> outfile = os.path.join(support, 'nosetests.xml') +>>> from nose.plugins.plugintest import run_buffered as run +>>> argv = [__file__, '-v', '--with-xunit', support, +... '--xunit-file=%s' % outfile] +>>> run(argv=argv, plugins=[Xunit(), Skip()]) # doctest: +ELLIPSIS +test_skip.test_ok ... ok +test_skip.test_err ... ERROR +test_skip.test_fail ... FAIL +test_skip.test_skip ... SKIP: not me + +====================================================================== +ERROR: test_skip.test_err +---------------------------------------------------------------------- +Traceback (most recent call last): +... +Exception: oh no + +====================================================================== +FAIL: test_skip.test_fail +---------------------------------------------------------------------- +Traceback (most recent call last): +... +AssertionError: bye + +---------------------------------------------------------------------- +XML: ...nosetests.xml +---------------------------------------------------------------------- +Ran 4 tests in ...s + +FAILED (SKIP=1, errors=1, failures=1) + +>>> open(outfile, 'r').read() # doctest: +ELLIPSIS +'.........' diff --git a/doc/docstring.py b/doc/docstring.py new file mode 100644 index 0000000..5652bd2 --- /dev/null +++ b/doc/docstring.py @@ -0,0 +1,25 @@ +from docutils import nodes +from docutils.statemachine import ViewList +from nose.util import resolve_name + + +def docstring_directive(dirname, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + obj_name = arguments[0] + obj = resolve_name(obj_name) + rst = ViewList() + rst.append(obj.__doc__, '') + print "CALLED", obj_name, obj, rst + node = nodes.section() + surrounding_title_styles = state.memo.title_styles + surrounding_section_level = state.memo.section_level + state.memo.title_styles = [] + state.memo.section_level = 0 + state.nested_parse(rst, 0, node, match_titles=1) + state.memo.title_styles = surrounding_title_styles + state.memo.section_level = surrounding_section_level + return node.children + + +def setup(app): + app.add_directive('docstring', docstring_directive, 1, (1, 0, 1)) diff --git a/doc/finding_tests.rst b/doc/finding_tests.rst new file mode 100644 index 0000000..5f9cb74 --- /dev/null +++ b/doc/finding_tests.rst @@ -0,0 +1,32 @@ +Finding and running tests +------------------------- + +nose, by default, follows a few simple rules for test discovery. + +* If it looks like a test, it's a test. Names of directories, modules, + classes and functions are compared against the testMatch regular + expression, and those that match are considered tests. Any class that is a + `unittest.TestCase` subclass is also collected, so long as it is inside of a + module that looks like a test. + +* Directories that don't look like tests and aren't packages are not + inspected. + +* Packages are always inspected, but they are only collected if they look + like tests. This means that you can include your tests inside of your + packages (somepackage/tests) and nose will collect the tests without + running package code inappropriately. + +* When a project appears to have library and test code organized into + separate directories, library directories are examined first. + +* When nose imports a module, it adds that module's directory to sys.path; + when the module is inside of a package, like package.module, it will be + loaded as package.module and the directory of *package* will be added to + sys.path. + +* If an object defines a __test__ attribute that does not evaluate to + True, that object will not be collected, nor will any objects it + contains. + +Be aware that plugins and command line options can change any of those rules. \ No newline at end of file diff --git a/doc/further_reading.rst b/doc/further_reading.rst new file mode 100644 index 0000000..4a93553 --- /dev/null +++ b/doc/further_reading.rst @@ -0,0 +1,34 @@ +Further reading +=============== + +.. toctree :: + :maxdepth: 2 + + doc_tests/test_addplugins/test_addplugins.rst + doc_tests/test_coverage_html/coverage_html.rst + doc_tests/test_doctest_fixtures/doctest_fixtures.rst + doc_tests/test_init_plugin/init_plugin.rst + doc_tests/test_issue089/unwanted_package.rst + doc_tests/test_issue097/plugintest_environment.rst + doc_tests/test_issue107/plugin_exceptions.rst + doc_tests/test_issue119/empty_plugin.rst + doc_tests/test_issue142/errorclass_failure.rst + doc_tests/test_issue145/imported_tests.rst + doc_tests/test_multiprocess/multiprocess.rst + doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst + doc_tests/test_selector_plugin/selector_plugin.rst + doc_tests/test_allmodules/test_allmodules.rst + more_info + +Articles, etc +------------- + +* `An Extended Introduction to the nose Unit Testing Framework`_: + Titus Brown's excellent article provides a great overview of + nose and its uses. +* `My blog`_ +* `Tweets`_ + +.. _`An Extended Introduction to the nose Unit Testing Framework` : http://ivory.idyll.org/articles/nose-intro.html +.. _`My blog` : http://somethingaboutorange.com/mrl/ +.. _`Tweets` : http://twitter.com/jpellerin \ No newline at end of file diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..cd80895 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,8 @@ + + Redirecting to nose 0.11 docs + + + +

Redirecting to nose 1.0 docs

+ + diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..eeb1805 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,74 @@ +.. nose documentation master file, created by sphinx-quickstart on Thu Mar 26 16:49:00 2009. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + + +Installation and quick start +============================ + +*On most UNIX-like systems, you'll probably need to run these commands as root +or using sudo.* + +Install nose using setuptools/distribute:: + + easy_install nose + +Or pip:: + + pip install nose + +Or, if you don't have setuptools/distribute installed, use the download +link at right to download the source package, and install it in the +normal fashion: Ungzip and untar the source package, cd to the new +directory, and:: + + python setup.py install + +However, **please note** that without setuptools/distribute installed, +you will not be able to use third-party nose plugins. + +This will install the nose libraries, as well as the :doc:`nosetests ` +script, which you can use to automatically discover and run tests. + +Now you can run tests for your project:: + + cd path/to/project + nosetests + +You should see output something like this:: + + .................................. + ---------------------------------------------------------------------- + Ran 34 tests in 1.440s + + OK + +Indicating that nose found and ran your tests. + +For help with nosetests' many command-line options, try:: + + nosetests -h + +or visit the :doc:`usage documentation `. + + +Python3 +======= + +nose supports python3. Building from source on python3 requires +`distribute `_. If you don't +have distribute installed, ``python3 setup.py install`` will install +it via distribute's bootstrap script. + +.. warning :: + + nose itself supports python 3, but many 3rd-party plugins do not! + + +.. toctree:: + :hidden: + + testing + developing + news + further_reading diff --git a/doc/man.rst b/doc/man.rst new file mode 100644 index 0000000..6318b24 --- /dev/null +++ b/doc/man.rst @@ -0,0 +1,24 @@ +=========== + nosetests +=========== + +------------------------ +nicer testing for python +------------------------ + +:Author: jpellerin+nose@gmail.com +:Date: 2009-04-23 +:Copyright: LGPL +:Version: 0.11 +:Manual section: 1 +:Manual group: User Commands + +SYNOPSIS +======== + + nosetests [options] [names] + +DESCRIPTION +=========== + +.. autohelp :: diff --git a/doc/manbuilder.py b/doc/manbuilder.py new file mode 100644 index 0000000..463d58b --- /dev/null +++ b/doc/manbuilder.py @@ -0,0 +1,24 @@ +from manpage import Writer +from sphinx.builders.text import TextBuilder + + +class ManBuilder(TextBuilder): + name = 'manpage' + format = 'man' + out_suffix = '.man' + + def prepare_writing(self, docnames): + self.writer = ManpageWriter(self) + + +class ManpageWriter(Writer): + def __init__(self, builder): + self.builder = builder + Writer.__init__(self) + + +def setup(app): + app.add_builder(ManBuilder) + + + diff --git a/doc/manbuilder.pyc b/doc/manbuilder.pyc new file mode 100644 index 0000000000000000000000000000000000000000..355955a18ef61cef975649c2e07dab3fc20b0ed9 GIT binary patch literal 1308 zcmbtU!EVz)5S_IXHz_Kl0``KRp;)(^1GK9{uPYH?mz+?F>2rgR)!Nx;y&qF+QYhV0<+4i zI={9dZs(pKWC$)IOm^b~=KeWMAi70)imj0xGr>7cd1*L<0Xp8WEkf$~-qs^4czbWl zWf)*mtQS?jvOdL}#P|tj-^LuDWKTJf|d;GO3c#7|v&m&}htRU2IBr85KVfxXX33%7ZLfZ-Vig<vX_*1X#_WxViiQCQG)ErFd5-8_&O1>Q;<4Hf^?8ZtZL18*Rpg>hEAG~vOf0kL z-m$0Q@3?W53&x0&np;CNt%sT)r;Q$A6W&NVi_}o1j6t(Ln!8&T9I_n4EOYo2Lv&Bn zf_dV>j7tkMo#QQ$I%f)c^fwM3vAdm=rk3& zg%?CA$g}#ZtF~E(#Ag9t0nOD&14+MVK9UDSM^GoH<86B$-fAHO~X A8UO$Q literal 0 HcmV?d00001 diff --git a/doc/manpage.py b/doc/manpage.py new file mode 100644 index 0000000..7935510 --- /dev/null +++ b/doc/manpage.py @@ -0,0 +1,1119 @@ +# $Id: manpage.py 5901 2009-04-07 13:26:48Z grubert $ +# Author: Engelbert Gruber +# Copyright: This module is put into the public domain. + +""" +Simple man page writer for reStructuredText. + +Man pages (short for "manual pages") contain system documentation on unix-like +systems. The pages are grouped in numbered sections: + + 1 executable programs and shell commands + 2 system calls + 3 library functions + 4 special files + 5 file formats + 6 games + 7 miscellaneous + 8 system administration + +Man pages are written *troff*, a text file formatting system. + +See http://www.tldp.org/HOWTO/Man-Page for a start. + +Man pages have no subsection only parts. +Standard parts + + NAME , + SYNOPSIS , + DESCRIPTION , + OPTIONS , + FILES , + SEE ALSO , + BUGS , + +and + + AUTHOR . + +A unix-like system keeps an index of the DESCRIPTIONs, which is accesable +by the command whatis or apropos. + +""" + +# NOTE: the macros only work when at line start, so try the rule +# start new lines in visit_ functions. + +__docformat__ = 'reStructuredText' + +import sys +import os +import time +import re +from types import ListType + +import docutils +from docutils import nodes, utils, writers, languages + +FIELD_LIST_INDENT = 7 +DEFINITION_LIST_INDENT = 7 +OPTION_LIST_INDENT = 7 +BLOCKQOUTE_INDENT = 3.5 + +# Define two macros so man/roff can calculate the +# indent/unindent margins by itself +MACRO_DEF = (r""" +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level magin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +""") + +class Writer(writers.Writer): + + supported = ('manpage') + """Formats this writer supports.""" + + output = None + """Final translated form of `document`.""" + + def __init__(self): + writers.Writer.__init__(self) + self.translator_class = Translator + + def translate(self): + visitor = self.translator_class(self.document) + self.document.walkabout(visitor) + self.output = visitor.astext() + + +class Table: + def __init__(self): + self._rows = [] + self._options = ['center', ] + self._tab_char = '\t' + self._coldefs = [] + def new_row(self): + self._rows.append([]) + def append_cell(self, cell_lines): + """cell_lines is an array of lines""" + self._rows[-1].append(cell_lines) + if len(self._coldefs) < len(self._rows[-1]): + self._coldefs.append('l') + def astext(self): + text = '.TS\n' + text += ' '.join(self._options) + ';\n' + text += '|%s|.\n' % ('|'.join(self._coldefs)) + for row in self._rows: + # row = array of cells. cell = array of lines. + # line above + text += '_\n' + max_lns_in_cell = 0 + for cell in row: + max_lns_in_cell = max(len(cell), max_lns_in_cell) + for ln_cnt in range(max_lns_in_cell): + line = [] + for cell in row: + if len(cell) > ln_cnt: + line.append(cell[ln_cnt]) + else: + line.append(" ") + text += self._tab_char.join(line) + '\n' + text += '_\n' + text += '.TE\n' + return text + +class Translator(nodes.NodeVisitor): + """""" + + words_and_spaces = re.compile(r'\S+| +|\n') + document_start = """Man page generated from reStructeredText.""" + + def __init__(self, document): + nodes.NodeVisitor.__init__(self, document) + self.settings = settings = document.settings + lcode = settings.language_code + self.language = languages.get_language(lcode, document.reporter) + self.head = [] + self.body = [] + self.foot = [] + self.section_level = -1 + self.context = [] + self.topic_class = '' + self.colspecs = [] + self.compact_p = 1 + self.compact_simple = None + # the list style "*" bullet or "#" numbered + self._list_char = [] + # writing the header .TH and .SH NAME is postboned after + # docinfo. + self._docinfo = { + "title" : "", "subtitle" : "", + "manual_section" : "", "manual_group" : "", + "author" : "", + "date" : "", + "copyright" : "", + "version" : "", + } + self._in_docinfo = 1 # FIXME docinfo not being found? + self._active_table = None + self._in_entry = None + self.header_written = 0 + self.authors = [] + self.section_level = -1 + self._indent = [0] + # central definition of simple processing rules + # what to output on : visit, depart + self.defs = { + 'indent' : ('.INDENT %.1f\n', '.UNINDENT\n'), + 'definition' : ('', ''), + 'definition_list' : ('', '.TP 0\n'), + 'definition_list_item' : ('\n.TP', ''), + #field_list + #field + 'field_name' : ('\n.TP\n.B ', '\n'), + 'field_body' : ('', '.RE\n', ), + 'literal' : ('\\fB', '\\fP'), + 'literal_block' : ('\n.nf\n', '\n.fi\n'), + + #option_list + 'option_list_item' : ('\n.TP', ''), + #option_group, option + 'description' : ('\n', ''), + + 'reference' : (r'\fI\%', r'\fP'), + #'target' : (r'\fI\%', r'\fP'), + 'emphasis': ('\\fI', '\\fP'), + 'strong' : ('\\fB', '\\fP'), + 'term' : ('\n.B ', '\n'), + 'title_reference' : ('\\fI', '\\fP'), + + 'problematic' : ('\n.nf\n', '\n.fi\n'), + # docinfo fields. + 'address' : ('\n.nf\n', '\n.fi\n'), + 'organization' : ('\n.nf\n', '\n.fi\n'), + } + # TODO dont specify the newline before a dot-command, but ensure + # check it is there. + + def comment_begin(self, text): + """Return commented version of the passed text WITHOUT end of line/comment.""" + prefix = '\n.\\" ' + return prefix+prefix.join(text.split('\n')) + + def comment(self, text): + """Return commented version of the passed text.""" + return self.comment_begin(text)+'\n' + + def astext(self): + """Return the final formatted document as a string.""" + if not self.header_written: + # ensure we get a ".TH" as viewers require it. + self.head.append(self.header()) + return ''.join(self.head + self.body + self.foot) + + def visit_Text(self, node): + text = node.astext().replace('-','\-') + text = text.replace("'","\\'") + self.body.append(text) + + def depart_Text(self, node): + pass + + def list_start(self, node): + class enum_char: + enum_style = { + 'arabic' : (3,1), + 'loweralpha' : (3,'a'), + 'upperalpha' : (3,'A'), + 'lowerroman' : (5,'i'), + 'upperroman' : (5,'I'), + 'bullet' : (2,'\\(bu'), + 'emdash' : (2,'\\(em'), + } + def __init__(self, style): + if style == 'arabic': + if node.has_key('start'): + start = node['start'] + else: + start = 1 + self._style = ( + len(str(len(node.children)))+2, + start ) + # BUG: fix start for alpha + else: + self._style = self.enum_style[style] + self._cnt = -1 + def next(self): + self._cnt += 1 + # BUG add prefix postfix + try: + return "%d." % (self._style[1] + self._cnt) + except: + if self._style[1][0] == '\\': + return self._style[1] + # BUG romans dont work + # BUG alpha only a...z + return "%c." % (ord(self._style[1])+self._cnt) + def get_width(self): + return self._style[0] + def __repr__(self): + return 'enum_style%r' % list(self._style) + + if node.has_key('enumtype'): + self._list_char.append(enum_char(node['enumtype'])) + else: + self._list_char.append(enum_char('bullet')) + if len(self._list_char) > 1: + # indent nested lists + # BUG indentation depends on indentation of parent list. + self.indent(self._list_char[-2].get_width()) + else: + self.indent(self._list_char[-1].get_width()) + + def list_end(self): + self.dedent() + self._list_char.pop() + + def header(self): + tmpl = (".TH %(title)s %(manual_section)s" + " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n" + ".SH NAME\n" + "%(title)s \- %(subtitle)s\n") + return tmpl % self._docinfo + + def append_header(self): + """append header with .TH and .SH NAME""" + # TODO before everything + # .TH title section date source manual + if self.header_written: + return + self.body.append(self.header()) + self.body.append(MACRO_DEF) + self.header_written = 1 + + def visit_address(self, node): + self._docinfo['address'] = node.astext() + raise nodes.SkipNode + + def depart_address(self, node): + pass + + def visit_admonition(self, node, name): + self.visit_block_quote(node) + + def depart_admonition(self): + self.depart_block_quote(None) + + def visit_attention(self, node): + self.visit_admonition(node, 'attention') + + def depart_attention(self, node): + self.depart_admonition() + + def visit_author(self, node): + self._docinfo['author'] = node.astext() + raise nodes.SkipNode + + def depart_author(self, node): + pass + + def visit_authors(self, node): + self.body.append(self.comment('visit_authors')) + + def depart_authors(self, node): + self.body.append(self.comment('depart_authors')) + + def visit_block_quote(self, node): + #self.body.append(self.comment('visit_block_quote')) + # BUG/HACK: indent alway uses the _last_ indention, + # thus we need two of them. + self.indent(BLOCKQOUTE_INDENT) + self.indent(0) + + def depart_block_quote(self, node): + #self.body.append(self.comment('depart_block_quote')) + self.dedent() + self.dedent() + + def visit_bullet_list(self, node): + self.list_start(node) + + def depart_bullet_list(self, node): + self.list_end() + + def visit_caption(self, node): + raise NotImplementedError, node.astext() + self.body.append(self.starttag(node, 'p', '', CLASS='caption')) + + def depart_caption(self, node): + raise NotImplementedError, node.astext() + self.body.append('

\n') + + def visit_caution(self, node): + self.visit_admonition(node, 'caution') + + def depart_caution(self, node): + self.depart_admonition() + + def visit_citation(self, node): + raise NotImplementedError, node.astext() + self.body.append(self.starttag(node, 'table', CLASS='citation', + frame="void", rules="none")) + self.body.append('\n' + '\n' + '\n' + '') + self.footnote_backrefs(node) + + def depart_citation(self, node): + raise NotImplementedError, node.astext() + self.body.append('\n' + '\n\n') + + def visit_citation_reference(self, node): + raise NotImplementedError, node.astext() + href = '' + if node.has_key('refid'): + href = '#' + node['refid'] + elif node.has_key('refname'): + href = '#' + self.document.nameids[node['refname']] + self.body.append(self.starttag(node, 'a', '[', href=href, + CLASS='citation-reference')) + + def depart_citation_reference(self, node): + raise NotImplementedError, node.astext() + self.body.append(']') + + def visit_classifier(self, node): + raise NotImplementedError, node.astext() + self.body.append(' : ') + self.body.append(self.starttag(node, 'span', '', CLASS='classifier')) + + def depart_classifier(self, node): + raise NotImplementedError, node.astext() + self.body.append('') + + def visit_colspec(self, node): + self.colspecs.append(node) + + def depart_colspec(self, node): + pass + + def write_colspecs(self): + self.body.append("%s.\n" % ('L '*len(self.colspecs))) + + def visit_comment(self, node, + sub=re.compile('-(?=-)').sub): + self.body.append(self.comment(node.astext())) + raise nodes.SkipNode + + def visit_contact(self, node): + self.visit_docinfo_item(node, 'contact') + + def depart_contact(self, node): + self.depart_docinfo_item() + + def visit_copyright(self, node): + self._docinfo['copyright'] = node.astext() + raise nodes.SkipNode + + def visit_danger(self, node): + self.visit_admonition(node, 'danger') + + def depart_danger(self, node): + self.depart_admonition() + + def visit_date(self, node): + self._docinfo['date'] = node.astext() + raise nodes.SkipNode + + def visit_decoration(self, node): + pass + + def depart_decoration(self, node): + pass + + def visit_definition(self, node): + self.body.append(self.defs['definition'][0]) + + def depart_definition(self, node): + self.body.append(self.defs['definition'][1]) + + def visit_definition_list(self, node): + self.indent(DEFINITION_LIST_INDENT) + + def depart_definition_list(self, node): + self.dedent() + + def visit_definition_list_item(self, node): + self.body.append(self.defs['definition_list_item'][0]) + + def depart_definition_list_item(self, node): + self.body.append(self.defs['definition_list_item'][1]) + + def visit_description(self, node): + self.body.append(self.defs['description'][0]) + + def depart_description(self, node): + self.body.append(self.defs['description'][1]) + + def visit_docinfo(self, node): + self._in_docinfo = 1 + + def depart_docinfo(self, node): + self._in_docinfo = None + # TODO nothing should be written before this + self.append_header() + + def visit_docinfo_item(self, node, name): + self.body.append(self.comment('%s: ' % self.language.labels[name])) + if len(node): + return + if isinstance(node[0], nodes.Element): + node[0].set_class('first') + if isinstance(node[0], nodes.Element): + node[-1].set_class('last') + + def depart_docinfo_item(self): + pass + + def visit_doctest_block(self, node): + raise NotImplementedError, node.astext() + self.body.append(self.starttag(node, 'pre', CLASS='doctest-block')) + + def depart_doctest_block(self, node): + raise NotImplementedError, node.astext() + self.body.append('\n\n') + + def visit_document(self, node): + self.body.append(self.comment(self.document_start).lstrip()) + # writing header is postboned + self.header_written = 0 + + def depart_document(self, node): + if self._docinfo['author']: + self.body.append('\n.SH AUTHOR\n%s\n' + % self._docinfo['author']) + if 'organization' in self._docinfo: + self.body.append(self.defs['organization'][0]) + self.body.append(self._docinfo['organization']) + self.body.append(self.defs['organization'][1]) + if 'address' in self._docinfo: + self.body.append(self.defs['address'][0]) + self.body.append(self._docinfo['address']) + self.body.append(self.defs['address'][1]) + if self._docinfo['copyright']: + self.body.append('\n.SH COPYRIGHT\n%s\n' + % self._docinfo['copyright']) + self.body.append( + self.comment( + 'Generated by docutils manpage writer on %s.\n' + % (time.strftime('%Y-%m-%d %H:%M')) ) ) + + def visit_emphasis(self, node): + self.body.append(self.defs['emphasis'][0]) + + def depart_emphasis(self, node): + self.body.append(self.defs['emphasis'][1]) + + def visit_entry(self, node): + # BUG entries have to be on one line separated by tab force it. + self.context.append(len(self.body)) + self._in_entry = 1 + + def depart_entry(self, node): + start = self.context.pop() + self._active_table.append_cell(self.body[start:]) + del self.body[start:] + self._in_entry = 0 + + def visit_enumerated_list(self, node): + self.list_start(node) + + def depart_enumerated_list(self, node): + self.list_end() + + def visit_error(self, node): + self.visit_admonition(node, 'error') + + def depart_error(self, node): + self.depart_admonition() + + def visit_field(self, node): + #self.body.append(self.comment('visit_field')) + pass + + def depart_field(self, node): + #self.body.append(self.comment('depart_field')) + pass + + def visit_field_body(self, node): + #self.body.append(self.comment('visit_field_body')) + if self._in_docinfo: + self._docinfo[ + self._field_name.lower().replace(" ","_")] = node.astext() + raise nodes.SkipNode + + def depart_field_body(self, node): + pass + + def visit_field_list(self, node): + self.indent(FIELD_LIST_INDENT) + + def depart_field_list(self, node): + self.dedent('depart_field_list') + + def visit_field_name(self, node): + if self._in_docinfo: + self._in_docinfo = 1 + self._field_name = node.astext() + raise nodes.SkipNode + else: + self.body.append(self.defs['field_name'][0]) + + def depart_field_name(self, node): + self.body.append(self.defs['field_name'][1]) + + def visit_figure(self, node): + raise NotImplementedError, node.astext() + + def depart_figure(self, node): + raise NotImplementedError, node.astext() + + def visit_footer(self, node): + raise NotImplementedError, node.astext() + + def depart_footer(self, node): + raise NotImplementedError, node.astext() + start = self.context.pop() + footer = (['\n', + self.starttag(node, 'div', CLASS='footer')] + + self.body[start:] + ['\n']) + self.body_suffix[:0] = footer + del self.body[start:] + + def visit_footnote(self, node): + raise NotImplementedError, node.astext() + self.body.append(self.starttag(node, 'table', CLASS='footnote', + frame="void", rules="none")) + self.body.append('\n' + '\n' + '') + self.footnote_backrefs(node) + + def footnote_backrefs(self, node): + raise NotImplementedError, node.astext() + if self.settings.footnote_backlinks and node.hasattr('backrefs'): + backrefs = node['backrefs'] + if len(backrefs) == 1: + self.context.append('') + self.context.append('' % (backrefs[0], node['id'])) + else: + i = 1 + backlinks = [] + for backref in backrefs: + backlinks.append('%s' + % (backref, i)) + i += 1 + self.context.append('(%s) ' % ', '.join(backlinks)) + self.context.append('' % node['id']) + else: + self.context.append('') + self.context.append('' % node['id']) + + def depart_footnote(self, node): + raise NotImplementedError, node.astext() + self.body.append('\n' + '\n\n') + + def visit_footnote_reference(self, node): + raise NotImplementedError, node.astext() + href = '' + if node.has_key('refid'): + href = '#' + node['refid'] + elif node.has_key('refname'): + href = '#' + self.document.nameids[node['refname']] + format = self.settings.footnote_references + if format == 'brackets': + suffix = '[' + self.context.append(']') + elif format == 'superscript': + suffix = '' + self.context.append('') + else: # shouldn't happen + suffix = '???' + self.content.append('???') + self.body.append(self.starttag(node, 'a', suffix, href=href, + CLASS='footnote-reference')) + + def depart_footnote_reference(self, node): + raise NotImplementedError, node.astext() + self.body.append(self.context.pop() + '') + + def visit_generated(self, node): + pass + + def depart_generated(self, node): + pass + + def visit_header(self, node): + raise NotImplementedError, node.astext() + self.context.append(len(self.body)) + + def depart_header(self, node): + raise NotImplementedError, node.astext() + start = self.context.pop() + self.body_prefix.append(self.starttag(node, 'div', CLASS='header')) + self.body_prefix.extend(self.body[start:]) + self.body_prefix.append('
\n\n') + del self.body[start:] + + def visit_hint(self, node): + self.visit_admonition(node, 'hint') + + def depart_hint(self, node): + self.depart_admonition() + + def visit_image(self, node): + raise NotImplementedError, node.astext() + atts = node.attributes.copy() + atts['src'] = atts['uri'] + del atts['uri'] + if not atts.has_key('alt'): + atts['alt'] = atts['src'] + if isinstance(node.parent, nodes.TextElement): + self.context.append('') + else: + self.body.append('

') + self.context.append('

\n') + self.body.append(self.emptytag(node, 'img', '', **atts)) + + def depart_image(self, node): + raise NotImplementedError, node.astext() + self.body.append(self.context.pop()) + + def visit_important(self, node): + self.visit_admonition(node, 'important') + + def depart_important(self, node): + self.depart_admonition() + + def visit_label(self, node): + raise NotImplementedError, node.astext() + self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(), + CLASS='label')) + + def depart_label(self, node): + raise NotImplementedError, node.astext() + self.body.append(']%s' % self.context.pop()) + + def visit_legend(self, node): + raise NotImplementedError, node.astext() + self.body.append(self.starttag(node, 'div', CLASS='legend')) + + def depart_legend(self, node): + raise NotImplementedError, node.astext() + self.body.append('\n') + + def visit_line_block(self, node): + self.body.append('\n') + + def depart_line_block(self, node): + self.body.append('\n') + + def visit_line(self, node): + pass + + def depart_line(self, node): + self.body.append('\n.br\n') + + def visit_list_item(self, node): + # man 7 man argues to use ".IP" instead of ".TP" + self.body.append('\n.IP %s %d\n' % ( + self._list_char[-1].next(), + self._list_char[-1].get_width(),) ) + + def depart_list_item(self, node): + pass + + def visit_literal(self, node): + self.body.append(self.defs['literal'][0]) + + def depart_literal(self, node): + self.body.append(self.defs['literal'][1]) + + def visit_literal_block(self, node): + self.body.append(self.defs['literal_block'][0]) + + def depart_literal_block(self, node): + self.body.append(self.defs['literal_block'][1]) + + def visit_meta(self, node): + raise NotImplementedError, node.astext() + self.head.append(self.emptytag(node, 'meta', **node.attributes)) + + def depart_meta(self, node): + pass + + def visit_note(self, node): + self.visit_admonition(node, 'note') + + def depart_note(self, node): + self.depart_admonition() + + def indent(self, by=0.5): + # if we are in a section ".SH" there already is a .RS + #self.body.append('\n[[debug: listchar: %r]]\n' % map(repr, self._list_char)) + #self.body.append('\n[[debug: indent %r]]\n' % self._indent) + step = self._indent[-1] + self._indent.append(by) + self.body.append(self.defs['indent'][0] % step) + + def dedent(self, name=''): + #self.body.append('\n[[debug: dedent %s %r]]\n' % (name, self._indent)) + self._indent.pop() + self.body.append(self.defs['indent'][1]) + + def visit_option_list(self, node): + self.indent(OPTION_LIST_INDENT) + + def depart_option_list(self, node): + self.dedent() + + def visit_option_list_item(self, node): + # one item of the list + self.body.append(self.defs['option_list_item'][0]) + + def depart_option_list_item(self, node): + self.body.append(self.defs['option_list_item'][1]) + + def visit_option_group(self, node): + # as one option could have several forms it is a group + # options without parameter bold only, .B, -v + # options with parameter bold italic, .BI, -f file + + # we do not know if .B or .BI + self.context.append('.B') # blind guess + self.context.append(len(self.body)) # to be able to insert later + self.context.append(0) # option counter + + def depart_option_group(self, node): + self.context.pop() # the counter + start_position = self.context.pop() + text = self.body[start_position:] + del self.body[start_position:] + self.body.append('\n%s%s' % (self.context.pop(), ''.join(text))) + + def visit_option(self, node): + # each form of the option will be presented separately + if self.context[-1]>0: + self.body.append(' ,') + if self.context[-3] == '.BI': + self.body.append('\\') + self.body.append(' ') + + def depart_option(self, node): + self.context[-1] += 1 + + def visit_option_string(self, node): + # do not know if .B or .BI + pass + + def depart_option_string(self, node): + pass + + def visit_option_argument(self, node): + self.context[-3] = '.BI' # bold/italic alternate + if node['delimiter'] != ' ': + self.body.append('\\fn%s ' % node['delimiter'] ) + elif self.body[len(self.body)-1].endswith('='): + # a blank only means no blank in output, just changing font + self.body.append(' ') + else: + # backslash blank blank + self.body.append('\\ ') + + def depart_option_argument(self, node): + pass + + def visit_organization(self, node): + self._docinfo['organization'] = node.astext() + raise nodes.SkipNode + + def depart_organization(self, node): + pass + + def visit_paragraph(self, node): + # BUG every but the first paragraph in a list must be intended + # TODO .PP or new line + return + + def depart_paragraph(self, node): + # TODO .PP or an empty line + if not self._in_entry: + self.body.append('\n\n') + + def visit_problematic(self, node): + self.body.append(self.defs['problematic'][0]) + + def depart_problematic(self, node): + self.body.append(self.defs['problematic'][1]) + + def visit_raw(self, node): + if node.get('format') == 'manpage': + self.body.append(node.astext()) + # Keep non-manpage raw text out of output: + raise nodes.SkipNode + + def visit_reference(self, node): + """E.g. link or email address.""" + self.body.append(self.defs['reference'][0]) + + def depart_reference(self, node): + self.body.append(self.defs['reference'][1]) + + def visit_revision(self, node): + self.visit_docinfo_item(node, 'revision') + + def depart_revision(self, node): + self.depart_docinfo_item() + + def visit_row(self, node): + self._active_table.new_row() + + def depart_row(self, node): + pass + + def visit_section(self, node): + self.section_level += 1 + + def depart_section(self, node): + self.section_level -= 1 + + def visit_status(self, node): + raise NotImplementedError, node.astext() + self.visit_docinfo_item(node, 'status', meta=None) + + def depart_status(self, node): + self.depart_docinfo_item() + + def visit_strong(self, node): + self.body.append(self.defs['strong'][1]) + + def depart_strong(self, node): + self.body.append(self.defs['strong'][1]) + + def visit_substitution_definition(self, node): + """Internal only.""" + raise nodes.SkipNode + + def visit_substitution_reference(self, node): + self.unimplemented_visit(node) + + def visit_subtitle(self, node): + self._docinfo["subtitle"] = node.astext() + raise nodes.SkipNode + + def visit_system_message(self, node): + # TODO add report_level + #if node['level'] < self.document.reporter['writer'].report_level: + # Level is too low to display: + # raise nodes.SkipNode + self.body.append('\.SH system-message\n') + attr = {} + backref_text = '' + if node.hasattr('id'): + attr['name'] = node['id'] + if node.hasattr('line'): + line = ', line %s' % node['line'] + else: + line = '' + self.body.append('System Message: %s/%s (%s:%s)\n' + % (node['type'], node['level'], node['source'], line)) + + def depart_system_message(self, node): + self.body.append('\n') + + def visit_table(self, node): + self._active_table = Table() + + def depart_table(self, node): + self.body.append(self._active_table.astext()) + self._active_table = None + + def visit_target(self, node): + self.body.append(self.comment('visit_target')) + #self.body.append(self.defs['target'][0]) + #self.body.append(node['refuri']) + + def depart_target(self, node): + self.body.append(self.comment('depart_target')) + #self.body.append(self.defs['target'][1]) + + def visit_tbody(self, node): + pass + + def depart_tbody(self, node): + pass + + def visit_term(self, node): + self.body.append(self.defs['term'][0]) + + def depart_term(self, node): + self.body.append(self.defs['term'][1]) + + def visit_tgroup(self, node): + pass + + def depart_tgroup(self, node): + pass + + def visit_compound(self, node): + pass + + def depart_compound(self, node): + pass + + def visit_thead(self, node): + raise NotImplementedError, node.astext() + self.write_colspecs() + self.body.append(self.context.pop()) # '\n' + # There may or may not be a ; this is for to use: + self.context.append('') + self.body.append(self.starttag(node, 'thead', valign='bottom')) + + def depart_thead(self, node): + raise NotImplementedError, node.astext() + self.body.append('\n') + + def visit_tip(self, node): + self.visit_admonition(node, 'tip') + + def depart_tip(self, node): + self.depart_admonition() + + def visit_title(self, node): + if isinstance(node.parent, nodes.topic): + self.body.append(self.comment('topic-title')) + elif isinstance(node.parent, nodes.sidebar): + self.body.append(self.comment('sidebar-title')) + elif isinstance(node.parent, nodes.admonition): + self.body.append(self.comment('admonition-title')) + elif self.section_level == 0: + # document title for .TH + self._docinfo['title'] = node.astext() + raise nodes.SkipNode + elif self.section_level == 1: + self._docinfo['subtitle'] = node.astext() + raise nodes.SkipNode + elif self.section_level == 2: + self.body.append('\n.SH ') + else: + self.body.append('\n.SS ') + + def depart_title(self, node): + self.body.append('\n') + + def visit_title_reference(self, node): + """inline citation reference""" + self.body.append(self.defs['title_reference'][0]) + + def depart_title_reference(self, node): + self.body.append(self.defs['title_reference'][1]) + + def visit_topic(self, node): + self.body.append(self.comment('topic: '+node.astext())) + raise nodes.SkipNode + ##self.topic_class = node.get('class') + + def depart_topic(self, node): + ##self.topic_class = '' + pass + + def visit_transition(self, node): + # .PP Begin a new paragraph and reset prevailing indent. + # .sp N leaves N lines of blank space. + # .ce centers the next line + self.body.append('\n.sp\n.ce\n----\n') + + def depart_transition(self, node): + self.body.append('\n.ce 0\n.sp\n') + + def visit_version(self, node): + self._docinfo["version"] = node.astext() + raise nodes.SkipNode + + def visit_warning(self, node): + self.visit_admonition(node, 'warning') + + def depart_warning(self, node): + self.depart_admonition() + + def visit_index(self, node): + pass + + def depart_index(self, node): + pass + + def visit_desc(self, node): + pass + + def depart_desc(self, node): + pass + + def visit_desc_signature(self, node): + # .. cmdoption makes options look like this + self.body.append('\n') + self.body.append('.TP') + self.body.append('\n') + + def depart_desc_signature(self, node): + pass + + def visit_desc_name(self, node): + self.body.append(r'\fB') # option name + + def depart_desc_name(self, node): + self.body.append(r'\fR') + + def visit_desc_addname(self, node): + self.body.append(r'\fR') + + def depart_desc_addname(self, node): + # self.body.append(r'\fR') + pass + + def visit_desc_content(self, node): + self.body.append('\n') # option help + + def depart_desc_content(self, node): + pass + + def unimplemented_visit(self, node): + pass + +# vim: set et ts=4 ai : diff --git a/doc/manpage.pyc b/doc/manpage.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3feb349660cf5b52dd4ae4d418e281158e266c2 GIT binary patch literal 51841 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 assert 2 == 4, "assert 2 is 4" + + Please note that dotted names are not expanded, and callables are not called + in the expansion. + +See below for the rest of the built-in plugins. + +Using Builtin plugins +--------------------- + +See :doc:`plugins/builtin` + +Writing plugins +--------------- + +.. toctree :: + :maxdepth: 2 + + plugins/writing + plugins/interface + plugins/errorclasses + plugins/documenting + +Testing plugins +--------------- + +.. toctree :: + :maxdepth: 2 + + plugins/testing \ No newline at end of file diff --git a/doc/plugins/allmodules.rst b/doc/plugins/allmodules.rst new file mode 100644 index 0000000..ad6d034 --- /dev/null +++ b/doc/plugins/allmodules.rst @@ -0,0 +1,4 @@ +AllModules: collect tests in all modules +======================================== + +.. autoplugin :: nose.plugins.allmodules \ No newline at end of file diff --git a/doc/plugins/attrib.rst b/doc/plugins/attrib.rst new file mode 100644 index 0000000..beaa834 --- /dev/null +++ b/doc/plugins/attrib.rst @@ -0,0 +1,4 @@ +Attrib: tag and select tests with attributes +============================================ + +.. autoplugin :: nose.plugins.attrib diff --git a/doc/plugins/builtin.rst b/doc/plugins/builtin.rst new file mode 100644 index 0000000..8d2147f --- /dev/null +++ b/doc/plugins/builtin.rst @@ -0,0 +1,30 @@ +Batteries included: builtin nose plugins +======================================== + +nose includes a number of builtin plugins that can make testing faster and easier. + +.. note :: + + nose 0.11.2 includes a change to default plugin loading. Now, a 3rd party + plugin with *the same name* as a builtin *will be loaded instead* + of the builtin plugin. + +.. toctree :: + :maxdepth: 2 + + allmodules + attrib + capture + collect + cover + debug + deprecated + doctests + failuredetail + isolate + logcapture + multiprocess + prof + skip + testid + xunit diff --git a/doc/plugins/capture.rst b/doc/plugins/capture.rst new file mode 100644 index 0000000..27a0c2e --- /dev/null +++ b/doc/plugins/capture.rst @@ -0,0 +1,5 @@ +Capture: capture stdout during tests +==================================== + +.. autoplugin :: nose.plugins.capture + diff --git a/doc/plugins/collect.rst b/doc/plugins/collect.rst new file mode 100644 index 0000000..011a96d --- /dev/null +++ b/doc/plugins/collect.rst @@ -0,0 +1,4 @@ +Collect: Collect tests quickly +============================== + +.. autoplugin :: nose.plugins.collect \ No newline at end of file diff --git a/doc/plugins/cover.rst b/doc/plugins/cover.rst new file mode 100644 index 0000000..e970b2c --- /dev/null +++ b/doc/plugins/cover.rst @@ -0,0 +1,14 @@ +Cover: code coverage +==================== + +.. note :: + + Newer versions of coverage contain their own nose plugin which is + superior to the builtin plugin. It exposes more of coverage's + options and uses coverage's native html output. Depending on the + version of coverage installed, the included plugin may override the + nose builtin plugin, or be available under a different name. Check + ``nosetests --help`` or ``nosetests --plugins`` to find out which + coverage plugin is available on your system. + +.. autoplugin :: nose.plugins.cover diff --git a/doc/plugins/debug.rst b/doc/plugins/debug.rst new file mode 100644 index 0000000..cac67f3 --- /dev/null +++ b/doc/plugins/debug.rst @@ -0,0 +1,4 @@ +Debug: drop into pdb on errors or failures +========================================== + +.. autoplugin :: nose.plugins.debug \ No newline at end of file diff --git a/doc/plugins/deprecated.rst b/doc/plugins/deprecated.rst new file mode 100644 index 0000000..ebb8140 --- /dev/null +++ b/doc/plugins/deprecated.rst @@ -0,0 +1,4 @@ +Deprecated: mark tests as deprecated +==================================== + +.. autoplugin :: nose.plugins.deprecated diff --git a/doc/plugins/doctests.rst b/doc/plugins/doctests.rst new file mode 100644 index 0000000..9763765 --- /dev/null +++ b/doc/plugins/doctests.rst @@ -0,0 +1,4 @@ +Doctests: run doctests with nose +================================ + +.. autoplugin :: nose.plugins.doctests diff --git a/doc/plugins/documenting.rst b/doc/plugins/documenting.rst new file mode 100644 index 0000000..c841f76 --- /dev/null +++ b/doc/plugins/documenting.rst @@ -0,0 +1,62 @@ +Documenting plugins +=================== + +A parable. If a plugin is released on pypi without any documentation, does +anyone care? + +To make it easy to document your plugins, nose includes a `Sphinx`_ extension +that will automatically generate plugin docs like those for nose's builtin +plugins. Simply add 'nose.sphinx.pluginopts' to the list of extensions in your +conf.py:: + + extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', + 'nose.sphinx.pluginopts'] + +Then in your plugin documents, include a title and the ``.. autoplugin`` +directive:: + + My Cool Plugin + ============== + + .. autoplugin :: package.module.with.plugin + :plugin: PluginClass + +The ``:plugin:`` option is optional. In most cases, the directive will +automatically detect which class in the named module is the plugin to be +documented. + +The output of the directive includes the docstring of the plugin module, the +options defined by the plugin, `autodoc`_ generated for the plugin class, and +the plugin module source. This is roughly equivalent to:: + + My Cool Plugin + ============== + + .. automodule :: package.module.with.plugin + + Options + ------- + + .. cmdoption :: --with-coolness + + Help text of the coolness option. + + .. cmdoption :: + + Plugin + ------- + + .. autoclass :: package.module.with.plugin.PluginClass + :members: + + Source + ------ + + .. include :: path/to/package/module/with/plugin.py + :literal: + +Document your plugins! Your users might not thank you -- but at least you'll +*have* some users. + +.. _`Sphinx` : http://sphinx.pocoo.org/ +.. _`autodoc`: http://sphinx.pocoo.org/ext/autodoc.html \ No newline at end of file diff --git a/doc/plugins/errorclasses.rst b/doc/plugins/errorclasses.rst new file mode 100644 index 0000000..1e758f4 --- /dev/null +++ b/doc/plugins/errorclasses.rst @@ -0,0 +1,7 @@ +.. automodule :: nose.plugins.errorclass + +Error class methods +------------------- + +.. autoclass :: nose.plugins.errorclass.ErrorClassPlugin + :members: diff --git a/doc/plugins/failuredetail.rst b/doc/plugins/failuredetail.rst new file mode 100644 index 0000000..5a3d0df --- /dev/null +++ b/doc/plugins/failuredetail.rst @@ -0,0 +1,4 @@ +Failure Detail: introspect asserts +================================== + +.. autoplugin :: nose.plugins.failuredetail diff --git a/doc/plugins/interface.rst b/doc/plugins/interface.rst new file mode 100644 index 0000000..cab8b9d --- /dev/null +++ b/doc/plugins/interface.rst @@ -0,0 +1,122 @@ + +.. _plugin-interface: + +Plugin Interface +================ + +Plugin base class +----------------- + +.. autoclass :: nose.plugins.base.Plugin + :members: + +Nose plugin API +--------------- + +Plugins may implement any or all of the methods documented below. Please note +that they *must not* subclass `IPluginInterface`; `IPluginInterface` is only a +description of the plugin API. + +When plugins are called, the first plugin that implements a method and returns +a non-None value wins, and plugin processing ends. The exceptions to this are +methods marked as `generative` or `chainable`. `generative` methods combine +the output of all plugins that respond with an iterable into a single +flattened iterable response (a generator, really). `chainable` methods pass +the results of calling plugin A as the input to plugin B, where the positions +in the chain are determined by the plugin sort order, which is in order by +`score` descending. + +In general, plugin methods correspond directly to methods of +`nose.selector.Selector`, `nose.loader.TestLoader` and +`nose.result.TextTestResult` are called by those methods when they are +called. In some cases, the plugin hook doesn't neatly match the method in +which it is called; for those, the documentation for the hook will tell you +where in the test process it is called. + +Plugin hooks fall into four broad categories: selecting and loading tests, +handling errors raised by tests, preparing objects used in the testing +process, and watching and reporting on test results. + +Selecting and loading tests +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To alter test selection behavior, implement any necessary `want*` methods as +outlined below. Keep in mind, though, that when your plugin returns True from +a `want*` method, you will send the requested object through the normal test +collection process. If the object represents something from which normal tests +can't be collected, you must also implement a loader method to load the tests. + +Examples: + +* The builtin :doc:`doctests plugin ` implements `wantFile` to + enable loading of doctests from files that are not python modules. It + also implements `loadTestsFromModule` to load doctests from + python modules, and `loadTestsFromFile` to load tests from the + non-module files selected by `wantFile`. + +* The builtin :doc:`attrib plugin ` implements `wantFunction` and + `wantMethod` so that it can reject tests that don't match the + specified attributes. + +Handling errors +^^^^^^^^^^^^^^^ + +To alter error handling behavior -- for instance to catch a certain class of +exception and handle it differently from the normal error or failure handling +-- you should subclass :class:`nose.plugins.errorclass.ErrorClassPlugin`. See +:doc:`the section on ErrorClass plugins ` for more details. + +Examples: + +* The builtin :doc:`skip ` and :doc:`deprecated ` plugins are + ErrorClass plugins. + + +Preparing test objects +^^^^^^^^^^^^^^^^^^^^^^ + +To alter, get a handle on, or replace test framework objects such as the +loader, result, runner, and test cases, use the appropriate prepare methods. +The simplest reason to use prepare is in the case that you need to use an +object yourself. For example, the isolate plugin implements `prepareTestLoader` +so that it can use the loader later on to load tests. If you return a value +from a prepare method, that value will be used in place of the loader, result, +runner or test case, depending on which prepare method you use. Be aware that +when replacing test cases, you are replacing the *entire* test case -- including +the whole `run(result)` method of the `unittest.TestCase` -- so if you want +normal unittest test result reporting, you must implement the same calls to +result as `unittest.TestCase.run`. + +Examples: + +* The builtin :doc:`isolate plugin ` implements `prepareTestLoader` + but does not replace the test loader. + +* The builtin :doc:`profile plugin ` implements `prepareTest` and does + replace the top-level test case by returning the case wrapped in + the profiler function. + +Watching or reporting on tests +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To record information about tests or other modules imported during +the testing process, output additional reports, or entirely change +test report output, implement any of the methods outlined below that +correspond to TextTestResult methods. + +Examples: + +* The builtin :doc:`cover plugin ` implements `begin` and `report` to + capture and report code coverage metrics for all or selected modules + loaded during testing. + +* The builtin :doc:`profile plugin ` implements `begin`, `prepareTest` + and `report` to record and output profiling information. In this + case, the plugin's `prepareTest` method constructs a function that + runs the test through the hotshot profiler's runcall() method. + +Plugin interface methods +------------------------ + +.. autoclass :: nose.plugins.base.IPluginInterface + :members: \ No newline at end of file diff --git a/doc/plugins/isolate.rst b/doc/plugins/isolate.rst new file mode 100644 index 0000000..94b5641 --- /dev/null +++ b/doc/plugins/isolate.rst @@ -0,0 +1,4 @@ +Isolate: protect tests from (some) side-effects +----------------------------------------------- + +.. autoplugin :: nose.plugins.isolate \ No newline at end of file diff --git a/doc/plugins/logcapture.rst b/doc/plugins/logcapture.rst new file mode 100644 index 0000000..4bf09c3 --- /dev/null +++ b/doc/plugins/logcapture.rst @@ -0,0 +1,4 @@ +Logcapture: capture logging during tests +======================================== + +.. autoplugin :: nose.plugins.logcapture \ No newline at end of file diff --git a/doc/plugins/multiprocess.rst b/doc/plugins/multiprocess.rst new file mode 100644 index 0000000..c9f4aa7 --- /dev/null +++ b/doc/plugins/multiprocess.rst @@ -0,0 +1,5 @@ +------------------------------ +Multiprocess: parallel testing +------------------------------ + +.. autoplugin :: nose.plugins.multiprocess diff --git a/doc/plugins/other.rst b/doc/plugins/other.rst new file mode 100644 index 0000000..47490c3 --- /dev/null +++ b/doc/plugins/other.rst @@ -0,0 +1,6 @@ +Third-party nose plugins +------------------------ + +Visit http://nose-plugins.jottit.com/ for a list of third-party nose plugins +compatible with nose 0.9 through 0.11. If you have released a plugin that you +don't see in the list, please add it! diff --git a/doc/plugins/prof.rst b/doc/plugins/prof.rst new file mode 100644 index 0000000..f778942 --- /dev/null +++ b/doc/plugins/prof.rst @@ -0,0 +1,4 @@ +Prof: enable profiling using the hotshot profiler +================================================= + +.. autoplugin :: nose.plugins.prof diff --git a/doc/plugins/skip.rst b/doc/plugins/skip.rst new file mode 100644 index 0000000..07f9207 --- /dev/null +++ b/doc/plugins/skip.rst @@ -0,0 +1,5 @@ +Skip: mark tests as skipped +=========================== + +.. autoplugin :: nose.plugins.skip + :plugin: Skip \ No newline at end of file diff --git a/doc/plugins/testid.rst b/doc/plugins/testid.rst new file mode 100644 index 0000000..377e2e7 --- /dev/null +++ b/doc/plugins/testid.rst @@ -0,0 +1,4 @@ +Testid: add a test id to each test name output +============================================== + +.. autoplugin :: nose.plugins.testid diff --git a/doc/plugins/testing.rst b/doc/plugins/testing.rst new file mode 100644 index 0000000..1e17fb2 --- /dev/null +++ b/doc/plugins/testing.rst @@ -0,0 +1,7 @@ +.. automodule :: nose.plugins.plugintest + +PluginTester methods +-------------------- + +.. autoclass :: nose.plugins.plugintest.PluginTester + :members: \ No newline at end of file diff --git a/doc/plugins/writing.rst b/doc/plugins/writing.rst new file mode 100644 index 0000000..ed73b9f --- /dev/null +++ b/doc/plugins/writing.rst @@ -0,0 +1 @@ +.. automodule :: nose.plugins \ No newline at end of file diff --git a/doc/plugins/xunit.rst b/doc/plugins/xunit.rst new file mode 100644 index 0000000..5602e5d --- /dev/null +++ b/doc/plugins/xunit.rst @@ -0,0 +1,4 @@ +Xunit: output test results in xunit format +========================================== + +.. autoplugin :: nose.plugins.xunit \ No newline at end of file diff --git a/doc/rtd-requirements.txt b/doc/rtd-requirements.txt new file mode 100644 index 0000000..2872958 --- /dev/null +++ b/doc/rtd-requirements.txt @@ -0,0 +1,3 @@ +# requirements file for Read The Docs +# http://readthedocs.org/docs/nose/ +sphinx>=1.0 diff --git a/doc/setuptools_integration.rst b/doc/setuptools_integration.rst new file mode 100644 index 0000000..b886e76 --- /dev/null +++ b/doc/setuptools_integration.rst @@ -0,0 +1,38 @@ +Setuptools integration +====================== + +.. warning :: Please note that when run under the setuptools test command, + many plugins will not be available, including the builtin + coverage and profiler plugins. If you want to access to all + available plugins, use the :doc:`nosetests ` + command instead. + +nose may be used with the setuptools_ test command. Simply specify +nose.collector as the test suite in your setup file:: + + setup ( + # ... + test_suite = 'nose.collector' + ) + +Then to find and run tests, you can run:: + + python setup.py test + +When running under setuptools, you can configure nose settings via the +environment variables detailed in the nosetests script usage message, +or the setup.cfg, ~/.noserc or ~/.nose.cfg config files. + +`nosetests` command +------------------- + +nose also includes its own setuptools command, ``nosetests``, that provides +support for all plugins and command line options. It works just like the +``test`` command:: + + python setup.py nosetests + +See :doc:`api/commands` for more information about the ``nosetests`` command. + +.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools + diff --git a/doc/testing.rst b/doc/testing.rst new file mode 100644 index 0000000..42bf6fe --- /dev/null +++ b/doc/testing.rst @@ -0,0 +1,55 @@ +Testing with nose +================= + +Writing tests is easier +----------------------- + +nose collects tests from :class:`unittest.TestCase` subclasses, of course. But +you can also write simple test functions, as well as test classes that are +*not* subclasses of :class:`unittest.TestCase`. nose also supplies a number of +helpful functions for writing timed tests, testing for exceptions, and other +common use cases. See :doc:`writing_tests` and :doc:`testing_tools` for more. + +Running tests is easier +----------------------- + +nose collects tests automatically, as long as you follow some simple +guidelines for organizing your library and test code. There's no need +to manually collect test cases into test suites. Running tests is +responsive, since nose begins running tests as soon as the first test +module is loaded. See :doc:`finding_tests` for more. + +Setting up your test environment is easier +------------------------------------------ + +nose supports fixtures at the package, module, class, and test case +level, so expensive initialization can be done as infrequently as +possible. See :ref:`fixtures` for more. + +Doing what you want to do is easier +----------------------------------- + +nose comes with a number of :doc:`builtin plugins ` to help +you with output capture, error introspection, code coverage, doctests, and +more. It also comes with plugin hooks for loading, running, watching and +reporting on tests and test runs. If you don't like the default collection +scheme, or it doesn't suit the layout of your project, or you need reports in +a format different from the unittest standard, or you need to collect some +additional information about tests (like code coverage or profiling data), you +can write a plugin to make nose do what you want. See the section on +:doc:`plugins/writing` for more. There are also many +`third-party nose plugins `_ available. + +Details +------- + +.. toctree :: + :maxdepth: 2 + + usage + writing_tests + finding_tests + testing_tools + plugins/builtin + plugins/other + setuptools_integration diff --git a/doc/testing_tools.rst b/doc/testing_tools.rst new file mode 100644 index 0000000..45e2958 --- /dev/null +++ b/doc/testing_tools.rst @@ -0,0 +1,11 @@ +Testing tools +------------- + +The nose.tools module provides a number of testing aids that you may +find useful, including decorators for restricting test execution time +and testing for exceptions, and all of the same assertX methods found +in `unittest.TestCase` (only spelled in pep08 fashion, so `assert_equal` +rather than `assertEqual`). + +.. automodule :: nose.tools + :members: \ No newline at end of file diff --git a/doc/usage.rst b/doc/usage.rst new file mode 100644 index 0000000..11e682b --- /dev/null +++ b/doc/usage.rst @@ -0,0 +1,42 @@ +Basic usage +----------- + +Use the nosetests script (after installation by setuptools):: + + nosetests [options] [(optional) test files or directories] + +In addition to passing command-line options, you may also put configuration +options in a .noserc or nose.cfg file in your home directory. These are +standard .ini-style config files. Put your nosetests configuration in a +[nosetests] section, with the -- prefix removed:: + + [nosetests] + verbosity=3 + with-doctest=1 + +There are several other ways to use the nose test runner besides the +`nosetests` script. You may use nose in a test script:: + + import nose + nose.main() + +If you don't want the test script to exit with 0 on success and 1 on failure +(like unittest.main), use nose.run() instead:: + + import nose + result = nose.run() + +`result` will be true if the test run succeeded, or false if any test failed +or raised an uncaught exception. Lastly, you can run nose.core directly, which +will run nose.main():: + + python /path/to/nose/core.py + +Please see the usage message for the nosetests script for information +about how to control which tests nose runs, which plugins are loaded, +and the test output. + +Extended usage +^^^^^^^^^^^^^^ + +.. autohelp :: diff --git a/doc/writing_tests.rst b/doc/writing_tests.rst new file mode 100644 index 0000000..d2418bd --- /dev/null +++ b/doc/writing_tests.rst @@ -0,0 +1,172 @@ +Writing tests +------------- + +As with py.test_, nose tests need not be subclasses of +:class:`unittest.TestCase`. Any function or class that matches the configured +testMatch regular expression (``(?:^|[\\b_\\.-])[Tt]est)`` by default -- that +is, has test or Test at a word boundary or following a - or _) and lives in a +module that also matches that expression will be run as a test. For the sake +of compatibility with legacy unittest test cases, nose will also load tests +from :class:`unittest.TestCase` subclasses just like unittest does. Like +py.test, nose runs functional tests in the order in which they appear in the +module file. TestCase-derived tests and other test classes are run in +alphabetical order. + +.. _py.test: http://codespeak.net/py/current/doc/test.html + +.. _fixtures: + +Fixtures +======== + +nose supports fixtures (setup and teardown methods) at the package, +module, class, and test level. As with py.test or unittest fixtures, +setup always runs before any test (or collection of tests for test +packages and modules); teardown runs if setup has completed +successfully, regardless of the status of the test run. For more detail +on fixtures at each level, see below. + +Test packages +============= + +nose allows tests to be grouped into test packages. This allows +package-level setup; for instance, if you need to create a test database +or other data fixture for your tests, you may create it in package setup +and remove it in package teardown once per test run, rather than having to +create and tear it down once per test module or test case. + +To create package-level setup and teardown methods, define setup and/or +teardown functions in the ``__init__.py`` of a test package. Setup methods may +be named `setup`, `setup_package`, `setUp`, or `setUpPackage`; teardown may +be named `teardown`, `teardown_package`, `tearDown` or `tearDownPackage`. +Execution of tests in a test package begins as soon as the first test +module is loaded from the test package. + +Test modules +============ + +A test module is a python module that matches the testMatch regular +expression. Test modules offer module-level setup and teardown; define the +method `setup`, `setup_module`, `setUp` or `setUpModule` for setup, +`teardown`, `teardown_module`, or `tearDownModule` for teardown. Execution +of tests in a test module begins after all tests are collected. + +Test classes +============ + +A test class is a class defined in a test module that matches testMatch or is +a subclass of :class:`unittest.TestCase`. All test classes are run the same +way: Methods in the class that match testMatch are discovered, and a test +case is constructed to run each method with a fresh instance of the test +class. Like :class:`unittest.TestCase` subclasses, other test classes can +define setUp and tearDown methods that will be run before and after each test +method. Test classes that do not descend from `unittest.TestCase` may also +include generator methods and class-level fixtures. Class-level setup fixtures +may be named `setup_class`, `setupClass`, `setUpClass`, `setupAll` or +`setUpAll`; teardown fixtures may be named `teardown_class`, `teardownClass`, +`tearDownClass`, `teardownAll` or `tearDownAll`. Class-level setup and teardown +fixtures must be class methods. + +Test functions +============== + +Any function in a test module that matches testMatch will be wrapped in a +`FunctionTestCase` and run as a test. The simplest possible failing test is +therefore:: + + def test(): + assert False + +And the simplest passing test:: + + def test(): + pass + +Test functions may define setup and/or teardown attributes, which will be +run before and after the test function, respectively. A convenient way to +do this, especially when several test functions in the same module need +the same setup, is to use the provided `with_setup` decorator:: + + def setup_func(): + "set up test fixtures" + + def teardown_func(): + "tear down test fixtures" + + @with_setup(setup_func, teardown_func) + def test(): + "test ..." + +For python 2.3 or earlier, add the attributes by calling the decorator +function like so:: + + def test(): + "test ... " + test = with_setup(setup_func, teardown_func)(test) + +or by direct assignment:: + + test.setup = setup_func + test.teardown = teardown_func + +Please note that `with_setup` is useful *only* for test functions, not +for test methods in `unittest.TestCase` subclasses or other test +classes. For those cases, define `setUp` and `tearDown` methods in the +class. + +Test generators +=============== + +nose supports test functions and methods that are generators. A simple +example from nose's selftest suite is probably the best explanation:: + + def test_evens(): + for i in range(0, 5): + yield check_even, i, i*3 + + def check_even(n, nn): + assert n % 2 == 0 or nn % 2 == 0 + +This will result in five tests. nose will iterate the generator, creating a +function test case wrapper for each tuple it yields. As in the example, test +generators must yield tuples, the first element of which must be a callable +and the remaining elements the arguments to be passed to the callable. + +By default, the test name output for a generated test in verbose mode +will be the name of the generator function or method, followed by the +args passed to the yielded callable. If you want to show a different test +name, set the ``description`` attribute of the yielded callable. + +Setup and teardown functions may be used with test generators. However, please +note that setup and teardown attributes attached to the *generator function* +will execute only once. To *execute fixtures for each yielded test*, attach +the setup and teardown attributes to the function that is yielded, or yield a +callable object instance with setup and teardown attributes. + +For example:: + + @with_setup(setup_func, teardown_func) + def test_generator(): + # ... + yield func, arg, arg # ... + +Here, the setup and teardown functions will be executed *once*. Compare to:: + + def test_generator(): + # ... + yield func, arg, arg # ... + + @with_setup(setup_func, teardown_func) + def func(arg): + assert something_about(arg) + +In the latter case the setup and teardown functions will execute once for each +yielded test. + +For generator methods, the setUp and tearDown methods of the class (if any) +will be run before and after each generated test case. The setUp and tearDown +methods *do not* run before the generator method itself, as this would cause +setUp to run twice before the first test without an intervening tearDown. + +Please note that method generators *are not* supported in `unittest.TestCase` +subclasses. \ No newline at end of file diff --git a/examples/attrib_plugin.py b/examples/attrib_plugin.py new file mode 100644 index 0000000..c1f8458 --- /dev/null +++ b/examples/attrib_plugin.py @@ -0,0 +1,82 @@ +""" +Examples of test function/method attribute usage with patched nose + +Simple syntax (-a, --attr) examples: + * nosetests -a status=stable + => only test cases with attribute "status" having value "stable" + + * nosetests -a priority=2,status=stable + => both attributes must match + + * nosetests -a tags=http + => attribute list "tags" must contain value "http" (see test_foobar() + below for definition) + + * nosetests -a slow + => attribute "slow" must be defined and its value cannot equal to False + (False, [], "", etc...) + + * nosetests -a !slow + => attribute "slow" must NOT be defined or its value must be equal to False + +Eval expression syntax (-A, --eval-attr) examples: + * nosetests -A "not slow" + * nosetests -A "(priority > 5) and not slow" + +This example and the accompanied patch is in public domain, free for any use. + +email: mika.eloranta@gmail.com + +""" + +__author__ = 'Mika Eloranta' + +def attr(**kwargs): + """Add attributes to a test function/method/class""" + def wrap(func): + func.__dict__.update(kwargs) + return func + return wrap + +# test function with single attribute +@attr(priority = 1) +def test_dummy(): + print "dummy" + +# test function with multiple attributes +@attr(status = "stable", # simple string attribute + slow = True, # attributes can be of any type + # (e.g. bool) + priority = 1, # ...or int + tags = ["http", "pop", "imap"]) # will be run if any of the list items + # matches +def test_foobar(): + print "foobar" + +# another way of adding attributes... +def test_fluffy(): + print "fluffy" +test_fluffy.status = "unstable" +test_fluffy.slow = True +test_fluffy.priority = 2 + +# works for class methods, too +class TestSomething: + @attr(status = "stable", priority = 2) + def test_xyz(self): + print "xyz" + +# class methods "inherit" attributes from the class but can override them +class TestOverride: + value = "class" + # run all methods with "nosetests -a value" + + @attr(value = "method") + def test_override(self): + # run with "nosetests -a value=method" + print "override" + + def test_inherit(self): + # run with "nosetests -a value=class" + print "inherit" + diff --git a/examples/html_plugin/htmlplug.py b/examples/html_plugin/htmlplug.py new file mode 100644 index 0000000..aa1bcb6 --- /dev/null +++ b/examples/html_plugin/htmlplug.py @@ -0,0 +1,92 @@ +"""This is a very basic example of a plugin that controls all test +output. In this case, it formats the output as ugly unstyled html. + +Upgrading this plugin into one that uses a template and css to produce +nice-looking, easily-modifiable html output is left as an exercise for +the reader who would like to see his or her name in the nose AUTHORS file. +""" +import traceback +from nose.plugins import Plugin + +class HtmlOutput(Plugin): + """Output test results as ugly, unstyled html. + """ + + name = 'html-output' + score = 2 # run late + + def __init__(self): + super(HtmlOutput, self).__init__() + self.html = [ '', + 'Test output', + '' ] + + def addSuccess(self, test): + self.html.append('ok') + + def addError(self, test, err): + err = self.formatErr(err) + self.html.append('ERROR') + self.html.append('
%s
' % err) + + def addFailure(self, test, err): + err = self.formatErr(err) + self.html.append('FAIL') + self.html.append('
%s
' % err) + + def finalize(self, result): + self.html.append('
') + self.html.append("Ran %d test%s" % + (result.testsRun, result.testsRun != 1 and "s" or "")) + self.html.append('
') + self.html.append('
') + if not result.wasSuccessful(): + self.html.extend(['FAILED ( ', + 'failures=%d ' % len(result.failures), + 'errors=%d' % len(result.errors), + ')']) + else: + self.html.append('OK') + self.html.append('
') + # print >> sys.stderr, self.html + for l in self.html: + self.stream.writeln(l) + + def formatErr(self, err): + exctype, value, tb = err + return ''.join(traceback.format_exception(exctype, value, tb)) + + def setOutputStream(self, stream): + # grab for own use + self.stream = stream + # return dummy stream + class dummy: + def write(self, *arg): + pass + def writeln(self, *arg): + pass + d = dummy() + return d + + def startContext(self, ctx): + try: + n = ctx.__name__ + except AttributeError: + n = str(ctx).replace('<', '').replace('>', '') + self.html.extend(['
', '', n, '']) + try: + path = ctx.__file__.replace('.pyc', '.py') + self.html.extend(['
', path, '
']) + except AttributeError: + pass + + def stopContext(self, ctx): + self.html.append('
') + + def startTest(self, test): + self.html.extend([ '
', + test.shortDescription() or str(test), + '' ]) + + def stopTest(self, test): + self.html.append('
') diff --git a/examples/html_plugin/setup.py b/examples/html_plugin/setup.py new file mode 100644 index 0000000..3caa08d --- /dev/null +++ b/examples/html_plugin/setup.py @@ -0,0 +1,24 @@ +import sys +try: + import ez_setup + ez_setup.use_setuptools() +except ImportError: + pass + +from setuptools import setup + +setup( + name='Example html output plugin', + version='0.1', + author='Jason Pellerin', + author_email = 'jpellerin+nose@gmail.com', + description = 'Example nose html output plugin', + license = 'GNU LGPL', + py_modules = ['htmlplug'], + entry_points = { + 'nose.plugins.0.10': [ + 'htmlout = htmlplug:HtmlOutput' + ] + } + + ) diff --git a/examples/plugin/plug.py b/examples/plugin/plug.py new file mode 100644 index 0000000..444226d --- /dev/null +++ b/examples/plugin/plug.py @@ -0,0 +1,4 @@ +from nose.plugins import Plugin + +class ExamplePlugin(Plugin): + pass diff --git a/examples/plugin/setup.py b/examples/plugin/setup.py new file mode 100644 index 0000000..4dd5dad --- /dev/null +++ b/examples/plugin/setup.py @@ -0,0 +1,27 @@ +""" +An example of how to create a simple nose plugin. + +""" +try: + import ez_setup + ez_setup.use_setuptools() +except ImportError: + pass + +from setuptools import setup + +setup( + name='Example plugin', + version='0.1', + author='Jason Pellerin', + author_email = 'jpellerin+nose@gmail.com', + description = 'Example nose plugin', + license = 'GNU LGPL', + py_modules = ['plug'], + entry_points = { + 'nose.plugins.0.10': [ + 'example = plug:ExamplePlugin' + ] + } + + ) diff --git a/functional_tests/doc_tests/test_addplugins/support/test$py.class b/functional_tests/doc_tests/test_addplugins/support/test$py.class new file mode 100644 index 0000000000000000000000000000000000000000..f4dea0b03aaef3375b7a6b45852200243e06b3c6 GIT binary patch literal 2628 zcmbtV-Bue_6#fngOcIBdM%q%K6fDv}fH5fA8bm2Ani5h3Oa;_9Bqw3$WM-V1(8ixj zpQB!Tg`mK)OCP|8a=FiB$SP#g8!s~FKWwpL!t5u$EA#Cj0TrI7;s>{>kE!5TeYBkB z!imgyUPdRnVhBTGh_}i~h%(3ztCs7lcNhk?+Z&hh2IMGW=w|3|Rku_#imqlF5-u}D z%Y}+=?x;HH?0=ogD~LzQ!#5eaT<*A|DT<<8&{fCP7+6NeHCz`AeH2s9G`Ng@ArZp> zF?teKUBXRPv<}^cd=Na~bYU3yQH7RIkBjDXvCaUljZpYAr zQ3^qL9KjevM3l-bsyc&`$nL58s-mk#MOk-k&8V~jqzM6`FoAJV)(M8I0kY$HhFk6H zv!U+jT*4H=G|})*iD4Q9*s#O(GSx2Y=ao`7gLg&h-ec$}5JylceUBNo6wB833Bih6 zwG_j2c){j6SLsYmr>19=vX2l|F9-{cQZkGFP5dvYrIMxBDw^RaPR+7R+f~H-v{jGb zE>*ZSriN_^9}F1HO{Vwes}OhB1@?`6#4qZrbdm+?M65HWwk&~vda67Ey! z=Bj3?rt|p-9uNo({!HME;YA{|9Z<=OI?}B?nXRn=*UlQsD_A;6cYwJ}CTM*4jj)7I zW7ImI$smM?7$1@U74EL6nnA@)v=t?6R6U++i2$49`3PN_Gs6V|ZT74AHO;N(^6MPv%9-9=KT{9Z7 z&9>@0FBTYll~=5=LYSE zMbn@>)QYZYuko@@lb_~llbvDaRd6rTuyg{J#(7U<6j6#{2L#k^8asO7tQ#5D6PY0R z{jW_WAl4y3po#hG-JS-(VYPmi4GC3-;q!h}ElP;3EUaeo+;%7jl+ez7Rj=_7t=fKC zSAqO18yfA{PJpF;a(;PEY!{30%DR+G%ge43yNgVqK(cQlewaU;Vd7oEdk@>q%P4D=!Pb^F6U3_@;dlB4CUpljke8R&m-5C} zzPUels)MC6o(JxjGZ=sez==r)O|fF-$9ELp`r~T~xl{!M`~mR?fMM#dnk>>7(D`WF z$qrWYtjem~;(2x*_1@{BB^xQC@!i5?Z?!RDm@mDH80QfDL@0V0ZX^kWOMoaLAyX+$ SEy@>@r>> import os +>>> support = os.path.join(os.path.dirname(__file__), 'support') +>>> from nose.plugins.plugintest import run_buffered as run +>>> argv = [__file__, '-v', support] # --collect-only +>>> run(argv=argv) +test.test ... ok + +---------------------------------------------------------------------- +Ran 1 test in ...s + +OK + +Without '-v', we get a dot. + +>>> run(argv=[__file__, support]) +. +---------------------------------------------------------------------- +Ran 1 test in ...s + +OK + +The plugin is simple. It captures and wraps the test result output stream and +replaces 'ok' with 'maybe' and '.' with '?'. + +>>> from nose.plugins.base import Plugin +>>> class Maybe(Plugin): +... def setOutputStream(self, stream): +... self.stream = stream +... return self +... def flush(self): +... self.stream.flush() +... def writeln(self, out=""): +... self.write(out + "\n") +... def write(self, out): +... if out == "ok\n": +... out = "maybe\n" +... elif out == ".": +... out = "?" +... self.stream.write(out) + +To activate the plugin, we pass an instance in the addplugins list. + +>>> run(argv=argv + ['--with-maybe'], addplugins=[Maybe()]) +test.test ... maybe + +---------------------------------------------------------------------- +Ran 1 test in ...s + +OK + +>>> run(argv=[__file__, support, '--with-maybe'], addplugins=[Maybe()]) +? +---------------------------------------------------------------------- +Ran 1 test in ...s + +OK + diff --git a/functional_tests/doc_tests/test_allmodules/support/mod$py.class b/functional_tests/doc_tests/test_allmodules/support/mod$py.class new file mode 100644 index 0000000000000000000000000000000000000000..63226274a79b0fe50b87aca00d7f20d8defc60b4 GIT binary patch literal 3239 zcmbtW>r)$56#re4up!;H5c{CCrNPEF5MT?oYNetS!KMX@fGD)OF3C+;*zCsL4K204 z-|yG>Xmv(E`N2<>DrM}b-~FQ;&)sYzGz;U5nc3XCd+vGM-}#+$?_Yoa_6L9-e8&*V zCyy~qq(&^epp=%~dCOF?md%y%drK8C%U zn3ND=kR0wBy2%;BsZ9H{j0oyu2t#6s*Xl`VV33E3CD%9aFzmmuxpNuYAU7a}?F=oo z=7!5=*3~Uj!cK;Uyjn19_X%wwova~GWAVYJ2lR+!T`(*6Kp$4=fK?DJpe<>Np_v6`(zSK7P|J@h1BAg8 z+C^47D0{-7n%8v0N%j)zs+!|-<$|h4(M7KX?pCtE&>i4q3!=$Ta7;L%d^FeQy0SY7 z_cLr$Rns!BaNANlcD5W{gi$PgJO3h*dSGSsPLD~JpJo@Mmmlt|Pg3_AuK zhueZpL$+<%Q9R19y{Iknq03ob65kmnx5(iZ2?-EYjS^V~a5_fp8kEslq0JCO>|d0l zc!FVf5SG&nhiZYkQxsF$oWUiW^$_wnF%rW$j1ngtZsds&BNZ}J2+N(Ku8qdTqjTwR~fV$<~*sD=BWV zq?ne&Rht`Jqp_#Er@L3l`&F+Qs)*B3a#q%#d8lcIK^rR@+)71wdsUAl)ow(bQ+EkSoQS{dANd@?9pN`_7XepT(*|cOV zU{TCo2x!S7k`YVGaa#hl;oVs}Vq89{7fS{|ZCS44+FFU>WKbP>yQqy(e6FezRm)h4 z!Nn!on_V_5dpl8UZ=+h^wqz{hia_i|;sW8cL4~0$0I&^9=x97#TYeK|(-f6gjakAg z4DDopSU0#yNAaL#5)S38YuOV#Z_pu2&851Wp?7_)57N2h1f0(sBa!hMUXS5byg^Ni zTD+m&DH74NGC}nFUzY+wu{uG5cf?ue&E6C3%bR@=K#cL_JGe;rh#|2tqGE}lv6X@G z%rv(hLW7`v_rf;W90|J00upI1;`jo(tHh$&JvB zWvpDBU6?3B2W_F_F|E7&o18;vvR zU$Cod1#RpW?i)+Tk9Msh&BoGF?UG@C$r*o1-LKfUf&(|mB*QoKA0t;{4st_PS#^Ab zW&+_I=?Ga(*XY%IIwDL;E&YpNv!o$(j;EtSC*Hk^-c>ww4F}Wlhb!~zXj(ykI{p~F zs%v=s&-Iq;yof56Be+JEn#gh*AqlM#!V z{`j-rsD?tnLmTZ($FreOXay4?#zIYvSVQ3^^s4Y^*b|05A@hW7RpA-Q6GqkvhkvFx zL?qPi{74#U^gvvJF2DsEV4&zr6BMP8?z)ZEnYeiirB&E#xZJUZs})-fcnL52tK;5U zpg!KZhBqtL7~aC$zWhf@mEc5oM`zdmpM)>aC9Luj$vi+$f}Z3m-o3Gk_v!iY2CJl8 zfSM=mgn+Y^`KYb~IJuc1wV9xEGeI4$leaot#J4o=qS+_3gHJQ~44;e1S$y#i{QFk= literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_allmodules/support/mod.py b/functional_tests/doc_tests/test_allmodules/support/mod.py new file mode 100644 index 0000000..e136d56 --- /dev/null +++ b/functional_tests/doc_tests/test_allmodules/support/mod.py @@ -0,0 +1,5 @@ +def test(): + pass + +def test_fails(): + assert False, "This test fails" diff --git a/functional_tests/doc_tests/test_allmodules/support/mod.pyc b/functional_tests/doc_tests/test_allmodules/support/mod.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17ebf6e33886edca4d43ecc61663689ffea0b28e GIT binary patch literal 435 zcmZ8dO-sZu5S?`OU_m|Ud5=9ctaw)uJ@(Lg(Mt(!77J+;GE+gki9f>M=`V1mEeqR1 zUfz7YylMY;o__t_77Z+);`^QtUNf`+KLBq`)I=1t2~^pJOGg8bGH@~vRGCN=83U|< zUOA^j>^=-xO?}XVM_Whhtl@i?@A4bHABToIKNwe&MMB;BW?>T7#<||NqqC@S48cdz z9OvPa#DKKO``VPZdV%fAS zidA=aO+zV~Hdj{bi?&+jnU;t3U7M?=ysNrAlWciitv}w`<3%^aaJ`L52_Xi_;jXS3 zoFSaZCO2esqAP|lB!+mao`fia{IF`dzIlgXV7t9@8E-(2B8F~;{#J8~HKXWirXk@n zL$q9|=;n^9lg<9unY@B{ln}nj(B*Q+9Zgdd<$|s{uExN!GOppe5a^?ra;Cv$^b3s` z21wA;u<8M7{l*IC$y00p_YE+cGYimZOl^{(D2!jbEMOnugt_J8PHyCcU zug|)=qjL$9MAJkAof5+oh_GRY>t(9lvY%H<-89}6se6y1qd*ctrSuUqY$=wl?Gu9) zw`wVd>F|Qhb*|Exo=Q(mD`lS{s$LKt9HnFy{hRo|pz3gSl$d}r`RHZL;-eTcn3M57J`gc~!q9WEE)woj z>1G=!=OTDOBsBOl!83*ziR^YjB`?~@w8~_+wgRA?HI!GdOpxsWciC*v`0yKH5ue7W zbv~0p3=uItBK#HZuBe(p#Z9zTC2mwbnQMsvpUI5~ZVH-D#K`97GM2F-Zux*YA&@Ma zYKhwtz99Yfn7?zA}d?hmWHF1IRbWVj~Bp_kuL}*^$ZYAwn%`ij{HZe<38IojwQPa6W`(eQ} zC=a!wYuanPtkdMDx!M$Gn0}Sq3p6a9fTeLBiHssjG303BBAXW5WYWf(q>qiRt?Y-N5myTNUTazF{~+*kD)57Daar*#!5 zzp}2;j_m|k`X}d?=OlKq2(PSL2{mH3NEXB8Wm=xOT6KrpUei&p(@HKrT@jRw<;|L1 zq-!yDRQNXvpblj7r1jL683f(t>#P5x&zGn5;dhflMZ|lVyfi zy1PFQWeaO1GEdT!3UmMufQyg>>TDy*kMFF$?Z;PVdRb-y@CPg(0EVew3|r(O;ONOV zlXcd}Eb=1X<8>Ze?)_+7!71#+pF61|SUyNg2~r Ss(dkhirFe<(SM9by1oIWr81`g literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_allmodules/test_allmodules.rst b/functional_tests/doc_tests/test_allmodules/test_allmodules.rst new file mode 100644 index 0000000..b541987 --- /dev/null +++ b/functional_tests/doc_tests/test_allmodules/test_allmodules.rst @@ -0,0 +1,67 @@ +Finding tests in all modules +============================ + +Normally, nose only looks for tests in modules whose names match testMatch. By +default that means modules with 'test' or 'Test' at the start of the name +after an underscore (_) or dash (-) or other non-alphanumeric character. + +If you want to collect tests from all modules, use the ``--all-modules`` +command line argument to activate the :doc:`allmodules plugin +<../../plugins/allmodules>`. + +.. Note :: + + The function :func:`nose.plugins.plugintest.run` reformats test result + output to remove timings, which will vary from run to run, and + redirects the output to stdout. + + >>> from nose.plugins.plugintest import run_buffered as run + +.. + + >>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> argv = [__file__, '-v', support] + +The target directory contains a test module and a normal module. + + >>> support_files = [d for d in os.listdir(support) + ... if not d.startswith('.') + ... and d.endswith('.py')] + >>> support_files.sort() + >>> support_files + ['mod.py', 'test.py'] + +When run without ``--all-modules``, only the test module is examined for tests. + + >>> run(argv=argv) + test.test ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + +When ``--all-modules`` is active, both modules are examined. + + >>> from nose.plugins.allmodules import AllModules + >>> argv = [__file__, '-v', '--all-modules', support] + >>> run(argv=argv, plugins=[AllModules()]) # doctest: +REPORT_NDIFF + mod.test ... ok + mod.test_fails ... FAIL + test.test ... ok + + ====================================================================== + FAIL: mod.test_fails + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + AssertionError: This test fails + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + FAILED (failures=1) + + + diff --git a/functional_tests/doc_tests/test_coverage_html/coverage_html.rst b/functional_tests/doc_tests/test_coverage_html/coverage_html.rst new file mode 100644 index 0000000..95f9e8a --- /dev/null +++ b/functional_tests/doc_tests/test_coverage_html/coverage_html.rst @@ -0,0 +1,57 @@ +Generating HTML Coverage with nose +---------------------------------- + +.. Note :: + + HTML coverage requires Ned Batchelder's `coverage.py`_ module. +.. + +Console coverage output is useful but terse. For a more browseable view of +code coverage, the coverage plugin supports basic HTML coverage output. + +.. hide this from the actual documentation: + >>> from nose.plugins.plugintest import run_buffered as run + >>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> cover_html_dir = os.path.join(support, 'cover') + >>> cover_file = os.path.join(os.getcwd(), '.coverage') + >>> if os.path.exists(cover_file): + ... os.unlink(cover_file) + ... + + +The console coverage output is printed, as normal. + + >>> from nose.plugins.cover import Coverage + >>> cover_html_dir = os.path.join(support, 'cover') + >>> run(argv=[__file__, '-v', '--with-coverage', '--cover-package=blah', + ... '--cover-html', '--cover-html-dir=' + cover_html_dir, + ... support, ], + ... plugins=[Coverage()]) # doctest: +REPORT_NDIFF + test_covered.test_blah ... hi + ok + + Name Stmts Miss Cover Missing + ------------------------------------- + blah 4 1 75% 6 + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + +The html coverage reports are saved to disk in the directory specified by the +``--cover-html-dir`` option. In that directory you'll find ``index.html`` +which links to a detailed coverage report for each module in the report. The +detail pages show the module source, colorized to indicated which lines are +covered and which are not. There is an example of this HTML output in the +`coverage.py`_ docs. + +.. hide this from the actual documentation: + >>> os.path.exists(cover_file) + True + >>> os.path.exists(os.path.join(cover_html_dir, 'index.html')) + True + >>> os.path.exists(os.path.join(cover_html_dir, 'blah.html')) + True + +.. _`coverage.py`: http://nedbatchelder.com/code/coverage/ diff --git a/functional_tests/doc_tests/test_coverage_html/coverage_html.rst.py3.patch b/functional_tests/doc_tests/test_coverage_html/coverage_html.rst.py3.patch new file mode 100644 index 0000000..f325a01 --- /dev/null +++ b/functional_tests/doc_tests/test_coverage_html/coverage_html.rst.py3.patch @@ -0,0 +1,16 @@ +--- coverage_html.rst.orig 2010-08-31 23:13:33.000000000 -0700 ++++ coverage_html.rst 2010-08-31 23:14:25.000000000 -0700 +@@ -78,11 +78,11 @@ + +
+
1
def dostuff():
+-
2
    print 'hi'
++
2
    print('hi')
+ + +
5
def notcov():
+-
6
    print 'not covered'
++
6
    print('not covered')
+ +
+ diff --git a/functional_tests/doc_tests/test_coverage_html/coverage_html_fixtures$py.class b/functional_tests/doc_tests/test_coverage_html/coverage_html_fixtures$py.class new file mode 100644 index 0000000000000000000000000000000000000000..79f80150725b572813a21ca4b3c2b43e9ec30a77 GIT binary patch literal 5318 zcmbtY3v?7$8UAiIu#@c+mgN-mL!EjwFIjaNnK0=B?to5>9Cn33%fJx%xoYi zR;#x7tdD9%tF2n0tuGqdB^Fz>KC0FFe!t)E_uHe^e)rDol4N&t@VMv9-n;ky^Uwc( z_xm5Sk34q&g8){G27$=H@=FA^w{$!1U`JuZA9C!DjN@7z+eW%vGjDZPZR{9wEwjJR zH+`$KwQ6_U$ku)Pt&HC(Fu#tCw+Y)GONw@CQF%8pWXhf7Ic_W^NCV|25W(s?CT!N#mT1 znG@(ZKlP#0(vew6NJnN1G&uBQuE2C}sOV>N8s-bkvK`NAFXW1YS=(#(4rB`&76`;A z`w+z<+R?}T9hT>7SS%0=yzX+HyudXz?ArT3Adzm}TmS5IEJadcu}mN_!QhNDY`GdP zrfr+#)Eu-lJdtNTK`L<1SB5`HU~#@^4_by4F`S}bDEh{L;~M==CJ<>Q33j+eYD$q9 zgO+cTcmnM;T%7U%+t4nN?+}=4<#~oTWV%+rp^@@POeWQ~kti+|&^PA` zen?(VV9}KDtixqZSc@kMEUZ@7RkSmH*0H0woKzh!26N6nGe?~Zr$W3F8=7eRQw3&_ zeZ_*2clwJtYdKBU6zU4hvN)AwD& z(6L==xEg&7b|Y>vjXplw+#Cw>qAUol?;*^#I$k4_mHd7*yNRx<|)u?2)^BrK@Xn zJU8&>c|_DO7~MH3*;oDO@foiE!>6R;wJ_zA_X%8>HxF2w4`r-^Gd@&kzH`vrm#dMxr^hMy$EQa-Xd2oc4FxPM+YlCl@ZohNTF30{F`c^RgAq}tOY`>GWHN2WQ z^k-d}A~n2LQF$PnvkYdp*GVYX>Ucu{AJ^;qRDY)ynL{9S zT#L8J_&CO6unuKCpDE;R^o1!RthrR2C)toLT@f|XdqPSC~6m@=g$8Z6tU!Ln`1pqeCY z$4|0b%I2h78a~8uRU#VB5J{7DYnaOq2y|`=%X=~?=E3ODeGWU5YFUp zqinw_U~Ei-mSbW$YcGswcu2=b1D=1JJlYVrdxeHi5_#J?D5GECww83Q9_d`Z2_dZR zqtbi!)L`QTl62P6UE^`-n#2y%)2HxhnRp-3Ap$CYmi>ToJMfv2uiwJX$;@l1VMpsu4Vz^`IuRp7zpCR4_@bPBy+%_b)pa{&zvV{pO~z4%uK>Q) z$A&a#UFkT!=euU1PHj`BRNE+*E4MOIzK!q1@GX3ozPUv^@NHdnzM6PjO}BJ>A3u;N z{*Zk&ahh<8z_J>EO%Ul6xVSnYY%kij44R6~QT$Y(mHNAweq}n|38#uvbUJuXSphGGZXMVHFKaxk~7xl z6y1y^Pm7%~&Gz|}omnr-L)~aIF`DwcJS?O8cM*rbAckg{z-el;tf^{bo1#V*RW-6h zRwH{$H8KgQk>RUG_8V$sW#Xt&FA?VTA9}Y7)A`y9u{70Kf|iJ-#t`pKC1PzQ%n`k* zX>xI1xHvCdoGure!^P%sF)9}qHa>uhdfOt2C4FtFhA}*W^EK-mQ;8)DsD6&%RFiVc z%W^9w$*ri!#nn?<%7QB=39dXR7^B8iVpR#3(C{^(IdPq%9BSl62ni&3T;MA!FqhIK zZwPV|<)qZnRD6bXe`A1MgwJ>eL5|N>WM(I^T z**Al;nY`k>X5+v7-b&AU(HKQnRDSNw0*H90a)G|I*rxNh8vyjjRd z4q(Bt%Qxy=KC#kqq z-4Q9_c0!#NxW*aQ1q+?J zlmFpJR@}PsG~T!J6iy~a$1rvZHz)2_<8b1`YP==!pc=O%KB7h=@i8@iA`)Q?_0ai; zBaz4`PDX@?%!*?SpE-p`BQhR(Rz(sU`zUcVt}Bs7B_fo_^%arNOOfO8xk_Z(IFUQ< zp?&fwSZzbA;~XU?8b|S{rW>|5P_1D1H3z_PJo<~q#V!3<@GjN>TRs5x50lV`DwsILQdvz_E&oFw>18af5^!e G{PS_F*xKv> literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_coverage_html/coverage_html_fixtures.py b/functional_tests/doc_tests/test_coverage_html/coverage_html_fixtures.py new file mode 100644 index 0000000..6829dc2 --- /dev/null +++ b/functional_tests/doc_tests/test_coverage_html/coverage_html_fixtures.py @@ -0,0 +1,26 @@ +import sys +import os +import shutil +from nose.plugins.skip import SkipTest +from nose.plugins.cover import Coverage +from nose.plugins.plugintest import munge_nose_output_for_doctest + +# This fixture is not reentrant because we have to cleanup the files that +# coverage produces once all tests have finished running. +_multiprocess_shared_ = True + +def setup_module(): + try: + import coverage + if 'active' in Coverage.status: + raise SkipTest("Coverage plugin is active. Skipping tests of " + "plugin itself.") + except ImportError: + raise SkipTest("coverage module not available") + +def teardown_module(): + # Clean up the files produced by coverage + cover_html_dir = os.path.join(os.path.dirname(__file__), 'support', 'cover') + if os.path.exists(cover_html_dir): + shutil.rmtree(cover_html_dir) + diff --git a/functional_tests/doc_tests/test_coverage_html/coverage_html_fixtures.pyc b/functional_tests/doc_tests/test_coverage_html/coverage_html_fixtures.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00eee1b23141080fc82c93e028079bb617e3dc9b GIT binary patch literal 1236 zcmah}O>fgc5S_KtG)X^5X%Pqs;RA|jC!qHm8txOB3}6E=ZyW)0;UfXe z1IPqyLdbhC?}5ky*z7>whk2hSA#8RbAHaM7LJeU)f~*I>XrDdEcD%FC`LopU7!J)Vjl6m$*z9$C(v=^^+LLj9`I}(*e41ol0Q+@aboH9qG z@h<2=I~OdnS1_avXOyCD>Wb+>CfN5q8S-KLAs9y|08}Ziy>n@F>DOf1mT93p)spb# zN=iKJHt-xss`09*D@~d=0&j_lSkdux7K7IbL@l-x8&h?V0X#k`}^AeZ;Z(s P(!Cjq1OD~;!EpE&wZ0JU literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_coverage_html/support/blah.py b/functional_tests/doc_tests/test_coverage_html/support/blah.py new file mode 100644 index 0000000..ef6657c --- /dev/null +++ b/functional_tests/doc_tests/test_coverage_html/support/blah.py @@ -0,0 +1,6 @@ +def dostuff(): + print 'hi' + + +def notcov(): + print 'not covered' diff --git a/functional_tests/doc_tests/test_coverage_html/support/blah.pyc b/functional_tests/doc_tests/test_coverage_html/support/blah.pyc new file mode 100644 index 0000000000000000000000000000000000000000..923a02c3c937b02e1e4b27596c9735f3935ae7c0 GIT binary patch literal 405 zcma)2Jx{|h5Iq+tDpglz#!Rt-*g@(5Q-*Xysv;9RBvKMv@ud>#+8@#1$uHo}iCVQ2 zmi+GB`QClF{aromk1uHf(-CCf==w(j4R8SXBq9P4pl7g&Zs_Skux7!61p?kbNVSP( z57K;GFGIel@C!-4EVV2W>qLWyxE49tOm}2KP%!?JPcp4n|)MAKZ+K;(Sb` gygOo3k2I{Gal?#1Yjl}~*tyzRgA2W$MqJ$f0Rm@5*#H0l literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_coverage_html/support/tests/test_covered.py b/functional_tests/doc_tests/test_coverage_html/support/tests/test_covered.py new file mode 100644 index 0000000..c669c5c --- /dev/null +++ b/functional_tests/doc_tests/test_coverage_html/support/tests/test_covered.py @@ -0,0 +1,4 @@ +import blah + +def test_blah(): + blah.dostuff() diff --git a/functional_tests/doc_tests/test_coverage_html/support/tests/test_covered.pyc b/functional_tests/doc_tests/test_coverage_html/support/tests/test_covered.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4d933c4a4d3c380971ba795f1e4a63fbd702aa1 GIT binary patch literal 340 zcmZ8c%?iRW3{IyY3VQSzc3PQuSMcs(-n@*ubvoSEvSxyK)Q9jneFBpz3@!A_m*mS| z{hCg0&*e6Q?-OL#^xd2w2RH-li3oTQ5ggft0i}K`He4|yK!8%f9YA;`ciVbB4v8Kr zb;1#m+&JutA|whuAPoa)+w!7NORTMIjMHhWE3L@JQ5-E~(KQ*CrcqUj+M$!V$-a#J zG&2|7s-sR%SXa_@)|wXON7@T?9@#67X`YYqVgqs>> something + 'Something?' + +The ``count`` variable is injected by the test-level fixture. + + >>> count + 1 + +.. warning :: + + This whole file is one doctest test. setup_test doesn't do what you think! + It exists to give you access to the test case and examples, but it runs + *once*, before all of them, not before each. + + >>> count + 1 + + Thus, ``count`` stays 1 throughout the test, no matter how many examples it + includes. \ No newline at end of file diff --git a/functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures$py.class b/functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures$py.class new file mode 100644 index 0000000000000000000000000000000000000000..eeeff8a716e6378441020bbf8366332b9bd5a164 GIT binary patch literal 4513 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 literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.py b/functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.py new file mode 100644 index 0000000..72fbfd7 --- /dev/null +++ b/functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.py @@ -0,0 +1,17 @@ +called = [] + +def globs(globs): + globs['something'] = 'Something?' + return globs + +def setup_module(module): + module.called[:] = [] + +def setup_test(test): + called.append(test) + test.globs['count'] = len(called) +setup_test.__test__ = False + +def teardown_test(test): + pass +teardown_test.__test__ = False diff --git a/functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.pyc b/functional_tests/doc_tests/test_doctest_fixtures/doctest_fixtures_fixtures.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf8a880a70efd96a8ad9e7852b8e190b862d8a73 GIT binary patch literal 846 zcmZuv%}(1u5T3P7+5`eG!NI3kAdW~NA#wDbpR!|U)!yhs^f~(k%r_2L zMNPD`^S|GG5VtH7J-1e>KZ*{l3;U$va%@i#d4R=ld z7}PA_OXW<0ThX#%Uq6@ZGj4~h>w~v@-}qAQi_OL6GJ6`kitD~B8_Pa$$Xs6?G}u;5 zy`SpuJnp^5|8dy&$Y%JV0DwItKw(zqV_FdTF|80OW7dXJA#6J7CMkw&qz_MND*-}Gsv4b z_+RI>MA?^n*S~bSxifJbNXSz#qSzH*fe)s@>pI?j*$5^ywOVOyaV*9(Ftj;;DhII7 UlL7S&M8Gx{(K1=a@{g9$8(EKvJpcdz literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_init_plugin/example.cfg b/functional_tests/doc_tests/test_init_plugin/example.cfg new file mode 100644 index 0000000..b02ac0e --- /dev/null +++ b/functional_tests/doc_tests/test_init_plugin/example.cfg @@ -0,0 +1,3 @@ +[DEFAULT] +can_frobnicate = 1 +likes_cheese = 0 diff --git a/functional_tests/doc_tests/test_init_plugin/init_plugin.rst b/functional_tests/doc_tests/test_init_plugin/init_plugin.rst new file mode 100644 index 0000000..6c64029 --- /dev/null +++ b/functional_tests/doc_tests/test_init_plugin/init_plugin.rst @@ -0,0 +1,164 @@ +Running Initialization Code Before the Test Run +----------------------------------------------- + +Many applications, especially those using web frameworks like Pylons_ +or Django_, can't be tested without first being configured or +otherwise initialized. Plugins can fulfill this requirement by +implementing :meth:`begin() `. + +In this example, we'll use a very simple example: a widget class that +can't be tested without a configuration. + +Here's the widget class. It's configured at the class or instance +level by setting the ``cfg`` attribute to a dictionary. + + >>> class ConfigurableWidget(object): + ... cfg = None + ... def can_frobnicate(self): + ... return self.cfg.get('can_frobnicate', True) + ... def likes_cheese(self): + ... return self.cfg.get('likes_cheese', True) + +The tests verify that the widget's methods can be called without +raising any exceptions. + + >>> import unittest + >>> class TestConfigurableWidget(unittest.TestCase): + ... longMessage = False + ... def setUp(self): + ... self.widget = ConfigurableWidget() + ... def test_can_frobnicate(self): + ... """Widgets can frobnicate (or not)""" + ... self.widget.can_frobnicate() + ... def test_likes_cheese(self): + ... """Widgets might like cheese""" + ... self.widget.likes_cheese() + ... def shortDescription(self): # 2.7 compat + ... try: + ... doc = self._testMethodDoc + ... except AttributeError: + ... # 2.4 compat + ... doc = self._TestCase__testMethodDoc + ... return doc and doc.split("\n")[0].strip() or None + +The tests are bundled into a suite that we can pass to the test runner. + + >>> def suite(): + ... return unittest.TestSuite([ + ... TestConfigurableWidget('test_can_frobnicate'), + ... TestConfigurableWidget('test_likes_cheese')]) + +When we run tests without first configuring the ConfigurableWidget, +the tests fail. + +.. Note :: + + The function :func:`nose.plugins.plugintest.run` reformats test result + output to remove timings, which will vary from run to run, and + redirects the output to stdout. + + >>> from nose.plugins.plugintest import run_buffered as run + +.. + + >>> argv = [__file__, '-v'] + >>> run(argv=argv, suite=suite()) # doctest: +REPORT_NDIFF + Widgets can frobnicate (or not) ... ERROR + Widgets might like cheese ... ERROR + + ====================================================================== + ERROR: Widgets can frobnicate (or not) + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + AttributeError: 'NoneType' object has no attribute 'get' + + ====================================================================== + ERROR: Widgets might like cheese + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + AttributeError: 'NoneType' object has no attribute 'get' + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + FAILED (errors=2) + +To configure the widget system before running tests, write a plugin +that implements :meth:`begin() ` +and initializes the system with a hard-coded configuration. (Later, we'll +write a better plugin that accepts a command-line argument specifying the +configuration file.) + + >>> from nose.plugins import Plugin + >>> class ConfiguringPlugin(Plugin): + ... enabled = True + ... def configure(self, options, conf): + ... pass # always on + ... def begin(self): + ... ConfigurableWidget.cfg = {} + +Now configure and execute a new test run using the plugin, which will +inject the hard-coded configuration. + + >>> run(argv=argv, suite=suite(), + ... plugins=[ConfiguringPlugin()]) # doctest: +REPORT_NDIFF + Widgets can frobnicate (or not) ... ok + Widgets might like cheese ... ok + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + OK + +This time the tests pass, because the widget class is configured. + +But the ConfiguringPlugin is pretty lame -- the configuration it +installs is hard coded. A better plugin would allow the user to +specify a configuration file on the command line: + + >>> class BetterConfiguringPlugin(Plugin): + ... def options(self, parser, env={}): + ... parser.add_option('--widget-config', action='store', + ... dest='widget_config', default=None, + ... help='Specify path to widget config file') + ... def configure(self, options, conf): + ... if options.widget_config: + ... self.load_config(options.widget_config) + ... self.enabled = True + ... def begin(self): + ... ConfigurableWidget.cfg = self.cfg + ... def load_config(self, path): + ... from ConfigParser import ConfigParser + ... p = ConfigParser() + ... p.read([path]) + ... self.cfg = dict(p.items('DEFAULT')) + +To use the plugin, we need a config file. + + >>> import os + >>> cfg_file = os.path.join(os.path.dirname(__file__), 'example.cfg') + >>> bytes = open(cfg_file, 'w').write("""\ + ... [DEFAULT] + ... can_frobnicate = 1 + ... likes_cheese = 0 + ... """) + +Now we can execute a test run using that configuration, after first +resetting the widget system to an unconfigured state. + + >>> ConfigurableWidget.cfg = None + >>> argv = [__file__, '-v', '--widget-config', cfg_file] + >>> run(argv=argv, suite=suite(), + ... plugins=[BetterConfiguringPlugin()]) # doctest: +REPORT_NDIFF + Widgets can frobnicate (or not) ... ok + Widgets might like cheese ... ok + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + OK + +.. _Pylons: http://pylonshq.com/ +.. _Django: http://www.djangoproject.com/ diff --git a/functional_tests/doc_tests/test_init_plugin/init_plugin.rst.py3.patch b/functional_tests/doc_tests/test_init_plugin/init_plugin.rst.py3.patch new file mode 100644 index 0000000..90a0a44 --- /dev/null +++ b/functional_tests/doc_tests/test_init_plugin/init_plugin.rst.py3.patch @@ -0,0 +1,10 @@ +--- init_plugin.rst.orig 2010-08-31 10:36:54.000000000 -0700 ++++ init_plugin.rst 2010-08-31 10:37:30.000000000 -0700 +@@ -143,6 +143,7 @@ + ... can_frobnicate = 1 + ... likes_cheese = 0 + ... """) ++ 46 + + Now we can execute a test run using that configuration, after first + resetting the widget system to an unconfigured state. diff --git a/functional_tests/doc_tests/test_issue089/support/unwanted_package/__init__$py.class b/functional_tests/doc_tests/test_issue089/support/unwanted_package/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..1ccf3e42355b66cbf34722a8818f192e5c953635 GIT binary patch literal 2181 zcmbtVZBrXn6n<_tupurj4U|$S1zM~L&@5nEi@_>I(X9zZf@rDZcG=vd+irGu_Jx`c z{ucf0rwUV=(a|5^k8(UWiL@chj59Jr?%jLOJ?A;keeOAb{r%gY0G7ZRr0VPZHC>;pTa69hsPjV4#?Dip8nbmT;3i{BWMiMe@Rdw44LW_ZE3I>oKp!IhcGQIv}43dLQSNIt}8;gST7#hGi zTwv&5wHzzF$8a%Qd}bUNs%!kpo!toFF~g8$2kwzn4pqDupX zINvehG%fChwDM8t{Vk_SlkY2FSP<#oCj2!XmJG`wDP+$)N^+~WY(}#&F>|{qp{!wy zq{-*1f)8+4%t-R%r>|2WDY{05`!YTu`I_{!LteJ(p3U#OZWx5V;W4blm4{Xqy%@#v z$`&dt74WCkOe&n=syWQ)r`&evR3O4Y$VC)Oh0`b&!gkdIzur3pl(Dl;u>X_O~s8$!HGG;^4q#!r_vR~)WvQj3O4aD zjS{vP22I1Z^?%-zvBC z!l~Do&r3px>9QO7CKoruaG9up;SjH=WifkOcYsyIwq=2 zYozB!V?XT^EA;<_zHZP=By?^*d5qM2;uVxbWM0w=gF(N-TFj}IV}yR6(AS3;Y@Nx^ zy~gFaBTQwkH<3HSyd?dBd}(1mvnWZ@A*Lk8qzgl6;?@!F{7NTuK_&3mmTX&%a-tFCY$)G?Tz|7 z%ANfR_0lAHyE?aUbK(~fOEB~1Z{!$Xzhv#a_i6bS@iK;_j7cVoL5D>1&h*Mz8wr?n YHi2uX&^SV~Zz=vG4SRSjCJ*r4KN9jycmMzZ literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue089/support/unwanted_package/__init__.py b/functional_tests/doc_tests/test_issue089/support/unwanted_package/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/functional_tests/doc_tests/test_issue089/support/unwanted_package/__init__.py @@ -0,0 +1 @@ +pass diff --git a/functional_tests/doc_tests/test_issue089/support/unwanted_package/__init__.pyc b/functional_tests/doc_tests/test_issue089/support/unwanted_package/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3e47f02cc4bf9f6c6cce5c94a35251acafbadae GIT binary patch literal 189 zcmZ9GK?=e!5Je-n61+!O9V;$$WlzwJ2N*&+O0Z2rW}=7}@tmH(33TO$|Ni{Hn)$ok zyg!G*nMEtjUd?O8Xw0Rl%g`vyA7s`IQuYrk1TrTGkqI+RWHr0CYxi(1!HJK-PAJ5} sFh;jrwWDV)v^$=FOG+^dl<>4cXvAdQ-QEZgeei literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue089/support/unwanted_package/test_spam$py.class b/functional_tests/doc_tests/test_issue089/support/unwanted_package/test_spam$py.class new file mode 100644 index 0000000000000000000000000000000000000000..2998cfd98c34b2771f8ef4f2b42ae6ff77deabef GIT binary patch literal 3051 zcmbtWTXz#x6#h<=c9Jka8@ZGgq)5f2X*)E6fZRosp7$~g``bWD z2r)=5_YB?S4B_I$A7Cy$X!{e@}y0%sx93uc#E%4<^NmV1*N1r9` z)d3ks6S);_O;ZfbEGhGzqnoAeIFr$f1A>J&!|xNEWE7X_+^4HgIyQS6JVp}C&Uz_Kz%Fe+$O7!Fm;>ze8Df@*8|RjtI8 z|9G~m5{T>Gfb%D;0w?^vcshzSPB3(x(M{bu%doPIBipE_-5CMn4OWDJpTV;+bmJ5$ zplNml&oM-V)UCW`Fem|f*dFx^o)>~3XZ8l{O)fGVZWYP{eJ^Os2A44IE5tW(B8GD~ zPr7iqQKY@OZ7C;Kr|^b$cYtP{}AdRyj+O za!g)$^mJ@^Bx4q@#Bd37GA`hv;9-HG`{9h1Kv9UCDO-h#!OuqU8Y$f5*M%$@eoSPq zw$^}08Obg!1xRB{Xfr|B0mo&tLCM+3n_d|ofx20v$6p6fZ9 z&2Ty>oqaD_Z4|@Jt)$~s7%{A%Ouji4v+mo;R=lm-0^O2f!4{0YO@ z4Y+EyS|Z~*Zp5&LDqR9ppbYgvhKfrd8zjH~^(GJ!>kuNiCCatmdp|%9v(*P|NcfOp zV5=Qvn+S1~@!9MmcU z-B(h-V$aArPO^JAJ(G%OMrwG0&7?Yh!|`<-yF=?4zM|hQYDJ%ts)?9Ki2i^RsZK&n z#rN0HU&E2_aVQl(TJQgW9)d~56ZENWVEC`i$Q_%IJMc9j^$>mvAqi;-VF_ROM^fg~ zuyElrcKQci9J+_ep}WY&r#CQv7q5mwf8g@;a4LQ!6bh{)8)7Wf(~S+haTjWHG86VE z!~P`mCr|!Du);4YDvd+BsTID4*P#mkfhd-dZ_Eym0b+K~eWb6%3-?g0;o1hwp$#~7 ztSDS~jn#2~EwL9*Z{TJfjo}@<+nB#i5fL#tHZ(la|Fbv?81R3EO(L?u!5ZGXQ^N;$ zSUnnor)jd{pkOG?8bod4Y@pr5V7rM9d_$le$l+UR_fYR+I{uR^ZsSwYxq#391vN%R Am;e9( literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue089/support/unwanted_package/test_spam.py b/functional_tests/doc_tests/test_issue089/support/unwanted_package/test_spam.py new file mode 100644 index 0000000..cfd1cc1 --- /dev/null +++ b/functional_tests/doc_tests/test_issue089/support/unwanted_package/test_spam.py @@ -0,0 +1,3 @@ +def test_spam(): + assert True + diff --git a/functional_tests/doc_tests/test_issue089/support/unwanted_package/test_spam.pyc b/functional_tests/doc_tests/test_issue089/support/unwanted_package/test_spam.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5598905df4e7b69edbbccf3c7f8360f3db15b005 GIT binary patch literal 333 zcmY*U!Ab)`49#dkEYw54Voy7^c+is;@w$iBQ!mS~8)dOOGfXlS>8b8#2av!Y^gmuCVI*GG#R?h8P_KhxZ zo0^fBY#W*b(!B17GTz=lSjsVm#5Vg+=Na2Ny5_^Z%VMQbbbT4W_*%X^7W8UTonWd# W!tzq^3DVYZ?^0PJ7~pXj(#9W=u||&o literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue089/support/wanted_package/__init__$py.class b/functional_tests/doc_tests/test_issue089/support/wanted_package/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..bb74c143e21d796fb276c82074ac10b27d279f1a GIT binary patch literal 2175 zcmbtVZBrXn6n<_tut{862$WJN1zN00pjp7S7K2raqFV!v1f!*n+hub@w`_KIb~n^~ z@VDq^KUJ7gMn`{uKg#jkB+`Z~GtS5ixp(h9_nhZE=k2e*fBO@_A~=Ion_Xbo$`oCH zPxYGNGuKf~*XL@fx$YZvUg){le&%zdS`Lkn7qUIKrRJmE=iCen442PQDMMnA10LFz zLkQv-En890hyE1ekm;(X+eQ#t3`|pyL@G(=?=Yl$^~o3@1)HwWGk!i61s5@x#03m7 z^siWs72apKlqo(p4h+>coISN1`j)d-$W|Dpdd;ji95b|BXWQ7dxr|FhTSj0W#^n@7 zF-8~yZr2!wi!Tf(%za~z&w5QFVyNd(##II5m`Gw0*BD|t#UKiQ2NuJ=>iO0I zQLct{Pj%dY>pr)+LF;0EF~6kNIuH$84|xydFng7%8-wuG9nxucNE;jdnvq&jDl&*2oiE+ zG2+3RTjfOH;MqXQxW$lIsk_yN&F?3mQr0;9g%D@>DWg5<4eft9(ROwsKy*dF5T!f9 zoTf$9M#~?CDsL%8+4#N!hI!%s9m2oI!wti7C=oK}KBYvfw`4{$5jL}xgvhI5jHF2C zih>VtPwXh!$IpgSASt>=mHRS2p<*zps6$@1>YmLXxNaDPzTq(}M>U3)7rhw8@#)io zf(KYjVHKZ}Z+^pR`*x>SU-z>}w-l`7bHUgblncaD*As@B2*SEVD1~mEC7{&YYB-K4 z=u^xxiVRuOzi!#wq2{;dI>bZ63|)VV*KBHQ)C5k&8J6D8`!#A>K}1LFMxtO7k5bq` ziDAGrY+L`gav91QEh_!~R~r^7786OZ9gzfYIFLb|Z@Sxh-6I-&b+x2bxF3+`L}TB9 zVK=x$(_@@rB2s>1+oG}22e8E-gP(sOv9r{_H9=&2PdP0pVz^kOd9c~2?{dG@XsNmY|M_F4J16 z+-U8mdt!zDpVHS&+6jj)%*Bt9n2WuFa)|UxI$<#AS4fLJb$5)=?^F8v5Cg3@`P^$< z$sJ)Ted7e#Bg{$CAINXa&!rb6Njk)o#F#WRh!fmC!rfo#rO-rW+0ipdtI!@l#9d(# zH13v3d)vx1*$}rQzhm-oy6_qw9pmE@e3mmTmz>iq@N`kA$L##z?gWS-20^s>CH0eY1*^>uU34(J literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue089/support/wanted_package/test_eggs$py.class b/functional_tests/doc_tests/test_issue089/support/wanted_package/test_eggs$py.class new file mode 100644 index 0000000000000000000000000000000000000000..b32d6903b0c2804c0166e8244c46654583d46cfc GIT binary patch literal 3045 zcmbtWTXz#x6#h<=c9Jka8@ZGgq)5f2X*)E6fZRosp7$~g``bWD z2r)=5_YB?S4B_I$A7Cy$X!{e@}@>0ni93uc#E%4<^NmV1*N1r9` z)d3ks6S);_O;ZfbEGhGzqnoAeIFr$f1A>J&!|xNEWE7X_+^4HgIyQS6JVp}C&Uz_Kz%Fe+$OD5BRj)8hrz*7B=bi7WrH zY*!@^)4c(wPgn&`=zH;W6lt7b=sKgDx_6dgWg9oP(N4QF0>&Gh2>CvPXJhEbDT;s7 z>KQyQ z;c}zMa45G8;l$|_UJ{(X%+R4ykqAnThNc+|#dh>HstJl$wiVNIx$1C(Yt)X7j*X_3 zVk74?Llp$MO2NuEdOl&g>sI*jQ>PWTV%wJEDeY_%z9uq0<`1@1_odayT7cZ$MT7KS4Mgl$|j!R9fAD zOJ=JjGOpuB3~Q)T`KRJ!s1Nc}l>cmy{QlQ#KuD}Zh~Sne)qd~&06EN7AFv_eLxzE^ zc9d-*#8Jj)vy0qui31|Eb4@cUJVehKx-i7-E*KzC)t!Ma(e!Hj@eM+h(Vjdy-15Ttm2{9Gl zUqgQlN503QRQzbY{{wmmCKXT6r@DdRzcwRxY(nn9*M!tV_$h=Wq$PwUeBmESnNP#Q zh0ECKA9!)-9wvwGA{(FH!2Dgj8Vdb^%hSWD_?1v7w2o|uu~1JpHt@z>sLjbt*q;ph zlgyty`3u1czo@7*4(X;=_!?e^D*OkcSVq1vJ3t1A***7>z7j9oL$QWy8!(49;MB3A zaN#vp$Nja$UOc^ln{_mXckpgw{x(HK#N^n}@JRp9;w)gm{}ncg$N~pzc<)XPAKYQ} yXb7IB$%=!5p)_j{wTZKVb`yi`COYs9fp#E=Z>ilwy^rbmPqMg;PetbfKKmEb=|Zvq literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue089/support/wanted_package/test_eggs.py b/functional_tests/doc_tests/test_issue089/support/wanted_package/test_eggs.py new file mode 100644 index 0000000..bb65550 --- /dev/null +++ b/functional_tests/doc_tests/test_issue089/support/wanted_package/test_eggs.py @@ -0,0 +1,3 @@ +def test_eggs(): + assert True + diff --git a/functional_tests/doc_tests/test_issue089/support/wanted_package/test_eggs.pyc b/functional_tests/doc_tests/test_issue089/support/wanted_package/test_eggs.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c12ec5f30cd8c6f66d6d42c8bc1e1321f651c16d GIT binary patch literal 331 zcmY*Uy=uci4A!NAkU~22DLna-(4n2$Qo4FbGG#G`tr}Cucbw#uk}dQh`kZ-!NG9ax zK+-4alTP2}FuC9TKV=1+o{{(_MiX%*z#qU`fPrN)kjf43LwS$l7$A(muz|e+BU8&n z_hkJD4+XAbhx{Pmod_e>zWt2R$3!Y{aok91L#(%s(eftP))VFt8*~z9=`5YwudXe0 zfopO`VzQ+#PDt~-ChBnd{9>sK!AG_~u4OFq;EHco$yz00RZ$v#c`P5E3wkrDN-$Y3 WU~wn-3TdmqcA+eh3-H>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> support_files = [d for d in os.listdir(support) + ... if not d.startswith('.')] + >>> support_files.sort() + >>> support_files + ['unwanted_package', 'wanted_package'] + +When we run nose normally, tests are loaded from both packages. + +.. Note :: + + The function :func:`nose.plugins.plugintest.run` reformats test result + output to remove timings, which will vary from run to run, and + redirects the output to stdout. + + >>> from nose.plugins.plugintest import run_buffered as run + +.. + + >>> argv = [__file__, '-v', support] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + unwanted_package.test_spam.test_spam ... ok + wanted_package.test_eggs.test_eggs ... ok + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + OK + +To exclude the tests in the unwanted package, we can write a simple +plugin that implements :meth:`IPluginInterface.wantDirectory()` and returns ``False`` if +the basename of the directory is ``"unwanted_package"``. This will +prevent nose from descending into the unwanted package. + + >>> from nose.plugins import Plugin + >>> class UnwantedPackagePlugin(Plugin): + ... # no command line arg needed to activate plugin + ... enabled = True + ... name = "unwanted-package" + ... + ... def configure(self, options, conf): + ... pass # always on + ... + ... def wantDirectory(self, dirname): + ... want = None + ... if os.path.basename(dirname) == "unwanted_package": + ... want = False + ... return want + +In the next test run we use the plugin, and the unwanted package is +not discovered. + + >>> run(argv=argv, + ... plugins=[UnwantedPackagePlugin()]) # doctest: +REPORT_NDIFF + wanted_package.test_eggs.test_eggs ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK \ No newline at end of file diff --git a/functional_tests/doc_tests/test_issue097/plugintest_environment.rst b/functional_tests/doc_tests/test_issue097/plugintest_environment.rst new file mode 100644 index 0000000..99b37cf --- /dev/null +++ b/functional_tests/doc_tests/test_issue097/plugintest_environment.rst @@ -0,0 +1,160 @@ +nose.plugins.plugintest, os.environ and sys.argv +------------------------------------------------ + +:class:`nose.plugins.plugintest.PluginTester` and +:func:`nose.plugins.plugintest.run` are utilities for testing nose +plugins. When testing plugins, it should be possible to control the +environment seen plugins under test, and that environment should never +be affected by ``os.environ`` or ``sys.argv``. + + >>> import os + >>> import sys + >>> import unittest + >>> import nose.config + >>> from nose.plugins import Plugin + >>> from nose.plugins.builtin import FailureDetail, Capture + >>> from nose.plugins.plugintest import PluginTester + +Our test plugin takes no command-line arguments and simply prints the +environment it's given by nose. + + >>> class PrintEnvPlugin(Plugin): + ... name = "print-env" + ... + ... # no command line arg needed to activate plugin + ... enabled = True + ... def configure(self, options, conf): + ... if not self.can_configure: + ... return + ... self.conf = conf + ... + ... def options(self, parser, env={}): + ... print "env:", env + +To test the argv, we use a config class that prints the argv it's +given by nose. We need to monkeypatch nose.config.Config, so that we +can test the cases where that is used as the default. + + >>> old_config = nose.config.Config + >>> class PrintArgvConfig(old_config): + ... + ... def configure(self, argv=None, doc=None): + ... print "argv:", argv + ... old_config.configure(self, argv, doc) + >>> nose.config.Config = PrintArgvConfig + +The class under test, PluginTester, is designed to be used by +subclassing. + + >>> class Tester(PluginTester): + ... activate = "-v" + ... plugins = [PrintEnvPlugin(), + ... FailureDetail(), + ... Capture(), + ... ] + ... + ... def makeSuite(self): + ... return unittest.TestSuite(tests=[]) + +For the purposes of this test, we need a known ``os.environ`` and +``sys.argv``. + + >>> old_environ = os.environ + >>> old_argv = sys.argv + >>> os.environ = {"spam": "eggs"} + >>> sys.argv = ["spamtests"] + +PluginTester always uses the [nosetests, self.activate] as its argv. +If ``env`` is not overridden, the default is an empty ``env``. + + >>> tester = Tester() + >>> tester.setUp() + argv: ['nosetests', '-v'] + env: {} + +An empty ``env`` is respected... + + >>> class EmptyEnvTester(Tester): + ... env = {} + >>> tester = EmptyEnvTester() + >>> tester.setUp() + argv: ['nosetests', '-v'] + env: {} + +... as is a non-empty ``env``. + + >>> class NonEmptyEnvTester(Tester): + ... env = {"foo": "bar"} + >>> tester = NonEmptyEnvTester() + >>> tester.setUp() + argv: ['nosetests', '-v'] + env: {'foo': 'bar'} + + +``nose.plugins.plugintest.run()`` should work analogously. + + >>> from nose.plugins.plugintest import run_buffered as run + >>> run(suite=unittest.TestSuite(tests=[]), + ... plugins=[PrintEnvPlugin()]) # doctest: +REPORT_NDIFF + argv: ['nosetests', '-v'] + env: {} + + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + >>> run(env={}, + ... suite=unittest.TestSuite(tests=[]), + ... plugins=[PrintEnvPlugin()]) # doctest: +REPORT_NDIFF + argv: ['nosetests', '-v'] + env: {} + + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + >>> run(env={"foo": "bar"}, + ... suite=unittest.TestSuite(tests=[]), + ... plugins=[PrintEnvPlugin()]) # doctest: +REPORT_NDIFF + argv: ['nosetests', '-v'] + env: {'foo': 'bar'} + + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + +An explicit argv parameter is honoured: + + >>> run(argv=["spam"], + ... suite=unittest.TestSuite(tests=[]), + ... plugins=[PrintEnvPlugin()]) # doctest: +REPORT_NDIFF + argv: ['spam'] + env: {} + + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + +An explicit config parameter with an env is honoured: + + >>> from nose.plugins.manager import PluginManager + >>> manager = PluginManager(plugins=[PrintEnvPlugin()]) + >>> config = PrintArgvConfig(env={"foo": "bar"}, plugins=manager) + >>> run(config=config, + ... suite=unittest.TestSuite(tests=[])) # doctest: +REPORT_NDIFF + argv: ['nosetests', '-v'] + env: {'foo': 'bar'} + + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + + +Clean up. + + >>> os.environ = old_environ + >>> sys.argv = old_argv + >>> nose.config.Config = old_config diff --git a/functional_tests/doc_tests/test_issue107/plugin_exceptions.rst b/functional_tests/doc_tests/test_issue107/plugin_exceptions.rst new file mode 100644 index 0000000..2c595f0 --- /dev/null +++ b/functional_tests/doc_tests/test_issue107/plugin_exceptions.rst @@ -0,0 +1,149 @@ +When Plugins Fail +----------------- + +Plugin methods should not fail silently. When a plugin method raises +an exception before or during the execution of a test, the exception +will be wrapped in a :class:`nose.failure.Failure` instance and appear as a +failing test. Exceptions raised at other times, such as in the +preparation phase with ``prepareTestLoader`` or ``prepareTestResult``, +or after a test executes, in ``afterTest`` will stop the entire test +run. + + >>> import os + >>> import sys + >>> from nose.plugins import Plugin + >>> from nose.plugins.plugintest import run_buffered as run + +Our first test plugins take no command-line arguments and raises +AttributeError in beforeTest and afterTest. + + >>> class EnabledPlugin(Plugin): + ... """Plugin that takes no command-line arguments""" + ... + ... enabled = True + ... + ... def configure(self, options, conf): + ... pass + ... def options(self, parser, env={}): + ... pass + >>> class FailBeforePlugin(EnabledPlugin): + ... name = "fail-before" + ... + ... def beforeTest(self, test): + ... raise AttributeError() + >>> class FailAfterPlugin(EnabledPlugin): + ... name = "fail-after" + ... + ... def afterTest(self, test): + ... raise AttributeError() + +Running tests with the fail-before plugin enabled will result in all +tests failing. + + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> suitepath = os.path.join(support, 'test_spam.py') + >>> run(argv=['nosetests', suitepath], + ... plugins=[FailBeforePlugin()]) + EE + ====================================================================== + ERROR: test_spam.test_spam + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + AttributeError + + ====================================================================== + ERROR: test_spam.test_eggs + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + AttributeError + + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + FAILED (errors=2) + +But with the fail-after plugin, the entire test run will fail. + + >>> run(argv=['nosetests', suitepath], + ... plugins=[FailAfterPlugin()]) + Traceback (most recent call last): + ... + AttributeError + +Likewise, since the next plugin fails in a preparatory method, outside +of test execution, the entire test run fails when the plugin is used. + + >>> class FailPreparationPlugin(EnabledPlugin): + ... name = "fail-prepare" + ... + ... def prepareTestLoader(self, loader): + ... raise TypeError("That loader is not my type") + >>> run(argv=['nosetests', suitepath], + ... plugins=[FailPreparationPlugin()]) + Traceback (most recent call last): + ... + TypeError: That loader is not my type + + +Even AttributeErrors and TypeErrors are not silently suppressed as +they used to be for some generative plugin methods (issue152). + +These methods caught TypeError and AttributeError and did not record +the exception, before issue152 was fixed: .loadTestsFromDir(), +.loadTestsFromModule(), .loadTestsFromTestCase(), +loadTestsFromTestClass, and .makeTest(). Now, the exception is +caught, but logged as a Failure. + + >>> class FailLoadPlugin(EnabledPlugin): + ... name = "fail-load" + ... + ... def loadTestsFromModule(self, module): + ... # we're testing exception handling behaviour during + ... # iteration, so be a generator function, without + ... # actually yielding any tests + ... if False: + ... yield None + ... raise TypeError("bug in plugin") + >>> run(argv=['nosetests', suitepath], + ... plugins=[FailLoadPlugin()]) + ..E + ====================================================================== + ERROR: Failure: TypeError (bug in plugin) + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + TypeError: bug in plugin + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + FAILED (errors=1) + + +Also, before issue152 was resolved, .loadTestsFromFile() and +.loadTestsFromName() didn't catch these errors at all, so the +following test would crash nose: + + >>> class FailLoadFromNamePlugin(EnabledPlugin): + ... name = "fail-load-from-name" + ... + ... def loadTestsFromName(self, name, module=None, importPath=None): + ... if False: + ... yield None + ... raise TypeError("bug in plugin") + >>> run(argv=['nosetests', suitepath], + ... plugins=[FailLoadFromNamePlugin()]) + E + ====================================================================== + ERROR: Failure: TypeError (bug in plugin) + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + TypeError: bug in plugin + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + FAILED (errors=1) diff --git a/functional_tests/doc_tests/test_issue107/support/test_spam$py.class b/functional_tests/doc_tests/test_issue107/support/test_spam$py.class new file mode 100644 index 0000000000000000000000000000000000000000..a06d9e90ff6f652104923b95b5f50fe50477a5f6 GIT binary patch literal 3210 zcmbtW>sK2^6#q>EY)H2)#6BprmRhWV09#^ft<)-|P*Z_YK(y4lF3BV;YQ9-~HV?^Y=f${|R6K-!eq= zT}K(Fl4F)rQ0%g|V3|tRa=0>C9(A-LAFL6~EI3@tO?#Th2fJ&eljRHZi#+QMGVHCR zQbLqLa=B;dCTD0!X1Zr(G@>bv21pEv+H(?`8RU_o?FH&xhK|eijmy{uxfyY6XK1fg zH(D~Yo^F{Eb}}^Q)q-KoYX<3T-%4dG63yh{eGJvBf~KjJu+6T?Fc+l$%~AGwz0 zwpeGF4r4=**7^X2aiOYoP)0X;;^ae$a-UZX&GmE!mXXniegUgO3H}dOyDWjAZ3{6s zY~?tq+l_~tk;XBGrc=79d#4!|xA1!lvedH?vL1jS`1%M=#L%KJ%^O_8 zsLvZelBeQ0hYYdBnIC?^HOSWw}o>FzD-!8{+F|;%) z<|RxMi}H?D%$Oc`OrE><5)1;6@eF3;n8C9$E?`oGeTkvv?kbjWnKFH_=I$)rDjuKK zi?+efSeEB{j%G8Q46A8BidrAVa`h{Mp1dXE%O=f1f;}RMKZR4_Llz5b$G<0uvgzg`o(djMI z2(W=0?vK}K+uiBd@T`&W8Sz}e#;|jY9w`&0;yic!zDZS2PoM14xFBbI+A29&F0#e2 zkKFdi%B=3{l++>9wCIgQ9=bS0;h!TMKA=}lo9L$GYMW>;{zxV0k94E?BlUV3W3-=; zqwfp)wS*?R7Jv(>hE>E?uw&v5S}vu2#jd^;9A~$2aw3%&>|4bXY$7GqZaEWdIU8(= z{Dz||IC7IzGJHw@<77(AL1w5LtA?|P(jRav)ks=XiM^}nSjD05a3Ga9T$z85R#KHp zBgjNi%5^qOc>meiV1@oR8<_U zsA{C$)MkP+9=7{u7J6>ue9tXRB&OEz+%3#SqkrJ}@!nM8g=jRof{7?&(bg8MVg43! zRpDTRFKqCI%oiT73eQTuuyKR%%rE4h0E^mN;6V$GK6oq02~dG%6tECTJIOmC-E{}) zO9}lpmR3<*gWI!)s})_%xQ24DJK^sIniA}PlAMyj3v-ymz{|q-o%gaJN<&rY(rvk zVQ1gG-Ff?F+TY8I&!78s1;a9OE@jh=gc{%*;6;Fmz(75NO){6OlY%ELxR$^G{u~S& z_%dLme3Pi)I+^L^nS^cVk*@@-h%j*Dy5|sFNQ8kY@T;WOhvvOxwA^~zIl>~~9!oj1 zG)wR7Ti;bGQ0@yxVzRZX2Bdk>5cQbdK3M9#cY$qW$(L>FKX@TOjD(sb^$6yFV`}?P qEBb138c`nG8x?W4BP|*fkKN}wwOr{qjEEvPX&q(M%j0NhF+Ttp!b!XU literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue119/empty_plugin.rst b/functional_tests/doc_tests/test_issue119/empty_plugin.rst new file mode 100644 index 0000000..6194c19 --- /dev/null +++ b/functional_tests/doc_tests/test_issue119/empty_plugin.rst @@ -0,0 +1,57 @@ +Minimal plugin +-------------- + +Plugins work as long as they implement the minimal interface required +by nose.plugins.base. They do not have to derive from +nose.plugins.Plugin. + + >>> class NullPlugin(object): + ... + ... enabled = True + ... name = "null" + ... score = 100 + ... + ... def options(self, parser, env): + ... pass + ... + ... def configure(self, options, conf): + ... pass + >>> import unittest + >>> from nose.plugins.plugintest import run_buffered as run + >>> run(suite=unittest.TestSuite(tests=[]), + ... plugins=[NullPlugin()]) # doctest: +REPORT_NDIFF + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + +Plugins can derive from nose.plugins.base and do nothing except set a +name. + + >>> import os + >>> from nose.plugins import Plugin + >>> class DerivedNullPlugin(Plugin): + ... + ... name = "derived-null" + +Enabled plugin that's otherwise empty + + >>> class EnabledDerivedNullPlugin(Plugin): + ... + ... enabled = True + ... name = "enabled-derived-null" + ... + ... def options(self, parser, env=os.environ): + ... pass + ... + ... def configure(self, options, conf): + ... if not self.can_configure: + ... return + ... self.conf = conf + >>> run(suite=unittest.TestSuite(tests=[]), + ... plugins=[DerivedNullPlugin(), EnabledDerivedNullPlugin()]) + ... # doctest: +REPORT_NDIFF + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK diff --git a/functional_tests/doc_tests/test_issue119/test_zeronine$py.class b/functional_tests/doc_tests/test_issue119/test_zeronine$py.class new file mode 100644 index 0000000000000000000000000000000000000000..8138d8c1bf1a228529d81b5b88fe408e67e483fe GIT binary patch literal 5731 zcmbtY3wRt=75;BF$!?Y@-3)0dZGj+xCf#nfDG#MVu#GJS(F}bt%o_p>&=RfDa zXZqk{cijV^N3|)$#yU1C?CczH-0^I2+MjUj?1`Ut~q7(E?U?%;acYCkZ<}{ zZ`Y#L!RZTzC#?~`SK-(?D&vSL#68O|)wD84o{R?FGd7T53<~}o9FzxZ|kniU0@x>-reOba>4a;zJ5*n5(B*Zpn#4IRe zk4$~VXYr#Iy7+Mn)s*bK?^~XqzzPM;c08-QSSXF>ZTfetLTaJmaU7@6JQ#>1utp&n zI=bC;rW7uYFy*5E!ukHLi|V7L;dq=N?w?59YAvl;9J+B5-3ZOwMYk+Bfs+;5=7~?4 zwmB{WNd3eAGRw@{jXL>sITUD{Ur^X@f7i=U+TM5lp;;wsl;L1RMDTVX2A}HWi=BW zbSG&TQ_g594E$gac!5M<*vah#am)xR(oyC=IcCbd#Of=U9#6a?B2$;BrFzFk%pXZipNVHBaWR;KAz_sKd?CBVbjbJp1VXy`hQpbI!$IKg7ST4V6kVzn~(7IrB&vy9A?Fh~6SKHdijI0|PiopVcqw;9io{BadlF2-db@A+}~q{y@LWLB2S3G{if zT#cvuaA^`HT*iDXR+;y5vHvs;&kgOrl307Cyl;hsa5X6|2tlZY=&xDg3=Pl63tEWI z3t5tjE!keO85zfp%or~?!)Adxqsq5pwuYDBr7g_hmnobQ5P&7c6VQMmJYbAE zmS@q#0bqetF0A8uo7RtshF9Q~QXa2j6rGY^EU`deBhlf-=@)E;;}@e> zrPN-z-d=~-OE$iN{usunHC!5(B7GBo(uUAK4Q~mV`nrIDZO0Z06B3PecJ0;hR=iC> ze!Jjsx`rD=9B!gT;#HnToVk<{OL_MWyi-)ai&c?xY)iv?B$gNBy}XRJc%J178rxjg z;RW@6(vTGSw#!DWqRdB<{G9XkkBy4=oKlj_2k=1&*{uvSSNl`4V{;VO+p;5%cq^T1NW}{+TqetFw%-y38;xkEn8lNSnt*fEMb?r{j;rge9~7R52?}li^wgi{xwcboX>`%8pgo z!YmjP1}{76j8xu&u;e|jWcBo%m7N!)?&5R;4{7*fSVmvwH4zdt8!U? z1K*VT`IZJ1X5V)bxQ{N7aCVqN7po&Xf?zjOYbuagIq!O`c+j;wfvu8_q zCh;hKMc>?#9r||0VnUd|Eh3u2)4#!QC1}56IxtT2wkVtw5wLk8{CYciaUJh0*|wDM zoXv6kNui7Sx3g!KhON)B8Hdt{@3=dyu>!vX$*XhX3Y%(4Bj~OoJ|X8@qTw(2YZ8CP z-^kZNH(sq1;p_gW4E(>oTP4dHBnkc{ZDR2DZ{a<8Q%blxc;hE*#D!mtP+T=Bw9mI~ zs>mR@*)4_K{0=NSY>xoRT|Y32q9<(QKcm?9O|6!n?)hHEarU z9!_E)?txOxNMe;xF-=`1tzR(m>JCP7PcZVsBN*9Q1taS<7};(IBX5sjlUsyNuKH zGx%pi?Zo55iL)qC#uNEJT{*X6b*g2-?k#n8AJ$({ckZ0J8Jt(f`BXYkRjN@bE#Jtw zwG_)3F=I&2WoEEf&EnY-k6hLM3}?pDspZ_!6z=G#?nvt^`KnLQqKwf<4U^PxsH$Od zzJ`W7Ff)Vq?I)H>rTF($V#;?u4SXc68~G4b#QDgY#%DPtSXm+N$Wlfmk4@`OOY1{{ zhi&Q9GKoL3nM z;hhzkd0y#({Sv%ZK02Q^dPtI~cz7*?BSE|{omwGw#L9TJ{#wE_i#MiI1dFD5p=-zY+~74|aU;cFlCO<3ctlc7vh)Y|VP&K(DdT6e`1Sf({65sxfEP4Jhl%;vem3TO8u&EsSE5e^$Xv~e>L4b(k1EjApwwA!VMD!zjrA5z fueZ>kBu^XgkUEBA8yaYbQi*=mqLT7*zB=kZvA;6k literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue119/test_zeronine.py b/functional_tests/doc_tests/test_issue119/test_zeronine.py new file mode 100644 index 0000000..6a4f450 --- /dev/null +++ b/functional_tests/doc_tests/test_issue119/test_zeronine.py @@ -0,0 +1,26 @@ +import os +import unittest +from nose.plugins import Plugin +from nose.plugins.plugintest import PluginTester +from nose.plugins.manager import ZeroNinePlugin + +here = os.path.abspath(os.path.dirname(__file__)) + +support = os.path.join(os.path.dirname(os.path.dirname(here)), 'support') + + +class EmptyPlugin(Plugin): + pass + +class TestEmptyPlugin(PluginTester, unittest.TestCase): + activate = '--with-empty' + plugins = [ZeroNinePlugin(EmptyPlugin())] + suitepath = os.path.join(here, 'empty_plugin.rst') + + def test_empty_zero_nine_does_not_crash(self): + print self.output + assert "'EmptyPlugin' object has no attribute 'loadTestsFromPath'" \ + not in self.output + + + diff --git a/functional_tests/doc_tests/test_issue119/test_zeronine.pyc b/functional_tests/doc_tests/test_issue119/test_zeronine.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e1e946c56efe7c7c6f2e1903f674967b05e1675 GIT binary patch literal 1362 zcmZux%Wl(95S{D1)0UPB$__u6I|db4Q9goi z;j{Px=3J*qK_v0@%-q*GGiS2;z1RKv^Tl*V>rY+YujR47WQd8DL_uXJ3KYePmL4TF z3TmWckCt^x8Wc3dsnN1YNsEFODNSVBWID7qx}F>4bnB5%hgT#L3B%W5$sdkA}te&%mK|kirbsHhd_Nk=m`AjK!Hh9hO?x+GHK=S zZv2nhvM#FV1BnK@$fhRM)oN~GcbVr|!Mr2(=_==&YHpJ+-0&9b+A^>lp~ydXCr$?* zLWqZ98m)8~a@RSlEG`pq2jbCo;0tlbd0{?9tjD~{$7yDDSm;DY@}7()qvzv!na`8F%Diq-E+S)(zAF(P%8dU6HNMB3|?dAk9E)buwqDCM!8FnIVlg*6IR@KP`%^ zl8lcf+w%F?$^h(AUvWbLLR27(Gi}2(<8W3)cJWA-S!^KHRs$r*2L<0@fMg=6GIZcB z|67-?l1`je=By+_uv&*b7b?!U&P?izg)TIs9c*b>W8R0i1@0_|-fzeRbm7$8R84iL z9(#jBiTHamI5C&0VK8K%hC)YoFt&}xPyU%(ui5OYx(Ef3tRflr4;rnaG`iA-kEZjH zeaAMsw2mFxGByQTYTaFPgLd*EDIf5Cd8kSga*1nBq=g(k_s`(t%IbJW&aHRbkpZ{U eKY|6;+e}p@hCp9oa2oEYj@rZ5d+H5qN3}oQ#}+F9 literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue142/errorclass_failure.rst b/functional_tests/doc_tests/test_issue142/errorclass_failure.rst new file mode 100644 index 0000000..c4ce287 --- /dev/null +++ b/functional_tests/doc_tests/test_issue142/errorclass_failure.rst @@ -0,0 +1,124 @@ +Failure of Errorclasses +----------------------- + +Errorclasses (skips, deprecations, etc.) define whether or not they +represent test failures. + + >>> import os + >>> import sys + >>> from nose.plugins.plugintest import run_buffered as run + >>> from nose.plugins.skip import Skip + >>> from nose.plugins.deprecated import Deprecated + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> sys.path.insert(0, support) + >>> from errorclass_failure_plugin import Todo, TodoPlugin, \ + ... NonFailureTodoPlugin + >>> todo_test = os.path.join(support, 'errorclass_failing_test.py') + >>> misc_test = os.path.join(support, 'errorclass_tests.py') + +nose.plugins.errorclass.ErrorClass has an argument ``isfailure``. With a +true isfailure, when the errorclass' exception is raised by a test, +tracebacks are printed. + + >>> run(argv=["nosetests", "-v", "--with-todo", todo_test], + ... plugins=[TodoPlugin()]) # doctest: +REPORT_NDIFF + errorclass_failing_test.test_todo ... TODO: fix me + errorclass_failing_test.test_2 ... ok + + ====================================================================== + TODO: errorclass_failing_test.test_todo + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + Todo: fix me + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + FAILED (TODO=1) + + +Also, ``--stop`` stops the test run. + + >>> run(argv=["nosetests", "-v", "--with-todo", "--stop", todo_test], + ... plugins=[TodoPlugin()]) # doctest: +REPORT_NDIFF + errorclass_failing_test.test_todo ... TODO: fix me + + ====================================================================== + TODO: errorclass_failing_test.test_todo + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + Todo: fix me + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + FAILED (TODO=1) + + +With a false .isfailure, errorclass exceptions raised by tests are +treated as "ignored errors." For ignored errors, tracebacks are not +printed, and the test run does not stop. + + >>> run(argv=["nosetests", "-v", "--with-non-failure-todo", "--stop", + ... todo_test], + ... plugins=[NonFailureTodoPlugin()]) # doctest: +REPORT_NDIFF + errorclass_failing_test.test_todo ... TODO: fix me + errorclass_failing_test.test_2 ... ok + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + OK (TODO=1) + + +Exception detail strings of errorclass errors are always printed when +-v is in effect, regardless of whether the error is ignored. Note +that exception detail strings may have more than one line. + + >>> run(argv=["nosetests", "-v", "--with-todo", misc_test], + ... plugins=[TodoPlugin(), Skip(), Deprecated()]) + ... # doctest: +REPORT_NDIFF + errorclass_tests.test_todo ... TODO: fix me + errorclass_tests.test_2 ... ok + errorclass_tests.test_3 ... SKIP: skipety-skip + errorclass_tests.test_4 ... SKIP + errorclass_tests.test_5 ... DEPRECATED: spam + eggs + + spam + errorclass_tests.test_6 ... DEPRECATED: spam + + ====================================================================== + TODO: errorclass_tests.test_todo + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + Todo: fix me + + ---------------------------------------------------------------------- + Ran 6 tests in ...s + + FAILED (DEPRECATED=2, SKIP=2, TODO=1) + +Without -v, the exception detail strings are only displayed if the +error is not ignored (otherwise, there's no traceback). + + >>> run(argv=["nosetests", "--with-todo", misc_test], + ... plugins=[TodoPlugin(), Skip(), Deprecated()]) + ... # doctest: +REPORT_NDIFF + T.SSDD + ====================================================================== + TODO: errorclass_tests.test_todo + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + Todo: fix me + + ---------------------------------------------------------------------- + Ran 6 tests in ...s + + FAILED (DEPRECATED=2, SKIP=2, TODO=1) + +>>> sys.path.remove(support) diff --git a/functional_tests/doc_tests/test_issue142/support/errorclass_failing_test$py.class b/functional_tests/doc_tests/test_issue142/support/errorclass_failing_test$py.class new file mode 100644 index 0000000000000000000000000000000000000000..cf2de6b471729fc4d4fafd09c772d8ef52ccc2fd GIT binary patch literal 3473 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 literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue142/support/errorclass_tests.py b/functional_tests/doc_tests/test_issue142/support/errorclass_tests.py new file mode 100644 index 0000000..4981224 --- /dev/null +++ b/functional_tests/doc_tests/test_issue142/support/errorclass_tests.py @@ -0,0 +1,20 @@ +from errorclass_failure_plugin import Todo +from nose import SkipTest, DeprecatedTest + +def test_todo(): + raise Todo('fix me') + +def test_2(): + pass + +def test_3(): + raise SkipTest('skipety-skip') + +def test_4(): + raise SkipTest() + +def test_5(): + raise DeprecatedTest('spam\neggs\n\nspam') + +def test_6(): + raise DeprecatedTest('spam') diff --git a/functional_tests/doc_tests/test_issue142/support/errorclass_tests.pyc b/functional_tests/doc_tests/test_issue142/support/errorclass_tests.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e95e34c1ccfa7f451db897a691869053cb3f665 GIT binary patch literal 1102 zcmbVLO>fgc5S@+lX$T8=##fdHN8c}hh{0M$$e*yEx z#V!SCtyr^C}2W*Bw$K_JsfunD{ZP=nkyMc#vul@syF40Uavc-zQ_va5M<}f7 z{42AB?pTGV(wc2;3X}4CxmYe%;jTGlE-z1Mv2~CZ>N2}WSZ{M{8?#)mLfceTS-a5G zby;UcYHb^~sBRpJmT1TILXAbuq`InbAyO5bENdgeqh4!|V#GR>C>d81Mvv1W`%{V(Rq4?; z`@Qvjz_ze|N9w$S516lKD3D?6Xk&EeJkiICo+t`l@WQ=i^I(j2X>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> from nose.util import ls_tree + >>> print ls_tree(support) # doctest: +REPORT_NDIFF + |-- package1 + | |-- __init__.py + | `-- test_module.py + |-- package2c + | |-- __init__.py + | `-- test_module.py + `-- package2f + |-- __init__.py + `-- test_module.py + +In these packages, the tests are all defined in package1, and are imported +into package2f and package2c. + +.. Note :: + + The run() function in :mod:`nose.plugins.plugintest` reformats test result + output to remove timings, which will vary from run to run, and + redirects the output to stdout. + + >>> from nose.plugins.plugintest import run_buffered as run + +.. + +package1 has fixtures, which we can see by running all of the tests. Note +below that the test names reflect the modules into which the tests are +imported, not the source modules. + + >>> argv = [__file__, '-v', support] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + package1 setup + test (package1.test_module.TestCase) ... ok + package1.test_module.TestClass.test_class ... ok + package1.test_module.test_function ... ok + package2c setup + test (package2c.test_module.TestCase) ... ok + package2c.test_module.TestClass.test_class ... ok + package2f setup + package2f.test_module.test_function ... ok + + ---------------------------------------------------------------------- + Ran 6 tests in ...s + + OK + +When tests are run in package2f or package2c, only the fixtures from those +packages are executed. + + >>> argv = [__file__, '-v', os.path.join(support, 'package2f')] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + package2f setup + package2f.test_module.test_function ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + >>> argv = [__file__, '-v', os.path.join(support, 'package2c')] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + package2c setup + test (package2c.test_module.TestCase) ... ok + package2c.test_module.TestClass.test_class ... ok + + ---------------------------------------------------------------------- + Ran 2 tests in ...s + + OK + +This also applies when only the specific tests are selected via the +command-line. + + >>> argv = [__file__, '-v', + ... os.path.join(support, 'package2c', 'test_module.py') + + ... ':TestClass.test_class'] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + package2c setup + package2c.test_module.TestClass.test_class ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + >>> argv = [__file__, '-v', + ... os.path.join(support, 'package2c', 'test_module.py') + + ... ':TestCase.test'] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + package2c setup + test (package2c.test_module.TestCase) ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + >>> argv = [__file__, '-v', + ... os.path.join(support, 'package2f', 'test_module.py') + + ... ':test_function'] + >>> run(argv=argv) # doctest: +REPORT_NDIFF + package2f setup + package2f.test_module.test_function ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK diff --git a/functional_tests/doc_tests/test_issue145/support/package1/__init__$py.class b/functional_tests/doc_tests/test_issue145/support/package1/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..1186513d2dcf58eb8944267b1d9b3e3423f1f8aa GIT binary patch literal 2758 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 literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue145/support/package1/test_module.py b/functional_tests/doc_tests/test_issue145/support/package1/test_module.py new file mode 100644 index 0000000..0c5ac78 --- /dev/null +++ b/functional_tests/doc_tests/test_issue145/support/package1/test_module.py @@ -0,0 +1,12 @@ +import unittest + +def test_function(): + pass + +class TestClass: + def test_class(self): + pass + +class TestCase(unittest.TestCase): + def test(self): + pass diff --git a/functional_tests/doc_tests/test_issue145/support/package1/test_module.pyc b/functional_tests/doc_tests/test_issue145/support/package1/test_module.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d033ce29b804b5f18b792a947f39e48bdaac479 GIT binary patch literal 807 zcmcIh%TB{E5Zokfp+!9)ej=w@LFEjh_a0m>^kP|w15xu*?I0nJ@DY3`U%-yjHVBC$ zkuq7Y*OQrX^);WpeJAks&G~ z%6WTy+K6y^6Hl0CU=Mh;0{f_E-^A#wV+Q&K?wNa{-4`#~8R4o{)QvT2Z%U*2n=F#W zwaB|VbwyL_QaNLt6m3knAOc>(l9D3Q2BaoL#3FI=+7z$ACj3)|B}WgX9jm z830~V{QV45y{s4#-8q9&wXTd(epad8l~NLv1D&rtrvw2m#xqRlTx;cqzh!vm5)HU|%}=fZSX7Y?Knv3jc#vwpx=68Aam4(z=s P9J>Wr2$+V`=xX#0ukCk+ literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue145/support/package2c/__init__$py.class b/functional_tests/doc_tests/test_issue145/support/package2c/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..ddc721a778f24420dd771409666fe7330eb03f05 GIT binary patch literal 2762 zcmbtWU0WMP6n=*Umc-D~NPz;iV6i3y*al0rrma$1YAR49hyv=mB$KeP*$c$>FZ9 z8Jr=U$R?L#XhmBTVMq+|#(feZ46z4g%dNS082Z=AXwKb!Z ziJ=#lBIrRM`LXDgs~ZfLgD#-Pq+n+Nm!lNUE3~Jm=&Ivt3@jVNAch2{SBb`X)8H`- z3y@K~#?TuO%d}bGgy14x7YGszZMQW;bMG>2o&n$tqMMx&@cvjdBS=NjiBX1Dl7VNn zW1OK~D8kIEI)j{;-Bh<#SyzpcT;uI*kJphF>X~4;7_c|F%y6Z-43^Y&olBUcY)A;Q zNAPA8H}Mvc>TtctaBlYaH4}kvBO?gB&2YYM8LWjuWcD_mRgFTzyQG<-ZI-i!%WZ=f z7{-GD2Si)f&pk{>F@^g=Ym~c1nN+k**(keZOEyf0D>m1;N@IFFJw729Yid_@#pRCc z$OSWBn~DF5<~SZt-uesY*A?(0TfTB)m^|c)M&C zJe}Wd#~ex3;7^3&7=BJ<*8;Bew2w^Vn(XRo02fCI?B84_$albT*=$g`s-?tZEQkdA zAO==VjvV1Had%GDC_VcUO}COr%Kn<}M8KKJ<#u!nMcs&!URGoH5X)lrNkCl^NM=p7 zz-&cu&_#Dp{n3@9jA6(nvO)6uU!74% ztVM{x7M0kaxdC!ms2s5&VVhy_B#yF0gxK=bLUx(k4sk$)wr;Ds$3xWGskih8uAf`d zsHe9AEd7o1%Tro*`Vx+{cM0E;x&>KO=Vqx{&wJ%{Zu?0`xlW$|`K>u2$!N~>>^v8D z#BhmVyL8I3=4f=4Q-)#Ew*nz_FhoJ$r0Abex9=52l%V#Ce&~->T>Oy|&>yKw(J0Y+ z;){OI=-+*`QJDuuQ{jEI@8aD23v{leen;2XE|Tm3WAmxFJhqQF*nFzx4_w{FwH;c| z@I5`-XcTiwuDZujnhDCfQmwwn{zF`8wo{;>RHo-M+M%Gs-8<}rR{zmQvP^uQS!#Zgc z;KRFE5&;1be1wl{)%FdL`S^m5N6dI zgM}|@_UA;gAo<$J=ve=+;w)g$e+o?E%7B4=I6M3Bc373(AL!j2p%_3|LJyO~@Et-l luT2M=8HSq~TJR$|ZNWqQL}M4tp3?D^EWX57V)6)I{|6Wx;Bf!| literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue145/support/package2c/__init__.py b/functional_tests/doc_tests/test_issue145/support/package2c/__init__.py new file mode 100644 index 0000000..106401f --- /dev/null +++ b/functional_tests/doc_tests/test_issue145/support/package2c/__init__.py @@ -0,0 +1,2 @@ +def setup(): + print 'package2c setup' diff --git a/functional_tests/doc_tests/test_issue145/support/package2c/__init__.pyc b/functional_tests/doc_tests/test_issue145/support/package2c/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..394ffd84d8c26c972e2cacf731b005befd87c09c GIT binary patch literal 304 zcmZ8cu?oUK3{3?G1@RXgJN2mOtl*$i2i;tb<9f{ zlANET;q5WYEOam8yyTA~4mrRbV9y|eUEE{k$F~ySKztFbH*9`;G90rOj6Ip7o7xFF zYC_Bs`~^C&rb6v6hMb~vP-TTq*RF7ebDE^dR2`bqlCMfrXmUuX^2&Bdxb}#ROJ{S1 uO$b#@>N8kr?MqKuC*ewc9@Q=)2cnz;Q5Fl?O87!jY8TxKBB?IgCZR9azdhst literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue145/support/package2c/test_module$py.class b/functional_tests/doc_tests/test_issue145/support/package2c/test_module$py.class new file mode 100644 index 0000000000000000000000000000000000000000..4a98b218fc406b60d38984ff694cb48b5a099a28 GIT binary patch literal 2512 zcmbtVZC4vb6n-Wf*d#73O=-(ZZK+)~}DGz}g?m|^rlJJM7`Giz$WwRN*5;|+%J zHf>uoG{*^Hgdx=V)Eqi;mEn3n2)*8rag8BD6U%n9wpC|%7F+Lko!K2+^2xSKQZByh zWAm6$a2;b2L~(<_iL|Yh21|4IhZ{BE#LmFxjSAc6H0#*7gi8;~i2~ zOkjS1cmy%b5(bAGRe`~~-ube6;eKZtEt1UmWi}Zp1#?J-p@Q@pDA7N^Y`U@_PrBN$ z^&`@#>ed^oX*s-PbAyvCO6gQOHLq4Zz?xAKFdVgFmAzR9mhL!Bo?ci~on}J_MfHW2 zYP3RFAmD?HoGJ8u2Bk_W%b6~>O8g3s}Vm~E2J{)-bx z)-A2VZ5eqgfiiVFmlvp882piCxsGdV4Tgt)9kY|gAV#r#el(+C3xx>2!Zzh*H_dKt zi-Z1pkNKvhponh-W8YG9B%XSXFx>MYtY-^}<$gZ_`PNp`G({PoW0vuZAx{3Yy1`9) z71k`1cxaYg%iiKugZe#{{`q!>`HKqgv}T{Dq8EvR=O{(+9lmE6p$2J`&MK6l5X5xnL=2KB7z-strP(O|2q`CO6P4rNX>4jpMmlS}lhPBKjS?%&wf z={+3+So&w@*Pm!xKlK-$Rv9GtprDB1>N-7?o6Y(jw>#~XDx99*^5KRMVx(X-?J^fP z!!SWt=TelS?&x%ZtEOquJ52~3YVRubVTKwcjEh!EP{&0->5NovG!B!USfO8ozMj*Y zLTHgq1Wyr)Muz@^vYC{kR|?5sbhMC61lqVs3-e3CMD)r8ZJK0D!DO)WJ$m+?2>gya z$C!CRo)~`cqKG*Y1?k0F!U*94QnRm+m_0!{y4XhM1RqHfMctT7L_d)v=@@B=G3nYU z+W7PY*_U)ec%pLl{EX7bXf1e*tcVDNk;7xpI!!Tzb?i^5PorPH!p13{wDI+98&A8w z!q~yCw>#R|ORPlIHngrchCP%$dxiK3lD9jvbIGaSL@dGR>%Xy6@E5F`w@u3n#OyeN nGE^pu!Gc6{Z+iPOLjVq)4bW+q#xa`hQ+z##100IUV;KJdPZ6>H literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue145/support/package2c/test_module.py b/functional_tests/doc_tests/test_issue145/support/package2c/test_module.py new file mode 100644 index 0000000..6affbf1 --- /dev/null +++ b/functional_tests/doc_tests/test_issue145/support/package2c/test_module.py @@ -0,0 +1 @@ +from package1.test_module import TestClass, TestCase diff --git a/functional_tests/doc_tests/test_issue145/support/package2c/test_module.pyc b/functional_tests/doc_tests/test_issue145/support/package2c/test_module.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2313273820073cdc9eefecec58889cf817c5399 GIT binary patch literal 298 zcmY+8!HU8_42CoHq6^*xU!kWC75A**y$9WsmoiMVOX;*TG^rrIh|lR0Y^GMwf&ZI7 zkmOJMSbp8^+rq;168ZNcS_>+G2EZveD{u*%6Ht|cQyQB_&_dh~5+9r)qWP;bghe`& zidTYQY-9c~&saLk)>nf={+Q(V$#L^BW*n6u6+OYVI34=>Vi+~IL3f_8?9rhFmU)(E v8~r$c$>FZ9 z8Jr=U$R?L#XhmBTVMq+|#(feZ46z4g%dNS082Z=AXwKb!Z ziJ=#lBIrRM`LXDgs~ZfLgD#-Pq+n+Nm!lNUE3~Jm=&Ivt3@jVNAch2{SBb`X)8H`- z3y@K~#?TuO%d}bGgy14x7YGszZMQW;bMG>2o&n$tqMMx&@cvjdBS=NjiBX1Dl7VNn zW1OK~D8kIEI)j{;-Bh<#SyzpcT;uI*kJphF>X~4;7_c|F%y6Z-43^Y&olBUcY)A;Q zNAPA8H}Mvc>TtctaBlYaH4}kvBO?gB&2YYM8LWjuWcD_mRgFTzyQG<-ZI-i!%WZ=f z7{-GD2Si)f&pk{>F@^g=Ym~c1nN+k**(keZOEyf0D>m1;N@IFFJw729Yid_@#pRCc z$OSWBn~DF5<~SZt-uesY*A?(0TfTB)m^|c)M&C zJe}Wd#~ex3;7^3&7=BJ<*8;Bew2w^Vn(XRo02fCI?B84_$albT*=$g`s-?tZEQkdA zAO==VjvV1Had%GDC_VcUO}COr%Kn<}M8KKJ<#u!nMcs&!URGoH5X)lrNkCl^NM=p7 zz-&cu&_#Dp{n3@9jA6(nvO)6uU!74% ztVM{x7M0kaxdC!ms2s5&VVhy_B#yF0gxK=bLUx(k4sk$)wr;Ds$3xWGskih8uAf`d zsHe9AEd7o1%Tro*`Vx+{cM0E;x&>KO=Vqx{&wJ%{Zu?0`xlW$|`K>u2$!N~>>^v8D z#BhmVyL8I3=4f=4Q-)#Ew*nz_FhoJ$r0Abex9=52l%V#Ce&~->T>Oy|&>yKw(J0Y+ z;){OI=-+*`QJDuuQ{jEI@8aD23v{leen;2XE|Tm3WAmxFJhqQF*nFzx4_w{FwH;c| z@I5`-XcTiwuDZujnhDCfQmwwn{zF`8wo{;>RHo-M+M%Gs-8<}rR{zmQvP^uQS!#Zgc z;KRFE5&;1be1wl{)%FdL`S^m5N6dI zgM}|@_UA;gAo<$J=ve=+;w)g$e+o?E%7B4=I6M3Bc373(AL!j2p%_3|LJyO~@Et-l luT2M=8HSq~TJR$|ZNWqQL}M4tp3?D^EWX57V)6)I{|9w&;C%o9 literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue145/support/package2f/__init__.py b/functional_tests/doc_tests/test_issue145/support/package2f/__init__.py new file mode 100644 index 0000000..fc203eb --- /dev/null +++ b/functional_tests/doc_tests/test_issue145/support/package2f/__init__.py @@ -0,0 +1,2 @@ +def setup(): + print 'package2f setup' diff --git a/functional_tests/doc_tests/test_issue145/support/package2f/__init__.pyc b/functional_tests/doc_tests/test_issue145/support/package2f/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28350da5d26f18a45e3f178ca41f858b093f273a GIT binary patch literal 304 zcmZ8cy$ZrW3{C|Hh2kqXcIr{7vx0+89dvU!j(U~D{v1szh^r6bbNU1(RYX0woRc_-XR7GxoLp0zQj)2mt$fNp2SI!d#2~xeGYyL~ zgyLFaSHU2Lq6k4|7_I5W7kY)KQyzD{Le(zSO&&&sVdO|Z)>TutDr(+y467pJ4Tj;? zwQ}7mdWLOaJ%nFipWYCL;8Gk|EZeZLT5XO+t3d z764E5x3AzX?nQ71Qw&4PhGlpw3^xKoOzbk;?RB&6gTA95m|Vu&#HYB3c^dCTaUXGt z!R2OI#NZb-Q&ulkb*9!JqKx&^$(UC#i@6ArNHGi)h^j%^w8=o047KJM$HYw4tJYM@ zc6q_!CfDgqr_!m6dd?EvEQl~%wPY84sTG#tx^(0%1fX~EkSny{WGf}enM8PLm7t#jV>@J*$WW&}=+>xIJjFKU=G3irZi~J4I-domrCM+m2mCk#^ofOT9UvP^dakZWw$ElafVMa(kx7!qWE-7vXDyI{?>2#0#nvz={T zHfhS!yuG;2kh#q8H5yfJz=G(EM8R`>6U9Ehr8!Om(kz^JC__H31<~(+Z6!gm0YQR; z01~{$Kn6AJy-x4d8o}VGt2u3#J1%8TFb*E;W}Qp4OeYv71MhF{7_>zP0ha#B{pClx z)(!oog(`zqL`XoyaCL*$<5sxuKh0o8h*+hP}iLctWBKR85{M)hCT>>DMY@*P%#_%0Up+eu@EnPj3g1fkWpg QNoDe1(QpVu2v2bIAHUVCzW@LL literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_issue145/support/package2f/test_module.py b/functional_tests/doc_tests/test_issue145/support/package2f/test_module.py new file mode 100644 index 0000000..e353c62 --- /dev/null +++ b/functional_tests/doc_tests/test_issue145/support/package2f/test_module.py @@ -0,0 +1 @@ +from package1.test_module import test_function diff --git a/functional_tests/doc_tests/test_issue145/support/package2f/test_module.pyc b/functional_tests/doc_tests/test_issue145/support/package2f/test_module.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a84068dc90b4a5552ce8191d10a1376ea801cdae GIT binary patch literal 269 zcmZWkK?=e!5ZrhadJud>PYEgYtS{(6Z(d4BqJ*YRXf}fQ5#Q+xOkxGWz|Jn2-JRL- zRjnV-wv({DRmz7HVUs`s3;>qEnE+7)PIabhlKCnD>VW+vP7;lqx>> from nose.plugins.plugintest import run_buffered as run + +.. + + >>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> test_not_shared = os.path.join(support, 'test_not_shared.py') + >>> test_shared = os.path.join(support, 'test_shared.py') + >>> test_can_split = os.path.join(support, 'test_can_split.py') + +The module with shared fixtures passes. + + >>> run(argv=['nosetests', '-v', test_shared]) #doctest: +REPORT_NDIFF + setup called + test_shared.TestMe.test_one ... ok + test_shared.test_a ... ok + test_shared.test_b ... ok + teardown called + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + OK + +As does the module with no fixture annotations. + + >>> run(argv=['nosetests', '-v', test_not_shared]) #doctest: +REPORT_NDIFF + setup called + test_not_shared.TestMe.test_one ... ok + test_not_shared.test_a ... ok + test_not_shared.test_b ... ok + teardown called + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + OK + +And the module that marks its fixtures as re-entrant. + + >>> run(argv=['nosetests', '-v', test_can_split]) #doctest: +REPORT_NDIFF + setup called + test_can_split.TestMe.test_one ... ok + test_can_split.test_a ... ok + test_can_split.test_b ... ok + teardown called + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + OK + +However, when run with the ``--processes=2`` switch, each test module +behaves differently. + + >>> from nose.plugins.multiprocess import MultiProcess + +The module marked ``_multiprocess_shared_`` executes correctly, although as with +any use of the multiprocess plugin, the order in which the tests execute is +indeterminate. + +First we have to reset all of the test modules. + + >>> import sys + >>> sys.modules['test_not_shared'].called[:] = [] + >>> sys.modules['test_can_split'].called[:] = [] + +Then we can run the tests again with the multiprocess plugin active. + + >>> run(argv=['nosetests', '-v', '--processes=2', test_shared], + ... plugins=[MultiProcess()]) #doctest: +ELLIPSIS + setup called + test_shared.... ok + teardown called + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + OK + +As does the one not marked -- however in this case, ``--processes=2`` +will do *nothing at all*: since the tests are in a module with +unmarked fixtures, the entire test module will be dispatched to a +single runner process. + +However, the module marked ``_multiprocess_can_split_`` will fail, since +the fixtures *are not reentrant*. A module such as this *must not* be +marked ``_multiprocess_can_split_``, or tests will fail in one or more +runner processes as fixtures are re-executed. + +We have to reset all of the test modules again. + + >>> import sys + >>> sys.modules['test_not_shared'].called[:] = [] + >>> sys.modules['test_can_split'].called[:] = [] + +Then we can run again and see the failures. + + >>> run(argv=['nosetests', '-v', '--processes=2', test_can_split], + ... plugins=[MultiProcess()]) #doctest: +ELLIPSIS + setup called + teardown called + test_can_split.... + ... + FAILED (failures=...) + +Other differences in test running +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The main difference between using the multiprocess plugin and not doing so +is obviously that tests run concurrently under multiprocess. However, there +are a few other differences that may impact your test suite: + +* More tests may be found + + Because tests are dispatched to worker processes by name, a worker + process may find and run tests in a module that would not be found during a + normal test run. For instance, if a non-test module contains a test-like + function, that function would be discovered as a test in a worker process + if the entire module is dispatched to the worker. This is because worker + processes load tests in *directed* mode -- the same way that nose loads + tests when you explicitly name a module -- rather than in *discovered* mode, + the mode nose uses when looking for tests in a directory. + +* Out-of-order output + + Test results are collected by workers and returned to the master process for + output. Since different processes may complete their tests at different + times, test result output order is not determinate. + +* Plugin interaction warning + + The multiprocess plugin does not work well with other plugins that expect to + wrap or gain control of the test-running process. Examples from nose's + builtin plugins include coverage and profiling: a test run using + both multiprocess and either of those is likely to fail in some + confusing and spectacular way. + +* Python 2.6 warning + + This is unlikely to impact you unless you are writing tests for nose itself, + but be aware that under python 2.6, the multiprocess plugin is not + re-entrant. For example, when running nose with the plugin active, you can't + use subprocess to launch another copy of nose that also uses the + multiprocess plugin. This is why this test is skipped under python 2.6 when + run with the ``--processes`` switch. diff --git a/functional_tests/doc_tests/test_multiprocess/multiprocess_fixtures$py.class b/functional_tests/doc_tests/test_multiprocess/multiprocess_fixtures$py.class new file mode 100644 index 0000000000000000000000000000000000000000..c373f1708ec2c2364f4c690773cea9bc6ca9579c GIT binary patch literal 4364 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 literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_multiprocess/support/test_can_split.py b/functional_tests/doc_tests/test_multiprocess/support/test_can_split.py new file mode 100644 index 0000000..a7ae6e7 --- /dev/null +++ b/functional_tests/doc_tests/test_multiprocess/support/test_can_split.py @@ -0,0 +1,30 @@ +import sys +called = [] + +_multiprocess_can_split_ = 1 + +def setup(): + print >> sys.stderr, "setup called" + called.append('setup') + + +def teardown(): + print >> sys.stderr, "teardown called" + called.append('teardown') + + +def test_a(): + assert len(called) == 1, "len(%s) !=1" % called + + +def test_b(): + assert len(called) == 1, "len(%s) !=1" % called + + +class TestMe: + def setup_class(cls): + cls._setup = True + setup_class = classmethod(setup_class) + + def test_one(self): + assert self._setup, "Class was not set up" diff --git a/functional_tests/doc_tests/test_multiprocess/support/test_can_split.pyc b/functional_tests/doc_tests/test_multiprocess/support/test_can_split.pyc new file mode 100644 index 0000000000000000000000000000000000000000..311493e24a8b9fd61d5f9718fd4c60ed3e9668b2 GIT binary patch literal 2152 zcmd5-!EVz)5S?|Lv?*=jfDjx&;ZS5QwNh~e1XLVAA_u1zdRZCo77UJUdDm1GoG2f` zZ}BVq0L&YwNf97%Kq*dkJ+nLBoq79a_UpH;jSpY$O){E4A-^ASb5rgN{}sI>IwX3{ zy&@CPVL+xwpIPmZ2}KCW^hM~C8Hg|-vqok}W*zD&(aS^D6-}cJUN`^F&m>nYE;jD5 zvd_)k;?A>;qL4obo;~*g<-Aour-~H2G>u?8ahtHVzO6@DS`^m!{@FkjVhvci6Xyqb zdt;kM#JMp>)4H~$iCCg#=Z;v7>n4AndK>#$9ha4}y0L{#`90np@9xD%ZJGJJD$_!H z>%5CimCYsSde#;`ubV2f&c&{+>#Fgw%w%b)U0vjUw?0X*v$zL`No}bNZ#-~*pWENV zaha{XO&e1kmuHBqpBocU5@4PL=&(bAeV3t&w95E#VwbqNKgYkr(@Y1iaQsY&w*l4L zq?i=U2p+xmDLBsxA(g|Lg)O5UcXxFA!B_|*eBeO|+;6kRW9MvxQ+(PqRdaDL2rY#i z=@o#1!%>J3g4$(cop0ECGH)caUybey_TS@{tMI%;cy zAVFD8gcev^5OKCRx|qn<;Q|V3Roa^@&%=&TI6Nk`F4LLS+T&i)dRCdXU^PL03vPng z%=$N#NswL3I7C|EF(z;k$K#NG12qgbgU%ZS(p6>@We=O5ZV> sys.stderr, "setup called" + called.append('setup') + + +def teardown(): + print >> sys.stderr, "teardown called" + called.append('teardown') + + +def test_a(): + assert len(called) == 1, "len(%s) !=1" % called + + +def test_b(): + assert len(called) == 1, "len(%s) !=1" % called + + +class TestMe: + def setup_class(cls): + cls._setup = True + setup_class = classmethod(setup_class) + + def test_one(self): + assert self._setup, "Class was not set up" diff --git a/functional_tests/doc_tests/test_multiprocess/support/test_not_shared.pyc b/functional_tests/doc_tests/test_multiprocess/support/test_not_shared.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d8a9eea7e61f61a435726b281313e9892956297 GIT binary patch literal 2150 zcmd5-!EVz)5S?|Lv?*=jfDjx&;ZS5QwTd_b0wN9|k%Q96IJM;F<`0KZ=jSpY$PcoW6A&-x_xhZ#s|BBub9T2_Y zUXcmtARyDD&#d;ygd&7w`Xcnn3`7`^StBzfvkvu?=+y!1il)&9@0)+;XOb%x&kpXf zGUn!Pap&1aQOGX@&tLd}ay}}cQ$>nhnntjlxJ_7F-`1lnEedOVe=!k-*aKGX#Q6b6 zZ*0?uI5);@TGzHT5lgh}97jrV-Q@36Z(~2J79M%H|SuJ!=b}*G-jK=VI5^b=CM-R?5n2ca%2P?A9jta{_GBKq0J6`i^wOTC1VDKTlRMnmt{#z;n}BSO ztW*e;Cnb|1CDRD+WZ*m+6v?SA7MM~Wi8NCRB?Ze9vG!o%e$!fUK}&^^WuZ@2xFuGr z%oHzKf^w&`B6aQtd%2*=FY#UHMGh_}sy8@OByG;J1Sm`#upVQh(Q)cVT$M%~>8Py* zg9NoT5n^C(fyCM3@Pa~LhYu*LRcUXsJdZm<@$gvGx=d$QYmcvs*0ajA1*-{iT!0hE zX4W57CP9KN0}*MJ7n#6ET#!Qw4%9H%3_5oZNmrjynx#Pf^dZsmxkwOoBDO> sys.stderr, "setup called" + _log('setup') + + +def teardown(): + print >> sys.stderr, "teardown called" + _clear() + + +def test_a(): + assert len(logged()) == 1, "len(%s) !=1" % called + + +def test_b(): + assert len(logged()) == 1, "len(%s) !=1" % called + + +class TestMe: + def setup_class(cls): + cls._setup = True + setup_class = classmethod(setup_class) + + def test_one(self): + assert self._setup, "Class was not set up" diff --git a/functional_tests/doc_tests/test_multiprocess/support/test_shared.pyc b/functional_tests/doc_tests/test_multiprocess/support/test_shared.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d48dca893e977dca4ef825429d92d81825f85ef0 GIT binary patch literal 3083 zcmc&$TW{k;6h3w?O|ogb+r4lV3PM28qJ|PiTg!pW0+YuAWMJTf!F)f+(#I)rZcIb=gs4x)IRpFYL z9TkRRdMfOS>8r3OW}w2p7`%r0A%_^vnLhGxgV~?^#k?6VLPHGz&0J z*@b8=v|}{)Av%x$fZs#Gr-3{Ph~o)14kTy>RDLyR@w@;WoQ%22)N#AyuH(*wahuz* zD=S;5z_UI1JFhcuRZ8>H*)hNK+~QG^dk*IFF?!L?rI037ojppto%lsHDez5P+uSDj zJvux(e19@u7OBt5BFSTKop%#crYni#c(KfVR@G%{otwC2Rh6}$=t{%OVf9#5jPvre zkMmrQ=Lh@2F8-*1K2+@(%~3H>1tLKYGzTN)4GB2aUX6nSEGajnV>%{o#a22!sY?8r z?_x4@^DMWKa=`Jj$g|?%2LIKPKn>y))YjJ_IoeNxn+*k2krQhQPBDI}Ost*ei~GCk zI6~h!_~DlPMEBsAQ@kTms~k~O$c^o}Yy92OePssl?FRmpnV-mE{ZnghLsDOsjplZxOH{eB_a&EK<9-p#f5(0CJp9$ZZlABfb4+<&tZ*yZ=l~1RO?KpEOCWvk zl6tQ}{tvKJ5n$o5fQaCRr5}V+?IFa~+OXf9pTo~hFy8i#kO2i;jN}@ZIUO=~X_}{<1 zfv;w{huZOtO);wv-TZSj_ZB+TcvO1C{4)rlSQL9Ljga9koNPukRktpjhO<# zGLdS4fYIn#2Ma%%x?AXvxzXr6aigO2BcL2DtEM=-idFRrB}Hq3v-viz1U+b?fl(Ir zJ;*D}O)=+U92d#L#<3shU{RW7j$y?7qSr2^YhnFaX`&G(x8&o|%Q*Wknq#bYgMK&+ z8&#P4BSLAiG%r7%rO0G8p=s8PV_s2!lI7vd)>efFWmYuT7RK^lu2}Kvwy8x;lYSHG tdKir#(Mckf9Mx<~tSGpT8l!zeKXz1W7!1RM;C9#z`#b$kzm3Me{U;1$%ZdO1 literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst b/functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst new file mode 100644 index 0000000..9513fdf --- /dev/null +++ b/functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst @@ -0,0 +1,89 @@ +Restricted Plugin Managers +-------------------------- + +In some cases, such as running under the ``python setup.py test`` command, +nose is not able to use all available plugins. In those cases, a +`nose.plugins.manager.RestrictedPluginManager` is used to exclude plugins that +implement API methods that nose is unable to call. + +Support files for this test are in the support directory. + + >>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + +For this test, we'll use a simple plugin that implements the ``startTest`` +method. + + >>> from nose.plugins.base import Plugin + >>> from nose.plugins.manager import RestrictedPluginManager + >>> class StartPlugin(Plugin): + ... def startTest(self, test): + ... print "started %s" % test + +.. Note :: + + The run() function in :mod:`nose.plugins.plugintest` reformats test result + output to remove timings, which will vary from run to run, and + redirects the output to stdout. + + >>> from nose.plugins.plugintest import run_buffered as run + +.. + +When run with a normal plugin manager, the plugin executes. + + >>> argv = ['plugintest', '-v', '--with-startplugin', support] + >>> run(argv=argv, plugins=[StartPlugin()]) # doctest: +REPORT_NDIFF + started test.test + test.test ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + +However, when run with a restricted plugin manager configured to exclude +plugins implementing `startTest`, an exception is raised and nose exits. + + >>> restricted = RestrictedPluginManager( + ... plugins=[StartPlugin()], exclude=('startTest',), load=False) + >>> run(argv=argv, plugins=restricted) #doctest: +REPORT_NDIFF +ELLIPSIS + Traceback (most recent call last): + ... + SystemExit: ... + +Errors are only raised when options defined by excluded plugins are used. + + >>> argv = ['plugintest', '-v', support] + >>> run(argv=argv, plugins=restricted) # doctest: +REPORT_NDIFF + test.test ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + +When a disabled option appears in a configuration file, instead of on the +command line, a warning is raised instead of an exception. + + >>> argv = ['plugintest', '-v', '-c', os.path.join(support, 'start.cfg'), + ... support] + >>> run(argv=argv, plugins=restricted) # doctest: +ELLIPSIS + RuntimeWarning: Option 'with-startplugin' in config file '...start.cfg' ignored: excluded by runtime environment + test.test ... ok + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + OK + +However, if an option appears in a configuration file that is not recognized +either as an option defined by nose, or by an active or excluded plugin, an +error is raised. + + >>> argv = ['plugintest', '-v', '-c', os.path.join(support, 'bad.cfg'), + ... support] + >>> run(argv=argv, plugins=restricted) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ConfigError: Error reading config file '...bad.cfg': no such option 'with-meltedcheese' diff --git a/functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst.py3.patch b/functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst.py3.patch new file mode 100644 index 0000000..51a09b4 --- /dev/null +++ b/functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst.py3.patch @@ -0,0 +1,9 @@ +--- restricted_plugin_options.rst.orig 2010-08-31 10:57:04.000000000 -0700 ++++ restricted_plugin_options.rst 2010-08-31 10:57:51.000000000 -0700 +@@ -86,5 +86,5 @@ + >>> run(argv=argv, plugins=restricted) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... +- ConfigError: Error reading config file '...bad.cfg': no such option 'with-meltedcheese' ++ nose.config.ConfigError: Error reading config file '...bad.cfg': no such option 'with-meltedcheese' + diff --git a/functional_tests/doc_tests/test_restricted_plugin_options/support/bad.cfg b/functional_tests/doc_tests/test_restricted_plugin_options/support/bad.cfg new file mode 100644 index 0000000..c050ec4 --- /dev/null +++ b/functional_tests/doc_tests/test_restricted_plugin_options/support/bad.cfg @@ -0,0 +1,2 @@ +[nosetests] +with-meltedcheese=1 \ No newline at end of file diff --git a/functional_tests/doc_tests/test_restricted_plugin_options/support/start.cfg b/functional_tests/doc_tests/test_restricted_plugin_options/support/start.cfg new file mode 100644 index 0000000..ea1e289 --- /dev/null +++ b/functional_tests/doc_tests/test_restricted_plugin_options/support/start.cfg @@ -0,0 +1,2 @@ +[nosetests] +with-startplugin=1 \ No newline at end of file diff --git a/functional_tests/doc_tests/test_restricted_plugin_options/support/test$py.class b/functional_tests/doc_tests/test_restricted_plugin_options/support/test$py.class new file mode 100644 index 0000000000000000000000000000000000000000..2336360216122f6374d26c7c1b119b01de772d75 GIT binary patch literal 2643 zcmbtVT~`}b6y28uhQy(zk+u{l1&cHxz!(&54Wg75O$jLyOa;_9BsXE`WM-V1(8iBP ze@A`x2|A z5RPXO1sR>_iXse&A=auVA;KU(tXZya-eDNrYVTae8;~Q2qK9Fi)!b6uD7l(xNVv=p zsT8ZaxvlDCbKrF*uOJqo2;XGra=GJ*Kl13^ixhb)8H}&ghmvD zBAxE)0= z#wZ05aW}>px<#qXlBzQ(@$9a;rz*N?RF%AIYeuz|AZHX12IEMGvQ98u4bV*#7;d$% z&$_y;a|u&K(|9vFC5mYfVZ#pBD^$Czk1IhpgLegV?=f@~Ng}9}e#8u#V%gdrF<5bH zmSUI=FWOw^DxIn6)bxx}@fo7(Md86w%4W&GiQgpcQ1&I4my4EOuWClov;?CZ#i?7C zX}gLLo3nat*9Kxn7^<<&18WIMoJ zCKEJG{JvPir%~#p&two`M2?Rr{wjA@RL!7T$J?qBRcfBhwRnKfM4=lu1?wlHWb<fhNYWcY7K@ht^#EO@1575vN9aDlrQA#OZX|z4-_!v{*)iVEC1ZD{j*w)zWXB)4 zae$$Hl4p2M-(7TyJ2lmr#c{d`s4gWt$znSl;h&n9zC{#I(BtVWPaGpPafsR2y%T(V zh=ow-FD$N1CSwmnq0j+lLyU#4^x_1WLoA;?-U)k;VegT7kE6elSrHn|1>Z?8og&=u z0hUDyKm<8F_FqRR0P))Q62lv@)nlw3Vf_S~6DRnlX)A&vzV)SJo-6?n8$Lm$X^o+Z z9sm7j0#(2|J~25p_^a>*jCkK7CQ%$<=m@*}N6`0K6VL|zw?a`2AuM5-N#fW;i0=M% hq}{_ZPewa%7eCP1M>mt)TN!)@TU;K&`47k7!Px)+ literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_restricted_plugin_options/support/test.py b/functional_tests/doc_tests/test_restricted_plugin_options/support/test.py new file mode 100644 index 0000000..f174823 --- /dev/null +++ b/functional_tests/doc_tests/test_restricted_plugin_options/support/test.py @@ -0,0 +1,2 @@ +def test(): + pass diff --git a/functional_tests/doc_tests/test_restricted_plugin_options/support/test.pyc b/functional_tests/doc_tests/test_restricted_plugin_options/support/test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46c102a9b86e031ebe63a8483a5a5303f176d1b9 GIT binary patch literal 281 zcmZ9G!3x4K42CoDB)#^HN4vrLe7~$prE0L-?FNfl1v#E%eWyyn6vZ%4}cS!4BBGFlOOIV-1@_{giFs`qAb(ld<|ogv%nFw7S>X&;0~g>wi#k`k;8H>> import os + >>> support = os.path.join(os.path.dirname(__file__), 'support') + +In this example, the project to be tested consists of a module and +package and associated tests, laid out like this:: + + >>> from nose.util import ls_tree + >>> print ls_tree(support) + |-- mymodule.py + |-- mypackage + | |-- __init__.py + | |-- strings.py + | `-- math + | |-- __init__.py + | `-- basic.py + `-- tests + |-- testlib.py + |-- math + | `-- basic.py + |-- mymodule + | `-- my_function.py + `-- strings + `-- cat.py + +Because the test modules do not include ``test`` in their names, +nose's default selector is unable to discover this project's tests. + +.. Note :: + + The run() function in :mod:`nose.plugins.plugintest` reformats test result + output to remove timings, which will vary from run to run, and + redirects the output to stdout. + + >>> from nose.plugins.plugintest import run_buffered as run + +.. + + >>> argv = [__file__, '-v', support] + >>> run(argv=argv) + ---------------------------------------------------------------------- + Ran 0 tests in ...s + + OK + +The tests for the example project follow a few basic conventions: + +* The are all located under the tests/ directory. +* Test modules are organized into groups under directories named for + the module or package they test. +* testlib is *not* a test module, but it must be importable by the + test modules. +* Test modules contain unitest.TestCase classes that are tests, and + may contain other functions or classes that are NOT tests, no matter + how they are named. + +We can codify those conventions in a selector class. + + >>> from nose.selector import Selector + >>> import unittest + >>> class MySelector(Selector): + ... def wantDirectory(self, dirname): + ... # we want the tests directory and all directories + ... # beneath it, and no others + ... parts = dirname.split(os.path.sep) + ... return 'tests' in parts + ... def wantFile(self, filename): + ... # we want python modules under tests/, except testlib + ... parts = filename.split(os.path.sep) + ... base, ext = os.path.splitext(parts[-1]) + ... return 'tests' in parts and ext == '.py' and base != 'testlib' + ... def wantModule(self, module): + ... # wantDirectory and wantFile above will ensure that + ... # we never see an unwanted module + ... return True + ... def wantFunction(self, function): + ... # never collect functions + ... return False + ... def wantClass(self, cls): + ... # only collect TestCase subclasses + ... return issubclass(cls, unittest.TestCase) + +To use our selector class, we need a plugin that can inject it into +the test loader. + + >>> from nose.plugins import Plugin + >>> class UseMySelector(Plugin): + ... enabled = True + ... def configure(self, options, conf): + ... pass # always on + ... def prepareTestLoader(self, loader): + ... loader.selector = MySelector(loader.config) + +Now we can execute a test run using the custom selector, and the +project's tests will be collected. + + >>> run(argv=argv, plugins=[UseMySelector()]) + test_add (basic.TestBasicMath) ... ok + test_sub (basic.TestBasicMath) ... ok + test_tuple_groups (my_function.MyFunction) ... ok + test_cat (cat.StringsCat) ... ok + + ---------------------------------------------------------------------- + Ran 4 tests in ...s + + OK diff --git a/functional_tests/doc_tests/test_selector_plugin/support/mymodule$py.class b/functional_tests/doc_tests/test_selector_plugin/support/mymodule$py.class new file mode 100644 index 0000000000000000000000000000000000000000..86c6620c81e1df8472738783c02bc828d882a469 GIT binary patch literal 3007 zcmbtWYg-#d6n=*dY?7vyMrfh*LM_%n61G&Uw$xTii>5+~1W{-;F3BWay4g+lLXBGQ z_s6?GKtKDbQUxA)>JRWo`FLitiKJQh;)i5rXU@KJ&U@Z-X8-=@&%XeS;U@;EG<1q# zE-`IdWu;nkZkmQtFfFdk)}}1A!pGY-=5JbDE#@56;p4+?yR)@x%PYL#j58eE0aAv< zAluy0HG?yR5}DzJ7&_4vMF=uOy!|~H5r){6O4aegMuVlTs?cDO0m zC^(vF$k@jaDdo$$xvc7hd1yDt{fI}%!$%qRR%-c@k1;gH(AR!Modp>O7{X-=)hwtw z!x4&gwZ+=^j;1RU zmZjEg8IKY9B~2&45`^RJ>#9*!{0z3Q2*e>g9>ocqq!gC&x@tQb1Ixsaz_4Ivgkevm zRxyjN&WEZs87ZPCAmmB2$j4*o#b^X+C=4q>67)UUCCHti1J-@8IxS;SbmO$=Y%`T% zJVn_O!mMXYNo=Da=UAFiZZC(X;05l}ct+&!St2%T8a#$`LNifNHS`964KFYpYu8Rg zp7ZLmNI2zLO4Pl6K8lN&AatASCDP&aHmZruNn93eUSa6SQ&k74*$;!Epj0hwm5Nw# zDpkcWZJxKd&Q%)6M#n}^D-D6Gdfwr-V=G0o;LpT!o?M_hH?4eCcgvcg*lx9IT8`3i zX|!4kBNJF0bth!JL`7Y)%u2>^xMlDn!)Opo0lD#`U)IBLT zPRdbY&(~FXA&gf@yavA`G|2FCBC}omK5{_j|0@vRCbP5@;95)e#)Gf}j>`o01;0pM z#cNR#(P9ilkcja*m7E&J>ohEfk*A>$hDt$ErN6f5si)%E9B3(eKc?Y@ zFjAs#JQt;dZpKhRQOs5X8mWMrHq|1xWKa|7Ezrf_@SIkmK6S}79owam?^$7_8pfL~EQ7HmPILjob5T3%r|9za-&36=1P_EMd5 z4MS9E(`OlPF$@#_l%{ioF3?HSARb&%DRaCeN`@|;ra8mu?TVkIhH3}gs;!>H@HXCw zqK0>=1<<{sH#-TpU91x+MtE%pDiM~bX#v2H6mHE3Nk(&~YZbW25ko(@?a(O;nypbPCk(@+j~nvP#&HVzB1Qix zHT6ExUdh!y(P_Mqs?i(i5~EQjJK~9c-_p}%bkRFNDj8Zwcnyzax6r+q{2e{%HJoA_ zcru%epGmLdEXyW4{y=gK=kJj{!*}%GMWdKgQ8f{7L!yr)U@_TAh@B5`Fq@RtF_6wn z`J8w>7ZPU;^oPWPFcQ;HKDUJ?H?)rI8I>8o7_D_|;9!-p8CIJ;3}-Dj8ppBxwy(5@XW-Zfs)dK5le%d7D!Fja(`e ze>0crj4$WN_DsmL4SBZY1KFPXjnGBV)N%YXyJ-|egw}9Fcn3tl$&?zFAgIlBh6$nd;^K*{&aAsjiH@3I`9L5b|8ZvY3!ld RXLS7M8GL~+#pEi!`WNFfEl~gf literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_selector_plugin/support/mymodule.py b/functional_tests/doc_tests/test_selector_plugin/support/mymodule.py new file mode 100644 index 0000000..66b3c16 --- /dev/null +++ b/functional_tests/doc_tests/test_selector_plugin/support/mymodule.py @@ -0,0 +1,2 @@ +def my_function(a, b, c): + return (a, (b, c)) diff --git a/functional_tests/doc_tests/test_selector_plugin/support/mymodule.pyc b/functional_tests/doc_tests/test_selector_plugin/support/mymodule.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6320e75acdd1bc7e991f3c1a638c2c089d418e1e GIT binary patch literal 327 zcmYjM%?iRW49?V(GVn2WTA6rP@V-O6c`382GFaELX2Q^;58-qA1SZ9aCHeB@r|DPw zPM7!Bw$$)F3B^5mNC|3yC%}nF0>^Ad6+I`I{J?W-f(Kawj~QH|9W&liW;};XtBB`_ zI+i6>L2^&>+Kkf(sEG;RH@6CnK4f>FI`O+~TDi+m$4L#MHEUDYZy0fVTW{xZ&0 zplOY+SH2ytO((+Oz3Wlf(YktQP3}ir(K~ij(<+t{SPCR%lrI;={}{KTLizjFE?(ls E2lBB#?*IS* literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_selector_plugin/support/mypackage/__init__$py.class b/functional_tests/doc_tests/test_selector_plugin/support/mypackage/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..34dc2b3c222b672774f2cadf4389a68b662cafeb GIT binary patch literal 2167 zcmbtVZBrXn6n<_tut{7fO({*G6lk#~fo2P~)f%i)6y2K8NHALJxLuN)blc7D&h7@y z2Y-ux_EUu^&FJV4@JBhGn?y>;GUJTQkbC#ubI*CsbKd^?`?o&Udl&wKqMZ$@4=OJI_3B)XISo@O)z3gMCIizH#F2>8`CAOhVSO^jNWqpP^h}?PMZq~t#4(QZ z45MqNZ3g!lE~X35jeSG447;J01JAS@`AmgjZrIFb%dQ5dWA7My7MF36XiE#s7jP+o zDWnL4&#gMcc%j*Ljp_@d!57^&aiE<1mT^VFG-l$M#Z`ugPRxg)?t@`ys;+156VYnW zbXD8&x$bd`8?>(ER&uLqy${T=^nm+;uhyJuep8a_!!?G~u=hPUG8W05y5}@CJK&zpYYe$C3PZxscSykvypzD&NGq7ff}kNw z7DFCvI5ke}O`HypjGGLxwWd>RS^RzsDrJk!zZc>RKc%%N!y)}IC))0A2#A5G=fiY| znA5bdn&|qYP{pk#C=1_Jz_2XbzeD&NJSZBbO{tJR^C=}*vnw-|4zZc3#6)IYz+{4S zt|@pA?~5HJ`Sj^<3M2)`sBurm$5aGWD&~Nf&8BPd2aXf?foHf3_re-N*Nb6{;`sP! zUcm!wB(RQ8$TzQL_k6oEtZ(pHs9Op)@tI)kbIJwcY2XRNLI`04B9ubcPZLmTZ?|k) z6!I}<83l$6>EARhZd23SaBSkCB`Rl|*F_0Y@gIvbte(vK4Qf_?NGBXbqF@V;5-6g? zFjh4zOaHfa8OmubEdBmh`xPk`5lOHUk_2x!kU<@9e$aZ;B^o?+y`)vR=ac6|5b(@~T`!hT$@WE+8uv(>E!A>$dIC7$Sr|7Rc2p^7(5>NQov(P*bAIbXO`j zT1V-gSfT%?^mT)F!lCh{=n-N|kylU-kbFre39_vHM@Ls<`-d0F!Sbb@(5qOWIez4==e7Al0sC*ER)5eL!y0ud*!T)2&(ipf@`SJ TI!U{4$o^vuyLcit5Ap3kyzWR+ literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_selector_plugin/support/mypackage/__init__.py b/functional_tests/doc_tests/test_selector_plugin/support/mypackage/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/functional_tests/doc_tests/test_selector_plugin/support/mypackage/__init__.py @@ -0,0 +1 @@ +pass diff --git a/functional_tests/doc_tests/test_selector_plugin/support/mypackage/__init__.pyc b/functional_tests/doc_tests/test_selector_plugin/support/mypackage/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8730b7e5453962623a73d715de2676d99239a2f GIT binary patch literal 189 zcmZ9GF$%*l3`Je&ROmf2MWl3ZPmrYt5XNzXX>1Ei4uoE$=j;h8L#7J;`_ktRir@3; z{ke|L>~m1RtN5sxjCph|89RmfgG9}MN?5IUkVud+dn|tOR(|k{4z!W literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/__init__$py.class b/functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..f40b42726d981eebc68001989e41580e6aee8c2c GIT binary patch literal 2310 zcmbtVZC4vb6n=&cY!bH?(zK<36fD>z&}_k0ZG%*bqFV!v1f!+ux+IgZu-V)2}5@wi%QuX#q37y1sic0F#?%7GE^LbmU;)PB6P$E!hs;p#aiWk?LN&jZV} zIYTU?Wh)8>F_c6MGDEuAc8%)3(ct-}5$xu74BxEAkzlygYtd}FGTvfHkl=X1ie)k6 zGsQjQz)&s2Zm8wJGwnuy5U-*Wo-g3-BotgEP<7oh{J>;jnt~Ce5`^dyL#o$rsV(C& zLEmsh1Y_s3px`P-6S#tF3`0w%Z3fE>mm?5nD-75B-K@3jYG69{ma$`T8ShZSG6M5B zt|u{xDZ=1$t4`Du&sb9hYcp<}QZUlDDPu;#ySS0SEZ$=n&?);-_(S3tJk|Bg14_0U zG+otpe6D-k;s&h?`Gx$VS`Q%_mL70F@YR}A4JGld6F#a3$J1S_)i7<WCS?P){}_Sz`)-_f?WBS3T&K_SX^ zggZ@(BA^qGf_u21q@pY+V7Mc~|CsPMc(87mHq}GsTve*KX6Me8OoY#DB`(Tq91}^h z`Kf{j_(Vu+WV1J&0!h&^YTT3Y1@%Ogrb@ue)W{Zp=s1BNc!tYxFKRM%qUgscw$D^4 zwnz9f2@OSx&1>1GvEA*r*R2*AmVymD7L1js7Ko=V6^7{u!n&?d3Ek)=pw!-M*|zBG zGt4r!7_wx4&9t~p(|6UeiHDYGpG{sDeMGb1>^j5Zo0DXfW|to^lDm;8sNkC8~1?p`6j8%I|-@1ERzRL=k)!Q3S6!knsbIj*_!9=KepWZH^na@4P zbnXZ@Q>43ag!d)s1wL4xpG$otNzx&1N{mS(!#Kgx5tjcT4Pl9zGqf{IE2F*GA(n+l zAb}M;3{R)Xhd53DjoBxu)#v!^7@wbDJ$HhyPHiQyiE?;5)wxSCre;s@`&#DBY>n$U)IfUR1KWD8uxLddPWG&hrMCpFUdv literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/basic$py.class b/functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/basic$py.class new file mode 100644 index 0000000000000000000000000000000000000000..dca938c40b4dc135b01f852c690974948940134f GIT binary patch literal 3109 zcmbtWTXz#x6#h<=bdoqoiCjtx(n_jH(`IND1cC@uiorJ2rd9zlPLq>#+R03unG~Yn z{err@`>szu_=H76mn{7O{wSCGOs0i4lfL+noRge={r0!_Isg3o=idOv@FPQ@96!P^ zo0u}Kic)Jhi>9FzO^YitjY&(b@=V*o+@i(RQr=M=o=LWy&NMD9Eb*d~Vc6Hfq=W#2 zWOGN?49*ZtWRvqULgcj3K0!O7Ssl6Wkh0iLc`x>f^m-Y3 z+v90LCZUheD3EQ2V}1qkklohL0UV5?ACD8l<$|u-j>f>UG6paxf`5X_Qf<`K;<8%d zN>z0hl?Bz-it$=QLY#v0$zj4Q@r;Z;crt<^JjKv`N;5R)G{e$1QEpS6PCk78Ur(6` zlEiQnJxCE}38FAY34cMN7XC_NYDryD6nxWYBTFtZ^#V@>ctr5mW zUw*d&k}yS0T(-<=)^NCGaN>E|4?Q0qcqz%q;ZhXSn33@!vI2-%hMujJmoP`xvB4 zBH-)P!)h3B&_XPHld_?~Gp%X{)j#1k4qQV-r>ZNnH{laOaz2c>;NIydp;(czfTGyZ zyl9)4!rYXpmbfKhnJ8bR8^GattyW-}+pOlBmW;Px3H)fD_Yh8-R2T+*0NW%&H`n3z-kq%*hG^T3m?d0iNRs_Y zP3H#PloO^wIMhYE%<{768@gdO#2LmPruqbpX4~ib+#HFF2HuY12Hs)VLD!64SZ`Q{ zd?M>d#{YF^3&M2?61*qIy}SEBOhI?|kq zHngJZbsnJS2VHjkzVg#^8ofJ002{ky|9peQwwB|eCy0cvi01+}hMiONpvcv$3*2%? zEX@FVrWBW_1v#U6vu+i+s1^-a3fmzs^O~(uQO_ENN$(ko(8eI8e}-`QlwKsgV%k!u zy<*t7D>bCM(q-tbbVJY@CV4`RKHt!c$Pn;|REp`%)pYnhG?r zUxcu~i9@-Rurm6EFf7XkXbj6q} zmhcffDY;^yhs2KmM*amhG|jxIdT4dATE#VyKM+9)+&dkhh{fsd`xv?$E8oLn6H9BT z4X?pz*@~c!6;C?m$^zZ7p*7rWS!1|`cfIpp2!B!f^zg`N|F6OqkZ3J#%?{9hfIjgi z-oMkthxGaQ4r^8AJNmy&+5-qm7-EuGzD9ud-ZtLpAkpbyq|-qcz9(;8xQHKU-A%jC QD1+Nse2y=~<|4lQ4-)(^y8r+H literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/basic.py b/functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/basic.py new file mode 100644 index 0000000..6cddd28 --- /dev/null +++ b/functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/basic.py @@ -0,0 +1,5 @@ +def add(a, b): + return a + b + +def sub(a, b): + return a - b diff --git a/functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/basic.pyc b/functional_tests/doc_tests/test_selector_plugin/support/mypackage/math/basic.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f434d6c01b1210288cd3f64d1fe09f84bef52bf GIT binary patch literal 433 zcmaJ-u};J=40Y}p0Erd&gif^%v4g~ZLvCZK+%y~&Z4)&v(g{XBg74%Du+sxN5hc&h zcH;QC|GGGT`@G#cI4&dhUQW6cXaGI{9)*}p3^Xg)6>CYkmB7z=&ymkuaKT+39Q40|hbdxnmiKQ)+OOb+U({wEgf(5h`iorINrdA4K+@_PTWwX2PZVFKm zZ+O2x-uwS*i$Ls0Qb8XEXm74$%`+ScQeF+BX|RJ(73>CyS1#lX&GmE!mX6^No)B4j zl6Vc^YSfzNME-Uh5#&F`u+ZfEChTkm9q@fE7@yB$3fk~Al^rQ4g5#84Q3uuZs)-Gp z)pXJv?DO;0bqSI40{{JwpI<(+MH0x7MRl(GEaMhKAK{Pb1~=)R z9<@y3p)52!#S22rbib{eGaTAjVWZS9-GIxx-jf*K#ybk$!n+Jx=mIiwYgNRMO{9aw z_`hBZLU1iY1Rsb7?aw|6ki%qUjSU%}F!a>>QL>2;M;)0=&v3^j4v5h3qGpu2L{A>N zZMy^QC#H3J>x2O|bkqIi2AMsWgbmLg8DEpS1z8MR$LSH1DVOHB&DG7n%6vDrWAYJ3i}=mnG>wu1pLh z<2gx^mXMJcleV>C75Tg1VRD8RrFbzr5Q^(rGCL9S%|gBzc|vAKf1?9LT-01@$F|WZ zVht^U3kQKH43uiCJ>-&DZNG=k*|@m^dl}9uZuGC>jVe|YH}Pi8I_}#Nd-2XyyjMkI zxQ+K~>$htR9_$}Tb^q!ghMwx+@9d)au4R09XBi*gVO6@mr*9MF#V&+obTU~C-$0^y lZQ8ThLhoh^E%<>zTW}FS(%4S3&*=Ei)A#~kipe>A^)J~wM^*p; literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_selector_plugin/support/mypackage/strings.py b/functional_tests/doc_tests/test_selector_plugin/support/mypackage/strings.py new file mode 100644 index 0000000..8ffc4cc --- /dev/null +++ b/functional_tests/doc_tests/test_selector_plugin/support/mypackage/strings.py @@ -0,0 +1,2 @@ +def cat(a, b): + return "%s%s" % (a, b) diff --git a/functional_tests/doc_tests/test_selector_plugin/support/mypackage/strings.pyc b/functional_tests/doc_tests/test_selector_plugin/support/mypackage/strings.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e18782f30afb790bb7dbc14a1366d711613be532 GIT binary patch literal 324 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

f(;OuvF@%IJ%0e_T}efZOV0O5ZM A-T(jq literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_selector_plugin/support/tests/strings/cat.py b/functional_tests/doc_tests/test_selector_plugin/support/tests/strings/cat.py new file mode 100644 index 0000000..3b410e8 --- /dev/null +++ b/functional_tests/doc_tests/test_selector_plugin/support/tests/strings/cat.py @@ -0,0 +1,12 @@ +import testlib +from mypackage import strings + +class StringsCat(testlib.Base): + + def test_cat(self): + self.assertEqual(strings.cat('one', 'two'), 'onetwo') + + +def test_helper_function(): + raise Exception( + "This test helper function should not be collected") diff --git a/functional_tests/doc_tests/test_selector_plugin/support/tests/strings/cat.pyc b/functional_tests/doc_tests/test_selector_plugin/support/tests/strings/cat.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80be4e79d5280fc9d8ddc5b1ec82bcd37e94e86c GIT binary patch literal 830 zcmZuv!H&}~5FMx8!Y(QTi3_(Jb81w?5ec-_UVBKn(2M0w+%~Ekhj_xWVo#Kh;5+vV zcAnD$2O`aQCLTY(dE>+5^yR^V&Q|hifB)?mO#;7kxOWukg5{0MD>U^ zimDO)B03U%S&yk2BUxYI+$y^efMuqD^$d=?y4eQ4l6W=53&VfzMcp;ZUW;Qv5o{IV z5-L_%B7c`9ieO{0g;+?%JqQ;?oPd!W5q8G}yu_DKkzy2(S|w!Y(hC_YJ;3xipE&(Gz;cuI0~6Ce_) z3?$>G){Qw>t)u2zAAHq2NsCJ<_)9!S1;7fisBJ`=Bqefu0A7fNSK?$iBJ)wvPZ1k~ zjABxD?0oO4wDFwY+O+fzecCDt$>4MIy|gW)`5!TUgL@&+ zfUD2AcueQEAwe7#3SE60VGsAMDfeb;i@1%EUCTIPr^QsVIQ$q5|4>%E#D>`5qBe|o O_Yp3j0MdQ%L>CxGZXJhyE^yYbI-lI|NQ&s-v9>jGefM< zeVAc7HSW0cTB+hMIJTB^T&_)3#$2Pwhnf~<7F=%Rv%cZ;p`NDIsmj^8%RJ`~G3?p^ zQbLSD^0;rAHfM;ZGCi{jHlbBU91=sKY@5E%JwJgqhVAQ7%wkExW`;JJIIe$IV4O;g zUp5vE%`)tHE$h3cJ>TqjBcgjatzZitkfC5Jxgk7sa)!mA-5-1yxrk;PR1wW~hR*f= zHyDzzgJ_uNzD+bU3`E$tr>DEnDNO7pPiKg%QN!a2>}A-j>jY}}zN_mB9u&KF;vwSs zWUwR2&?;u#gAB5+7oB|B;<}!|0Rqx>;f#2CgjPikEOV~ARFQCqp+%=p_C#bnBwRrc zdfSjfnw%gP3VJ}F$FMJT&q(?)AR^I-tO)c`C(nlzJd8nseS|@6W>mtXB#5GMg^yZ> zM;5O~uz4v0JWN@{N?8X|NC~Y!k8v4iFhM?e+$u0A<15Oc z%$>!Q$lMD;LDXv&w32HsQuomOVo9?dkLxbCI7w0;92gupq7_0rhNTM}Ps=;Ga3-F0 zGC~c;arKf_o;PjHE0;tg(CV8*(*vbS0yB~2SC^{~hO&XS!4164rlk}IsD-C_DYFiRsr8*q0 zm*rCl%u&W|epRTE;g?iqwPG5LZbdO0zR6s?7$Mj-86Aqkjxd+WL=|&brFn3n{DJ~j zuh)fokDD+|o5UH}2ZB#YJjGzo&NW352Glc~z#*Zuj7&;$6kNs?F?)?L*9%p^jXOr3 zyAr4q?8wn^naMG&@T3mvlu))O#QnJ(M5DAHx0CA`JZL-=E+#YKxB zb!_6HEXrt_7eqZ#VJ~lIII_Ae1U)t4tY7Pif_Lz)jJNR~!xlO=Exl354B1pBN`C+A zf*~Zp0zcCdf4?5n zvuaoW5{@pRuZH6%;%T*OH+|p1PQ?3J4Ns?A{=n!GPTeA8hVSWDCW~SY#ORl@6xtsq z`-0$PTJ27&2gwG(q!VP8u+OL4g#+m-4zEWNKDQDEHG&XHryH9<%J$yDWbbWEtLJLC zbQ>?LdNB6IVs~Lo^rh8YEEZeBbd0gswszDoe;elVVpnUh7+R4^Kfj7h5YJf!HsHqy>ucI;C z#0TN}O`<4H77q6I_3!;HFotA(2)^v3IpyV}TUC5=i`9wxf!Mw?w%%qJ8GA zMfW!Q_#_WH;s#@S z+c{%sk%}%hQ;_m7l})UTDL920wBu3_qkpmFG;MmhF%Vl|~+o`X~$KD2$ zY_&}K$%F=3T~i#*5T+;JYn+B5j-+Qv@Y`~Dt;Aavf;r \ No newline at end of file diff --git a/functional_tests/doc_tests/test_xunit_plugin/support/test_skip$py.class b/functional_tests/doc_tests/test_xunit_plugin/support/test_skip$py.class new file mode 100644 index 0000000000000000000000000000000000000000..7dc7e85e37b2b10b441655ff1565a4373e10866f GIT binary patch literal 4186 zcmbtW3wIkw75>Juy|TJ?6*bMnq12_O$d)a|PC_3Jfh3JV#db|>40*t^mPd|aX;-XP zNn9x9RbJ)MmRDOIEpI5V)M*{lkhZ)J{F43(4&ThKbmV9iI0v6Io>|>{XYT#(H}}rJ z{{8A}04DGcfmm^Pzre}#jO(rDs_Xta*U1%JPv+*=r#-tOr`iOI=R9ezEcmuBr$*YO z^Xm^UpO*!HN?><~Ee*s3jGFYzB}WQ$r}HCACVJ4DKsOA5xZ~DjR$eN^(I=2RZ(p!; zW!qWJE%;u^Sv7DAbr#l2)kRtJ78>OX<}3v4I)GLwNZ`v8?G{-ae+M%ryX}! zk)i4V;fuhqH5x<9KLAo78Stoxp>5uRwprUXx8Y1nzG?uUR0XaA=w` zygVLCkTLH!abG}!S%%WUJ&uP3ZqZ*wyHp-NK-?{BMK0G@Eh~0hc`1^KjjH_!uYQq* zIPdt_m1lR}5>OUA9z2wcLtZ2v<77OLdkRt6HwFmYiIzURC`g*Ni<|U612& z6Q2*V@I_McaK&Azm*tT-zRW7&$cw6U2|S<9M>UtW4%FU&@~2NnkZN0|rlR(aI4+-$ zB4JotpTJiY|Gs8|eM;r>8}y&WYt}9~EIsLtrX)p0>+DKLoH?=-$GEDTPJ(-W$HY^3 zTCJXmXhF)88P{Hso`L5`+yb8#zFc6}F3V%C>(_kGu68IRI*N85)z3{+^7Q-oK?2{y z4;hM;MZC+ldt&03_*DYGz>BQe zx;Zqig(`OWDEj?hk3mJTE=7Xhsg|x+msMTYs|{5E_3C9+)bwgIf-Ljv4Xg}o2@Gxb zvQi~nUhe38eo1;YB1E`*F4*O|jPb2I$ahkt{_J9jkK7(WOgvh9ffa1ujVWqrkOtl$ z_Z5@^x6bhOI#;hOOHUV1Rsz0>3v08Au!#k??iHlUpuk?b?emnSQmw>9A9YwSZI#0s zpHf*?S%E(wfjz3E($zhx#Os+&NzZKbdS=nlGq0#T)3l~AYa6^v|P>@s5z z_zVB<((wqcRDyds`ZyHR$gnW#d$>~4fsDZuGHrYGQV)D@3B|?GCPxcpvpbX2MyUrj zab#=*$2Rf4EzG=5QQ$yeZ19Wp4UJbq<3pKbLYjeJF!*+1%F~kgH4Rjm$lRlrl)JCu1VrqR0k6l42^%*^%O?^(!$5LO=^Os^V@@94{lln?57TdtZm=LiY zNo?WkSMX$0cyF&3c59)~!ilDEJ{d@RTBLJLsZ921+ujywp((8-O)WKAq-!q`5tS46 znJ}y*XPwv$JgEqysPRpFE0hi~WF_5s4cSwvZ(qfCH}UKio*&)9PlCPr@KgLO)J|z# z5-gS7!moqf1b&0xhVnN_pKk7UWOQtN-;3%jkW9g!`hA!q%`wKYpX1;ret&rrFL7Mq zc!lHYWf9O?rTZCDIjs_%6=A+xQa{#7u)mYwU?;(SodkzF3A*q%`qzcW@psNUxq6it PT+8E+xULot;kEw&U+qrx literal 0 HcmV?d00001 diff --git a/functional_tests/doc_tests/test_xunit_plugin/support/test_skip.py b/functional_tests/doc_tests/test_xunit_plugin/support/test_skip.py new file mode 100644 index 0000000..cb26c41 --- /dev/null +++ b/functional_tests/doc_tests/test_xunit_plugin/support/test_skip.py @@ -0,0 +1,13 @@ +from nose.exc import SkipTest + +def test_ok(): + pass + +def test_err(): + raise Exception("oh no") + +def test_fail(): + assert False, "bye" + +def test_skip(): + raise SkipTest("not me") diff --git a/functional_tests/doc_tests/test_xunit_plugin/support/test_skip.pyc b/functional_tests/doc_tests/test_xunit_plugin/support/test_skip.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2cf730bbb61efda5d15c1205eac6a18e1d5ad484 GIT binary patch literal 766 zcmaJ`fzGkRnT-dlaUZ+l1 zI-_+M4Wr9=*3>zcrp~GqiO0u<$$t_1>9VOyOl{RXmv!tLYmLKLBA%p0c7?~pA{%Dj zBIvmXpf-Zp6~X5OkTfg!Q1GtS;yF6@9kS+SP#Y-*yX20Rxwb59r>KNtfZQ74)Xs6k z_L5$3B7p6ILZT$%fM&sM6J}(^Pb)1RvW$nU@*2+w-FdGaD;POv+`lqdsK}UQWyQ{3 z9S4eM{k6ftLa(dm+Wy9Vn3X4Rx@8^9-I3n#pd-D^6Fw~AJCksw3C}8Fp&{40-jkF~ VK$5q#>vr=2H*Dli_JeMV-ya<#d`>> import os +>>> from nose.plugins.xunit import Xunit +>>> from nose.plugins.skip import SkipTest, Skip +>>> support = os.path.join(os.path.dirname(__file__), 'support') +>>> outfile = os.path.join(support, 'nosetests.xml') +>>> from nose.plugins.plugintest import run_buffered as run +>>> argv = [__file__, '-v', '--with-xunit', support, +... '--xunit-file=%s' % outfile] +>>> run(argv=argv, plugins=[Xunit(), Skip()]) # doctest: +ELLIPSIS +test_skip.test_ok ... ok +test_skip.test_err ... ERROR +test_skip.test_fail ... FAIL +test_skip.test_skip ... SKIP: not me + +====================================================================== +ERROR: test_skip.test_err +---------------------------------------------------------------------- +Traceback (most recent call last): +... +Exception: oh no + +====================================================================== +FAIL: test_skip.test_fail +---------------------------------------------------------------------- +Traceback (most recent call last): +... +AssertionError: bye + +---------------------------------------------------------------------- +XML: ...nosetests.xml +---------------------------------------------------------------------- +Ran 4 tests in ...s + +FAILED (SKIP=1, errors=1, failures=1) + +>>> open(outfile, 'r').read() # doctest: +ELLIPSIS +'.........' diff --git a/functional_tests/support/att/test_attr$py.class b/functional_tests/support/att/test_attr$py.class new file mode 100644 index 0000000000000000000000000000000000000000..ab941f676d6b865574143ab3db3a8165ce5aad60 GIT binary patch literal 10453 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

*#3UIga4lWPkjI&>oD0QDY@VYQ90y zDQZ27>}CEu{vJY=!yHfps0Gvm{D2_90L%f*1(<*kpaHM|uo@5rv;npOb^|&AF~A^T zKj0AHHb}=Us;dOP7uJwm19Kw*(bJ_*z0#!)f8)NjdDhuHS4GU(RhVXj30Je5X%|ABBcX{%=(swyMGn3-sxkb6hC3mlgsV0ZRbO0Lx(=Iuo}`7D2q1 z^A~{$O^lmqX%(iWU_!s+W?E5&X$6?j8dZ1x8s!P`kW@P5{IFUoasHQV=X}|rS?8uX zWz#(F(5zQ9Uxnz$#rcKe9dYxXwt1iQcK#YTJPcR|SPzK6I<$Lkm#l?&FXyiX6FNgT zQ&SbDCNQCibTe(N!n6@g=r?VqlpXnfrIEkc?TIPd6XzW-w#p`<3-BNud0rej4U|-y zk_ZiAFT%fd5Vc}w^tWzxTdJtr0w%P=Hd7R7DrKaxRNXBipk!BFPqu&=-LuU+ZQJ^A zDf4!>t>3b3eaW%4z1Y^5#Ma-lZ8gz~lxO=ZHv6j%`}KjK9g9(p4H&b5~ramy?UFc@&ufo(1^1?%adh8?ET6zQrAfU9d=UVNg2ClcgU6KGg zbq_!`9;I%D4pvd*;)zIPd;bGLh5v|!qpU*eN zjXq}IJd>t*^EqyHu%VrPg{)Ob79?F;Kqkzf!qydG;{|NmG!?eK2%9Qkv*rSY_0Mce zCu`-k*1gf!z}NJnG~I1BDw;r%ggeVesf_4xbDbs%77?8)BYM(|XrkFgM5hbdoH3&sjSnKuiO|z! zGR~WuHPM_R8B+xrm&`Veo?9g2n+5a><~EJKstEl?*%mLE+cl9{w8gi|h+Z{!YNB~X zM6VP!x@_*&=upu{Z1V4Mj5EEXcTG#x#0rg`^< zY3|g*X7Nq@0h>Iei1ES-wM}!6^zO1*FQewl7Zl%fxbfxn@!bs@o0mSR(DaTTR7Za| z=Jx?k03HOK1Uv+I81N|IF~BLn6M)lzrvS3cdB8N_65x5ji-4B_uK->HybibwcoXmz z;Oz^%@9YI001N^?0yqG;1#m0iHoy>I1TYFX0yqjt0n&hRKo*b(90S||xD#+U;A4RM z03QdO06cJ(MMLH<-~sv^-YoY~tskG_{22Hl@?maIPk137^g^8ULOkS!c-RZ^s2Acf zFT^P?#1meK(_Vs literal 0 HcmV?d00001 diff --git a/functional_tests/support/att/test_attr.py b/functional_tests/support/att/test_attr.py new file mode 100644 index 0000000..dd2f292 --- /dev/null +++ b/functional_tests/support/att/test_attr.py @@ -0,0 +1,96 @@ +from nose.plugins.attrib import attr +import unittest + +def test_one(): + pass +test_one.a = 1 +test_one.d = [1, 2] + + +def test_two(): + pass +test_two.a = 1 +test_two.c = 20 +test_two.d = [2, 3] + +def test_three(): + pass +test_three.b = 1 +test_three.d = [1, 3] + +class TestClass: + a = 1 + def test_class_one(self): + pass + + def test_class_two(self): + pass + test_class_two.b = 2 + + def test_class_three(self): + pass + + +class Something(unittest.TestCase): + b = 2 + def test_case_one(self): + pass + + def test_case_two(self): + pass + test_case_two.c = 50 + + def test_case_three(self): + pass + + +class Superclass: + def test_method(self): + pass + test_method.from_super = True + +class TestSubclass(Superclass): + pass + + +class Static: + def test_with_static(self): + pass + test_with_static.with_static = True + + def static(self): + pass + static = staticmethod(static) + + +class TestClassAndMethodAttrs(unittest.TestCase): + def test_method(self): + pass + test_method.meth_attr = 'method' +TestClassAndMethodAttrs.cls_attr = 'class' + + +class TestAttrClass: + from_super = True + + def ends_with_test(self): + pass + + def test_one(self): + pass + + def test_two(self): + pass + test_two.from_super = False + +TestAttrClass = attr('a')(TestAttrClass) + + +class TestAttrSubClass(TestAttrClass): + def test_sub_three(self): + pass + +def added_later_test(self): + pass + +TestAttrSubClass.added_later_test = added_later_test diff --git a/functional_tests/support/att/test_attr.pyc b/functional_tests/support/att/test_attr.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61806b7ee9ae52e3b6a08d654a5a82af84a19da4 GIT binary patch literal 3713 zcmb_e+in{-5S`V{l5P1SDNdXgMcSr_fB;exy8n%OP2#3wAP zYFSmws)RLll^Wm^1~s|BKDKH0UgULuTkMXwg2X#=5s2N*26n~P)pjrIs*Bwhhh7*S zWW9T057n@db?u97E;(&w0|#R7i93++Q0$RTx}S|T#KG?ac^`;tu*%@g#BK%neUA)VVQGZ!}}Qm zJQ+qwk}beMUeW3~8V_pZ0^NRl` z`4a6dj#E@&IF3dx3_ZCD!_mafhy3KSks+W)ZTWp}I3tfm-xMrE5bxnh=%x}>f?Baw zs1=mw-elzbRXo1jFwZLc^KcmF>Hk5o36p|liYPoeXl8@{To|Xf}iFq zD8YtVX2^)yWkW?9ok!FLh9X(XCingsp4)8FRc}6ZGfj!RbW+4w+I~QuYVAQ?Ks7>o z64%o;!`|T%%t~r%V!y=aHD7He+EU0(T2hy1v$<1wU(6<>5J#+=t&F+>+b$kGDxlzk zCu(O{tT#Wu88PvDMbj#pG-AZZ-cyY--^R+7j$rtL(}_qW@JzFwk9<71iz#>XSD0Sc z{4&e9D{HkY6X+u5tP6B@g1n3URha1RzJ}k;WCtToeSVYsye--X7&0w1o~$qJ!yL1d z?4&+YEv$r^XZBY#BvT|+k0th!SS>#<$Mz=`(@PZF6~$QA0d1Kzl6|3L%-r7{y5>phe zVNEojvfQ#_gYJ{l%&C3BB3%Ddy@>@A>@qLvyx_?VOn>ToFCWREK-=ZAQ?b!2Sl z^v&)>$^1Ojn$zc28I}Zk>?{HB7@aFa{2T+X@#wrtux3RdCXuzyhQr9anck$|EWBe^ zq`))5cmeNBhx5yLoOF1xj?c9|@Kx_Z^KtAcxRfgeoS0gr%@9??lQk`-)Vj_hA1Amz z80M8>5iva94bK?EJXEW}Fwtx^kJwEF%ok|dT9Z{9ydP+vA!w;1E|SjBh1I1lL%*pP zU5f&@~}skwbzsui2{M)3&0e*&wR B&ZYnW literal 0 HcmV?d00001 diff --git a/functional_tests/support/ctx/mod_import_skip$py.class b/functional_tests/support/ctx/mod_import_skip$py.class new file mode 100644 index 0000000000000000000000000000000000000000..d4efb252a17375a3a087efe10ab1a82c5b0023e0 GIT binary patch literal 3835 zcmb_e`&S!96#j+)o1|$ADQzijY1*O<1V}-vr=?aYg_;T!38H=Ix+DVxHoI|mLrZ;E z>)ZN%v_4v2RjWr4Y|7E2{lVY-qdeZ(4H3wOqo?Ma&CKqdxsUID_s;zJ*Dt>VXv1{| z&uGJLhQV;JZj41U)7H4IMH9NgqXW}DhLYwTMS`JmgDc6nrC7Y9u}C^FeRO1kC#(*J zjU`k{@GwXwx73uz8Op=4#^C@eQ0YfGB>JG~CilX}P&J`UDN$9?#-ee{NNHmdRxx6k?4hTx9uB1E@yO zk1DJq=R{n3LQxr_E;dUm5g4q;27$pwf@h3d8o|%d>Y`-@*aO&vI-z7ULolr;Wd{Y> zB+MEz(-O8al*w%jbuO+rAa?|?4Iv-uv7MoIIlK!Amavnem1PQ5m1Tx_F@+X0#l>14 zy%mGG1-JUqfL#18CIv2N#1=r!82A$*BJH|`?27%D_sj)DeMTE{b)i9L!djI zvGP)nY`{zdI5lK(B8OEEqoFuspCJ}Rq{rBq{58!?QK@$dvgD%#(^dzT@?ZuEF>H~z+{*;!o4lqotK!6p}SjvXW_XXlo z#Xg9K{J0&*$ZL@&FHTTgV%FY3_&)4-_Xr_xfAiu|hV^a*JT{k(#2u9ktS#sOYk?!Bwu%*w)(C+8!Ov zb5&7g;jbArvl-DtqY3MDbeV#oKkHPRG z12X{C;cAwlIzFyv)nrK1tNlxVkh6vaM@&xNf-&+~Z0kJs@g#YS0kV%t+(y-T;bh84iuct;@i zE?pX^F)rD{u-yf)B}?eGva`7S2D6$bDzD(Pgbx`SNqGkm(lx>%o3M0akdLZ# z@1YJ_SkBPCxYoO<+nFwB&{9tVxQLJZxPXu8Y_kv7g{l-CIp#*co4Th7ij@fxTo&Eb zo_#Jlmp%K^g%|_V3%E$Q!Vp^OQ947=7}3sw*f2LtLW7{Km{Qa%_t2HIj*e2-^?gGr zx@%SdtnEqj@@cx({Zox}jnGdy9q3~PJocG;gzk8s1TOKJyn zXH87@3D)}Kde%sAkw%716puybhf`*XvfZgsV^KsRO>8Gzk_6D#kWeeyDaBALT9G}{ z@oA5ATe3&0RePjE$sVcw)99uB1Tj6c^!5jxaTF%bN6K^X2L0u~A<*CA39gB^M9St- zL-Y21<&of;O(d^nG_9m{^q#|(=2`5>Av}-P8>Hd?{0V(!n4|w7`A1oxN90R*>c!9W zzly#cppi-Vk*36yd_YNBD3Lrv*_(_-*j4Zb3v$44ui@wqpsZHiBp2KQ8#_6W( z7;BotWKf%jHHWF-X?r{zJY$bLJRVACUvnh*gvaBV#iWNZPjwaM@yr~~7KA6gwy@k5 zGFv!Y5GqxUv|^D|77z#!s9)z@s-jV3seBe^1&V+V=kP*a8X|XvwB}dT9}B*C9WUqb z>O9_Rn#X&NE+5{<2l>@Odo55Ite?jxjy8r*aVamqLYxq+Xl-h4*?d*l0>aKfNddOd z6QZXfhtFnm_=29VW|+{!Y;;FycSvNip4ek2bWv^_N(sWH1kI%cWw=H*%aBHn#u}P^ QLmqq^!&Q7ICP(o7-`e2zr~m)} literal 0 HcmV?d00001 diff --git a/functional_tests/support/ctx/mod_import_skip.py b/functional_tests/support/ctx/mod_import_skip.py new file mode 100644 index 0000000..3a5af10 --- /dev/null +++ b/functional_tests/support/ctx/mod_import_skip.py @@ -0,0 +1,9 @@ +from nose import SkipTest + +raise SkipTest("Don't run me") + +def test(): + assert False, "Should not be run" + +def test2(): + assert False, "Should not be run" diff --git a/functional_tests/support/ctx/mod_import_skip.pyc b/functional_tests/support/ctx/mod_import_skip.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90721fa9c033bc037d290410abfd2400393da8ca GIT binary patch literal 562 zcmb_ZJx{|h5IrXam8wNzV9!RTSgqIr@i7J|Vd@fv#E7U#Y}pqCVk`UzekZ@6cW$9# zCz7Av`Of#=#ohO8^8R(6RQIidJ;lC zf;>K;JF(!~V}g9umcSI#MmjqY!))i$OR*aO~ED2;7iN>ma%BYW*sVO6V2 z+ULnUS;(iMued?k7RctM8;sEwWreS@({<5w92SnIOUxUNs3#Y)565mn{O5cbcg*;o z?&t6WeW&JNY#Bo)~yOPl)0ynObMKw+e)rD&{m<`z0_emQhEO4~ zmSG^-W7#9=@~C^*GSfNB=IQ>?Zd)($t}4O4!#3CRgRbuKuGT7P|LETA5uS6q7?#v9 zsUXClINUXgCTFNmW?F|-G@vnxdMFIB>U|0#4C<~@+4apk49y2?J6ACWY6MZtWmsNq zuDfF9+@fVFn9mR?Xd{M|)eW+_d^VE{5Q|WRw|K!<%85?etZ8|kt&C_|7>nuW2zNbW z3?0dyBlM9 z+=i7=tU#Qhp_@*^!?>M*ELnMtaITcKf?hP7M0r#}f}u{M$b*jRN2%ft+!;X&k__{< zI}W!6Si5Z7vcqU&m|N11@?9r#yez&m3>Y z9|7Ne34RY@P+p|4wy}NOiA@`@J;nr8Iem3ge;4 z17;KJ#R1o=*ykPaVHJa({v(t6*)R?;ED8X#b%UuG!q+I?bIOogM4a1;6#^ zh9(Z^q@7B+Y}sx)=blL4;Gv^D8V0!zXa`=UDmYASv0z)JjOlXQjMGu#KG9uX3_|`B^P?ekfxF}YJ^U2J?>fU)XUoykP0c@R-jx0r~e z$I|oMR`4pNB}cci%Lj|4vcY#)mg~5-PU#9POL>aw7{%wQt;Fru@kSJ{;Y~UlW#2p7 z#_BSkxh?QmD&EFB0t&~95)PG|YuN+5V9>jSF03hahIP{$#ZJ0poPaBOW+W;;#wSsHgfnyz%H}#*(xS&^ zg6Q|Z-cf>Lb%F$+i%U$d&Wk=NS6_$@C|6$vP^5o!5+McOFvMrZRw@%nc6xh%W{BGk z;X)uc9Mg>o57Fb0ULnna`n~&#^eSusSm!C{0@ZEvjft6dttj}37%u>1nBPOsy}n8* z%Wc_gss8B^nLFAmXc`@~Dt3;GyfG}JuwC*pRCJ1z-t8v!A6;b5!72jc5Fzp{BJ^IO z=1ZY26^%>IbT7!6E4NV79SFmu+7&f!3xVJAA+qPyLJ6T^UT&>dYs|@%mO_6Gy{l3nyuT%FM){kM+ zC34Fk{?CKR0i!`c%K$0$et(F|?*)Df+2R zAbmYG%|o5^&?v3f;a9S>fYvF56tpO)SMW1^VuE45!?f>kNvf$)dK6~c$FO@G4^E)( zx>OfYdp5;?B4zdF==9A)!JDJBjoP2T`6A}Zqn>NKipSe7BOe=?KH5B>-$9vmTvC&W{G=_YLvCx7hOyJaIoSqVHtCzxhDP&T(c}h4CmcoWdP43kU^b)U5run`jovs~^K@0gOoDb9mmD#_3E#y6_rW4#Zx#ikHUm$^_nO zo4|XXtq8I317ABPbqU;9%LLAP))+p;XTJO|VyocmrndGq%@>6)&=cAF6Eum_NYF^q zXdlP9OXIje<4YP}Ut+=@lO#AzR7;5Tw=kat(+H8QC1|fDSX)c5p_ZTyV-!ps3b;)3 TLRx)Cp?#mh54b26d+_7G@k-=+ literal 0 HcmV?d00001 diff --git a/functional_tests/support/ctx/mod_setup_fails.py b/functional_tests/support/ctx/mod_setup_fails.py new file mode 100644 index 0000000..d7f49bc --- /dev/null +++ b/functional_tests/support/ctx/mod_setup_fails.py @@ -0,0 +1,12 @@ +def setup(): + assert False, "Failure in mod setup" + + +def test_a(): + raise AssertionError("test_a should not run") + + +def test_b(): + raise AssertionError("test_b should not run") + + diff --git a/functional_tests/support/ctx/mod_setup_fails.pyc b/functional_tests/support/ctx/mod_setup_fails.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3638b826f303429b5d0bf5784a31b979c1c61ac GIT binary patch literal 613 zcmbtRO-sZu5S?_Z&;`YVp3SkRh86EBqQ?r|o0rmUvsh@8kW5AKrv3wa)oU84d}rn|_$Qmc5aq?PSn6xtPYCm>npxPu4{U+GB6cmG{=? z6oDD=hFs-*f8U^1I67r)(6!ftZs=SUtKvj$C)09+W~fmI3@S{{S&yp42Swa<Jw!M~U{e`MZdGjo|Q$f+XmJ6Al_j~KfZCuT99I#}RYTpnk$G)q4Kb@^!J literal 0 HcmV?d00001 diff --git a/functional_tests/support/ctx/mod_setup_skip$py.class b/functional_tests/support/ctx/mod_setup_skip$py.class new file mode 100644 index 0000000000000000000000000000000000000000..6cc946d2eb6ce760a44cf42e58ccb6921a6eaa2f GIT binary patch literal 3763 zcmbtW=~o+76#qREm=LBdO*cx_rWQ32U6K5tA)VA*X zzAx3*UF%jsDaRg{Z=T~HGysbG*&R7#ugbIx=9ZHgBue@ z2Fc>Kmen~!IFYI!l2ML|D8i8FgKk(nf=Y(!33WbzxDhvr zkP{5+bH+%TvRBBbEh=zbo+55wC`;23K|Z;NXp_;1rb;By%uwSG(=Cw%0(m5zrn+e9 zG{b-&?@M7_Is>c5(u&Z&~qYjWx&@0`x?yht&_e*byJMhP-mRHg9t;pQk+@fHd% zz^@Ktgp<$>I3RLBT~~21t7q*)3=^xIxe9Dboe}WegTFxTtvD1#9Jeu)J0*;uof2+S zlcv?WR))3hmc>nxj84-u%n0rfm2H*Lq0(#UT*h&M-&&kt*c61+A%^XK3@x_G zpgOE^3HQ^~Ot?X(qj&%hl68w~qcn@UeHfXTLk4^cLqk`2eLaigG(#C5}tn*}`rnkOzx z^|1<6+=CQOLF0!aI8CL~`Dp=Xh6{;Q&~WxmaQ^^Loj4Hy)MW*13$h(>Tq+gRnO?um z;9=2}kH}yy49}4NGzWXsEVX-WqNFNeCFjU&P6V7;KNLZO=%!OqLd;V#&fzh!dnRCn z3s1TYb%dJ|o})Bp=vub<0Nr{TKWrGbWt(cg#OQIN@cSq}7gY(lFW|)}p2tfR8&%Vd zZLfcJPt=(_01qR$fEVyGwkU0IFm z^nB?sbjm{^V;g2aAJyn#LL;as&d|Da3UtuqumavR%RQ0t4&II8ZM;Ww-nmc~8&=#a zsUZ0MuZNU?SeXF9c`@dl-N#~DI=hQv4m!I}10d2jvzQ?XpEJal`Y2-3|flAbf z(Mqn?h`HoYdI~s{ZV89d?cz{sQiswt=}@|B97=Zx(MqyMIi~dk{reLY^cV%sC&TlI z#G>KfAon(fVrvJQl4T3nNc+|U;bd&>R^rz%+E$R8z@>;X|UiB=W;Bb<2A zG}Cv%*_;#}kk|95Z=8iPkG%`n{|9kgI~9(NF#Qs;JYB`p-IA=1ILe)_@{wTW2&oYm z{mSxE<-TBLfpQvqp7OU?<9L0c;R@7-Iq=x{0`xiLW2QrunC;NM*px$$ghEuMp2lQs zIur`cf`=Fjt*^!c9-YJ4BJ*&C!wfr2<}eQyncdZr!!2LJJzC@rR#!RPiY46spD2AI zfi$H($E%5oVuWXLRuoBq>v25capM$};BLH%(SMc;ao?XDp4GVbPwN;5X@TMmn zb7X;vSaJdHyVe*!z=t0HFDkt#{=SCBrfrwRS->urILV9CzK&LcR^vQAx;&2yv_7Hr z*=6QJNc^57xYr>pA;}~`zDJ1m-ZoK+(O8PHs}y5TDMlGY@MRdoEYXd$`;tQZDuu7{ Kjo2K;rGEkaTjbsV literal 0 HcmV?d00001 diff --git a/functional_tests/support/ctx/mod_setup_skip.py b/functional_tests/support/ctx/mod_setup_skip.py new file mode 100644 index 0000000..6e5ec65 --- /dev/null +++ b/functional_tests/support/ctx/mod_setup_skip.py @@ -0,0 +1,14 @@ +from nose import SkipTest + +def setup(): + raise SkipTest("no thanks") + + +def test_a(): + raise AssertionError("test_a should not run") + + +def test_b(): + raise AssertionError("test_b should not run") + + diff --git a/functional_tests/support/ctx/mod_setup_skip.pyc b/functional_tests/support/ctx/mod_setup_skip.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e634758e162a2c256a4987a185481fc9236cd50 GIT binary patch literal 646 zcmbtSO-sW-5S>lqN0o{f?>XimT`JyH#A^;oPrZb+yVBBhciEkY;!XV#{!V{^Gg~nV zB8Y*^yv#83-rLRQZFKPXdR{Ex<4GuA(i=`GD1bFUD_|x-C;=}3vjkKU!2r}Cf)rE= zRlY+5x!}=#3e|e$raIua#n;?Drhv3tz_WlS!6a4Av_%+FG&aL!W7Z+=!mqn>YC|8e zpik-i>aIa8apR=1LDyclx}iK7PsV5RwlfP{S<|#NlAItz=bZH@7kDo>R@Fgc=juRm z$L;~yM63)LuqqILvT#BZOjI{nSlX^tnX#Dp&TQ=*QTr+c?OD@}_ty7*A}aX=arbk} z9-)8fnE#}sWP86e&kuX%B3>@0RN*5oDMQJJQ*x<{Hy=^o;9BhVEL&^r?;`#tY;u$g H(_#7!L6&Wk literal 0 HcmV?d00001 diff --git a/functional_tests/support/dir1/mod$py.class b/functional_tests/support/dir1/mod$py.class new file mode 100644 index 0000000000000000000000000000000000000000..1cce5cc0e7bc9671ee4237cf917da39439b03d40 GIT binary patch literal 2114 zcmbtVZBrXn7(JH_Y=}!sQ%Wh60xi}AXtrQmt-&g#qN#x*LA2B{U6LERb+en<-Jtp4 zZ_&?wsxYODj{X3Dl;gQcqzzeSoG~-Wy}9>!InQ}s?q7fZ_9uXONCB-feN$i~mG}Hz zv(^Zod9GRVd}*#XR(z`}vt1XP&wOc>3!xRtY`W`qz42)0xh#cQfs3b@)S(IJfeann zC4yKgm)GhHu` zMPEA7;yRz1&n%dgHWVPKAG)_;+Qv zYS}I`oH|vNwNh=$45uP|rnlm%V9sMGL7R&P-oyK9$9f$-nNEcy?^$K(>-d=UTw-5_ zvS3$hj=b-AVG#OOP2g@+Z#Sdp#;A^uRkH@}V>y8(JYZ~o-EGBoyW3u;T4Y!TR`97} z>@(H^dFoIRn2I2*Lxd%C?IZ!~jg7kNs-8T?tRpXwru`M$kuK-gvgeYAdMWh$4OwwG znb^n2;sOhA*8MW)P7pEFI*}My!=nUNu`V!BvK*)QZ%YakQn{$|`(Kk(l~|7|g3X8` zc+G(h4zI~h@6{S<@Xe+5+?MnMMot=g_bjI_H9i2N0%MW#tD80_e=i{Be++*9fnq1A zf8!C*@hxjwQ6z9S&yl`XukJ{{IV;)Wd?-rat*Syy6ui1$k}5L-mzcVcUbgJOW&)R7 z*W-&qgaM`)>M&#e3K~Y#>?G6?HD#JB+l^}<@5u_kPxa`!KvO3(^9q+{4lt3tc7*f+W;N{(WLD>9lQ%U@+sA|^gm!Ka zN4Rx>JHJv>Sz=qZ?F@2N)?@p)qdbBy)?Hd}d6}dSbvyJs#vdoMukhg^K03lDGe^j^ zZ1v+IK5t7Wo3g~496!PrEo*$7QE1;pFU8F3&(I;hd?8wK@9_8* sc^N@W$GFhdQiR5Rdwcn`iyoAy+k>kpa~&qzP5G%vX%Ge0jeC%z=LxTILWxU`@kzoWbO_Q{-0!i5t+iE3N(`a-nq!=RF)RAuBl5V@~?(ANo zeDJsEXFt_snld{21N>2r=K`iBY-XI18Mv2w&&zY3dtUzf`?o&<t$gIo=*?mmf8<@pYfW=Gh95yqzZ*W4Y;r^ zhY&tBaTAt_nLa$qXPA9K=wJJlbR5w1iOG2dv>b-bvtXq%_Q9L57(jk0A={Zk-b^<0na1af66kZMyZA z&F{saQwBKvdnwNFQ_6Te{7nDl#Ms#h0Wt8B`7qld?hGR=&%5zxR7EQZ%B*)aFf7aP zZxjAKE{dk*&{L;QRi&h9c4a10AwJWUn9Q8>m`IS#RSoaqeYvB=oj&=T5=p@|>)coI zF(r153RLj2)%0wB-*rVGeA8pN8&>4qD26f0<73slhWl7gU=5#8Y<|n>#ddqx-k@4& zSQ<9)nPlv9$_3(SK!sr;gs=e-N}+2f2`IIz7{+R*ZCC%TO@?yH2ur{J)ly}O zMPw3eha|yk4pdMVn;-Pv^oRyuUn?0E?gtb((Kxbi+AXfoB$#HH36(EyTQmYj0G9hP z`1uDCJ4yW;(?G>{l+%(ThO-6g?VGLUF88}llPa7hM(ue~CSs!Ow)`5GFT-$&o=%XL ziWOM&fNPH9(r6)s0Tw9KNs9SvD43E>l%P(@F4JA9+-M!8dt!yYPw3xu+R1>rXc9qAD?y6%$oo|k#@A#W#s$LynI{uMqv z!bcr^n(4sk*^1)?jQSRT@X(C3++Lg@mjhSDhFUid7&%_bF zdck^e@6z!t;$;d^6|+p0OBD+3``gQ>T|`hL-3YFtPU{5iz9IjQ4D8^s+&sXy{{R#@ BHO~M5 literal 0 HcmV?d00001 diff --git a/functional_tests/support/dir1/pak/__init__.py b/functional_tests/support/dir1/pak/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/functional_tests/support/dir1/pak/__init__.py @@ -0,0 +1 @@ +pass diff --git a/functional_tests/support/dir1/pak/__init__.pyc b/functional_tests/support/dir1/pak/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9057c2f664b32734236ea798407c39164adf74d3 GIT binary patch literal 157 zcmd1(#LHDZ!^AI{0SXv_v;zH0`UORqWr-!J`X#vq z`g!@qsqsarIjM<2+EC9>&qzP5G%vX%Ge0jeC%z=LxTILWxU`@kzogB))cs@OJTWUYrdCqG=p5fvtCS^zrvd;s{ zvBhP|tm1JAT~^XUr1?68~lmR$=>$KEn_EH2{$(UuaJ&*P$k zNhAq_&#eZ7T5ydQTGOd7xNTxUIsTGyS;Z8lu^&w?jY-JZ3)3c8EJ&4@>oKJSr8^vO>xAz6ypV5&mt$ zzsrN7VcPV{sZ&)cZ<<}1$yA8XbR{N|<~$}8vbmz-1AHiUl)F-a)2_JDGMcp6Y)m=7UrK!j50+DQUR?ah{Li~2mmETh1XCj0BA#ck?kYmQAkv}%Fl zZSsaiU5aY@NStB$?Yv*39^{8KxIrW;Ht zNMa|ce`_Gf_?~iFP{eSyK<#~_)!gA;w{KE~)6l5BD2hZ>%1+Cxaq%(?m+0vN@=`H< zlOAx@wjG)-gwV%4g*r(we*+0g(LV`lQgoT_O65lDDBTk)^#7E;uG3BgbY?Mnh}dG} zcc}YFyrdHbgMNjy*fUXB^!t>)KEha6C!2YNOPK@ACa!gmKER?R{e^6CX)$qAlB9ji zN{mV8#?is81KjzIG=(K9%f6j)T7~uKKJEyQpy_p&toOXkkq>b@@h4^;C-SfG@gY9x z;PXrede2rI5AkJRI?&qzP5G%vX%Ge0jeC%z=LxTILWxU`@kzo%k6lk#~fo2}Ax$bmCRjm4V`Td9KM z?W%4&c;?t@&GEQeYHxT(ljnynwx4<2sFwpH;Q92>ZK?gR`kdE-Jj2CPRLYPTWS<9? zX%m8IN=sK1j9@f@C}g^7=$7FJCIizH#F2>8`8y2BVSO^jNWrEf^h}?QMZsB&$8iSd z7)Do3+YIh8Tu2q38+(Ro8TPJP4m{J|&8I62bHipfT6Qfk9edlTT3p5jqAevbpU1@n zCNV`Ad~P)ul!9x#Q2kbQ(QOk0iQ_LBmlaH7CXQKLVTkC&dKlt95Qayp>zR8*vKlm9 z)pmTYd)(p%t+{M2yP`JwpbSe7xF7gr)^!~(Q0u0bQ~Q=xT^H`^de&{naFts)7&jq1aIpPoE5@KvHmw zI`?FJN=aU$VhwoNY`PY|?>K=Uc!tYxH>}{hUJPRt$45`|3hrY)fi-+azIiRX=iBXJ zeS^QZ5ir15X$hLI@iWp%l7yl7Lcst7Y4wP>(RnC@`c+|AuLCo0{6X zV-pXpTHttFykSwhqT)UhXIObV@7Jjr`5~Qd5Q&0KJWQa762n-{uq^%G`eZ1lw6OI1 zU+q<-SVSbjc1RK&b0C8{+WesPrb{$<>RL&waL*^tiN=vV!)kGfrouGCOsITu+oW+Y z0pWkdXtK920P^xg69<>)mk%)=1)ADLuM26uK zg)Sf~71K8;$;d^8M90liw=qQ{q5z`E+VMW+X$|rPU{5iz9svQHSFMt*gU{@ F{{VrTIz|8h literal 0 HcmV?d00001 diff --git a/functional_tests/support/dir1/pak/sub/__init__.py b/functional_tests/support/dir1/pak/sub/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/functional_tests/support/dir1/pak/sub/__init__.py @@ -0,0 +1 @@ +pass diff --git a/functional_tests/support/dir1/pak/sub/__init__.pyc b/functional_tests/support/dir1/pak/sub/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b550b6c6eea1e5d610f5d04587f5e205463fa6e GIT binary patch literal 161 zcmZ9FK?=e!5Je-n61+!O86xiW1O@20U@4{!6A&wlrM zAN$e$vavl|Woww68@gut&fxx_7jwXs9#IqM!jOw3tZ7csHu&HVa9i`JRC1hAlc<2K b%%v(!R0D9lo45iZWl}`XPi=pv2C=#?SG6WM literal 0 HcmV?d00001 diff --git a/functional_tests/support/dir2/mod$py.class b/functional_tests/support/dir2/mod$py.class new file mode 100644 index 0000000000000000000000000000000000000000..1243c9cd350109034a2ed0fa66d2c1b132b01d06 GIT binary patch literal 2114 zcmbtVZBrXn7(JH_Y=}!sQ%Wh60xi}AXtrQmt-&g#qN#x*LA2B{U6LERb+en<-Jtp4 zZ_&?wsxYODj{X3Dl;gQcqzzeSoG~-Wy}9>!InQ}s?q7fZ_9uXONCB-feN$i~mG}Hz zv(^Zod9GRVd}*#XR(z`}vt1XP&wOc>3!xRtY`W`qz42)0xh#cQfs3b@)S(IJfeann zC4yKgm)GhHu` zMPEA7;yRz1&n%dgHWVPKAG)_;+Qv zYS}I`oH|vNwNh=$45uP|rnlm%V9sMGL7R&P-oyK9$9f$-nNEcy?^$K(>-d=UTw-5_ zvS3$hj=b-AVG#OOP2g@+Z#Sdp#;A^uRkH@}V>y8(JYZ~o-EGBoyW3u;T4Y!TR`97} z>@(H^dFoIRn2I2*Lxd%C?IZ!~jg7kNs-8T?tRpXwru`M$kuK-gvgeYAdMWh$4OwwG znb^n2;sOhA*8MW)P7pEFI*}My!=nUNu`V!BvK*)QZ%YakQn{$|`(Kk(l~|7|g3X8` zc+G(h4zI~h@6{S<@Xe+5+?MnMMot=g_bjI_H9i2N0%MW#tD80_e=i{Be++*9fnq1A zf8!C*@hxjwQ6z9S&yl`XukJ{{IV;)Wd?-rat*Syy6ui1$k}5L-mzcVcUbgJOW&)R7 z*W-&qgaM`)>M&#e3K~Y#>?G6?HD#JB+l^}<@5u_kPxa`!KvO3(^9q+{4lt3tc7*f+W;N{(WLD>9lQ%U@+sA|^gm!Ka zN4Rx>JHJv>Sz=qZ?F@2N)?@p)qdbBy)?Hd}d6}dSbvyJs#vdoMukhg^K03lDGe^j^ zZ1v+IK5t7Wo3g~496!PrEo*$7QE1;pFU8F3&(I;hd?8wK@9_8* sc^N@W$GFhdQiR5Rdwcn`iyoAy+k>kpa~&qzP5G%vX%Ge0jeC%z=LxTILWxU`@kzo2r=K`iBY-XI18Mv2w&&zY3dtUzf`?o&tBavC>6x1u-tU`)Fzcpw5jXnCIN3%zdnD>`}At5q3dwVLzVFyjGrW3#SJ2EwdvMd zHoq5xP8s0v@1;1yPif=v@H73F6Juv51jN8g=EH1taD$* z$CTJLDp0}8R@1Zjeb*I%@J)~5Zdj3bqZr00kB?RJ8t!8~fi-+WvH2~h7u)S&dxL7B zVQJXFXOgkcDHn*R0TqV%5W)sTD21+_B%svZYB`Q9&ST6f3Je*tzhT+jp+>guI>bY( zCR~4uH*9KARM5xb3@dNu{W>+EAf&quBGIskhY1u>Vi>ELwq5Z{KV+ce&qfnpEL5F>23?G7%GHx8>Kkd>MvI^mKx} zRII?F2V8R;mqrUA3@}fjPEyQYLqSS5QG%M1U8cKIxzRdG_rwZ)pU}VSw37jyS&SYb zwitN{?EuLabi!cLuauU1CJT#xpU}S#FxJ({WnbY^_7JnlYaL_`v8X72AXi*kOx{!! zT|`hL-3YFtPU{5iz9IjQ4D8^s+&sXy{{R}Q BHP8S6 literal 0 HcmV?d00001 diff --git a/functional_tests/support/dir2/pak/__init__.py b/functional_tests/support/dir2/pak/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/functional_tests/support/dir2/pak/__init__.py @@ -0,0 +1 @@ +pass diff --git a/functional_tests/support/dir2/pak/__init__.pyc b/functional_tests/support/dir2/pak/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9680c7cb2fc33f21a630001d6ad33eeef088b395 GIT binary patch literal 157 zcmd1(#LHDZ!^AI{0SXv_v;zH0`UORqWr-!J`X#vq z`g!@qsqsarIjM<2+EC9>&qzP5G%vX%Ge0jeC%z=LxTILWxU`@kzo%k6lk#~fo2P~)f%i)6uUK{kzlmcFzU8ZT1l9K=h6eW#nz*p=e#O%3>Qx^sX}2;11@aK zAq3HskuGZ(!f*mnsB~4Y*k&Ls24-l8BN3>)Rx9bdA-ZNk54YxM$wTOYl@t2Cr8YVFn$26`mL@LC37~&ofh6lRmTYE&ZE*hTh zxB;*D+~y{&OWCFDvR?0jGVO}sfe7@V>3Oa%^qS=_>pjm|uNA{phKa$Tx}H?b6YRS0 zHVj8_-{CceY#1{k?Dnu~xQ_P{co!)RvzU_+X2@fR!kSy-M9b(&-c{USh^-KBO`G3~ zL8l~e_zzN?;pdd`Wbmc_%ZahQ9Ri~7F>_(IL);lgSgLp8(WsD?6O>%Ir$T(D%Q2ZW=P{Nbn=2YVz=v{2xjT9CIVF<3Yu320;#11) zD%GgqC9C1t{J!goK=`J|a5t>ZJ5dZ`l*dP^ISu!*mcS}LquBhW(~a%+puK*z(6BVD z;|s~y1Ih*BsZWJrE`+c?5lW$JCkZIFHk*zk>+=Y+iabM_?5|rkcc_=GxeoErtP0oP z{>1d-WbPF{KEx+& ze4c5;=-P_oA-?QMCp)r)H#yzLS6yo~s8Q&qzP5G%vX%Ge0jeC%z=LxTILWxU`@kzo}AxSO#a18VffWw$cU1 z+g06m@XWE*n&WY`)ZXxnCeIIDY(Mk3Q7;EZ!1I}*+fw^s^*OHvd4`LpsFWcw$UYA& z(@Xd~P)ul!9x#Q2kbQ!EF-*iQ_LBmlaH5I*u7!VTkC&dKlt95Qayp>zR8*vKlm9 z)pmTYd)(p%t;@OP+=|-hgEA~V;C|qfS=V*EK&_kJirTlV>bh`O*K=+=hN}#zVefh- zWh@Z>hUYXjJK&zp>kPRtib81agH~`I?%%#gGTfDp@DJ6N0iZ$S6v*}v=zT*Ub;2AE%-LQi1dNGVq93MT+E4YvK1lI5w`R29ko^Q8@ z^$k7?bxXkpz7ULkNx48g4Lo6(4yx3J*22>7 zf3;VUViA!9+aXDC%z+H*Xmf+sn=aAdscR*z!abinCmKig46DT@nhH}4)1mUkZIi~q z2*8$q@_+e}#7|5Zh~aF3`ut|AS>;~0fl`Ij^r*ckibPD5ot9VQA~Fn@ zD0Bf?shGY=0bH|fhsF#c^f6DaPLj{xKtf71Qi7TiU8cKIxzRdG_rwbQKc%nhv=a`U zS&SYcwix*x%07}W>4d?cUm-2_OcWOVKBcdZFxGvO%f7;;>;Yzz*E+}?U{R9(Law;9 zn7k=T(mrM-#-wxO=-}1??)*kCg(fP?zMgSfh4$z^?g)#ZF?N@<_pHp34RJg1CuSZe z^RMvnAwKEg^K1uNPgfid@Ks+r*_9={$(arw^|aB%M!A1qr|B3aZCA2OH>Q6TwgfZB ze-nrJ`X%f6U8UnhcM88b{4iw=qQ{q5z`E+VMW+X$|rPU{5iz9svQHSFMt*gU{@ F{{V;(I!6Ei literal 0 HcmV?d00001 diff --git a/functional_tests/support/dir2/pak/sub/__init__.py b/functional_tests/support/dir2/pak/sub/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/functional_tests/support/dir2/pak/sub/__init__.py @@ -0,0 +1 @@ +pass diff --git a/functional_tests/support/dir2/pak/sub/__init__.pyc b/functional_tests/support/dir2/pak/sub/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf84d0c51f80899fc900a23afb03b921f63dcebf GIT binary patch literal 161 zcmd1(#LHDZ!^AI{0SXv_v;z&qzP5G%vX%Ge0jeC%z=LxTILWxU`@kzo09V;2IRF3v literal 0 HcmV?d00001 diff --git a/functional_tests/support/dtt/docs/doc.txt b/functional_tests/support/dtt/docs/doc.txt new file mode 100644 index 0000000..4cf3955 --- /dev/null +++ b/functional_tests/support/dtt/docs/doc.txt @@ -0,0 +1,6 @@ +This document is a doctest. + + >>> 1 + 1 + 2 + +That's all diff --git a/functional_tests/support/dtt/docs/errdoc.txt b/functional_tests/support/dtt/docs/errdoc.txt new file mode 100644 index 0000000..a947b52 --- /dev/null +++ b/functional_tests/support/dtt/docs/errdoc.txt @@ -0,0 +1,7 @@ +This document contains an invalid doctest. + + >>> def foo(): + >>> def bar(): + ... pass + +That is all. diff --git a/functional_tests/support/dtt/docs/nodoc.txt b/functional_tests/support/dtt/docs/nodoc.txt new file mode 100644 index 0000000..665f935 --- /dev/null +++ b/functional_tests/support/dtt/docs/nodoc.txt @@ -0,0 +1 @@ +This document contains no doctests. diff --git a/functional_tests/support/dtt/some_mod$py.class b/functional_tests/support/dtt/some_mod$py.class new file mode 100644 index 0000000000000000000000000000000000000000..51dd34612842644bc922ea3154fca9d0b91dc8a8 GIT binary patch literal 3170 zcmbtW>sJ#;6#oqgY_hC1q&`qkW2G7hu!4_PW3fu5#sX5HShaP#B$E)_?54XLEN$)E zzTdCYxBc8t7TUj1W@?(%eZ!|KH1`Pq4%b<+%zrsb@x zrbQ59XmzX-*Gg8dtGpabk;GBe3hN#2cf?>_- z27{U$o!1w1)zHm?>MIH`;k(#9qhJ&6lc8WUxl4MCqRAP;$&6TPLtHG~&k%34FJUW# za;8*v{eT^Yj>{X{Qm`HEQEbBwin>wVNX5*$Maz`XK@sJ(T0o>T2qE8n1-r02N*?ZE zXwF+!7XjL&4Jvq`2HZY|NP%LqvI3f26x6-kdl36YvXNeD2s8lZ}UGWtP*NqNoC9k!0 zY~?sPxfc%!Cm&{LIZ-r=?hwQLJsi15@;0&&@Vy=t@#i?wGCFZW6q#j3K=vboV^JZF~HMoSw$yQQu_c$^#&f*+tces&f z*fe@q0TMW)7!%-(6T{>L{f35y669rau-kh*~oHE@X zOWZ?Y0?(C3AI0a@tyDWN;w2f2c$s1&e$`?-(x`9k zw!p9yynY z81&`Gri*klY6jTAb?0YdS;zV$+;vHk@C~tBfW@$Rlr9+KmC`J?y^c*KPZz1|)iFUy zdCIETSuS$Ku!G!o$;wR8DN-s=nWjZIF7nW!Xj`bYGkiujq;}EB$<=mo=zAj_&E812 zCvT*lOE|WKuG@HIJQY9LSH)>Io@)9XN0)Hs7HMJlo}Myk2U_VbGDR8kZJnf$V3~>QlTo6OX#g)u%@nWy|Nf>Ya=z(2Yh8N-QPr}=6Y`9iJoPgk3YGB3(M#V zui)}BdgIS}uX6l3Z=4N<{zPu9Hx1;Mi(v)x%P_)WZ!r`%r%2*tOz}j* zo(S_qj{QouM1a(Pd=FwYGWtfi8Ny#6isxbbs|oT>tZuo3mWy%cHY!zITfz076}(>4 z6~!BP)0d8WvOr6`Wd-lmv@yJo5B&8T1e}0we@|~;$1lPbkno>> foo(1) + 2 + >>> 2 + 2 + 4 +""" +def foo(a): + """ + >>> foo(2) + 3 + """ + return a + 1 diff --git a/functional_tests/support/dtt/some_mod.pyc b/functional_tests/support/dtt/some_mod.pyc new file mode 100644 index 0000000000000000000000000000000000000000..081a3f62c955bb98c79a3b65b29eebcb85333f1f GIT binary patch literal 407 zcmX|7u};J=3{8#`pa2sC5>qyc5RF=HE=VjlpbmwJC5qadRH<#E#ytta$Vc#P>!4#qJ)p@&O^hOu`<&b~3>tXZ+wxH??EMrIkN zjrqLsA(AakmkA*dn3$4UmMj2UOqCzoy#cGB_iAv#>d|)AV9e4i&DCxiYG~bHI!(_A zDoozH5mW=9g6pmBU6c9`p#MM}=NQ78ZmbzE6M6xhAZXpVTI-64Sxn5ZniW-x*2gpC I0(Fo1A9R;UmjD0& literal 0 HcmV?d00001 diff --git a/functional_tests/support/empty/.hidden b/functional_tests/support/empty/.hidden new file mode 100644 index 0000000..e69de29 diff --git a/functional_tests/support/ep/Some_plugin.egg-info/PKG-INFO b/functional_tests/support/ep/Some_plugin.egg-info/PKG-INFO new file mode 100644 index 0000000..63420aa --- /dev/null +++ b/functional_tests/support/ep/Some_plugin.egg-info/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: Some-plugin +Version: 0.0.0 +Summary: UNKNOWN +Home-page: UNKNOWN +Author: UNKNOWN +Author-email: UNKNOWN +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN diff --git a/functional_tests/support/ep/Some_plugin.egg-info/SOURCES.txt b/functional_tests/support/ep/Some_plugin.egg-info/SOURCES.txt new file mode 100644 index 0000000..69e3e80 --- /dev/null +++ b/functional_tests/support/ep/Some_plugin.egg-info/SOURCES.txt @@ -0,0 +1,6 @@ +setup.py +Some_plugin.egg-info/PKG-INFO +Some_plugin.egg-info/SOURCES.txt +Some_plugin.egg-info/dependency_links.txt +Some_plugin.egg-info/entry_points.txt +Some_plugin.egg-info/top_level.txt diff --git a/functional_tests/support/ep/Some_plugin.egg-info/dependency_links.txt b/functional_tests/support/ep/Some_plugin.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/functional_tests/support/ep/Some_plugin.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/functional_tests/support/ep/Some_plugin.egg-info/entry_points.txt b/functional_tests/support/ep/Some_plugin.egg-info/entry_points.txt new file mode 100644 index 0000000..3afd2db --- /dev/null +++ b/functional_tests/support/ep/Some_plugin.egg-info/entry_points.txt @@ -0,0 +1,3 @@ +[nose.plugins.0.10] +someplugin = someplugin:SomePlugin + diff --git a/functional_tests/support/ep/Some_plugin.egg-info/top_level.txt b/functional_tests/support/ep/Some_plugin.egg-info/top_level.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/functional_tests/support/ep/Some_plugin.egg-info/top_level.txt @@ -0,0 +1 @@ + diff --git a/functional_tests/support/ep/setup.py b/functional_tests/support/ep/setup.py new file mode 100644 index 0000000..e751d18 --- /dev/null +++ b/functional_tests/support/ep/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup, find_packages + +setup( + name='Some plugin', + packages = find_packages(), + entry_points = { + 'nose.plugins.0.10': [ + 'someplugin = someplugin:SomePlugin' + ] + }) diff --git a/functional_tests/support/ep/someplugin.py b/functional_tests/support/ep/someplugin.py new file mode 100644 index 0000000..3442a2d --- /dev/null +++ b/functional_tests/support/ep/someplugin.py @@ -0,0 +1,4 @@ +from nose.plugins import Plugin + +class SomePlugin(Plugin): + pass diff --git a/functional_tests/support/fdp/test_fdp$py.class b/functional_tests/support/fdp/test_fdp$py.class new file mode 100644 index 0000000000000000000000000000000000000000..5dd138403fead28ec22c43aa2648d197c30a7824 GIT binary patch literal 4150 zcmbtXiFX@E8UKxCdnI*~C?ZYMq;4H*h-}$b>^gzs5J=OcQL$AM8$*)9vX;k=;?=IY zyGr9gDL3V4DYP6ZN6QW6O5|2C4QaUhDCb|qCea56LHxXZc9s(;q8b4ACMxtZ1Du346oErPkTt~3{CebbkdgDujT z)rS_&$)Z0ga9bOf8e#&PCw;4AOM&i8esEq#4|bMhkCD4a{fnBXB2VK$dW`Ve26iv%A41w9!zBjkGeKk5XUdI65 zlEmG3tH74Bxgw8UD9Q?#Tj1W-^%{VQa@jDQ)RDnpk~%s>=|#kK)LE3IbSs9r1|tH! zhe~$IKP+&r!INF z(xU=*X7Ul5P1zs7F%I-N?E=GClnd2m!-(Sq#cde2V_%f6!>Eg822bmF5bt4>Oi^)Y z%Y?=0BQj{ehK>Rr;uU5%@(CR$gV<)try(B^IM^xiPJZ8qc@F0N?Dv3mg*hQ1py zpli#;H%_DtE_@sxPvT?v1aY-39}U5*5FFKp|AGBe_%t`+GmHZ|7f(mqjgIG*!jr_b3{Kt4dDTkAas3=IcW$E?qm|V-p3w2R099Y0pv?Y~ z*Ka$1|3n;L66iT0Ez61HDS_0@Cx(WnX^R{xJBw9I9**Oy)JV#S2~XhpO#V#k8S_TI zh~4*$*PleRenHn2XZY@FUs)MY_lMa<){iSn@u{ z@jc%)D{amw6-sN2{JCLDfd2`8n#7OsGs?}a+CgpyS~+R6S!7x|evV&o;eJVwpgL_b z5!f41uuUp-9^Bo!lTTJ{n>TSIW(`*a2Fd^Nk|k}r?;Uk)szbHtJMKxjWYJBIsIQ?e zFy2fXM~SAqh#Sr3NOY{>r6g*2nHWOdJT`U>Q)xa5h5y&hlabcNAova62h{8ubETSH zXF^o78xathS>33RhTjYHZ;q{8p(44tBQyDV>3UQbDssQB#= z=pNkzh_T1K7cP+6&5dcgb7}Z1A)X5;ux*Mi$c1WoLAr{1i3aG}U0j)Fm?mePs#}!Y zH-XzJY@b${FL@#y&CBN zR3qIk)JS_*jdZ_KBb_{Iq{D^A1o@|$)AKiadj`F9UIAXVyN-AbTMO5aI+cA1J4R|4 z6<0A{$fhSo>Ug^-WVKe6QmC>Ls-zOFI(Dd233a+&#$pZ2muXpnztitla8Cq190~_W zPzVDYB^gV1W_!qUHho(idxmS+TgSe2WN#=t_R*{C*^0mu+CEE`?D64jTt#(g9q-Pj zde!{?LN-S6JV0rsIInCvPoC?TzJz_*SRL;z)G#+<)bRmPps&widktr@=?{{Uv5pVl z$Tnrs)szK6hhhOV{DXee6v-^QHS}rx`#MVngF1|GAI&(sZP`?kgJ^OZlqN_kN$NzK zq0x10QASsWuHyXA8ZM?kx{gn-VXwB1&#qxE{dx8J#UAx~fBH%FdM5p4H6Dt^uH!4y z!`bvRu~@8zi!mW$+f!J_H`Z{eA)M$|!fquLN;n$pR+h2!w`R$FMN=9*O&Xk6?5f&H?2Zx47_PnT6u|F78Is0ioKu?CA;W}Qq zT*q(ed6k~uT^0daxu=iQzrO>jry^)7VvYWiF*iFxFx*bCznx&BouCW@~ literal 0 HcmV?d00001 diff --git a/functional_tests/support/fdp/test_fdp.py b/functional_tests/support/fdp/test_fdp.py new file mode 100644 index 0000000..bcaab0d --- /dev/null +++ b/functional_tests/support/fdp/test_fdp.py @@ -0,0 +1,10 @@ +def test_err(): + raise TypeError("I can't type") + +def test_fail(): + print "Hello" + a = 2 + assert a == 4, "a is not 4" + +def test_ok(): + pass diff --git a/functional_tests/support/fdp/test_fdp.pyc b/functional_tests/support/fdp/test_fdp.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29dba793767e92b825fb805c6a32eaf7a8498480 GIT binary patch literal 602 zcmZutyH3ME5ZtqqKq5f=0j|JRtf2IO5F#`#3>8ge#TUuo*r&SQsXPU+ zulZ86MGYZUqNzIqK17SWzTk*AM#nOahkz%rJ8K&ogHQ)N15J!A-*VOouS`-mPS35b zbdGm2o{TS(<+@&Q+0=P8XYJS}Zf#6sIawAa5zidP*lhR^%{Y(h ktDJv#h%^pCGLewUluXk8^}*k6+P0HvR{~BJ7>2|CAJd#(SO5S3 literal 0 HcmV?d00001 diff --git a/functional_tests/support/fdp/test_fdp_no_capt$py.class b/functional_tests/support/fdp/test_fdp_no_capt$py.class new file mode 100644 index 0000000000000000000000000000000000000000..e0bef8e9177414196b417968ffbc68271b0bd104 GIT binary patch literal 4064 zcmbtXjdvVX75}|#lG!ZNcDuBtv;?*g?IxRKo03*BrCMqWi^&q37;USLliio?HnTI! z%xq~;L=-^<1qDC&Q1Ank54CLQcBKM-bB=$M$9vyQ)}%X|!#U=h+52Yh_r1S&-^;)M z^Uiw!Ch&nkY-#9#z^Tl%kdtj z{`^W!o^V~qjbj(RmZcxq6By4-pEoa$p>rB(xKw zT!S%z{v#E;;vW?_zlqOHpw!Jr#P<;SjOH8;B#;EPD!zu5IBusmhP^{&7sy3`V{@a2 zaG25YF@fs>beI)u=paLae)dM_=51e=rAzhr1g5wiM+9!p{@G`IK!Wk$FUGaIBG-Q9-bC% z+ceoUoL!hE<-Qer=N?2d>ew@gOBpR5uBfA zaDIUX+>&9Lo?j6Vc^zkPmS@KoY1|bYTk7~S50eB89d`s!HL2e1uEtSZdv%tFs75jn zS|Ynj$XSl#oWRZq3=J;`+|s%6*Jj?lxoAlZ%c_G4snrB5(5CG5q(u&PP6uIZ7(E1` z8t!KZxipCL>7r@x_tQR2zBv5IK@*H44Hrm$$#trXMc0<44?h*#1MMPK5_kX)(%`k_ z10kptlB3%3KhS>-4{}NWj!Lz)2KH>yt zbJLDllCFjq39&`Gk@#}1Qmt9?F30gb-!*GpP8k(SXN>&0tx9PB1zt+v=XjZNbL)1H z+o8_Ev{5ZGEgiqYueor)AxKc2HmC^fizwIz5xPm;*14Te)oq)1ayw=XzY`cH`zI@w zwCUz{+_9++^`h^%r{t1FcQTrPZE=Bv>u1MtnpIxJ-DG1VI$p;c3A~0k=}1?1hqW!k z^E4lY!vE_I$w=#A5M1IDU9B2CFxBd9o{DPKjDX1O%36gqv;@){W2@GvNN(=fY<@wy z9@T}4+;_pW>M}+bW4eA_bDD?len3(*0xOSPuwZ}j#I`suI& zylih1@dmaQt|ECl`!;rrH83u&;9wz}njCB531u*7lIxRB~B}XKp5aS@tC_S+eenOsT-QuJKDg$Cib_GeP8LZjee!i zS_GO<_fgVhi?i7vsyhqW7{%}@N+sE^tldS{nmG9!_Ge>F+*@d1X3S{fv!Xy>pU3tF z=Ci2<5;9si{eE^`Td^dVE3~K!QSLO%?Zy3>jT5&epvdIAs zV4cCB6hRu?BN~sP(q(K>O0SJvfitoSFI8{htENVvfgpm1ZpUqd8J9X-cARoy1~W zy^@S8ZvQ%N`7O$j^QO5TrjR76`rE+M+;@iIkMWa`o2K-byW=VbPp5u*1rWlYn0h@K2R zqfNYZsfks3F4J@6k_b@Ay?mJZI;46jf@UJt?kg#ClOq_T-53YDF($h)dhkyQqX&=R WUqpA%>K#h)Y94>ayS%s;@BJ6)kGwME50FrLh*q0+mGc<>S=dh!xNvV~}yEW1;osF&(T_?`U%-)u^A zF_5=!X7$4ycHJh+aUHCyJ`TlYpv_p5g)S?3Dq6J5ini z*lWHOZJ;4sm1^pafe%qxG?yIl+UQuG;}PH)(Y>{;jS)}}{D?3ywtg>IC%iOC(>k47 zz0d{D>0~;&O6IGkOWews)^akhOd^>aByZZhEDTS~nvc-b=e4ymf|Kz` zc`p+0y<8xG`7feRkbqtTGGkHyO*|R9qzEjV-o`)#=p6#3LVCOrH e<^4LrDX@k`z&=#>uuT?+;3^>NWoDZ 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 literal 0 HcmV?d00001 diff --git a/functional_tests/support/id_fails/test_a.py b/functional_tests/support/id_fails/test_a.py new file mode 100644 index 0000000..8eac49a --- /dev/null +++ b/functional_tests/support/id_fails/test_a.py @@ -0,0 +1 @@ +import apackagethatdoesntexist diff --git a/functional_tests/support/id_fails/test_a.pyc b/functional_tests/support/id_fails/test_a.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e9f3d42e504d93e322924395d85c6890c58ac813 GIT binary patch literal 197 zcmX|*K?=e^3`NJS(6v|Ss-fau_d*xCb2Ef#szckEp-DyYBA(L|m?>f)?|r)$56#rcUY?5wUh<#AnQn0ZN1lWSDTB%h^p{9kD0-~krx=C(eVY3@|H?*nm z_uKL9=okIu2R~J+DPu?d?jPlN?qo~J zVvro}8oJ3DniHwsc^NHejiMP6L#$p-LWDscFBDy0y~A+uT4Uofc0i6Gik%Fd_39={ zX4cg$Q^IbBNM2nutc+%m&d%*rwj&lH5AS1Wb-CllhbW4?YG{tDGq9A5eb_G;Iw+>J zWpWvvLL!QTWJE})nRu}*;V?r-fSoZb$46xB#gPcQ&`nm(=%((TWmpcXj-TdjlF-OT zz;|DnsKfm@8bur86ol|OjAIO8Q7kK~84OBda#>r|6hku?m08!;&Bc0%)IcBGd6r2z?s3INp)vg97;X(31 zRZYvh$!$wj3HyoO1sSLCP!uQeFhk1(fq;i`nqk+d<8WJGWZbqbI}F0}&VshW$8ThL zQG92Z-KH$tT+na5@HSCKHffgX{#Fvb}62Bl}7;YfY#8%UqiG6t70 z;RWqg`N=3QAVpPnxRIyKOx6%ePF%vI0O1rvlS<5a)g$+k>O2wjOyNaGuMNE!TH9c4?hjAsagjHh_W@!rKZL5$n zU2dB^_urx7yDQ@<%ttYYr)6BmvRLb(+^b`xo+q`i5K#RsH5v)h9bhh%3Tg;{hNyT!U`Ufepb#-;$$#pvDNQHbb|xB% z5*!L1&%Q)}&EEMi4vPsf9wnX2GC1;LM`NLG$O=y;EiK1w31o9`mJSV<&+3Ju!OvNi z>$tX7WH=qvLtYg1K8nvZQ37cZFGgX(rr7L~S&i*fy}qqtfnmvTQ4$$jB`i>$wumtF z1SD(=3k`>(^}RP!GELETHJ>HiWauUR6S~1oI$Xyrlk!l?x|Th|^9CK8G+1iv48xln zeT+^TC*Z{0>WPe(@k$gg;Z+(~G_ehJqy5CdN(I61e;xAzVod@BZ;GSK+r2GDm$!R2 z02tHdjl4*BpP_rJM};D###TnBQ}f(*C>xa8mQ~Ft@etiuI_cC88jEKu%TC+ zA8!!bz2(?+Ymx97;asGRVfQ56LDHo{hTGnVq^_r%O?G8UfHOL4mFz4R)nYh6Zo6b< zUUzgV>Zoa2bOj<09rRH67bp)O&?Tlr%uaH(LyQ-1r55y7I@rCH#yYKG;-}2f^98-F zpp}j<;BvCLg76x4rSGEcYVud?8CXLPyMqVP$=LCM3X&|HlX80|!6cO_d$YqCzR)sqo!Qc3AwIGZI2A#*Yr7BaEH3Wh6q zWbh7%073@;{{q{UF&$Qs*T}wyy4S-q7n%#4EFU89Q;|>0^OmiKT*U(^mNk` zui&j)6}&^wd$(A%)FPRAlI|91>|%a4HzhXSh>&PR=x;=5!q;T230Lq9t$S$qAw}>} N3LoPWvAKv({{=GwMZf?6 literal 0 HcmV?d00001 diff --git a/functional_tests/support/id_fails/test_b.py b/functional_tests/support/id_fails/test_b.py new file mode 100644 index 0000000..8999edc --- /dev/null +++ b/functional_tests/support/id_fails/test_b.py @@ -0,0 +1,5 @@ +def test(): + pass + +def test_fail(): + assert False diff --git a/functional_tests/support/id_fails/test_b.pyc b/functional_tests/support/id_fails/test_b.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d8b147351ca2928f69d98cd77c764a4041a972e GIT binary patch literal 397 zcmZWlO>4qH5S`UZvCtlR@Hga?Me*K>*h?XYc%?g2-EIVVNcq{!8{hj^-=NTsQ=F6#osokR_&eyFyC~wOfnr!U8GOR$D54V9~8W5s=!VjbV2dLbJ)5WNA_B zyT0GAGRVQ6%@H*4B6y2+qs zhUfJKT{U%URt*(Jmgp2-Q(Hl&2;K894SeF@DB%9&Ex z3w?DNTBkR+rCN6&n@Ho|UM&$4@Fw~B`f3Dyf#6V$;DGD#1%4n9 z&)Zzs@i!$f7MVCf(MlMn>MA;RN#vVjaY~?`C^jP4U+W)}cv{8;h_I9Lh+U|dyxSx& zX03m{KBVA#r@z{&zX?1eYJ!^Npjvi}1!{Y$S1PNP?Q+fGCf8}~?dk35Q&(BkO-+#K zs&1uRwjED33T2hJ?ih==!Ql8Fca0md<~T2C4bNMOoZu1^qvA6WieZb=*L3c zu&o!kBjGC5VV*8Kk53q-vdK@|w&!||US>EN)k%CPwHU?n+EFUL7x9veMZ8SDITb7L z?QpHW^~WOJQt%316~w(pDM37~JHoIxg0OX4=#t)7+fc?UmL+=08fFP^F?5jrA;aVr zJqiYGi+HHyJ=+=QMU$?3y1&-8GxXhAuY=UM-H4aMdL#_Ehrp z4$EI25h*E8*cB(wMT{7B5^RsGOc}00p*&?-Ha*w~p-bL2(6zwu8NISv#7#g@Tg0Qm zAL-@gkJO|5k*<1wq`LG+x>aeE=sfX9|KHKCOQbPQx{5#c4L0t@|BEmT=}KX<_)DzA|Ztk3pstico0l z1kHr4ec5D_uehB|G}9~_t0J?6t}6Nhb=@14HPNwVQbTH zTIUL04|Fx+4ZIodPWgL*hE&4}-V3xbypIn;`E?>rP`AIctGo3VVGBt6Ux7&^0nk>( zM>nhZ5*nBUMZ&j;(L9`{HxsmPCaA-Yq_hsx_=(0Qnte{@zsTWB Kd?hAl@%6vxFIHaw literal 0 HcmV?d00001 diff --git a/functional_tests/support/idp/exm.py b/functional_tests/support/idp/exm.py new file mode 100644 index 0000000..a5ae6e2 --- /dev/null +++ b/functional_tests/support/idp/exm.py @@ -0,0 +1,21 @@ +""" +Module-level doctest + + >>> 1 + 1 + >>> add_one(_) + 2 +""" + +def add_one(i): + """ + function doctest + + >>> add_one(1) + 2 + >>> add_one(2) + 3 + >>> add_one('steve') + joe + """ + return i + 1 diff --git a/functional_tests/support/idp/exm.pyc b/functional_tests/support/idp/exm.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7e9fb7ab61fe3c6761a020e17584a360f293298 GIT binary patch literal 476 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 literal 0 HcmV?d00001 diff --git a/functional_tests/support/idp/tests.py b/functional_tests/support/idp/tests.py new file mode 100644 index 0000000..fc58278 --- /dev/null +++ b/functional_tests/support/idp/tests.py @@ -0,0 +1,38 @@ +import unittest + +def test_a(): + pass + +def test_b(): + raise TypeError("I am typeless") + +def test_c(): + assert False, "I am contrary" + +def test_gen(): + def tryit(i): + pass + + for i in range(0, 4): + yield tryit, i + + +class TestCase(unittest.TestCase): + def test_a(self): + pass + def test_b(self): + pass + + +class TestCls: + def test_a(self): + pass + + def test_gen(self): + def tryit(i): + pass + for i in range(0, 4): + yield tryit, i + + def test_z(self): + pass diff --git a/functional_tests/support/idp/tests.pyc b/functional_tests/support/idp/tests.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e08e562656c6f494d95e85402a43b27a1a0720a2 GIT binary patch literal 1786 zcmbtU+iuf95FI<0CWS(gK&XI35km4)tRmjIlp^txhiwI-Qe~OgsVox5csGbZ`$G8$ zzLPKDoN*8_4}`9{ehf*4g9}Avj;GV$S09~IG+53<4NMH z<4e*|ry)sGou(u$Nky`j46&@u!4r`W`z=XUWQY)*ze8;X-VM6QP$%}#+h*csm3TMc zNI!s)1><%w78YgpC3b1(#zmMh;vil;aZVmRXm5pNbifcEdOAVyD~0kip#aI?M*zgsk;I1 zrL}2EHZB2YKqm62(Cj@JoDJ_qbwR*HbwL_pb`{Z8 zs%9BB9Ss*C8s?)6&JKnBvvwhmh6OD~BTskhXaTcool%s><1~ugDn_DcJV~Y_bTz%< zt~!!>dMf4`CT^qYy={A4-;s3XY-InX>@gWT_3sm>ujw20l1UUlK=n! literal 0 HcmV?d00001 diff --git a/functional_tests/support/ipt/test1/ipthelp$py.class b/functional_tests/support/ipt/test1/ipthelp$py.class new file mode 100644 index 0000000000000000000000000000000000000000..92ac70143586190d31d62e7bcc39f83f862adf11 GIT binary patch literal 2803 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 Q0Dhju7x+>P&f%;70EcTSqW}N^ literal 0 HcmV?d00001 diff --git a/functional_tests/support/ipt/test2/ipthelp.py b/functional_tests/support/ipt/test2/ipthelp.py new file mode 100644 index 0000000..cafd917 --- /dev/null +++ b/functional_tests/support/ipt/test2/ipthelp.py @@ -0,0 +1,5 @@ +print "2help imported" + +def help(a): + print "2 help %s" % 1 + pass diff --git a/functional_tests/support/ipt/test2/ipthelp.pyc b/functional_tests/support/ipt/test2/ipthelp.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1e7ae43aff48c809174c4987a1564d1468507e8 GIT binary patch literal 313 zcmYjMJ!`{243(XBXdxLgWX@9X8nL5WTA@ zJ$O$~(mkE7y-!~+w{>38cp{Bk1<#dgqW66r(Us^^vzR`T33@Z7>|`6ibgl0Pv2_I> z5G!^Ho+_zTO$(htqc!Ijt!K+%eq#qqqM!syK;>_aq8yZrAKZ5-)=2A3*Rw4K+t^a$ zDqW?S`8s!%xV|f!LM#g#K6@->oEL*i289Px|07R25jy+>%Tknww~t8vDYU{S$G^=s JP>g%e@dGOtIko@* literal 0 HcmV?d00001 diff --git a/functional_tests/support/ipt/test2/tests$py.class b/functional_tests/support/ipt/test2/tests$py.class new file mode 100644 index 0000000000000000000000000000000000000000..f5fd269a3e7a82c45510652b455b230a8c77c2e1 GIT binary patch literal 3397 zcmbtW3sVzU6#gy=Y!cUsQ4tXpmD)g%Ews`q)K;n3C?EyI3bpAHZbERgo9=GVw6*qS zANKuzSNod2J2td2Q>Xm_{ZXBsyIClZg>k0Lu=nOZ&i&4JzH{#V`=3Al0?>h97<|d* z+Zl!<{f3!RvUz*j(3ON?a%CvrXQ~!!xMHl!{!xC zO7JmA7Pqyu&KdlXSj(7CPTZ2JFAki6T5^ryU9q#n96CKLUSuaeI>y1$r5g3SQC%aiK=az@wki@VIqQ7 ziY$!=&_>L}Kaxbl>Yb|~UZlD6bfrs*;|%Y58HW9h6t&9!U* z`xxXSnXK)q)?(N?v6|hxaCeY6-A|FMWS}pnC+xJLOCa?Jlkt>hOsX2$^oqLcg^YV} zFh~sFOR$7qI-5x*K_aTWTgKsX1bQh`%9xJ`Ep-x751=vK<|%G=%Q%V$gqUJvHW}Ac z%T6<}n2h7-7sm~dxj{qcGERu|8!<#d^)T3LOmVVy2*bkK2t!RzT2I>t8D=U2w~+j+ zaJbSL9^>b7RP` zohqXTE&s*%EHshf86gOg`f-J{K1+33u`^jkH!L1ExyDs$JK8(iJC&p>P*saNUMf~D zD+;MX`JgzwI$x_jn-3r>!zdRdlc9Ebjg??i8uyfI!odJ$NoRyz$c5obBsSqum6zM- zu3QrvANTNhKCztWb$i+NI4LkrpOOKV)*SiOc$JmeC#VGi@8Mh^?BuM5i=Iv1CK zUMh2z$D3p+5*hE|{Se;82MlYSTWCHl#AOuolHdP&2nmT*2@!lE%Ba)(%tH=C`FS=Z zTw!QhiX)RHLQLh*P;88w7I8p?R?n(hj{E2(*HCd8xa$W-)AWR^23W@h>*{%0wmb-n z?pg`olDY+14D0&oZ8n(8OmfpnI?8o=@g>d<2uX%UjGUR^B1Q~b2)0e9jHRtKh4PTD z8+7v%LJK>|=`-a0Rn(wf6jOp)FJ3uLOBa;WQju|5N<^ontA<*M_7QLN|2_RGkOe=r zm!tk70^v}!gxbMqIJCEjjchPlLxTl0l+Z+DMKp)^6cG*6WVDTji|8of&Zs1IcDXyd z+?@d$jE1{tk62PG))a7H@CJHLM}Nb8dkYw4*YOaYHSX@H`W=G>Ja~=JF#JH@I(oW_ zJ|IFNP>SCIKSkTce}Lx1?5=3-!io2X`^o^15x`T?3cEFq%>aF=oVa#U^hgzUCR(rK z(bhRkg}X|anL~rW1bq(2!jCyWEuZgCbu>hl_r{3nyA80WV8xd{7mge>U}`~zl`B4d@VZ1 G@XfysCY}2L literal 0 HcmV?d00001 diff --git a/functional_tests/support/ipt/test2/tests.py b/functional_tests/support/ipt/test2/tests.py new file mode 100644 index 0000000..1c95896 --- /dev/null +++ b/functional_tests/support/ipt/test2/tests.py @@ -0,0 +1,8 @@ +import sys + +print 'ipthelp', sys.modules.get('ipthelp') +import ipthelp +print ipthelp + +def test2(): + ipthelp.help(1) diff --git a/functional_tests/support/ipt/test2/tests.pyc b/functional_tests/support/ipt/test2/tests.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c38ba74d37b1b689e2390f480cf80b837784a18 GIT binary patch literal 391 zcmY*U%Sr=55bW7a&3?N<-sN zzcVyF6}fg-a-tZMyoNR%8Qj6(LP1RPPtaK-{44b#DsZiJlgtz#Qh*&m9+mnu#`i{o zmoE8!;IJ7(8yY>A<+5D)=c(UH9Qvl+NMK>lQ%b`qUW0j5R)$=rz06h5N(j#ICs$Yz t^YET+qHfqttu~_e62v0bAU1W4utx3SkhX@NV6qzkpDHY}OSf?I>=y}!L$Uw> literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue038/test$py.class b/functional_tests/support/issue038/test$py.class new file mode 100644 index 0000000000000000000000000000000000000000..89ae0ca2f22537dd864ac6d916dc5cde3e29ddcd GIT binary patch literal 3400 zcmbtW>sK2^6#oqgY!XvS(-)=HrZ%>LK(5+~1hFrTn=qjZo85GGgG84Gg48dfkdoqevv_%kv#1PUAi>LW~F@!Kf$BZ(k$eN;0%Q@Rr^=S!rkxp(_ ztxRys4q*es=CvoO<%)!j3=vuwraf#LWroYik+pjbUh;nO38{`mru&kQ%ndh+O^8L% zfz9Nch|MS}8iVX}X7f!14qLHJ;IN$`wl=D|EEYo!Avd7w%^0 z@@_X=)r+=j=n{4_giHBp%_t}u?b+4b$0+vT-Y|K%mjbmZ19>IUPx&n6HN~=329}9p zANC8CBxN^i=sb!ZVbg^aDGHlJg+l3L==3>f$e7}TQQQYPj5Me?+m5TcYM)@3@vBl7 z|8+oUc81S)cQcWP0UV4Vj{E8SdXynNupDtAF+g}tbGr^0L)s_J>-0txM{!JKnYyGU zPw4sKU9WaR;z67cNIXPvc~e;TSi&jtEuW{F)AD(SoENE;ZM#n7%|%fR;o%5Q;|#-w zvNFrh%olk@ln=vE?|BU(@WY{DO5r_%Ml_+;3ZWYq`Phe8O`y|1e*Ehe=mqelAprU5nKl4t|d=3pOGu3I!6>M+vSQZ2r+MR zjVm;F7Mav5xE!nD81l7r^WmWlsgGXfXIbErRP>4dQmk>D?I$k!Wsv18L z0^uO({8`a&4A+wxKQ8nTrk5`>7ccr0&8k!k`t9v=TqfgJEG{8jfhy``CJLfekujoI ziF$BUQKt#o0Thiyu?#U4PM7y7h(4JNlIt;O@`Y@*OWW-RWxGCXTs>~u4 zip_I$$!PqvVc3>!DwQVP;6&l|QG9NgQZ?4_Tm?OKH38z&i410V4TctwWwAb5W<5gW3-PQ0}!fOoOwEwWGah+ZaLxxT`RExG@j`Na6 zZvmo+hC0K*%6=ZAOT+ScMy&QEiZ}3P1h3;Qq8^9imdjsIUB<8c{@2?@lvs->g7?Hd z^^F&P=zpGZTf= z93neHkj8iP?`ztDqQHDAxQI|J68rSSG=mfz65fZm&QHpBPy+YSLZqY@AcV!o!jy3_3#Q2>TntY)8nEwyuzl{6cOC$B5`$S2}2RP+34(paq0c!BtlpCpU$3 z%N?XI#LU~U7csYl7kZZPa(%BbUcsxbcFfTQ+G6P?yj|ao;T^o|%6}s41^Vfp-oBkb zi?e`aeIi_eT{Pk}5{r2M<|00%@$pSo&*2aBy+qb=1SO=IB&Kf>ptZY9G!rD733{6e dTJR&C)`9}A)4YXNpHT##XYd8S6pOR?>R*=9kn;cl literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue038/test.py b/functional_tests/support/issue038/test.py new file mode 100644 index 0000000..c55e5a9 --- /dev/null +++ b/functional_tests/support/issue038/test.py @@ -0,0 +1,9 @@ +from nose.exc import SkipTest + + +def test_a(): + pass + + +def test_b(): + raise SkipTest("I'm not ready for test b") diff --git a/functional_tests/support/issue038/test.pyc b/functional_tests/support/issue038/test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1982d2d0dabf993a465c140c003364995a039335 GIT binary patch literal 461 zcmZ8dJx{|h5IrXqf=ZPb87(o8DQrwO+S7flM)Ump!bEYyCtXqb^wilwE$5G_y$-9PzBxyp$Z|3CX^Qm zuDB$~cC}e)hetk+ymFNDh%fWGNkl(516-0%8C$;$Gb-mNM)r~IbUBrv{UTuf2 z#JcZ_CP(VylpBocEvnkNLC+ta6uV=y#|vU(J1@AkpJLR= z(Zt;lF7H~|^(d__s=eIyRx+pD_%ZPR$Y;}}R17B(8%{llNc_@#F;5ul_cG;#&cdt7AKFDs_5c6? literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue072/test$py.class b/functional_tests/support/issue072/test$py.class new file mode 100644 index 0000000000000000000000000000000000000000..70e2ac245cf4b74f9a22fdd55b3b18706f5ddd31 GIT binary patch literal 3422 zcmbtW`&Sc36#j+{Y>2C3s-mD3E7d@N6|7pt+9E<53lV|Rify+`n1tYFH{IQ+Snb=s zpZ2MJXrHI=T4-ZVJ#ByQAJx-4n@GVdIiAy;lbzX_JNNO;ckkZ6|M~q-0G;@mK}t4n zXBdtQSawRy&bU*SsU|F&t3xyWww~eLWgDYYHrFRdT;1i}EoHkyGlwUpdBW{xs9!>* z42eN@xND?M&Jc*iTE-Mqqb7s^WQK70IT^JKN?#`Hmee~8jmMTYu3#CIT7vti$?R)T4n8W@*VaOookq(3db2xNN}25Cw9p(BDN-`*;{LU7q4L8EC?0Vc<4~ zpupEk=mx{Bk(ke+1tywtyMTTxVWDZ0d?J_9v>+n%n&NH|8-|X^z_flsR}I}vsilZ2 zT)%==M1|HihIQpPP_!?>>-^Rf1F`gC7>(m?Z!Ug%w0s)xMgw$cMBqfa1X?Lfpi-Z0 zTXql!7?x-B>rrR| zug7TBu`=A9qI3>o!k5{_)Q~Yr1uJP=8Ihc}$?5fGGibhCB#{b%V~QbU@{^@3_ww0~ z9sgBeDmEvmT&r#-tD2U>HJck;r?Io6v!hE*mdc@SXd-4ub#mFP zWxHzHadLe7?k-gDv^C%G--CE|C2KDjO*NoTSK zKVVs|Ypw%aIBgZA`@~GLkp=T(`500j(y{iUT z=UM0SDH6N!5*BoNGQJ^p3$Peg4p83~&t)dK?Nw1qdFloe#|H%^LnBtsPH=HY4C~2l zm#mDXoitr#k7-)eHj;-9HdD~!6#ZwAsV$-cORlaH4U#ufALETwYrK)Z<=#kn>5cRO zqfw^wgdhFCqql>op|1hZ77gSPoW+XxHPjuA{)*M@vxu{+7>-B7qwRS-$l}qe-*Epd zdajT>!}s*8p;62!+2%E#hvaD-k5+pc8w=PJt?{N?;!%lIZ6j4OEkwgDdC>cg3)mKw z^4J}p#ol%;kNqr8s~53~j77t}B%~E^?_Ue;S9$hVkS&m9cw+!R&@4>e96>-vy)2&J z(p}LC6%cxgh;1zQq?4^z@kr|&^l+kp^c))H0y1;h63%+BcD47qSCamKJJ=QtpO7SJ z7P`ckw5kpToSwrODd25N;U`B(Vl?1M1Uw1mNyL94i^4vYt&;sZ8U<>BS)36_0=0Ms z&z5$Z$TzXO`WotwhM&8Nb9ua2!0W9Ayj|2)i}QG=BrReUznXA;0T+te7%t+Y(*8NR z330nyTienSHd|(b?DLs$SCJ8RT08j{qy8KcZ(4L-VpgCHvH)pL+yf03bg;tqS@f`o?cS z?*+vZGGgf0kinRFc?4fAr`i%)CvU!c^;+{fDbI$RsF8Ev9kWZ~L-&wUNTmuhg%Ys` znS$Av{6{0sN*iq#*e#MAR&)Y1RvIENr=tF(k2`xp$Y>6RYJaeu`eA eT$k_=K<3yCO}$^O>OZD(zGOB4%RM)_QH33WcRY9i literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue082/_mypackage/__init__.py b/functional_tests/support/issue082/_mypackage/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/functional_tests/support/issue082/_mypackage/__init__.py @@ -0,0 +1 @@ +pass diff --git a/functional_tests/support/issue082/_mypackage/_eggs.py b/functional_tests/support/issue082/_mypackage/_eggs.py new file mode 100644 index 0000000..81c65f7 --- /dev/null +++ b/functional_tests/support/issue082/_mypackage/_eggs.py @@ -0,0 +1,8 @@ +""" +>>> True +False +""" +""" +>>> True +False +""" diff --git a/functional_tests/support/issue082/_mypackage/bacon.py b/functional_tests/support/issue082/_mypackage/bacon.py new file mode 100644 index 0000000..81c65f7 --- /dev/null +++ b/functional_tests/support/issue082/_mypackage/bacon.py @@ -0,0 +1,8 @@ +""" +>>> True +False +""" +""" +>>> True +False +""" diff --git a/functional_tests/support/issue082/mypublicpackage/__init__$py.class b/functional_tests/support/issue082/mypublicpackage/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..6188ba993098aef870176fc00590f850105af7da GIT binary patch literal 2163 zcmbtVZBrXn6n<_tup}-mO({*G6lk#~fo1{Q+8S)7D7rP!NHAKerkl+T-FCCPvoB~q z_*?X|pDK)$(a|5^k8(UWiL^^~o3|1zV2LGkH1|1!pmqz!{ul z7+y7PGq}faAya&=@9V0i+k0v`@JxHJkgYJx44T;pZ6h!pdq=NXT*d{WEh8|W$HgQj zkR}X1w;BxN#b(P5YnExay75Bap9G;|fEpN>PaNpbL!Q8`bs9 zeIi{AnyzX)KCgP*;ySI%`Q`kI+USDRt!lviz*qgybsaBIP2Ugs(w!Bx4~1H-3M#7A zyxSVVRfhDS=NJ|D44~Za63m9BOa_f zbxxd(^#@1B4Th1`rc)0ses2USWs1$e6XFa%X0#`R;ruTr+U{-yh@OZSqI5@?)3m61 zX#1m3wXGy61K(A^uqfQWMfmr4u&J9ig+6oYQ%bI8TV^5?VKZA95t(!z<4MxFs^C4` z5j#ro$^LK(Bt=KBb5F*HQ~(AQa=^=G)3x{m#|iwv(_MzUQO%(3#UMs;eDbuQ-~rZ? zSi?u;n-|(0-);@+>wOmKmVym@A{hIWa)Eg2dBQLkL0FFnrO>s00!ppz(6&VppJ0|z zWXO{K4b$Q_HN17lCLTf~aJ+5au&9+$;h%^zth|}`>(r?Hh%VTRM8OswC9#PT!>FNK zR`uUnWhiH~sPy|^tyiR2OeDchL=wE_Kn8WY*^Ct z#w5d3r2OWNNuy#2V9P)FKYdSP{nWoPHDr8CIV~t+I9sF{uoX6I+-o;is&JYt#*0mn zh{>`OdIlGfVYo!03&={v^i2xjnr%BYf(W6HIdXM^eEt#=(xRyn)U@a_?Ul-n)?vCQ zR_Obb{#~b?aOliJ{0Jipv0tGaAoY??7D(BOaq|$jf1#H`6P0CG&ls&jd;9>mg+0^A}(MHo8d)& QitInuu!|>R^AKPE0|cH%DF6Tf literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue082/mypublicpackage/__init__.py b/functional_tests/support/issue082/mypublicpackage/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/functional_tests/support/issue082/mypublicpackage/__init__.py @@ -0,0 +1 @@ +pass diff --git a/functional_tests/support/issue082/mypublicpackage/__init__.pyc b/functional_tests/support/issue082/mypublicpackage/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..212022abd015091d32f416c11d56e7eb733a1ab7 GIT binary patch literal 173 zcmZ9FJqp4=5QSH;61+#Mj0qNYdV(~mWm(rz7WZe!%nHGacur5?1gw1Uz7JmW(r3GQ zz4wDRzpT_JEw2?nBSQ~N>Yg?LS`93aSt@#3mgDVpN literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue082/mypublicpackage/_foo$py.class b/functional_tests/support/issue082/mypublicpackage/_foo$py.class new file mode 100644 index 0000000000000000000000000000000000000000..314b0ec344c4e3532d14d7d3b150d8c2a26e4498 GIT binary patch literal 2334 zcmbtVZC4vb6n-WP*<{_?G}4wrZK0)_0A(BcVrwcDP&74AB$x_S*Ue^vL_rz2X}!UK}uNKKHp%uY^X(i}?ZTdizOjhnr!MLDu!UYwCIm62rw- z+ly+pWqO9WYc#p0H(YnhYs(mBNazKIu^6mM=vz*+s6s|6jU>_pbV{N!-eO1xJZ#!- z&9E7?TzSXXGc?<9npzJiCPXjYd{xC6ysbdRSxTL7Y|G&cL%EVLokLcb-XT*%w~ZjQ z7+6Wgd0a@-`-=?O!MtQ#B7ikl;KWte3$0=tm(v)-y9~*PmScqr4CiCA&sQ0)4u)BY z95b|BXVa+JT*f4AYEI;Q71tG9!xZHZaJ#{9seH;N5W(-^eL--JA)_oTEKF?r5m#0W zJK#zR(+s17al4$#m}O8KzS}A}A@?0#XPApiK8E|_?VW?B;3i0k37sBWn&(@4Brq*( zd79$}T=%)n4H^q`g}HgH(JR@o^^gZ)paqfVxqhfwK@jm<_vf{^O6RT&Way9y|4@c!-Y_(%6Cuh7ZJ6e@yu|dAMp=4#_ij>Qa(VtK)Ma7ZWpIO<`DQ zZ9*Z;&r~d7S*J*)<5jFFDB}s$=0{Gqw!4FP{mWv@Qt>4=1Y;G_0`b(hgkdsHnmbZ+Q;q;^KYK5Woku1Oc(CYM8y`i z6?~0n3};Nkw)I!%hoO=y#mVn~wSFP7gb;xqBZAi)$S@ct`lGizqQTb|*GpCI2UI!H zIJ{@r5tr!38eGZVuyaa^luRoeSsS@*`a+XhseyxhZv#h)0s@_)hzKlM)z^$1vxU< z^i&7{6buASR{|z_a7%QB3AlUHFL7h~0Qv095ef%TvUfV;JxTft4_0SqvPDUf_K}wu zlg?#ughvNh{Eb$M2y}vbco`ZQy`$U~MRXvIPw;8aIzh37b>vSZx3ViQ@%bShA7Op^ z2%BB3G`>Q$=bi2N5&_xd5x(i7F?@^fdiFZiBPzNwJ$rNfR}o8)`1&()i0@ypE(a}o ueTO(0#gL37lcCDkfkgA(^zvzj1RPqMz-_oRj?m1c`1KMv8e;Mo&Hn%`k8vFU literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue082/mypublicpackage/_foo.py b/functional_tests/support/issue082/mypublicpackage/_foo.py new file mode 100644 index 0000000..81c65f7 --- /dev/null +++ b/functional_tests/support/issue082/mypublicpackage/_foo.py @@ -0,0 +1,8 @@ +""" +>>> True +False +""" +""" +>>> True +False +""" diff --git a/functional_tests/support/issue082/mypublicpackage/_foo.pyc b/functional_tests/support/issue082/mypublicpackage/_foo.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c75cbd9412506ef946587ec102100536c4f5d2ca GIT binary patch literal 210 zcmYL@u?oUK42G{#6b=L*!Ld`%76+&5H0oq;Pm1={sIidGK&BJ literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue082/mypublicpackage/bar$py.class b/functional_tests/support/issue082/mypublicpackage/bar$py.class new file mode 100644 index 0000000000000000000000000000000000000000..b2f16f9222cf7b29bc7a28b32cebc7f6952871ea GIT binary patch literal 2331 zcmbtVZC4vb6n-WP*<{_?G*U{Tw$M^dfU*sJu{9V46ip2j38pPo<7P8S7dE?l_65xc ze~W(hQ-vet=&?VbKg#2sB&7{mKAD{CO!nS+?!C`EbLZ8+f4u~dgUcW_rf)KAW=f9N z)ZBLP%&|4Y@wm3pUiI`A&kq>3o_SoamjgZE`Rssoqy4b9!;K)%AXlq($Ea2lkQhc= zZ8xl0rs3+wuHNKYP4}kVwv1thST)Bm9syMjJkxIGRmeyr5l52Y!YO&mc!weB^Pp)t zHQi#+GNm1TPuDEnZfZTCh!DMOvlSI*@U8+CXDM^Su}qsY3}uSKbPj1@dXG$vs-^pZ z$-s&#&SNx5?=LW<2lJ9~kpR{mffH3-FSLpYTuNdb?=!>~P1_6#4Cf=V&sG?&42D?^ zZ6h!pdrPlbT*egbX-4FH1=kc@#WdyMbF0BHR*K#O;`al5DEQ4Vq?AITFuCQ0Tv^pE zpDPK>(9*%MT}oxlF{lmCX%+2&dp55#%tz%OLH+UW&O=jh1Ej-Pl^%~Z*E9D>URuy{ zHQVud)#Db|Y0S;%<`=X^uVUS*2HX#P%@1AI@dC~C{gB_hx1dE7I`6gg(gfP|g&i1HCqqkh5!PAyDiWTnpR5{T& zyr)|sm*~10XPAieU)wV2o*D*N?ic^JpULec^>1BSGN^SA35pobmZ-CZwV58f$OvBp?xTaNX^QJ7@_H-*;L~7EcPeH_Hp?+IWk!E zR0seR3#rqN%{x(*XCx^c}bG?k(C&e z&ZTgKPY+C1f=6f_^yk_@I8L$+3Qq~sOa*{+>MFfMJz$$o6pE0etgcl9JJ{5 tZQ@`ILo(t_hALwR63u(lOQ#uPuxV`!x8Tq?LNkNn*Nfn2h{*#q{{tFrZ_oe$ literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue082/mypublicpackage/bar.py b/functional_tests/support/issue082/mypublicpackage/bar.py new file mode 100644 index 0000000..81c65f7 --- /dev/null +++ b/functional_tests/support/issue082/mypublicpackage/bar.py @@ -0,0 +1,8 @@ +""" +>>> True +False +""" +""" +>>> True +False +""" diff --git a/functional_tests/support/issue082/mypublicpackage/bar.pyc b/functional_tests/support/issue082/mypublicpackage/bar.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2f734408bd42e7732ca03251253938ab82e6aee GIT binary patch literal 209 zcmYL@O$x$5427ps6b1y3;IgZZEiPPDS1!BJg}51}(Wiy+HniqL|gbv;sJ$35dU2g*bt*as)Aq@HMW5O5v{fsv_%SS6aoUJ720l>a1#TY-MG6!v9&Mz ze!pAoLr)*}@yS9PbLwgR-ao3Rb2m#$$inG4{SfYDXYS10-~49o{Pp+me*);jPYj-9 zXg|Yfcu+S|(d>eCM%SVV-QdxYg#km!@a_u1_!)yMGh>!w@$S|N>Bz$H^ej(U-3$$D zn3UjQkW6l=X^k^@!?D(h0IE^rhZhnCTlt7O@`*FwVemB0fAchv5}#v z(%e8!OIT@LldzegHYul6eOgh;X485m>k+J_2=6EcpUZ~2XlHTL3S}20Y-6aB>5H3* zG0RA6sqO$8vAq@zXkyryQRaC6`2^33Kp2j=Wx#>b4dv2NvbqCz`suDa85&*U@6%^E zDQ!iIaJ`$MrYEhXtz!(c>-by;+_ikTd|$o*<##Vae$-(fLp7y7m-XRp2A^Poo={YV zXn1f|nOCB!qNSn^VC!3KMVnwuguySeASZP_bb#_H%QJjBmy%^4I_cjOw~F~?=ydVn z2Cfr!0Q=D;T#(hqN){EhB;3QWL6$XLyTA=ymZ_$~t&;&9#=U+V!hM1lWT4sQGBM5K zDQ-}9?nk%CP7fWH`B{d;>!fDgh>zg`;q^hPtGKT50FDdR`q9U*sn;~QA?Vg`7`ov@ zKf~BMep~`x&Zmp0oRAL-LOw$Ek(5=%w9*VL7QhLd6cscWKu3{{aiYL-?tBb8ZlyH6=rv=WFR0St{j}H$I`S65m za*GU<@D%l(q@ia-stk?K{C5(SivDRRe#n>>M4(<9i)M}VJax~gmB~gm-Q=>tRj$z5 z)!Ef~AewZ#lcLHZa5HM=vRU1*qG{92@vejWqvCUCcEJbA?@Y0!q^Jrse$HVO!%pg| zE?N4QacRto9L+J*t*$!>8OlpfMxV*4{Fo0q<&kWQM#6A49Gh~f)YUe+D|KR%lP(dJ zrLo)1wu`%%>*btIcNT`YnHc~pvMfjOQx_Og($r|1!fv?OM~N{RTW4F?#b@h;58Ff^ z8}$=|o(DawH>fK!+>r1Bu{uFdrp3q7nXJl>>$+uHhLT;Q>D!@H#wb3Q zjuMk!!X-am#LILyBc~PbcBrzN%a6HUD}Yz=nn3Jz;sW7Rc7$QK3t(j`^wjUIEYH!L zriscd#Vp}%hE}pakXE@yqeY*t5e~V8r5mF>snXCut*NBWaA0LG=%Y4by38Bpkp%EA z-t*%fyiYGIJu9lbTuh?X#oXxkzlIz^u_{4=kHrIP?>-gH&E9?PLX44xWn3g&A$pca zl*tk_MznV%Ho*;(&>(25=M^=_Jv6V;)UfIiarB4A(=@161FY+!dF?!@txm^^nN7ks z#B%`~!{$Mnz~Z^gG&k%nN>xvDU1DxXkkdb==Zpjw*<#pEVO!*7B5kHAslA$}(*Q^j zn%GU(A155XfJE~RHCPI@QA|4aO6|#B>4CFXy0g8~IAX8#5YXzQ^MoY*zN7!kM5=A8%?0ebZEK2CHTDS+Bqf;s7DM&eq4q?4bRrUr=5a8O zBbTvn5l7?n>yY#4WpVm;1@#m}Bsf4Sasjd1kri=O*>M#;RO32%t0xZ!;gQfF!7Jfg zI}B<*j^j(%Xoq^P?Is>;Tf$WE$pR8fXbvXr^^CUwbqPm3o((x^*|?}I$CW+7cno825PZ@3y!pf zf)&y&x6p7p_{>dQ%;Wh2UTG`fjiRkuyot9Q?VznohzA=A_@HQw;X{1n$gdOTf*_r3 z?H$cO3tvFExY(K9LHic^h4T31MjoHh@A3^+%)h`kNvB%`stwEmcLmO&wFKd{1np}H ds_+AOs{+T5v~Ho@mvn=#V)zz literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue130/test.py b/functional_tests/support/issue130/test.py new file mode 100644 index 0000000..9778eef --- /dev/null +++ b/functional_tests/support/issue130/test.py @@ -0,0 +1,5 @@ +def setup(): + raise "KABOOM" + +def test_foo(): + assert(1==1) diff --git a/functional_tests/support/issue130/test.pyc b/functional_tests/support/issue130/test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c52735b7acbdccbae2430a1f67c192da2214a4ea GIT binary patch literal 413 zcmZ8dO-lnY5S_Gs7W63oggtFs^{!Psh;$3<&C9a1DXg@ckeL<4oA@LAo&Eyfq{U)G z^73XfZ}O7W&&lZH`?{RbW*F?ZSbPSU6MYfAfS5!K%?I?H4RQ2OaIb=q3JiUsh)^^@ zSjqwLX?!=GPBJjs0eM8uhS3_`k;?sH9+Rir-sSrIS71Wz78lHk`YqVX6w3DeWoC%fHAX+ w>fj+pA9tQmrLrTfg^POO{T}${@-qEGrB_#)UWdw3Zrf_zMO)n{B>D017djz9umAu6 literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue143/not-a-package/__init__.py b/functional_tests/support/issue143/not-a-package/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/functional_tests/support/issue143/not-a-package/__init__.py @@ -0,0 +1 @@ +pass diff --git a/functional_tests/support/issue143/not-a-package/test.py b/functional_tests/support/issue143/not-a-package/test.py new file mode 100644 index 0000000..c1fb1c2 --- /dev/null +++ b/functional_tests/support/issue143/not-a-package/test.py @@ -0,0 +1,2 @@ +def test(): + raise Exception("do not run") diff --git a/functional_tests/support/issue191/UNKNOWN.egg-info/PKG-INFO b/functional_tests/support/issue191/UNKNOWN.egg-info/PKG-INFO new file mode 100644 index 0000000..11b3dcd --- /dev/null +++ b/functional_tests/support/issue191/UNKNOWN.egg-info/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: UNKNOWN +Version: 0.0.0 +Summary: UNKNOWN +Home-page: UNKNOWN +Author: UNKNOWN +Author-email: UNKNOWN +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN diff --git a/functional_tests/support/issue191/UNKNOWN.egg-info/SOURCES.txt b/functional_tests/support/issue191/UNKNOWN.egg-info/SOURCES.txt new file mode 100644 index 0000000..75d8cfe --- /dev/null +++ b/functional_tests/support/issue191/UNKNOWN.egg-info/SOURCES.txt @@ -0,0 +1,6 @@ +setup.cfg +setup.py +UNKNOWN.egg-info/PKG-INFO +UNKNOWN.egg-info/SOURCES.txt +UNKNOWN.egg-info/dependency_links.txt +UNKNOWN.egg-info/top_level.txt \ No newline at end of file diff --git a/functional_tests/support/issue191/UNKNOWN.egg-info/dependency_links.txt b/functional_tests/support/issue191/UNKNOWN.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/functional_tests/support/issue191/UNKNOWN.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/functional_tests/support/issue191/UNKNOWN.egg-info/top_level.txt b/functional_tests/support/issue191/UNKNOWN.egg-info/top_level.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/functional_tests/support/issue191/UNKNOWN.egg-info/top_level.txt @@ -0,0 +1 @@ + diff --git a/functional_tests/support/issue191/setup.cfg b/functional_tests/support/issue191/setup.cfg new file mode 100644 index 0000000..431cf7a --- /dev/null +++ b/functional_tests/support/issue191/setup.cfg @@ -0,0 +1,2 @@ +[nosetests] +verbosity=2 \ No newline at end of file diff --git a/functional_tests/support/issue191/setup.py b/functional_tests/support/issue191/setup.py new file mode 100644 index 0000000..86276be --- /dev/null +++ b/functional_tests/support/issue191/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup(name='issue191') diff --git a/functional_tests/support/issue191/test$py.class b/functional_tests/support/issue191/test$py.class new file mode 100644 index 0000000000000000000000000000000000000000..b68f450a188fd2ead427f7975b6275ebecf5dd30 GIT binary patch literal 2611 zcmbtVT~`}b6y28uCW%8!BW)>A3KnTXfH5dq3|1*EngS^jOa;_9gqtvQGBeIhXyeDD zzoS0;grLB(OMier%H_F}A*+x{Uwk0up?02r)Z-_?j44Kt@goXya73i7`hqyTiq?zjFPLFhJ?!u z(Q>h(o7<{RKKozi@(SWnLii>_m&+Y@G)-BQi@NH#8UxG9xQ6ROp^tLPnFg29FDzmh zAVtr@s!O=Z&=(M8!Q9~)8CP*DiXjYBj5*EF+LX=Mn%cHwq{gX3DTs1aF{?+v~`@}YJhFBz;LU5 zf7aD)olBS`nkE|Plo+N!gbh1fFH`T9{kl@=rtz*Q-FpljMbZdrrH_~)t5~+SPYhPv zs-+mF!;3c8xk_hxDm^u=lzoP%dQn7h6sKlcrtK=4(MP6LC%d^B0PXDkc>T!)`3?w|%?3>ipZAOSG)6DwGZ{n( zk>ex6U*YbGsu|SFL|ap$Le zfhNYScY7K@ht>L7H6&CShR@@uT2v5QnP1Hoxb094sGyzus$SzE+N}MwodWe&)-_tG zod8S!HDazguOG?a3g4!pBig(h(^G;epbV{U0b)vs#^xp$? zkqt1B3LhhKh)cPb=-Ej9j^4>bjIa~DolC{LictY(#u|y!0zjJVCc-vowB!^!O2G;Iv44v3`op@l$-$@D)W7-}>5dPnQZ1A38<3;f+)gmsPSAnx$!Q1f;s-i= R>1I-RD~s=7i_0T8{{b_Nv(*3q literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue191/test.py b/functional_tests/support/issue191/test.py new file mode 100644 index 0000000..f174823 --- /dev/null +++ b/functional_tests/support/issue191/test.py @@ -0,0 +1,2 @@ +def test(): + pass diff --git a/functional_tests/support/issue191/test.pyc b/functional_tests/support/issue191/test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d774193fa7028d737a0e56c8eafd22923c370929 GIT binary patch literal 249 zcmd1(#LHDZ!^AI{0SXv_v;z}hA1F|ks(-v2`GZ1&KabR1)?rk1EkYW z14uBS5hb2LG5vy~%(BFiRQ;0N0{y)F;?($})ST2rAZ@5;sAr^~R+^Vwl9`{Em=j-; zT3k}BUtC&HkY7}ypIKa7nrdiis1Fj>E2u01SqNf->?;PjiGk4%YDgWi4OXa#p)y zcjeTS(w6u84dvZJd6vhdPOEt66n#u(U zFpXORnd;~Xfu+Kn=dYWM?Qp|$&5Gwsb8-8UZ`b8a&%yGBFYUE*XoqrUtmkxb`-#;} zSqWzZ?&~8`LqwR>Z$Cg$1)|!sA ztQ-yqD);w3k+XaTJ*1F@Z+74wN*-PXo-Z4xIABw_Ib8Dexjh-a` z#iuH=p~U4d##g2xmZzf_$8|zrf5^C6tM-~zaqJ)%ZES0p5*V-;)HJ0@)^wb}^e{|N z4`a>HI+i6+?nP)P5c@l^2Sjx|j7Rdw_2X(HRKGdkXlGkGTB zETNoY*Q~cDNmRf&<>h;b=yNKv=LI(R(78wE`{_vep46rS-+S@CJdWc+6y-RzIm`;& z9i{F12FrU(`lF{bMxYG|y;u3CZCJ zfjbBi3R1N~)ZJf53u0oPjt^l;RYqCIJ+aIW6J;txvkFQAPp8z{|2IPYgQw$3d_?8< zvcSHm=`_QdV_xumdppo@m4T_&95#eP;c7=yN_^}6qLT43d_0ejf;hi7#j~*$fd~7S z^s>F`NDZrO=7l)3Yk5?lT5BMks=z&SJ*7whu46+1kZB#TcmSIc)8tjqx{UI$xcyMDy0=h(lb7dXB6;0qeH}H@mvn; z@ijPa*BH$ssbv*$6Akr<=3o~r6Q9Oba+p$0!OjyCU)J$Cd|vIonsS*aadV!%CVdTG zCt@p{2SQn{)f!JGGW+gViiEric4Sbuv`As+W?UCN@*v*y_ zO;rxx#rIUuzE3!?1$D6q97_pU7YXN>E^xB5&ZDlDGOu)7qT?s{X&yhu&)8t2d+~Njs_U_ohQR-6f>S6P zP#}0owc%*@D|K&0yI-qzAMJjd0+Ge-+Y!?6dx4|fw$&R9l5bvEEUidCU|bmF!7bZq z$_&R+jzc4<^z+L#&ZC2Xn0_XBiG@7!pNZ+12sGRv;#B~7rOk2BTxix;r61KITL8z` z%C&g~(|p-$`W312CUBT$ht#rC3u;X71=saB=hDIe#~6qTBk~7m9NgGEY3d=>bfT3r zOSG~sqm|cIwDOjZR$h%F^4a0r5^p$qgR{kK3%Tr@$S)KPy@h>~R>mqD#^erG z#4fI!%@z&gFlk1_+3Y00e4;q;I!Zg3yFqS&oBSQ(s`hO5ZTSJeX8HBWqH%B0xQ~J< zqBuxhJ2Y!M7m8L0|*;*5W_i{()V z6j=?X(A4rOGTbNILcfEFeg`M|9X#CcU;wYvi~%@kaXrA@D|GRXCHx70R-4E1mwy90 CumRry literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue269/test_bad_class.py b/functional_tests/support/issue269/test_bad_class.py new file mode 100644 index 0000000..b5642a6 --- /dev/null +++ b/functional_tests/support/issue269/test_bad_class.py @@ -0,0 +1,5 @@ +class TestCrashy(object): + def __init__(self): + raise Exception("pow") + def test_whatever(self): + pass diff --git a/functional_tests/support/issue269/test_bad_class.pyc b/functional_tests/support/issue269/test_bad_class.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea243cdb06deab2c71a876d7542fbb297dae5f41 GIT binary patch literal 571 zcmY*WO-sW-5S^r2Y@y)UUyxIm77;ut7Oy#^UhHMrCR-ZG7nwEeDSsy8kMG+!g}-mW>mvu=Fk}E8U=u(Vz!$&~;C01^5SEeD(U{@IBQ08o z+rvpC;PexRY7l^MRA3bfnUz)pNP3Mpm1>EQVng*>r9;U6qQ6D=hIi zXs~OVx+Rk%c7A?$ZzOHkF0*Oj5U0(7u2|RFyvoU1F^2*WoW>E0I*xeK5hN4$&Saa& zFlVEQ)X_vhdhNIDcjsH3sF#kkqVHR|(p#%Pz}m9Tx`L0%rN|Ed$!Y`DPB^a*jiNIQ eMq#Y4Nr)$56#rd9*d%T(jeS#Wuu%hnY{AxAL2QdfQ-M-Il-3%T%@jXMRm^{cZ zl^Qec1;whki>9IEO`9u|l~G$Q@xcbd%%aWJ!nCWpe6YKLI$1eAzr^$IAj7UEA|-?v zB!|1YW^jhqRJMCoMi^}|v_fKtH}Xk{GRP-OmRl3=Fm#-2u3W}#kfVrU3&Y+WZr9s&T$kD?O^hTV<6`{|Kz7egeM%d5Jc%P~wh0$_cfn}=l+1jtC@?idc>9)>L?b(z;= z#c;GyUmZC?BI~S!GE(S{QEGe0y#TvL%mSxFJJ2g)(8tg=q#2q!%&@eH&P}k?Oh>@` z8uSI66&#GA9h9oDm%|9|qks$0n-p^fB>)|pOFWGG1w4+BM*<$n0Re>p84vo|CZCV` zK0ib?B~C=}FhRAL(^bdS7+6-uahwnk9-$Kxrom;L6qt@-lwn7JLEW=4GKMGc6nUOaT=3LeHZPKX!*mm_8Npc*U}}zY zie+mn)b13wWGRN}@SM$cuF~3{>CX%(#acU2^_=k6QJk`6nYOEFj#K6XM~*5%VaQmO z2+qrR+Rq;qVf)SfLV`-58KOo|*7G*#W&?STbg+1#U}lDb;zJT`r&2N|w$~n5OHvwrVwLN1hjrHj3AERs!-0F2qpA zMe>c1>-%=NvDIv33v^4y^LRl->_x%?#c2ZzLsvk+HXNaud!Vser^<#Q3bk&tgjX24 zN&cv&bAx95h-pw9%6Zqcr+87P=}q&jew<-oeQOw@cH#sKvW=F=cnz<|@G34-@Arnx zjUo}VCL095|8?6C5Ni=2cw4l7Z}+Zfh~Dn~0ANg3Zp1~xhYX307L_atjjbG?%+7M# zp=eNO!z-#@<{`Q`(S+^@JU>38(Tyn#u>MQVkA#Sho71sws7m;Pa4y2euyu^CLKEfE zJh#23N>xu6to-u00B3C4EZcc5vPCVG%yvo3tmbHx)Z+$qD^=vnK^M7yisJAQB)Usb zeQ$SQ81xQ_O@ z^sm_7w~7qAhJlH6{7_#N53q@})OcjLcH~&?NXu_{WEGEIA(0GU({GGai9JXSRcBps z1|j+aJJVs3nvUhV)+^kKk|fc!jx?YWfvM|Hm6y&pQ;%8zwI5BsJd4 z*y8EE*mDizJy&riKD~yys~C!(^VY8TGv4}aDD(&B$9vQ9LMRkk#hDOep>6G0!{Sve z)rp5%Jz}dzWF9e7CmxeLVt5^K_!qKY#Dh9o&7yW%z4)wRNyH6^q6DKxO^~&My8Svj z=i=ryJXeLYhDy&GF8Q*ecnL4pPRG5o6t#Hg8s6}wF}#VlYV@Bd3nJZ_p5DHWpM@^a zWy}AFJnf?=K~J)Zcdk_N9z7piVSYBhq4!1NPM}pnCzHhTB|@~XZIjIisb+-UW`q`e bOWIm+9^cWropv9S2cKl|DLxaMQ~3N}2l#Sc literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue279/test_mod_setup_fails.py b/functional_tests/support/issue279/test_mod_setup_fails.py new file mode 100644 index 0000000..80d22d7 --- /dev/null +++ b/functional_tests/support/issue279/test_mod_setup_fails.py @@ -0,0 +1,5 @@ +def setup(): + raise Exception("I would prefer not to") + +def test(): + raise Exception("I should never run") diff --git a/functional_tests/support/issue279/test_mod_setup_fails.pyc b/functional_tests/support/issue279/test_mod_setup_fails.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a6f6736a19314f4e3b88ef88bf294ac3ea16991 GIT binary patch literal 456 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 Gm;V80^d`^% literal 0 HcmV?d00001 diff --git a/functional_tests/support/issue408/test.py b/functional_tests/support/issue408/test.py new file mode 100644 index 0000000..a5e88e9 --- /dev/null +++ b/functional_tests/support/issue408/test.py @@ -0,0 +1,16 @@ +class base: + @classmethod + def setup_class(cls): + cls.inited = 1 + @classmethod + def teardown_class(cls): + cls.inited = 0 + def test1(self): + assert self.inited + def test2(self): + assert self.inited + +class testa(base): + pass +class testb(base): + pass diff --git a/functional_tests/support/issue408/test.pyc b/functional_tests/support/issue408/test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2231811ddd8994fe021daaad0da4eef4e9986ce6 GIT binary patch literal 1134 zcmb_aO>fjN5FIDGDJ_&eAovYAZP==Z-hd(wT=w9e$i+$%uZWZ{#SRF;iSi@(o%{mk zjkk$}kN}BDp4c~r(BdQX*Nl&WY7=kb)5zb;E=t7lLF+Xy$qTd3vmAPiwrKFtV6h;`F2G>Au|!Xz pW`NuO1%R>HECAu*MV$t&MqGwrIZAY48y*~rUcfkX@X4cR(NFAal;HpX literal 0 HcmV?d00001 diff --git a/functional_tests/support/ltfn/state$py.class b/functional_tests/support/ltfn/state$py.class new file mode 100644 index 0000000000000000000000000000000000000000..ddda8d699a258210336c920c5bc5554944e7a6c1 GIT binary patch literal 2438 zcmbtVZC4vb6n=&cY_cvEBBc~+OD$@`+m=YJZ4jkcG;K(cU=*sxC7Hm|&2HSC(8hE8 zE&AC{6^_8MM}L4n%Hy46c?nti#hkNeCNp=Qd+#&Pz4On%fBp?%20t+bo+~FmWX1H`0TE0+miJ@oC zax5{=a6R!FG#Oq$;u`w!7DL~TxoaAAVcEul=a~&(#bwH(V%auBf^eGqw&`pc0TrDq z67dR#Gz{V@MNu+so0p>)VTejzTv}i-5@!zPg=aZiowsqqw}gBf*CmHHh~7%kHhp0+ zu#AqIcqc~dw)y29d+;GJxSgB43|S9NER4wbW*xhcS^!?oi%gQ<|@WXS&0^J zlNu&4MXr5rR~WRcFQqe9Ymh{u$8!}kIt<*7A%zbZx{4&qupk2(7?zBhXYCS@A*waQ zaeZF&xXn!(XQpSSQ${5S$h3=s`@%Q;dadSq!mvffF`9m-YmF%GG7NR5)AFO@9?7oa zxz&s#xaaUP!*m!YA#?_j>PTZw!+m_L;|@NQ`F%njLk=vuWlof|k40ZW{u%qSQBh!(HWy$J~^7!@io5DP{2Cn=GC26 zZg)EI+K+`$DNE)lzLku9M_M4B+Kw>X3?Zz|gd}w9JOL|>)w<)z$~=i#g~^a4{3Xlg z4mJ8k*C8J2CE7*=3p|l^cycv`?(iSEqjq zWEr6`IT1NPbPsRlUZHP&>JS4{>{7x z(My29^!Oot`{YCXPkh}8qE9bseq2;aA`VkqK=z&hTvrNrZ@BUD;w z3|rU^_RB4S(W@d8Mvs)$s_#uAA&qzP5G%vX%Ge0jeC%z=L lxTILWxU`@kzo2 zzO|cSI5waglZn!_c~aLBS>51?q3M1@De_*IVDzNHmE4G_n7p^mB^{bRJTb+yW-r6~ zIwmFf7^E^c)q=(u{IOKqn2bgQ!tg_4Xe_&VX`vftzo8cyPQ(TlTk1XT{^Svj4MM8zxQEP%RK_Yq!-!xt zLr{dJlYa~ek2C8V5fH4!Isw6YhUns`hNdNKV2}?MOQwTxnPKCSUTws6AvEK9hGw_9 z{)(103%Vv@6GJGUnN;-&MJ1cf^?j7F8Cyc+;Z{3;l~QXDZJy+&y_=!SBjzPIBI739 zEJBV^Qk7DPYn0S>h9uaD1?k=}w^g`c|!x_m}e%4UIqrDXJC zkAU!QgRJpPu97_>I^v9PcCC_7t>&xtC$e?RKpgm*7dgGd#XK zd>2$bi2g8IaHptNZdOV`q^RU-2}la?lQITud?ADt#zB& zvrWdRz2{yTBleyn)*f3ch+_Yg>e$6 z79MZYI|%A%D{5Xj`0D~bYUQa!$tawr2AVL7rG%!JdB)%>S7`3(>gh@*@=oO|YDU;7 zC(4zQ=#&Z7%xj67+0t|nhKvX8Y?zdTJycp1mG2GWG?7^2X9O1+F2qt^t<=?T-R(9t zKJLM?1u5(Gvh8tPD&$`Eg(m~LlH-Pi=ZL{sxs;M(1=huB7_0Y1% z17x8mGTyU-{jXP#pjd+-!AGJ;TdR*n^RreLL`$<)pL$Sa zXu67!gwGjT7W!5!5lBX2-%x6d8)d?UKyEy(s1@#`r(83=1w8c!M+@|ZYXn%&+49AQ zNNsUq=3Q+PE)nAefD9`K=y8^=6eqZ0^-ijPdh%sY4GNluNA!x3wP8lnd z3zXh{nx@m`Odgi$xsaqL%kTw4Xc8@zTx}8!-kRxhux7f1t(jVlHB*sVGu?64Ojn0B z({(^|i0l!@G=89ee?-a`$D@Zjg-$vi@z5D^bcU-sA-A2Qj?TEF)9@?y&S2kV z@{i$1`i;^>FV=uaT7ZU#hIrycvx~k9$>z93*2(Kxv_<2y=~_p-iAUUa(o5Nq$O2K+Z71yVXKX1uVX{Dd(Xn$0%dN03v|q&o?N^YC=I5YZ z!B|wY=5(}V%{!xIYp(cwl*Yl1c=U|V=bJ&!$Cz(b1ao-g3eMJqhXR(+ZwZ+tJWvx3 zM{H^1Jn2wPIvxpGdjs>NM}H>dMRur-JB~+a7TNO8;H=1^$k*d|!jZO6P(r%q8j{DN zPhQ2-vv_t6FSpO(b=y`5Z{STwJ8J1t)}zTeyl-1$_y8X|^1p~1f*m{CJ32RhC(Z)8 z?a2bKh1RV!Vl+BtasKiwKA~}u#%GtA&9R?Ic8=0XyN(=>Px# literal 0 HcmV?d00001 diff --git a/functional_tests/support/ltfn/test_mod.py b/functional_tests/support/ltfn/test_mod.py new file mode 100644 index 0000000..b98d207 --- /dev/null +++ b/functional_tests/support/ltfn/test_mod.py @@ -0,0 +1,10 @@ +from state import called + +def setup(): + called.append('test_mod.setup') + +def test_mod(): + called.append('test_mod.test_mod') + +def teardown(): + called.append('test_mod.teardown') diff --git a/functional_tests/support/ltfn/test_mod.pyc b/functional_tests/support/ltfn/test_mod.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a04ddfe52a9df2c057b998a4e0042908bbb1a9e1 GIT binary patch literal 649 zcmb_a!Ait15S_GDtgwQA(StOscvtY`rH6XkO9@RDifI~}sUjZrBm7Rkz?pQn#hZeK zzDzrr_vTH~e{Ig+K5wcP4o^bs9lha-Km$AhSOGf$LJRl+*d?Ho7^I-n7-XO`cq~q+ zpb%VoO3*56js8J<&$&wi1n2}1-#gO+)v{_8E;wfbHvOQ>U~uv=fh%7OmG{Q!f;Phn z_&}f1kKMCEBeD0=4Z$>{u}0ClEw|;h+)u8>Zg9#rtP^CIydOrC7WYmr(*70MOza@# zfZGG{H#5=b(ae!L`l*?%=1HyX$EaoEFU>aMl)hG zN-&o~uk;2jz0xbRrG?U4aKLd%Ve!HXZ~O~>MOVKwGgjry$g6Ah;b=&5JO*!szp}yjsb?idfab;n4Wzsckaw5VwdDfNY(!6JSa$+#T zon3ig@tiDs69PANP^lpypf#jtRc$HIohuD4=;%Rj8r{$YdKyZ15-EYqIrD;9uuOZo zFz>llds)Laf!?xdS#l|fK7kvqy`);JYq(Az&5h%Flde+}cqBJm7L#7-50n*_4gdNsSEVYh&OU#;$i5pD?V z+0v>#xH*M>+#=8)RX5qR%U;#7HQXwYsu;_bvuIk>+27el9s6)wiWc4;?|+Mq_;50wX#;gU>3Lb4;pPuS=Ur9TM0TwKs^hhGBtihQTXM&vOk!$EeaUf&zo% z$%bdt>aeb%u+kbs7ALb0`4QgpLCkPoaoAm^s5xurdsR`Dit?gT8ZswsiG#IH(Lg z#Mo5~%WQa60a4O1fg>uWcd-&?99!zRTLrlvpJyCmt~%~4NgDoX+@lOX&cGe1+EwqU zz`3Z|t!mr?D4p(z`5ppZVf8*trm+|It8$fIvz|nWHRnmwU2-nk14R}5qK>H`zSBCs z7#REjBfTM&#DfCc{jAY(gKOY`I(I7|Vbj%0m9@@xnD4E2m_OsqaXJA~N9oO+l zaQ4eO&ID(f(f}LkEG3gL1#XOk>Vm+nQS5Eh%1Lw4k{XuSHFH4>Drrb8Gjk1TRcOyt zI|P}fD$c1aofp{I;*7Q&_Dx}Tih=t^w{vKEg)OXPd(yQful`?4tOfDM;igdsOFPa6 zh9rq5J5IZOK@yX=C~)0>zlV8VQDNrHm@m}b>IHV{f>)~-Y^Na&S6b2}9UC1REfy+a zi!m)jDQOfM&AMvI1@UV!5`IB z@OQ7PcI)r{tQx4l`-^Ia{_bxvV3}Rnh?Rza2<+W#Wv$L|xrJl1r3L9W7$Jtc=YnZ9 zWrFX>em+EE<)=?p`GD*J#Mm>9SD&ERwJB;Fs~WBl`zn-dI#YZZ&NOR_()D{TI|1LD z<@3`Du<3cH>6WF+puldL?eUg{YNN_TAG2+TF9TZG;9I50<}7dt+ptS@Vw$>34F{ic z!1$B{%BOq?_>|4Zrz~5aa?bjc$#<<|2lvIp-TCbH-Q@2Q-1bs#6`z{9ioK`uZ)0F&759oO_`*y+dtyY% z%;YmMGP9w~+=fgls^{TQ=JbY)9+i14lvxaAy57OjRUErS9|Ydz?@r$z!JSHUFIS37 zA&y83y*@;q;_l09yn)`WVKAFt!~Wq_+_{Fs59K3jdwCRm^9TjgP|e{`&29NiRL{&- zdNgVv$fJ7F5qlQ4(xZ~e5{c^Ri|BcDD?NSGzz!Yi`51kEcs?Aug0n-HVP)-gc$e{L z_JU6rvQPMQCi|pM@60~q(`OS2!e@FopZ#hgkywS55F&AX2J86dWjxp?_u&?IF{Arjt&RKGTTGk62FFh5UWU|9<|>|7xKRlC zF1{CX_tJI6-EkGgQ`zrd!HaA7;W~acw2of}x>EQRejQ3@eOclxTU^KQ18o9-z-uA@ z6GEn&+KvtlkL-C<-31&Bi2r?n`yAIW*M6??HT>z)8s6Y~lk2Zsf4?LGbYCU^Wgj!A^`LofuvC7wzl9(|C_`2X}8V2CF4p#@lLh0`L3> DG0+-p literal 0 HcmV?d00001 diff --git a/functional_tests/support/ltfn/test_pak1/__init__.py b/functional_tests/support/ltfn/test_pak1/__init__.py new file mode 100644 index 0000000..7087716 --- /dev/null +++ b/functional_tests/support/ltfn/test_pak1/__init__.py @@ -0,0 +1,13 @@ +from state import called + +def setup(): + called.append('test_pak1.setup') + +def teardown(): + called.append('test_pak1.teardown') + +def test_one_one(): + called.append('test_pak1.test_one_one') + +def test_one_two(): + called.append('test_pak1.test_one_two') diff --git a/functional_tests/support/ltfn/test_pak1/__init__.pyc b/functional_tests/support/ltfn/test_pak1/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd6403a36f329f02bfd0ed1efce25410bebffaf4 GIT binary patch literal 826 zcmb_a%}&EG40hTLF~o%6z%%UBXb8?Aao?f4?Gi;xCROXwsPUK(M|cRHlPAFTx*u)~ z(JIcDIR5;_&gyG;@%DK?nM3zwG(XWu4`dv`5};NvQ=puJ4}e()E)$T0%LVkn^#t_6 z^l5b-Dngpz4UOMOxZFi|F@t}Wft%)J85F~^x@t`1>23Rj)ckEtxm#tA4L7iq*C|N$ Z#4Ag3c3wtDwdbZh%6sN646{Mb|2O%*hxz~j literal 0 HcmV?d00001 diff --git a/functional_tests/support/ltfn/test_pak1/test_mod$py.class b/functional_tests/support/ltfn/test_pak1/test_mod$py.class new file mode 100644 index 0000000000000000000000000000000000000000..a71fc4fad63a505b89c18f0679ac95ea5152190c GIT binary patch literal 3807 zcmbtW`*R!B75=UzdnIK9LIjdHr8va2vSnMb5>gU}v?0N*f^A$oI4`KWmaZHnt6i~L zCAg463w^)K`_%v~^Z_lw0mlOa)89JNKZ@bIyK9Z@U3ogwAFj^Po^#JVkMEp&|NF%! zp92`hl0dB5zgJ);J7GHu`NpDq-nQ~(+mZR{#c{`|%dwE)>{ zTF|gXpr>q@rmQ5eRp8c*ht%o~4L1v<$k>iM?$~vKN3s(eEsdQEe~O64PeO6vT!bu$ zRL3pIq>#p~0tpqCP5%V)5odNbqEc`hZdWPTE|A$6)%2oERx~eyv9TC^s)i*WWEL z5)tzz7}4=Hd|ic{Wl@brLs~5Apumo>zkaSY3<+#0m3XA#x=yL2V?J z!?-w__qe)U>2EM^^s%eNpQy-uRE_D_hy6(ua1TpZDk*%GN&>Utwpfe)&Y9XY=5OF$ zgW@Z1>_@Oi090$Lxxv<9FkJ<@hmX@Nmtl zxkm&pgxzkpUhw1_#tL`RV#t-67ycs7A^#dVSa4$;|s!>L{3=*qkG zM&7cUvgAlp8lWu4lqZ1(Cs3rKU;6|c`W~SW z;KU^Y4%2eWlt&V{#CEXcWrb>itJz}Icn;0*@CYxSJsW}7b;TZwvK?_;u^83HLEAl! z#T0w<2Rek0$tUSQ$I_%xWBz)x9k&u!bx-H^Y{Z!Za|w*9raG4*vYyfLLp-IppF|9C z<;jF?RHUQfXT*M)_p&QzdGDF>e%p4Nu4Ax72cxEe7e&}d^|`&32>%6snZnQUE5_!u zEI+n;!uHm0iwsN0Z}3}{vEQ+GnWuGI1a?OxY@G_<6MMoOo0 zQ(An#9J4Lvp;dNmXGT^{zD#)Owbca*YbVArPUB|8qh`G)I$pqwDLjvt1h#rt#cE@# z8>1KnzyIqcr6AU&K=7KH_8#|!8lxU}RSiXtdn*DW(~GMa((q@2zV*J<8%&auKRR8U zlTMR)VUoKq8D>ky_&n@ocSq_^o~-d**bRu$E6q0_r`pECthppLyhp^V1Pa_d!3W<| zt3EFsZ|buD`HU=IoK!GP&Dt%eELGhEcG7K^r_9xwHJ10NW!b#&>0y&kkOIfEz+aHW z4mE=5>JBx!Jj$!Wqr7fB%4^-DY*LT%w)7}3A&>GxAx+XAbIkQm{`)t2_!nJYn1u3Qu^vEu2aF((X0V z>9+K2I_d50StC9DAsttB;y@1^Pm`*;#g}kJ)l*gOY5XXV_Aw|W-S#O8XEHy&foGTT z(-r)Ba0S2jZ6)ys{4vnZc)F~Arm%vSeQN@*;MG9>AEHFzk`7@?py!K0TPBumA+g7+NKTQ6bhZXq2N9 z)Ii?8WHN8xo9yOediwHyTP|U^5?b%+j@JYhU=7eJSSV0d!5hFL0hw&`pU9d3hsf}^rf z6N2+Lr#2V?=k%yU-#i+08aF}vF1osRtuwUFidk`^S6jctrt_w)d7r4`HiWK6-QvpY zy@?%T6X6*-pm<4{uw_tx3wTK_d+jkGI@8Yy(heU)-7cB4c3AG>rsz-q!Ehenuy6zKg>V0Mt5hjO<+ KVbdf_v-A^ek$&U= literal 0 HcmV?d00001 diff --git a/functional_tests/support/ltfn/test_pak2/__init__$py.class b/functional_tests/support/ltfn/test_pak2/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..2e14e5cd7bda61b58e45cad8f9533e3451c0710f GIT binary patch literal 4037 zcmbtWTX!2*75SK(=fvR@{=rA&{nZQLtTO2e(N|r(^lZQ8Jnl zqfrtArQFM{g>o&|mOGSN>a>n&XcsTM@W#LBuVC?=nX#6f8F{f54@diG_CEXU%eVJF z|N76HZv&XX+XAWT&|!gv{EX|Z78`5+Mb{}-Tu&C~*QPzIE~jFQvll&Stt|SMFQ@$4mP`8tiV1giSE532H} zzHItt!@67=YA{?ht;gg~l0zYwrgR*^WCkTXz|5JZf|Y3sEXJ#$6ZG8!w4PNG|qLN<=3G}b#_w6 zli}G<=(rG`WlBSAsIy#YSORw^L3K&s-Z=KQYUQl8Y)cI*?3(#72GuMiR++h`w5zmd zrW1n9QVo|>mM#nIZF9yu9ZrMYDFN?}lmc}(5SHpCCO5l#R(0GKI&m;#X$kAtsloNG#rDe;LX?%`lw~rFu-^KT`_zu2L-@KL+`u0Fv-}YmPZt3_TUQ!YJ z5r;nGwC#w%p@e{K6XA2?{&?Xov>Zp(Y};lHKNA?H{^^=69X@YPyAI>fs`#$AAgeYX zE}V&Nae-22mpIL?-As7%Y_~+m%XlS=pW_z-1HnkysvI?0%1QA1zaCl&Vm%53zfn^m z*!@nm+hF$x)j)&YA5}XHc7IL)%lz6_tTg;p;NW&E>kWp>E1sG!FG;V-2r=A!S1h|F zQ+!7b@*$EaKYO;u2V@^0CZ1`&a*blQr>JABYPd=4t5C90%TSOUty z6i{}jfbt;_P!5lPvTOs&IU7)puYhtK1(c(OG)w)AKG)y)?;Z5>84LWQ(7S=Ok?nm8 z`rKH`*u6Mb=-I>`?n}pe3&!sKdqWC@#PzMuFX8tA253ZrU;l1 zx0wsMklWY6oo{o`=Q6?B{toUFZSHbT54c(f_vzP|HiZq2oyff$X^_lyJg0D@5b||= zBjO&U>x#SQ7E0%gZ{EZU8~FAnelW6$ABVa!_z8X*NgIJIab}b@@yk$~z_0M@i2n{D z(@Sk9M@GjEysGX3j)o-oKE!>VYn1CS*W?C%dwm1H=X#avPh5YwE<$wQBL5{KI*(or zC7~&@i4^zIcC;Jga5u(eH^$L!j1%1$J@`BA>%ld=MY@N(*BFEKGH&2?wK;<~{tG>t B8v6hM literal 0 HcmV?d00001 diff --git a/functional_tests/support/ltfn/test_pak2/__init__.py b/functional_tests/support/ltfn/test_pak2/__init__.py new file mode 100644 index 0000000..88a2ae8 --- /dev/null +++ b/functional_tests/support/ltfn/test_pak2/__init__.py @@ -0,0 +1,13 @@ +from state import called + +def setup(): + called.append('test_pak2.setup') + +def teardown(): + called.append('test_pak2.teardown') + +def test_two_one(): + called.append('test_pak2.test_two_one') + +def test_two_two(): + called.append('test_pak2.test_two_two') diff --git a/functional_tests/support/ltfn/test_pak2/__init__.pyc b/functional_tests/support/ltfn/test_pak2/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f25b6dfb917c2b145b0aa88f6c1088d706ee14b GIT binary patch literal 826 zcmb_a!A`?440YQLF~o%6z&GsFXcL@4;=V(7+a-#YOsdwUQR6Woj_?tDCtrZ=cB^({ zh*ou8YWvyo%l3PG_5SrRUqSbzv_8`pAIVsN4M3H^GJ(V<@CmR?!KMN-u$h1X*nxl{ z*dZ(?7nC_kn1j*ERF$(Q{4eagC4&HE0z~}5{6M)}ejabw=OXH0HnTiB+y(JqPbkoY z;JlqsH1q}+v{j)gUky5i+n{_Moo-y^46W0An$OgF=T}(P-c*_kMHP1;)D5Z%*IpfK zE3HdkqSkqM!y)lXFBt>w6q57Kbw?>j?i`(IY`yn#w1Vjho?3qrK?N7WOEz6R58+Sc z?}g+rR%rzHwXVJUO$*nD>@H{Y&utLx*=Ijys}t|5h3Kk!!Cx(mTko$1`>y~!A+>B?YOovBq?3i^2kxL+7-L2 z1o0@eJo_$v(Uuly>65;K4T?&dw!F{jAJx-uRx2Y%D<94&Kdg4VGjs2Ezx&-g$n`BNq z_g%x#ah+PV4c7~7QLDTJ`sg(n4vYyT4Wr~NS1f553EW6ihN0l7zitvpwPV-t83Fxp zsqBY|_5^mF=qv*rgBa>T8X19|?dFbD?7UxeYz-sKXu(*qoF&tu&7GT>%wl&BLnnSE zqkGB&dx%^q#v3SCHS87WGB~*_!bZ;a<%)DCbli&DdN7XLxs`l{nZAxyu! zDAEs*&NHS}A>#)F#tEaz_~%sWx8V@M6%5Pt{Gx!!>6pal73jlM{ETBu9e1fH()a>3 z65(>vS(Ze33+`4ZzbMdspvbzQyHioc8Y0{z6PwPz(SxR){SKt0;P7atqr8J^UgDyCTJ1&D)#TwG2dYBI7R*u z+yuT%yv#RKqiUNryB;BDAba9T23;z-2O;J-F~uJ=*)zUWymBo zR1oM58eqjQTG>OcYgRoCCn>c;(P9Hjr%$vr$S}R_UsUHhh0{qC!A)03L^il6a6{)Z zUNDy|slnm7l2*p1@n8~VxCG)!izt&*%`&C7eN>dT&!}?cArw{eVJ~Ng&z9Zd8J;;= zzf{iJjwcOQT9QrA7#kfM-IFbZoy4?^z(v-pl-2o=wfusg&j#N{%hd!Pj_k<|M;acb zx(lvTQhma;qM082zR;2 z4jQ(zZ{s^EvhV5;jcWRS0zN}vMNXSVn<9v;#$Y!^R0`H?+o0w!%E841_A15MNmBk( z9Y4g6)ad6C=Z-Qr<(SLT)$j`nJfqF>`xMcQvD2lBLGi$H$_VH=k4CU#?c#m-f1 zTNUd%W(|K37^M9pMN6tXz@%f7hf3ae+&NjWc&Fnzy}n#vPiuP!nqkCsa3c~Oui;Ng z{1JZ^xI8!^npG<>pUy=o@c+7lDwTC95u8)qI~c91BQ+Skp^k`PREv54dk zE2elwo~e|Uq#M*AOP?3-{OM^W&*Z#Qar08;OJE1#`t-6`^omUDA=`F%BOx*m{Uo4F zCjJf$+f~yg)a|M_1#e!1f;a0ic=MDC-n@7OZyuw3_i#VCnWD<0`is6|H7(JGG z1AFLcN}R_mLCl4?dL!1H2-n;nuBpMi6oHB~LEqF(h~Rc2QV9Aoy(-pK?3>BNYPciy zSz5W147A14&7B}_>U`bGByA|!WHP-yw9>F}54jo+jd4iRH5|LQlR=dhc0B&7w7kmy zUX;NXhR(wqTEp4YBXxXj4Nu1Fcytk=8oH^Hmq=)o#!tf8z98?w)(aRvmU`hlepbVab^L0mj^8$H_275-eYiRmSR%)%@jCv} zuqNLM*xhMjZ>Nbayhj*aco^^Vy_KW47~;VkF5n+(a5pag E4=}S(p8x;= literal 0 HcmV?d00001 diff --git a/functional_tests/support/ltftc/tests.py b/functional_tests/support/ltftc/tests.py new file mode 100644 index 0000000..02208a1 --- /dev/null +++ b/functional_tests/support/ltftc/tests.py @@ -0,0 +1,9 @@ +import unittest + +class Tests(unittest.TestCase): + + def setUp(self): + self.value = 1 + + def test_value(self): + self.assertEqual(self.value, 1) diff --git a/functional_tests/support/ltftc/tests.pyc b/functional_tests/support/ltftc/tests.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fed34d55f1ca5a5b8b789338a5d64174762a4dc2 GIT binary patch literal 627 zcmZuuO-sW-5S>l6*heIvs8!YjvIl)8Y>5alM}!Fg{0|}rp)``C1zZqm$|pke zWv;!qY1#W&4y`hfP%OZYfTZn;ZOT63s!i%9c-Q&Tr})pNv*~q`_jSfaQ>Ue486pXN z+cq61CFh(ax;Jg#v{UfBYGvFYhR7VP%SEX53ncIjtAd!;&@LkLcL?FDSw2UWhTuDX zco5fu9+l702PS$T|G0rfbFegGFmVo3l1 literal 0 HcmV?d00001 diff --git a/functional_tests/support/namespace_pkg/namespace_pkg/__init__$py.class b/functional_tests/support/namespace_pkg/namespace_pkg/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..c5703151d41168d7e1d685f94e404d335c2be30f GIT binary patch literal 2730 zcmbtWZC4vb6n=&THc7XZrqEKTEm+h*AS+nZmWWc6HkCJlqNVC~NhV=ov%Bt2DA7~j z|A&6|Q-x#7(W5`WALa2*2$UonPS25Z$jt2Ax%WQLJa=aP`S*{%0gT}%hERU+4Tj~! zoZ}TVw=6atTgy5g*Otn&o>AnJO%tn|9yf9;!Vr9NsA+boytuK&vtp9rQVW*~LJW$} zg=N~DA)Lqzt*K~7M+{*oH05p=O2V`vh%#JgbYd1=1!owd)NwqqC|%r1%xxJvhGrRd zL0b`?X&0LFJ_2+~&MMC0r5IG4V~EHEovdLoXfNiz23&$UkGKSLfxr~Fu!%>8RDhRL zbLqxK>Esf_8NMsHozq=IY({XIp{J3RBia@8k-4r@R6__)*HyeM-CV&{f}~eH=z0YG z)sak0j$Wyb2&<**48>+KoZR!)Ti$x+RGfo&RVMfvk(Jji!xtt4%cw|TC`$N;8RE@w z6^xK{796?#y{!YQNI{EY6eRMFv}v1SilIAbC)OCQHHVok*;!#a_NuXAaRoQ1U=nhJ z$MIGS6L_0)@VS+z6z3$FzH4N;PWl~m%TxiKjo%bZs;J)xRzvRlg(CR`rjC5Txq3bEP9Km#; zoDMTkFvFncJ*Swl1@~;8dvP7tFsS$tvoU;tJ1XA8`x1GEA_i>PjFaP%w8m0aFh}}K z7oA+m;!_bUkWg&?Sei5ZoX9*3DE>wtlg&1n_4R-x>Nb2b$ag@vOeRR+RXi$|@NtX; zxU7QVBME=?@Kl&LOq+H;(Q+wit5~(Umk2wB4BmFz=o z6V3kWmj%L7!I77Y6-Wzoj@2z;7ziM&&V(d%y^(;W@^ZI0$%^Ai|o*FZBPWb`XUwtMjim+p_;4qx69QL~6G%YD&I5$U^|3ay_!M*C)N)=8|itP5h6k=?}DS25gcZT6I zZJnSfYo>3~22R_yLw6fx=wpDOP7vlDgwP|;RZ6u-o-);z%8l9%nkQE1|63a6=$#R! z_HHso+XuM2k9(oeUzne#5R0KuXb)2% z#zN;iae$S5-2aVMNN-fsH9wuyO7G!4+?P>-C_cr5+H60?kh5KXBK;t~{tTa0@Wlag z!w1+r^c97Pt(tYbYD?V2(+6-5y)k?VuQs0}jwDN0het;HewDE#uh0LwDiBZEA@=X- w^9{n>gRp`$Q)KfELe#Hyue6$I!wneE{3^aN(4xG<0}?~xE* z{hm)Buhlk(z6LTk(taT*07`%Z00Ec?CY5OfST?&aL_qQbA4MwHnyY3~TsvhOPuNmLT4t7J=~A6Ko3m-G mu8_&ISH5$uX<3Em-szlB68$+uV+|vw?#?0pI>3hrljsA7(Lo0Q literal 0 HcmV?d00001 diff --git a/functional_tests/support/namespace_pkg/namespace_pkg/example$py.class b/functional_tests/support/namespace_pkg/namespace_pkg/example$py.class new file mode 100644 index 0000000000000000000000000000000000000000..9df8e999dc66e6c24cc2de720d9f2aa4abea8396 GIT binary patch literal 2322 zcmbtVZC4vb6n-Wf*koNOjkHanwxLB$cw0kXv<9hwqFaF?!Dy)(mt+zaHoJRvH?;ZS zZ_&?ws&J$nJ?#(hM|r%Hth7m%Pv)FtCNp>LeeQFgx%2lwf4%~chsz+&Yt=IvywJC?z2|YGS`3VU7jk{Oo2^H?`@9kq7_OY-QijAJ`#i8r zn={0-dak5m0D}tRkQvf8#r0jI!prVKUE|M;hHLRjw)?!q(#w2MdyXm1nhTG=xEz+_;$ zic7ehr1dKdss2=DTqOV-jz}>IZs=ddIIblzhIbeS7fjm>78x!@K+KgGZuEy)ZQ7N< zbnI%(LT9~n6l|^}d&2PG{;{{rGe%=2<5zV=+1a7OC??~h>L#i?@A??|sq zWPC(QT4*@cro|T%_?QG?^QXd`;g_ubBtlrdJ_`Ld`p!-Sh_1pGqI^e$)Agtu+0IAB z0zOelXp1TsJ{0BtlbhD>+#hD$Vf+R~<8;+{{L6O99hhSlT}y;)-nQT?0=PHz z0`E>8BbS;!MgADWsXOiQo+Q1*{q>pYR6&xYBjhB;q>Cds#e-uk{Z1!^N4m42zY!W4 zt;LV9Bq9Sze1^}%-3-MNyQ6q_shQdF-$X1y+w0Hh34VIcI{7zf`3}*RMqGx%WHIbRqIo#IcHTw| YHl2+T)eeoLG^7|55rz)KQd zv;6T~Hm&(aCUq@IxnOV1PMDc(Inmi%z$YkTA}G!=U8yP6z*7Yff7C{VDSu!mt;M0c zcm~S>t=ioHJ$R{gRaXmp7-P#_ioSQchb)bVX{N)-97i7$9KMS?*03pU;h9wfFA{-vt5@7 z?C&0R+(M?j;LkaBChxd1Grln5nk6~hAUH7RN^@qyH+?zW(;yvRxNZ8d%=^Ov=QdHP zAu6DG(zlAX6o_?adnR>kKuZEKXaXB;+V#q2UK-{3LL99E?T5`HX2vq@LT18ui*`Z7 z8Pp+6c*=FO)bR1IJO8RPmghYLBm!7eP5~U*MRo~ zwyx{rc{sln+i-zEXQR52ik7KVIJ)^-kbYIOKz|52>(EL^FVYH}9ReMV{#P@hp}Fu0qHu1 zL7sQ_9MW+a1`^0%kgQXV#&Jckj_#Pg?;3`VtCaQ(t`^u*7eyurjC|}W6Z4${5rc7s zp;9I2u#REuYQ+$)rEx>vFK|^@k=3ZPZp7E&dgap|dYyA@sbjCgdmC;PXxrm?(p4GT z=emv?#|Z1EWX{Wd$MUkQz6(sOW4C!$!#rA5%II!JHbEb5QAiBx7_A07#;Tk(EYtIg z0wSy9HjFE3+|F!;*xKvNNb0&26AGyV0xi3WcG2H0aCjZ3*P(Ya8zJ9UkY~M3;?4vv z#9gegM*hZeNZ{-+mQD&>&`80R5;|Z`TT;V40rvtUMgsTZK4#IA)-20(v?;@g$u#l` zlNs7BSNxPeXZk76b{tNfRK;V{ql%CUSm}>*YzwpOd0E?+t}UtOzgF5Rfe&Lofg;$# zA_fnM`!Z#>c!X^~{Fu1l8>}99( zLT*9zp6GZJZzb>s-ez;=mB2DqDn&KtY#9Ci*VRi=ETTy8zG|((?86W`j4!O9q2XhJ zRJ|XiG68Wjd&aYq()EY|0o`!Kv??;nyJM$nUv==-){h-1^3u2g5Cac+pC03~x>dYb zuDL&I_=@aSU)!?zEqfl=Ui zI<|y(G7*N;U8ix~B2u~EaN)u9 z&$y&-5qF4FIG9T(r}~txTsra#_Ag@J3ECIJ(gI;D5Q;!}$4~S?!O31( zGuqBk!5dq|aRocjibwEhO`4)ZO1k+s3>{29b_!1{;i+Z3(7TLRs=8Y7DqgFtP6lfk zd~#?R?^Lx3yo>j0@~4J~*+2k4}gx3DmMNda+Fb pI3#K~u7NbwOwiR#5W)9U8i9i!IBw?bQ#$xr7N6q_HMs>}{tK~{=VSl? literal 0 HcmV?d00001 diff --git a/functional_tests/support/namespace_pkg/namespace_pkg/test_pkg.py b/functional_tests/support/namespace_pkg/namespace_pkg/test_pkg.py new file mode 100644 index 0000000..9315c52 --- /dev/null +++ b/functional_tests/support/namespace_pkg/namespace_pkg/test_pkg.py @@ -0,0 +1,6 @@ +from namespace_pkg import example +from namespace_pkg import example2 + +def test_namespace_pkg(): + assert example.test == 'the nose knows' + assert example2.test == 'put that snoot to use' diff --git a/functional_tests/support/namespace_pkg/namespace_pkg/test_pkg.pyc b/functional_tests/support/namespace_pkg/namespace_pkg/test_pkg.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abcaebcf42f3fdbe88236f64ec9608c62b386158 GIT binary patch literal 535 zcmZ8eyH3O~5L`Q;Lr9@OLbT~fb8cJADJpJ$$X??3p)jZc7q;ba zhIposE$bM-poTb9hHDPR4Lq~TE8c#?;LpsaXY=_GypA`Z zC!t}8dHAfNR6kY~*C>H?S7MAxH}tP#9M_W=!&?l43zlPvMTW}}DQ3$IH~Yh^ww$W4 zTxZMNvAK!~deV%{`6g~_xP?i|A>ekMVZ3-Quf*9qcvo_kq1awaq{Xf&(t+c;v~|<1 zfG04;Fxnr!%b$uF2EFdP&4MGi@9-MKToi@~o==}`4u*zVkQ`$b`r{a$Zyk`z4AJxq z#|?PJ=QcNK&CTWJ=8bxY+_Wq5nFAwed7kSFqZemj2|i0|+BX|KICJ(=TXSAJfjc_p zyUMvoP$M#3aci7(J$zO@Dn1~AEi~O)%jSy-d`LQR_>a<@;pa@@X@uU+`pEa&6t=e` zlIVHwe3b8qaD_rtrFQV>Sinab>2Fa7!~0SMA5;DfF4j!TA?aq$y-Lz*c5JR>B4TFC z2_&Ts(;E5wOvf@-pB60A~-5xiu{!e1s42EsR~Sv`nqQymoj zIm*wcS4rS&Sl3X*W5VXQoG!L|{r-BdMZ(hYB{n5vCDH=%)N_SlB7(3U6Oz#Fvjl9k zH(QP)OZYTr6<;%C$^WWlb1CT+*C8HSRpI)ZylzvIrLsRYXPAGz;8v(j1rhzZHxnIC zu&v=6JY^W7=4e-578OG&Q;3q^{~8KXVlgR#N`weraiGFvNcTo>dPIY7ENv9Z+z$vj z(Kv8m+AXfoEE|*kt%uamer?O5AvOT8+)u$T&&aIDiU!?l^GpRb_PC^o;bM`d(|W79 z!~ITcr3$B+SKVKeLexrb%dc|zGBmH~=>$b7TY*Inxa2r44M)l_zyv|PMVNPJN{q@L zOR0{^`P5md+jLecJ6Z?Hj@Y5!efsYX6uJY9PsfjuIK+kZ>D2J-F-F+>bnJJG9^%Gx zI%crLt8z~R0_o7fGKRv+yR?qbu1fK11#r~m<`EkI0iLCLbpQYW literal 0 HcmV?d00001 diff --git a/functional_tests/support/namespace_pkg/site-packages/namespace_pkg/example2.py b/functional_tests/support/namespace_pkg/site-packages/namespace_pkg/example2.py new file mode 100644 index 0000000..47f20d0 --- /dev/null +++ b/functional_tests/support/namespace_pkg/site-packages/namespace_pkg/example2.py @@ -0,0 +1 @@ +test = 'put that snoot to use' diff --git a/functional_tests/support/namespace_pkg/site-packages/namespace_pkg/example2.pyc b/functional_tests/support/namespace_pkg/site-packages/namespace_pkg/example2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59959b4846651647d61fa9a5f1d2e94c529fd28b GIT binary patch literal 236 zcmZ9Gu?oUK42G}bRB&|f*u@?#?)n5BbaN@8##-#%g(eln7x6iL0&^k`2J-#+6B6QH zi}dka*RAG=#yXFCGLJx(kK3s+`48f? zv^4ft5VYdR`oP$XI3TE&MOjq#H2GHg;9+Ru9kx6Xg;A^rhfL67Lzm8SFSt^7fe!g^ ThBt6DU{%mv;!~Rlj&HmH^u#^U literal 0 HcmV?d00001 diff --git a/functional_tests/support/namespace_pkg/site-packages/namespace_pkg/test_pkg2$py.class b/functional_tests/support/namespace_pkg/site-packages/namespace_pkg/test_pkg2$py.class new file mode 100644 index 0000000000000000000000000000000000000000..cea159e9039b828b5108219fa9803cbaad4a4421 GIT binary patch literal 3644 zcmbtW`*Ryb68=U~yt3FhjtJm51{CmdwiR30JY$DI2r(zl7QQ$b5(9f{c`Pel?JB#g zB!=)Nyr0M84vxFz4(fPU1qU1#6i~&F{87|>vr>#@tz1;0s@2SH_w;ms{dLd${L8mL z02suV0@1nd>je(>j5%&0Q(pAv9XpeET$!0z9Cgi-9BB|7ns=o+JL#Lg9O-S4PAuL# zb5!R25rHe3nA8vz&^+l|MOzBQda}J!IyRvtffzJ_%{KS-%4S{~<%L2Vtpe>w&Esaq zGVMZU(szq?LBl1qAy1m6vL)l#EO6=i-HN5MhAjdKP8`=Cb)AyHgFR#GO^r-9ehLYz zibl3~Iz(0rS;tl+6KKb!0&x|elQ%7aOvs+ijVLTG!{rK#D`=(`;!qsh1(NGyoLJPb zLqI=JD*H9yJ%Jq?`gkR-YDE{W7U*m=H(Igte$lZt>=bC7GYXb7V_LM?+1z6t*P^?X z{{BJWGGBVWQJ27>?m?O|j9EETDHui^J$x-lziL`wAOxNbc%`EcX@$-%fsV!is;SU0 zAaIFc*pB^(bREMW(R+H2=(rw(31l!t+9^-txG~V9JErfuhN0tTWj%vi7;;@0nIJL} za;UpZ)OQO+3J{I!3Uk6~nlVyBqSDz|CQ0RwK`bA>WQWlvDfYcFwV-j{OSp zF5D&1w$JmVt8#Y0bsaa3QC3jNT#yG&=4DxZ7ns~&-{yG@^Jvv5qx%!G3Hor4LSjhA zST)#j*5#aGnVw%15Lq4fVnUJQFHBd+Ui+O{Nn3x!q(bVDK+B$@UG(<~9Noa_4G7+B zkC5*dk!QtC;r;}!!2|3gAqyy>Sr+i^H`QWcMlk2XSz;9`%QXKR>a-^<#*bZtpH zzqQy_DSQ+Q2^7IL*5=R4llP)si#y!Fxqt_OtCTm<8#rldTrtKzT8aV>-%Jwy`tC_mrI3 ziY52PQ6cwic|vhY;7m_8tg|39XND#Tvnjet1SEfLrm0$802?xd&aj`Y&kMO2)qA4jZM>7fTX>hbZSM)%^$zEC8w4I}ZH@1XR3U;6sPvFU#G)0G$blZ0rKAe2&0-jmM z=@q=(w}RKJwp#H9-mGaS16>B69A3fStJVbmf%j_i(@d92*mZro`gbUN_)7)<)f9Jd z-nEQ>o?FJh&WS1sl-fAG=u!XR literal 0 HcmV?d00001 diff --git a/functional_tests/support/namespace_pkg/site-packages/namespace_pkg/test_pkg2.py b/functional_tests/support/namespace_pkg/site-packages/namespace_pkg/test_pkg2.py new file mode 100644 index 0000000..44ed6c1 --- /dev/null +++ b/functional_tests/support/namespace_pkg/site-packages/namespace_pkg/test_pkg2.py @@ -0,0 +1,6 @@ +from namespace_pkg import example +from namespace_pkg import example2 + +def test_namespace_pkg2(): + assert example.test == 'the nose knows' + assert example2.test == 'put that snoot to use' diff --git a/functional_tests/support/namespace_pkg/site-packages/namespace_pkg/test_pkg2.pyc b/functional_tests/support/namespace_pkg/site-packages/namespace_pkg/test_pkg2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2d933ad98f6bf7a55ec185630aa341678ee6419 GIT binary patch literal 551 zcmZ8e$xg#C5FI<9BBXFZh-+@-Laj#J0U@qEgj+9_)tWYBv6Y#yh#TS~_)fln85gNb zq&)k**~jf~diwcuGhM*{r}@0&AFmje05w3PV5UGVRU{f_nu&&QfLQ{|1fKFE_RW<< zjTqd!ZA0TW!pT-B_KG}+d%-{el>*NGq9Kexk%liWM@f|Rfy^yEu|?YHj-+RDNF-_Q5nWG}?;iW!_q5w|&SuyGd8Q`x z)vG0Es0~KS&wJN6%jaY~85ibx=oXUAHghR~28R$hujy>-FxZ8ghk9kOB6rQKwkwAR zHi;9bVjMo`oNGp5FTRiwPyp1Wnr=o+neBO%VlQQ5{kyU7wHM4&*q1Y03#XqQw#F<35zVlXYGsYO7In{*Pk-R!Qrn?h8) zWbIyA%GynYi=idO*_=zD<9yrD@ zoyeJXMX~DcEz?j+rp=Y9`nauDd8T7y=9bOXxxA~oJd^C$ovL3c&hwI+Vd&ieQbK@1 za=5E&24@H+vdP&9y3r#e2#F!qc}_x@A#$;5xh;5yq3`C#$|Kl+Ze*-LPa-=s!o`DH-p@cSePt)l3|OhRk>4eZ<_;Y2ASVctCr3Oth$6<3|$3U z^h3(Kwq{f^5$wjEFnY0?a;JQ@;=U1wjXdf)R4xM>cD?yuxkL5XT{* z;VFjQo$fayBw>KaFH~!!`=np|TY=ovsKYoS<7p%)pyh(DI}NQI&2v}S1TD8u|FhHMh=jdb{|uX;r(m&S;UD4wAlXa_We6BHGZoXw~x ziQK%ps4BW@RFqZ#H%EOI&xtslB6259gGZ1Nc_ZUA!%iQtagX`GXB7Uv1w;d+^>BKIi$h|>$WC^#KwxFE;rXLNKl-gYiAlJpjj`)nO4a(<+_ zO*8uSWI}k+H-(x^NVrS}lx?#rGS4>n9K*1mk3NjGiy?;z882Z{}l z({vrzRxO58er>@srPD^S+&)SbVc|^~CT#M}t{F|=PITg}J?86H1TJcVxJ60{;%Uth zhC@Dttr4L+vA?s@r)!2Gio9*Jgtr)ygg>t7+@L#n%ruCHTFEu-Xk#68alRHlcCXS|JuLc5vC0q(H7T6xe3f@>o zF&OkV1F^Y0Nt_IN5@!mTc@|&bOEI~Oul@t%mM@C{ literal 0 HcmV?d00001 diff --git a/functional_tests/support/package1/example.py b/functional_tests/support/package1/example.py new file mode 100644 index 0000000..d758778 --- /dev/null +++ b/functional_tests/support/package1/example.py @@ -0,0 +1,8 @@ +def times_two(a): + """ + >>> times_two(2) + 4 + >>> times_two('bee') + beebee + """ + return a * 2 diff --git a/functional_tests/support/package1/example.pyc b/functional_tests/support/package1/example.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fefff27aa42aa6cf2776b29b384bfbec57b6d8b GIT binary patch literal 352 zcmZuryN<#z5Om;#I3&9J0J@kaRuFaWBq&je(9uM`+=3$Vu;S$vL1oIMXilz zg|vYJG}SN&gPGbMstohG(nX43(JFf|rfyIfo$d7wLIr1CngRlI!B6_IbIp702X7k) Wul16!t=5WTEIL0)=k(h>W8noI*Fu2+ literal 0 HcmV?d00001 diff --git a/functional_tests/support/package1/tests/test_example_function$py.class b/functional_tests/support/package1/tests/test_example_function$py.class new file mode 100644 index 0000000000000000000000000000000000000000..c63b8ff442e32d2827a3f26a8a3e631ecb2d62f5 GIT binary patch literal 5193 zcmcIo`Fk5j8Ggr-y|TP@6g5qAwM~ct+lr$&O-dSv5ZkqbTiY>laESwK*7C|xvf5Qw zt0ZnHH$5m6C=_TZwA@gRCU&cchP2#Axo>E>9*zS41P|}5R*r0~OdpOP#xt64<~!c+ zm~Z{`Kc9FKKsWv&5K6ag6FAsDY&$c_+^l=dwvs8^F_L4mgN~jxdTI!VjyZ-tJ?`qR z(bG{w9h*Hcb=*j~JpxzOVW}V_pyUnL%vgp%xP7Q&LPaB*q6kAF$vCZNbEXkNv%uv` znPjp#1uX*2T-c6#K(g51K73q1sV7a{nn{kkPR5$4)w^QQGCHfc46RY9SRoLR3fd{% z6i7Zl_P*iLn3af2V=gC~f|YSyBkx9Vg}|Dn)>Pn9a3u|&FuGk*L5^a#WnKO8f+BUAWsdHaULvKDK}$V+NGN>5V(4|6K&lB zQBBL*(*@Jev6{B)FKG)$T z67XGwSkLDT#~rw(pqmlwrY_`CRz}61vR_`x-33Vzyo{0HN%tdNOZzTp%grk26=={1 z@cIDxLzZjI7*3Che%#QEJ`8xkGfYiQ6Bw_}F7?p|~V5+1wQ_s5@0WqZF6*wrJI?iG;YFmbiS4uB;VuCmai0Zee z4I;P`uaXEJ7HHa&VV3R{I9_W|B}03biPU;B0`@C-vq&7ltE1S4*9f#O4#f!Q$`(cu zjg%FLF}D#h1B(4yjLda z`}p2i#wox2@pTF=&zH*M`|$y3;|FEUVuKU(Cisnlz|myR$(&?AlXSDWq-Ey~%`r@a zZ>ZM2wR`LKWTiaorY2R*C-a4zY(A1XJ#|8#F}joG?La%W=4K;!F!1d!YoOpGd>7M> zot5f4mNEVO8vx%16%XU1Q9Oi?sdxY%l8Akxl5JH@k%CY0x!6;Fw)aNx85UBaD(j)Z zL+wM=`B!P9Y8$JXouSFefMBe=&z}E#f_Mjr8wz}N{et}nJ|}(pc@?4@kY9}8-g4VC zqGy;c>jJB!cbn0c_0FtbRAqcCJ0>FNl@<9NQ9@T#aSn5GRU*%&kuBK`+xoQOD0qSy zkzzaS8slt7O{34Y-Ms7QIf3hgDuky>ZFg{utuFXLhq7w{x)a|%}3ww<;77GDcM zQqAa>1uqgnrMMv1iZ(a((*zCcAWy!2w&{@H^1UkrnFk>3B3+%To z;!sGrwsX)(oBSYPJzF>~u)VsDc#l-Tx5Q#iRD1{DjpEyQnsw4^cq(Zr>*P>S1pZea zFfz&-WDxvNzN+5p$FgbgRzH;$(_8&qR#9*DOIawr)vp7><`QZOo)Or%Sl?`pz&Xj@ zv7rgW$rCFA-*{3t3r2`vYU^s6LI3>7A-1nQjezLBE&uRo9$V_Hs`gL8pO^&_RDl)4 z{Pr3xWTypGFiXXx@?%d+`BK@IcRL~!mQ zO3y+*X{^*#b&YHfy_t2$o7t#(GjrCP*`atd+b?hC%j3;_i@ceS4d)i}CH@?L;jd@W z#1BMZJ`pY=5|1WIh>a%V(XJv^iP1!(++6K%uJ$(-ZiX*n4bQDEVnh7uA~tntA#FUq zxoZw-QNr=v;Y57%TC!Xvc89yTlS{;!d2J3`N8=7p-B~&1EIHNcpSpl;hZB!s2hZLu z&f~QdcGQ>O@Hh_5;pVecTi~z!yV^5BaMi#O=4j?Ql6Okvhju zQqdxAyGai3Dc{qq=!f4GE?&*PJg z-p|2M=psHnLeG9S6bjAZzK{^1l`)j?g?T)>K-{c&#Bdcc9cIe#W+;AkoY#*=yc3Py z3FZQ?PcB?{V$B}4sp|fnOYX;_-VvpW!R-qSj>cL&YNU#K_c>Z8!RHI?+Z^L8<2gKs zM`eu5yqU*iK6NAUkknNd(04fg_<5W!;!7nw)mg&V%evqk)b<7XVd;P@5C zZ_bKxM#}g)2Zi?-S$)C}u&P+wR*$fw9${BK!k&7Bef0=8)FU+DZ&bVi5905fS8?@Q Rg7mu~{2qUhiyQIBe*wAhlv@A* literal 0 HcmV?d00001 diff --git a/functional_tests/support/package1/tests/test_example_function.py b/functional_tests/support/package1/tests/test_example_function.py new file mode 100644 index 0000000..d74f2a5 --- /dev/null +++ b/functional_tests/support/package1/tests/test_example_function.py @@ -0,0 +1,15 @@ +import example +import unittest + +class TestExampleFunction_TestCase(unittest.TestCase): + def test_times_two(self): + self.assertEqual(example.times_two(2), 4) + + +class TestExampleFunction: + def test_times_two(self): + assert example.times_two(2) == 4 + + +def test_times_two(): + assert example.times_two(2) == 4 diff --git a/functional_tests/support/package1/tests/test_example_function.pyc b/functional_tests/support/package1/tests/test_example_function.pyc new file mode 100644 index 0000000000000000000000000000000000000000..674c778588a22e3ea020e2a0c4785d04bdca1a0d GIT binary patch literal 1054 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 Tc6XcG`S9>>(+%*Evz*^={wPc; literal 0 HcmV?d00001 diff --git a/functional_tests/support/package2/test_pak/__init__$py.class b/functional_tests/support/package2/test_pak/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..9f7b4a71ecdf735424ab1517a29963a96b9770ef GIT binary patch literal 3558 zcmbtW3wIMm6#k}ZyGgo0i9E}zK-Dy9yR?W3MGz1)*p}L2p+#M{$)u!gcGukv7!eWQ zFMPjW;0xc1MMIAq{Q>?ck9Rhk(3(w;=Xf|_rh8}ZeD}Neelvgl{rjH)dNIQg%64pF z7>N&<_LO23-Q%XAq)nSE!^H!(mgoI7hQr5gu4P7D&E@@_HP+$cp~-2UcKaDt)^RBz z#2`7`)pG`CXpE;i$7D32If_O|3@t8qT-DM}bXY|R5rzh}m!U1-s8QF>8B_f-S|CRd z#Uh4S?V%Eu(0LXub#;SbT~Mq(XaQs?Vo|i>1_pV5-g5nT9ELT=0wDMsWh}>v2yI)* zu%Z^iK+bU`tY&E5nKN?kE{65-1-uCuYp^zoHmqZ4oz_ljO2N(P${yR+ijIU0L|Zne z>kM)7kIU;*u+{h3Xc`(x&xl-bD_W9J-k63uT8F7)b zPRdSg%m)fa+Rd4Ugd{^Gt4`_Wq^1*(L7A-5*o~eDxm3y(Qm{HG^^qy=mUlDs1b|$i z3T5yZyADw+o=Lom5$V0*H%>-JA{Yrpo(r#`()fvt~JVG zSXGHQT~!%IYl~`bpDaLQeUpqku}7S|m&nMfy5_h!29}a>7xoL14p0>gnFg0}k6^78 zDGDsW-##&Fz#cBx?Hm} z<|(72Ptdwm#^Exp_sbY9<2*tHS5}2F&agZv^={P&MT~kuXJak+scy%5b@D@XUX&)T)T( zFdfBloM2d5J^MNVc_JW@NVHc>?YX>V+AgPhHU)*G;$sSl+)3(e#m!raVLDv3xz05j zdwY6&`jo8SlA5jxH;&>IEa614wDbvWiuWlM?~1Alp;FZzs~CnYm-gg?MVNKviVrx@6V;lA4$ZD6Dyz?GN%Da9k=CBoTgF zJciRk?;n@J%7ym?;ivW=)N*uNw8iUgr6S6EYgWwDU_YSFu`nV+#*am*pq`cSB+iN1 z^8sQhkPMhwhT9Teq&%nThIRQUWmxBXP1AK;TeBFp2eqviMJ-10xw@4K`(?Zm#Y;F( zvDpQq9NV^9fAhBmP$|0THM}kudxL6$c$&9`VPgPc^Olf~Zm#Xvk%D1}X02kD@D4*K z`9F};xj`?HeWpP?6w>Pr=E}o9QcEY`c`_e~jQ8+<6z}2#QUXuP zD;**fJrz`b|LaX8N~}Q?!6!nEz1an!ci!x?fWjCqR(O%{1w;FMM0ty-v6Vf;sWEOl z#0F8@bW+m`JVejCHhQrHt{*&{qjy~szc;YiJwqzKZqTY!)3u$=FTZB_!BTQmWd!#*@EdOZN;0*;NeFk`aGNYovC~ zQGd;Zzoy|g+&zPXm+5$hpXmRX2ZhnBfqnv3ge;VFAM6f<=pV2t+1gC*2~Y{!Vz-sB zvug&sOW1cLS>v;r_JV?>;tcZBQsZZAAwMnTht$c(a~_L4Jm(Tu@o?e_GO_F|^efmM zGrVyuW_jb{nB$FwQ0Nbw8th8OilIu#hEKOTQ!bIp0Uw067^&ptQsd; zBc8Qsj`ip-6rv!J>8ruv1hL0xe{KO#jA-~ zyjk`Y!CQFSUmf$-5{a?sEIurIWB3Rk`}RweDv_+7L|6Bk@5EU^ygYb`TubwI`gN4> z>E#kG((m)jtep0r>3f!Tx1&))lu2Uv0U?_E(~f$Ecs)Z`JwpS;nGLWZVq8YEuPA~` NDSVA@#N;5p{RbpPwu%4% literal 0 HcmV?d00001 diff --git a/functional_tests/support/package2/test_pak/__init__.py b/functional_tests/support/package2/test_pak/__init__.py new file mode 100644 index 0000000..3a05d5f --- /dev/null +++ b/functional_tests/support/package2/test_pak/__init__.py @@ -0,0 +1,11 @@ +print "*** test_pak imported" +state = [] + +def setup(): + # print "SETUP CALLED", state, id(state) + state.append('test_pak.setup') + + +def teardown(): + # print "TEARDOWN CALLED", state, id(state) + state.append('test_pak.teardown') diff --git a/functional_tests/support/package2/test_pak/__init__.pyc b/functional_tests/support/package2/test_pak/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50777a8e8ca31ac1dec4122ff4bdfdb2a1726340 GIT binary patch literal 514 zcmb7Au};G<5Iv`~qDUYyu$L{$5UUkCNDMG#NF@dq%Zg1EwQ;QY0z!=N5qu|Kz@3wl znMi(q&Yj=8=jP}9?Bn~Us9^jA?RT`oHGu(mc-#Rz6BY19M1V;mNx;lto1Sp%6+xC| zE3^TX*ZWo7__jl9{!|m{xaZbO0tBc@N5H25K^EJT%SE{0+9;6+i@lEpobyH0XmLSI zdvBddiNOT;L`Uhn`jy%wHeR|mSk>8s)wHkkb-s}=y{oWpojxeG2{QDa=agPod%d$8 zd32Xb)viXRa{oTq^E5Flj|$2Eg2m9sU}$w`+BX-om8@3sn@?FHE7=E68OHu4@i4d- f>5LA;-N<;d;UBZ9Fo_O&Y;?;I_$B9Jku2xGnZ#U; literal 0 HcmV?d00001 diff --git a/functional_tests/support/package2/test_pak/test_mod$py.class b/functional_tests/support/package2/test_pak/test_mod$py.class new file mode 100644 index 0000000000000000000000000000000000000000..e967fe1b85e08ce6bf4afd6d9c59299b34acbf0d GIT binary patch literal 5313 zcmbtX33MA*6}{iGEm@f)isGhO+SF-Hu_N2E-Rus9G)r-?oe&3?B!yutjU6S@j2MlQ zxTWk63Y4}$Aq|C;tw7mQr&UDLg}qQH`@Wa8?EBh7;r>68DqCYW9DL6B{n7jXzxVE2 z=fTJBz7If;s22!~wRQ{aY8$Yf@pyK^-EUj*gzcE|ofF#}BV+c}Fm~^EOd~n$8m`&b zUc=ovv19arnQ;3A&Yj`PAOZrxoav@hmMKu**55v&p#hCy)FUX+?3y`O&l(5gYLT&% zt=Wknngr_f9)YDkGKXC!WsUb~m;`lj=Mveba~sr0ppMnPaD>Fyri3t)*isKV5ngU zmWHty%LGCaFgszS1>$FSeU&NaW4Q$P0$x)FH-r@ek?Cp%aS_eUN#};}B!RGJ)i%e@ z2wdqSci;bDX@C1Z9~4SzYIri*q^<1&+SW|gEd^3eVA-t4V(4h1n>ra8(|yAj&RYpL zWm`eS>7p@xJZ+B}Y2N7)#<$&)_CEymcXbm+H7YshKBWC zq->ywanto=2z2?tHLJ5X;wjQgmr__N8N$;97S~$jDH+6N^rfyd9@4rlFkBnU)pCJeCMCcL?gC0UdglNj2+yVKRSFr&WNWW9dX)ylGkR3MuGWzBURem5bUkT~ z=ErqCgmHzOWm{L7jxBu_XlviAA%z1Hzk__|l_4hv`XdLPaDE!P}39Rgz`D}i~w z+`Lvp&U!fO5MC@WH)9+$w;oBDS^2wf_*cZtti7KCl}icoOYt(<0A8-) zd0rg8vRprvr(VTE<#ni(mCv=ZTGZ+5HN3|2{%bY7+B@~Sa>!)lz7TFu?}n4K4_mFP zBvL)y8s6X?d!u^q(eNhk{mmL)?_G1V67C%f;VlB^_|=yYfeUJjM!5{`Hb&EC5O1T9 zHre9#;?^)u;O)%HoS7aISTs=X57o_=SiBSOk^%Q_R?hN{F3)&7$wgVM_xRQi!6~Q6YCB(Z~p3O zO$SyOr#wbW*@_C|E>00;8(X|aXeL*LaC!z1IJKz2_=t=IPE{juHi<)QrExctjazok z)EzTz8l*j4Jzc%=u~K(3(z=wBi|6uLIYh*>M&h6`ZuWXks2+v7vJ)ZPqv4Df9`^~H zSBrzEv=?n3Wwcr5;Zo@kSk7_FCnX*)Eg#1xWYm9>;kJ>pS3YfC8p5Yp&v}b1%K|6b z`u*De^jV^>b~NbUyVpndl<4g9yW2-xzpojkzVR7+R@(5O2H_>o=czxN)}WDMwOHEb zhk!cDM98Q;i`sl-wvU9+B(vtNVdlbDHGBbIl&i1v&gn@fuN$z9r0E3lO(tuCQ?qLh zGmO&aWw!0+T*t@?toJLBN|jn2<l-_&$Ch z5&I!ihd50+BCx^-uqj(O?O!w7i<%1JSu0IY!m%{;rD-i=g1JN zlR@xnIj*SHZ)L+!tKZ8epjLmBbzH6f>=P`HV->_-1zM(RnaL6^C%$QC|A^`2h!Ej! zIAo;rW`Lh@OKWC@(*D8SDSp2-0HWub+^t8rZMuu9CW9dU&DfVv3d|qihuKg*Gio}j zVXzYLb1!jlPzG#x*v>l%Q+iNf30>?`%1A1gqN6uimd(MC8s@Nqkenbke?yc*J=+IW zT`aqrq8t+y+ePDP-h@|@o;CXu85Uf_im_g@8$J^BIKqM89$xUn}4nMSO2%5kK2hkJxfmS(R0 RP7MChkALD{a5 literal 0 HcmV?d00001 diff --git a/functional_tests/support/package2/test_pak/test_mod.py b/functional_tests/support/package2/test_pak/test_mod.py new file mode 100644 index 0000000..09dc9e5 --- /dev/null +++ b/functional_tests/support/package2/test_pak/test_mod.py @@ -0,0 +1,20 @@ +print "test_mod imported!" + +import maths +from test_pak import state + +def setup(): + print "MOD setup called", state, id(state) + state.append('test_pak.test_mod.setup') + +def test_add(): + print "MOD.test_add called", state, id(state) + state.append('test_pak.test_mod.test_add') + assert maths.add(1, 2) == 3 + +def test_minus(): + state.append('test_pak.test_mod.test_minus') + +def teardown(): + print "MOD teardown called", state, id(state) + state.append('test_pak.test_mod.teardown') diff --git a/functional_tests/support/package2/test_pak/test_mod.pyc b/functional_tests/support/package2/test_pak/test_mod.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0deba4267b8773fc04674674eb0dd440f670044f GIT binary patch literal 1136 zcmb7DO>fgc5S?}6mQ;kV9*_`+AaU?1Rx9p+C~7YR$(1|`k1HsA5?2ySGK?Li=ylTv(tX$IuYo5(A#m$r==wNSv zV154_0$I)jfCMF2u3=qZEfeB_r2z6W8z6AzA3(l)e>U+ptleaxS1W7$V>n}Nr%59W{fs|-S zY|b@^O65x;?obq}{M=H;Ix~L^3CcQRv@u)Op1{Aw*|Qcsh$`7Qv;cy@GUh7qOTmqT z8}UMw4xc^HfKOw>J*=<2w=L;;+qO;HQyaB{k>L&nhXrSjpnGN~8RMP~R)};wgu-!9 zBxN@a+@ATT++(5}osXWXtk*tcSO=ThUN8jEpSTapGgkIM`Ojh9Fh2=ax2E}4-^P3w z6-V|Ts`mo#SnLk*`Rzt3zQ}?@-!lz@x}2Q5f=7!H1#jOqH~MH6F$Bscgo%0pT?aV? UFXP+33oT7XYMhLQqjWU*3p;<$J^%m! literal 0 HcmV?d00001 diff --git a/functional_tests/support/package2/test_pak/test_sub/__init__$py.class b/functional_tests/support/package2/test_pak/test_sub/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..d776b66d35ae148a5b40da00fe9611eae0cb2cf1 GIT binary patch literal 3489 zcmbtW>vt1H6#q?=c9XC`2?a_)DNr?S+J;6!p@>4E8f-&tYmtYp+ho$F-R!Qrn?iiQ z@cn+`>wqsl_z8=a9y$64_(yrXv%8ILv+401KWy&J+&g#f{N{J>%wK>1{wIJwTw@3o zI`%P)CWZ`aDrHujX+uxt42!2mDub3<;{7#-@o9^z`7uXzczol{eqs!yfH9IL2LNm$3w zXp8kDSkJI|^)khhDPbc+j1*vf3(lUvU<~beNE9V^%4P%^sy&CRR^GUvcccaQX&FNvzQZye@e)4eR=VdE!J`aY zf~YgWu&b5}OWYk-Cp9i%ocdV8%Vi>l$8eeuw7FJb*g6zM2}11*&I;7d5i6EFdY4E; z%2HZS1X6LS5fMY=0=1Wn?r=-z`TtJ;D(zHE#_$w!)GllD@bRQ3*`QFDR#K)_oTI*% za!O`OH*Bt0T;nRqzTUpxbgJN&w5lm0n4PlArf7jFQ_an)Q#|d}1+~^{fus~gkVa8@ z%}NB*GN!z9nxWGA(|prxI^UuFuv)z?z*C5%dYdkHv^O6&Vi7 zLVK2CuI8x7WXMqS+>-D-aXUwsro+eRO49f-!*FcJQcZ?~L6z$6qBch{T%Af3ei1Ll z@B&_@y;)`5+uQ!ycq^v`ZcE0icugR7iSAp%X~h(V-2s5DaG}e7?`i->Dx+mx7xh`q zS;AWkofLntsBxVh7XyY)IFxgaVU6;FM$ZPikE-qr>E#_^fI6ie@ZwmRiHvvfZVYeZ zJ%;se8(%64(akeK^!s1W96_-LL4uD&Cw8?@M2mB^&jN_?FD??kU}#^NQOP7|tkltw z%mlY=LW7`foKv+j57CRQjV}B^|Kaf>J=Gck*7v-9^&+{gK8|JAmV_(Ba{(K}#vyux zWy_^WZn?dbs-9kUx!GYs&e)hywsKsY7Q=QrPKQ>RDB49j)T6p?&<#u(+Vmz!Q`2R* z3<<5Gvr?+9qT#!eE>>4kV{s*wq$}y(awS~{BqKDRkfiTB`ga*k^dJN-Cc_Je#AD$< zAZL3*@eN}=$%aL=ke)siPR2KECwnU+wTb-ZaeMX_+Rr3^MMuv(j0>=8cM zWV2N3GvfP<`aTW6;mACWUZ=GgzNg;}5Wq9~gp^35Q}(kwNMmAjcQQheDVqg!#*+)! z*FBE|3pjK-Sre|w4b@1$L=ivCAwSHXWOKA8PIj$0QHns_$d7Xg>)muJT{rP~*A1MH zKe>qf4P@g5S00ELT{#;H5uC%_$#^Lg3eDquh_TS7W-Q|A8?dX!lTEHM>>8PCJYF@9 zHcPIxahY}G7s5hdMQzbfshOm}E<6ugUB3|iQ z#Oq$HDBi%EzIWX91)AdNMZE7tWB33c`u1z|bP7|n-mdPR9Y2e;fP^OqYG5aQ?eujl z;N$BH_>{iSuQTtYf1vRK&2C3nLYhfJTt$dt$?inVFY{uY!fg^1Syylp0fqjWr91- zIG{%RhwO>}UQsdnb=By--N>Vj5s(e3 zRPAb1D);ZB54M$(SYY#z{BP7-YB_4mqoUQl>E2wNwq)9p?{l6mqLKx1$ziI$AUxS9 h;u@yxc$Fg&TP@>?PH0Zd3L`nIKEzdW$11s8`~tGyW#<3@ literal 0 HcmV?d00001 diff --git a/functional_tests/support/package2/test_pak/test_sub/test_mod$py.class b/functional_tests/support/package2/test_pak/test_sub/test_mod$py.class new file mode 100644 index 0000000000000000000000000000000000000000..02f576eaa5c4ebae08909958e9a9e0371c5533c0 GIT binary patch literal 7596 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 N#@qOOG1Pej$ip9!3$&%d%!CHfEDub|x+2TiYMu z@AMaV&z&UO#wMgL%w?aupjF6I$`uuej!!x3<-6|zDS*sCm`_$ZEXUza^tLR+pOnIc@skfNE z!<{1<1lV+jBZ>E7xnp;+W~__yc-RudPQH0#lqD@|+$5Jz-TVYs<2`Ynn{m=)i^|SL zJZ$I1;Fy85-6(I5Gi##4q$cL~ervyV(C$yhJvYe4aT?(~+qRQD&kEPh`clkJ(SWICnk#h1eMmnP!4h-HgyL{^$`oLu=0+L|f^ihPBkQmlCyBes@7LCv0-?VvimWJU6HCJxWAR7h zVl4&~Gy+%W_h&x!BV5j7#fCTB>=*7hRu&WA?>QTaOofcXU6;BFWipgWmX_BT3o4um zN@UjEm`QAtv=>;XhnUcCqOiM4QO|&8IeYUsytblV8e1z0EBO;i3w6EJmOOciZm@rm zrQh1X5WrXk*TVUnx2%QJnYc)@lksw>8W*&P6E}W&D_Hw4s87~~TBhqFnUX9f?JuCz z(C5AX#r1SuTr(c7x@W!3UKU`vP4Vn`-?;)F@=cWZ0UuthZHsxXB+qvx&sppLF83AP zKULGB=nKF#;^NAg432#zfcz|w@7di)hr4@rp)6?0ZZOVUG^(fweFXvD`vdr$6@$6R zF(DKjvay5$E&)nH=7g6Y3=d~zuUeKpKC3WQ{w?B5o3S6S0(v^Kk;-Xp%ZD zwQ+T{kibQ+sKE1xo5(IvL==tVk%=O=#WPXFKZHrjLye(BU65ijGVUl#G{z!tjS``6 zp+g3iQWHyxipH%;VY$slP1V)5Z5F literal 0 HcmV?d00001 diff --git a/functional_tests/support/package3/lib/a$py.class b/functional_tests/support/package3/lib/a$py.class new file mode 100644 index 0000000000000000000000000000000000000000..5628cbcf9604bfbd48d8cbde0f9b7b16574dafbd GIT binary patch literal 2600 zcmbtVU0WMf5PlB{Y!a82M%q%K6fDv}AZw^-YY?ThXdt0TkP4`AlbnQw&F;Fpp^YDx z{tmtN3PFL#KKcXvQ9jPu1fD`Rz43ya!_J)fc;}s&fByaRZvaXBzz`^pzQeE<%b0dW zvFh%QX(&b0=E`b)(N?QG)goBmvAJ5xxvI-k<1NzF`qRQLFS;p)n{7-=2rx(vcXiF+ z48d4>d_zVjx*`ZdVu-fBlMrT*pHwZkY2INN*lzD!#v72sh@hLHzt!Ag%_zE>X-K%r z5H9B{x>-h?H-)s15?V10E~8(lM=(Gi z)KRN0;Wk5`kNpL+#8Wb^<4zbu7-s02)eOy@W7zepN)z7KB;U@5&-bP=f&W{$8$l07 zDH}C}F@}(+kXcl91|^o+RrggzSB;92b8XG2wDRJ-^1@mSsmx!}T&Dlo5y$Xw$eaKzo;=BTr(#@-@R{SW+xo+b7m4Zq-r@ z)8ToW>s+NXnMfw4m2#66s-Abb<2s5{vn4SfouM@p2T4NNS&z}-UpW%xLHn2&#*)^k)+f1vyi_d)1 zlCh011!7+j7YL^dPZ&mg0K1?3YO_rJE0pjd|> zfhH!cciZ!!!)pBu4GC3-;frxpEds<==2z1j+;#{90$IO%lCOStU86nP z39#gk&Mz-W?Q#)bS*a3g#BKo=!_^Ee$dy{Pz-_PTsMl%j7WcA(l98NQvx{8Rh~XB6 z?UI)b&C#fo^M+y4ex?W=j8M`E%H9ymNeoO1wNDHc@1(}_PTD?nN~A|P(cg3W?=iaQ z4KNuG9wT&!D=RP2vl;&#y;Fx6VJCQdB_6#ub&NP$iFf>gTZb4tAbo}x^xH+JxKmS| zTO6aCfa*%TlPtD-Bm7hI(yxf(8M?hU%M&L^P8?w-`tTGV9brBY_zMf!$$0chAP_jj zOn|Y#wH}-zeT3z6;k}?I40=N52}gb-vm!K_3r#0Ibc%3;hgcRV0AZ})X;V5(0SIaD zOAKvBS5L5ZjP+A&O`PKEhOIF2_@=2H^>hh<=+G(34QmV)>@?*+5vT&zv5Cp4fnS9$ zVA%T=F^S>;gU8rCIEH?}8i3a6Tb802L{P#IlfNu ROE;6;TWNd?TU?gl{0FNdu8;r# literal 0 HcmV?d00001 diff --git a/functional_tests/support/package3/lib/a.py b/functional_tests/support/package3/lib/a.py new file mode 100644 index 0000000..b2dde25 --- /dev/null +++ b/functional_tests/support/package3/lib/a.py @@ -0,0 +1,2 @@ +def a(): + pass diff --git a/functional_tests/support/package3/lib/a.pyc b/functional_tests/support/package3/lib/a.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74202a25ba51469038bb52cc2e03c34eee9d18a8 GIT binary patch literal 247 zcmd1(#LHDZ!^AI{0SXv_v;z}hA1F|ks(-v2`GZ1&KabR1)?rk1EkYW z14uBS5hY$gG5vy~%(BFiRQ;0N0{y)F;?($})ST2rAZ@5;sAr^~R+^Vwl9`{Em=j-; zT3k}BUtC&HkY7}yUyzubotU0#te=yaq@So)P+0=9C=o;ygIvVG=m)ks2qc2U0!e`# O5(Hv_HQ9qTfw%xEQz`QR literal 0 HcmV?d00001 diff --git a/functional_tests/support/package3/src/b$py.class b/functional_tests/support/package3/src/b$py.class new file mode 100644 index 0000000000000000000000000000000000000000..559cdec7d159ccba9afda77c3b8b6783b9a2d02f GIT binary patch literal 2600 zcmbtVU0WMf5PlB{EQw1?BW)>A3KnTXAZw^-YY?ThXdp?EAQe#KCOHWUo85JHLmNLX z{T+Jk6@mhfee?(TqkNpR1fD`Rz43ya!_J)fc;}s&fByaRZvaXBz!0d6y~D5?&6svo zu^R5SX(%Pr=E_QA(N=3b)goBiwz*o)yQ<4mu@>n{<7sh+m)sP?%{C?_1Q;ZTySip@ zhF~-uTbI#^t}ud-7$U9jB!n2`CpF7$ns*omx7s_G@do4&!suZbXf?N3H%hK%8WOHD zgerxqZWdLYY!1B66yci$T5&8%DO3u&>bM#MOUt-{n?l-83FS{G3@wNr3vqAl5gk3=X=wb!2d1W4Wk!h zlufZ4;|$%RLS{+T8I)*dN8M8uT{Wso-nBKO+RBUb$_r~z#6&eG7_R%gjjc1>XRjd5I;@Bmnn6Mh%ctEvmP2kj* zgb#?U72B+(4VT*nFEb?kQ1B^8Gg=w5_%MtV=48Bw_eG8$GxT0Af`ms@r`eiWuIqfR z8;^;U27fLX#qe`9z2#H3%QjN2I_b?#pU}>l#;Z=s&$f@dblPu3G%3G`Pr}q8pUNOQ zh#Z$F{wjB~s%B6zqivrO5o(^!ji`^$*m^f^3qntZ$>wJ=GRTTs-e(qwNHV5c=C*{- ziL51>Z7$DiHB0A@Ow)B-TeTP-`gNk0MQe=W^Z8Ta=reo~#yU1AH@j|}<~Gx6@8UDx zv}A1IOM%!|#0A3X!V`v3AHXiC&`iGDO4`-BVTgu1k6D7s5F`7Gn$8W{0Sl%Csa-C@D=Sq(o!BkFVz`>21(~bYirn^^j(VNeZfQ3wC>hS1b-TnxjTmlG z*e-cl*Bp&XId2#y?PrS6!6+r2pzIB?oW#JSQ2WJD@lI+y@1*TRr$l;$6a77>{~n`@ z-T;&F;1RkHa3%K=y&Li0(KmH~QFe^CbMeT%sUyT$F5dA6ZXIA~pY$1C&~F!=;!aI< zZgHG$0;((VPO{kUjqp#+OTQwDXXx?XEKeLGIdO=Y$iowSbcp#t;4ds>C*zSPfk5B@ zGXcf|*Lrb+^dXkdh4+GRYbWTycVx5!5AZ#m ReRMO)y_Lqdu*GEw&VNYyuBHG0 literal 0 HcmV?d00001 diff --git a/functional_tests/support/package3/src/b.py b/functional_tests/support/package3/src/b.py new file mode 100644 index 0000000..40aaa8c --- /dev/null +++ b/functional_tests/support/package3/src/b.py @@ -0,0 +1,2 @@ +def b(): + pass diff --git a/functional_tests/support/package3/src/b.pyc b/functional_tests/support/package3/src/b.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a31963536da52f7e957fc49cef9654794d996fdf GIT binary patch literal 247 zcmd1(#LHDZ!^AI{0SXv_v;z}hA1F|ks(-v2`GZ1&KabR1)?rk1EkYW z14uBS5hY$gG5vy~%(BFiRQ;0N0{y)F;?($})ST2rAZ@5;sAr^~R+^Vwl9`{Em=j-; zT3k}BUtC&HkY7}yUyzubotU0#tY2J|te>P;P+0=9C<#OqgIvVG=m)ks2qc2U0!e`# O5(Hv_HQ9qTfw%xJ+bRYC literal 0 HcmV?d00001 diff --git a/functional_tests/support/package3/tests/test_a$py.class b/functional_tests/support/package3/tests/test_a$py.class new file mode 100644 index 0000000000000000000000000000000000000000..6b2750d6f83e540ad6506d68454d457725577ec1 GIT binary patch literal 3002 zcmbtWZFdtz6n>@&*<@LygaV~hC{i(P+Aam5LJ%pHVxTXftwlgww&^5n+3d#MO(812 zqaNS=1^U@fSTyv=(I4QC@_1*n?PW;BnPiVDJpPXCZMK8y&ZxfRe zVhobYJ=3r_Lp+o3nN_e22^n!n3`|cV#jtzhaida|u$>`A6Wj47#o6+OeYBpe`CN?O^p=X8^7c5G$xc^pg;!b23O z$6Zg;y9eoXnS0IS3Sq% z#I~tv45!0Fd`7(~$f8de>?d)RG*fpygS3=apyHSan4*kN*cMlCLNKrwFH_h7164}R zDS{ut+lW2Sa|#aQl@x|>njvx4ung}U!$KJAs`|G`>SjJ7-mlSB4rgU_;#KnB#GJ%y z411zdHOp`)oQD;$%;<9_mvDhfKO+R2!|O6eaFN(?xmlttjjfVGabLn4BJMmxi$;wi zN?AeKFzC)v{f#Io#wrje`S&1IN`;AXwehgb!DBYgJLoYE>`Z)yw>Z z+I-yvFi@>0F&-(Zrb`KvR8A$wuH-F`I~JdRcJT(F6jXg!e zHInJsialR5`MD&nlg2E5PiTYT=S==)L>Jm+3R=i?SWzE}xGo(LlU6 z@wQBYzomdRWlWVOQ7Mk=hDABeYRlP6+PRT;w6)=QR?oi+Zm3pE%y;>ac;y7ydH^y`>4yfhxZtE_+56T2pOg``6&7Q zuWMgOtVM|6L(wz+*~bxbDAZTjknkx(*Lob4DiPwS!-f1TcU8Mz@0Jd_ zG$Z$q&lvQKV z^0ZxZid^J~VL!q4$jhwZ8kEXm%d+W0BZMwRJ4Ahy;WN5x+C|?asO_Su_#+jbKhixy zqeT0NC;EL$|85~cXMo{syn$p|&Mu>MBAb@`8)#z_*#x~@LdV2YbY9Q?j;{VCoMBIJ zj<%c+wzT|#<4ZXCh>S3NM^BlIi#ZUZCnX|qb&6(!!-H9gtdfrgUKEesz6J)aWkYrn zzKu9NsmcQz*XUbG*tpXB1f#u=aXCG)jG4!njm7@LTjPD%^o>|7wuH+u#$vl#v5a>f z837oXjDB#DJa9e~0q)^0su-fH^1++cI(DgKbf^q}K zGVI=ExJ_Frc&G)t)4nAjkseyc`%P;MAK*c-{*c%g{2%S@>p$?T?+jhd!9Uqf^Uel7 zdep!tk64rB@9Et*q3A?h!Vr_h@C{-#52jt4Ep%_T(1IVxXbYMBk;XQfeNOHl=J5r- J6qC#N>OZ2oGgtrs literal 0 HcmV?d00001 diff --git a/functional_tests/support/package3/tests/test_a.py b/functional_tests/support/package3/tests/test_a.py new file mode 100644 index 0000000..3b978fc --- /dev/null +++ b/functional_tests/support/package3/tests/test_a.py @@ -0,0 +1,4 @@ +import a + +def test_a(): + a.a() diff --git a/functional_tests/support/package3/tests/test_a.pyc b/functional_tests/support/package3/tests/test_a.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3731991f6e6d6eecd4ead7c125482f1e76a8be6c GIT binary patch literal 296 zcmZ9GOA5j;5QZle1O;7shOQc_?iJh@tUEU))F`2^&`bq!sfX~Kp1_$@6btsTIXIilezac6E-Xsa2VwcIc$*tZiD9R^^2{>4oge%uGf0 hf?Sk;GQ@&*<@Lygtt;E6sedtZI^;jA&8VpG0>OL)*>J-n{*PkYa|*T~AtMfnffbWTG3;D_+^E(hY-LE%#CE(Xark0pd_iB-Ra3Vr z>Wt?YRwayhC6rCjD%ghSWGL9qkQC7Dl5R4n&t^UdS%k3zX%WUwhV**;g@%OP49bOS z%?ns^8TQ}Um_r47(UHO)>|^K*n;WZJCC{)e3HynavQ{zeqHdDS&dp3dj{_+}c#s11 zxa(=fo*_D2;a=-F!(c?vn^N}z4vUz37&^irTii%E!mw4-$c^rKj;1Ndh*K}2mvCqr zv2AJ^!>OW;lowkdQFtFTGLB+^a?+& zwqCaY4AvS+Ohk&RYGnUAzHk1y?XFqktI&moY9veUqVmeNT~a zm1KIRYR}hAem03~q%n)%72069ylm1nO5J^RJHxT3%YBquoEvciuSKHZ9vU+4;ys3KewSS-LWY@4K1zQ7 z>)IC*YZD^)Q1ncH_Hl$93XK&uBz(%yy%tBcMua%(NFhJR9hW#DLbojHW}V0AyQPyZ z&B*-|vj%;zYysHNefO7pv~7J7p1MmUd`;>WWHD?Xr!SGodbP+Mzdcf|)Avp3&V-Pp zJY(0L5*ImQ*hjEE@-k<*2BmVuvTVB02%$^S4pSdx_>AtF4$(IWYKLel{z!%Ak93dF zDA7LRiGJVGzne(V8DJzEZz7qNv&(3o%%y&Hx~^q^NB6)IPP4~2 zOIywbTiX7>u_c^%NJbdGqo+*9#T?;bFN#NBe-ndOvmrYP z-$tCCRONwn>AK-ql{(#sQ{2%G-AK3q^?+o3o!9UqS^R6a7 zdf3D#4_S-k@9EtHq3A+f!Z4G>@C{-#52oE4E%a=((1st#Xd9XRk;Yv#`<&cA$m0uq JDJGZj)qmE>GhP4y literal 0 HcmV?d00001 diff --git a/functional_tests/support/package3/tests/test_b.py b/functional_tests/support/package3/tests/test_b.py new file mode 100644 index 0000000..d8c182a --- /dev/null +++ b/functional_tests/support/package3/tests/test_b.py @@ -0,0 +1,4 @@ +import b + +def test_b(): + b.b() diff --git a/functional_tests/support/package3/tests/test_b.pyc b/functional_tests/support/package3/tests/test_b.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93148cfffd3e1a115807fe51c9ecfa465948197b GIT binary patch literal 296 zcmZ9GOA5j;5QZle1O;7shOQc_?iJh@tUEU)tx-x}p_vNeQV-!dJ%KZ+C>qE&|2+At z=VW|+ETatGPf%Rbw==>4pa9sD5O60UxU%y-YW;3(cw^F6ms(K_gD{xOV^?RGH?=Ag)E2F@oiR;|(x|LZC%uq;nVF=4 ix!{nl_St}xWxF7p>zHNCh5wWr&RG0$yJF?GN8Sr^G&5-c literal 0 HcmV?d00001 diff --git a/functional_tests/support/pass/test$py.class b/functional_tests/support/pass/test$py.class new file mode 100644 index 0000000000000000000000000000000000000000..56cce808ea1d746ba5053f31c393729ae17001b1 GIT binary patch literal 2607 zcmbtVT~`}b6y28uCW%8!BW)>A3KnTXAR{Q+8mv+Znv$eQFcnba5N=@TWM-V1(8iBP ze@A`x2|aOTzp#j5 zfD}Cot0CbgLtj9Yd2@?rWL(9qD26ahF=jPGbMG_k2KB}-@@q=aE=It2-RiGk(KK<2PKhB6B5c^DTWt`>_$M-F8au{>SWi~1E8I~9j`Z;Am0Juve}?X;d6cwpT_8wd?tg)AaZ;} z__w*6S2ctBm}qNCG^lwx*Af9Slcfl53Pw-F$mi!Wa>$EYF<>MJBso*v;l6IwT7~;7#F-uSxlH`9;)44&bVBR#S4)uy_+AF-O z(~zeT+EizlewEzwG#8zK{cs+Mj0(15*aQ)^ncnnQEQZTD+LMKPZIj#HqocP@+qd#GFDMx+nsvLvMUNP65NwyC zlr%@9R?ZoQNo$%AIvAm()0Dj-7L=Hk1hr2L74M{n=bf~E=#)s0>O_Cf=)Z^PA{$^L z6+S}b0GA3c(X*EN9letW7-7eFyO4_CnLI*@6;d64;Kl(4_er1OIsJCgDem;BnjXjK zCQ5ZF)kz-P*@*b`coffn~-|~cF5Mc>JOcKW)LUi}9!|ep4?F1e8o}6~zE`Ff1mu@D7 Nx3c&Swzxck^B(~#vcv!Y literal 0 HcmV?d00001 diff --git a/functional_tests/support/pass/test.py b/functional_tests/support/pass/test.py new file mode 100644 index 0000000..f174823 --- /dev/null +++ b/functional_tests/support/pass/test.py @@ -0,0 +1,2 @@ +def test(): + pass diff --git a/functional_tests/support/pass/test.pyc b/functional_tests/support/pass/test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4ab48531a2c34c642687a4e2aea771fefb9477e GIT binary patch literal 245 zcmd1(#LHDZ!^AI{0SXv_v;z}hA1F|ks(-v2`GZ1&KabR1)?rk1EkYW z14uBS5hbobG5vy~%(BFiRQ;0N0{y)F;?($})ST2rAZ@5;sAr^~R+^Vwl9`{Em=j-; zT3k}BUtC&HkY7}yUyxW_tPc{>E2u01Sq5T*>?#Jihk?-#Y;h1s1c?Qb0y`rJ!~(0g I2WtXx0p9H@0RR91 literal 0 HcmV?d00001 diff --git a/functional_tests/support/test.cfg b/functional_tests/support/test.cfg new file mode 100644 index 0000000..738c764 --- /dev/null +++ b/functional_tests/support/test.cfg @@ -0,0 +1,2 @@ +[nosetests] +verbosity=10 diff --git a/functional_tests/support/test_buggy_generators$py.class b/functional_tests/support/test_buggy_generators$py.class new file mode 100644 index 0000000000000000000000000000000000000000..acdd55c24d40c6ad6e5221963787ab62990a88c0 GIT binary patch literal 7309 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 literal 0 HcmV?d00001 diff --git a/functional_tests/support/todo/test_with_todo.py b/functional_tests/support/todo/test_with_todo.py new file mode 100644 index 0000000..b48f6a8 --- /dev/null +++ b/functional_tests/support/todo/test_with_todo.py @@ -0,0 +1,7 @@ +from todoplug import Todo + +def test_some_important_thing(): + raise Todo("Not done yet") + +def test_something_else(): + pass diff --git a/functional_tests/support/todo/test_with_todo.pyc b/functional_tests/support/todo/test_with_todo.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04ae346a7c316144f112c2156b51921da11c8c97 GIT binary patch literal 479 zcmZ8eO-sZu6ntq#R9p}|d0X((Q^SgP1@9HQp7s*TChiv7rZlh0;!XV#{!V{^Z^CNP zLMQL5GxM7EYj*Pbd0nh&|0HtW${Q{PEYTBDqiCf_Sw$a2tAuP4gOqGa%j`hrGKDV? zJ$UQ?aNVJECcr|c=v~p90`D?|QV~vpp6AwgZoYFIsrZK~lP*ob_mb*e%qM?l!431DuljT?@;`wcs+Yf=V&joT3sPJ_kS-3X#^hCb&Z>@41Wk8~F%>Vykb) cO(`*zkS<{&159b`{?~!n4Io~{EICfU0iq;U$N&HU literal 0 HcmV?d00001 diff --git a/functional_tests/support/todo/todoplug$py.class b/functional_tests/support/todo/todoplug$py.class new file mode 100644 index 0000000000000000000000000000000000000000..a13a34dff2abcf466e59a091970686a235f6340d GIT binary patch literal 3644 zcmbtW`*Ryt75=Uzdu3Hg)oq%_NuW+EK(;JxZ79$Juu~W_@g@g?n-hbYwckM&8+U-d+s^+obP<+?EUY% z|NJ+AX}l?rsO26MIG>;Q{B@(z3NLxCQT2RjEVbr*t1f2;1k0CvX{}X4E0nVn1Jb3| zxz)?E8qNybyNgK;2>~sTp<}yJAek>utYk2Zku;LfsONftEH#|wy6pxf>HD5vb*vyr zVN@V{*}7^Oj^(Z!mC(1{bq#k2q|d5dXWF~=ao6qlrN~YScM9CS(;>Uw&~O*kIr02( z&iCp9Pv+-$o-uoIa49D8Helt6i!rheDjD3318L~EMvFDZG!cuglP7S_MimNA8i%4DQ3YI`J4Yw(PBWDLf!B zqGq{iB50a*Z>{M_(@f!mjLkIFG3qTZa0gGVnNh5Pz|s7k=S-rY&Y7ZGNb8CRxkgJv ziJ`_|a>iSevl)B<501hBUC{^e(C}d*QMazhh#~^lV}jo!Yr7pAv);+`o)JETkEZbv ze5}jnW@tOcY2UY6frf{PZ_Rc{ihRD8b{)e#MC=|#Hq7Eg8prVoCPHPT%lS_VWNyFb zW(0;zB6K*$?{+Z}$VYHS0r{v(v)_~{%n1xT)~a+;_%uO|PuQ*PQVwv%BV1tv-n_ zq+!CM4}o-Q0{iBBm`%*9SW}p*X1ILr(Q_%R$Ch=s;W4yKjlHVuhSGQC+W%h6+gZK> zCymQsyBjju|I0?hx399J8)3a+sQzpE(vcR&>C$v*#;A2dvm7&&K^Pc8v!Pn1p#q8i zRGpR@trQv=cx?{()hefJPVm;b(u&;i*#9m3Eq$u%Jni>CE z24BJrHG7^mcQQ(uoA<0W>1+4~nONnAH=Y>J&TMdDS2Mceh6}d7Po?6~k zCG)<8Z>RB1e2205O}8D}LxcAE)ncGjH2)sHuMqnI+Zy|QpNhb-7{L0r@WSxGP5_o# z=bNsps%hJ24L=l^p#3@9k?Iq3#&d~7vl@E-d0BIK^38q$J|=_J;~r@ zypqO`@MD&0)ZV)E7pUaRarFDYu1bnxLy81HSKqN{_Dj{#qS>!wh;bVi4X+A}_j^=t z5E|b&y;NS2en2z`?eJC0Y03m|eTM~(#O_~Mwt3wf2E_D@;I(Vqwlf_)?Oem3$#Vsp zz@78F$1OJNtJ04;AFH0X!|IgEU;f zSu7^>{gq-;&sK_sp)DNb!p!kxLEk^dO^3ztWHA}NXS?r(5h^F&!O_Kn{{BteUo;bD zMb8yCFeA3`aiyE4{-J_C!2{@cL8F(2O%!k2P+H(#p$OE`y-q+BB-K9MY^1F3r>Uut zcBHwk-y1kvG?Qkfpnob_8a68m$K%PdWI``j#*#_>GnJwW=Ntk0Y*B%@80Uya7uf7N zj@-b$D2Qh!-^S|XE!6Z&TX1jTto}?i28jeuS(qy5%|s%xfm%X{#9dizp>+$_w}rWp zNSN#q&TI=$W!uu>9%=;w(yg-HG!YvXC3*Un2#i_l_sZ(M^vKu%eM#S z2)xSx<9rDJFK%w)SA2eRQ?zsXH?G&HJ&vRXLuhLF3lf}nrn%h&`P~Fly9tKycb+zc Y2HxO!fV1B+g5Q<#d;CF79>X8s1J;($e*gdg literal 0 HcmV?d00001 diff --git a/functional_tests/support/todo/todoplug.py b/functional_tests/support/todo/todoplug.py new file mode 100644 index 0000000..585c26a --- /dev/null +++ b/functional_tests/support/todo/todoplug.py @@ -0,0 +1,7 @@ +from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin + +class Todo(Exception): + pass + +class TodoPlugin(ErrorClassPlugin): + todo = ErrorClass(Todo, label='TODO', isfailure=True) diff --git a/functional_tests/support/todo/todoplug.pyc b/functional_tests/support/todo/todoplug.pyc new file mode 100644 index 0000000000000000000000000000000000000000..761015ebdff8f3836172689c1492f3cbda17a6cb GIT binary patch literal 670 zcmZuuO-sW-5S^rn)S@5=di9u7LdCmc?ZHbbTQBAkVzQOs=7a1+6p!^s_&fat&P+;! zU|?rw-ezasdt3b+&pyAe;v9D0g!UU+c0r*4cm*&HBn}uC5*M7p0X>0@1BDA;09$~k z#Dii2+7;B>qdpWqtb+sU6S&-QMq$}?O}8+awf5vs`C$5&vPO@<$EIlZd>jI9Y1t_S z1ZJYv0i&^WMaI*#&MKXz$RpCUYKq=a8Bhgdz +]]> \ No newline at end of file diff --git a/functional_tests/support/xunit/test_xunit_as_suite$py.class b/functional_tests/support/xunit/test_xunit_as_suite$py.class new file mode 100644 index 0000000000000000000000000000000000000000..58de0d4b4d6ba49f76ccd8b20a7275d83b0253c3 GIT binary patch literal 5802 zcmb_f349z?8UMd*lAUa)?RMx9Qjh>ql5W$br9~hWT1ra;X$VakuIf15Oww&PGt10u z+lYuF;*E$3h$wjAfuL;)+W-RI2a5NB_kp5#-?!-hy_pT0?rg+kf4{fin|a^&z3=|M zdFY9I9{{jYEEY(NcAO}%wX@Il#+#FlSl|8eLE=R9b2HWv$tzlM-!S;Xhf1C`4hf|7J(xUXOJH+ zCNW2#g&Wrk&XeS4clPbF_E_11<&0$q126B4)yiB^W)^vM%*DJEbj%mfq;PJ|DhOns z8v96TsmKBtQjsIb#&Lang}pDQVUa-ka50luLY%?f`Qni62O5qPNQH`R^4xKO?J-wh z@*gPM+jU8O_vl!Pqole=Q-+e04+15_F#@fJTfO4aB#x!u#_YghTnMa)5pP!e9*+)b zn5XkXsqhBNw>6v~Fvm2>)CvO6G<9@ItmCjuK$lpXT<=0Do`&TD^Qzx;tfak6bKD&% z6>QVgkR@Z&l)}j;o*K29BykcMZyYZMQJne$OJ_CH8l2pM)p({rTP?dyB_|i;T_=fC z8I_~vSi#+4709z~Hp};5Z3_+YEGi_eZu+HM&i4I|RW$FYS+M*dFCcn#tiyVVcqWsi z-*s#qXGzM{*eI}AQSOtbF)g1g@`2q^oJitqdNAhl4ep5DqvH(pwqP^P5olhUC++D1 zyW(;`isxA@Q}2$L_f^T!hb<|bhJHGsT6GNrj05#%)XEn+=p>CX5#`{~tPu6t>KMe3 z46JQrVfnu81sg9dSp^Lj2sD_CgE(hIl|siwxL8``5{bJ?$Fsx7=crg>1t^%Nz<8~R zs;gjq+3KIns?c*`$sE|BV{7QI5k}Xvbu^4t%x8O^>vgQ=>Ci;c-YB0mFnC&Myf7AZ z><-h8wXDHBNrw|E=n4$}w_5!h*)Nr{djfOEt=;yw8k_i4VOzZ&|phrT(ix|=fX6( z0xy(7@gg1153hT%z(RH1VeB=$go>&Yw-gjhLC48VEk79HlZKZGH0|}6nHpXp?_Hzg zRiXJ`t>dL3=hsvMKcv_2+R6)`ouPwur@=f~$LmA7H|Tg>NOwaeBqW-KH9-XCR0_n7V29w)bv_AuO18XlyPUAO2j zZ{O>=&RBcEN}}OIq<-v%+aA054hII`9_QMM%UvG^$|F8rR%uzT={6h4kmDzjJ= z@gW)L97VTfi(Y;YMa%}{#jKp5OwTUZ92U%#D^{*pl^ujUvFTOY? zpeyr%3ZR}i-Z9tp#&)}?fqw~KmYMMt9U{!zuW2|Cj%8b{JgaP5Y~Cws+In2!ES`}! zkp^AE8qSun{oWMg^}9O0fp5y)_hTj>$*#|}Mr<#MN10MNei8)sAg9-YeWvRMe&AWf zIu&1uQX3rd^)xB-=0|ueg&*R_)Xgh7p>AtxJIGA37;DOWeH=fPM*A5foi%a>iNLWj z2AhGxPnqLu!+&eZab#JTmN|)E33QSFrhLJcQ|JcQp&d%O!1cD;qXm8xu?hQu;$)OH$RPNW>=SDD7db?#-QVPR zqIUm~ZCUO9DLbaz2_ZY2+9hPqQoE*@P#T!1STZU2@z*|6hw&nf>1Eds^bXsePy5js zO?#|D$xiSKwyma*NBCQY^89pd0>sM8{aZOJEIr(TRr6a?%x7juLkrCB<2Px4X?%z6 zshnl0;FoT0_ZAt>sX@2ojq;*M@C}Y^TkUm^KiS3gx%g9|&kCUtutN;Ac06Ef*Via14L z=aes?%E=WhQS~o-n>XNc!{BnnGz(D9k-FICmz>SbB~^X|6)C zXQEt|o~KZfRVWiPQLaueP$*gz%2hK_u1haeDCz2(uAPZLR`qq%VskH}P!as4fPJ34Kr7v$-}`>^>U<2#4&y)u3dY1qwOj64w%956zl38 uoK^2&bG?JUdItmb4uh4&0n0=Kc%4J@^*@ literal 0 HcmV?d00001 diff --git a/functional_tests/support/xunit/test_xunit_as_suite.py b/functional_tests/support/xunit/test_xunit_as_suite.py new file mode 100644 index 0000000..ec256b5 --- /dev/null +++ b/functional_tests/support/xunit/test_xunit_as_suite.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +import sys +from nose.exc import SkipTest +import unittest + +class TestForXunit(unittest.TestCase): + + def test_success(self): + pass + + def test_fail(self): + self.assertEqual("this","that") + + def test_error(self): + raise TypeError("oops, wrong type") + + def test_non_ascii_error(self): + raise Exception(u"日本") + + def test_output(self): + sys.stdout.write("test-generated output\n") + + def test_skip(self): + raise SkipTest("skipit") diff --git a/functional_tests/support/xunit/test_xunit_as_suite.pyc b/functional_tests/support/xunit/test_xunit_as_suite.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42a7f844439e2ed321ee4489f02cd32c4049aca1 GIT binary patch literal 1325 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_attribute_plugin.py b/functional_tests/test_attribute_plugin.py new file mode 100644 index 0000000..a093cd5 --- /dev/null +++ b/functional_tests/test_attribute_plugin.py @@ -0,0 +1,181 @@ +import os +import sys +import unittest +from nose.plugins.attrib import AttributeSelector +from nose.plugins import PluginTester + +support = os.path.join(os.path.dirname(__file__), 'support') + +compat_24 = sys.version_info >= (2, 4) + +class AttributePluginTester(PluginTester, unittest.TestCase): + plugins = [AttributeSelector()] + suitepath = os.path.join(support, 'att') + # Some cases need -a to activate and others need -A, so + # let's treat -v as the activate argument and let individual + # cases specify their -a arguments as part of args + activate = '-v' + + def runTest(self): + print '*' * 70 + print str(self.output) + print '*' * 70 + self.verify() + + def verify(self): + raise NotImplementedError() + + +class TestSimpleAttribute(AttributePluginTester): + args = ["-a", "a"] + + def verify(self): + assert 'test_attr.test_one ... ok' in self.output + assert 'test_attr.test_two ... ok' in self.output + assert 'TestClass.test_class_one ... ok' in self.output + assert 'TestClass.test_class_two ... ok' in self.output + assert 'TestClass.test_class_three ... ok' in self.output + assert 'test_three' not in self.output + assert 'test_case_two' not in self.output + assert 'test_case_one' not in self.output + assert 'test_case_three' not in self.output + assert 'TestAttrClass.test_one ... ok' in self.output + assert 'TestAttrClass.test_two ... ok' in self.output + assert 'TestAttrClass.ends_with_test ... ok' in self.output + + +class TestNotSimpleAttribute(AttributePluginTester): + args = ["-a", "!a"] + + def verify(self): + assert 'test_attr.test_one ... ok' not in self.output + assert 'test_attr.test_two ... ok' not in self.output + assert 'TestClass.test_class_one ... ok' not in self.output + assert 'TestClass.test_class_two ... ok' not in self.output + assert 'TestClass.test_class_three ... ok' not in self.output + assert 'test_three' in self.output + assert 'test_case_two' in self.output + assert 'test_case_one' in self.output + assert 'test_case_three' in self.output + + +class TestAttributeValue(AttributePluginTester): + args = ["-a", "b=2"] + + def verify(self): + assert 'test_attr.test_one ... ok' not in self.output + assert 'test_attr.test_two ... ok' not in self.output + assert 'test_attr.test_three ... ok' not in self.output + assert 'TestClass.test_class_one ... ok' not in self.output + assert 'TestClass.test_class_two ... ok' in self.output + assert 'TestClass.test_class_three ... ok' not in self.output + assert 'test_case_two' in self.output + assert 'test_case_one' in self.output + assert 'test_case_three' in self.output + + +class TestAttributeArray(AttributePluginTester): + args = ["-a", "d=2"] + + def verify(self): + assert 'test_attr.test_one ... ok' in self.output + assert 'test_attr.test_two ... ok' in self.output + assert 'test_attr.test_three ... ok' not in self.output + assert 'TestClass.test_class_one ... ok' not in self.output + assert 'TestClass.test_class_two ... ok' not in self.output + assert 'TestClass.test_class_three ... ok' not in self.output + assert 'test_case_two' not in self.output + assert 'test_case_one' not in self.output + assert 'test_case_three' not in self.output + + +class TestAttributeArrayAnd(AttributePluginTester): + args = ["-a", "d=1,d=2"] + + def verify(self): + assert 'test_attr.test_one ... ok' in self.output + assert 'test_attr.test_two ... ok' not in self.output + assert 'test_attr.test_three ... ok' not in self.output + assert 'TestClass.test_class_one ... ok' not in self.output + assert 'TestClass.test_class_two ... ok' not in self.output + assert 'TestClass.test_class_three ... ok' not in self.output + assert 'test_case_two' not in self.output + assert 'test_case_one' not in self.output + assert 'test_case_three' not in self.output + + +class TestAttributeArrayOr(AttributePluginTester): + args = ["-a", "d=1", "-a", "d=2"] + + def verify(self): + assert 'test_attr.test_one ... ok' in self.output + assert 'test_attr.test_two ... ok' in self.output + assert 'test_attr.test_three ... ok' in self.output + assert 'TestClass.test_class_one ... ok' not in self.output + assert 'TestClass.test_class_two ... ok' not in self.output + assert 'TestClass.test_class_three ... ok' not in self.output + assert 'test_case_two' not in self.output + assert 'test_case_one' not in self.output + assert 'test_case_three' not in self.output + + +class TestInheritance(AttributePluginTester): + # Issue #412 + args = ["-a", "from_super"] + + def verify(self): + assert 'TestSubclass.test_method ... ok' in self.output + assert 'TestAttrSubClass.test_sub_three ... ok' in self.output + assert 'TestAttrSubClass.test_one ... ok' in self.output + assert 'TestAttrSubClass.added_later_test ... ok' in self.output + assert 'test_two' not in self.output + assert 'test_case_one' not in self.output + assert 'test_case_three' not in self.output + + +class TestStatic(AttributePluginTester): + # Issue #411 + args = ["-a", "with_static"] + suitepath = os.path.join(support, 'att', 'test_attr.py:Static') + + def verify(self): + assert 'Static.test_with_static ... ok' in self.output + assert 'test_case_two' not in self.output + assert 'test_case_one' not in self.output + assert 'test_case_three' not in self.output + + +class TestClassAndMethodAttrs(AttributePluginTester): + # Issue #324 + args = ["-a", "meth_attr=method,cls_attr=class"] + + def verify(self): + assert '(test_attr.TestClassAndMethodAttrs) ... ok' in self.output + assert 'test_case_two' not in self.output + assert 'test_case_one' not in self.output + assert 'test_case_three' not in self.output + + +if compat_24: + class TestAttributeEval(AttributePluginTester): + args = ["-A", "c>20"] + + def verify(self): + assert 'test_attr.test_one ... ok' not in self.output + assert 'test_attr.test_two ... ok' not in self.output + assert 'test_attr.test_three ... ok' not in self.output + assert 'TestClass.test_class_one ... ok' not in self.output + assert 'TestClass.test_class_two ... ok' not in self.output + assert 'TestClass.test_class_three ... ok' not in self.output + assert 'test_case_two' in self.output + assert 'test_case_one' not in self.output + assert 'test_case_three' not in self.output + + +# Avoid trying to run base class as tests +del AttributePluginTester + +if __name__ == '__main__': + #import logging + #logging.basicConfig(level=logging.DEBUG) + unittest.main() diff --git a/functional_tests/test_attribute_plugin.pyc b/functional_tests/test_attribute_plugin.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b25237be0abb0b91aef192fbf7f0763220335a87 GIT binary patch literal 8217 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_buggy_generators.py b/functional_tests/test_buggy_generators.py new file mode 100644 index 0000000..9e6e168 --- /dev/null +++ b/functional_tests/test_buggy_generators.py @@ -0,0 +1,36 @@ +import os +import unittest +from cStringIO import StringIO +from nose.core import TestProgram +from nose.config import Config +from nose.result import _TextTestResult + +here = os.path.dirname(__file__) +support = os.path.join(here, 'support') + + +class TestRunner(unittest.TextTestRunner): + def _makeResult(self): + self.result = _TextTestResult( + self.stream, self.descriptions, self.verbosity) + return self.result + + +class TestBuggyGenerators(unittest.TestCase): + def test_run_buggy_generators(self): + stream = StringIO() + runner = TestRunner(stream=stream) + prog = TestProgram( + argv=['nosetests', + os.path.join(support, 'test_buggy_generators.py')], + testRunner=runner, + config=Config(), + exit=False) + res = runner.result + print stream.getvalue() + self.assertEqual(res.testsRun, 12, + "Expected to run 12 tests, ran %s" % res.testsRun) + assert not res.wasSuccessful() + assert len(res.errors) == 4 + assert not res.failures + diff --git a/functional_tests/test_buggy_generators.pyc b/functional_tests/test_buggy_generators.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4e33d385288139b734919439bcb01be8f677894 GIT binary patch literal 1849 zcmb7FO>-MX5bargS^i9%*bpdEI*=+IQdT*|9f-?>Tnsh#1zol_+L5x+YFF&c8oRJB zh#$e7f5Xq=7w}%Mu*25@yQX)hyJxzm-|Nxh_npljfBEceBI~~vzF%PYUm;SFxyZPg z7#SN$jm#s-TQY8mNh6uJCGW_%gGEc`UCDbg?qSiE`G(|u8TZ9Fk!(}4Epgkj!YMnl z*6fPgk+dVLNXC1TcEuUV9>^47dh)Z#O60roLrFK(u&;*xo#7GFJ0ET$&$FRn${P^p zp~{Nc>u(y(7Bm;mhi|KLRwau@*@g0?ET-A4S?^&TU%0D4PwRZ0htPx0*UM#Dg}?}E zs*A!^ca>tumvnmw5l|+r4$>Y+V$X*Wv}Y4KakMc`F#IzJINzWqk;<0Jwybm#cXZjo z#klQk$YjI6Wylk~%cp@JK2$DQgl$Yy=OnF`CxJghwZN98fJkqKx0vv)Oe#b0|*| z%E})tuLDaTFOs=yD!PNEXU<}tMngspge#7VWZ~jiiyX&`GOcqgEfc>_O=p(=mQ~?3 zJ?q;w;SoLr=Ot5kb(g{aN2!B@7*$dF4HKZW61g=JBGUfGU}`5gGUa*NU(XLg2SfEf z{kE6rDO~j@gU@4iIbbWLoANG__l(?e_!sw-JwhzbEUS;?MUrZEsnj6A3Zws!w*P{X z4M3yS22dJJw14z8ca;U)3y=&TPOp}35?ne6<)EsI!SRcMMtDA`l49`8XG~!D0fgfJ zXS-^03U0&FCeq*97eI)VUZXCP@Kz`9%B--^i*9WC8pw!zmE^b`!!6PMmK_~h5a<96 zJ#*nQ$!n)=pma^VcU3t3zD{!W(8O`+K=S^O`15))ao$hyKZe J%|Yu)?+?uVV+8;J literal 0 HcmV?d00001 diff --git a/functional_tests/test_cases$py.class b/functional_tests/test_cases$py.class new file mode 100644 index 0000000000000000000000000000000000000000..675bc99223cf4782b715259a0feb472ca46ce9c0 GIT binary patch literal 7732 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_collector$py.class b/functional_tests/test_collector$py.class new file mode 100644 index 0000000000000000000000000000000000000000..bdc2c85a71af320c47ce9d911ab5a71bf357daa9 GIT binary patch literal 7738 zcmb_h37i{eb$@Rq?TqCKkw?DPj-54elHJv64_|S-j>*Q@VI8k*EXT%nV34)5TG^6D znHhP#IXKb-XrXsglBNZE5D2tQWRMpEq%AEi(EBdE=uPjFo|N|gz8Pt?mUc@E_V3et z^BwQ{-uu7z&3@*~AN>Rojq?*sk=dPNOs58pJ8ogLy5P?_mC?N8+M_2Ij=H(BJ<&07 zdd{_TGg&|9+Y^HwvnLl$T)brG{RyT`E0EME!lZe&Un*8?rs%-b;2DEr)ElQLX-qwi zr_&my@ly(~@! z-OQwmY)(E`VjBI*$cH+M9_z>yJ(4i3w*W;pfL^Uq^nKg&b=rit`J7vUn3(H>F8M&{ zsS`SFWlG$rj7HnAWogZ=Oz}YdqpnkCdZ>$l7yb_>nHs#X@);VmgI+F1xs7RU#qsPB z*Y;{9=y5yK&DLrAs(%`4oeBr<#IY7^zXG>m8tEd~s`ekGAu(JE9#PHtb2_D&Hs9z1 zP507h6e3v`*yViRwJd|iW&arMf)F!BSA@`M4{T>yv&E8aSvu`gZ#dVIWie$teZJkb zSGLNkTI{F0MT!H^V$OCU!a?*dImL=jhnUuh7g%|xR6>Y2?#}9hM)$(wt#PI;T{wmg zZP0ymcnwX`E1AmwU$MK8Z@TTuQwHs)qoUCL60)_b#GOu4OfPF4eyvimU7a3)V=89| z-=~?3rT3jsFm738XQmdM#YuG*a!PVCrlr$`h90EX zz?z-pj@Bx9zvxsnI*+)UwF)KYVy*<5-7FuP#-NAjwQJy(uVcE&D(5cSfwS*~o2`NK z1vCuG1ws`z3Q?KU(RLeTeLE#zO%e%#@hzg9j1V&eE_oCw! z{RN$XNpJbud%((4uByO(s)%nmP@oA6538^QfHuR@6FeLvAO16Im2#e6Wa24**13`$xgxU=bjS5W3WCG6xIYS{y6L6th239rH*U{I@Nq>VuCjyP$f>RZOhhY(Hf3yo&er=UmSxR@{nY$oX>`(ca|@nE?*M)QK;d@-1MP_=v{GBDX|vn6(znIwTj<*n4uS!P za~_cJJDE1fTLy_ljW%IWr|(9!&~<9(Zl-=;K-0TitXlJqd)c$*i~d|2hyvt$EX3p1 zE;>$Ce7j(~aHXf{X>q0RW!gS4)n#Ry^y}#RF!QvYtIp@p80-yw>bWLm

? zS>>SH4Ibu#|Aprd3eO)xyudy4^D{cV7g&w+^KEyj?s%VAY_~zr1zF_-SaPaX@rz~q zY*3r%^dqulk3l~cEO{Oja|%6ms^e7isQwC$3qbl1W&&8(slgP>&msHjv?%-c8uZa% z|BDD1IpCA7Q$Sg%)5qaf8K(lbcu7|7Gw3IRl|L!3cNz3k!Rt@U>wbfNCV2f>rj4D* ze!S@U8hu8Nb`@hvb@n*@T%0~jKM(FXw{S(LUqoIK|APK1jr}qJ80dB6YTi}|%rxB| zn>c-EK6kOyr7m8r$ZU~=BmOG=nnc^LgK>7j^X>AHt3~A0--H8qYS!AQ(Qm`J<(!{K zirCiaP%R7`Ja5qN(C^0SbM$+tp_l5(mgpM&L6D}dFJe+nGtpCP|htT63c&1PLl zhj?JnztF#m;{PUu#>Wi$kAU!h!iA)sG3dX71z$GkoeJEM-)Q^ln5A=%LEjUsjw-_A z2KOq$tjW9mu)%8-U5UIA8)`sM1VNNTxu$woS~#z)3Sc8$)4HEFJ7 ziEiHLat%kS3p(#Lcu(LOcR|d90oWeW`EHa{5Kc-ZrgsfYbx9U&**P?H=g8Fg^IhD3 znfFX|quqtwRM&bpsO5Nq4~l8-F%TY7)$uEII2kI(0D1X7IA|3ai(iS3Q}cB`3I&nI zrg1DN(z{kziZ*h>veKj#7dqW5{*HV7Bp)~URXioVOqbh%&^hkpW^7kuTunCQQI7j| z7B}sZeV^m_p6}+WUF(693rdDgw~*o1Rs_^3&c^xGd>TqYuP820{NQ?$2^lJB$m7@7Cnzddi-a zD=c8XwVY{JJ2vO4SgLE^VTBJlKWyaGDTF;qO zOa?cGgB!!aUK!jR4sH$yH5uFz4sHntbs4-R`f=KxNk`27Y&zAmNITKrbs(BD`}@Fi zJ)@gYB=2Z09JpcOz|ult1MwO(Y>qT&x49QT!zuGF3^Zs0lHU`uP6+Szm9Gd^-h-8O zI?|w5Vezr%;Na(_49w-Yybnnu4S;@*Sy!dBe8vYHfQm zUFTSH+mknJ16PQa1*1%z^|aOCwVY|t7oMVRsRY!*q)3tEn?&XY{lM90DU#~>FfG*S zub;;m_V63aY0fvO6t0C#rsRWdRq5}=e52qpxDwsuR8}u%>dS&z)RLunAF4)xImWi8XuiE5d zK}Ojoy~H`LEOHYhxf$GnTOs#M$bH{(#nyKy#vCY?5;%L_d=>@?m}t$}t!j1)n3(UE*m1eq-kKknP!20*W-*eMAVVILikS8OGT$5Utb45NC)>Y}ui63j?3Bfvj3LT8s>C@;yz8C4U=lY`8=@TNgIFDdDNr78Eb)DL|6jZ8Xa{ubCa==Yx@BbB%Vl08eY^h>G4oj2+QxG4nk z!%uaA!-DxoEd%RK0}rZwf1-#Yiieairnsj0m*HxJ_tUfv@tZRL7VE4<`bRJvTE20# zPG8KVfLzd|LI18m;c4ot)0fm>gZ{gHcMA|0*VL!@8T$M(V34G<5p5INX0)wn{b)PT z?m)W}Z2)Z$ZAbybC5nN{5-@0dRpUp9HTpclp>3Q#C*#B)X&>ar)L_q0+h8mhn(G)s z=%DrtLWd9G?@NF$-ZgZclS9{dQU=E8 zgR1kvB&Ss8OG!>khxH_ns*x|6$JFykY>{_gqi2%5Pc7IViNMvThf_&D5Q#+UyeYyQ zS(m^UnnTz4-sY^CNvu_~(WO~>YgE$J^l&7}lUYQ-6A44DiK#U#6zH)Qg(nlHn(bYp zz_DmTBCSb60(&@y7$5FU^5N{TmgFPZVd!u_$ODKH>lFoUi9YiSI6Hw`RPTW;31tWV zBNf#7UI9eHUkeRB7S8T~on>~zi!^yI$q!uT=?0%zVn>7BsP0puiDoZ^m;ydR5rq N;VE|6lg=^r{|BKL4DbK| literal 0 HcmV?d00001 diff --git a/functional_tests/test_collector.py b/functional_tests/test_collector.py new file mode 100644 index 0000000..c3b9dca --- /dev/null +++ b/functional_tests/test_collector.py @@ -0,0 +1,46 @@ +import os +import sys +import unittest +import warnings +from cStringIO import StringIO +from nose.result import _TextTestResult +here = os.path.dirname(__file__) +support = os.path.join(here, 'support') + + +class TestRunner(unittest.TextTestRunner): + def _makeResult(self): + self.result = _TextTestResult( + self.stream, self.descriptions, self.verbosity) + return self.result + + +class TestNoseTestCollector(unittest.TestCase): + + def test_skip_works_with_collector(self): + verbosity = 2 + stream = StringIO() + runner = TestRunner(stream=stream, verbosity=verbosity) + pwd = os.getcwd() + + # we don't need to see our own warnings + warnings.filterwarnings(action='ignore', + category=RuntimeWarning, + module='nose.plugins.manager') + + try: + os.chdir(os.path.join(support, 'issue038')) + unittest.TestProgram( + None, None, + argv=['test_collector', '-v', 'nose.collector'], + testRunner=runner) + except SystemExit: + pass + os.chdir(pwd) + out = stream.getvalue() + assert runner.result.wasSuccessful() + assert 'SKIP' in out, "SKIP not found in %s" % out + + +if __name__ == '__main__': + unittest.main() diff --git a/functional_tests/test_collector.pyc b/functional_tests/test_collector.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5057bf284a16394a0648ee2a48cb6127859d0906 GIT binary patch literal 1975 zcmb7FOK;pZ5FTn@e(c(gQ`=2bq%P0|3X0lE+e2@C7(N6Awt+T4(!B_(#9h;?l?0Nq zW7NLH{So~o{b4=y+;4`CjR3uLWsWFvIGma9oAL7R(c15Seml!L03nXt!qKFKU${e;uOrX44?ulx>PjLH~J^84t$ z5C0z^&L9IW0R4jraJ=f0S4^TSMIM|6Silz;0V#^EBJw>leNpIeL?AV23$}uzh^ZJb zA+M>UE{;WYUF49=i2Mc>t4wv2fC9E=i))Afa1LcKq+&#K+_^!QP>Le;y1GwYzfGrW zRHKaWt0Gcw*oAevMA^U)+zYI~0)5XIus+5pI^L`J9)BS)qa0g zw^gxks{O|<{unkw5X_+WMIT~gI)#D=);Q|g$;w>&{Ja3@NFLsN_B^u{I))R6XQuSp zey{|WyjkkkK@GSN#Nk3!>1gx#O<4rdM!aT~h10l;!c;{2aztCM|90$!ZX+y4+k z80klHj-fk3hfLUQ2~RAryRihzR15dAM|bWf&-n9878oHYF>;rbfCC%&o`qurt4K6I QBRtz`r}JR&;NeL93q^%_DF6Tf literal 0 HcmV?d00001 diff --git a/functional_tests/test_commands$py.class b/functional_tests/test_commands$py.class new file mode 100644 index 0000000000000000000000000000000000000000..1307d3fe9c746379254ce83c807d9d2952800c33 GIT binary patch literal 7688 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_commands.py b/functional_tests/test_commands.py new file mode 100644 index 0000000..682af04 --- /dev/null +++ b/functional_tests/test_commands.py @@ -0,0 +1,47 @@ +import os +import sys +import unittest +from nose.plugins.skip import SkipTest +from nose import commands +from StringIO import StringIO + +support = os.path.join( + os.path.dirname(__file__), 'support', 'issue191') + + +class TestCommands(unittest.TestCase): + def setUp(self): + try: + import setuptools + except ImportError: + raise SkipTest("setuptools not available") + self.dir = os.getcwd() + self.stderr = sys.stderr + os.chdir(support) + + def tearDown(self): + os.chdir(self.dir) + sys.stderr = self.stderr + + def test_setup_nosetests_command_works(self): + from setuptools.dist import Distribution + buf = StringIO() + sys.stderr = buf + cmd = commands.nosetests( + Distribution(attrs={'script_name': 'setup.py', + 'package_dir': {'issue191': support}})) + cmd.finalize_options() + ## FIXME why doesn't Config see the chdir above? + print cmd._nosetests__config.workingDir + cmd._nosetests__config.workingDir = support + cmd._nosetests__config.stream = buf + try: + cmd.run() + except SystemExit, e: + self.assertFalse(e.args[0], buf.getvalue()) + else: + self.fail("cmd.run() did not exit") + + +if __name__ == '__main__': + unittest.main() diff --git a/functional_tests/test_commands.pyc b/functional_tests/test_commands.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa3bd3ec25d6d55842cca36784df8e3f52751150 GIT binary patch literal 1977 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 L&fgOKa9{o5_x6%uIXH zitvDzkKjA`0?w)SX2ZT>S;vkhol@3wT(%nOxr(#dWDK5+wyq{n(J;zw>DFpl! zV4g_8T|=s`L|jUTp(jOO*{V-)dhv(ZTX`r`eV`W(zND z@y=-!=Qj0nSMrk?FA=b)A^=<0Ap+2?pTDuQA7tt57OZJrI_99AQo4n9f?J`;?PPmlNq>aZ z5<<`1GvgtYKy%$Hu5BKEQRQV_R2Fv8uhy5VK{}Gnu31{%4fNXj@Y9x;T7naEzjN3fnxV;zHrQ2@X@ec%06GN?1`@8L zdRKKc7+WJ&jyPk`d(;@k-lce(f++Q%9=QogIrzN!uKulh4<|Kuk_;mnlIUP5nlGTB zsnF%bgs~QberCI3S`t~K`!TZIOV!%rFkv9#6l)i22DDz`U3y~`%Q|1aSxPiN0JGV& z0CA=xy*o6L^Fq@e>%VKfRwi#-G}@D2cX_G1=e{|u+ag@NQ4~HDH!@(~hwVcweOv@x zbLW~nQ>om)gaYoDB74gE`hhj+f__KVI$!^#57QNWdYT2sx!Jl79pO$dT;8l(=KIki z{+$=>8BK(}56u$1{eVInjLEQcXpbQNhvY{%@&Id3GBZaVeOE23Ygrrn4h5^C8L_-^ zohsF|i7oTbuF)5%XR=Y2W~ojO-io}aoEzV!7i~iwK%U-JMIdu66(@WWs~4$pYAtW+ zL$NFxzCe4QY!~O^u+r0Wcsa(?pgu`I6fMJN P7{knrl84FRgOT|ct6L>Z literal 0 HcmV?d00001 diff --git a/functional_tests/test_doctest_plugin$py.class b/functional_tests/test_doctest_plugin$py.class new file mode 100644 index 0000000000000000000000000000000000000000..a1b2cee7a9647f5b57f410c47ffc0a22d1f45d80 GIT binary patch literal 7655 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_doctest_plugin.py b/functional_tests/test_doctest_plugin.py new file mode 100644 index 0000000..c91ecc5 --- /dev/null +++ b/functional_tests/test_doctest_plugin.py @@ -0,0 +1,44 @@ +import os +import unittest +from nose.plugins.doctests import Doctest +from nose.plugins import PluginTester + +support = os.path.join(os.path.dirname(__file__), 'support') + +class TestDoctestPlugin(PluginTester, unittest.TestCase): + activate = '--with-doctest' + args = ['-v'] + plugins = [Doctest()] + suitepath = os.path.join(support, 'dtt') + + def runTest(self): + print str(self.output) + + assert 'Doctest: some_mod ... ok' in self.output + assert 'Doctest: some_mod.foo ... ok' in self.output + assert 'Ran 2 tests' in self.output + assert str(self.output).strip().endswith('OK') + + +class TestDoctestFiles(PluginTester, unittest.TestCase): + activate = '--with-doctest' + args = ['-v', '--doctest-extension=.txt'] + plugins = [Doctest()] + suitepath = os.path.join(support, 'dtt', 'docs') + + def runTest(self): + print str(self.output) + + expect = [ + 'Doctest: doc.txt ... ok', + 'Doctest: errdoc.txt ... FAIL' + ] + for line in self.output: + if not line.strip(): + continue + if line.startswith('='): + break + self.assertEqual(line.strip(), expect.pop(0)) + +if __name__ == '__main__': + unittest.main() diff --git a/functional_tests/test_doctest_plugin.pyc b/functional_tests/test_doctest_plugin.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a175e1f31bebce9a53158b2dbfac825d632c838 GIT binary patch literal 1947 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 G`14~q7tOH% literal 0 HcmV?d00001 diff --git a/functional_tests/test_entrypoints.py b/functional_tests/test_entrypoints.py new file mode 100644 index 0000000..a57f218 --- /dev/null +++ b/functional_tests/test_entrypoints.py @@ -0,0 +1,17 @@ +import os +import sys +from nose.exc import SkipTest + +try: + from pkg_resources import EntryPoint +except ImportError: + raise SkipTest("No setuptools available; skipping") + +here = os.path.dirname(__file__) +support = os.path.join(here, 'support') +ep = os.path.join(support, 'ep') + + +def test_plugin_entrypoint_is_loadable(): + epfile = os.path.join(ep, 'Some_plugin.egg-info', 'entry_points.txt') + assert EntryPoint.parse_map(open(epfile, 'r').readlines()) diff --git a/functional_tests/test_entrypoints.pyc b/functional_tests/test_entrypoints.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94315b7bd03ada0caa5f4961e9588c39756dd974 GIT binary patch literal 931 zcmY*YO^?$s5FID|*mm1OflAycoO+?vB5oY`+yj>irAVM$qR>pc-X^hSPgh$37s`)7 z;$QL$m~jg%kvz6%#?NowIMuIy_w$dZ^CcX=CeJT;_@8VFfDJ%;SSUyp6bd#Glrf|+ zsNE2X2sRBUn~*j^eW)Km16y#}1#=e^e;7j%!(PER1$6}KBLe6)=o7$>xYI5iJ*R+O zC>pSjAno07_Mm76XCI0dd}lTTp|zN_ncsY(gz5>K#YWrr=;`3;vgf=ew|i%FP5z(w`*NbalL-5>y`1h zIeC-LF@=`FU`)VS)Bxb`cVE@jO zya8b_#p`9*{CN=6=ZM{`nVL6Sw?v;vqv@2*+e(@pYD@$>Jb^;HI3k5fWdpG(`qYv`zs>PN8}s(~6t JchpFY;@@Oi!A$@F literal 0 HcmV?d00001 diff --git a/functional_tests/test_failuredetail_plugin$py.class b/functional_tests/test_failuredetail_plugin$py.class new file mode 100644 index 0000000000000000000000000000000000000000..36d293338db1f9e9259a578488eaaa1f20f96e10 GIT binary patch literal 7060 zcmb_g33yyp6+S03>AXx|%XFqox~0>Qwn-+*G-(T^8x5s_)HDrEZEXuar^!n?Wim6o zc~c08qNup<3!n&!f}mnaVHzom;)0?mii#qNJ1B~~;)ed`zBkh(vrv_M-_5=6F6Z6z zpZ_dx9(?Yudx@x>cQSc~8dfsxZ0t!{!_oApeK3`b4x}tI+Be!|#YW8aMGLzQT4rpp z-;UYl`lh1QzR?{A4w(b?dZu}0MEc0X8F>*_RvX8K8|Qd0XnVgu(*O zQl{CHu0Ie<+qk|)r^2LL@(;_H^oiRvT7j89rSdM|U|JeA;lQwIC&4kMwi36mXsige z2+FOXPda8F)F{eSJC!eaF8V-qnr;|)UCg#E!_aA!obRC3IDaS=Pik};t}__7I(@N8PVRHJU-CsyAQ%K+OK zG0l>vDSX}ze=nMDfTDiG7)cFg9aHovQ;3noFbH2&Mu$$j9Ccre#n^xyKa9wv(H>b` zrPE$#aUav%Vt(|*Gq#V;XPUMl4j%>lb&VDLK>YiqWccX4qh;~>)+mF@OCTskB+xDYmQql`v~5LL!% zol*`OX#~+sHg21a^H@y5VsLz9!)v#Wq-{5*XP6dNKu4#;ba4%3=@QVhSX@^&3FD-a zKDrcdA2Nm$sROYDWR{|`3z$wXr^{>L=&!&LE1MLCG<1kAYjk>*qsy!15$?ON1TVXj zwmEEC>veh!y;fY}bpY8I$-+RV6(gLF7nRbQPOqmo2)u6;c#&@GM3QN7C1Eh}o9Qin z0P|K!3O0h@+o2AkkpqEF?{uQsyFg&fx~XR|@?r^<3dp>N-YX&SeIRctYp1jDv}**4 z({*~kgUknn&fVo@IO*zxBISBHxmKqSIVV3Xk7wxg5$ExvxVO>1Qm31p=a1p~hB0U! z$PSAi-m0Q&GL^j4v{DiyJ&jHKbovB+Qh>Tm4#$%^9dUrW9VX~aB~6`U7yhC zi?9K>10AGrfmbbRtjL}((U-;U59+kTQR1txsUW@uAmI3AB{K30t4Mhrb^028U99pj z)F?^{d*b%NLZ$IdP`uKPhEfqVPp5A=qwlNzNGd;Y_Ww|x z&5LyUk;C2}gS{g$sTh9BG^YeeO}m+vl#hP9Vh0kYkA4BMjZQjw%1=+yFCjK#CXfT_ zdh7}X07&;V{aQ%(8*$N2Xm4w8>xd4yaW|GQL~W9HOpB1lkU9@%U91MvMlxkctt$|* z9~VfP7rV(t1=&)*R!Baq^9<0LXNrb{8VB&I4Ng(BN#h{g7}6x9FkRo+J+W?ZL7PxF zl%46`x37dp<&&n%vjhOpq$5mAOCU9A3qGh%Ee?&ul92XuR)ep-$nTtBU!ps!SVn7lps{A z5)z;y+2M>-@>O#iC3LINnG(*`sIvrF`bP6u`FIOcc(SvMqyd)|?d}w;Zze%_%`%0zJ0s`CDSQA`0b-NJ95CZ zlxrd);1`vF3%3hl{r#z|HDHPbndal#Hr}#3o{7WIok@hjnAk8wOOXbiB<8azM75F^ zaMfCgs;WnMta^L^sUAh4>QU3F9;Ke@@kyY1)OM;z;!`~mrt0xAsd`kj!s>f!#X zNBy9B6ho>=4dweMyh3U zt~)u`ot!3SpTQE#g!Sl8bgsT!wxj62qOBf+})5I>tS)MQ^ne&6yb z`<55>1=L#_^BbF|Y-~QUQ6%EVIcf{8%+Z?Q>G;<&apDYTVm)@`=*%(Nnxk`}<2Lu^ z0UXt;@1YLNHDgb!;W7Fn!CDd6gQKm6*XR%SwvN$5JWk&bb$at}Z^g`TBv5xU5*~qs z$MX^%nIfTGDbbI2_KwjLtwxT1!o4~A)HSpy66jEi>OwygU2>Gzb2E7&Rd>>TV|3>c zShb3K@xP6x<3GzYng`8`2K&OwL>ihOtq!dnt%G;qe+W!CO4UB<^pV%cJ=L9kK(E>o>iaZMJ0S1D?u}EN+5`X0wU6rFZjnmsAfuOqGJ96~y zmWU@uR|l_!nZPx*7%JL6N6lQ<8}Z^cH^9vT^VH0Zdm;f?8L-}j(R?+!1*3%+MS>rP zc*Te|@ztw)PrOO!GN$NqEzJTUBEe(WYmC$J`-(YIuXr#EsszzOXmil!q1B?zM_Z_P z5rq1;fe{5>V1*^JJG5Y*ON%Ea(_)Fb^(0z^F?DjZSc;vlCr+Y8B)Gi55;)^jS`}DQ zuYfAUl1Hboq(R+u5=$O?ewHi&3}LjTX!U3f00l|T1!V$DmMQTQSppqk#xg8fQMA2D z%}iuTM9m0G;4m&rz#xo-B~grCV3sU{g3HlXpf#aI&|1)1(V`Vtf@0AHqyz4RIK<1F zkI}E1kJ2;2-;dK@kJ8_Q|4{vv!GEcKfAHU`zb?cGk=Srkh&^g>V~DHO;Gz)w)Zms7 z*Qmi0A)c-Vj|R7?erup>oKHDQSA}?%n%M2}AR}#WiG(=h@p#7Q84t5(R)EHN-cg>P zUk&yK{A$&EVwE29f{Hln3Gssd7H^0b^&_o35D2K9)oLe;9J;?C=aE32TAemQ&f{g| zG^m}v334909k@zRLW%9*r-G2gbMF|>mw-i7gYVoW?&?xtE30#kQ|I0ghmUc6jvL0g zxp|!19APya<(2MsacTLR7VI47(;RWkr}J8O{V6xbZE9|5U3jb7N^6}?#l8lN8_|RU zE74Zxc-@g4Z$R6Gwi)fLBkaUgNiA0+L^P@pf@mSRg*=6D0wEGxN-V4_x3Ie0!rF2R h8_F$IaUbqig>UQg(9cHt+zJf3y7?SFR|ea-`#&QgPmcfq literal 0 HcmV?d00001 diff --git a/functional_tests/test_failuredetail_plugin.py b/functional_tests/test_failuredetail_plugin.py new file mode 100644 index 0000000..284cf49 --- /dev/null +++ b/functional_tests/test_failuredetail_plugin.py @@ -0,0 +1,50 @@ +import os +import sys +import unittest +from nose.plugins.failuredetail import FailureDetail +from nose.plugins.capture import Capture +from nose.plugins import PluginTester + +support = os.path.join(os.path.dirname(__file__), 'support') + +class TestFailureDetail(PluginTester, unittest.TestCase): + activate = "-d" + args = ['-v'] + plugins = [FailureDetail()] + suitepath = os.path.join(support, 'fdp') + + def runTest(self): + print '*' * 70 + print str(self.output) + print '*' * 70 + + expect = \ + 'AssertionError: a is not 4\n' + ' print "Hello"\n' + ' 2 = 2\n' + '>> assert 2 == 4, "a is not 4"' + + assert expect in self.output + + +class TestFailureDetailWithCapture(PluginTester, unittest.TestCase): + activate = "-d" + args = ['-v'] + plugins = [FailureDetail(), Capture()] + suitepath = os.path.join(support, 'fdp/test_fdp_no_capt.py') + + def runTest(self): + print '*' * 70 + print str(self.output) + print '*' * 70 + + expect = \ + 'AssertionError: a is not 4\n' + ' print "Hello"\n' + ' 2 = 2\n' + '>> assert 2 == 4, "a is not 4"' + + assert expect in self.output + +if __name__ == '__main__': + unittest.main() diff --git a/functional_tests/test_failuredetail_plugin.pyc b/functional_tests/test_failuredetail_plugin.pyc new file mode 100644 index 0000000000000000000000000000000000000000..387d58e2a6ec73655dad56c1608d1f8f1833f2c5 GIT binary patch literal 1795 zcmcIkU2oh(6utItydTM?ZJP2KP>^_7A+f7gec}b$K+_i@G(|$}i)F26(@7lL@{Fri zlo!H};J5Iz_ywGEy=}7!LAk!EM@)c;rR`g{}YgloQo_v zA(BO;C$TJI$s;-MNzs=@U!pvg^DQY7StQu>aptcV+l8_QbQ7P}*tu+x`I z*qKPaC3Yw_7Pl)am`&t&kxP-E7q=uIZ0d$@b+^Udk$fa)u~fV8g4g%>R#Zj^K;+qU zjPOsU5i^+po@B0Q8~d#d5W3?5j*qfB;J7>3!NCtjd+N#`tq-;dyy4rru9^_;K<4p= zp^d6N=Iuv-fRZ6Y)C>8m^g7hU*ARUBVeW?H@md@7E}K%o-m*eR~Wqzee&J|EfGoq#j5V`tsYG zdi7!IB+&R5?g`C)40z-{%HXQUx%N1%wUe{@mA0{IwW+sZ+!G`kMZ3``PGUg?B%ZSQVF*amGP|&8 z8pxnDy{PiG;Fh`!UKy9>n>D9CT;rB{>wNpbBrC7C>nuE1;jD6{!MTJHz_%_~>I~hz zD}zdgHw=41r6(j&g2;EGdvQ3x_8KF9a^d-!)Jy2~N9H`n^2dOGA*bC^ih_-aSmh^1 z*MgZHMYrEjbms<&`uAZU6kT^CuhX(hmzXd#;eVyAVTS*mwT5i%xJSAw0S()GBPqK^ zu>PN8{WF2c1hY*bm5gr5dvgn*tXTKOkbj2X6SGISOTZGdPq>i>_n`e0>k1F{$OA?W zo@TC0)2SMx?QS$*c~!Ti3mlQ~5j32q*_xrVjhX2}WfU8C*g||H&3iP<?oOh=0pKn_#Q*>R literal 0 HcmV?d00001 diff --git a/functional_tests/test_generator_fixtures$py.class b/functional_tests/test_generator_fixtures$py.class new file mode 100644 index 0000000000000000000000000000000000000000..38db04878b992a60f694151c389c9d36c8d960ed GIT binary patch literal 8553 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 rOnh2d&^7LAQ_)5fPo?k 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_id_plugin.py b/functional_tests/test_id_plugin.py new file mode 100644 index 0000000..7b3d39d --- /dev/null +++ b/functional_tests/test_id_plugin.py @@ -0,0 +1,261 @@ +import os +import re +import sys +import tempfile +import unittest + +from nose.plugins import PluginTester +from nose.plugins.builtin import Doctest +from nose.plugins.builtin import TestId +from cPickle import dump, load + +support = os.path.join(os.path.dirname(__file__), 'support') +idfile = tempfile.mktemp() +test_part = re.compile(r'(#\d+)? +([^(]+)') + +def teardown(): + try: + os.remove(idfile) + except OSError: + pass + +class TestDiscoveryMode(PluginTester, unittest.TestCase): + activate = '--with-id' + plugins = [TestId()] + args = ['-v', '--id-file=%s' % idfile] + suitepath = os.path.join(support, 'idp') + + def test_ids_added_to_output(self): + #print '>' * 70 + #print str(self.output) + #print '<' * 70 + + for line in self.output: + if line.startswith('='): + break + if not line.strip(): + continue + if 'test_gen' in line and not '(0,)' in line: + assert not line.startswith('#'), \ + "Generated test line '%s' should not have id" % line + else: + assert line.startswith('#'), \ + "Test line '%s' missing id" % line.strip() + + # test that id file is written + def test_id_file_contains_ids_seen(self): + assert os.path.exists(idfile) + fh = open(idfile, 'rb') + ids = load(fh)['ids'] + fh.close() + assert ids + assert ids.keys() + self.assertEqual(map(int, ids.keys()), ids.keys()) + assert ids.values() + + +class TestLoadNamesMode(PluginTester, unittest.TestCase): + """NOTE that this test passing requires the previous test case to + be run! (Otherwise the ids file will not exist) + """ + activate = '--with-id' + plugins = [TestId()] + # Not a typo: # is optional before ids + args = ['-v', '--id-file=%s' % idfile, '2', '#5'] + suitepath = None + + def makeSuite(self): + return None + + def test_load_ids(self): + #print '#' * 70 + #print str(self.output) + #print '#' * 70 + + for line in self.output: + if line.startswith('#'): + assert line.startswith('#2 ') or line.startswith('#5 '), \ + "Unexpected test line '%s'" % line + assert os.path.exists(idfile) + fh = open(idfile, 'rb') + ids = load(fh) + fh.close() + assert ids + assert ids.keys() + ids = ids['ids'] + self.assertEqual(filter(lambda i: int(i), ids.keys()), ids.keys()) + assert len(ids.keys()) > 2 + + +class TestLoadNamesMode_2(PluginTester, unittest.TestCase): + """NOTE that this test passing requires the previous test case to + be run! (Otherwise the ids file will not exist) + + Tests that generators still only have id on one line + """ + activate = '--with-id' + plugins = [TestId()] + args = ['-v', '--id-file=%s' % idfile, '9'] + suitepath = None + + def makeSuite(self): + return None + + def test_load_ids(self): + #print '%' * 70 + #print str(self.output) + #print '%' * 70 + + count = 0 + for line in self.output: + if line.startswith('#'): + count += 1 + self.assertEqual(count, 1) + teardown() + + +class TestWithDoctest_1(PluginTester, unittest.TestCase): + activate = '--with-id' + plugins = [Doctest(), TestId()] + args = ['-v', '--id-file=%s' % idfile, '--with-doctest'] + suitepath = os.path.join(support, 'idp') + + def test_doctests_get_ids(self): + #print '>' * 70 + #print str(self.output) + #print '>' * 70 + + last = None + for line in self.output: + if line.startswith('='): + break + if not line.strip(): + continue + # assert line startswith # or test part matches last + m = test_part.match(line.rstrip()) + assert m + idx, name = m.groups() + assert idx or last is None or name == last, \ + "Expected an id on line %s" % line.strip() + last = name + + fh = open(idfile, 'rb') + ids = load(fh)['ids'] + fh.close() + for key, (file, mod, call) in ids.items(): + assert mod != 'doctest', \ + "Doctest test was incorrectly identified as being part of "\ + "the doctest module itself (#%s)" % key + + +class TestWithDoctest_2(PluginTester, unittest.TestCase): + activate = '--with-id' + plugins = [Doctest(), TestId()] + args = ['-v', '--id-file=%s' % idfile, '--with-doctest', '#2'] + suitepath = None + + def setUp(self): + sys.path.insert(0, os.path.join(support, 'idp')) + super(TestWithDoctest_2, self).setUp() + + def tearDown(self): + sys.path.remove(os.path.join(support, 'idp')) + super(TestWithDoctest_2, self).tearDown() + + def makeSuite(self): + return None + + def test_load_ids_doctest(self): + print '*' * 70 + print str(self.output) + print '*' * 70 + + assert 'Doctest: exm.add_one ... FAIL' in self.output + + count = 0 + for line in self.output: + if line.startswith('#'): + count += 1 + self.assertEqual(count, 1) + teardown() + + +class TestWithDoctestFileTests_1(PluginTester, unittest.TestCase): + activate = '--with-id' + plugins = [Doctest(), TestId()] + args = ['-v', '--id-file=%s' % idfile, '--with-doctest', + '--doctest-extension=.txt'] + suitepath = os.path.join(support, 'dtt', 'docs') + + def test_docfile_tests_get_ids(self): + print '>' * 70 + print str(self.output) + print '>' * 70 + + last = None + for line in self.output: + if line.startswith('='): + break + # assert line startswith # or test part matches last + if not line.strip(): + continue + m = test_part.match(line.rstrip()) + assert m, "line %s does not match expected pattern" % line.strip() + idx, name = m.groups() + assert idx or last is None or name == last, \ + "Expected an id on line %s" % line.strip() + + last = name + fh = open(idfile, 'rb') + ids = load(fh)['ids'] + fh.close() + for key, (file, mod, call) in ids.items(): + assert mod != 'doctest', \ + "Doctest test was incorrectly identified as being part of "\ + "the doctest module itself (#%s)" % key + + +class TestWithDoctestFileTests_2(PluginTester, unittest.TestCase): + activate = '--with-id' + plugins = [Doctest(), TestId()] + args = ['-v', '--id-file=%s' % idfile, '--with-doctest', + '--doctest-extension=.txt', '2'] + suitepath = None + + def setUp(self): + sys.path.insert(0, os.path.join(support, 'dtt', 'docs')) + super(TestWithDoctestFileTests_2, self).setUp() + + def tearDown(self): + sys.path.remove(os.path.join(support, 'dtt', 'docs')) + super(TestWithDoctestFileTests_2, self).tearDown() + + def makeSuite(self): + return None + + def test_load_from_name_id_docfile_test(self): + print '*' * 70 + print str(self.output) + print '*' * 70 + + assert 'Doctest: errdoc.txt ... FAIL' in self.output + + count = 0 + for line in self.output: + if line.startswith('#'): + count += 1 + assert count == 1 + teardown() + + +if __name__ == '__main__': + import logging + logging.basicConfig() + l = logging.getLogger('nose.plugins.testid') + l.setLevel(logging.DEBUG) + + try: + unittest.main() + finally: + teardown() + diff --git a/functional_tests/test_id_plugin.pyc b/functional_tests/test_id_plugin.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ba28e93d84519b0ef15d52ca99a762e4d446777 GIT binary patch literal 9442 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_importer$py.class b/functional_tests/test_importer$py.class new file mode 100644 index 0000000000000000000000000000000000000000..84c0ec721c90909d4c1668ad8561176a7b76ea73 GIT binary patch literal 16447 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 L^52(nJ@5S&3w<_z literal 0 HcmV?d00001 diff --git a/functional_tests/test_isolate_plugin$py.class b/functional_tests/test_isolate_plugin$py.class new file mode 100644 index 0000000000000000000000000000000000000000..0c8c05ad57ccd38f426c0edc0ad7b5b8bf73f7c9 GIT binary patch literal 8005 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_isolate_plugin.py b/functional_tests/test_isolate_plugin.py new file mode 100644 index 0000000..087dcaa --- /dev/null +++ b/functional_tests/test_isolate_plugin.py @@ -0,0 +1,57 @@ +import os +import sys +import unittest +from nose.plugins.isolate import IsolationPlugin +from nose.plugins import PluginTester + +support = os.path.join(os.path.dirname(__file__), 'support') + +class TestDiscovery(PluginTester, unittest.TestCase): + activate = '--with-isolation' + args = ['-v'] + plugins = [IsolationPlugin()] + suitepath = os.path.join(support, 'ipt') + + def runTest(self): + print str(self.output) + + for line in self.output: + if not line.strip(): + continue + if line.startswith('-'): + break + assert line.strip().endswith('ok'), \ + "Failed test: %s" % line.strip() + + +class TestLoadFromNames(PluginTester, unittest.TestCase): + activate = '--with-isolation' + args = ['-v', 'test1/tests.py', 'test2/tests.py'] + plugins = [IsolationPlugin()] + suitepath = None + + def setUp(self): + self._dir = os.getcwd() + os.chdir(os.path.join(support, 'ipt')) + super(TestLoadFromNames, self).setUp() + + def tearDown(self): + os.chdir(self._dir) + super(TestLoadFromNames, self).tearDown() + + def makeSuite(self): + return None + + def runTest(self): + print str(self.output) + + for line in self.output: + if not line.strip(): + continue + if line.startswith('-'): + break + assert line.strip().endswith('ok'), \ + "Failed test: %s" % line.strip() + +if __name__ == '__main__': + unittest.main() diff --git a/functional_tests/test_isolate_plugin.pyc b/functional_tests/test_isolate_plugin.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2361b219ff7554d20199faf2b3cce9d62150b799 GIT binary patch literal 2373 zcmd5-U2hvj6uq)Y1b>1*$1mWVyN(kg!ZViUdcNo0bI(0H`>VJ5>mQ#F$Fle}@c$K>{}Uz^nTU+) zj**eknMg*Fq(&wUnKotA6q80WY00!LqqZ0)l653okwvE~ZdJ~4v?1p>dP~x#xVE@R zvNbuvO)YsNaxU^>)RPnoSJyhrwRLeDlCH?BNQ!m5g5#S!WpV=tL=O91cs%SII==z) ztuLlY$cp^=bbg%W^}-q!>hbr^2Ump-x6jM6s6yzW&*_8AkBbvmo&5uq$K3e{CIAwI z13ch-B**A9#5L&*`z=PWK%jdO^(v!Am^pgVQX$9Ap>1`iPS!p@F}w z3}Z&asV3SJG=ByI2VP@2BC5~6`6XS94t{zezZrRLBvWU~j+meC3-RGxi!S3t(Gv6g zVp2C|ZN`D6@{SlFf=n$hx%YddxBRFO(~V)EQ-eUaiitw~G|8qe-44L+i|vnne}zsm z1RtuvGu};)LYoKXp_~Vun`XHSL;{-->Z`vxtAB-#g&`71poWH`@0YKj^_E8S0&S!KKnh|xK4tL zOXbUMd8Xv3<~cb*Mrp>xtVgTTHa*iZo2F&@q?EcF$9Xbyajc=l@vKPaQx551g7@f= zMxIp1^@Z0cs_)gW`x;)a`^yA?5XM(UmRtNwAlLmo3yy1+_*_DuJxelMW=gjb-L`3) zu30m8qwp4njOx20NuO55YzW8w7?b*A<{qQ@Lzru92RFdv|I_2qCtL&P8}pw0IG@t zhc|d`5lQY(%_Qnpg66i@?ZV?0J}ax^F&<0|D|_) zNWJ5Vf>=LIt56M#+}#+s@_Cj_+#xyr8P2E>N_{^Q!E5#Y|A7Enroz&r8`aNJ*lM5^ ze8OuDL(Y7>GxN^O-~asa7l1UrX9$%>?lP<;mQ1^> zST%RoG?apAb7i%*V5=3LX?ob$wYgeccU6~X#+qJNYfpCec)`suTyJ4gLWn_fxT|Xh zX9y>ub^=!hWs+NXol2*sl#4+BhxIC-P#I(cGg{9xiUev1Kee^L9@i~ zg#~;ZqZax^2Ju70_?YmQxx1`t230lDQk9rd@no(g0(_3;BDg7-J`p3EpUPOmvbe1W z3=M%~$yAHnmhc&|w?ISC@R3K zH)vVRn+D~fT5wH!jhA#<12kkC;tW%-l6#(prW3G1&LfdgKrx0L5K%j6=;-;gHl&VC zWP|AUzt)zZSeqb$CPuJ#dlo>4)!JD$BvcrN&f}<9ln`5)Tg~RU?NAOVq3!#sUgaU$ zyM45~0_B%CG+Mar089Vi{QQE%E*9aH-7BF=>=wylxV%K0bfsF^;kMUw)a$gL3(uAX zC1dMm)h=*RBZeCU+a)hK&C#fobB1BkA}53nhAHS2MQ@1hB_=09?G;1CJE`%!lhzWQ z63J1X=;!MEB;$7`kC9|6$+q8d;}8P} zB+u}Iz8!RmJ2h3q;warjs=AWxWU-Zw@K4Q4UxDH&Iz64m@e`!Sk1!p-e~J%}Fc%8_ ziTUM;Wc*Pm6gtFoh_TR>Zk!@}gvG|=ov`;9_8yt{IQ%P_70_rd_)fa%6mY|bSQH@u zQLNyJ|2jkf#B0w>3~t6(Pq223jZ^!ttj&N+Lw-dvXp@M;3-OVYYb)V`tLtd zQbk%v$0sKHe-XZbA@3_NiQ)hQ$JjeKhJL{639Zp@%YRsVx|!tO%HkW?;_?{IzxSEIX#fBK literal 0 HcmV?d00001 diff --git a/functional_tests/test_issue120/support/some_test.py b/functional_tests/test_issue120/support/some_test.py new file mode 100644 index 0000000..9947266 --- /dev/null +++ b/functional_tests/test_issue120/support/some_test.py @@ -0,0 +1,3 @@ +def some_test(): + pass + diff --git a/functional_tests/test_issue120/support/some_test.pyc b/functional_tests/test_issue120/support/some_test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4872b18ea1f509355b8920b93d19c48199e766df GIT binary patch literal 264 zcmZ8bK?=e^3{1s~g8%5LvBkTB_a54tmr`nkLfc)MR1mK|!gu-tlT}1r*vafDUDj^m|!>VPTD9zT$llN{EWY<$LB>o}`-TM*jd$x!Wu$r4J)hX;F>> import os + >>> from nose.plugins.plugintest import run_buffered as run + >>> from nose.plugins.doctests import Doctest + + >>> support = os.path.join(os.path.dirname(__file__), 'support') + >>> test_name = os.path.join(support, 'some_test.py') + ':nonexistent' + >>> run(argv=['nosetests', '--with-doctest', test_name], + ... plugins=[Doctest()]) + E + ====================================================================== + ERROR: Failure: ValueError (No such test nonexistent) + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + ValueError: No such test nonexistent + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + FAILED (errors=1) diff --git a/functional_tests/test_issue_072$py.class b/functional_tests/test_issue_072$py.class new file mode 100644 index 0000000000000000000000000000000000000000..5c8f6451811b69e9236683b4c178afecc5e61ba2 GIT binary patch literal 7141 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 rX<>Dx1vn{8(dCZKl@_XaH~6apw*Ba5r%Tjt~ literal 0 HcmV?d00001 diff --git a/functional_tests/test_issue_072.py b/functional_tests/test_issue_072.py new file mode 100644 index 0000000..3848f6f --- /dev/null +++ b/functional_tests/test_issue_072.py @@ -0,0 +1,45 @@ +import os +import sys +import unittest + +from nose.plugins import PluginTester +from nose.plugins.builtin import FailureDetail, Capture + +support = os.path.join(os.path.dirname(__file__), 'support') + + +class TestFailureDetailWorks(PluginTester, unittest.TestCase): + activate = '-d' + plugins = [FailureDetail()] + args = ['-v'] + suitepath = os.path.join(support, 'issue072') + + def test_assert_info_in_output(self): + print + print '!' * 70 + print str(self.output) + print '!' * 70 + print + assert '>> assert 4 == 2' in str(self.output) + +class TestFailureDetailWorksWhenChained(PluginTester, unittest.TestCase): + activate = '-d' + plugins = [FailureDetail(), Capture()] + args = ['-v'] + suitepath = os.path.join(support, 'issue072') + + def test_assert_info_and_capt_stdout_in_output(self): + out = str(self.output) + print + print 'x' * 70 + print out + print 'x' * 70 + print + + assert '>> assert 4 == 2' in out, \ + "Assert info not found in chained output" + assert 'something' in out, \ + "Captured stdout not found in chained output" + +if __name__ == '__main__': + unittest.main() diff --git a/functional_tests/test_issue_072.pyc b/functional_tests/test_issue_072.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f3ac45584ee58467521c71c329cbf3534d0d0bd GIT binary patch literal 1889 zcmaJ?U2hvj6uq{s;ezU%)xDb{!L_m3#NjotZoL-Z^Ko{JY=#?axPNGg5C6Ua1>M9Eb{7EPHpMJ0h;wPewjXfQDQnz@8*c zG58B4?aLhTTJnd;waCxYfh6tixz5|UeKB_=*^$dY@&Qi4_PgArGKDlmo)5do`)sIK z!XDI*i|RbhelpgXav0Fthk6#Li>fr=8wbO7;F-jQgU8XfRZ--nbMM3E&aKri^76`V zwd*@>c?#vwD^!R^1!7RA$T{q$pglBNV%m~+*ca1XH=@Tzpz`Ui=;y;0qMTW#`DIdn zUb-EOQfn*o)z`-})r$&)aQtu>_!ABB$}hsQ%M!#2#j(s5wTV(!{7^2!yJa|4)9{gyP( zekjYl^mn0I*$`4I$DWOgGJP34Gj_{joaNR;rCFF5--n}zqvP?s%4QBx;zi_e3wBH$ z`Bp^e#i)4YK0+Apt+R~MY@S0!buui$gFuv0T{VCxH6$v0Q==%0mnMo_51uGm=1H~S zkPVM3zj)&w4jN6a|>8=4xaIm!oCfMdt zNwxxyHA8pTz})*EU~1Ov0ZD^0fmpm2TUh&D-N5JTTd;(OXzT%vAWBo-1mJN?w%zU8 zO?s1`HBNth2Ttv0SXRR@BqqWvci}v*vIN6$R(C91LDt_+zBKM4&Ccx?*tvQPlF+&Y zi{7={Q&y7+9^#r*@C3m=&1C(fQ_nr%5K=3D=k9Y1Zy`9&l4ynpDXP!kAo^3Rws#mp z?R!kOi0R-#FeFYjaifVKO^|E)JX}}xXp&R!Q!$1nKlD8+i0A{V51=+V-T>!)OneA= z3(k8rIEh@42N;Z^p%2E`^&9cZdNNg6>R6*=RwVjp&7Xh2(2l3vTrv8kN*4}1^7Zlj zP1e4&BrVC}6`|)T8C;W&iIFuEyeOVpn!$aKJUyAR|8JpvSbxAFSA0zM36<{>45_!0 fpPSy)_ebA1RF<`O(UWe_R|mmS<8W|z&{zKeGv;E& literal 0 HcmV?d00001 diff --git a/functional_tests/test_issue_082$py.class b/functional_tests/test_issue_082$py.class new file mode 100644 index 0000000000000000000000000000000000000000..5861e105f6ab6437be1d1b41b483b8624e38e398 GIT binary patch literal 9681 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_issue_082.py b/functional_tests/test_issue_082.py new file mode 100644 index 0000000..06fa019 --- /dev/null +++ b/functional_tests/test_issue_082.py @@ -0,0 +1,74 @@ +import os +import re +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO +import sys +import unittest + +from nose.plugins import Plugin, PluginTester +from nose.plugins.builtin import FailureDetail, Capture, Doctest + +support = os.path.join(os.path.dirname(__file__), 'support') + + +class IncludeUnderscoreFilesPlugin(Plugin): + + # Note that this is purely for purposes of testing nose itself, and is + # not intended to be a useful plugin. In particular, the rules it + # applies for _*.py files differ from the nose defaults (e.g. the + # --testmatch option is ignored). + + name = "underscorefiles" + + def wantFile(self, file): + base = os.path.basename(file) + dummy, ext = os.path.splitext(base) + pysrc = ext == '.py' + if pysrc and os.path.basename(file).startswith("_"): + return True + + def wantDirectory(self, dirname): + if os.path.basename(dirname).startswith("_"): + return True + + +class TestIncludeUnderscoreFiles(PluginTester, unittest.TestCase): + activate = '--with-underscorefiles' + plugins = [IncludeUnderscoreFilesPlugin(), Doctest()] + args = ['-v', '--with-doctest'] + suitepath = os.path.join(support, 'issue082') + ignoreFiles = (re.compile(r'^\.'), + # we want _*.py, but don't want e.g. __init__.py, since that + # appears to cause infinite recursion at the moment + re.compile(r'^__'), + re.compile(r'^setup\.py$') + ) + + def test_assert_info_in_output(self): + print self.output + # In future, all four test cases will be run. Backwards-compatibility + # means that can't be done in nose 0.10. + assert '_mypackage._eggs' not in str(self.output) + assert '_mypackage.bacon' not in str(self.output) + assert 'Doctest: mypublicpackage._foo ... FAIL' in str(self.output) + assert 'Doctest: mypublicpackage.bar ... FAIL' in str(self.output) + + +class TestExcludeUnderscoreFilesByDefault(PluginTester, unittest.TestCase): + activate = '-v' + plugins = [Doctest()] + args = ['--with-doctest'] + suitepath = os.path.join(support, 'issue082') + + def test_assert_info_in_output(self): + print self.output + assert '_mypackage._eggs' not in str(self.output) + assert '_mypackage.bacon' not in str(self.output) + assert 'mypublicpackage._foo' not in str(self.output) + assert 'Doctest: mypublicpackage.bar ... FAIL' in str(self.output) + + +if __name__ == '__main__': + unittest.main() diff --git a/functional_tests/test_issue_082.pyc b/functional_tests/test_issue_082.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5304403f24937031583fdaeb64454cc02238fecb GIT binary patch literal 3101 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_load_tests_from_test_case$py.class b/functional_tests/test_load_tests_from_test_case$py.class new file mode 100644 index 0000000000000000000000000000000000000000..f5db34efd85203d0d4e8c002c7063dd1f8ccdc4f GIT binary patch literal 7962 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_load_tests_from_test_case.py b/functional_tests/test_load_tests_from_test_case.py new file mode 100644 index 0000000..42f8563 --- /dev/null +++ b/functional_tests/test_load_tests_from_test_case.py @@ -0,0 +1,56 @@ +""" +Tests that plugins can override loadTestsFromTestCase +""" +import os +import unittest +from nose import loader +from nose.plugins import PluginTester +from nose.plugins.base import Plugin + + +support = os.path.join(os.path.dirname(__file__), 'support') + + +class NoFixturePlug(Plugin): + enabled = True + + def options(self, parser, env): + print "options" + pass + + def configure(self, options, conf): + print "configure" + pass + + def loadTestsFromTestCase(self, testCaseClass): + print "Called!" + class Derived(testCaseClass): + def setUp(self): + pass + def tearDown(self): + pass + # must use nose loader here because the default loader in 2.3 + # won't load tests from base classes + l = loader.TestLoader() + return l.loadTestsFromTestCase(Derived) + + +class TestLoadTestsFromTestCaseHook(PluginTester, unittest.TestCase): + + activate = '-v' + args = [] + plugins = [NoFixturePlug()] + suitepath = os.path.join(support, 'ltftc') + + def runTest(self): + expect = [ + 'test_value (%s.Derived) ... ERROR' % __name__, + 'test_value (tests.Tests) ... ok'] + print str(self.output) + for line in self.output: + if expect: + self.assertEqual(line.strip(), expect.pop(0)) + + +if __name__ == '__main__': + unittest.main() diff --git a/functional_tests/test_load_tests_from_test_case.pyc b/functional_tests/test_load_tests_from_test_case.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0044ab8a4e72237ec3c1d1e716c2fa200553ab2f GIT binary patch literal 2334 zcmb7F+m0JW5Url^W$nEr8_04C;7tHcgyJPA5)Vj#;!Vf{a!@A-#S$7OGi_($@yukp zXIaGV)8-@i4StO;piYf1tMCH0y4+n=U0s(`<;CB-{XhP^H;!rf@%i^VUiL303DJb9 zqPe1pl8r|bj}njOK4mSMv`8gB&D)f9XwuEl=f-4*(S3?7i_0Z z7i?#jk`9?Inlh(LuZb>*o=x^B*&@RNkJ2kN<;Qw!*;O+8l=SJPN5vl7XO(NXrEIv?+2@YF{{Eb*$ z8#j#msl0*BGQTTx`2GJTxA>MbQFUDWmVdOdDj0MShG8BpOc=Vp92P}VXMEH+YP8?R zN!c}3K|Md&{|%Ehgm~`G1U#k%c+Pkvw=pQw zE~Pv%dzc*LfySK^!As_*E*9M386aqcr+#I^IOW$4*v`zOrWMYJQhp9o z=Jac7PRGA|LFWo>QQRU0UI&fDez052Wp8+T@g4R?y$WRgESLtk$M%q^!`L{9vf2!S zPwjBoM7IaS;c)Ok>&M!D{Du~8*r6n@Q7h&tt55*MNYMl~=Cm}ilj&w%B~TE4QMCZbVyzG z_f(%cs;dIQ(gsifTg3Ad6INV_s+rY*ixqA@!j!%*$0>)x`57g3srVel5qikQ3jCKzzD&Qph!%j5VX z&{rTrEI18UzJk&G2O>mcG=Y7a2@fUy?&9ko`Umgc I*xyzE0B(eh$^ZZW literal 0 HcmV?d00001 diff --git a/functional_tests/test_loader$py.class b/functional_tests/test_loader$py.class new file mode 100644 index 0000000000000000000000000000000000000000..da4d7bd73c489c968d9428a77528a9fd94b576cb GIT binary patch literal 32386 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 X7}x@_&!KI;n61CmUr}T~{h$8_p~NuX literal 0 HcmV?d00001 diff --git a/functional_tests/test_loader.py b/functional_tests/test_loader.py new file mode 100644 index 0000000..6f73559 --- /dev/null +++ b/functional_tests/test_loader.py @@ -0,0 +1,451 @@ +import os +import sys +import unittest +from difflib import ndiff +from cStringIO import StringIO + +from nose.config import Config +from nose.plugins.manager import PluginManager +from nose.plugins.skip import Skip +from nose import loader +from nose import suite +from nose.result import _TextTestResult +try: + # 2.7+ + from unittest.runner import _WritelnDecorator +except ImportError: + from unittest import _WritelnDecorator + +support = os.path.abspath(os.path.join(os.path.dirname(__file__), 'support')) + +class TestNoseTestLoader(unittest.TestCase): + + def setUp(self): + self._mods = sys.modules.copy() + suite.ContextSuiteFactory.suiteClass = TreePrintContextSuite + + def tearDown(self): + to_del = [ m for m in sys.modules.keys() if + m not in self._mods ] + if to_del: + for mod in to_del: + del sys.modules[mod] + sys.modules.update(self._mods) + suite.ContextSuiteFactory.suiteClass = suite.ContextSuite + + def test_load_from_name_file(self): + res = unittest.TestResult() + wd = os.path.join(support, 'package1') + l = loader.TestLoader(workingDir=wd) + + file_suite = l.loadTestsFromName('tests/test_example_function.py') + file_suite(res) + assert not res.errors, res.errors + assert not res.failures, res.failures + + def test_load_from_name_dot(self): + res = unittest.TestResult() + wd = os.path.join(support, 'package1') + l = loader.TestLoader(workingDir=wd) + dir_suite = l.loadTestsFromName('.') + dir_suite(res) + assert not res.errors, res.errors + assert not res.failures, res.failures + + def test_load_from_name_file_callable(self): + res = unittest.TestResult() + wd = os.path.join(support, 'package1') + l = loader.TestLoader(workingDir=wd) + suite = l.loadTestsFromName( + 'tests/test_example_function.py:test_times_two') + suite(res) + assert not res.errors, res.errors + assert not res.failures, res.failures + self.assertEqual(res.testsRun, 1) + + def test_fixture_context(self): + res = unittest.TestResult() + wd = os.path.join(support, 'package2') + l = loader.TestLoader(workingDir=wd) + dir_suite = l.loadTestsFromName('.') + dir_suite(res) + + m = sys.modules['test_pak'] + print "test pak state", m.state + + assert not res.errors, res.errors + assert not res.failures, res.failures + self.assertEqual(res.testsRun, 5) + + # Expected order of calls + expect = ['test_pak.setup', + 'test_pak.test_mod.setup', + 'test_pak.test_mod.test_add', + 'test_pak.test_mod.test_minus', + 'test_pak.test_mod.teardown', + 'test_pak.test_sub.setup', + 'test_pak.test_sub.test_mod.setup', + 'test_pak.test_sub.test_mod.TestMaths.setup_class', + 'test_pak.test_sub.test_mod.TestMaths.setup', + 'test_pak.test_sub.test_mod.TestMaths.test_div', + 'test_pak.test_sub.test_mod.TestMaths.teardown', + 'test_pak.test_sub.test_mod.TestMaths.setup', + 'test_pak.test_sub.test_mod.TestMaths.test_two_two', + 'test_pak.test_sub.test_mod.TestMaths.teardown', + 'test_pak.test_sub.test_mod.TestMaths.teardown_class', + 'test_pak.test_sub.test_mod.test', + 'test_pak.test_sub.test_mod.teardown', + 'test_pak.test_sub.teardown', + 'test_pak.teardown'] + self.assertEqual(len(m.state), len(expect)) + for item in m.state: + self.assertEqual(item, expect.pop(0)) + + def test_fixture_context_name_is_module(self): + res = unittest.TestResult() + wd = os.path.join(support, 'package2') + l = loader.TestLoader(workingDir=wd) + suite = l.loadTestsFromName('test_pak.test_mod') + suite(res) + + assert 'test_pak' in sys.modules, \ + "Context did not load test_pak" + m = sys.modules['test_pak'] + print "test pak state", m.state + expect = ['test_pak.setup', + 'test_pak.test_mod.setup', + 'test_pak.test_mod.test_add', + 'test_pak.test_mod.test_minus', + 'test_pak.test_mod.teardown', + 'test_pak.teardown'] + self.assertEqual(len(m.state), len(expect)) + for item in m.state: + self.assertEqual(item, expect.pop(0)) + + def test_fixture_context_name_is_test_function(self): + res = unittest.TestResult() + wd = os.path.join(support, 'package2') + l = loader.TestLoader(workingDir=wd) + suite = l.loadTestsFromName('test_pak.test_mod:test_add') + suite(res) + + assert 'test_pak' in sys.modules, \ + "Context did not load test_pak" + m = sys.modules['test_pak'] + print "test pak state", m.state + expect = ['test_pak.setup', + 'test_pak.test_mod.setup', + 'test_pak.test_mod.test_add', + 'test_pak.test_mod.teardown', + 'test_pak.teardown'] + self.assertEqual(len(m.state), len(expect)) + for item in m.state: + self.assertEqual(item, expect.pop(0)) + + def test_fixture_context_name_is_test_class(self): + res = unittest.TestResult() + wd = os.path.join(support, 'package2') + l = loader.TestLoader(workingDir=wd) + suite = l.loadTestsFromName( + 'test_pak.test_sub.test_mod:TestMaths') + suite(res) + + assert 'test_pak' in sys.modules, \ + "Context did not load test_pak" + m = sys.modules['test_pak'] + # print "test pak state", m.state + expect = ['test_pak.setup', + 'test_pak.test_sub.setup', + 'test_pak.test_sub.test_mod.setup', + 'test_pak.test_sub.test_mod.TestMaths.setup_class', + 'test_pak.test_sub.test_mod.TestMaths.setup', + 'test_pak.test_sub.test_mod.TestMaths.test_div', + 'test_pak.test_sub.test_mod.TestMaths.teardown', + 'test_pak.test_sub.test_mod.TestMaths.setup', + 'test_pak.test_sub.test_mod.TestMaths.test_two_two', + 'test_pak.test_sub.test_mod.TestMaths.teardown', + 'test_pak.test_sub.test_mod.TestMaths.teardown_class', + 'test_pak.test_sub.test_mod.teardown', + 'test_pak.test_sub.teardown', + 'test_pak.teardown'] + self.assertEqual(m.state, expect, diff(expect, m.state)) + + def test_fixture_context_name_is_test_class_test(self): + res = unittest.TestResult() + wd = os.path.join(support, 'package2') + l = loader.TestLoader(workingDir=wd) + suite = l.loadTestsFromName( + 'test_pak.test_sub.test_mod:TestMaths.test_div') + suite(res) + + assert 'test_pak' in sys.modules, \ + "Context not load test_pak" + m = sys.modules['test_pak'] + print "test pak state", m.state + expect = ['test_pak.setup', + 'test_pak.test_sub.setup', + 'test_pak.test_sub.test_mod.setup', + 'test_pak.test_sub.test_mod.TestMaths.setup_class', + 'test_pak.test_sub.test_mod.TestMaths.setup', + 'test_pak.test_sub.test_mod.TestMaths.test_div', + 'test_pak.test_sub.test_mod.TestMaths.teardown', + 'test_pak.test_sub.test_mod.TestMaths.teardown_class', + 'test_pak.test_sub.test_mod.teardown', + 'test_pak.test_sub.teardown', + 'test_pak.teardown'] + self.assertEqual(m.state, expect, diff(expect, m.state)) + + def test_fixture_context_multiple_names(self): + res = unittest.TestResult() + wd = os.path.join(support, 'package2') + l = loader.TestLoader(workingDir=wd) + suite = l.loadTestsFromNames( + ['test_pak.test_sub.test_mod:TestMaths.test_div', + 'test_pak.test_sub.test_mod:TestMaths.test_two_two', + 'test_pak.test_mod:test_add']) + print suite + suite(res) + assert not res.errors, res.errors + assert not res.failures, res.failures + assert 'test_pak' in sys.modules, \ + "Context not load test_pak" + m = sys.modules['test_pak'] + print "test pak state", m.state + expect = ['test_pak.setup', + 'test_pak.test_sub.setup', + 'test_pak.test_sub.test_mod.setup', + 'test_pak.test_sub.test_mod.TestMaths.setup_class', + 'test_pak.test_sub.test_mod.TestMaths.setup', + 'test_pak.test_sub.test_mod.TestMaths.test_div', + 'test_pak.test_sub.test_mod.TestMaths.teardown', + 'test_pak.test_sub.test_mod.TestMaths.setup', + 'test_pak.test_sub.test_mod.TestMaths.test_two_two', + 'test_pak.test_sub.test_mod.TestMaths.teardown', + 'test_pak.test_sub.test_mod.TestMaths.teardown_class', + 'test_pak.test_sub.test_mod.teardown', + 'test_pak.test_sub.teardown', + 'test_pak.test_mod.setup', + 'test_pak.test_mod.test_add', + 'test_pak.test_mod.teardown', + 'test_pak.teardown'] + self.assertEqual(m.state, expect, diff(expect, m.state)) + + def test_fixture_context_multiple_names_some_common_ancestors(self): + stream = _WritelnDecorator(StringIO()) + res = _TextTestResult(stream, 0, 2) + wd = os.path.join(support, 'ltfn') + l = loader.TestLoader(workingDir=wd) + suite = l.loadTestsFromNames( + ['test_pak1.test_mod', + 'test_pak2:test_two_two', + 'test_pak1:test_one_one']) + print suite + suite(res) + res.printErrors() + print stream.getvalue() + assert not res.errors, res.errors + assert not res.failures, res.failures + assert 'state' in sys.modules, \ + "Context not load state module" + m = sys.modules['state'] + print "state", m.called + + expect = ['test_pak1.setup', + 'test_pak1.test_mod.setup', + 'test_pak1.test_mod.test_one_mod_one', + 'test_pak1.test_mod.teardown', + 'test_pak1.test_one_one', + 'test_pak1.teardown', + 'test_pak2.setup', + 'test_pak2.test_two_two', + 'test_pak2.teardown'] + self.assertEqual(m.called, expect, diff(expect, m.called)) + + def test_fixture_context_multiple_names_no_common_ancestors(self): + stream = _WritelnDecorator(StringIO()) + res = _TextTestResult(stream, 0, 2) + wd = os.path.join(support, 'ltfn') + l = loader.TestLoader(workingDir=wd) + suite = l.loadTestsFromNames( + ['test_pak1.test_mod', + 'test_pak2:test_two_two', + 'test_mod']) + print suite + suite(res) + res.printErrors() + print stream.getvalue() + assert not res.errors, res.errors + assert not res.failures, res.failures + assert 'state' in sys.modules, \ + "Context not load state module" + m = sys.modules['state'] + print "state", m.called + + expect = ['test_pak1.setup', + 'test_pak1.test_mod.setup', + 'test_pak1.test_mod.test_one_mod_one', + 'test_pak1.test_mod.teardown', + 'test_pak1.teardown', + 'test_pak2.setup', + 'test_pak2.test_two_two', + 'test_pak2.teardown', + 'test_mod.setup', + 'test_mod.test_mod', + 'test_mod.teardown'] + self.assertEqual(m.called, expect, diff(expect, m.called)) + + def test_mod_setup_fails_no_tests_run(self): + ctx = os.path.join(support, 'ctx') + l = loader.TestLoader(workingDir=ctx) + suite = l.loadTestsFromName('mod_setup_fails.py') + + res = unittest.TestResult() + suite(res) + + assert res.errors + assert not res.failures, res.failures + assert res.testsRun == 0, \ + "Expected to run 0 tests but ran %s" % res.testsRun + + def test_mod_setup_skip_no_tests_run_no_errors(self): + config = Config(plugins=PluginManager(plugins=[Skip()])) + ctx = os.path.join(support, 'ctx') + l = loader.TestLoader(workingDir=ctx, config=config) + suite = l.loadTestsFromName('mod_setup_skip.py') + + res = unittest.TestResult() + suite(res) + + assert not suite.was_setup, "Suite setup did not fail" + assert not res.errors, res.errors + assert not res.failures, res.failures + assert res.skipped + assert res.testsRun == 0, \ + "Expected to run 0 tests but ran %s" % res.testsRun + + def test_mod_import_skip_one_test_no_errors(self): + config = Config(plugins=PluginManager(plugins=[Skip()])) + ctx = os.path.join(support, 'ctx') + l = loader.TestLoader(workingDir=ctx, config=config) + suite = l.loadTestsFromName('mod_import_skip.py') + + res = unittest.TestResult() + suite(res) + + assert not res.errors, res.errors + assert not res.failures, res.failures + assert res.testsRun == 1, \ + "Expected to run 1 tests but ran %s" % res.testsRun + + def test_failed_import(self): + ctx = os.path.join(support, 'ctx') + l = loader.TestLoader(workingDir=ctx) + suite = l.loadTestsFromName('no_such_module.py') + + res = _TextTestResult( + stream=_WritelnDecorator(sys.stdout), + descriptions=0, verbosity=1) + suite(res) + + print res.errors + res.printErrors() + assert res.errors, "Expected errors but got none" + assert not res.failures, res.failures + assert res.testsRun == 1, \ + "Expected to run 1 tests but ran %s" % res.testsRun + + def test_failed_import_module_name(self): + ctx = os.path.join(support, 'ctx') + l = loader.TestLoader(workingDir=ctx) + suite = l.loadTestsFromName('no_such_module') + + res = _TextTestResult( + stream=_WritelnDecorator(sys.stdout), + descriptions=0, verbosity=1) + suite(res) + print res.errors + res.printErrors() + assert res.errors, "Expected errors but got none" + assert not res.failures, res.failures + err = res.errors[0][0].test.exc_class + assert err is ImportError, \ + "Expected import error, got %s" % err + + def test_load_nonsense_name(self): + ctx = os.path.join(support, 'ctx') + l = loader.TestLoader(workingDir=ctx) + suite = l.loadTestsFromName('fred!') + + res = _TextTestResult( + stream=_WritelnDecorator(sys.stdout), + descriptions=0, verbosity=1) + suite(res) + print res.errors + assert res.errors, "Expected errors but got none" + assert not res.failures, res.failures + + def test_generator_with_closure(self): + """Test that a generator test can employ a closure + + Issue #3. If the generator binds early, the last value + of the closure will be seen for each generated test and + the tests will fail. + """ + gen = os.path.join(support, 'gen') + l = loader.TestLoader(workingDir=gen) + suite = l.loadTestsFromName('test') + res = _TextTestResult( + stream=_WritelnDecorator(sys.stdout), + descriptions=0, verbosity=1) + suite(res) + assert not res.errors + self.assertEqual(res.testsRun, 5) + + def test_issue_269(self): + """Test classes that raise exceptions in __init__ do not stop test run + """ + wdir = os.path.join(support, 'issue269') + l = loader.TestLoader(workingDir=wdir) + suite = l.loadTestsFromName('test_bad_class') + res = _TextTestResult( + stream=_WritelnDecorator(sys.stdout), + descriptions=0, verbosity=1) + suite(res) + print res.errors + self.assertEqual(len(res.errors), 1) + assert 'raise Exception("pow")' in res.errors[0][1] + + +# used for comparing lists +def diff(a, b): + return '\n' + '\n'.join([ l for l in ndiff(a, b) + if not l.startswith(' ') ]) + + +# used for context debugging +class TreePrintContextSuite(suite.ContextSuite): + indent = '' + + def setUp(self): + print self, 'setup -->' + suite.ContextSuite.setUp(self) + TreePrintContextSuite.indent += ' ' + + def tearDown(self): + TreePrintContextSuite.indent = TreePrintContextSuite.indent[:-2] + try: + suite.ContextSuite.tearDown(self) + finally: + print self, 'teardown <--' + def __repr__(self): + + return '%s<%s>' % (self.indent, + getattr(self.context, '__name__', self.context)) + __str__ = __repr__ + + +if __name__ == '__main__': + #import logging + #logging.basicConfig() #level=logging.DEBUG) + #logging.getLogger('nose.suite').setLevel(logging.DEBUG) + unittest.main() diff --git a/functional_tests/test_loader.pyc b/functional_tests/test_loader.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c1394e61f8e2e5b860b0d7a6baabf9cd9367d91 GIT binary patch literal 17408 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_multiprocessing/test_nameerror.py b/functional_tests/test_multiprocessing/test_nameerror.py new file mode 100644 index 0000000..f73d02b --- /dev/null +++ b/functional_tests/test_multiprocessing/test_nameerror.py @@ -0,0 +1,31 @@ +import os +import unittest + +from nose.plugins import PluginTester +from nose.plugins.skip import SkipTest +from nose.plugins.multiprocess import MultiProcess + + +support = os.path.join(os.path.dirname(__file__), 'support') + + +def setup(): + try: + import multiprocessing + if 'active' in MultiProcess.status: + raise SkipTest("Multiprocess plugin is active. Skipping tests of " + "plugin itself.") + except ImportError: + raise SkipTest("multiprocessing module not available") + + +class TestMPNameError(PluginTester, unittest.TestCase): + activate = '--processes=2' + plugins = [MultiProcess()] + suitepath = os.path.join(support, 'nameerror.py') + + def runTest(self): + print str(self.output) + assert 'NameError' in self.output + assert "'undefined_variable' is not defined" in self.output + diff --git a/functional_tests/test_multiprocessing/test_nameerror.pyc b/functional_tests/test_multiprocessing/test_nameerror.pyc new file mode 100644 index 0000000000000000000000000000000000000000..014f45f062c92f63a1f4b00c094c8788c4f4423a GIT binary patch literal 1511 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_namespace_pkg.py b/functional_tests/test_namespace_pkg.py new file mode 100644 index 0000000..2db051e --- /dev/null +++ b/functional_tests/test_namespace_pkg.py @@ -0,0 +1,58 @@ +import os +import sys +import unittest +from cStringIO import StringIO +from nose.core import TestProgram +from test_program import TestRunner + +here = os.path.dirname(__file__) +support = os.path.join(here, 'support') + +class TestNamespacePackages(unittest.TestCase): + + def setUp(self): + self.cwd = os.getcwd() + self.orig_path = sys.path[:] + test_dir = os.path.join(support, 'namespace_pkg') + os.chdir(test_dir) + sys.path.append(os.path.join(test_dir, 'site-packages')) + + def tearDown(self): + sys.path = self.orig_path + os.chdir(self.cwd) + + def test_namespace_pkg(self): + """Ensure namespace packages work/can import from each other""" + stream = StringIO() + runner = TestRunner(stream=stream) + runner.verbosity = 2 + prog = TestProgram(argv=[''], + testRunner=runner, + exit=False) + res = runner.result + self.assertEqual(res.testsRun, 1, + "Expected to run 1 test, ran %s" % res.testsRun) + assert res.wasSuccessful() + assert not res.errors + assert not res.failures + + def test_traverse_namespace(self): + """Ensure the --traverse-namespace option tests the other + namespace package sibling also. + """ + stream = StringIO() + runner = TestRunner(stream=stream) + runner.verbosity = 2 + prog = TestProgram(argv=['', '--traverse-namespace'], + testRunner=runner, + exit=False) + res = runner.result + self.assertEqual(res.testsRun, 2, + "Expected to run 2 tests, ran %s" % res.testsRun) + assert res.wasSuccessful() + assert not res.errors + assert not res.failures + + +if __name__ == '__main__': + unittest.main() diff --git a/functional_tests/test_namespace_pkg.pyc b/functional_tests/test_namespace_pkg.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eee29210c3146cf17b1224e16ed40b0bdc6e9cb2 GIT binary patch literal 2552 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_plugin_api.py b/functional_tests/test_plugin_api.py new file mode 100644 index 0000000..c508ded --- /dev/null +++ b/functional_tests/test_plugin_api.py @@ -0,0 +1,45 @@ +""" +Functional tests of plugin apis -- individual plugintester runs for +test plugins that implement one or more hooks for testing. +""" +import os +import sys +import unittest +from nose.plugins import Plugin, PluginTester + +support = os.path.join(os.path.dirname(__file__), 'support') + +class AllFail(Plugin): + def prepareTestCase(self, test): + self.test = test + return self.fail + + def fail(self, result): + result.startTest(self.test) + try: + try: + assert False, "I want to fail!" + except: + result.addFailure(self.test, sys.exc_info()) + finally: + result.stopTest(self.test) + +class TestPrepareTestCase_MakeAllFail(PluginTester, unittest.TestCase): + activate = '--with-allfail' + args = ['-v'] + plugins = [AllFail()] + suitepath = os.path.join(support, 'package2') + + def runTest(self): + print "x" * 70 + print str(self.output) + print "x" * 70 + for line in self.output: + if line.startswith('test_pak'): + assert line.strip().endswith('FAIL'), \ + "Expected failure but got: %s" % line.strip() + assert not str(self.output).strip().endswith('OK') + + +if __name__ == '__main__': + unittest.main() diff --git a/functional_tests/test_plugin_api.pyc b/functional_tests/test_plugin_api.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e301c023c832f13997a0a49f24908534b44eccb8 GIT binary patch literal 2048 zcmZ8hZEqVz5S~51BzE&wXet3kT#8UlB|B=wCxiqcZQ2TOs|!+!rRuc!*3Q=FyW@5* zO`u4q>5t$GU-^Oj0_K@>oIsr2o86tAoq6V&ng6ri{q4^$hGSa%HTe4t5BE1u3DJmW zMKeVsB|<=>K!gU38k7VyYf{>xQH#yWOqVYBa)U1T@(oIwWLh-g zoff?#x*+;#v_?sr4F5OCuG55HSXrKJklCbUm0ksuZ?Jt<*}^TADN^j)QLh7k|KO>6 zSY=~x^Gv6qH_p2-pM*tPO>GwH!n&~E4{es%Q=3$*QlCK4lwnzAE}Z0L7wg553;l`q zp`910nVZaqd1k`A4Ci@i!jn9o$qjkRX464;*bCrFo2TdUs#{x%Kgu(H1qxMB}I($acNSc`TJn-V6eA4 zSppx!yxYYb*8s;1{h&Dan{1~jO`%HzFb??kI&V4nM5?2f(X@Ci(IhNzG*$w#Od!tq zuV2ymHnH19my~aTi*Ex`$5}{9DQLGP@CGryOv4MN7@sp)UL3PMD0CJIF!zG@jwJS> zgM&}Q*P0XU^AMoy^t!y~kiiKj@kHKYbCJ z(08AOC4Fn|PPvIR$TX1>$SqX-qE~b-`$HG~;T^O523U@8hYpDL`>(A(>FYE_UK}Xe zKb0^GJ)Y^Q*&D-i>;r%U9_}xmm>yg*lU&itEhw5WoZR6@%u_AZhqH}?F$nMa? z@iTkKvjpTB>$uP}Db2(Er{B9feDvt7Fk^2L;U%*u{JHYsH1}VIpE-}PpMBQ@p=x&d zQn)G1%`0D2b;YMPGeQRk3Ohh5%yquBg*-Hj300(jaoYqpcKBpL z$Bx?IV(wC#iMbc_z@-S46`?oD8=8AGnu!Fc%c+ZifS9X0MjcS@M4`~^By##Hw^_tr zs*A9)o_mQui9Y77E7z`388z||4~JOVipi|j)OH{~a6M?7#c>Zx@^l<0`IyTCp+xXn z^s3ByB*bF}jc;)2;#HTIv%28}3sXjSVdGQSK!y`rq7oPHPOMZR?^7K&vW9zTh!NZo zxFlrwC|MQF`tWmT+`}Z$-iIVUvv3WEIz<5XYk=q8;|U1h_kOSw+}^x%b6x!lcSV8t literal 0 HcmV?d00001 diff --git a/functional_tests/test_plugins$py.class b/functional_tests/test_plugins$py.class new file mode 100644 index 0000000000000000000000000000000000000000..9e5aa9c1808f78d62deb2ec3ac2e5f80060660d9 GIT binary patch literal 10200 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 a7GG~IMmqy}_zbW%v%{KY%@*JstM0Go4|CxF literal 0 HcmV?d00001 diff --git a/functional_tests/test_plugins.py b/functional_tests/test_plugins.py new file mode 100644 index 0000000..eff6c7a --- /dev/null +++ b/functional_tests/test_plugins.py @@ -0,0 +1,71 @@ +import os +import sys +import unittest +from nose.config import Config +from nose.core import TestProgram + +here = os.path.abspath(os.path.dirname(__file__)) +support = os.path.join(here, 'support') +units = os.path.normpath(os.path.join(here, '..', 'unit_tests')) + +if units not in sys.path: + sys.path.insert(0, units) +from mock import RecordingPluginManager + + +class TestPluginCalls(unittest.TestCase): + """ + Tests how plugins are called throughout a standard test run + """ + def test_plugin_calls_package1(self): + wdir = os.path.join(support, 'package1') + man = RecordingPluginManager() + conf = Config(plugins=man, stream=sys.stdout) + t = TestProgram(defaultTest=wdir, config=conf, + argv=['test_plugin_calls_package1'], exit=False) + print man.calls() + assert man.called + + self.assertEqual( + man.calls(), + ['loadPlugins', 'addOptions', 'configure', 'begin', + 'prepareTestLoader', 'loadTestsFromNames', 'loadTestsFromName', + 'prepareTestRunner', 'prepareTest', 'setOutputStream', + 'prepareTestResult', 'beforeDirectory', 'wantFile', + 'wantDirectory', 'beforeContext', 'beforeImport', + 'afterImport', 'wantModule', 'wantClass', 'wantFunction', + 'makeTest', 'wantMethod', 'loadTestsFromTestClass', + 'loadTestsFromTestCase', 'loadTestsFromModule', 'startContext', + 'beforeTest', 'prepareTestCase', 'startTest', 'addSuccess', + 'stopTest', 'afterTest', 'stopContext', 'afterContext', + 'loadTestsFromDir', 'afterDirectory', + 'report', 'finalize']) + + def test_plugin_calls_package1_verbose(self): + wdir = os.path.join(support, 'package1') + man = RecordingPluginManager() + conf = Config(plugins=man, stream=sys.stdout) + t = TestProgram(defaultTest=wdir, config=conf, + argv=['test_plugin_calls_package1', '-v'], exit=False) + print man.calls() + assert man.called + + self.assertEqual( + man.calls(), + ['loadPlugins', 'addOptions', 'configure', 'begin', + 'prepareTestLoader', 'loadTestsFromNames', 'loadTestsFromName', + 'prepareTestRunner', 'prepareTest', 'setOutputStream', + 'prepareTestResult', 'beforeDirectory', 'wantFile', + 'wantDirectory', 'beforeContext', 'beforeImport', + 'afterImport', 'wantModule', 'wantClass', 'wantFunction', + 'makeTest', 'wantMethod', 'loadTestsFromTestClass', + 'loadTestsFromTestCase', 'loadTestsFromModule', 'startContext', + 'beforeTest', 'prepareTestCase', 'startTest', 'describeTest', + 'testName', 'addSuccess', 'stopTest', 'afterTest', 'stopContext', + 'afterContext', 'loadTestsFromDir', 'afterDirectory', + 'report', 'finalize']) + + + +if __name__ == '__main__': + unittest.main() diff --git a/functional_tests/test_plugins.pyc b/functional_tests/test_plugins.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7db306402b54617d6f40405730f1fae19c4dc559 GIT binary patch literal 3160 zcmcguS#ujj5bl+)$hV!t&aK?aCC4FzncbM)l5CAgdsOP95;&1~OyVQbAC$$7%U-~5ePSS-6gMGuNcNy? zY9O2*42soZu_LmFJvcZc4&Q*9Uyez-fNOLT&x#%88WaUd08_?fYfjqpQlG~JMwBEz zDot2kmS05nM82;d6L(zfxV$V$It@+Oa)K*`0+IFV1e{;128_cfU?okOajTef>4nSv z^DJ#;QOA#QQ?J`iGoP0*uGRcFetJplLyvuVjF}W?PhezRBh74_w4S$ntvGoaB~i;| zeg;dbR;4RZyPfangm!#^C$9iJ5~6u1k^rCTLxKcwFf3-hI>eoi0pk^Xs57r@rmrep z)tpzN%vBoD?`*|yW@)drnf81os^mUOY?MKYBbBU|==laOCn>6UR`BE{NMJ;a_?N`v zZlty(yMcIQOJsLg5EB@*gZvGNKO%7%^3pEGNcphr!1@u{!NE~Mwpy4T5kHAN!@GJa zo~E8BUqL}UBi%6x{H*+p-0tAkdFm~8Afrg4h0;l}Q(~vZ&WHse=ETm6Ju3E?SS0#{ z*afkRVo!=aCHAz~Gh)w*Jty|O*oxQ-VlRrlB=)k{D`KyTy(YFQ_PW>`VsDDQCHA)1 znzXJe&rlZY)g{DY4c>`#qsA6;xU5v@N7<7$xz8LJLdICvjCyU)Z0J%$8Rdtu6lJY# ze+mP}JuJ?LOi&)~Yvw-pI`*oookq4GPp(vptbNw?ahl}5jD3abUgnSwkqrlu`8kZb znd>6=G~rul0O#f*r80{0)hz9-MI8*e`A_1*Doig)pn|@=rC4d4JO8ZbyFI^N_kcbW?TMP+ zW&JrzkxHJXwg;Vz52q{bD9`n@Ue`++jGx}$iMBLG8d^Q-{AOxBYxkX~FlvRK)*k=Ri`rExG>-`P2%ix?CfoBLeTGHt;+?SMVR^sS^IG?fVakr-$yt*U91qID zVlac>iD0fYQ`+ysH?X?GeG~s}7e)(i;iX>wca8Y}?ZS3R>>aVo(yAV`|wa+K{jsWPL~9-Zrcs!_qLU2gBB4$a%vTv0+IV_ASGF7&6>2J%*)jl*9`h zEfP)=*uc$c!WqI@!a2fuLWOXFaFK9{aG7w0aFuWkpdHD&yphElMH8ak4w5^fQ06KVv$mCPN0R;5|yl%X;25%Bcc{60q{viX4UA>ku}GW-*cJ|!sYKj-KH z;UPh5g8xVUX^9vz*34i&*-zX9HxjWExW{jA6TClJ)Q1JB~E>%7=AM@knu=%s2>M kkwkPIc|gcX_%hm5IhYEjO6Sn3E|+G5g=`v3p{ literal 0 HcmV?d00001 diff --git a/functional_tests/test_plugintest$py.class b/functional_tests/test_plugintest$py.class new file mode 100644 index 0000000000000000000000000000000000000000..2393f42a8ec3fc11e61b55f14d6a519185fb9b81 GIT binary patch literal 9339 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_program$py.class b/functional_tests/test_program$py.class new file mode 100644 index 0000000000000000000000000000000000000000..7c24d9732f4c99481135507f1ad00d21e91b5d44 GIT binary patch literal 15837 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_program.py b/functional_tests/test_program.py new file mode 100644 index 0000000..bb63818 --- /dev/null +++ b/functional_tests/test_program.py @@ -0,0 +1,189 @@ +import os +import unittest +from cStringIO import StringIO +from nose import SkipTest +from nose.core import TestProgram +from nose.config import Config +from nose.plugins.manager import DefaultPluginManager +from nose.result import _TextTestResult + +here = os.path.dirname(__file__) +support = os.path.join(here, 'support') + +class TestRunner(unittest.TextTestRunner): + def _makeResult(self): + self.result = _TextTestResult( + self.stream, self.descriptions, self.verbosity) + return self.result + +# Note that all of these tests use a set config to avoid the loading +# of plugins or settings from .noserc. + +class TestTestProgram(unittest.TestCase): + + def test_run_support_ctx(self): + """Collect and run tests in functional_tests/support/ctx + + This should collect no tests in the default configuration, since + none of the modules have test-like names. + """ + stream = StringIO() + runner = TestRunner(stream=stream) + prog = TestProgram(defaultTest=os.path.join(support, 'ctx'), + argv=['test_run_support_ctx'], + testRunner=runner, + config=Config(), + exit=False) + res = runner.result + print stream.getvalue() + self.assertEqual(res.testsRun, 0, + "Expected to run 0 tests, ran %s" % res.testsRun) + assert res.wasSuccessful() + assert not res.errors + assert not res.failures + + def test_run_support_package2(self): + """Collect and run tests in functional_tests/support/package2 + + This should collect and run 5 tests. + """ + stream = StringIO() + runner = TestRunner(stream=stream) + prog = TestProgram(defaultTest=os.path.join(support, 'package2'), + argv=['test_run_support_package2', '-v'], + testRunner=runner, + config=Config(), + exit=False) + res = runner.result + print stream.getvalue() + self.assertEqual(res.testsRun, 5, + "Expected to run 5 tests, ran %s" % res.testsRun) + assert res.wasSuccessful() + assert not res.errors + assert not res.failures + + def test_run_support_package3(self): + """Collect and run tests in functional_tests/support/package3 + + This should collect and run 2 test. The package layout is: + + lib/ + a.py + src/ + b.py + tests/ + test_a.py + test_b.py + """ + stream = StringIO() + runner = TestRunner(stream=stream) + + prog = TestProgram(defaultTest=os.path.join(support, 'package3'), + argv=['test_run_support_package3', '-v'], + testRunner=runner, + config=Config(), + exit=False) + res = runner.result + print stream.getvalue() + self.assertEqual(res.testsRun, 2, + "Expected to run 2 tests, ran %s" % res.testsRun) + assert res.wasSuccessful() + assert not res.errors + assert not res.failures + + def test_run_support_twist(self): + """Collect and run tests in functional/support/twist + + This should collect and run 4 tests with 2 fails and an error. + """ + try: + from twisted.trial.unittest import TestCase + except ImportError: + raise SkipTest('twisted not available; skipping') + stream = StringIO() + runner = TestRunner(stream=stream, verbosity=2) + + prog = TestProgram(defaultTest=os.path.join(support, 'twist'), + argv=['test_run_support_twist'], + testRunner=runner, + config=Config(stream=stream), + exit=False) + res = runner.result + print stream.getvalue() + + # some versions of twisted.trial.unittest.TestCase have + # runTest in the base class -- this is wrong! But we have + # to deal with it + if hasattr(TestCase, 'runTest'): + expect = 5 + else: + expect = 4 + self.assertEqual(res.testsRun, expect, + "Expected to run %s tests, ran %s" % + (expect, res.testsRun)) + assert not res.wasSuccessful() + assert len(res.errors) == 1 + assert len(res.failures) == 2 + + def test_issue_130(self): + """Collect and run tests in support/issue130 without error. + + This tests that the result and error classes can handle string + exceptions. + """ + import warnings + warnings.filterwarnings('ignore', category=DeprecationWarning, + module='test') + + stream = StringIO() + runner = TestRunner(stream=stream, verbosity=2) + + prog = TestProgram(defaultTest=os.path.join(support, 'issue130'), + argv=['test_issue_130'], + testRunner=runner, + config=Config(stream=stream, + plugins=DefaultPluginManager()), + exit=False) + res = runner.result + print stream.getvalue() + self.assertEqual(res.testsRun, 0) # error is in setup + assert not res.wasSuccessful() + assert res.errors + assert not res.failures + + def test_defaultTest_list(self): + stream = StringIO() + runner = TestRunner(stream=stream, verbosity=2) + tests = [os.path.join(support, 'package2'), + os.path.join(support, 'package3')] + prog = TestProgram(defaultTest=tests, + argv=['test_run_support_package2_3', '-v'], + testRunner=runner, + config=Config(), + exit=False) + res = runner.result + print stream.getvalue() + self.assertEqual(res.testsRun, 7) + + def test_illegal_packages_not_selected(self): + stream = StringIO() + runner = TestRunner(stream=stream, verbosity=2) + + prog = TestProgram(defaultTest=os.path.join(support, 'issue143'), + argv=['test_issue_143'], + testRunner=runner, + config=Config(stream=stream, + plugins=DefaultPluginManager()), + exit=False) + res = runner.result + print stream.getvalue() + self.assertEqual(res.testsRun, 0) + assert res.wasSuccessful() + assert not res.errors + assert not res.failures + + +if __name__ == '__main__': + #import logging + #logging.basicConfig(level=logging.DEBUG) + unittest.main() diff --git a/functional_tests/test_program.pyc b/functional_tests/test_program.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7ee92f665a01c07635d58aa563cb3b033a10ce6 GIT binary patch literal 6646 zcmd^DTW=gm6+S)o*q-s&j<2z^w`L*BXqDKT3lc0^ZI*1v?gM7kiL~GcTAi8lxZ`%u zY*&qw2uDI|egs0|2_EII9CpN5Mg@l--%XRDOsygR8=R4=< zZwvLG|LX2;OUmCA{@%l5|AL||awxLrD}n3<(hlUXBH5JeO-azM$l~tI44<6_G)NU<*+W{O^E=sy2=8BwR{beb8R>Ukz zdsa>>vbQSjIWd8xYqF0G)Z~{Ury@VyTbFiS2R3wIemHPd%%-#(^0Xq|MSKPCZt;~M z53m7|N6iHsZnqimoO381xgyOwpZ<8P=P>O(HrDx`dCXz!v!dH6k|WWa(lvPP*B%S5K@ln90jsB{1pe{3Vf+mJ)!~n2_`#6>A z%W3wf4S5Nj074cy`tTF#%oRDAk`w@}5n;Z*0o4guUE#bQFz#06OW=upYtoqLSwGX$ zo`xAdMr;f=6J^6F!jCJqZ0rCTthfN2F`@gT2HZXys`) z%_H=SzUYk_R7&KBNoI|o3-m=aSbAIlTtD40?l{T%Mp3R8lUQpC_uz|ulIaW;F3#%+ ztY?XR)Ni$nwfnGtgzZN<9m07~6flD{Gln(4{C<*VeSE9Qz34am8Q0l-gT?D86v0|} z7+*o(CLa4H3V~~?1xvvu${9QjJo9KT25Z4epr-Q-*8YT70B7jH?sA9dK)uF3x^|(_9{Iy`*&r&vpBsV{QQ7?|8+WKuJV-s|E~Ki|An77K=%2 z45p~zy?Gqb*+nZXR#BuB(7Bi>KYbPx5#cw&LZr$)@l4&@-r*-MBN`N067#I`64X~FSGkya9R+@XzG9^QvJe^7f+z2)dmL1kS ztxGyDpuavYfr$q{BEeFM1j`K<=U)diL-YavMw$is1%`A*UV?OkW+{Do_zB1p-NXW^ zTDOk*ivmKHGjv*FjWldSPC)Cf$_c0(C>c7nxnB`a#?ad2&OP_qQa0wU>8#gyzVcn7s1x|zJ)_#fNDW%zQtzh}U|E;XJfRBM|w(L@UVX zKlDA1FeOftF369cbvzoNfx~eF>V#5KJR72zWM|i{KiL3jDO3@G6*-F2@9~aIdmDjf zl5O|%)X`L^e;gc7>C>l2j0Qa!;@KaSap>3R)bvybiA|g<370a>3w@muzRcufK98sy zqhs`F-p$@(?LuPqE=GTb$KF7JXf>D$mO;dpLCMwuauT#9PtdAYf_jCmwaR+%hNo#1 zD#dvpkNqnOk8_4E02tIAcmiBeYUpzsR0#DF*@(OVL`*`!HHXd$LnqWh&*mgmaO3^~ zWrj|uG!)qj3erUdy?=d)D94&u0hlZy4rC*e0>lB)5yT1MHNrMUqGFi~2W)Kcbr0FA zK7c~!ribaMW9!Tmob*wXe8~QW7jRMhGyr63ZQtCw^Pa+t&aQ;k(5ERxsesVhgT!eB zLb`1y0uO0^%Hzyp?n0;k8?9phvphN#-y(|n8dY!Q>CNm{3$f0h(E_G~rU*iSQO zieVRT^kdU24EBf+%g6cKm5+nh5ioYAg>CXK*pU#(?Dx$eh-LcOm?7?lg>dhlcdeT=RCBB@Ed^!VPaG(O9MYmAkAB(@cY;(x_*n zG?Tuk4S(rVyOWy1Y=`bP}ldw6HeqNva>A6kCERsLJ?rDJUBEhSQH z9pq0-O}8=lQjAQlrj&ki$87^sBTc4BBWA23T8D^PmFOK7O&0X!5e-gs zkHviy>>n$9GKN_{>Z9dd&s1vbwYAzCwYO{6YKtlmtGjElyx-y9JM<;8P-1b}{{@F}~Vp?JS+e_K{D|N0iX$74HTkC67!MsDEjv z1v7!=hT%n+RVGXi48A!`Ca8}Y=J+-Jrs=roCZ| u4aqKRB$K*NpR>@FsE?x?*UNUC_aO_*^=dEy3!{IP6|k(W+SczDrv3qZiL>Sa literal 0 HcmV?d00001 diff --git a/functional_tests/test_result$py.class b/functional_tests/test_result$py.class new file mode 100644 index 0000000000000000000000000000000000000000..a8334dcb4804355a98a7f0654b9a85178a034626 GIT binary patch literal 6055 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_result.py b/functional_tests/test_result.py new file mode 100644 index 0000000..7206020 --- /dev/null +++ b/functional_tests/test_result.py @@ -0,0 +1,32 @@ +import os +import sys +import unittest +from cStringIO import StringIO +from nose.config import Config +from nose.core import TestProgram +from nose.plugins.manager import PluginManager + + +support = os.path.join(os.path.dirname(__file__), 'support') + +class TestResultSummary(unittest.TestCase): + + def test_with_todo_plugin(self): + pkpath = os.path.join(support, 'todo') + sys.path.insert(0, pkpath) + from todoplug import TodoPlugin + + stream = StringIO() + config = Config(stream=stream, + plugins=PluginManager([TodoPlugin()])) + + TestProgram(argv=['t', '--with-todo', pkpath], + config=config, exit=False) + out = stream.getvalue() + print out + self.assert_('FAILED (TODO=1)' in out) + + +if __name__ == '__main__': + unittest.main() + diff --git a/functional_tests/test_result.pyc b/functional_tests/test_result.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2595f1b1679ce694f15a05f47e0170f512e4e75a GIT binary patch literal 1445 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 LlDtV?_xk29A!f%H literal 0 HcmV?d00001 diff --git a/functional_tests/test_skip_pdb_interaction$py.class b/functional_tests/test_skip_pdb_interaction$py.class new file mode 100644 index 0000000000000000000000000000000000000000..2827fa4fa256bbc11abc8ec816649954c905777e GIT binary patch literal 7904 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 Vc&6VogVpbz8MOB{?mQfre*q#hv?c%m literal 0 HcmV?d00001 diff --git a/functional_tests/test_success$py.class b/functional_tests/test_success$py.class new file mode 100644 index 0000000000000000000000000000000000000000..6a07a87457c766f7f361eb0ce971be808ea01256 GIT binary patch literal 6176 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 AbpQYW literal 0 HcmV?d00001 diff --git a/functional_tests/test_success.py b/functional_tests/test_success.py new file mode 100644 index 0000000..760a7d1 --- /dev/null +++ b/functional_tests/test_success.py @@ -0,0 +1,43 @@ +import os +import unittest +from nose.plugins.plugintest import PluginTester, remove_timings + +support = os.path.join(os.path.dirname(__file__), 'support') + + +class TestSingleTestPass(PluginTester, unittest.TestCase): + activate = '-v' + plugins = [] + suitepath = os.path.join(support, 'pass') + + def test_single_test_pass(self): + # note that this doesn't use nose.plugins.doctests.run, in order that + # this test fails if the final terminating newline is not present (it + # could still be written as a doctest -- PluginTester was just closer + # to hand) + print self.output + output = remove_timings(str(self.output)) + assert output == """\ +test.test ... ok + +---------------------------------------------------------------------- +Ran 1 test in ...s + +OK +""" + +class TestZeroTestsPass(PluginTester, unittest.TestCase): + activate = '-v' + plugins = [] + suitepath = os.path.join(support, 'empty') + + def test_zero_tests_pass(self): + print self.output + output = remove_timings(str(self.output)) + assert output == """\ + +---------------------------------------------------------------------- +Ran 0 tests in ...s + +OK +""" diff --git a/functional_tests/test_success.pyc b/functional_tests/test_success.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ed5392b409ab3d57c462a3eb14f76b094665ab0 GIT binary patch literal 1601 zcmcIkOK;Oa5T143Zp%C30#e|VOC4IlnL>p?LJHNk7kn{RvT5Bow&fjHDwIphkKlLm z3z%=6rj*C6INsUWoyqKcGoN?yW5fUQ{rPx8%cmjpjtqM)VoEe83Ts7CsA#Ha?oryH zut8~)!X_z0l(Z;m(^Bb>>C%O`dUWAY*rREKjJ%2xpJpCSoAiz7g6LzoM$^`HTYF_& z$LagSj-)XjddO&9#9mq*CfR|pY)X4eHcGR|PfWzgBFPRd!`)Uzk(bQ(M2F|TSW*LR zFShm?(${d`7Qq73p$k1a6kYOdUc%~JLg@mE{;(;38(UOa{;-8ZfytV5#%)pZii?W*FC_<3%cV2DveuNG;1 zUxM5&yURyTInI+z%RX^Wgsl=bMa)MUt1F03(+I3V2`Cn#t?=u6d{-6+f+#d)j@kY{ za-y4NQSj-_v-6XLzDY7Zb7ZuGKiGIhBMmqj?bZ0&|4A}ee10GlXSjM$}4_Cn)+o~*Kq{R=# z5#$e+lv~cu_WOlbBVXm>m^w{LaC%AaSpq)S>;&6DtX=t}90xTLa~EnI2;Wa_KoZVc bqm|V!$m%+R#4jN%iqum*b>Dl^c+~$1W#t`% literal 0 HcmV?d00001 diff --git a/functional_tests/test_suite$py.class b/functional_tests/test_suite$py.class new file mode 100644 index 0000000000000000000000000000000000000000..4a308800c46ca36f1d95849867621ec7bfa978ed GIT binary patch literal 7698 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 literal 0 HcmV?d00001 diff --git a/functional_tests/test_suite.py b/functional_tests/test_suite.py new file mode 100644 index 0000000..a411469 --- /dev/null +++ b/functional_tests/test_suite.py @@ -0,0 +1,47 @@ +import os +import sys +import unittest +from nose import case +from nose.suite import ContextSuiteFactory + +support = os.path.abspath(os.path.join(os.path.dirname(__file__), 'support')) + +class TestContextSuiteFactory(unittest.TestCase): + + def setUp(self): + self._mods = sys.modules.copy() + self._path = sys.path[:] + sys.path.insert(0, os.path.join(support, 'package2')) + + def tearDown(self): + to_del = [ m for m in sys.modules.keys() if + m not in self._mods ] + if to_del: + for mod in to_del: + del sys.modules[mod] + sys.modules.update(self._mods) + sys.path = self._path + + def test_find_context(self): + from test_pak import test_mod + + factory = ContextSuiteFactory() + tests = [case.FunctionTestCase(test_mod.test_add), + case.FunctionTestCase(test_mod.test_minus)] + suite = factory(tests) + self.assertEqual(suite.context, test_mod) + + def test_ancestry(self): + from test_pak.test_sub.test_mod import TestMaths + from test_pak.test_sub import test_mod + from test_pak import test_sub + import test_pak + + factory = ContextSuiteFactory() + ancestry = [l for l in factory.ancestry(TestMaths)] + self.assertEqual(ancestry, + [test_mod, test_sub, test_pak]) + + +if __name__ == '__main__': + unittest.main() diff --git a/functional_tests/test_suite.pyc b/functional_tests/test_suite.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00af036288ec38f4086d93d3ae0831b5a61a3abe GIT binary patch literal 2210 zcmaJ?ZExH}5T5nD#Yrv=DQ`un1ytauXrfenr67{_6U~PW2yrN~+_NvZ=h&CqJ!r&T zg%l+I0^(=#3wWMc=MoSijVH6S_U_E{JTpD}tH1usADxoL}VBo zBSWK+NQRMQMkXyO+cIp6$s(C_r0mMDD<&5ydQz-Oa0}rz7@w(7^{sBYw|^s{=gl2@K9Dj1;I|WFeBYHPVs0Ty&cS(8EoBO^S{bvG{e~ zN5rD0_7+Y$Dq#4M$TS^I($oCWV1p5Ex(lcEp4HjBM4gIIJ-txJ<>lBjs|4F&Z<_ia zLrA?YHjB#TKq`V;hnHT|MP;!R#o6+ufi7wMW0<|^toSMQ`JO+U?p3wRlUZKoU?$n$ z-QRt*H=b7`U({7vCLUS2J=(;PrClV)oLug|ow5$c^Tq6#zELQ-$ubnm-{G8My^O>w zoCPE$a(ME-ER8JiivpmYp41y+77;@`=dX*w^D2^MOBOAu?}>T&F1^E(ZCQc}J!;XG z&(FV>@2Irq@VGe-k~KVeov$VvY=&b6<7lvnj1=AEhy4>v4lJ|P&?E;Pl){;UW~oHa z@_e0Sd8v$n3JuMt8342~b}hM8F@?`wW9$~q4h?mFjlb<=1&}}*SQ;(?AhFpH z09b7DtCe0UqNZ0_ld_J22RCwt4yNg(DF6%N@qe8>Zn|2P4TX7@`?S%Qwl;&S+tMsU z8(m#pRCA|lQxyi?QG|-Kr}|Py^*=eEr=>R1C=8SvY~00LmUG13p}7b1Z#GDn);cb# zENPD5+m5M-=|{1-A8iIo$T&5wl>7?knvz}^u#uE4vSKiFCT%G@grCC@izQ|UI09=? zXbg}6+8|t|>hPp5%QZ5ySVJQ`lUMiz%z`g41xHC-bmde>bC|G%wqcFQ5!7dx6E2L| z6|^Lh;&!Wun?DcKzJss=0BU#D!B%j{?>|oUE3|ID91wn!=iEHrzKuu#o(-XUX*EKI z81zi03@!5>+=2EI+s%GNqjV{8+DjiZ#tZCy8gj@dn9x-!Q%%?Jc zQ6r?ZF=Ot{W#dBg zMm)ew862RuG(-DHo=kW7vF%c(|-ZPg8|+qu_uqYyCncL`-Ag7!%d#VRYxi GzWEz8+Jq1Q literal 0 HcmV?d00001 diff --git a/functional_tests/test_withid_failures.rst b/functional_tests/test_withid_failures.rst new file mode 100644 index 0000000..5a371b7 --- /dev/null +++ b/functional_tests/test_withid_failures.rst @@ -0,0 +1,50 @@ + >>> import os + >>> import sys + >>> from nose.plugins.plugintest import run_buffered as run + >>> from nose.plugins.testid import TestId + >>> import tempfile + >>> idfile = tempfile.mktemp() + >>> support = os.path.join(os.path.dirname(__file__), 'support', 'id_fails') + >>> argv = [__file__, '-v', '--with-id', '--id-file', idfile, support] + >>> run(argv=argv, plugins=[TestId()]) + #1 Failure: ImportError (No module named apackagethatdoesntexist) ... ERROR + #2 test_b.test ... ok + #3 test_b.test_fail ... FAIL + + ====================================================================== + ERROR: Failure: ImportError (No module named apackagethatdoesntexist) + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + ImportError: No module named apackagethatdoesntexist + + ====================================================================== + FAIL: test_b.test_fail + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + AssertionError + + ---------------------------------------------------------------------- + Ran 3 tests in ...s + + FAILED (errors=1, failures=1) + +Addressing failures works (sometimes). + + >>> argv.append('1') + >>> _junk = sys.modules.pop('test_a', None) # 2.3 requires + >>> run(argv=argv, plugins=[TestId()]) + #1 Failure: ImportError (No module named apackagethatdoesntexist) ... ERROR + + ====================================================================== + ERROR: Failure: ImportError (No module named apackagethatdoesntexist) + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + ImportError: No module named apackagethatdoesntexist + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + FAILED (errors=1) diff --git a/functional_tests/test_xunit$py.class b/functional_tests/test_xunit$py.class new file mode 100644 index 0000000000000000000000000000000000000000..b699ed2983395de486380abad723a894b15b9ec8 GIT binary patch literal 9468 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 o=vruE$3hdm3r$q' in result + assert '' in result + assert '' % (Exception.__module__,)) in result + else: + assert ('' % (Exception.__module__,)).decode('utf8') in result + assert '' in result + assert '' in result + + +class TestIssue279(PluginTester, unittest.TestCase): + activate = '--with-xunit' + args = ['-v','--xunit-file=%s' % xml_results_filename] + plugins = [Xunit(), Skip()] + suitepath = os.path.join(support, 'issue279') + + def runTest(self): + print str(self.output) + f = open(xml_results_filename,'r') + result = f.read() + f.close() + print result + assert 'tests="1" errors="1" failures="0" skip="0"' in result + assert "Exception: I would prefer not to" in result diff --git a/functional_tests/test_xunit.pyc b/functional_tests/test_xunit.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d0f1676d38040b7a8a988069d429a28fed341e5 GIT binary patch literal 2827 zcmd5;+j1L45bc#L+49{cH%NfZLO@Vhj#E?!smLLulHdZI)Hc(Jk?aBQ_(?HI#ua=vO6J3O*%F4`W4x&OEM{)NldD;J0(d&It}rR zC-JnzGcp>?ikXvrY&aqN*l>QVhrzt`YpPt3eqBsMOjY7V8Q{=K`B`LNeEl+;X$nUc6c ze9X)kkXeF&U^{aXZZ+%Ja?{e$m6a#4d$^)5*$NepExlSPRV#xyG3^W1F=9395r8Tl zYHs4O1vK#Qxky}L3hsOZKg-#px@5?ltIAKFX7gBe^?2(EaT9XtxE1gwuJbKoLQ0Xw z{fhSjhk_JSgyBU+$I~2p&nx5!jF4e)1MW2hx+A_#AETRjQ|D$So|8V{oRjnYWqDMS zG(czz;^rk@kbwu6k8VC}9(Kr(Yb1;=iuO&p2XOaB!^%H*v}xYsfF*JWiYs4TeqI} zOy&?};O`l0yE{<7{N?wTzx@H9X1HLL8`G(Et-x}w0k1a?_SU1COf8BBN4Z?VwI&%j zR&QFktZ?9*$PH`JWN_jwV&YCf1+XI^QY6kstY8MN%aAA2v$|E9nW5%zpwrxR`)X3I z(}M3Mgr3J~u1x95Gm9b+86`7{hlA9ytsO+}*eRoEFYOnJi6Y$q$KvQFV+l!_ke-K& z5KTO#aO)VYW_kR$>&&X#%T}3s1d5Wo3iilMdgx^A(N+2 zb@QFFA!?PVirrt&&b0Ac$3&(j&{@|#RHH{}JPe^1g-bKecj4Pm9ipJ4@CK7jdT^xi&yA z;DpNqU3%2b^->#kbM5Tx=IG$)?}weUQQj`NM&8h|OR_0#hCJ7P$}`zsv{#IK2-?PB z4?$yMuWJVv1M9>5fYn$14gX16Bz-^2&A{aTFm=95^=CoIzF6{_l@QKRkS`_8d7&!T zLl$(%Z2o&Cl<6lgF5|IeCrIoqD0ypR!iMiatwasaa-;{I + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/nose/__init__.py b/nose/__init__.py new file mode 100644 index 0000000..1ebf76d --- /dev/null +++ b/nose/__init__.py @@ -0,0 +1,15 @@ +from nose.core import collector, main, run, run_exit, runmodule +# backwards compatibility +from nose.exc import SkipTest, DeprecatedTest +from nose.tools import with_setup + +__author__ = 'Jason Pellerin' +__versioninfo__ = (1, 1, 2) +__version__ = '.'.join(map(str, __versioninfo__)) + +__all__ = [ + 'main', 'run', 'run_exit', 'runmodule', 'with_setup', + 'SkipTest', 'DeprecatedTest', 'collector' + ] + + diff --git a/nose/case.py b/nose/case.py new file mode 100644 index 0000000..cffa4ab --- /dev/null +++ b/nose/case.py @@ -0,0 +1,397 @@ +"""nose unittest.TestCase subclasses. It is not necessary to subclass these +classes when writing tests; they are used internally by nose.loader.TestLoader +to create test cases from test functions and methods in test classes. +""" +import logging +import sys +import unittest +from inspect import isfunction +from nose.config import Config +from nose.failure import Failure # for backwards compatibility +from nose.util import resolve_name, test_address, try_run + +log = logging.getLogger(__name__) + + +__all__ = ['Test'] + + +class Test(unittest.TestCase): + """The universal test case wrapper. + + When a plugin sees a test, it will always see an instance of this + class. To access the actual test case that will be run, access the + test property of the nose.case.Test instance. + """ + __test__ = False # do not collect + def __init__(self, test, config=None, resultProxy=None): + # sanity check + if not callable(test): + raise TypeError("nose.case.Test called with argument %r that " + "is not callable. A callable is required." + % test) + self.test = test + if config is None: + config = Config() + self.config = config + self.tbinfo = None + self.capturedOutput = None + self.resultProxy = resultProxy + self.plugins = config.plugins + self.passed = None + unittest.TestCase.__init__(self) + + def __call__(self, *arg, **kwarg): + return self.run(*arg, **kwarg) + + def __str__(self): + name = self.plugins.testName(self) + if name is not None: + return name + return str(self.test) + + def __repr__(self): + return "Test(%r)" % self.test + + def afterTest(self, result): + """Called after test is complete (after result.stopTest) + """ + try: + afterTest = result.afterTest + except AttributeError: + pass + else: + afterTest(self.test) + + def beforeTest(self, result): + """Called before test is run (before result.startTest) + """ + try: + beforeTest = result.beforeTest + except AttributeError: + pass + else: + beforeTest(self.test) + + def exc_info(self): + """Extract exception info. + """ + exc, exv, tb = sys.exc_info() + return (exc, exv, tb) + + def id(self): + """Get a short(er) description of the test + """ + return self.test.id() + + def address(self): + """Return a round-trip name for this test, a name that can be + fed back as input to loadTestByName and (assuming the same + plugin configuration) result in the loading of this test. + """ + if hasattr(self.test, 'address'): + return self.test.address() + else: + # not a nose case + return test_address(self.test) + + def _context(self): + try: + return self.test.context + except AttributeError: + pass + try: + return self.test.__class__ + except AttributeError: + pass + try: + return resolve_name(self.test.__module__) + except AttributeError: + pass + return None + context = property(_context, None, None, + """Get the context object of this test (if any).""") + + def run(self, result): + """Modified run for the test wrapper. + + From here we don't call result.startTest or stopTest or + addSuccess. The wrapper calls addError/addFailure only if its + own setup or teardown fails, or running the wrapped test fails + (eg, if the wrapped "test" is not callable). + + Two additional methods are called, beforeTest and + afterTest. These give plugins a chance to modify the wrapped + test before it is called and do cleanup after it is + called. They are called unconditionally. + """ + if self.resultProxy: + result = self.resultProxy(result, self) + try: + try: + self.beforeTest(result) + self.runTest(result) + except KeyboardInterrupt: + raise + except: + err = sys.exc_info() + result.addError(self, err) + finally: + self.afterTest(result) + + def runTest(self, result): + """Run the test. Plugins may alter the test by returning a + value from prepareTestCase. The value must be callable and + must accept one argument, the result instance. + """ + test = self.test + plug_test = self.config.plugins.prepareTestCase(self) + if plug_test is not None: + test = plug_test + test(result) + + def shortDescription(self): + desc = self.plugins.describeTest(self) + if desc is not None: + return desc + # work around bug in unittest.TestCase.shortDescription + # with multiline docstrings. + test = self.test + try: + test._testMethodDoc = test._testMethodDoc.strip()# 2.5 + except AttributeError: + try: + # 2.4 and earlier + test._TestCase__testMethodDoc = \ + test._TestCase__testMethodDoc.strip() + except AttributeError: + pass + # 2.7 compat: shortDescription() always returns something + # which is a change from 2.6 and below, and breaks the + # testName plugin call. + try: + desc = self.test.shortDescription() + except Exception: + # this is probably caused by a problem in test.__str__() and is + # only triggered by python 3.1's unittest! + pass + try: + if desc == str(self.test): + return + except Exception: + # If str() triggers an exception then ignore it. + # see issue 422 + pass + return desc + + +class TestBase(unittest.TestCase): + """Common functionality for FunctionTestCase and MethodTestCase. + """ + __test__ = False # do not collect + + def id(self): + return str(self) + + def runTest(self): + self.test(*self.arg) + + def shortDescription(self): + if hasattr(self.test, 'description'): + return self.test.description + func, arg = self._descriptors() + doc = getattr(func, '__doc__', None) + if not doc: + doc = str(self) + return doc.strip().split("\n")[0].strip() + + +class FunctionTestCase(TestBase): + """TestCase wrapper for test functions. + + Don't use this class directly; it is used internally in nose to + create test cases for test functions. + """ + __test__ = False # do not collect + + def __init__(self, test, setUp=None, tearDown=None, arg=tuple(), + descriptor=None): + """Initialize the MethodTestCase. + + Required argument: + + * test -- the test function to call. + + Optional arguments: + + * setUp -- function to run at setup. + + * tearDown -- function to run at teardown. + + * arg -- arguments to pass to the test function. This is to support + generator functions that yield arguments. + + * descriptor -- the function, other than the test, that should be used + to construct the test name. This is to support generator functions. + """ + + self.test = test + self.setUpFunc = setUp + self.tearDownFunc = tearDown + self.arg = arg + self.descriptor = descriptor + TestBase.__init__(self) + + def address(self): + """Return a round-trip name for this test, a name that can be + fed back as input to loadTestByName and (assuming the same + plugin configuration) result in the loading of this test. + """ + if self.descriptor is not None: + return test_address(self.descriptor) + else: + return test_address(self.test) + + def _context(self): + return resolve_name(self.test.__module__) + context = property(_context, None, None, + """Get context (module) of this test""") + + def setUp(self): + """Run any setup function attached to the test function + """ + if self.setUpFunc: + self.setUpFunc() + else: + names = ('setup', 'setUp', 'setUpFunc') + try_run(self.test, names) + + def tearDown(self): + """Run any teardown function attached to the test function + """ + if self.tearDownFunc: + self.tearDownFunc() + else: + names = ('teardown', 'tearDown', 'tearDownFunc') + try_run(self.test, names) + + def __str__(self): + func, arg = self._descriptors() + if hasattr(func, 'compat_func_name'): + name = func.compat_func_name + else: + name = func.__name__ + name = "%s.%s" % (func.__module__, name) + if arg: + name = "%s%s" % (name, arg) + # FIXME need to include the full dir path to disambiguate + # in cases where test module of the same name was seen in + # another directory (old fromDirectory) + return name + __repr__ = __str__ + + def _descriptors(self): + """Get the descriptors of the test function: the function and + arguments that will be used to construct the test name. In + most cases, this is the function itself and no arguments. For + tests generated by generator functions, the original + (generator) function and args passed to the generated function + are returned. + """ + if self.descriptor: + return self.descriptor, self.arg + else: + return self.test, self.arg + + +class MethodTestCase(TestBase): + """Test case wrapper for test methods. + + Don't use this class directly; it is used internally in nose to + create test cases for test methods. + """ + __test__ = False # do not collect + + def __init__(self, method, test=None, arg=tuple(), descriptor=None): + """Initialize the MethodTestCase. + + Required argument: + + * method -- the method to call, may be bound or unbound. In either + case, a new instance of the method's class will be instantiated to + make the call. Note: In Python 3.x, if using an unbound method, you + must wrap it using pyversion.unbound_method. + + Optional arguments: + + * test -- the test function to call. If this is passed, it will be + called instead of getting a new bound method of the same name as the + desired method from the test instance. This is to support generator + methods that yield inline functions. + + * arg -- arguments to pass to the test function. This is to support + generator methods that yield arguments. + + * descriptor -- the function, other than the test, that should be used + to construct the test name. This is to support generator methods. + """ + self.method = method + self.test = test + self.arg = arg + self.descriptor = descriptor + if isfunction(method): + raise ValueError("Unbound methods must be wrapped using pyversion.unbound_method before passing to MethodTestCase") + self.cls = method.im_class + self.inst = self.cls() + if self.test is None: + method_name = self.method.__name__ + self.test = getattr(self.inst, method_name) + TestBase.__init__(self) + + def __str__(self): + func, arg = self._descriptors() + if hasattr(func, 'compat_func_name'): + name = func.compat_func_name + else: + name = func.__name__ + name = "%s.%s.%s" % (self.cls.__module__, + self.cls.__name__, + name) + if arg: + name = "%s%s" % (name, arg) + return name + __repr__ = __str__ + + def address(self): + """Return a round-trip name for this test, a name that can be + fed back as input to loadTestByName and (assuming the same + plugin configuration) result in the loading of this test. + """ + if self.descriptor is not None: + return test_address(self.descriptor) + else: + return test_address(self.method) + + def _context(self): + return self.cls + context = property(_context, None, None, + """Get context (class) of this test""") + + def setUp(self): + try_run(self.inst, ('setup', 'setUp')) + + def tearDown(self): + try_run(self.inst, ('teardown', 'tearDown')) + + def _descriptors(self): + """Get the descriptors of the test method: the method and + arguments that will be used to construct the test name. In + most cases, this is the method itself and no arguments. For + tests generated by generator methods, the original + (generator) method and args passed to the generated method + or function are returned. + """ + if self.descriptor: + return self.descriptor, self.arg + else: + return self.method, self.arg diff --git a/nose/commands.py b/nose/commands.py new file mode 100644 index 0000000..b819b38 --- /dev/null +++ b/nose/commands.py @@ -0,0 +1,146 @@ +""" +nosetests setuptools command +---------------------------- + +The easiest way to run tests with nose is to use the `nosetests` setuptools +command:: + + python setup.py nosetests + +This command has one *major* benefit over the standard `test` command: *all +nose plugins are supported*. + +To configure the `nosetests` command, add a [nosetests] section to your +setup.cfg. The [nosetests] section can contain any command line arguments that +nosetests supports. The differences between issuing an option on the command +line and adding it to setup.cfg are: + +* In setup.cfg, the -- prefix must be excluded +* In setup.cfg, command line flags that take no arguments must be given an + argument flag (1, T or TRUE for active, 0, F or FALSE for inactive) + +Here's an example [nosetests] setup.cfg section:: + + [nosetests] + verbosity=1 + detailed-errors=1 + with-coverage=1 + cover-package=nose + debug=nose.loader + pdb=1 + pdb-failures=1 + +If you commonly run nosetests with a large number of options, using +the nosetests setuptools command and configuring with setup.cfg can +make running your tests much less tedious. (Note that the same options +and format supported in setup.cfg are supported in all other config +files, and the nosetests script will also load config files.) + +Another reason to run tests with the command is that the command will +install packages listed in your `tests_require`, as well as doing a +complete build of your package before running tests. For packages with +dependencies or that build C extensions, using the setuptools command +can be more convenient than building by hand and running the nosetests +script. + +Bootstrapping +------------- + +If you are distributing your project and want users to be able to run tests +without having to install nose themselves, add nose to the setup_requires +section of your setup():: + + setup( + # ... + setup_requires=['nose>=1.0'] + ) + +This will direct setuptools to download and activate nose during the setup +process, making the ``nosetests`` command available. + +""" +try: + from setuptools import Command +except ImportError: + Command = nosetests = None +else: + from nose.config import Config, option_blacklist, user_config_files, \ + flag, _bool + from nose.core import TestProgram + from nose.plugins import DefaultPluginManager + + + def get_user_options(parser): + """convert a optparse option list into a distutils option tuple list""" + opt_list = [] + for opt in parser.option_list: + if opt._long_opts[0][2:] in option_blacklist: + continue + long_name = opt._long_opts[0][2:] + if opt.action not in ('store_true', 'store_false'): + long_name = long_name + "=" + short_name = None + if opt._short_opts: + short_name = opt._short_opts[0][1:] + opt_list.append((long_name, short_name, opt.help or "")) + return opt_list + + + class nosetests(Command): + description = "Run unit tests using nosetests" + __config = Config(files=user_config_files(), + plugins=DefaultPluginManager()) + __parser = __config.getParser() + user_options = get_user_options(__parser) + + def initialize_options(self): + """create the member variables, but change hyphens to + underscores + """ + + self.option_to_cmds = {} + for opt in self.__parser.option_list: + cmd_name = opt._long_opts[0][2:] + option_name = cmd_name.replace('-', '_') + self.option_to_cmds[option_name] = cmd_name + setattr(self, option_name, None) + self.attr = None + + def finalize_options(self): + """nothing to do here""" + pass + + def run(self): + """ensure tests are capable of being run, then + run nose.main with a reconstructed argument list""" + self.run_command('egg_info') + + # Build extensions in-place + self.reinitialize_command('build_ext', inplace=1) + self.run_command('build_ext') + + if self.distribution.install_requires: + self.distribution.fetch_build_eggs( + self.distribution.install_requires) + if self.distribution.tests_require: + self.distribution.fetch_build_eggs( + self.distribution.tests_require) + + argv = ['nosetests'] + for (option_name, cmd_name) in self.option_to_cmds.items(): + if option_name in option_blacklist: + continue + value = getattr(self, option_name) + if value is not None: + argv.extend( + self.cfgToArg(option_name.replace('_', '-'), value)) + TestProgram(argv=argv, config=self.__config) + + def cfgToArg(self, optname, value): + argv = [] + if flag(value): + if _bool(value): + argv.append('--' + optname) + else: + argv.extend(['--' + optname, value]) + return argv diff --git a/nose/config.py b/nose/config.py new file mode 100644 index 0000000..d787fed --- /dev/null +++ b/nose/config.py @@ -0,0 +1,638 @@ +import logging +import optparse +import os +import re +import sys +import ConfigParser +from optparse import OptionParser +from nose.util import absdir, tolist +from nose.plugins.manager import NoPlugins +from warnings import warn + +log = logging.getLogger(__name__) + +# not allowed in config files +option_blacklist = ['help', 'verbose'] + +config_files = [ + # Linux users will prefer this + "~/.noserc", + # Windows users will prefer this + "~/nose.cfg" + ] + +# plaforms on which the exe check defaults to off +# Windows and IronPython +exe_allowed_platforms = ('win32', 'cli') + + +class NoSuchOptionError(Exception): + def __init__(self, name): + Exception.__init__(self, name) + self.name = name + + +class ConfigError(Exception): + pass + + +class ConfiguredDefaultsOptionParser(object): + """ + Handler for options from commandline and config files. + """ + def __init__(self, parser, config_section, error=None, file_error=None): + self._parser = parser + self._config_section = config_section + if error is None: + error = self._parser.error + self._error = error + if file_error is None: + file_error = lambda msg, **kw: error(msg) + self._file_error = file_error + + def _configTuples(self, cfg, filename): + config = [] + if self._config_section in cfg.sections(): + for name, value in cfg.items(self._config_section): + config.append((name, value, filename)) + return config + + def _readFromFilenames(self, filenames): + config = [] + for filename in filenames: + cfg = ConfigParser.RawConfigParser() + try: + cfg.read(filename) + except ConfigParser.Error, exc: + raise ConfigError("Error reading config file %r: %s" % + (filename, str(exc))) + config.extend(self._configTuples(cfg, filename)) + return config + + def _readFromFileObject(self, fh): + cfg = ConfigParser.RawConfigParser() + try: + filename = fh.name + except AttributeError: + filename = '' + try: + cfg.readfp(fh) + except ConfigParser.Error, exc: + raise ConfigError("Error reading config file %r: %s" % + (filename, str(exc))) + return self._configTuples(cfg, filename) + + def _readConfiguration(self, config_files): + try: + config_files.readline + except AttributeError: + filename_or_filenames = config_files + if isinstance(filename_or_filenames, basestring): + filenames = [filename_or_filenames] + else: + filenames = filename_or_filenames + config = self._readFromFilenames(filenames) + else: + fh = config_files + config = self._readFromFileObject(fh) + return config + + def _processConfigValue(self, name, value, values, parser): + opt_str = '--' + name + option = parser.get_option(opt_str) + if option is None: + raise NoSuchOptionError(name) + else: + option.process(opt_str, value, values, parser) + + def _applyConfigurationToValues(self, parser, config, values): + for name, value, filename in config: + if name in option_blacklist: + continue + try: + self._processConfigValue(name, value, values, parser) + except NoSuchOptionError, exc: + self._file_error( + "Error reading config file %r: " + "no such option %r" % (filename, exc.name), + name=name, filename=filename) + except optparse.OptionValueError, exc: + msg = str(exc).replace('--' + name, repr(name), 1) + self._file_error("Error reading config file %r: " + "%s" % (filename, msg), + name=name, filename=filename) + + def parseArgsAndConfigFiles(self, args, config_files): + values = self._parser.get_default_values() + try: + config = self._readConfiguration(config_files) + except ConfigError, exc: + self._error(str(exc)) + else: + self._applyConfigurationToValues(self._parser, config, values) + return self._parser.parse_args(args, values) + + +class Config(object): + """nose configuration. + + Instances of Config are used throughout nose to configure + behavior, including plugin lists. Here are the default values for + all config keys:: + + self.env = env = kw.pop('env', {}) + self.args = () + self.testMatch = re.compile(r'(?:^|[\\b_\\.%s-])[Tt]est' % os.sep) + self.addPaths = not env.get('NOSE_NOPATH', False) + self.configSection = 'nosetests' + self.debug = env.get('NOSE_DEBUG') + self.debugLog = env.get('NOSE_DEBUG_LOG') + self.exclude = None + self.getTestCaseNamesCompat = False + self.includeExe = env.get('NOSE_INCLUDE_EXE', + sys.platform in exe_allowed_platforms) + self.ignoreFiles = (re.compile(r'^\.'), + re.compile(r'^_'), + re.compile(r'^setup\.py$') + ) + self.include = None + self.loggingConfig = None + self.logStream = sys.stderr + self.options = NoOptions() + self.parser = None + self.plugins = NoPlugins() + self.srcDirs = ('lib', 'src') + self.runOnInit = True + self.stopOnError = env.get('NOSE_STOP', False) + self.stream = sys.stderr + self.testNames = () + self.verbosity = int(env.get('NOSE_VERBOSE', 1)) + self.where = () + self.py3where = () + self.workingDir = None + """ + + def __init__(self, **kw): + self.env = env = kw.pop('env', {}) + self.args = () + self.testMatchPat = env.get('NOSE_TESTMATCH', + r'(?:^|[\b_\.%s-])[Tt]est' % os.sep) + self.testMatch = re.compile(self.testMatchPat) + self.addPaths = not env.get('NOSE_NOPATH', False) + self.configSection = 'nosetests' + self.debug = env.get('NOSE_DEBUG') + self.debugLog = env.get('NOSE_DEBUG_LOG') + self.exclude = None + self.getTestCaseNamesCompat = False + self.includeExe = env.get('NOSE_INCLUDE_EXE', + sys.platform in exe_allowed_platforms) + self.ignoreFilesDefaultStrings = [r'^\.', + r'^_', + r'^setup\.py$', + ] + self.ignoreFiles = map(re.compile, self.ignoreFilesDefaultStrings) + self.include = None + self.loggingConfig = None + self.logStream = sys.stderr + self.options = NoOptions() + self.parser = None + self.plugins = NoPlugins() + self.srcDirs = ('lib', 'src') + self.runOnInit = True + self.stopOnError = env.get('NOSE_STOP', False) + self.stream = sys.stderr + self.testNames = [] + self.verbosity = int(env.get('NOSE_VERBOSE', 1)) + self.where = () + self.py3where = () + self.workingDir = os.getcwd() + self.traverseNamespace = False + self.firstPackageWins = False + self.parserClass = OptionParser + self.worker = False + + self._default = self.__dict__.copy() + self.update(kw) + self._orig = self.__dict__.copy() + + def __getstate__(self): + state = self.__dict__.copy() + del state['stream'] + del state['_orig'] + del state['_default'] + del state['env'] + del state['logStream'] + # FIXME remove plugins, have only plugin manager class + state['plugins'] = self.plugins.__class__ + return state + + def __setstate__(self, state): + plugincls = state.pop('plugins') + self.update(state) + self.worker = True + # FIXME won't work for static plugin lists + self.plugins = plugincls() + self.plugins.loadPlugins() + # needed so .can_configure gets set appropriately + dummy_parser = self.parserClass() + self.plugins.addOptions(dummy_parser, {}) + self.plugins.configure(self.options, self) + + def __repr__(self): + d = self.__dict__.copy() + # don't expose env, could include sensitive info + d['env'] = {} + keys = [ k for k in d.keys() + if not k.startswith('_') ] + keys.sort() + return "Config(%s)" % ', '.join([ '%s=%r' % (k, d[k]) + for k in keys ]) + __str__ = __repr__ + + def _parseArgs(self, argv, cfg_files): + def warn_sometimes(msg, name=None, filename=None): + if (hasattr(self.plugins, 'excludedOption') and + self.plugins.excludedOption(name)): + msg = ("Option %r in config file %r ignored: " + "excluded by runtime environment" % + (name, filename)) + warn(msg, RuntimeWarning) + else: + raise ConfigError(msg) + parser = ConfiguredDefaultsOptionParser( + self.getParser(), self.configSection, file_error=warn_sometimes) + return parser.parseArgsAndConfigFiles(argv[1:], cfg_files) + + def configure(self, argv=None, doc=None): + """Configure the nose running environment. Execute configure before + collecting tests with nose.TestCollector to enable output capture and + other features. + """ + env = self.env + if argv is None: + argv = sys.argv + + cfg_files = getattr(self, 'files', []) + options, args = self._parseArgs(argv, cfg_files) + # If -c --config has been specified on command line, + # load those config files and reparse + if getattr(options, 'files', []): + options, args = self._parseArgs(argv, options.files) + + self.options = options + if args: + self.testNames = args + if options.testNames is not None: + self.testNames.extend(tolist(options.testNames)) + + if options.py3where is not None: + if sys.version_info >= (3,): + options.where = options.py3where + + # `where` is an append action, so it can't have a default value + # in the parser, or that default will always be in the list + if not options.where: + options.where = env.get('NOSE_WHERE', None) + + # include and exclude also + if not options.ignoreFiles: + options.ignoreFiles = env.get('NOSE_IGNORE_FILES', []) + if not options.include: + options.include = env.get('NOSE_INCLUDE', []) + if not options.exclude: + options.exclude = env.get('NOSE_EXCLUDE', []) + + self.addPaths = options.addPaths + self.stopOnError = options.stopOnError + self.verbosity = options.verbosity + self.includeExe = options.includeExe + self.traverseNamespace = options.traverseNamespace + self.debug = options.debug + self.debugLog = options.debugLog + self.loggingConfig = options.loggingConfig + self.firstPackageWins = options.firstPackageWins + self.configureLogging() + + if options.where is not None: + self.configureWhere(options.where) + + if options.testMatch: + self.testMatch = re.compile(options.testMatch) + + if options.ignoreFiles: + self.ignoreFiles = map(re.compile, tolist(options.ignoreFiles)) + log.info("Ignoring files matching %s", options.ignoreFiles) + else: + log.info("Ignoring files matching %s", self.ignoreFilesDefaultStrings) + + if options.include: + self.include = map(re.compile, tolist(options.include)) + log.info("Including tests matching %s", options.include) + + if options.exclude: + self.exclude = map(re.compile, tolist(options.exclude)) + log.info("Excluding tests matching %s", options.exclude) + + # When listing plugins we don't want to run them + if not options.showPlugins: + self.plugins.configure(options, self) + self.plugins.begin() + + def configureLogging(self): + """Configure logging for nose, or optionally other packages. Any logger + name may be set with the debug option, and that logger will be set to + debug level and be assigned the same handler as the nose loggers, unless + it already has a handler. + """ + if self.loggingConfig: + from logging.config import fileConfig + fileConfig(self.loggingConfig) + return + + format = logging.Formatter('%(name)s: %(levelname)s: %(message)s') + if self.debugLog: + handler = logging.FileHandler(self.debugLog) + else: + handler = logging.StreamHandler(self.logStream) + handler.setFormatter(format) + + logger = logging.getLogger('nose') + logger.propagate = 0 + + # only add our default handler if there isn't already one there + # this avoids annoying duplicate log messages. + if handler not in logger.handlers: + logger.addHandler(handler) + + # default level + lvl = logging.WARNING + if self.verbosity >= 5: + lvl = 0 + elif self.verbosity >= 4: + lvl = logging.DEBUG + elif self.verbosity >= 3: + lvl = logging.INFO + logger.setLevel(lvl) + + # individual overrides + if self.debug: + # no blanks + debug_loggers = [ name for name in self.debug.split(',') + if name ] + for logger_name in debug_loggers: + l = logging.getLogger(logger_name) + l.setLevel(logging.DEBUG) + if not l.handlers and not logger_name.startswith('nose'): + l.addHandler(handler) + + def configureWhere(self, where): + """Configure the working directory or directories for the test run. + """ + from nose.importer import add_path + self.workingDir = None + where = tolist(where) + warned = False + for path in where: + if not self.workingDir: + abs_path = absdir(path) + if abs_path is None: + raise ValueError("Working directory %s not found, or " + "not a directory" % path) + log.info("Set working dir to %s", abs_path) + self.workingDir = abs_path + if self.addPaths and \ + os.path.exists(os.path.join(abs_path, '__init__.py')): + log.info("Working directory %s is a package; " + "adding to sys.path" % abs_path) + add_path(abs_path) + continue + if not warned: + warn("Use of multiple -w arguments is deprecated and " + "support may be removed in a future release. You can " + "get the same behavior by passing directories without " + "the -w argument on the command line, or by using the " + "--tests argument in a configuration file.", + DeprecationWarning) + self.testNames.append(path) + + def default(self): + """Reset all config values to defaults. + """ + self.__dict__.update(self._default) + + def getParser(self, doc=None): + """Get the command line option parser. + """ + if self.parser: + return self.parser + env = self.env + parser = self.parserClass(doc) + parser.add_option( + "-V","--version", action="store_true", + dest="version", default=False, + help="Output nose version and exit") + parser.add_option( + "-p", "--plugins", action="store_true", + dest="showPlugins", default=False, + help="Output list of available plugins and exit. Combine with " + "higher verbosity for greater detail") + parser.add_option( + "-v", "--verbose", + action="count", dest="verbosity", + default=self.verbosity, + help="Be more verbose. [NOSE_VERBOSE]") + parser.add_option( + "--verbosity", action="store", dest="verbosity", + metavar='VERBOSITY', + type="int", help="Set verbosity; --verbosity=2 is " + "the same as -v") + parser.add_option( + "-q", "--quiet", action="store_const", const=0, dest="verbosity", + help="Be less verbose") + parser.add_option( + "-c", "--config", action="append", dest="files", + metavar="FILES", + help="Load configuration from config file(s). May be specified " + "multiple times; in that case, all config files will be " + "loaded and combined") + parser.add_option( + "-w", "--where", action="append", dest="where", + metavar="WHERE", + help="Look for tests in this directory. " + "May be specified multiple times. The first directory passed " + "will be used as the working directory, in place of the current " + "working directory, which is the default. Others will be added " + "to the list of tests to execute. [NOSE_WHERE]" + ) + parser.add_option( + "--py3where", action="append", dest="py3where", + metavar="PY3WHERE", + help="Look for tests in this directory under Python 3.x. " + "Functions the same as 'where', but only applies if running under " + "Python 3.x or above. Note that, if present under 3.x, this " + "option completely replaces any directories specified with " + "'where', so the 'where' option becomes ineffective. " + "[NOSE_PY3WHERE]" + ) + parser.add_option( + "-m", "--match", "--testmatch", action="store", + dest="testMatch", metavar="REGEX", + help="Files, directories, function names, and class names " + "that match this regular expression are considered tests. " + "Default: %s [NOSE_TESTMATCH]" % self.testMatchPat, + default=self.testMatchPat) + parser.add_option( + "--tests", action="store", dest="testNames", default=None, + metavar='NAMES', + help="Run these tests (comma-separated list). This argument is " + "useful mainly from configuration files; on the command line, " + "just pass the tests to run as additional arguments with no " + "switch.") + parser.add_option( + "-l", "--debug", action="store", + dest="debug", default=self.debug, + help="Activate debug logging for one or more systems. " + "Available debug loggers: nose, nose.importer, " + "nose.inspector, nose.plugins, nose.result and " + "nose.selector. Separate multiple names with a comma.") + parser.add_option( + "--debug-log", dest="debugLog", action="store", + default=self.debugLog, metavar="FILE", + help="Log debug messages to this file " + "(default: sys.stderr)") + parser.add_option( + "--logging-config", "--log-config", + dest="loggingConfig", action="store", + default=self.loggingConfig, metavar="FILE", + help="Load logging config from this file -- bypasses all other" + " logging config settings.") + parser.add_option( + "-I", "--ignore-files", action="append", dest="ignoreFiles", + metavar="REGEX", + help="Completely ignore any file that matches this regular " + "expression. Takes precedence over any other settings or " + "plugins. " + "Specifying this option will replace the default setting. " + "Specify this option multiple times " + "to add more regular expressions [NOSE_IGNORE_FILES]") + parser.add_option( + "-e", "--exclude", action="append", dest="exclude", + metavar="REGEX", + help="Don't run tests that match regular " + "expression [NOSE_EXCLUDE]") + parser.add_option( + "-i", "--include", action="append", dest="include", + metavar="REGEX", + help="This regular expression will be applied to files, " + "directories, function names, and class names for a chance " + "to include additional tests that do not match TESTMATCH. " + "Specify this option multiple times " + "to add more regular expressions [NOSE_INCLUDE]") + parser.add_option( + "-x", "--stop", action="store_true", dest="stopOnError", + default=self.stopOnError, + help="Stop running tests after the first error or failure") + parser.add_option( + "-P", "--no-path-adjustment", action="store_false", + dest="addPaths", + default=self.addPaths, + help="Don't make any changes to sys.path when " + "loading tests [NOSE_NOPATH]") + parser.add_option( + "--exe", action="store_true", dest="includeExe", + default=self.includeExe, + help="Look for tests in python modules that are " + "executable. Normal behavior is to exclude executable " + "modules, since they may not be import-safe " + "[NOSE_INCLUDE_EXE]") + parser.add_option( + "--noexe", action="store_false", dest="includeExe", + help="DO NOT look for tests in python modules that are " + "executable. (The default on the windows platform is to " + "do so.)") + parser.add_option( + "--traverse-namespace", action="store_true", + default=self.traverseNamespace, dest="traverseNamespace", + help="Traverse through all path entries of a namespace package") + parser.add_option( + "--first-package-wins", "--first-pkg-wins", "--1st-pkg-wins", + action="store_true", default=False, dest="firstPackageWins", + help="nose's importer will normally evict a package from sys." + "modules if it sees a package with the same name in a different " + "location. Set this option to disable that behavior.") + + self.plugins.loadPlugins() + self.pluginOpts(parser) + + self.parser = parser + return parser + + def help(self, doc=None): + """Return the generated help message + """ + return self.getParser(doc).format_help() + + def pluginOpts(self, parser): + self.plugins.addOptions(parser, self.env) + + def reset(self): + self.__dict__.update(self._orig) + + def todict(self): + return self.__dict__.copy() + + def update(self, d): + self.__dict__.update(d) + + +class NoOptions(object): + """Options container that returns None for all options. + """ + def __getstate__(self): + return {} + + def __setstate__(self, state): + pass + + def __getnewargs__(self): + return () + + def __getattr__(self, attr): + return None + + def __nonzero__(self): + return False + + +def user_config_files(): + """Return path to any existing user config files + """ + return filter(os.path.exists, + map(os.path.expanduser, config_files)) + + +def all_config_files(): + """Return path to any existing user config files, plus any setup.cfg + in the current working directory. + """ + user = user_config_files() + if os.path.exists('setup.cfg'): + return user + ['setup.cfg'] + return user + + +# used when parsing config files +def flag(val): + """Does the value look like an on/off flag?""" + if val == 1: + return True + elif val == 0: + return False + val = str(val) + if len(val) > 5: + return False + return val.upper() in ('1', '0', 'F', 'T', 'TRUE', 'FALSE', 'ON', 'OFF') + + +def _bool(val): + return str(val).upper() in ('1', 'T', 'TRUE', 'ON') diff --git a/nose/core.py b/nose/core.py new file mode 100644 index 0000000..e219903 --- /dev/null +++ b/nose/core.py @@ -0,0 +1,324 @@ +"""Implements nose test program and collector. +""" +from __future__ import generators + +import logging +import os +import sys +import time +import unittest + +from nose.config import Config, all_config_files +from nose.loader import defaultTestLoader +from nose.plugins.manager import PluginManager, DefaultPluginManager, \ + RestrictedPluginManager +from nose.result import TextTestResult +from nose.suite import FinalizingSuiteWrapper +from nose.util import isclass, tolist + + +log = logging.getLogger('nose.core') +compat_24 = sys.version_info >= (2, 4) + +__all__ = ['TestProgram', 'main', 'run', 'run_exit', 'runmodule', 'collector', + 'TextTestRunner'] + + +class TextTestRunner(unittest.TextTestRunner): + """Test runner that uses nose's TextTestResult to enable errorClasses, + as well as providing hooks for plugins to override or replace the test + output stream, results, and the test case itself. + """ + def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1, + config=None): + if config is None: + config = Config() + self.config = config + unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity) + + + def _makeResult(self): + return TextTestResult(self.stream, + self.descriptions, + self.verbosity, + self.config) + + def run(self, test): + """Overrides to provide plugin hooks and defer all output to + the test result class. + """ + wrapper = self.config.plugins.prepareTest(test) + if wrapper is not None: + test = wrapper + + # plugins can decorate or capture the output stream + wrapped = self.config.plugins.setOutputStream(self.stream) + if wrapped is not None: + self.stream = wrapped + + result = self._makeResult() + start = time.time() + test(result) + stop = time.time() + result.printErrors() + result.printSummary(start, stop) + self.config.plugins.finalize(result) + return result + + +class TestProgram(unittest.TestProgram): + """Collect and run tests, returning success or failure. + + The arguments to TestProgram() are the same as to + :func:`main()` and :func:`run()`: + + * module: All tests are in this module (default: None) + * defaultTest: Tests to load (default: '.') + * argv: Command line arguments (default: None; sys.argv is read) + * testRunner: Test runner instance (default: None) + * testLoader: Test loader instance (default: None) + * env: Environment; ignored if config is provided (default: None; + os.environ is read) + * config: :class:`nose.config.Config` instance (default: None) + * suite: Suite or list of tests to run (default: None). Passing a + suite or lists of tests will bypass all test discovery and + loading. *ALSO NOTE* that if you pass a unittest.TestSuite + instance as the suite, context fixtures at the class, module and + package level will not be used, and many plugin hooks will not + be called. If you want normal nose behavior, either pass a list + of tests, or a fully-configured :class:`nose.suite.ContextSuite`. + * exit: Exit after running tests and printing report (default: True) + * plugins: List of plugins to use; ignored if config is provided + (default: load plugins with DefaultPluginManager) + * addplugins: List of **extra** plugins to use. Pass a list of plugin + instances in this argument to make custom plugins available while + still using the DefaultPluginManager. + """ + verbosity = 1 + + def __init__(self, module=None, defaultTest='.', argv=None, + testRunner=None, testLoader=None, env=None, config=None, + suite=None, exit=True, plugins=None, addplugins=None): + if env is None: + env = os.environ + if config is None: + config = self.makeConfig(env, plugins) + if addplugins: + config.plugins.addPlugins(addplugins) + self.config = config + self.suite = suite + self.exit = exit + extra_args = {} + version = sys.version_info[0:2] + if version >= (2,7) and version != (3,0): + extra_args['exit'] = exit + unittest.TestProgram.__init__( + self, module=module, defaultTest=defaultTest, + argv=argv, testRunner=testRunner, testLoader=testLoader, + **extra_args) + + def makeConfig(self, env, plugins=None): + """Load a Config, pre-filled with user config files if any are + found. + """ + cfg_files = all_config_files() + if plugins: + manager = PluginManager(plugins=plugins) + else: + manager = DefaultPluginManager() + return Config( + env=env, files=cfg_files, plugins=manager) + + def parseArgs(self, argv): + """Parse argv and env and configure running environment. + """ + self.config.configure(argv, doc=self.usage()) + log.debug("configured %s", self.config) + + # quick outs: version, plugins (optparse would have already + # caught and exited on help) + if self.config.options.version: + from nose import __version__ + sys.stdout = sys.__stdout__ + print "%s version %s" % (os.path.basename(sys.argv[0]), __version__) + sys.exit(0) + + if self.config.options.showPlugins: + self.showPlugins() + sys.exit(0) + + if self.testLoader is None: + self.testLoader = defaultTestLoader(config=self.config) + elif isclass(self.testLoader): + self.testLoader = self.testLoader(config=self.config) + plug_loader = self.config.plugins.prepareTestLoader(self.testLoader) + if plug_loader is not None: + self.testLoader = plug_loader + log.debug("test loader is %s", self.testLoader) + + # FIXME if self.module is a string, add it to self.testNames? not sure + + if self.config.testNames: + self.testNames = self.config.testNames + else: + self.testNames = tolist(self.defaultTest) + log.debug('defaultTest %s', self.defaultTest) + log.debug('Test names are %s', self.testNames) + if self.config.workingDir is not None: + os.chdir(self.config.workingDir) + self.createTests() + + def createTests(self): + """Create the tests to run. If a self.suite + is set, then that suite will be used. Otherwise, tests will be + loaded from the given test names (self.testNames) using the + test loader. + """ + log.debug("createTests called with %s", self.suite) + if self.suite is not None: + # We were given an explicit suite to run. Make sure it's + # loaded and wrapped correctly. + self.test = self.testLoader.suiteClass(self.suite) + else: + self.test = self.testLoader.loadTestsFromNames(self.testNames) + + def runTests(self): + """Run Tests. Returns true on success, false on failure, and sets + self.success to the same value. + """ + log.debug("runTests called") + if self.testRunner is None: + self.testRunner = TextTestRunner(stream=self.config.stream, + verbosity=self.config.verbosity, + config=self.config) + plug_runner = self.config.plugins.prepareTestRunner(self.testRunner) + if plug_runner is not None: + self.testRunner = plug_runner + result = self.testRunner.run(self.test) + self.success = result.wasSuccessful() + if self.exit: + sys.exit(not self.success) + return self.success + + def showPlugins(self): + """Print list of available plugins. + """ + import textwrap + + class DummyParser: + def __init__(self): + self.options = [] + def add_option(self, *arg, **kw): + self.options.append((arg, kw.pop('help', ''))) + + v = self.config.verbosity + self.config.plugins.sort() + for p in self.config.plugins: + print "Plugin %s" % p.name + if v >= 2: + print " score: %s" % p.score + print '\n'.join(textwrap.wrap(p.help().strip(), + initial_indent=' ', + subsequent_indent=' ')) + if v >= 3: + parser = DummyParser() + p.addOptions(parser) + if len(parser.options): + print + print " Options:" + for opts, help in parser.options: + print ' %s' % (', '.join(opts)) + if help: + print '\n'.join( + textwrap.wrap(help.strip(), + initial_indent=' ', + subsequent_indent=' ')) + print + + def usage(cls): + import nose + if hasattr(nose, '__loader__'): + ld = nose.__loader__ + if hasattr(ld, 'zipfile'): + # nose was imported from a zipfile + return ld.get_data( + os.path.join(ld.prefix, 'nose', 'usage.txt')) + return open(os.path.join( + os.path.dirname(__file__), 'usage.txt'), 'r').read() + usage = classmethod(usage) + +# backwards compatibility +run_exit = main = TestProgram + + +def run(*arg, **kw): + """Collect and run tests, returning success or failure. + + The arguments to `run()` are the same as to `main()`: + + * module: All tests are in this module (default: None) + * defaultTest: Tests to load (default: '.') + * argv: Command line arguments (default: None; sys.argv is read) + * testRunner: Test runner instance (default: None) + * testLoader: Test loader instance (default: None) + * env: Environment; ignored if config is provided (default: None; + os.environ is read) + * config: :class:`nose.config.Config` instance (default: None) + * suite: Suite or list of tests to run (default: None). Passing a + suite or lists of tests will bypass all test discovery and + loading. *ALSO NOTE* that if you pass a unittest.TestSuite + instance as the suite, context fixtures at the class, module and + package level will not be used, and many plugin hooks will not + be called. If you want normal nose behavior, either pass a list + of tests, or a fully-configured :class:`nose.suite.ContextSuite`. + * plugins: List of plugins to use; ignored if config is provided + (default: load plugins with DefaultPluginManager) + * addplugins: List of **extra** plugins to use. Pass a list of plugin + instances in this argument to make custom plugins available while + still using the DefaultPluginManager. + + With the exception that the ``exit`` argument is always set + to False. + """ + kw['exit'] = False + return TestProgram(*arg, **kw).success + + +def runmodule(name='__main__', **kw): + """Collect and run tests in a single module only. Defaults to running + tests in __main__. Additional arguments to TestProgram may be passed + as keyword arguments. + """ + main(defaultTest=name, **kw) + + +def collector(): + """TestSuite replacement entry point. Use anywhere you might use a + unittest.TestSuite. The collector will, by default, load options from + all config files and execute loader.loadTestsFromNames() on the + configured testNames, or '.' if no testNames are configured. + """ + # plugins that implement any of these methods are disabled, since + # we don't control the test runner and won't be able to run them + # finalize() is also not called, but plugins that use it aren't disabled, + # because capture needs it. + setuptools_incompat = ('report', 'prepareTest', + 'prepareTestLoader', 'prepareTestRunner', + 'setOutputStream') + + plugins = RestrictedPluginManager(exclude=setuptools_incompat) + conf = Config(files=all_config_files(), + plugins=plugins) + conf.configure(argv=['collector']) + loader = defaultTestLoader(conf) + + if conf.testNames: + suite = loader.loadTestsFromNames(conf.testNames) + else: + suite = loader.loadTestsFromNames(('.',)) + return FinalizingSuiteWrapper(suite, plugins.finalize) + + + +if __name__ == '__main__': + main() diff --git a/nose/exc.py b/nose/exc.py new file mode 100644 index 0000000..8b780db --- /dev/null +++ b/nose/exc.py @@ -0,0 +1,9 @@ +"""Exceptions for marking tests as skipped or deprecated. + +This module exists to provide backwards compatibility with previous +versions of nose where skipped and deprecated tests were core +functionality, rather than being provided by plugins. It may be +removed in a future release. +""" +from nose.plugins.skip import SkipTest +from nose.plugins.deprecated import DeprecatedTest diff --git a/nose/ext/__init__.py b/nose/ext/__init__.py new file mode 100644 index 0000000..5fd1516 --- /dev/null +++ b/nose/ext/__init__.py @@ -0,0 +1,3 @@ +""" +External or vendor files +""" diff --git a/nose/ext/dtcompat.py b/nose/ext/dtcompat.py new file mode 100644 index 0000000..332cf08 --- /dev/null +++ b/nose/ext/dtcompat.py @@ -0,0 +1,2272 @@ +# Module doctest. +# Released to the public domain 16-Jan-2001, by Tim Peters (tim@python.org). +# Major enhancements and refactoring by: +# Jim Fulton +# Edward Loper + +# Provided as-is; use at your own risk; no warranty; no promises; enjoy! +# +# Modified for inclusion in nose to provide support for DocFileTest in +# python 2.3: +# +# - all doctests removed from module (they fail under 2.3 and 2.5) +# - now handles the $py.class extension when ran under Jython + +r"""Module doctest -- a framework for running examples in docstrings. + +In simplest use, end each module M to be tested with: + +def _test(): + import doctest + doctest.testmod() + +if __name__ == "__main__": + _test() + +Then running the module as a script will cause the examples in the +docstrings to get executed and verified: + +python M.py + +This won't display anything unless an example fails, in which case the +failing example(s) and the cause(s) of the failure(s) are printed to stdout +(why not stderr? because stderr is a lame hack <0.2 wink>), and the final +line of output is "Test failed.". + +Run it with the -v switch instead: + +python M.py -v + +and a detailed report of all examples tried is printed to stdout, along +with assorted summaries at the end. + +You can force verbose mode by passing "verbose=True" to testmod, or prohibit +it by passing "verbose=False". In either of those cases, sys.argv is not +examined by testmod. + +There are a variety of other ways to run doctests, including integration +with the unittest framework, and support for running non-Python text +files containing doctests. There are also many ways to override parts +of doctest's default behaviors. See the Library Reference Manual for +details. +""" + +__docformat__ = 'reStructuredText en' + +__all__ = [ + # 0, Option Flags + 'register_optionflag', + 'DONT_ACCEPT_TRUE_FOR_1', + 'DONT_ACCEPT_BLANKLINE', + 'NORMALIZE_WHITESPACE', + 'ELLIPSIS', + 'IGNORE_EXCEPTION_DETAIL', + 'COMPARISON_FLAGS', + 'REPORT_UDIFF', + 'REPORT_CDIFF', + 'REPORT_NDIFF', + 'REPORT_ONLY_FIRST_FAILURE', + 'REPORTING_FLAGS', + # 1. Utility Functions + 'is_private', + # 2. Example & DocTest + 'Example', + 'DocTest', + # 3. Doctest Parser + 'DocTestParser', + # 4. Doctest Finder + 'DocTestFinder', + # 5. Doctest Runner + 'DocTestRunner', + 'OutputChecker', + 'DocTestFailure', + 'UnexpectedException', + 'DebugRunner', + # 6. Test Functions + 'testmod', + 'testfile', + 'run_docstring_examples', + # 7. Tester + 'Tester', + # 8. Unittest Support + 'DocTestSuite', + 'DocFileSuite', + 'set_unittest_reportflags', + # 9. Debugging Support + 'script_from_examples', + 'testsource', + 'debug_src', + 'debug', +] + +import __future__ + +import sys, traceback, inspect, linecache, os, re +import unittest, difflib, pdb, tempfile +import warnings +from StringIO import StringIO + +# Don't whine about the deprecated is_private function in this +# module's tests. +warnings.filterwarnings("ignore", "is_private", DeprecationWarning, + __name__, 0) + +# There are 4 basic classes: +# - Example: a pair, plus an intra-docstring line number. +# - DocTest: a collection of examples, parsed from a docstring, plus +# info about where the docstring came from (name, filename, lineno). +# - DocTestFinder: extracts DocTests from a given object's docstring and +# its contained objects' docstrings. +# - DocTestRunner: runs DocTest cases, and accumulates statistics. +# +# So the basic picture is: +# +# list of: +# +------+ +---------+ +-------+ +# |object| --DocTestFinder-> | DocTest | --DocTestRunner-> |results| +# +------+ +---------+ +-------+ +# | Example | +# | ... | +# | Example | +# +---------+ + +# Option constants. + +OPTIONFLAGS_BY_NAME = {} +def register_optionflag(name): + # Create a new flag unless `name` is already known. + return OPTIONFLAGS_BY_NAME.setdefault(name, 1 << len(OPTIONFLAGS_BY_NAME)) + +DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1') +DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE') +NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE') +ELLIPSIS = register_optionflag('ELLIPSIS') +IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL') + +COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 | + DONT_ACCEPT_BLANKLINE | + NORMALIZE_WHITESPACE | + ELLIPSIS | + IGNORE_EXCEPTION_DETAIL) + +REPORT_UDIFF = register_optionflag('REPORT_UDIFF') +REPORT_CDIFF = register_optionflag('REPORT_CDIFF') +REPORT_NDIFF = register_optionflag('REPORT_NDIFF') +REPORT_ONLY_FIRST_FAILURE = register_optionflag('REPORT_ONLY_FIRST_FAILURE') + +REPORTING_FLAGS = (REPORT_UDIFF | + REPORT_CDIFF | + REPORT_NDIFF | + REPORT_ONLY_FIRST_FAILURE) + +# Special string markers for use in `want` strings: +BLANKLINE_MARKER = '' +ELLIPSIS_MARKER = '...' + +###################################################################### +## Table of Contents +###################################################################### +# 1. Utility Functions +# 2. Example & DocTest -- store test cases +# 3. DocTest Parser -- extracts examples from strings +# 4. DocTest Finder -- extracts test cases from objects +# 5. DocTest Runner -- runs test cases +# 6. Test Functions -- convenient wrappers for testing +# 7. Tester Class -- for backwards compatibility +# 8. Unittest Support +# 9. Debugging Support +# 10. Example Usage + +###################################################################### +## 1. Utility Functions +###################################################################### + +def is_private(prefix, base): + """prefix, base -> true iff name prefix + "." + base is "private". + + Prefix may be an empty string, and base does not contain a period. + Prefix is ignored (although functions you write conforming to this + protocol may make use of it). + Return true iff base begins with an (at least one) underscore, but + does not both begin and end with (at least) two underscores. + """ + warnings.warn("is_private is deprecated; it wasn't useful; " + "examine DocTestFinder.find() lists instead", + DeprecationWarning, stacklevel=2) + return base[:1] == "_" and not base[:2] == "__" == base[-2:] + +def _extract_future_flags(globs): + """ + Return the compiler-flags associated with the future features that + have been imported into the given namespace (globs). + """ + flags = 0 + for fname in __future__.all_feature_names: + feature = globs.get(fname, None) + if feature is getattr(__future__, fname): + flags |= feature.compiler_flag + return flags + +def _normalize_module(module, depth=2): + """ + Return the module specified by `module`. In particular: + - If `module` is a module, then return module. + - If `module` is a string, then import and return the + module with that name. + - If `module` is None, then return the calling module. + The calling module is assumed to be the module of + the stack frame at the given depth in the call stack. + """ + if inspect.ismodule(module): + return module + elif isinstance(module, (str, unicode)): + return __import__(module, globals(), locals(), ["*"]) + elif module is None: + return sys.modules[sys._getframe(depth).f_globals['__name__']] + else: + raise TypeError("Expected a module, string, or None") + +def _indent(s, indent=4): + """ + Add the given number of space characters to the beginning every + non-blank line in `s`, and return the result. + """ + # This regexp matches the start of non-blank lines: + return re.sub('(?m)^(?!$)', indent*' ', s) + +def _exception_traceback(exc_info): + """ + Return a string containing a traceback message for the given + exc_info tuple (as returned by sys.exc_info()). + """ + # Get a traceback message. + excout = StringIO() + exc_type, exc_val, exc_tb = exc_info + traceback.print_exception(exc_type, exc_val, exc_tb, file=excout) + return excout.getvalue() + +# Override some StringIO methods. +class _SpoofOut(StringIO): + def getvalue(self): + result = StringIO.getvalue(self) + # If anything at all was written, make sure there's a trailing + # newline. There's no way for the expected output to indicate + # that a trailing newline is missing. + if result and not result.endswith("\n"): + result += "\n" + # Prevent softspace from screwing up the next test case, in + # case they used print with a trailing comma in an example. + if hasattr(self, "softspace"): + del self.softspace + return result + + def truncate(self, size=None): + StringIO.truncate(self, size) + if hasattr(self, "softspace"): + del self.softspace + +# Worst-case linear-time ellipsis matching. +def _ellipsis_match(want, got): + if ELLIPSIS_MARKER not in want: + return want == got + + # Find "the real" strings. + ws = want.split(ELLIPSIS_MARKER) + assert len(ws) >= 2 + + # Deal with exact matches possibly needed at one or both ends. + startpos, endpos = 0, len(got) + w = ws[0] + if w: # starts with exact match + if got.startswith(w): + startpos = len(w) + del ws[0] + else: + return False + w = ws[-1] + if w: # ends with exact match + if got.endswith(w): + endpos -= len(w) + del ws[-1] + else: + return False + + if startpos > endpos: + # Exact end matches required more characters than we have, as in + # _ellipsis_match('aa...aa', 'aaa') + return False + + # For the rest, we only need to find the leftmost non-overlapping + # match for each piece. If there's no overall match that way alone, + # there's no overall match period. + for w in ws: + # w may be '' at times, if there are consecutive ellipses, or + # due to an ellipsis at the start or end of `want`. That's OK. + # Search for an empty string succeeds, and doesn't change startpos. + startpos = got.find(w, startpos, endpos) + if startpos < 0: + return False + startpos += len(w) + + return True + +def _comment_line(line): + "Return a commented form of the given line" + line = line.rstrip() + if line: + return '# '+line + else: + return '#' + +class _OutputRedirectingPdb(pdb.Pdb): + """ + A specialized version of the python debugger that redirects stdout + to a given stream when interacting with the user. Stdout is *not* + redirected when traced code is executed. + """ + def __init__(self, out): + self.__out = out + pdb.Pdb.__init__(self) + + def trace_dispatch(self, *args): + # Redirect stdout to the given stream. + save_stdout = sys.stdout + sys.stdout = self.__out + # Call Pdb's trace dispatch method. + try: + return pdb.Pdb.trace_dispatch(self, *args) + finally: + sys.stdout = save_stdout + +# [XX] Normalize with respect to os.path.pardir? +def _module_relative_path(module, path): + if not inspect.ismodule(module): + raise TypeError, 'Expected a module: %r' % module + if path.startswith('/'): + raise ValueError, 'Module-relative files may not have absolute paths' + + # Find the base directory for the path. + if hasattr(module, '__file__'): + # A normal module/package + basedir = os.path.split(module.__file__)[0] + elif module.__name__ == '__main__': + # An interactive session. + if len(sys.argv)>0 and sys.argv[0] != '': + basedir = os.path.split(sys.argv[0])[0] + else: + basedir = os.curdir + else: + # A module w/o __file__ (this includes builtins) + raise ValueError("Can't resolve paths relative to the module " + + module + " (it has no __file__)") + + # Combine the base directory and the path. + return os.path.join(basedir, *(path.split('/'))) + +###################################################################### +## 2. Example & DocTest +###################################################################### +## - An "example" is a pair, where "source" is a +## fragment of source code, and "want" is the expected output for +## "source." The Example class also includes information about +## where the example was extracted from. +## +## - A "doctest" is a collection of examples, typically extracted from +## a string (such as an object's docstring). The DocTest class also +## includes information about where the string was extracted from. + +class Example: + """ + A single doctest example, consisting of source code and expected + output. `Example` defines the following attributes: + + - source: A single Python statement, always ending with a newline. + The constructor adds a newline if needed. + + - want: The expected output from running the source code (either + from stdout, or a traceback in case of exception). `want` ends + with a newline unless it's empty, in which case it's an empty + string. The constructor adds a newline if needed. + + - exc_msg: The exception message generated by the example, if + the example is expected to generate an exception; or `None` if + it is not expected to generate an exception. This exception + message is compared against the return value of + `traceback.format_exception_only()`. `exc_msg` ends with a + newline unless it's `None`. The constructor adds a newline + if needed. + + - lineno: The line number within the DocTest string containing + this Example where the Example begins. This line number is + zero-based, with respect to the beginning of the DocTest. + + - indent: The example's indentation in the DocTest string. + I.e., the number of space characters that preceed the + example's first prompt. + + - options: A dictionary mapping from option flags to True or + False, which is used to override default options for this + example. Any option flags not contained in this dictionary + are left at their default value (as specified by the + DocTestRunner's optionflags). By default, no options are set. + """ + def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, + options=None): + # Normalize inputs. + if not source.endswith('\n'): + source += '\n' + if want and not want.endswith('\n'): + want += '\n' + if exc_msg is not None and not exc_msg.endswith('\n'): + exc_msg += '\n' + # Store properties. + self.source = source + self.want = want + self.lineno = lineno + self.indent = indent + if options is None: options = {} + self.options = options + self.exc_msg = exc_msg + +class DocTest: + """ + A collection of doctest examples that should be run in a single + namespace. Each `DocTest` defines the following attributes: + + - examples: the list of examples. + + - globs: The namespace (aka globals) that the examples should + be run in. + + - name: A name identifying the DocTest (typically, the name of + the object whose docstring this DocTest was extracted from). + + - filename: The name of the file that this DocTest was extracted + from, or `None` if the filename is unknown. + + - lineno: The line number within filename where this DocTest + begins, or `None` if the line number is unavailable. This + line number is zero-based, with respect to the beginning of + the file. + + - docstring: The string that the examples were extracted from, + or `None` if the string is unavailable. + """ + def __init__(self, examples, globs, name, filename, lineno, docstring): + """ + Create a new DocTest containing the given examples. The + DocTest's globals are initialized with a copy of `globs`. + """ + assert not isinstance(examples, basestring), \ + "DocTest no longer accepts str; use DocTestParser instead" + self.examples = examples + self.docstring = docstring + self.globs = globs.copy() + self.name = name + self.filename = filename + self.lineno = lineno + + def __repr__(self): + if len(self.examples) == 0: + examples = 'no examples' + elif len(self.examples) == 1: + examples = '1 example' + else: + examples = '%d examples' % len(self.examples) + return ('' % + (self.name, self.filename, self.lineno, examples)) + + + # This lets us sort tests by name: + def __cmp__(self, other): + if not isinstance(other, DocTest): + return -1 + return cmp((self.name, self.filename, self.lineno, id(self)), + (other.name, other.filename, other.lineno, id(other))) + +###################################################################### +## 3. DocTestParser +###################################################################### + +class DocTestParser: + """ + A class used to parse strings containing doctest examples. + """ + # This regular expression is used to find doctest examples in a + # string. It defines three groups: `source` is the source code + # (including leading indentation and prompts); `indent` is the + # indentation of the first (PS1) line of the source code; and + # `want` is the expected output (including leading indentation). + _EXAMPLE_RE = re.compile(r''' + # Source consists of a PS1 line followed by zero or more PS2 lines. + (?P + (?:^(?P [ ]*) >>> .*) # PS1 line + (?:\n [ ]* \.\.\. .*)*) # PS2 lines + \n? + # Want consists of any non-blank lines that do not start with PS1. + (?P (?:(?![ ]*$) # Not a blank line + (?![ ]*>>>) # Not a line starting with PS1 + .*$\n? # But any other line + )*) + ''', re.MULTILINE | re.VERBOSE) + + # A regular expression for handling `want` strings that contain + # expected exceptions. It divides `want` into three pieces: + # - the traceback header line (`hdr`) + # - the traceback stack (`stack`) + # - the exception message (`msg`), as generated by + # traceback.format_exception_only() + # `msg` may have multiple lines. We assume/require that the + # exception message is the first non-indented line starting with a word + # character following the traceback header line. + _EXCEPTION_RE = re.compile(r""" + # Grab the traceback header. Different versions of Python have + # said different things on the first traceback line. + ^(?P Traceback\ \( + (?: most\ recent\ call\ last + | innermost\ last + ) \) : + ) + \s* $ # toss trailing whitespace on the header. + (?P .*?) # don't blink: absorb stuff until... + ^ (?P \w+ .*) # a line *starts* with alphanum. + """, re.VERBOSE | re.MULTILINE | re.DOTALL) + + # A callable returning a true value iff its argument is a blank line + # or contains a single comment. + _IS_BLANK_OR_COMMENT = re.compile(r'^[ ]*(#.*)?$').match + + def parse(self, string, name=''): + """ + Divide the given string into examples and intervening text, + and return them as a list of alternating Examples and strings. + Line numbers for the Examples are 0-based. The optional + argument `name` is a name identifying this string, and is only + used for error messages. + """ + string = string.expandtabs() + # If all lines begin with the same indentation, then strip it. + min_indent = self._min_indent(string) + if min_indent > 0: + string = '\n'.join([l[min_indent:] for l in string.split('\n')]) + + output = [] + charno, lineno = 0, 0 + # Find all doctest examples in the string: + for m in self._EXAMPLE_RE.finditer(string): + # Add the pre-example text to `output`. + output.append(string[charno:m.start()]) + # Update lineno (lines before this example) + lineno += string.count('\n', charno, m.start()) + # Extract info from the regexp match. + (source, options, want, exc_msg) = \ + self._parse_example(m, name, lineno) + # Create an Example, and add it to the list. + if not self._IS_BLANK_OR_COMMENT(source): + output.append( Example(source, want, exc_msg, + lineno=lineno, + indent=min_indent+len(m.group('indent')), + options=options) ) + # Update lineno (lines inside this example) + lineno += string.count('\n', m.start(), m.end()) + # Update charno. + charno = m.end() + # Add any remaining post-example text to `output`. + output.append(string[charno:]) + return output + + def get_doctest(self, string, globs, name, filename, lineno): + """ + Extract all doctest examples from the given string, and + collect them into a `DocTest` object. + + `globs`, `name`, `filename`, and `lineno` are attributes for + the new `DocTest` object. See the documentation for `DocTest` + for more information. + """ + return DocTest(self.get_examples(string, name), globs, + name, filename, lineno, string) + + def get_examples(self, string, name=''): + """ + Extract all doctest examples from the given string, and return + them as a list of `Example` objects. Line numbers are + 0-based, because it's most common in doctests that nothing + interesting appears on the same line as opening triple-quote, + and so the first interesting line is called \"line 1\" then. + + The optional argument `name` is a name identifying this + string, and is only used for error messages. + """ + return [x for x in self.parse(string, name) + if isinstance(x, Example)] + + def _parse_example(self, m, name, lineno): + """ + Given a regular expression match from `_EXAMPLE_RE` (`m`), + return a pair `(source, want)`, where `source` is the matched + example's source code (with prompts and indentation stripped); + and `want` is the example's expected output (with indentation + stripped). + + `name` is the string's name, and `lineno` is the line number + where the example starts; both are used for error messages. + """ + # Get the example's indentation level. + indent = len(m.group('indent')) + + # Divide source into lines; check that they're properly + # indented; and then strip their indentation & prompts. + source_lines = m.group('source').split('\n') + self._check_prompt_blank(source_lines, indent, name, lineno) + self._check_prefix(source_lines[1:], ' '*indent + '.', name, lineno) + source = '\n'.join([sl[indent+4:] for sl in source_lines]) + + # Divide want into lines; check that it's properly indented; and + # then strip the indentation. Spaces before the last newline should + # be preserved, so plain rstrip() isn't good enough. + want = m.group('want') + want_lines = want.split('\n') + if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): + del want_lines[-1] # forget final newline & spaces after it + self._check_prefix(want_lines, ' '*indent, name, + lineno + len(source_lines)) + want = '\n'.join([wl[indent:] for wl in want_lines]) + + # If `want` contains a traceback message, then extract it. + m = self._EXCEPTION_RE.match(want) + if m: + exc_msg = m.group('msg') + else: + exc_msg = None + + # Extract options from the source. + options = self._find_options(source, name, lineno) + + return source, options, want, exc_msg + + # This regular expression looks for option directives in the + # source code of an example. Option directives are comments + # starting with "doctest:". Warning: this may give false + # positives for string-literals that contain the string + # "#doctest:". Eliminating these false positives would require + # actually parsing the string; but we limit them by ignoring any + # line containing "#doctest:" that is *followed* by a quote mark. + _OPTION_DIRECTIVE_RE = re.compile(r'#\s*doctest:\s*([^\n\'"]*)$', + re.MULTILINE) + + def _find_options(self, source, name, lineno): + """ + Return a dictionary containing option overrides extracted from + option directives in the given source string. + + `name` is the string's name, and `lineno` is the line number + where the example starts; both are used for error messages. + """ + options = {} + # (note: with the current regexp, this will match at most once:) + for m in self._OPTION_DIRECTIVE_RE.finditer(source): + option_strings = m.group(1).replace(',', ' ').split() + for option in option_strings: + if (option[0] not in '+-' or + option[1:] not in OPTIONFLAGS_BY_NAME): + raise ValueError('line %r of the doctest for %s ' + 'has an invalid option: %r' % + (lineno+1, name, option)) + flag = OPTIONFLAGS_BY_NAME[option[1:]] + options[flag] = (option[0] == '+') + if options and self._IS_BLANK_OR_COMMENT(source): + raise ValueError('line %r of the doctest for %s has an option ' + 'directive on a line with no example: %r' % + (lineno, name, source)) + return options + + # This regular expression finds the indentation of every non-blank + # line in a string. + _INDENT_RE = re.compile('^([ ]*)(?=\S)', re.MULTILINE) + + def _min_indent(self, s): + "Return the minimum indentation of any non-blank line in `s`" + indents = [len(indent) for indent in self._INDENT_RE.findall(s)] + if len(indents) > 0: + return min(indents) + else: + return 0 + + def _check_prompt_blank(self, lines, indent, name, lineno): + """ + Given the lines of a source string (including prompts and + leading indentation), check to make sure that every prompt is + followed by a space character. If any line is not followed by + a space character, then raise ValueError. + """ + for i, line in enumerate(lines): + if len(line) >= indent+4 and line[indent+3] != ' ': + raise ValueError('line %r of the docstring for %s ' + 'lacks blank after %s: %r' % + (lineno+i+1, name, + line[indent:indent+3], line)) + + def _check_prefix(self, lines, prefix, name, lineno): + """ + Check that every line in the given list starts with the given + prefix; if any line does not, then raise a ValueError. + """ + for i, line in enumerate(lines): + if line and not line.startswith(prefix): + raise ValueError('line %r of the docstring for %s has ' + 'inconsistent leading whitespace: %r' % + (lineno+i+1, name, line)) + + +###################################################################### +## 4. DocTest Finder +###################################################################### + +class DocTestFinder: + """ + A class used to extract the DocTests that are relevant to a given + object, from its docstring and the docstrings of its contained + objects. Doctests can currently be extracted from the following + object types: modules, functions, classes, methods, staticmethods, + classmethods, and properties. + """ + + def __init__(self, verbose=False, parser=DocTestParser(), + recurse=True, _namefilter=None, exclude_empty=True): + """ + Create a new doctest finder. + + The optional argument `parser` specifies a class or + function that should be used to create new DocTest objects (or + objects that implement the same interface as DocTest). The + signature for this factory function should match the signature + of the DocTest constructor. + + If the optional argument `recurse` is false, then `find` will + only examine the given object, and not any contained objects. + + If the optional argument `exclude_empty` is false, then `find` + will include tests for objects with empty docstrings. + """ + self._parser = parser + self._verbose = verbose + self._recurse = recurse + self._exclude_empty = exclude_empty + # _namefilter is undocumented, and exists only for temporary backward- + # compatibility support of testmod's deprecated isprivate mess. + self._namefilter = _namefilter + + def find(self, obj, name=None, module=None, globs=None, + extraglobs=None): + """ + Return a list of the DocTests that are defined by the given + object's docstring, or by any of its contained objects' + docstrings. + + The optional parameter `module` is the module that contains + the given object. If the module is not specified or is None, then + the test finder will attempt to automatically determine the + correct module. The object's module is used: + + - As a default namespace, if `globs` is not specified. + - To prevent the DocTestFinder from extracting DocTests + from objects that are imported from other modules. + - To find the name of the file containing the object. + - To help find the line number of the object within its + file. + + Contained objects whose module does not match `module` are ignored. + + If `module` is False, no attempt to find the module will be made. + This is obscure, of use mostly in tests: if `module` is False, or + is None but cannot be found automatically, then all objects are + considered to belong to the (non-existent) module, so all contained + objects will (recursively) be searched for doctests. + + The globals for each DocTest is formed by combining `globs` + and `extraglobs` (bindings in `extraglobs` override bindings + in `globs`). A new copy of the globals dictionary is created + for each DocTest. If `globs` is not specified, then it + defaults to the module's `__dict__`, if specified, or {} + otherwise. If `extraglobs` is not specified, then it defaults + to {}. + + """ + # If name was not specified, then extract it from the object. + if name is None: + name = getattr(obj, '__name__', None) + if name is None: + raise ValueError("DocTestFinder.find: name must be given " + "when obj.__name__ doesn't exist: %r" % + (type(obj),)) + + # Find the module that contains the given object (if obj is + # a module, then module=obj.). Note: this may fail, in which + # case module will be None. + if module is False: + module = None + elif module is None: + module = inspect.getmodule(obj) + + # Read the module's source code. This is used by + # DocTestFinder._find_lineno to find the line number for a + # given object's docstring. + try: + file = inspect.getsourcefile(obj) or inspect.getfile(obj) + source_lines = linecache.getlines(file) + if not source_lines: + source_lines = None + except TypeError: + source_lines = None + + # Initialize globals, and merge in extraglobs. + if globs is None: + if module is None: + globs = {} + else: + globs = module.__dict__.copy() + else: + globs = globs.copy() + if extraglobs is not None: + globs.update(extraglobs) + + # Recursively expore `obj`, extracting DocTests. + tests = [] + self._find(tests, obj, name, module, source_lines, globs, {}) + # Sort the tests by alpha order of names, for consistency in + # verbose-mode output. This was a feature of doctest in Pythons + # <= 2.3 that got lost by accident in 2.4. It was repaired in + # 2.4.4 and 2.5. + tests.sort() + return tests + + def _filter(self, obj, prefix, base): + """ + Return true if the given object should not be examined. + """ + return (self._namefilter is not None and + self._namefilter(prefix, base)) + + def _from_module(self, module, object): + """ + Return true if the given object is defined in the given + module. + """ + if module is None: + return True + elif inspect.isfunction(object): + return module.__dict__ is object.func_globals + elif inspect.isclass(object): + # Some jython classes don't set __module__ + return module.__name__ == getattr(object, '__module__', None) + elif inspect.getmodule(object) is not None: + return module is inspect.getmodule(object) + elif hasattr(object, '__module__'): + return module.__name__ == object.__module__ + elif isinstance(object, property): + return True # [XX] no way not be sure. + else: + raise ValueError("object must be a class or function") + + def _find(self, tests, obj, name, module, source_lines, globs, seen): + """ + Find tests for the given object and any contained objects, and + add them to `tests`. + """ + if self._verbose: + print 'Finding tests in %s' % name + + # If we've already processed this object, then ignore it. + if id(obj) in seen: + return + seen[id(obj)] = 1 + + # Find a test for this object, and add it to the list of tests. + test = self._get_test(obj, name, module, globs, source_lines) + if test is not None: + tests.append(test) + + # Look for tests in a module's contained objects. + if inspect.ismodule(obj) and self._recurse: + for valname, val in obj.__dict__.items(): + # Check if this contained object should be ignored. + if self._filter(val, name, valname): + continue + valname = '%s.%s' % (name, valname) + # Recurse to functions & classes. + if ((inspect.isfunction(val) or inspect.isclass(val)) and + self._from_module(module, val)): + self._find(tests, val, valname, module, source_lines, + globs, seen) + + # Look for tests in a module's __test__ dictionary. + if inspect.ismodule(obj) and self._recurse: + for valname, val in getattr(obj, '__test__', {}).items(): + if not isinstance(valname, basestring): + raise ValueError("DocTestFinder.find: __test__ keys " + "must be strings: %r" % + (type(valname),)) + if not (inspect.isfunction(val) or inspect.isclass(val) or + inspect.ismethod(val) or inspect.ismodule(val) or + isinstance(val, basestring)): + raise ValueError("DocTestFinder.find: __test__ values " + "must be strings, functions, methods, " + "classes, or modules: %r" % + (type(val),)) + valname = '%s.__test__.%s' % (name, valname) + self._find(tests, val, valname, module, source_lines, + globs, seen) + + # Look for tests in a class's contained objects. + if inspect.isclass(obj) and self._recurse: + for valname, val in obj.__dict__.items(): + # Check if this contained object should be ignored. + if self._filter(val, name, valname): + continue + # Special handling for staticmethod/classmethod. + if isinstance(val, staticmethod): + val = getattr(obj, valname) + if isinstance(val, classmethod): + val = getattr(obj, valname).im_func + + # Recurse to methods, properties, and nested classes. + if ((inspect.isfunction(val) or inspect.isclass(val) or + isinstance(val, property)) and + self._from_module(module, val)): + valname = '%s.%s' % (name, valname) + self._find(tests, val, valname, module, source_lines, + globs, seen) + + def _get_test(self, obj, name, module, globs, source_lines): + """ + Return a DocTest for the given object, if it defines a docstring; + otherwise, return None. + """ + # Extract the object's docstring. If it doesn't have one, + # then return None (no test for this object). + if isinstance(obj, basestring): + docstring = obj + else: + try: + if obj.__doc__ is None: + docstring = '' + else: + docstring = obj.__doc__ + if not isinstance(docstring, basestring): + docstring = str(docstring) + except (TypeError, AttributeError): + docstring = '' + + # Find the docstring's location in the file. + lineno = self._find_lineno(obj, source_lines) + + # Don't bother if the docstring is empty. + if self._exclude_empty and not docstring: + return None + + # Return a DocTest for this object. + if module is None: + filename = None + else: + filename = getattr(module, '__file__', module.__name__) + if filename[-4:] in (".pyc", ".pyo"): + filename = filename[:-1] + elif sys.platform.startswith('java') and \ + filename.endswith('$py.class'): + filename = '%s.py' % filename[:-9] + return self._parser.get_doctest(docstring, globs, name, + filename, lineno) + + def _find_lineno(self, obj, source_lines): + """ + Return a line number of the given object's docstring. Note: + this method assumes that the object has a docstring. + """ + lineno = None + + # Find the line number for modules. + if inspect.ismodule(obj): + lineno = 0 + + # Find the line number for classes. + # Note: this could be fooled if a class is defined multiple + # times in a single file. + if inspect.isclass(obj): + if source_lines is None: + return None + pat = re.compile(r'^\s*class\s*%s\b' % + getattr(obj, '__name__', '-')) + for i, line in enumerate(source_lines): + if pat.match(line): + lineno = i + break + + # Find the line number for functions & methods. + if inspect.ismethod(obj): obj = obj.im_func + if inspect.isfunction(obj): obj = obj.func_code + if inspect.istraceback(obj): obj = obj.tb_frame + if inspect.isframe(obj): obj = obj.f_code + if inspect.iscode(obj): + lineno = getattr(obj, 'co_firstlineno', None)-1 + + # Find the line number where the docstring starts. Assume + # that it's the first line that begins with a quote mark. + # Note: this could be fooled by a multiline function + # signature, where a continuation line begins with a quote + # mark. + if lineno is not None: + if source_lines is None: + return lineno+1 + pat = re.compile('(^|.*:)\s*\w*("|\')') + for lineno in range(lineno, len(source_lines)): + if pat.match(source_lines[lineno]): + return lineno + + # We couldn't find the line number. + return None + +###################################################################### +## 5. DocTest Runner +###################################################################### + +class DocTestRunner: + # This divider string is used to separate failure messages, and to + # separate sections of the summary. + DIVIDER = "*" * 70 + + def __init__(self, checker=None, verbose=None, optionflags=0): + """ + Create a new test runner. + + Optional keyword arg `checker` is the `OutputChecker` that + should be used to compare the expected outputs and actual + outputs of doctest examples. + + Optional keyword arg 'verbose' prints lots of stuff if true, + only failures if false; by default, it's true iff '-v' is in + sys.argv. + + Optional argument `optionflags` can be used to control how the + test runner compares expected output to actual output, and how + it displays failures. See the documentation for `testmod` for + more information. + """ + self._checker = checker or OutputChecker() + if verbose is None: + verbose = '-v' in sys.argv + self._verbose = verbose + self.optionflags = optionflags + self.original_optionflags = optionflags + + # Keep track of the examples we've run. + self.tries = 0 + self.failures = 0 + self._name2ft = {} + + # Create a fake output target for capturing doctest output. + self._fakeout = _SpoofOut() + + #///////////////////////////////////////////////////////////////// + # Reporting methods + #///////////////////////////////////////////////////////////////// + + def report_start(self, out, test, example): + """ + Report that the test runner is about to process the given + example. (Only displays a message if verbose=True) + """ + if self._verbose: + if example.want: + out('Trying:\n' + _indent(example.source) + + 'Expecting:\n' + _indent(example.want)) + else: + out('Trying:\n' + _indent(example.source) + + 'Expecting nothing\n') + + def report_success(self, out, test, example, got): + """ + Report that the given example ran successfully. (Only + displays a message if verbose=True) + """ + if self._verbose: + out("ok\n") + + def report_failure(self, out, test, example, got): + """ + Report that the given example failed. + """ + out(self._failure_header(test, example) + + self._checker.output_difference(example, got, self.optionflags)) + + def report_unexpected_exception(self, out, test, example, exc_info): + """ + Report that the given example raised an unexpected exception. + """ + out(self._failure_header(test, example) + + 'Exception raised:\n' + _indent(_exception_traceback(exc_info))) + + def _failure_header(self, test, example): + out = [self.DIVIDER] + if test.filename: + if test.lineno is not None and example.lineno is not None: + lineno = test.lineno + example.lineno + 1 + else: + lineno = '?' + out.append('File "%s", line %s, in %s' % + (test.filename, lineno, test.name)) + else: + out.append('Line %s, in %s' % (example.lineno+1, test.name)) + out.append('Failed example:') + source = example.source + out.append(_indent(source)) + return '\n'.join(out) + + #///////////////////////////////////////////////////////////////// + # DocTest Running + #///////////////////////////////////////////////////////////////// + + def __run(self, test, compileflags, out): + """ + Run the examples in `test`. Write the outcome of each example + with one of the `DocTestRunner.report_*` methods, using the + writer function `out`. `compileflags` is the set of compiler + flags that should be used to execute examples. Return a tuple + `(f, t)`, where `t` is the number of examples tried, and `f` + is the number of examples that failed. The examples are run + in the namespace `test.globs`. + """ + # Keep track of the number of failures and tries. + failures = tries = 0 + + # Save the option flags (since option directives can be used + # to modify them). + original_optionflags = self.optionflags + + SUCCESS, FAILURE, BOOM = range(3) # `outcome` state + + check = self._checker.check_output + + # Process each example. + for examplenum, example in enumerate(test.examples): + + # If REPORT_ONLY_FIRST_FAILURE is set, then supress + # reporting after the first failure. + quiet = (self.optionflags & REPORT_ONLY_FIRST_FAILURE and + failures > 0) + + # Merge in the example's options. + self.optionflags = original_optionflags + if example.options: + for (optionflag, val) in example.options.items(): + if val: + self.optionflags |= optionflag + else: + self.optionflags &= ~optionflag + + # Record that we started this example. + tries += 1 + if not quiet: + self.report_start(out, test, example) + + # Use a special filename for compile(), so we can retrieve + # the source code during interactive debugging (see + # __patched_linecache_getlines). + filename = '' % (test.name, examplenum) + + # Run the example in the given context (globs), and record + # any exception that gets raised. (But don't intercept + # keyboard interrupts.) + try: + # Don't blink! This is where the user's code gets run. + exec compile(example.source, filename, "single", + compileflags, 1) in test.globs + self.debugger.set_continue() # ==== Example Finished ==== + exception = None + except KeyboardInterrupt: + raise + except: + exception = sys.exc_info() + self.debugger.set_continue() # ==== Example Finished ==== + + got = self._fakeout.getvalue() # the actual output + self._fakeout.truncate(0) + outcome = FAILURE # guilty until proved innocent or insane + + # If the example executed without raising any exceptions, + # verify its output. + if exception is None: + if check(example.want, got, self.optionflags): + outcome = SUCCESS + + # The example raised an exception: check if it was expected. + else: + exc_info = sys.exc_info() + exc_msg = traceback.format_exception_only(*exc_info[:2])[-1] + if not quiet: + got += _exception_traceback(exc_info) + + # If `example.exc_msg` is None, then we weren't expecting + # an exception. + if example.exc_msg is None: + outcome = BOOM + + # We expected an exception: see whether it matches. + elif check(example.exc_msg, exc_msg, self.optionflags): + outcome = SUCCESS + + # Another chance if they didn't care about the detail. + elif self.optionflags & IGNORE_EXCEPTION_DETAIL: + m1 = re.match(r'[^:]*:', example.exc_msg) + m2 = re.match(r'[^:]*:', exc_msg) + if m1 and m2 and check(m1.group(0), m2.group(0), + self.optionflags): + outcome = SUCCESS + + # Report the outcome. + if outcome is SUCCESS: + if not quiet: + self.report_success(out, test, example, got) + elif outcome is FAILURE: + if not quiet: + self.report_failure(out, test, example, got) + failures += 1 + elif outcome is BOOM: + if not quiet: + self.report_unexpected_exception(out, test, example, + exc_info) + failures += 1 + else: + assert False, ("unknown outcome", outcome) + + # Restore the option flags (in case they were modified) + self.optionflags = original_optionflags + + # Record and return the number of failures and tries. + self.__record_outcome(test, failures, tries) + return failures, tries + + def __record_outcome(self, test, f, t): + """ + Record the fact that the given DocTest (`test`) generated `f` + failures out of `t` tried examples. + """ + f2, t2 = self._name2ft.get(test.name, (0,0)) + self._name2ft[test.name] = (f+f2, t+t2) + self.failures += f + self.tries += t + + __LINECACHE_FILENAME_RE = re.compile(r'[\w\.]+)' + r'\[(?P\d+)\]>$') + def __patched_linecache_getlines(self, filename): + m = self.__LINECACHE_FILENAME_RE.match(filename) + if m and m.group('name') == self.test.name: + example = self.test.examples[int(m.group('examplenum'))] + return example.source.splitlines(True) + else: + return self.save_linecache_getlines(filename) + + def run(self, test, compileflags=None, out=None, clear_globs=True): + """ + Run the examples in `test`, and display the results using the + writer function `out`. + + The examples are run in the namespace `test.globs`. If + `clear_globs` is true (the default), then this namespace will + be cleared after the test runs, to help with garbage + collection. If you would like to examine the namespace after + the test completes, then use `clear_globs=False`. + + `compileflags` gives the set of flags that should be used by + the Python compiler when running the examples. If not + specified, then it will default to the set of future-import + flags that apply to `globs`. + + The output of each example is checked using + `DocTestRunner.check_output`, and the results are formatted by + the `DocTestRunner.report_*` methods. + """ + self.test = test + + if compileflags is None: + compileflags = _extract_future_flags(test.globs) + + save_stdout = sys.stdout + if out is None: + out = save_stdout.write + sys.stdout = self._fakeout + + # Patch pdb.set_trace to restore sys.stdout during interactive + # debugging (so it's not still redirected to self._fakeout). + # Note that the interactive output will go to *our* + # save_stdout, even if that's not the real sys.stdout; this + # allows us to write test cases for the set_trace behavior. + save_set_trace = pdb.set_trace + self.debugger = _OutputRedirectingPdb(save_stdout) + self.debugger.reset() + pdb.set_trace = self.debugger.set_trace + + # Patch linecache.getlines, so we can see the example's source + # when we're inside the debugger. + self.save_linecache_getlines = linecache.getlines + linecache.getlines = self.__patched_linecache_getlines + + try: + return self.__run(test, compileflags, out) + finally: + sys.stdout = save_stdout + pdb.set_trace = save_set_trace + linecache.getlines = self.save_linecache_getlines + if clear_globs: + test.globs.clear() + + #///////////////////////////////////////////////////////////////// + # Summarization + #///////////////////////////////////////////////////////////////// + def summarize(self, verbose=None): + """ + Print a summary of all the test cases that have been run by + this DocTestRunner, and return a tuple `(f, t)`, where `f` is + the total number of failed examples, and `t` is the total + number of tried examples. + + The optional `verbose` argument controls how detailed the + summary is. If the verbosity is not specified, then the + DocTestRunner's verbosity is used. + """ + if verbose is None: + verbose = self._verbose + notests = [] + passed = [] + failed = [] + totalt = totalf = 0 + for x in self._name2ft.items(): + name, (f, t) = x + assert f <= t + totalt += t + totalf += f + if t == 0: + notests.append(name) + elif f == 0: + passed.append( (name, t) ) + else: + failed.append(x) + if verbose: + if notests: + print len(notests), "items had no tests:" + notests.sort() + for thing in notests: + print " ", thing + if passed: + print len(passed), "items passed all tests:" + passed.sort() + for thing, count in passed: + print " %3d tests in %s" % (count, thing) + if failed: + print self.DIVIDER + print len(failed), "items had failures:" + failed.sort() + for thing, (f, t) in failed: + print " %3d of %3d in %s" % (f, t, thing) + if verbose: + print totalt, "tests in", len(self._name2ft), "items." + print totalt - totalf, "passed and", totalf, "failed." + if totalf: + print "***Test Failed***", totalf, "failures." + elif verbose: + print "Test passed." + return totalf, totalt + + #///////////////////////////////////////////////////////////////// + # Backward compatibility cruft to maintain doctest.master. + #///////////////////////////////////////////////////////////////// + def merge(self, other): + d = self._name2ft + for name, (f, t) in other._name2ft.items(): + if name in d: + print "*** DocTestRunner.merge: '" + name + "' in both" \ + " testers; summing outcomes." + f2, t2 = d[name] + f = f + f2 + t = t + t2 + d[name] = f, t + +class OutputChecker: + """ + A class used to check the whether the actual output from a doctest + example matches the expected output. `OutputChecker` defines two + methods: `check_output`, which compares a given pair of outputs, + and returns true if they match; and `output_difference`, which + returns a string describing the differences between two outputs. + """ + def check_output(self, want, got, optionflags): + """ + Return True iff the actual output from an example (`got`) + matches the expected output (`want`). These strings are + always considered to match if they are identical; but + depending on what option flags the test runner is using, + several non-exact match types are also possible. See the + documentation for `TestRunner` for more information about + option flags. + """ + # Handle the common case first, for efficiency: + # if they're string-identical, always return true. + if got == want: + return True + + # The values True and False replaced 1 and 0 as the return + # value for boolean comparisons in Python 2.3. + if not (optionflags & DONT_ACCEPT_TRUE_FOR_1): + if (got,want) == ("True\n", "1\n"): + return True + if (got,want) == ("False\n", "0\n"): + return True + + # can be used as a special sequence to signify a + # blank line, unless the DONT_ACCEPT_BLANKLINE flag is used. + if not (optionflags & DONT_ACCEPT_BLANKLINE): + # Replace in want with a blank line. + want = re.sub('(?m)^%s\s*?$' % re.escape(BLANKLINE_MARKER), + '', want) + # If a line in got contains only spaces, then remove the + # spaces. + got = re.sub('(?m)^\s*?$', '', got) + if got == want: + return True + + # This flag causes doctest to ignore any differences in the + # contents of whitespace strings. Note that this can be used + # in conjunction with the ELLIPSIS flag. + if optionflags & NORMALIZE_WHITESPACE: + got = ' '.join(got.split()) + want = ' '.join(want.split()) + if got == want: + return True + + # The ELLIPSIS flag says to let the sequence "..." in `want` + # match any substring in `got`. + if optionflags & ELLIPSIS: + if _ellipsis_match(want, got): + return True + + # We didn't find any match; return false. + return False + + # Should we do a fancy diff? + def _do_a_fancy_diff(self, want, got, optionflags): + # Not unless they asked for a fancy diff. + if not optionflags & (REPORT_UDIFF | + REPORT_CDIFF | + REPORT_NDIFF): + return False + + # If expected output uses ellipsis, a meaningful fancy diff is + # too hard ... or maybe not. In two real-life failures Tim saw, + # a diff was a major help anyway, so this is commented out. + # [todo] _ellipsis_match() knows which pieces do and don't match, + # and could be the basis for a kick-ass diff in this case. + ##if optionflags & ELLIPSIS and ELLIPSIS_MARKER in want: + ## return False + + # ndiff does intraline difference marking, so can be useful even + # for 1-line differences. + if optionflags & REPORT_NDIFF: + return True + + # The other diff types need at least a few lines to be helpful. + return want.count('\n') > 2 and got.count('\n') > 2 + + def output_difference(self, example, got, optionflags): + """ + Return a string describing the differences between the + expected output for a given example (`example`) and the actual + output (`got`). `optionflags` is the set of option flags used + to compare `want` and `got`. + """ + want = example.want + # If s are being used, then replace blank lines + # with in the actual output string. + if not (optionflags & DONT_ACCEPT_BLANKLINE): + got = re.sub('(?m)^[ ]*(?=\n)', BLANKLINE_MARKER, got) + + # Check if we should use diff. + if self._do_a_fancy_diff(want, got, optionflags): + # Split want & got into lines. + want_lines = want.splitlines(True) # True == keep line ends + got_lines = got.splitlines(True) + # Use difflib to find their differences. + if optionflags & REPORT_UDIFF: + diff = difflib.unified_diff(want_lines, got_lines, n=2) + diff = list(diff)[2:] # strip the diff header + kind = 'unified diff with -expected +actual' + elif optionflags & REPORT_CDIFF: + diff = difflib.context_diff(want_lines, got_lines, n=2) + diff = list(diff)[2:] # strip the diff header + kind = 'context diff with expected followed by actual' + elif optionflags & REPORT_NDIFF: + engine = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) + diff = list(engine.compare(want_lines, got_lines)) + kind = 'ndiff with -expected +actual' + else: + assert 0, 'Bad diff option' + # Remove trailing whitespace on diff output. + diff = [line.rstrip() + '\n' for line in diff] + return 'Differences (%s):\n' % kind + _indent(''.join(diff)) + + # If we're not using diff, then simply list the expected + # output followed by the actual output. + if want and got: + return 'Expected:\n%sGot:\n%s' % (_indent(want), _indent(got)) + elif want: + return 'Expected:\n%sGot nothing\n' % _indent(want) + elif got: + return 'Expected nothing\nGot:\n%s' % _indent(got) + else: + return 'Expected nothing\nGot nothing\n' + +class DocTestFailure(Exception): + """A DocTest example has failed in debugging mode. + + The exception instance has variables: + + - test: the DocTest object being run + + - excample: the Example object that failed + + - got: the actual output + """ + def __init__(self, test, example, got): + self.test = test + self.example = example + self.got = got + + def __str__(self): + return str(self.test) + +class UnexpectedException(Exception): + """A DocTest example has encountered an unexpected exception + + The exception instance has variables: + + - test: the DocTest object being run + + - excample: the Example object that failed + + - exc_info: the exception info + """ + def __init__(self, test, example, exc_info): + self.test = test + self.example = example + self.exc_info = exc_info + + def __str__(self): + return str(self.test) + +class DebugRunner(DocTestRunner): + + def run(self, test, compileflags=None, out=None, clear_globs=True): + r = DocTestRunner.run(self, test, compileflags, out, False) + if clear_globs: + test.globs.clear() + return r + + def report_unexpected_exception(self, out, test, example, exc_info): + raise UnexpectedException(test, example, exc_info) + + def report_failure(self, out, test, example, got): + raise DocTestFailure(test, example, got) + +###################################################################### +## 6. Test Functions +###################################################################### +# These should be backwards compatible. + +# For backward compatibility, a global instance of a DocTestRunner +# class, updated by testmod. +master = None + +def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None, + report=True, optionflags=0, extraglobs=None, + raise_on_error=False, exclude_empty=False): + """m=None, name=None, globs=None, verbose=None, isprivate=None, + report=True, optionflags=0, extraglobs=None, raise_on_error=False, + exclude_empty=False + + Test examples in docstrings in functions and classes reachable + from module m (or the current module if m is not supplied), starting + with m.__doc__. Unless isprivate is specified, private names + are not skipped. + + Also test examples reachable from dict m.__test__ if it exists and is + not None. m.__test__ maps names to functions, classes and strings; + function and class docstrings are tested even if the name is private; + strings are tested directly, as if they were docstrings. + + Return (#failures, #tests). + + See doctest.__doc__ for an overview. + + Optional keyword arg "name" gives the name of the module; by default + use m.__name__. + + Optional keyword arg "globs" gives a dict to be used as the globals + when executing examples; by default, use m.__dict__. A copy of this + dict is actually used for each docstring, so that each docstring's + examples start with a clean slate. + + Optional keyword arg "extraglobs" gives a dictionary that should be + merged into the globals that are used to execute examples. By + default, no extra globals are used. This is new in 2.4. + + Optional keyword arg "verbose" prints lots of stuff if true, prints + only failures if false; by default, it's true iff "-v" is in sys.argv. + + Optional keyword arg "report" prints a summary at the end when true, + else prints nothing at the end. In verbose mode, the summary is + detailed, else very brief (in fact, empty if all tests passed). + + Optional keyword arg "optionflags" or's together module constants, + and defaults to 0. This is new in 2.3. Possible values (see the + docs for details): + + DONT_ACCEPT_TRUE_FOR_1 + DONT_ACCEPT_BLANKLINE + NORMALIZE_WHITESPACE + ELLIPSIS + IGNORE_EXCEPTION_DETAIL + REPORT_UDIFF + REPORT_CDIFF + REPORT_NDIFF + REPORT_ONLY_FIRST_FAILURE + + Optional keyword arg "raise_on_error" raises an exception on the + first unexpected exception or failure. This allows failures to be + post-mortem debugged. + + Deprecated in Python 2.4: + Optional keyword arg "isprivate" specifies a function used to + determine whether a name is private. The default function is + treat all functions as public. Optionally, "isprivate" can be + set to doctest.is_private to skip over functions marked as private + using the underscore naming convention; see its docs for details. + + Advanced tomfoolery: testmod runs methods of a local instance of + class doctest.Tester, then merges the results into (or creates) + global Tester instance doctest.master. Methods of doctest.master + can be called directly too, if you want to do something unusual. + Passing report=0 to testmod is especially useful then, to delay + displaying a summary. Invoke doctest.master.summarize(verbose) + when you're done fiddling. + """ + global master + + if isprivate is not None: + warnings.warn("the isprivate argument is deprecated; " + "examine DocTestFinder.find() lists instead", + DeprecationWarning) + + # If no module was given, then use __main__. + if m is None: + # DWA - m will still be None if this wasn't invoked from the command + # line, in which case the following TypeError is about as good an error + # as we should expect + m = sys.modules.get('__main__') + + # Check that we were actually given a module. + if not inspect.ismodule(m): + raise TypeError("testmod: module required; %r" % (m,)) + + # If no name was given, then use the module's name. + if name is None: + name = m.__name__ + + # Find, parse, and run all tests in the given module. + finder = DocTestFinder(_namefilter=isprivate, exclude_empty=exclude_empty) + + if raise_on_error: + runner = DebugRunner(verbose=verbose, optionflags=optionflags) + else: + runner = DocTestRunner(verbose=verbose, optionflags=optionflags) + + for test in finder.find(m, name, globs=globs, extraglobs=extraglobs): + runner.run(test) + + if report: + runner.summarize() + + if master is None: + master = runner + else: + master.merge(runner) + + return runner.failures, runner.tries + +def testfile(filename, module_relative=True, name=None, package=None, + globs=None, verbose=None, report=True, optionflags=0, + extraglobs=None, raise_on_error=False, parser=DocTestParser()): + """ + Test examples in the given file. Return (#failures, #tests). + + Optional keyword arg "module_relative" specifies how filenames + should be interpreted: + + - If "module_relative" is True (the default), then "filename" + specifies a module-relative path. By default, this path is + relative to the calling module's directory; but if the + "package" argument is specified, then it is relative to that + package. To ensure os-independence, "filename" should use + "/" characters to separate path segments, and should not + be an absolute path (i.e., it may not begin with "/"). + + - If "module_relative" is False, then "filename" specifies an + os-specific path. The path may be absolute or relative (to + the current working directory). + + Optional keyword arg "name" gives the name of the test; by default + use the file's basename. + + Optional keyword argument "package" is a Python package or the + name of a Python package whose directory should be used as the + base directory for a module relative filename. If no package is + specified, then the calling module's directory is used as the base + directory for module relative filenames. It is an error to + specify "package" if "module_relative" is False. + + Optional keyword arg "globs" gives a dict to be used as the globals + when executing examples; by default, use {}. A copy of this dict + is actually used for each docstring, so that each docstring's + examples start with a clean slate. + + Optional keyword arg "extraglobs" gives a dictionary that should be + merged into the globals that are used to execute examples. By + default, no extra globals are used. + + Optional keyword arg "verbose" prints lots of stuff if true, prints + only failures if false; by default, it's true iff "-v" is in sys.argv. + + Optional keyword arg "report" prints a summary at the end when true, + else prints nothing at the end. In verbose mode, the summary is + detailed, else very brief (in fact, empty if all tests passed). + + Optional keyword arg "optionflags" or's together module constants, + and defaults to 0. Possible values (see the docs for details): + + DONT_ACCEPT_TRUE_FOR_1 + DONT_ACCEPT_BLANKLINE + NORMALIZE_WHITESPACE + ELLIPSIS + IGNORE_EXCEPTION_DETAIL + REPORT_UDIFF + REPORT_CDIFF + REPORT_NDIFF + REPORT_ONLY_FIRST_FAILURE + + Optional keyword arg "raise_on_error" raises an exception on the + first unexpected exception or failure. This allows failures to be + post-mortem debugged. + + Optional keyword arg "parser" specifies a DocTestParser (or + subclass) that should be used to extract tests from the files. + + Advanced tomfoolery: testmod runs methods of a local instance of + class doctest.Tester, then merges the results into (or creates) + global Tester instance doctest.master. Methods of doctest.master + can be called directly too, if you want to do something unusual. + Passing report=0 to testmod is especially useful then, to delay + displaying a summary. Invoke doctest.master.summarize(verbose) + when you're done fiddling. + """ + global master + + if package and not module_relative: + raise ValueError("Package may only be specified for module-" + "relative paths.") + + # Relativize the path + if module_relative: + package = _normalize_module(package) + filename = _module_relative_path(package, filename) + + # If no name was given, then use the file's name. + if name is None: + name = os.path.basename(filename) + + # Assemble the globals. + if globs is None: + globs = {} + else: + globs = globs.copy() + if extraglobs is not None: + globs.update(extraglobs) + + if raise_on_error: + runner = DebugRunner(verbose=verbose, optionflags=optionflags) + else: + runner = DocTestRunner(verbose=verbose, optionflags=optionflags) + + # Read the file, convert it to a test, and run it. + s = open(filename).read() + test = parser.get_doctest(s, globs, name, filename, 0) + runner.run(test) + + if report: + runner.summarize() + + if master is None: + master = runner + else: + master.merge(runner) + + return runner.failures, runner.tries + +def run_docstring_examples(f, globs, verbose=False, name="NoName", + compileflags=None, optionflags=0): + """ + Test examples in the given object's docstring (`f`), using `globs` + as globals. Optional argument `name` is used in failure messages. + If the optional argument `verbose` is true, then generate output + even if there are no failures. + + `compileflags` gives the set of flags that should be used by the + Python compiler when running the examples. If not specified, then + it will default to the set of future-import flags that apply to + `globs`. + + Optional keyword arg `optionflags` specifies options for the + testing and output. See the documentation for `testmod` for more + information. + """ + # Find, parse, and run all tests in the given module. + finder = DocTestFinder(verbose=verbose, recurse=False) + runner = DocTestRunner(verbose=verbose, optionflags=optionflags) + for test in finder.find(f, name, globs=globs): + runner.run(test, compileflags=compileflags) + +###################################################################### +## 7. Tester +###################################################################### +# This is provided only for backwards compatibility. It's not +# actually used in any way. + +class Tester: + def __init__(self, mod=None, globs=None, verbose=None, + isprivate=None, optionflags=0): + + warnings.warn("class Tester is deprecated; " + "use class doctest.DocTestRunner instead", + DeprecationWarning, stacklevel=2) + if mod is None and globs is None: + raise TypeError("Tester.__init__: must specify mod or globs") + if mod is not None and not inspect.ismodule(mod): + raise TypeError("Tester.__init__: mod must be a module; %r" % + (mod,)) + if globs is None: + globs = mod.__dict__ + self.globs = globs + + self.verbose = verbose + self.isprivate = isprivate + self.optionflags = optionflags + self.testfinder = DocTestFinder(_namefilter=isprivate) + self.testrunner = DocTestRunner(verbose=verbose, + optionflags=optionflags) + + def runstring(self, s, name): + test = DocTestParser().get_doctest(s, self.globs, name, None, None) + if self.verbose: + print "Running string", name + (f,t) = self.testrunner.run(test) + if self.verbose: + print f, "of", t, "examples failed in string", name + return (f,t) + + def rundoc(self, object, name=None, module=None): + f = t = 0 + tests = self.testfinder.find(object, name, module=module, + globs=self.globs) + for test in tests: + (f2, t2) = self.testrunner.run(test) + (f,t) = (f+f2, t+t2) + return (f,t) + + def rundict(self, d, name, module=None): + import new + m = new.module(name) + m.__dict__.update(d) + if module is None: + module = False + return self.rundoc(m, name, module) + + def run__test__(self, d, name): + import new + m = new.module(name) + m.__test__ = d + return self.rundoc(m, name) + + def summarize(self, verbose=None): + return self.testrunner.summarize(verbose) + + def merge(self, other): + self.testrunner.merge(other.testrunner) + +###################################################################### +## 8. Unittest Support +###################################################################### + +_unittest_reportflags = 0 + +def set_unittest_reportflags(flags): + global _unittest_reportflags + + if (flags & REPORTING_FLAGS) != flags: + raise ValueError("Only reporting flags allowed", flags) + old = _unittest_reportflags + _unittest_reportflags = flags + return old + + +class DocTestCase(unittest.TestCase): + + def __init__(self, test, optionflags=0, setUp=None, tearDown=None, + checker=None): + + unittest.TestCase.__init__(self) + self._dt_optionflags = optionflags + self._dt_checker = checker + self._dt_test = test + self._dt_setUp = setUp + self._dt_tearDown = tearDown + + def setUp(self): + test = self._dt_test + + if self._dt_setUp is not None: + self._dt_setUp(test) + + def tearDown(self): + test = self._dt_test + + if self._dt_tearDown is not None: + self._dt_tearDown(test) + + test.globs.clear() + + def runTest(self): + test = self._dt_test + old = sys.stdout + new = StringIO() + optionflags = self._dt_optionflags + + if not (optionflags & REPORTING_FLAGS): + # The option flags don't include any reporting flags, + # so add the default reporting flags + optionflags |= _unittest_reportflags + + runner = DocTestRunner(optionflags=optionflags, + checker=self._dt_checker, verbose=False) + + try: + runner.DIVIDER = "-"*70 + failures, tries = runner.run( + test, out=new.write, clear_globs=False) + finally: + sys.stdout = old + + if failures: + raise self.failureException(self.format_failure(new.getvalue())) + + def format_failure(self, err): + test = self._dt_test + if test.lineno is None: + lineno = 'unknown line number' + else: + lineno = '%s' % test.lineno + lname = '.'.join(test.name.split('.')[-1:]) + return ('Failed doctest test for %s\n' + ' File "%s", line %s, in %s\n\n%s' + % (test.name, test.filename, lineno, lname, err) + ) + + def debug(self): + self.setUp() + runner = DebugRunner(optionflags=self._dt_optionflags, + checker=self._dt_checker, verbose=False) + runner.run(self._dt_test) + self.tearDown() + + def id(self): + return self._dt_test.name + + def __repr__(self): + name = self._dt_test.name.split('.') + return "%s (%s)" % (name[-1], '.'.join(name[:-1])) + + __str__ = __repr__ + + def shortDescription(self): + return "Doctest: " + self._dt_test.name + +def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, + **options): + """ + Convert doctest tests for a module to a unittest test suite. + + This converts each documentation string in a module that + contains doctest tests to a unittest test case. If any of the + tests in a doc string fail, then the test case fails. An exception + is raised showing the name of the file containing the test and a + (sometimes approximate) line number. + + The `module` argument provides the module to be tested. The argument + can be either a module or a module name. + + If no argument is given, the calling module is used. + + A number of options may be provided as keyword arguments: + + setUp + A set-up function. This is called before running the + tests in each file. The setUp function will be passed a DocTest + object. The setUp function can access the test globals as the + globs attribute of the test passed. + + tearDown + A tear-down function. This is called after running the + tests in each file. The tearDown function will be passed a DocTest + object. The tearDown function can access the test globals as the + globs attribute of the test passed. + + globs + A dictionary containing initial global variables for the tests. + + optionflags + A set of doctest option flags expressed as an integer. + """ + + if test_finder is None: + test_finder = DocTestFinder() + + module = _normalize_module(module) + tests = test_finder.find(module, globs=globs, extraglobs=extraglobs) + if globs is None: + globs = module.__dict__ + if not tests: + # Why do we want to do this? Because it reveals a bug that might + # otherwise be hidden. + raise ValueError(module, "has no tests") + + tests.sort() + suite = unittest.TestSuite() + for test in tests: + if len(test.examples) == 0: + continue + if not test.filename: + filename = module.__file__ + if filename[-4:] in (".pyc", ".pyo"): + filename = filename[:-1] + elif sys.platform.startswith('java') and \ + filename.endswith('$py.class'): + filename = '%s.py' % filename[:-9] + test.filename = filename + suite.addTest(DocTestCase(test, **options)) + + return suite + +class DocFileCase(DocTestCase): + + def id(self): + return '_'.join(self._dt_test.name.split('.')) + + def __repr__(self): + return self._dt_test.filename + __str__ = __repr__ + + def format_failure(self, err): + return ('Failed doctest test for %s\n File "%s", line 0\n\n%s' + % (self._dt_test.name, self._dt_test.filename, err) + ) + +def DocFileTest(path, module_relative=True, package=None, + globs=None, parser=DocTestParser(), **options): + if globs is None: + globs = {} + + if package and not module_relative: + raise ValueError("Package may only be specified for module-" + "relative paths.") + + # Relativize the path. + if module_relative: + package = _normalize_module(package) + path = _module_relative_path(package, path) + + # Find the file and read it. + name = os.path.basename(path) + doc = open(path).read() + + # Convert it to a test, and wrap it in a DocFileCase. + test = parser.get_doctest(doc, globs, name, path, 0) + return DocFileCase(test, **options) + +def DocFileSuite(*paths, **kw): + """A unittest suite for one or more doctest files. + + The path to each doctest file is given as a string; the + interpretation of that string depends on the keyword argument + "module_relative". + + A number of options may be provided as keyword arguments: + + module_relative + If "module_relative" is True, then the given file paths are + interpreted as os-independent module-relative paths. By + default, these paths are relative to the calling module's + directory; but if the "package" argument is specified, then + they are relative to that package. To ensure os-independence, + "filename" should use "/" characters to separate path + segments, and may not be an absolute path (i.e., it may not + begin with "/"). + + If "module_relative" is False, then the given file paths are + interpreted as os-specific paths. These paths may be absolute + or relative (to the current working directory). + + package + A Python package or the name of a Python package whose directory + should be used as the base directory for module relative paths. + If "package" is not specified, then the calling module's + directory is used as the base directory for module relative + filenames. It is an error to specify "package" if + "module_relative" is False. + + setUp + A set-up function. This is called before running the + tests in each file. The setUp function will be passed a DocTest + object. The setUp function can access the test globals as the + globs attribute of the test passed. + + tearDown + A tear-down function. This is called after running the + tests in each file. The tearDown function will be passed a DocTest + object. The tearDown function can access the test globals as the + globs attribute of the test passed. + + globs + A dictionary containing initial global variables for the tests. + + optionflags + A set of doctest option flags expressed as an integer. + + parser + A DocTestParser (or subclass) that should be used to extract + tests from the files. + """ + suite = unittest.TestSuite() + + # We do this here so that _normalize_module is called at the right + # level. If it were called in DocFileTest, then this function + # would be the caller and we might guess the package incorrectly. + if kw.get('module_relative', True): + kw['package'] = _normalize_module(kw.get('package')) + + for path in paths: + suite.addTest(DocFileTest(path, **kw)) + + return suite + +###################################################################### +## 9. Debugging Support +###################################################################### + +def script_from_examples(s): + output = [] + for piece in DocTestParser().parse(s): + if isinstance(piece, Example): + # Add the example's source code (strip trailing NL) + output.append(piece.source[:-1]) + # Add the expected output: + want = piece.want + if want: + output.append('# Expected:') + output += ['## '+l for l in want.split('\n')[:-1]] + else: + # Add non-example text. + output += [_comment_line(l) + for l in piece.split('\n')[:-1]] + + # Trim junk on both ends. + while output and output[-1] == '#': + output.pop() + while output and output[0] == '#': + output.pop(0) + # Combine the output, and return it. + # Add a courtesy newline to prevent exec from choking (see bug #1172785) + return '\n'.join(output) + '\n' + +def testsource(module, name): + """Extract the test sources from a doctest docstring as a script. + + Provide the module (or dotted name of the module) containing the + test to be debugged and the name (within the module) of the object + with the doc string with tests to be debugged. + """ + module = _normalize_module(module) + tests = DocTestFinder().find(module) + test = [t for t in tests if t.name == name] + if not test: + raise ValueError(name, "not found in tests") + test = test[0] + testsrc = script_from_examples(test.docstring) + return testsrc + +def debug_src(src, pm=False, globs=None): + """Debug a single doctest docstring, in argument `src`'""" + testsrc = script_from_examples(src) + debug_script(testsrc, pm, globs) + +def debug_script(src, pm=False, globs=None): + "Debug a test script. `src` is the script, as a string." + import pdb + + # Note that tempfile.NameTemporaryFile() cannot be used. As the + # docs say, a file so created cannot be opened by name a second time + # on modern Windows boxes, and execfile() needs to open it. + srcfilename = tempfile.mktemp(".py", "doctestdebug") + f = open(srcfilename, 'w') + f.write(src) + f.close() + + try: + if globs: + globs = globs.copy() + else: + globs = {} + + if pm: + try: + execfile(srcfilename, globs, globs) + except: + print sys.exc_info()[1] + pdb.post_mortem(sys.exc_info()[2]) + else: + # Note that %r is vital here. '%s' instead can, e.g., cause + # backslashes to get treated as metacharacters on Windows. + pdb.run("execfile(%r)" % srcfilename, globs, globs) + + finally: + os.remove(srcfilename) + +def debug(module, name, pm=False): + """Debug a single doctest docstring. + + Provide the module (or dotted name of the module) containing the + test to be debugged and the name (within the module) of the object + with the docstring with tests to be debugged. + """ + module = _normalize_module(module) + testsrc = testsource(module, name) + debug_script(testsrc, pm, module.__dict__) + + +__test__ = {} diff --git a/nose/failure.py b/nose/failure.py new file mode 100644 index 0000000..1dff970 --- /dev/null +++ b/nose/failure.py @@ -0,0 +1,39 @@ +import logging +import unittest +from traceback import format_tb + +log = logging.getLogger(__name__) + + +__all__ = ['Failure'] + + +class Failure(unittest.TestCase): + """Unloadable or unexecutable test. + + A Failure case is placed in a test suite to indicate the presence of a + test that could not be loaded or executed. A common example is a test + module that fails to import. + + """ + __test__ = False # do not collect + def __init__(self, exc_class, exc_val, tb=None, address=None): + log.debug("A failure! %s %s %s", exc_class, exc_val, format_tb(tb)) + self.exc_class = exc_class + self.exc_val = exc_val + self.tb = tb + self._address = address + unittest.TestCase.__init__(self) + + def __str__(self): + return "Failure: %s (%s)" % ( + getattr(self.exc_class, '__name__', self.exc_class), self.exc_val) + + def address(self): + return self._address + + def runTest(self): + if self.tb is not None: + raise self.exc_class, self.exc_val, self.tb + else: + raise self.exc_class(self.exc_val) diff --git a/nose/importer.py b/nose/importer.py new file mode 100644 index 0000000..c971b79 --- /dev/null +++ b/nose/importer.py @@ -0,0 +1,154 @@ +"""Implements an importer that looks only in specific path (ignoring +sys.path), and uses a per-path cache in addition to sys.modules. This is +necessary because test modules in different directories frequently have the +same names, which means that the first loaded would mask the rest when using +the builtin importer. +""" +import logging +import os +import sys +from nose.config import Config + +from imp import find_module, load_module, acquire_lock, release_lock + +log = logging.getLogger(__name__) + +class Importer(object): + """An importer class that does only path-specific imports. That + is, the given module is not searched for on sys.path, but only at + the path or in the directory specified. + """ + def __init__(self, config=None): + if config is None: + config = Config() + self.config = config + + def importFromPath(self, path, fqname): + """Import a dotted-name package whose tail is at path. In other words, + given foo.bar and path/to/foo/bar.py, import foo from path/to/foo then + bar from path/to/foo/bar, returning bar. + """ + # find the base dir of the package + path_parts = os.path.normpath(os.path.abspath(path)).split(os.sep) + name_parts = fqname.split('.') + if path_parts[-1].startswith('__init__'): + path_parts.pop() + path_parts = path_parts[:-(len(name_parts))] + dir_path = os.sep.join(path_parts) + # then import fqname starting from that dir + return self.importFromDir(dir_path, fqname) + + def importFromDir(self, dir, fqname): + """Import a module *only* from path, ignoring sys.path and + reloading if the version in sys.modules is not the one we want. + """ + dir = os.path.normpath(os.path.abspath(dir)) + log.debug("Import %s from %s", fqname, dir) + + # FIXME reimplement local per-dir cache? + + # special case for __main__ + if fqname == '__main__': + return sys.modules[fqname] + + if self.config.addPaths: + add_path(dir, self.config) + + path = [dir] + parts = fqname.split('.') + part_fqname = '' + mod = parent = fh = None + + for part in parts: + if part_fqname == '': + part_fqname = part + else: + part_fqname = "%s.%s" % (part_fqname, part) + try: + acquire_lock() + log.debug("find module part %s (%s) in %s", + part, part_fqname, path) + fh, filename, desc = find_module(part, path) + old = sys.modules.get(part_fqname) + if old is not None: + # test modules frequently have name overlap; make sure + # we get a fresh copy of anything we are trying to load + # from a new path + log.debug("sys.modules has %s as %s", part_fqname, old) + if (self.sameModule(old, filename) + or (self.config.firstPackageWins and + getattr(old, '__path__', None))): + mod = old + else: + del sys.modules[part_fqname] + mod = load_module(part_fqname, fh, filename, desc) + else: + mod = load_module(part_fqname, fh, filename, desc) + finally: + if fh: + fh.close() + release_lock() + if parent: + setattr(parent, part, mod) + if hasattr(mod, '__path__'): + path = mod.__path__ + parent = mod + return mod + + def sameModule(self, mod, filename): + mod_paths = [] + if hasattr(mod, '__path__'): + for path in mod.__path__: + mod_paths.append(os.path.dirname( + os.path.normpath( + os.path.abspath(path)))) + elif hasattr(mod, '__file__'): + mod_paths.append(os.path.dirname( + os.path.normpath( + os.path.abspath(mod.__file__)))) + else: + # builtin or other module-like object that + # doesn't have __file__; must be new + return False + new_path = os.path.dirname(os.path.normpath(filename)) + for mod_path in mod_paths: + log.debug( + "module already loaded? mod: %s new: %s", + mod_path, new_path) + if mod_path == new_path: + return True + return False + + +def add_path(path, config=None): + """Ensure that the path, or the root of the current package (if + path is in a package), is in sys.path. + """ + + # FIXME add any src-looking dirs seen too... need to get config for that + + log.debug('Add path %s' % path) + if not path: + return [] + added = [] + parent = os.path.dirname(path) + if (parent + and os.path.exists(os.path.join(path, '__init__.py'))): + added.extend(add_path(parent, config)) + elif not path in sys.path: + log.debug("insert %s into sys.path", path) + sys.path.insert(0, path) + added.append(path) + if config and config.srcDirs: + for dirname in config.srcDirs: + dirpath = os.path.join(path, dirname) + if os.path.isdir(dirpath): + sys.path.insert(0, dirpath) + added.append(dirpath) + return added + + +def remove_path(path): + log.debug('Remove path %s' % path) + if path in sys.path: + sys.path.remove(path) diff --git a/nose/inspector.py b/nose/inspector.py new file mode 100644 index 0000000..a6c4a3e --- /dev/null +++ b/nose/inspector.py @@ -0,0 +1,207 @@ +"""Simple traceback introspection. Used to add additional information to +AssertionErrors in tests, so that failure messages may be more informative. +""" +import inspect +import logging +import re +import sys +import textwrap +import tokenize + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +log = logging.getLogger(__name__) + +def inspect_traceback(tb): + """Inspect a traceback and its frame, returning source for the expression + where the exception was raised, with simple variable replacement performed + and the line on which the exception was raised marked with '>>' + """ + log.debug('inspect traceback %s', tb) + + # we only want the innermost frame, where the exception was raised + while tb.tb_next: + tb = tb.tb_next + + frame = tb.tb_frame + lines, exc_line = tbsource(tb) + + # figure out the set of lines to grab. + inspect_lines, mark_line = find_inspectable_lines(lines, exc_line) + src = StringIO(textwrap.dedent(''.join(inspect_lines))) + exp = Expander(frame.f_locals, frame.f_globals) + + while inspect_lines: + try: + for tok in tokenize.generate_tokens(src.readline): + exp(*tok) + except tokenize.TokenError, e: + # this can happen if our inspectable region happens to butt up + # against the end of a construct like a docstring with the closing + # """ on separate line + log.debug("Tokenizer error: %s", e) + inspect_lines.pop(0) + mark_line -= 1 + src = StringIO(textwrap.dedent(''.join(inspect_lines))) + exp = Expander(frame.f_locals, frame.f_globals) + continue + break + padded = [] + if exp.expanded_source: + exp_lines = exp.expanded_source.split('\n') + ep = 0 + for line in exp_lines: + if ep == mark_line: + padded.append('>> ' + line) + else: + padded.append(' ' + line) + ep += 1 + return '\n'.join(padded) + + +def tbsource(tb, context=6): + """Get source from a traceback object. + + A tuple of two things is returned: a list of lines of context from + the source code, and the index of the current line within that list. + The optional second argument specifies the number of lines of context + to return, which are centered around the current line. + + .. Note :: + This is adapted from inspect.py in the python 2.4 standard library, + since a bug in the 2.3 version of inspect prevents it from correctly + locating source lines in a traceback frame. + """ + + lineno = tb.tb_lineno + frame = tb.tb_frame + + if context > 0: + start = lineno - 1 - context//2 + log.debug("lineno: %s start: %s", lineno, start) + + try: + lines, dummy = inspect.findsource(frame) + except IOError: + lines, index = [''], 0 + else: + all_lines = lines + start = max(start, 1) + start = max(0, min(start, len(lines) - context)) + lines = lines[start:start+context] + index = lineno - 1 - start + + # python 2.5 compat: if previous line ends in a continuation, + # decrement start by 1 to match 2.4 behavior + if sys.version_info >= (2, 5) and index > 0: + while lines[index-1].strip().endswith('\\'): + start -= 1 + lines = all_lines[start:start+context] + else: + lines, index = [''], 0 + log.debug("tbsource lines '''%s''' around index %s", lines, index) + return (lines, index) + + +def find_inspectable_lines(lines, pos): + """Find lines in home that are inspectable. + + Walk back from the err line up to 3 lines, but don't walk back over + changes in indent level. + + Walk forward up to 3 lines, counting \ separated lines as 1. Don't walk + over changes in indent level (unless part of an extended line) + """ + cnt = re.compile(r'\\[\s\n]*$') + df = re.compile(r':[\s\n]*$') + ind = re.compile(r'^(\s*)') + toinspect = [] + home = lines[pos] + home_indent = ind.match(home).groups()[0] + + before = lines[max(pos-3, 0):pos] + before.reverse() + after = lines[pos+1:min(pos+4, len(lines))] + + for line in before: + if ind.match(line).groups()[0] == home_indent: + toinspect.append(line) + else: + break + toinspect.reverse() + toinspect.append(home) + home_pos = len(toinspect)-1 + continued = cnt.search(home) + for line in after: + if ((continued or ind.match(line).groups()[0] == home_indent) + and not df.search(line)): + toinspect.append(line) + continued = cnt.search(line) + else: + break + log.debug("Inspecting lines '''%s''' around %s", toinspect, home_pos) + return toinspect, home_pos + + +class Expander: + """Simple expression expander. Uses tokenize to find the names and + expands any that can be looked up in the frame. + """ + def __init__(self, locals, globals): + self.locals = locals + self.globals = globals + self.lpos = None + self.expanded_source = '' + + def __call__(self, ttype, tok, start, end, line): + # TODO + # deal with unicode properly + + # TODO + # Dealing with instance members + # always keep the last thing seen + # if the current token is a dot, + # get ready to getattr(lastthing, this thing) on the + # next call. + + if self.lpos is not None: + if start[1] >= self.lpos: + self.expanded_source += ' ' * (start[1]-self.lpos) + elif start[1] < self.lpos: + # newline, indent correctly + self.expanded_source += ' ' * start[1] + self.lpos = end[1] + + if ttype == tokenize.INDENT: + pass + elif ttype == tokenize.NAME: + # Clean this junk up + try: + val = self.locals[tok] + if callable(val): + val = tok + else: + val = repr(val) + except KeyError: + try: + val = self.globals[tok] + if callable(val): + val = tok + else: + val = repr(val) + + except KeyError: + val = tok + # FIXME... not sure how to handle things like funcs, classes + # FIXME this is broken for some unicode strings + self.expanded_source += val + else: + self.expanded_source += tok + # if this is the end of the line and the line ends with + # \, then tack a \ and newline onto the output + # print line[end[1]:] + if re.match(r'\s+\\\n', line[end[1]:]): + self.expanded_source += ' \\\n' diff --git a/nose/loader.py b/nose/loader.py new file mode 100644 index 0000000..1103099 --- /dev/null +++ b/nose/loader.py @@ -0,0 +1,595 @@ +""" +Test Loader +----------- + +nose's test loader implements the same basic functionality as its +superclass, unittest.TestLoader, but extends it by more liberal +interpretations of what may be a test and how a test may be named. +""" +from __future__ import generators + +import logging +import os +import sys +import unittest +import types +from inspect import isfunction +from nose.pyversion import unbound_method, ismethod +from nose.case import FunctionTestCase, MethodTestCase +from nose.failure import Failure +from nose.config import Config +from nose.importer import Importer, add_path, remove_path +from nose.selector import defaultSelector, TestAddress +from nose.util import func_lineno, getpackage, isclass, isgenerator, \ + ispackage, regex_last_key, resolve_name, transplant_func, \ + transplant_class, test_address +from nose.suite import ContextSuiteFactory, ContextList, LazySuite +from nose.pyversion import sort_list, cmp_to_key + + +log = logging.getLogger(__name__) +#log.setLevel(logging.DEBUG) + +# for efficiency and easier mocking +op_normpath = os.path.normpath +op_abspath = os.path.abspath +op_join = os.path.join +op_isdir = os.path.isdir +op_isfile = os.path.isfile + + +__all__ = ['TestLoader', 'defaultTestLoader'] + + +class TestLoader(unittest.TestLoader): + """Test loader that extends unittest.TestLoader to: + + * Load tests from test-like functions and classes that are not + unittest.TestCase subclasses + * Find and load test modules in a directory + * Support tests that are generators + * Support easy extensions of or changes to that behavior through plugins + """ + config = None + importer = None + workingDir = None + selector = None + suiteClass = None + + def __init__(self, config=None, importer=None, workingDir=None, + selector=None): + """Initialize a test loader. + + Parameters (all optional): + + * config: provide a `nose.config.Config`_ or other config class + instance; if not provided a `nose.config.Config`_ with + default values is used. + * importer: provide an importer instance that implements + `importFromPath`. If not provided, a + `nose.importer.Importer`_ is used. + * workingDir: the directory to which file and module names are + relative. If not provided, assumed to be the current working + directory. + * selector: a selector class or instance. If a class is + provided, it will be instantiated with one argument, the + current config. If not provided, a `nose.selector.Selector`_ + is used. + """ + if config is None: + config = Config() + if importer is None: + importer = Importer(config=config) + if workingDir is None: + workingDir = config.workingDir + if selector is None: + selector = defaultSelector(config) + elif isclass(selector): + selector = selector(config) + self.config = config + self.importer = importer + self.workingDir = op_normpath(op_abspath(workingDir)) + self.selector = selector + if config.addPaths: + add_path(workingDir, config) + self.suiteClass = ContextSuiteFactory(config=config) + unittest.TestLoader.__init__(self) + + def getTestCaseNames(self, testCaseClass): + """Override to select with selector, unless + config.getTestCaseNamesCompat is True + """ + if self.config.getTestCaseNamesCompat: + return unittest.TestLoader.getTestCaseNames(self, testCaseClass) + + def wanted(attr, cls=testCaseClass, sel=self.selector): + item = getattr(cls, attr, None) + if isfunction(item): + item = unbound_method(cls, item) + elif not ismethod(item): + return False + return sel.wantMethod(item) + cases = filter(wanted, dir(testCaseClass)) + for base in testCaseClass.__bases__: + for case in self.getTestCaseNames(base): + if case not in cases: + cases.append(case) + # add runTest if nothing else picked + if not cases and hasattr(testCaseClass, 'runTest'): + cases = ['runTest'] + if self.sortTestMethodsUsing: + sort_list(cases, cmp_to_key(self.sortTestMethodsUsing)) + return cases + + def loadTestsFromDir(self, path): + """Load tests from the directory at path. This is a generator + -- each suite of tests from a module or other file is yielded + and is expected to be executed before the next file is + examined. + """ + log.debug("load from dir %s", path) + plugins = self.config.plugins + plugins.beforeDirectory(path) + if self.config.addPaths: + paths_added = add_path(path, self.config) + + entries = os.listdir(path) + sort_list(entries, regex_last_key(self.config.testMatch)) + for entry in entries: + # this hard-coded initial-dot test will be removed: + # http://code.google.com/p/python-nose/issues/detail?id=82 + if entry.startswith('.'): + continue + entry_path = op_abspath(op_join(path, entry)) + is_file = op_isfile(entry_path) + wanted = False + if is_file: + is_dir = False + wanted = self.selector.wantFile(entry_path) + else: + is_dir = op_isdir(entry_path) + if is_dir: + # this hard-coded initial-underscore test will be removed: + # http://code.google.com/p/python-nose/issues/detail?id=82 + if entry.startswith('_'): + continue + wanted = self.selector.wantDirectory(entry_path) + is_package = ispackage(entry_path) + if wanted: + if is_file: + plugins.beforeContext() + if entry.endswith('.py'): + yield self.loadTestsFromName( + entry_path, discovered=True) + else: + yield self.loadTestsFromFile(entry_path) + plugins.afterContext() + elif is_package: + # Load the entry as a package: given the full path, + # loadTestsFromName() will figure it out + yield self.loadTestsFromName( + entry_path, discovered=True) + else: + # Another test dir in this one: recurse lazily + yield self.suiteClass( + lambda: self.loadTestsFromDir(entry_path)) + tests = [] + for test in plugins.loadTestsFromDir(path): + tests.append(test) + # TODO: is this try/except needed? + try: + if tests: + yield self.suiteClass(tests) + except (KeyboardInterrupt, SystemExit): + raise + except: + yield self.suiteClass([Failure(*sys.exc_info())]) + + # pop paths + if self.config.addPaths: + for p in paths_added: + remove_path(p) + plugins.afterDirectory(path) + + def loadTestsFromFile(self, filename): + """Load tests from a non-module file. Default is to raise a + ValueError; plugins may implement `loadTestsFromFile` to + provide a list of tests loaded from the file. + """ + log.debug("Load from non-module file %s", filename) + try: + tests = [test for test in + self.config.plugins.loadTestsFromFile(filename)] + if tests: + # Plugins can yield False to indicate that they were + # unable to load tests from a file, but it was not an + # error -- the file just had no tests to load. + tests = filter(None, tests) + return self.suiteClass(tests) + else: + # Nothing was able to even try to load from this file + open(filename, 'r').close() # trigger os error + raise ValueError("Unable to load tests from file %s" + % filename) + except (KeyboardInterrupt, SystemExit): + raise + except: + exc = sys.exc_info() + return self.suiteClass( + [Failure(exc[0], exc[1], exc[2], + address=(filename, None, None))]) + + def loadTestsFromGenerator(self, generator, module): + """Lazy-load tests from a generator function. The generator function + may yield either: + + * a callable, or + * a function name resolvable within the same module + """ + def generate(g=generator, m=module): + try: + for test in g(): + test_func, arg = self.parseGeneratedTest(test) + if not callable(test_func): + test_func = getattr(m, test_func) + yield FunctionTestCase(test_func, arg=arg, descriptor=g) + except KeyboardInterrupt: + raise + except: + exc = sys.exc_info() + yield Failure(exc[0], exc[1], exc[2], + address=test_address(generator)) + return self.suiteClass(generate, context=generator, can_split=False) + + def loadTestsFromGeneratorMethod(self, generator, cls): + """Lazy-load tests from a generator method. + + This is more complicated than loading from a generator function, + since a generator method may yield: + + * a function + * a bound or unbound method, or + * a method name + """ + # convert the unbound generator method + # into a bound method so it can be called below + if hasattr(generator, 'im_class'): + cls = generator.im_class + inst = cls() + method = generator.__name__ + generator = getattr(inst, method) + + def generate(g=generator, c=cls): + try: + for test in g(): + test_func, arg = self.parseGeneratedTest(test) + if not callable(test_func): + test_func = unbound_method(c, getattr(c, test_func)) + if ismethod(test_func): + yield MethodTestCase(test_func, arg=arg, descriptor=g) + elif isfunction(test_func): + # In this case we're forcing the 'MethodTestCase' + # to run the inline function as its test call, + # but using the generator method as the 'method of + # record' (so no need to pass it as the descriptor) + yield MethodTestCase(g, test=test_func, arg=arg) + else: + yield Failure( + TypeError, + "%s is not a function or method" % test_func) + except KeyboardInterrupt: + raise + except: + exc = sys.exc_info() + yield Failure(exc[0], exc[1], exc[2], + address=test_address(generator)) + return self.suiteClass(generate, context=generator, can_split=False) + + def loadTestsFromModule(self, module, path=None, discovered=False): + """Load all tests from module and return a suite containing + them. If the module has been discovered and is not test-like, + the suite will be empty by default, though plugins may add + their own tests. + """ + log.debug("Load from module %s", module) + tests = [] + test_classes = [] + test_funcs = [] + # For *discovered* modules, we only load tests when the module looks + # testlike. For modules we've been directed to load, we always + # look for tests. (discovered is set to True by loadTestsFromDir) + if not discovered or self.selector.wantModule(module): + for item in dir(module): + test = getattr(module, item, None) + # print "Check %s (%s) in %s" % (item, test, module.__name__) + if isclass(test): + if self.selector.wantClass(test): + test_classes.append(test) + elif isfunction(test) and self.selector.wantFunction(test): + test_funcs.append(test) + sort_list(test_classes, lambda x: x.__name__) + sort_list(test_funcs, func_lineno) + tests = map(lambda t: self.makeTest(t, parent=module), + test_classes + test_funcs) + + # Now, descend into packages + # FIXME can or should this be lazy? + # is this syntax 2.2 compatible? + module_paths = getattr(module, '__path__', []) + if path: + path = os.path.realpath(path) + for module_path in module_paths: + log.debug("Load tests from module path %s?", module_path) + log.debug("path: %s os.path.realpath(%s): %s", + path, module_path, os.path.realpath(module_path)) + if (self.config.traverseNamespace or not path) or \ + os.path.realpath(module_path).startswith(path): + tests.extend(self.loadTestsFromDir(module_path)) + + for test in self.config.plugins.loadTestsFromModule(module, path): + tests.append(test) + + return self.suiteClass(ContextList(tests, context=module)) + + def loadTestsFromName(self, name, module=None, discovered=False): + """Load tests from the entity with the given name. + + The name may indicate a file, directory, module, or any object + within a module. See `nose.util.split_test_name` for details on + test name parsing. + """ + # FIXME refactor this method into little bites? + log.debug("load from %s (%s)", name, module) + + suite = self.suiteClass + + # give plugins first crack + plug_tests = self.config.plugins.loadTestsFromName(name, module) + if plug_tests: + return suite(plug_tests) + + addr = TestAddress(name, workingDir=self.workingDir) + if module: + # Two cases: + # name is class.foo + # The addr will be incorrect, since it thinks class.foo is + # a dotted module name. It's actually a dotted attribute + # name. In this case we want to use the full submitted + # name as the name to load from the module. + # name is module:class.foo + # The addr will be correct. The part we want is the part after + # the :, which is in addr.call. + if addr.call: + name = addr.call + parent, obj = self.resolve(name, module) + if (isclass(parent) + and getattr(parent, '__module__', None) != module.__name__): + parent = transplant_class(parent, module.__name__) + obj = getattr(parent, obj.__name__) + log.debug("parent %s obj %s module %s", parent, obj, module) + if isinstance(obj, Failure): + return suite([obj]) + else: + return suite(ContextList([self.makeTest(obj, parent)], + context=parent)) + else: + if addr.module: + try: + if addr.filename is None: + module = resolve_name(addr.module) + else: + self.config.plugins.beforeImport( + addr.filename, addr.module) + # FIXME: to support module.name names, + # do what resolve-name does and keep trying to + # import, popping tail of module into addr.call, + # until we either get an import or run out of + # module parts + try: + module = self.importer.importFromPath( + addr.filename, addr.module) + finally: + self.config.plugins.afterImport( + addr.filename, addr.module) + except (KeyboardInterrupt, SystemExit): + raise + except: + exc = sys.exc_info() + return suite([Failure(exc[0], exc[1], exc[2], + address=addr.totuple())]) + if addr.call: + return self.loadTestsFromName(addr.call, module) + else: + return self.loadTestsFromModule( + module, addr.filename, + discovered=discovered) + elif addr.filename: + path = addr.filename + if addr.call: + package = getpackage(path) + if package is None: + return suite([ + Failure(ValueError, + "Can't find callable %s in file %s: " + "file is not a python module" % + (addr.call, path), + address=addr.totuple())]) + return self.loadTestsFromName(addr.call, module=package) + else: + if op_isdir(path): + # In this case we *can* be lazy since we know + # that each module in the dir will be fully + # loaded before its tests are executed; we + # also know that we're not going to be asked + # to load from . and ./some_module.py *as part + # of this named test load* + return LazySuite( + lambda: self.loadTestsFromDir(path)) + elif op_isfile(path): + return self.loadTestsFromFile(path) + else: + return suite([ + Failure(OSError, "No such file %s" % path, + address=addr.totuple())]) + else: + # just a function? what to do? I think it can only be + # handled when module is not None + return suite([ + Failure(ValueError, "Unresolvable test name %s" % name, + address=addr.totuple())]) + + def loadTestsFromNames(self, names, module=None): + """Load tests from all names, returning a suite containing all + tests. + """ + plug_res = self.config.plugins.loadTestsFromNames(names, module) + if plug_res: + suite, names = plug_res + if suite: + return self.suiteClass([ + self.suiteClass(suite), + unittest.TestLoader.loadTestsFromNames(self, names, module) + ]) + return unittest.TestLoader.loadTestsFromNames(self, names, module) + + def loadTestsFromTestCase(self, testCaseClass): + """Load tests from a unittest.TestCase subclass. + """ + cases = [] + plugins = self.config.plugins + for case in plugins.loadTestsFromTestCase(testCaseClass): + cases.append(case) + # For efficiency in the most common case, just call and return from + # super. This avoids having to extract cases and rebuild a context + # suite when there are no plugin-contributed cases. + if not cases: + return super(TestLoader, self).loadTestsFromTestCase(testCaseClass) + cases.extend( + [case for case in + super(TestLoader, self).loadTestsFromTestCase(testCaseClass)]) + return self.suiteClass(cases) + + def loadTestsFromTestClass(self, cls): + """Load tests from a test class that is *not* a unittest.TestCase + subclass. + + In this case, we can't depend on the class's `__init__` taking method + name arguments, so we have to compose a MethodTestCase for each + method in the class that looks testlike. + """ + def wanted(attr, cls=cls, sel=self.selector): + item = getattr(cls, attr, None) + if isfunction(item): + item = unbound_method(cls, item) + elif not ismethod(item): + return False + return sel.wantMethod(item) + cases = [self.makeTest(getattr(cls, case), cls) + for case in filter(wanted, dir(cls))] + for test in self.config.plugins.loadTestsFromTestClass(cls): + cases.append(test) + return self.suiteClass(ContextList(cases, context=cls)) + + def makeTest(self, obj, parent=None): + try: + return self._makeTest(obj, parent) + except (KeyboardInterrupt, SystemExit): + raise + except: + exc = sys.exc_info() + try: + addr = test_address(obj) + except KeyboardInterrupt: + raise + except: + addr = None + return Failure(exc[0], exc[1], exc[2], address=addr) + + def _makeTest(self, obj, parent=None): + """Given a test object and its parent, return a test case + or test suite. + """ + plug_tests = [] + try: + addr = test_address(obj) + except KeyboardInterrupt: + raise + except: + addr = None + for test in self.config.plugins.makeTest(obj, parent): + plug_tests.append(test) + # TODO: is this try/except needed? + try: + if plug_tests: + return self.suiteClass(plug_tests) + except (KeyboardInterrupt, SystemExit): + raise + except: + exc = sys.exc_info() + return Failure(exc[0], exc[1], exc[2], address=addr) + + if isfunction(obj) and parent and not isinstance(parent, types.ModuleType): + # This is a Python 3.x 'unbound method'. Wrap it with its + # associated class.. + obj = unbound_method(parent, obj) + + if isinstance(obj, unittest.TestCase): + return obj + elif isclass(obj): + if parent and obj.__module__ != parent.__name__: + obj = transplant_class(obj, parent.__name__) + if issubclass(obj, unittest.TestCase): + return self.loadTestsFromTestCase(obj) + else: + return self.loadTestsFromTestClass(obj) + elif ismethod(obj): + if parent is None: + parent = obj.__class__ + if issubclass(parent, unittest.TestCase): + return parent(obj.__name__) + else: + if isgenerator(obj): + return self.loadTestsFromGeneratorMethod(obj, parent) + else: + return MethodTestCase(obj) + elif isfunction(obj): + if parent and obj.__module__ != parent.__name__: + obj = transplant_func(obj, parent.__name__) + if isgenerator(obj): + return self.loadTestsFromGenerator(obj, parent) + else: + return FunctionTestCase(obj) + else: + return Failure(TypeError, + "Can't make a test from %s" % obj, + address=addr) + + def resolve(self, name, module): + """Resolve name within module + """ + obj = module + parts = name.split('.') + for part in parts: + parent, obj = obj, getattr(obj, part, None) + if obj is None: + # no such test + obj = Failure(ValueError, "No such test %s" % name) + return parent, obj + + def parseGeneratedTest(self, test): + """Given the yield value of a test generator, return a func and args. + + This is used in the two loadTestsFromGenerator* methods. + + """ + if not isinstance(test, tuple): # yield test + test_func, arg = (test, tuple()) + elif len(test) == 1: # yield (test,) + test_func, arg = (test[0], tuple()) + else: # yield test, foo, bar, ... + assert len(test) > 1 # sanity check + test_func, arg = (test[0], test[1:]) + return test_func, arg + +defaultTestLoader = TestLoader + diff --git a/nose/plugins/__init__.py b/nose/plugins/__init__.py new file mode 100644 index 0000000..260b628 --- /dev/null +++ b/nose/plugins/__init__.py @@ -0,0 +1,190 @@ +""" +Writing Plugins +--------------- + +nose supports plugins for test collection, selection, observation and +reporting. There are two basic rules for plugins: + +* Plugin classes should subclass :class:`nose.plugins.Plugin`. + +* Plugins may implement any of the methods described in the class + :doc:`IPluginInterface ` in nose.plugins.base. Please note that + this class is for documentary purposes only; plugins may not subclass + IPluginInterface. + +Hello World +=========== + +Here's a basic plugin. It doesn't do much so read on for more ideas or dive +into the :doc:`IPluginInterface ` to see all available hooks. + +.. code-block:: python + + import logging + import os + + from nose.plugins import Plugin + + log = logging.getLogger('nose.plugins.helloworld') + + class HelloWorld(Plugin): + name = 'helloworld' + + def options(self, parser, env=os.environ): + super(HelloWorld, self).options(parser, env=env) + + def configure(self, options, conf): + super(HelloWorld, self).configure(options, conf) + if not self.enabled: + return + + def finalize(self, result): + log.info('Hello pluginized world!') + +Registering +=========== + +.. Note:: + Important note: the following applies only to the default + plugin manager. Other plugin managers may use different means to + locate and load plugins. + +For nose to find a plugin, it must be part of a package that uses +setuptools_, and the plugin must be included in the entry points defined +in the setup.py for the package: + +.. code-block:: python + + setup(name='Some plugin', + # ... + entry_points = { + 'nose.plugins.0.10': [ + 'someplugin = someplugin:SomePlugin' + ] + }, + # ... + ) + +Once the package is installed with install or develop, nose will be able +to load the plugin. + +.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools + +Registering a plugin without setuptools +======================================= + +It is currently possible to register a plugin programmatically by +creating a custom nose runner like this : + +.. code-block:: python + + import nose + from yourplugin import YourPlugin + + if __name__ == '__main__': + nose.main(addplugins=[YourPlugin()]) + +Defining options +================ + +All plugins must implement the methods ``options(self, parser, env)`` +and ``configure(self, options, conf)``. Subclasses of nose.plugins.Plugin +that want the standard options should call the superclass methods. + +nose uses optparse.OptionParser from the standard library to parse +arguments. A plugin's ``options()`` method receives a parser +instance. It's good form for a plugin to use that instance only to add +additional arguments that take only long arguments (--like-this). Most +of nose's built-in arguments get their default value from an environment +variable. + +A plugin's ``configure()`` method receives the parsed ``OptionParser`` options +object, as well as the current config object. Plugins should configure their +behavior based on the user-selected settings, and may raise exceptions +if the configured behavior is nonsensical. + +Logging +======= + +nose uses the logging classes from the standard library. To enable users +to view debug messages easily, plugins should use ``logging.getLogger()`` to +acquire a logger in the ``nose.plugins`` namespace. + +Recipes +======= + +* Writing a plugin that monitors or controls test result output + + Implement any or all of ``addError``, ``addFailure``, etc., to monitor test + results. If you also want to monitor output, implement + ``setOutputStream`` and keep a reference to the output stream. If you + want to prevent the builtin ``TextTestResult`` output, implement + ``setOutputSteam`` and *return a dummy stream*. The default output will go + to the dummy stream, while you send your desired output to the real stream. + + Example: `examples/html_plugin/htmlplug.py`_ + +* Writing a plugin that handles exceptions + + Subclass :doc:`ErrorClassPlugin `. + + Examples: :doc:`nose.plugins.deprecated `, + :doc:`nose.plugins.skip ` + +* Writing a plugin that adds detail to error reports + + Implement ``formatError`` and/or ``formatFailture``. The error tuple + you return (error class, error message, traceback) will replace the + original error tuple. + + Examples: :doc:`nose.plugins.capture `, + :doc:`nose.plugins.failuredetail ` + +* Writing a plugin that loads tests from files other than python modules + + Implement ``wantFile`` and ``loadTestsFromFile``. In ``wantFile``, + return True for files that you want to examine for tests. In + ``loadTestsFromFile``, for those files, return an iterable + containing TestCases (or yield them as you find them; + ``loadTestsFromFile`` may also be a generator). + + Example: :doc:`nose.plugins.doctests ` + +* Writing a plugin that prints a report + + Implement ``begin`` if you need to perform setup before testing + begins. Implement ``report`` and output your report to the provided stream. + + Examples: :doc:`nose.plugins.cover `, :doc:`nose.plugins.prof ` + +* Writing a plugin that selects or rejects tests + + Implement any or all ``want*`` methods. Return False to reject the test + candidate, True to accept it -- which means that the test candidate + will pass through the rest of the system, so you must be prepared to + load tests from it if tests can't be loaded by the core loader or + another plugin -- and None if you don't care. + + Examples: :doc:`nose.plugins.attrib `, + :doc:`nose.plugins.doctests `, :doc:`nose.plugins.testid ` + + +More Examples +============= + +See any builtin plugin or example plugin in the examples_ directory in +the nose source distribution. There is a list of third-party plugins +`on jottit`_. + +.. _examples/html_plugin/htmlplug.py: http://python-nose.googlecode.com/svn/trunk/examples/html_plugin/htmlplug.py +.. _examples: http://python-nose.googlecode.com/svn/trunk/examples +.. _on jottit: http://nose-plugins.jottit.com/ + +""" +from nose.plugins.base import Plugin +from nose.plugins.manager import * +from nose.plugins.plugintest import PluginTester + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/nose/plugins/allmodules.py b/nose/plugins/allmodules.py new file mode 100644 index 0000000..1ccd777 --- /dev/null +++ b/nose/plugins/allmodules.py @@ -0,0 +1,45 @@ +"""Use the AllModules plugin by passing ``--all-modules`` or setting the +NOSE_ALL_MODULES environment variable to enable collection and execution of +tests in all python modules. Normal nose behavior is to look for tests only in +modules that match testMatch. + +More information: :doc:`../doc_tests/test_allmodules/test_allmodules` + +.. warning :: + + This plugin can have surprising interactions with plugins that load tests + from what nose normally considers non-test modules, such as + the :doc:`doctest plugin `. This is because any given + object in a module can't be loaded both by a plugin and the normal nose + :class:`test loader `. Also, if you have functions + or classes in non-test modules that look like tests but aren't, you will + likely see errors as nose attempts to run them as tests. + +""" + +import os +from nose.plugins.base import Plugin + +class AllModules(Plugin): + """Collect tests from all python modules. + """ + def options(self, parser, env): + """Register commandline options. + """ + env_opt = 'NOSE_ALL_MODULES' + parser.add_option('--all-modules', + action="store_true", + dest=self.enableOpt, + default=env.get(env_opt), + help="Enable plugin %s: %s [%s]" % + (self.__class__.__name__, self.help(), env_opt)) + + def wantFile(self, file): + """Override to return True for all files ending with .py""" + # always want .py files + if file.endswith('.py'): + return True + + def wantModule(self, module): + """Override return True for all modules""" + return True diff --git a/nose/plugins/attrib.py b/nose/plugins/attrib.py new file mode 100644 index 0000000..3d4422a --- /dev/null +++ b/nose/plugins/attrib.py @@ -0,0 +1,286 @@ +"""Attribute selector plugin. + +Oftentimes when testing you will want to select tests based on +criteria rather then simply by filename. For example, you might want +to run all tests except for the slow ones. You can do this with the +Attribute selector plugin by setting attributes on your test methods. +Here is an example: + +.. code-block:: python + + def test_big_download(): + import urllib + # commence slowness... + + test_big_download.slow = 1 + +Once you've assigned an attribute ``slow = 1`` you can exclude that +test and all other tests having the slow attribute by running :: + + $ nosetests -a '!slow' + +There is also a decorator available for you that will set attributes. +Here's how to set ``slow=1`` like above with the decorator: + +.. code-block:: python + + from nose.plugins.attrib import attr + @attr('slow') + def test_big_download(): + import urllib + # commence slowness... + +And here's how to set an attribute with a specific value: + +.. code-block:: python + + from nose.plugins.attrib import attr + @attr(speed='slow') + def test_big_download(): + import urllib + # commence slowness... + +This test could be run with :: + + $ nosetests -a speed=slow + +In Python 2.6 and higher, ``@attr`` can be used on a class to set attributes +on all its test methods at once. For example: + +.. code-block:: python + + from nose.plugins.attrib import attr + @attr(speed='slow') + class MyTestCase: + def test_long_integration(self): + pass + def test_end_to_end_something(self): + pass + +Below is a reference to the different syntaxes available. + +Simple syntax +------------- + +Examples of using the ``-a`` and ``--attr`` options: + +* ``nosetests -a status=stable`` + Only runs tests with attribute "status" having value "stable" + +* ``nosetests -a priority=2,status=stable`` + Runs tests having both attributes and values + +* ``nosetests -a priority=2 -a slow`` + Runs tests that match either attribute + +* ``nosetests -a tags=http`` + If a test's ``tags`` attribute was a list and it contained the value + ``http`` then it would be run + +* ``nosetests -a slow`` + Runs tests with the attribute ``slow`` if its value does not equal False + (False, [], "", etc...) + +* ``nosetests -a '!slow'`` + Runs tests that do NOT have the attribute ``slow`` or have a ``slow`` + attribute that is equal to False + **NOTE**: + if your shell (like bash) interprets '!' as a special character make sure to + put single quotes around it. + +Expression Evaluation +--------------------- + +Examples using the ``-A`` and ``--eval-attr`` options: + +* ``nosetests -A "not slow"`` + Evaluates the Python expression "not slow" and runs the test if True + +* ``nosetests -A "(priority > 5) and not slow"`` + Evaluates a complex Python expression and runs the test if True + +""" +import inspect +import logging +import os +import sys +from inspect import isfunction +from nose.plugins.base import Plugin +from nose.util import tolist + +log = logging.getLogger('nose.plugins.attrib') +compat_24 = sys.version_info >= (2, 4) + +def attr(*args, **kwargs): + """Decorator that adds attributes to classes or functions + for use with the Attribute (-a) plugin. + """ + def wrap_ob(ob): + for name in args: + setattr(ob, name, True) + for name, value in kwargs.iteritems(): + setattr(ob, name, value) + return ob + return wrap_ob + +def get_method_attr(method, cls, attr_name, default = False): + """Look up an attribute on a method/ function. + If the attribute isn't found there, looking it up in the + method's class, if any. + """ + Missing = object() + value = getattr(method, attr_name, Missing) + if value is Missing and cls is not None: + value = getattr(cls, attr_name, Missing) + if value is Missing: + return default + return value + + +class ContextHelper: + """Object that can act as context dictionary for eval and looks up + names as attributes on a method/ function and its class. + """ + def __init__(self, method, cls): + self.method = method + self.cls = cls + + def __getitem__(self, name): + return get_method_attr(self.method, self.cls, name) + + +class AttributeSelector(Plugin): + """Selects test cases to be run based on their attributes. + """ + + def __init__(self): + Plugin.__init__(self) + self.attribs = [] + + def options(self, parser, env): + """Register command line options""" + parser.add_option("-a", "--attr", + dest="attr", action="append", + default=env.get('NOSE_ATTR'), + metavar="ATTR", + help="Run only tests that have attributes " + "specified by ATTR [NOSE_ATTR]") + # disable in < 2.4: eval can't take needed args + if compat_24: + parser.add_option("-A", "--eval-attr", + dest="eval_attr", metavar="EXPR", action="append", + default=env.get('NOSE_EVAL_ATTR'), + help="Run only tests for whose attributes " + "the Python expression EXPR evaluates " + "to True [NOSE_EVAL_ATTR]") + + def configure(self, options, config): + """Configure the plugin and system, based on selected options. + + attr and eval_attr may each be lists. + + self.attribs will be a list of lists of tuples. In that list, each + list is a group of attributes, all of which must match for the rule to + match. + """ + self.attribs = [] + + # handle python eval-expression parameter + if compat_24 and options.eval_attr: + eval_attr = tolist(options.eval_attr) + for attr in eval_attr: + # "" + # -> eval(expr) in attribute context must be True + def eval_in_context(expr, obj, cls): + return eval(expr, None, ContextHelper(obj, cls)) + self.attribs.append([(attr, eval_in_context)]) + + # attribute requirements are a comma separated list of + # 'key=value' pairs + if options.attr: + std_attr = tolist(options.attr) + for attr in std_attr: + # all attributes within an attribute group must match + attr_group = [] + for attrib in attr.strip().split(","): + # don't die on trailing comma + if not attrib: + continue + items = attrib.split("=", 1) + if len(items) > 1: + # "name=value" + # -> 'str(obj.name) == value' must be True + key, value = items + else: + key = items[0] + if key[0] == "!": + # "!name" + # 'bool(obj.name)' must be False + key = key[1:] + value = False + else: + # "name" + # -> 'bool(obj.name)' must be True + value = True + attr_group.append((key, value)) + self.attribs.append(attr_group) + if self.attribs: + self.enabled = True + + def validateAttrib(self, method, cls = None): + """Verify whether a method has the required attributes + The method is considered a match if it matches all attributes + for any attribute group. + .""" + # TODO: is there a need for case-sensitive value comparison? + any = False + for group in self.attribs: + match = True + for key, value in group: + attr = get_method_attr(method, cls, key) + if callable(value): + if not value(key, method, cls): + match = False + break + elif value is True: + # value must exist and be True + if not bool(attr): + match = False + break + elif value is False: + # value must not exist or be False + if bool(attr): + match = False + break + elif type(attr) in (list, tuple): + # value must be found in the list attribute + if not str(value).lower() in [str(x).lower() + for x in attr]: + match = False + break + else: + # value must match, convert to string and compare + if (value != attr + and str(value).lower() != str(attr).lower()): + match = False + break + any = any or match + if any: + # not True because we don't want to FORCE the selection of the + # item, only say that it is acceptable + return None + return False + + def wantFunction(self, function): + """Accept the function if its attributes match. + """ + return self.validateAttrib(function) + + def wantMethod(self, method): + """Accept the method if its attributes match. + """ + try: + cls = method.im_class + except AttributeError: + return False + return self.validateAttrib(method, cls) diff --git a/nose/plugins/base.py b/nose/plugins/base.py new file mode 100644 index 0000000..40701d2 --- /dev/null +++ b/nose/plugins/base.py @@ -0,0 +1,728 @@ +import os +import textwrap +from optparse import OptionConflictError +from warnings import warn +from nose.util import tolist + +class Plugin(object): + """Base class for nose plugins. It's recommended but not *necessary* to + subclass this class to create a plugin, but all plugins *must* implement + `options(self, parser, env)` and `configure(self, options, conf)`, and + must have the attributes `enabled`, `name` and `score`. The `name` + attribute may contain hyphens ('-'). + + Plugins should not be enabled by default. + + Subclassing Plugin (and calling the superclass methods in + __init__, configure, and options, if you override them) will give + your plugin some friendly default behavior: + + * A --with-$name option will be added to the command line interface + to enable the plugin, and a corresponding environment variable + will be used as the default value. The plugin class's docstring + will be used as the help for this option. + * The plugin will not be enabled unless this option is selected by + the user. + """ + can_configure = False + enabled = False + enableOpt = None + name = None + score = 100 + + def __init__(self): + if self.name is None: + self.name = self.__class__.__name__.lower() + if self.enableOpt is None: + self.enableOpt = "enable_plugin_%s" % self.name.replace('-', '_') + + def addOptions(self, parser, env=None): + """Add command-line options for this plugin. + + The base plugin class adds --with-$name by default, used to enable the + plugin. + + .. warning :: Don't implement addOptions unless you want to override + all default option handling behavior, including + warnings for conflicting options. Implement + :meth:`options + ` + instead. + """ + self.add_options(parser, env) + + def add_options(self, parser, env=None): + """Non-camel-case version of func name for backwards compatibility. + + .. warning :: + + DEPRECATED: Do not use this method, + use :meth:`options ` + instead. + + """ + # FIXME raise deprecation warning if wasn't called by wrapper + if env is None: + env = os.environ + try: + self.options(parser, env) + self.can_configure = True + except OptionConflictError, e: + warn("Plugin %s has conflicting option string: %s and will " + "be disabled" % (self, e), RuntimeWarning) + self.enabled = False + self.can_configure = False + + def options(self, parser, env): + """Register commandline options. + + Implement this method for normal options behavior with protection from + OptionConflictErrors. If you override this method and want the default + --with-$name option to be registered, be sure to call super(). + """ + env_opt = 'NOSE_WITH_%s' % self.name.upper() + env_opt = env_opt.replace('-', '_') + parser.add_option("--with-%s" % self.name, + action="store_true", + dest=self.enableOpt, + default=env.get(env_opt), + help="Enable plugin %s: %s [%s]" % + (self.__class__.__name__, self.help(), env_opt)) + + def configure(self, options, conf): + """Configure the plugin and system, based on selected options. + + The base plugin class sets the plugin to enabled if the enable option + for the plugin (self.enableOpt) is true. + """ + if not self.can_configure: + return + self.conf = conf + if hasattr(options, self.enableOpt): + self.enabled = getattr(options, self.enableOpt) + + def help(self): + """Return help for this plugin. This will be output as the help + section of the --with-$name option that enables the plugin. + """ + if self.__class__.__doc__: + # doc sections are often indented; compress the spaces + return textwrap.dedent(self.__class__.__doc__) + return "(no help available)" + + # Compatiblity shim + def tolist(self, val): + warn("Plugin.tolist is deprecated. Use nose.util.tolist instead", + DeprecationWarning) + return tolist(val) + + +class IPluginInterface(object): + """ + IPluginInterface describes the plugin API. Do not subclass or use this + class directly. + """ + def __new__(cls, *arg, **kw): + raise TypeError("IPluginInterface class is for documentation only") + + def addOptions(self, parser, env): + """Called to allow plugin to register command-line options with the + parser. DO NOT return a value from this method unless you want to stop + all other plugins from setting their options. + + .. warning :: + + DEPRECATED -- implement + :meth:`options ` instead. + """ + pass + add_options = addOptions + add_options.deprecated = True + + def addDeprecated(self, test): + """Called when a deprecated test is seen. DO NOT return a value + unless you want to stop other plugins from seeing the deprecated + test. + + .. warning :: DEPRECATED -- check error class in addError instead + """ + pass + addDeprecated.deprecated = True + + def addError(self, test, err): + """Called when a test raises an uncaught exception. DO NOT return a + value unless you want to stop other plugins from seeing that the + test has raised an error. + + :param test: the test case + :type test: :class:`nose.case.Test` + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + addError.changed = True + + def addFailure(self, test, err): + """Called when a test fails. DO NOT return a value unless you + want to stop other plugins from seeing that the test has failed. + + :param test: the test case + :type test: :class:`nose.case.Test` + :param err: 3-tuple + :type err: sys.exc_info() tuple + """ + pass + addFailure.changed = True + + def addSkip(self, test): + """Called when a test is skipped. DO NOT return a value unless + you want to stop other plugins from seeing the skipped test. + + .. warning:: DEPRECATED -- check error class in addError instead + """ + pass + addSkip.deprecated = True + + def addSuccess(self, test): + """Called when a test passes. DO NOT return a value unless you + want to stop other plugins from seeing the passing test. + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + addSuccess.changed = True + + def afterContext(self): + """Called after a context (generally a module) has been + lazy-loaded, imported, setup, had its tests loaded and + executed, and torn down. + """ + pass + afterContext._new = True + + def afterDirectory(self, path): + """Called after all tests have been loaded from directory at path + and run. + + :param path: the directory that has finished processing + :type path: string + """ + pass + afterDirectory._new = True + + def afterImport(self, filename, module): + """Called after module is imported from filename. afterImport + is called even if the import failed. + + :param filename: The file that was loaded + :type filename: string + :param filename: The name of the module + :type module: string + """ + pass + afterImport._new = True + + def afterTest(self, test): + """Called after the test has been run and the result recorded + (after stopTest). + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + afterTest._new = True + + def beforeContext(self): + """Called before a context (generally a module) is + examined. Because the context is not yet loaded, plugins don't + get to know what the context is; so any context operations + should use a stack that is pushed in `beforeContext` and popped + in `afterContext` to ensure they operate symmetrically. + + `beforeContext` and `afterContext` are mainly useful for tracking + and restoring global state around possible changes from within a + context, whatever the context may be. If you need to operate on + contexts themselves, see `startContext` and `stopContext`, which + are passed the context in question, but are called after + it has been loaded (imported in the module case). + """ + pass + beforeContext._new = True + + def beforeDirectory(self, path): + """Called before tests are loaded from directory at path. + + :param path: the directory that is about to be processed + """ + pass + beforeDirectory._new = True + + def beforeImport(self, filename, module): + """Called before module is imported from filename. + + :param filename: The file that will be loaded + :param module: The name of the module found in file + :type module: string + """ + beforeImport._new = True + + def beforeTest(self, test): + """Called before the test is run (before startTest). + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + beforeTest._new = True + + def begin(self): + """Called before any tests are collected or run. Use this to + perform any setup needed before testing begins. + """ + pass + + def configure(self, options, conf): + """Called after the command line has been parsed, with the + parsed options and the config container. Here, implement any + config storage or changes to state or operation that are set + by command line options. + + DO NOT return a value from this method unless you want to + stop all other plugins from being configured. + """ + pass + + def finalize(self, result): + """Called after all report output, including output from all + plugins, has been sent to the stream. Use this to print final + test results or perform final cleanup. Return None to allow + other plugins to continue printing, or any other value to stop + them. + + :param result: test result object + + .. Note:: When tests are run under a test runner other than + :class:`nose.core.TextTestRunner`, such as + via ``python setup.py test``, this method may be called + **before** the default report output is sent. + """ + pass + + def describeTest(self, test): + """Return a test description. + + Called by :meth:`nose.case.Test.shortDescription`. + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + describeTest._new = True + + def formatError(self, test, err): + """Called in result.addError, before plugin.addError. If you + want to replace or modify the error tuple, return a new error + tuple. + + :param test: the test case + :type test: :class:`nose.case.Test` + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + formatError._new = True + formatError.chainable = True + # test arg is not chainable + formatError.static_args = (True, False) + + def formatFailure(self, test, err): + """Called in result.addFailure, before plugin.addFailure. If you + want to replace or modify the error tuple, return a new error + tuple. Because this method is chainable, you must return the + test as well, so you'll return something like:: + + return (test, err) + + :param test: the test case + :type test: :class:`nose.case.Test` + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + formatFailure._new = True + formatFailure.chainable = True + # test arg is not chainable + formatFailure.static_args = (True, False) + + def handleError(self, test, err): + """Called on addError. To handle the error yourself and prevent normal + error processing, return a true value. + + :param test: the test case + :type test: :class:`nose.case.Test` + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + handleError._new = True + + def handleFailure(self, test, err): + """Called on addFailure. To handle the failure yourself and + prevent normal failure processing, return a true value. + + :param test: the test case + :type test: :class:`nose.case.Test` + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + handleFailure._new = True + + def loadTestsFromDir(self, path): + """Return iterable of tests from a directory. May be a + generator. Each item returned must be a runnable + unittest.TestCase (or subclass) instance or suite instance. + Return None if your plugin cannot collect any tests from + directory. + + :param path: The path to the directory. + """ + pass + loadTestsFromDir.generative = True + loadTestsFromDir._new = True + + def loadTestsFromModule(self, module, path=None): + """Return iterable of tests in a module. May be a + generator. Each item returned must be a runnable + unittest.TestCase (or subclass) instance. + Return None if your plugin cannot + collect any tests from module. + + :param module: The module object + :type module: python module + :param path: the path of the module to search, to distinguish from + namespace package modules + + .. note:: + + NEW. The ``path`` parameter will only be passed by nose 0.11 + or above. + """ + pass + loadTestsFromModule.generative = True + + def loadTestsFromName(self, name, module=None, importPath=None): + """Return tests in this file or module. Return None if you are not able + to load any tests, or an iterable if you are. May be a + generator. + + :param name: The test name. May be a file or module name plus a test + callable. Use split_test_name to split into parts. Or it might + be some crazy name of your own devising, in which case, do + whatever you want. + :param module: Module from which the name is to be loaded + :param importPath: Path from which file (must be a python module) was + found + + .. warning:: DEPRECATED: this argument will NOT be passed. + """ + pass + loadTestsFromName.generative = True + + def loadTestsFromNames(self, names, module=None): + """Return a tuple of (tests loaded, remaining names). Return + None if you are not able to load any tests. Multiple plugins + may implement loadTestsFromNames; the remaining name list from + each will be passed to the next as input. + + :param names: List of test names. + :type names: iterable + :param module: Module from which the names are to be loaded + """ + pass + loadTestsFromNames._new = True + loadTestsFromNames.chainable = True + + def loadTestsFromFile(self, filename): + """Return tests in this file. Return None if you are not + interested in loading any tests, or an iterable if you are and + can load some. May be a generator. *If you are interested in + loading tests from the file and encounter no errors, but find + no tests, yield False or return [False].* + + .. Note:: This method replaces loadTestsFromPath from the 0.9 + API. + + :param filename: The full path to the file or directory. + """ + pass + loadTestsFromFile.generative = True + loadTestsFromFile._new = True + + def loadTestsFromPath(self, path): + """ + .. warning:: DEPRECATED -- use loadTestsFromFile instead + """ + pass + loadTestsFromPath.deprecated = True + + def loadTestsFromTestCase(self, cls): + """Return tests in this test case class. Return None if you are + not able to load any tests, or an iterable if you are. May be a + generator. + + :param cls: The test case class. Must be subclass of + :class:`unittest.TestCase`. + """ + pass + loadTestsFromTestCase.generative = True + + def loadTestsFromTestClass(self, cls): + """Return tests in this test class. Class will *not* be a + unittest.TestCase subclass. Return None if you are not able to + load any tests, an iterable if you are. May be a generator. + + :param cls: The test case class. Must be **not** be subclass of + :class:`unittest.TestCase`. + """ + pass + loadTestsFromTestClass._new = True + loadTestsFromTestClass.generative = True + + def makeTest(self, obj, parent): + """Given an object and its parent, return or yield one or more + test cases. Each test must be a unittest.TestCase (or subclass) + instance. This is called before default test loading to allow + plugins to load an alternate test case or cases for an + object. May be a generator. + + :param obj: The object to be made into a test + :param parent: The parent of obj (eg, for a method, the class) + """ + pass + makeTest._new = True + makeTest.generative = True + + def options(self, parser, env): + """Called to allow plugin to register command line + options with the parser. + + DO NOT return a value from this method unless you want to stop + all other plugins from setting their options. + + :param parser: options parser instance + :type parser: :class:`ConfigParser.ConfigParser` + :param env: environment, default is os.environ + """ + pass + options._new = True + + def prepareTest(self, test): + """Called before the test is run by the test runner. Please + note the article *the* in the previous sentence: prepareTest + is called *only once*, and is passed the test case or test + suite that the test runner will execute. It is *not* called + for each individual test case. If you return a non-None value, + that return value will be run as the test. Use this hook to + wrap or decorate the test with another function. If you need + to modify or wrap individual test cases, use `prepareTestCase` + instead. + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + + def prepareTestCase(self, test): + """Prepare or wrap an individual test case. Called before + execution of the test. The test passed here is a + nose.case.Test instance; the case to be executed is in the + test attribute of the passed case. To modify the test to be + run, you should return a callable that takes one argument (the + test result object) -- it is recommended that you *do not* + side-effect the nose.case.Test instance you have been passed. + + Keep in mind that when you replace the test callable you are + replacing the run() method of the test case -- including the + exception handling and result calls, etc. + + :param test: the test case + :type test: :class:`nose.case.Test` + """ + pass + prepareTestCase._new = True + + def prepareTestLoader(self, loader): + """Called before tests are loaded. To replace the test loader, + return a test loader. To allow other plugins to process the + test loader, return None. Only one plugin may replace the test + loader. Only valid when using nose.TestProgram. + + :param loader: :class:`nose.loader.TestLoader` + (or other loader) instance + """ + pass + prepareTestLoader._new = True + + def prepareTestResult(self, result): + """Called before the first test is run. To use a different + test result handler for all tests than the given result, + return a test result handler. NOTE however that this handler + will only be seen by tests, that is, inside of the result + proxy system. The TestRunner and TestProgram -- whether nose's + or other -- will continue to see the original result + handler. For this reason, it is usually better to monkeypatch + the result (for instance, if you want to handle some + exceptions in a unique way). Only one plugin may replace the + result, but many may monkeypatch it. If you want to + monkeypatch and stop other plugins from doing so, monkeypatch + and return the patched result. + + :param result: :class:`nose.result.TextTestResult` + (or other result) instance + """ + pass + prepareTestResult._new = True + + def prepareTestRunner(self, runner): + """Called before tests are run. To replace the test runner, + return a test runner. To allow other plugins to process the + test runner, return None. Only valid when using nose.TestProgram. + + :param runner: :class:`nose.core.TextTestRunner` + (or other runner) instance + """ + pass + prepareTestRunner._new = True + + def report(self, stream): + """Called after all error output has been printed. Print your + plugin's report to the provided stream. Return None to allow + other plugins to print reports, any other value to stop them. + + :param stream: stream object; send your output here + :type stream: file-like object + """ + pass + + def setOutputStream(self, stream): + """Called before test output begins. To direct test output to a + new stream, return a stream object, which must implement a + `write(msg)` method. If you only want to note the stream, not + capture or redirect it, then return None. + + :param stream: stream object; send your output here + :type stream: file-like object + """ + + def startContext(self, context): + """Called before context setup and the running of tests in the + context. Note that tests have already been *loaded* from the + context before this call. + + :param context: the context about to be setup. May be a module or + class, or any other object that contains tests. + """ + pass + startContext._new = True + + def startTest(self, test): + """Called before each test is run. DO NOT return a value unless + you want to stop other plugins from seeing the test start. + + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + + def stopContext(self, context): + """Called after the tests in a context have run and the + context has been torn down. + + :param context: the context about to be setup. May be a module or + class, or any other object that contains tests. + """ + pass + stopContext._new = True + + def stopTest(self, test): + """Called after each test is run. DO NOT return a value unless + you want to stop other plugins from seeing that the test has stopped. + + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + + def testName(self, test): + """Return a short test name. Called by `nose.case.Test.__str__`. + + :param err: sys.exc_info() tuple + :type err: 3-tuple + """ + pass + testName._new = True + + def wantClass(self, cls): + """Return true if you want the main test selector to collect + tests from this class, false if you don't, and None if you don't + care. + + :param cls: The class being examined by the selector + """ + pass + + def wantDirectory(self, dirname): + """Return true if you want test collection to descend into this + directory, false if you do not, and None if you don't care. + + :param dirname: Full path to directory being examined by the selector + """ + pass + + def wantFile(self, file): + """Return true if you want to collect tests from this file, + false if you do not and None if you don't care. + + Change from 0.9: The optional package parameter is no longer passed. + + :param file: Full path to file being examined by the selector + """ + pass + + def wantFunction(self, function): + """Return true to collect this function as a test, false to + prevent it from being collected, and None if you don't care. + + :param function: The function object being examined by the selector + """ + pass + + def wantMethod(self, method): + """Return true to collect this method as a test, false to + prevent it from being collected, and None if you don't care. + + :param method: The method object being examined by the selector + :type method: unbound method + """ + pass + + def wantModule(self, module): + """Return true if you want to collection to descend into this + module, false to prevent the collector from descending into the + module, and None if you don't care. + + :param module: The module object being examined by the selector + :type module: python module + """ + pass + + def wantModuleTests(self, module): + """ + .. warning:: DEPRECATED -- this method will not be called, it has + been folded into wantModule. + """ + pass + wantModuleTests.deprecated = True + diff --git a/nose/plugins/builtin.py b/nose/plugins/builtin.py new file mode 100644 index 0000000..4fcc001 --- /dev/null +++ b/nose/plugins/builtin.py @@ -0,0 +1,34 @@ +""" +Lists builtin plugins. +""" +plugins = [] +builtins = ( + ('nose.plugins.attrib', 'AttributeSelector'), + ('nose.plugins.capture', 'Capture'), + ('nose.plugins.logcapture', 'LogCapture'), + ('nose.plugins.cover', 'Coverage'), + ('nose.plugins.debug', 'Pdb'), + ('nose.plugins.deprecated', 'Deprecated'), + ('nose.plugins.doctests', 'Doctest'), + ('nose.plugins.isolate', 'IsolationPlugin'), + ('nose.plugins.failuredetail', 'FailureDetail'), + ('nose.plugins.prof', 'Profile'), + ('nose.plugins.skip', 'Skip'), + ('nose.plugins.testid', 'TestId'), + ('nose.plugins.multiprocess', 'MultiProcess'), + ('nose.plugins.xunit', 'Xunit'), + ('nose.plugins.allmodules', 'AllModules'), + ('nose.plugins.collect', 'CollectOnly'), + ) + +for module, cls in builtins: + try: + plugmod = __import__(module, globals(), locals(), [cls]) + except KeyboardInterrupt: + raise + except: + continue + plug = getattr(plugmod, cls) + plugins.append(plug) + globals()[cls] = plug + diff --git a/nose/plugins/capture.py b/nose/plugins/capture.py new file mode 100644 index 0000000..911215d --- /dev/null +++ b/nose/plugins/capture.py @@ -0,0 +1,128 @@ +""" +This plugin captures stdout during test execution. If the test fails +or raises an error, the captured output will be appended to the error +or failure output. It is enabled by default but can be disabled with +the options ``-s`` or ``--nocapture``. + +:Options: + ``--nocapture`` + Don't capture stdout (any stdout output will be printed immediately) + +""" +import logging +import os +import sys +from nose.plugins.base import Plugin +from nose.util import ln +from StringIO import StringIO + + +log = logging.getLogger(__name__) + +class Capture(Plugin): + """ + Output capture plugin. Enabled by default. Disable with ``-s`` or + ``--nocapture``. This plugin captures stdout during test execution, + appending any output captured to the error or failure output, + should the test fail or raise an error. + """ + enabled = True + env_opt = 'NOSE_NOCAPTURE' + name = 'capture' + score = 500 + + def __init__(self): + self.stdout = [] + self._buf = None + + def options(self, parser, env): + """Register commandline options + """ + parser.add_option( + "-s", "--nocapture", action="store_false", + default=not env.get(self.env_opt), dest="capture", + help="Don't capture stdout (any stdout output " + "will be printed immediately) [NOSE_NOCAPTURE]") + + def configure(self, options, conf): + """Configure plugin. Plugin is enabled by default. + """ + self.conf = conf + if not options.capture: + self.enabled = False + + def afterTest(self, test): + """Clear capture buffer. + """ + self.end() + self._buf = None + + def begin(self): + """Replace sys.stdout with capture buffer. + """ + self.start() # get an early handle on sys.stdout + + def beforeTest(self, test): + """Flush capture buffer. + """ + self.start() + + def formatError(self, test, err): + """Add captured output to error report. + """ + test.capturedOutput = output = self.buffer + self._buf = None + if not output: + # Don't return None as that will prevent other + # formatters from formatting and remove earlier formatters + # formats, instead return the err we got + return err + ec, ev, tb = err + return (ec, self.addCaptureToErr(ev, output), tb) + + def formatFailure(self, test, err): + """Add captured output to failure report. + """ + return self.formatError(test, err) + + def addCaptureToErr(self, ev, output): + if isinstance(ev, Exception): + if hasattr(ev, '__unicode__'): + # 2.6+ + ev = unicode(ev) + else: + # 2.5- + if not hasattr(ev, 'message'): + # 2.4 + msg = len(ev.args) and ev.args[0] or '' + else: + msg = ev.message + if not isinstance(msg, unicode): + msg = msg.decode('utf8', 'replace') + ev = u'%s: %s' % (ev.__class__.__name__, msg) + if not isinstance(output, unicode): + output = output.decode('utf8', 'replace') + return u'\n'.join([ev, ln(u'>> begin captured stdout <<'), + output, ln(u'>> end captured stdout <<')]) + + def start(self): + self.stdout.append(sys.stdout) + self._buf = StringIO() + sys.stdout = self._buf + + def end(self): + if self.stdout: + sys.stdout = self.stdout.pop() + + def finalize(self, result): + """Restore stdout. + """ + while self.stdout: + self.end() + + def _get_buffer(self): + if self._buf is not None: + return self._buf.getvalue() + + buffer = property(_get_buffer, None, None, + """Captured stdout output.""") diff --git a/nose/plugins/collect.py b/nose/plugins/collect.py new file mode 100644 index 0000000..6f9f0fa --- /dev/null +++ b/nose/plugins/collect.py @@ -0,0 +1,94 @@ +""" +This plugin bypasses the actual execution of tests, and instead just collects +test names. Fixtures are also bypassed, so running nosetests with the +collection plugin enabled should be very quick. + +This plugin is useful in combination with the testid plugin (``--with-id``). +Run both together to get an indexed list of all tests, which will enable you to +run individual tests by index number. + +This plugin is also useful for counting tests in a test suite, and making +people watching your demo think all of your tests pass. +""" +from nose.plugins.base import Plugin +from nose.case import Test +import logging +import unittest + +log = logging.getLogger(__name__) + + +class CollectOnly(Plugin): + """ + Collect and output test names only, don't run any tests. + """ + name = "collect-only" + enableOpt = 'collect_only' + + def options(self, parser, env): + """Register commandline options. + """ + parser.add_option('--collect-only', + action='store_true', + dest=self.enableOpt, + default=env.get('NOSE_COLLECT_ONLY'), + help="Enable collect-only: %s [COLLECT_ONLY]" % + (self.help())) + + def prepareTestLoader(self, loader): + """Install collect-only suite class in TestLoader. + """ + # Disable context awareness + log.debug("Preparing test loader") + loader.suiteClass = TestSuiteFactory(self.conf) + + def prepareTestCase(self, test): + """Replace actual test with dummy that always passes. + """ + # Return something that always passes + log.debug("Preparing test case %s", test) + if not isinstance(test, Test): + return + def run(result): + # We need to make these plugin calls because there won't be + # a result proxy, due to using a stripped-down test suite + self.conf.plugins.startTest(test) + result.startTest(test) + self.conf.plugins.addSuccess(test) + result.addSuccess(test) + self.conf.plugins.stopTest(test) + result.stopTest(test) + return run + + +class TestSuiteFactory: + """ + Factory for producing configured test suites. + """ + def __init__(self, conf): + self.conf = conf + + def __call__(self, tests=(), **kw): + return TestSuite(tests, conf=self.conf) + + +class TestSuite(unittest.TestSuite): + """ + Basic test suite that bypasses most proxy and plugin calls, but does + wrap tests in a nose.case.Test so prepareTestCase will be called. + """ + def __init__(self, tests=(), conf=None): + self.conf = conf + # Exec lazy suites: makes discovery depth-first + if callable(tests): + tests = tests() + log.debug("TestSuite(%r)", tests) + unittest.TestSuite.__init__(self, tests) + + def addTest(self, test): + log.debug("Add test %s", test) + if isinstance(test, unittest.TestSuite): + self._tests.append(test) + else: + self._tests.append(Test(test, config=self.conf)) + diff --git a/nose/plugins/cover.py b/nose/plugins/cover.py new file mode 100644 index 0000000..f10b198 --- /dev/null +++ b/nose/plugins/cover.py @@ -0,0 +1,308 @@ +"""If you have Ned Batchelder's coverage_ module installed, you may activate a +coverage report with the ``--with-coverage`` switch or NOSE_WITH_COVERAGE +environment variable. The coverage report will cover any python source module +imported after the start of the test run, excluding modules that match +testMatch. If you want to include those modules too, use the ``--cover-tests`` +switch, or set the NOSE_COVER_TESTS environment variable to a true value. To +restrict the coverage report to modules from a particular package or packages, +use the ``--cover-packages`` switch or the NOSE_COVER_PACKAGES environment +variable. + +.. _coverage: http://www.nedbatchelder.com/code/modules/coverage.html +""" +import logging +import os +import re +import sys +from nose.plugins.base import Plugin +from nose.util import src, tolist + +log = logging.getLogger(__name__) + +COVERAGE_TEMPLATE = ''' + +%(title)s + + +%(header)s + +

+
+%(body)s +
+ + +''' + +COVERAGE_STATS_TEMPLATE = '''Covered: %(covered)s lines
+Missed: %(missed)s lines
+Skipped %(skipped)s lines
+Percent: %(percent)s %%
+''' + + +class Coverage(Plugin): + """ + Activate a coverage report using Ned Batchelder's coverage module. + """ + coverTests = False + coverPackages = None + _coverInstance = None + score = 200 + status = {} + + def coverInstance(self): + if not self._coverInstance: + import coverage + try: + self._coverInstance = coverage.coverage() + except coverage.CoverageException: + self._coverInstance = coverage + return self._coverInstance + coverInstance = property(coverInstance) + + def options(self, parser, env): + """ + Add options to command line. + """ + Plugin.options(self, parser, env) + parser.add_option("--cover-package", action="append", + default=env.get('NOSE_COVER_PACKAGE'), + metavar="PACKAGE", + dest="cover_packages", + help="Restrict coverage output to selected packages " + "[NOSE_COVER_PACKAGE]") + parser.add_option("--cover-erase", action="store_true", + default=env.get('NOSE_COVER_ERASE'), + dest="cover_erase", + help="Erase previously collected coverage " + "statistics before run") + parser.add_option("--cover-tests", action="store_true", + dest="cover_tests", + default=env.get('NOSE_COVER_TESTS'), + help="Include test modules in coverage report " + "[NOSE_COVER_TESTS]") + parser.add_option("--cover-inclusive", action="store_true", + dest="cover_inclusive", + default=env.get('NOSE_COVER_INCLUSIVE'), + help="Include all python files under working " + "directory in coverage report. Useful for " + "discovering holes in test coverage if not all " + "files are imported by the test suite. " + "[NOSE_COVER_INCLUSIVE]") + parser.add_option("--cover-html", action="store_true", + default=env.get('NOSE_COVER_HTML'), + dest='cover_html', + help="Produce HTML coverage information") + parser.add_option('--cover-html-dir', action='store', + default=env.get('NOSE_COVER_HTML_DIR', 'cover'), + dest='cover_html_dir', + metavar='DIR', + help='Produce HTML coverage information in dir') + + def configure(self, options, config): + """ + Configure plugin. + """ + try: + self.status.pop('active') + except KeyError: + pass + Plugin.configure(self, options, config) + if config.worker: + return + if self.enabled: + try: + import coverage + except ImportError: + log.error("Coverage not available: " + "unable to import coverage module") + self.enabled = False + return + self.conf = config + self.coverErase = options.cover_erase + self.coverTests = options.cover_tests + self.coverPackages = [] + if options.cover_packages: + for pkgs in [tolist(x) for x in options.cover_packages]: + self.coverPackages.extend(pkgs) + self.coverInclusive = options.cover_inclusive + if self.coverPackages: + log.info("Coverage report will include only packages: %s", + self.coverPackages) + self.coverHtmlDir = None + if options.cover_html: + self.coverHtmlDir = options.cover_html_dir + log.debug('Will put HTML coverage report in %s', self.coverHtmlDir) + if self.enabled: + self.status['active'] = True + + def begin(self): + """ + Begin recording coverage information. + """ + log.debug("Coverage begin") + self.skipModules = sys.modules.keys()[:] + if self.coverErase: + log.debug("Clearing previously collected coverage statistics") + self.coverInstance.erase() + self.coverInstance.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]') + self.coverInstance.start() + + def report(self, stream): + """ + Output code coverage report. + """ + log.debug("Coverage report") + self.coverInstance.stop() + self.coverInstance.save() + modules = [ module + for name, module in sys.modules.items() + if self.wantModuleCoverage(name, module) ] + log.debug("Coverage report will cover modules: %s", modules) + self.coverInstance.report(modules, file=stream) + if self.coverHtmlDir: + log.debug("Generating HTML coverage report") + if hasattr(self.coverInstance, 'html_report'): + self.coverInstance.html_report(modules, self.coverHtmlDir) + else: + self.report_html(modules) + + def report_html(self, modules): + if not os.path.exists(self.coverHtmlDir): + os.makedirs(self.coverHtmlDir) + files = {} + for m in modules: + if hasattr(m, '__name__') and hasattr(m, '__file__'): + files[m.__name__] = m.__file__ + self.coverInstance.annotate(files.values()) + global_stats = {'covered': 0, 'missed': 0, 'skipped': 0} + file_list = [] + for m, f in files.iteritems(): + if f.endswith('pyc'): + f = f[:-1] + coverfile = f+',cover' + outfile, stats = self.htmlAnnotate(m, f, coverfile, + self.coverHtmlDir) + for field in ('covered', 'missed', 'skipped'): + global_stats[field] += stats[field] + file_list.append((stats['percent'], m, outfile, stats)) + os.unlink(coverfile) + file_list.sort() + global_stats['percent'] = self.computePercent( + global_stats['covered'], global_stats['missed']) + # Now write out an index file for the coverage HTML + index = open(os.path.join(self.coverHtmlDir, 'index.html'), 'w') + index.write('Coverage Index' + '

') + index.write(COVERAGE_STATS_TEMPLATE % global_stats) + index.write('') + for junk, name, outfile, stats in file_list: + stats['a'] = '%s' % (outfile, name) + index.write('' % stats) + index.write('
FileCoveredMissed' + 'SkippedPercent
%(a)s%(covered)s' + '%(missed)s%(skipped)s' + '%(percent)s %%

', '>'), + ('"', '"'), ): + line = line.replace(old, new) + if status == '!': + rows.append('
'
+                            '%s
%s
' % (lineno, + line)) + stats['missed'] += 1 + elif status == '>': + rows.append('
%s
' + '
%s
' % (lineno, line)) + stats['covered'] += 1 + else: + rows.append('' % (lineno, line)) + stats['skipped'] += 1 + stats['percent'] = self.computePercent(stats['covered'], + stats['missed']) + html = COVERAGE_TEMPLATE % {'title': '%s' % name, + 'header': name, + 'body': '\n'.join(rows), + 'stats': COVERAGE_STATS_TEMPLATE % stats, + } + outfilename = name + '.html' + outfile = open(os.path.join(outputDir, outfilename), 'w') + outfile.write(html) + outfile.close() + return outfilename, stats + + def computePercent(self, covered, missed): + if covered + missed == 0: + percent = 1 + else: + percent = covered/(covered+missed+0.0) + return int(percent * 100) + + def wantModuleCoverage(self, name, module): + if not hasattr(module, '__file__'): + log.debug("no coverage of %s: no __file__", name) + return False + module_file = src(module.__file__) + if not module_file or not module_file.endswith('.py'): + log.debug("no coverage of %s: not a python file", name) + return False + if self.coverPackages: + for package in self.coverPackages: + if (re.findall(r'^%s\b' % re.escape(package), name) + and (self.coverTests + or not self.conf.testMatch.search(name))): + log.debug("coverage for %s", name) + return True + if name in self.skipModules: + log.debug("no coverage for %s: loaded before coverage start", + name) + return False + if self.conf.testMatch.search(name) and not self.coverTests: + log.debug("no coverage for %s: is a test", name) + return False + # accept any package that passed the previous tests, unless + # coverPackages is on -- in that case, if we wanted this + # module, we would have already returned True + return not self.coverPackages + + def wantFile(self, file, package=None): + """If inclusive coverage enabled, return true for all source files + in wanted packages. + """ + if self.coverInclusive: + if file.endswith(".py"): + if package and self.coverPackages: + for want in self.coverPackages: + if package.startswith(want): + return True + else: + return True + return None diff --git a/nose/plugins/debug.py b/nose/plugins/debug.py new file mode 100644 index 0000000..c7fc462 --- /dev/null +++ b/nose/plugins/debug.py @@ -0,0 +1,62 @@ +""" +This plugin provides ``--pdb`` and ``--pdb-failures`` options. The ``--pdb`` +option will drop the test runner into pdb when it encounters an error. To +drop into pdb on failure, use ``--pdb-failures``. +""" + +import pdb +from nose.plugins.base import Plugin + +class Pdb(Plugin): + """ + Provides --pdb and --pdb-failures options that cause the test runner to + drop into pdb if it encounters an error or failure, respectively. + """ + enabled_for_errors = False + enabled_for_failures = False + score = 5 # run last, among builtins + + def options(self, parser, env): + """Register commandline options. + """ + parser.add_option( + "--pdb", action="store_true", dest="debugErrors", + default=env.get('NOSE_PDB', False), + help="Drop into debugger on errors") + parser.add_option( + "--pdb-failures", action="store_true", + dest="debugFailures", + default=env.get('NOSE_PDB_FAILURES', False), + help="Drop into debugger on failures") + + def configure(self, options, conf): + """Configure which kinds of exceptions trigger plugin. + """ + self.conf = conf + self.enabled = options.debugErrors or options.debugFailures + self.enabled_for_errors = options.debugErrors + self.enabled_for_failures = options.debugFailures + + def addError(self, test, err): + """Enter pdb if configured to debug errors. + """ + if not self.enabled_for_errors: + return + self.debug(err) + + def addFailure(self, test, err): + """Enter pdb if configured to debug failures. + """ + if not self.enabled_for_failures: + return + self.debug(err) + + def debug(self, err): + import sys # FIXME why is this import here? + ec, ev, tb = err + stdout = sys.stdout + sys.stdout = sys.__stdout__ + try: + pdb.post_mortem(tb) + finally: + sys.stdout = stdout diff --git a/nose/plugins/deprecated.py b/nose/plugins/deprecated.py new file mode 100644 index 0000000..461a26b --- /dev/null +++ b/nose/plugins/deprecated.py @@ -0,0 +1,45 @@ +""" +This plugin installs a DEPRECATED error class for the :class:`DeprecatedTest` +exception. When :class:`DeprecatedTest` is raised, the exception will be logged +in the deprecated attribute of the result, ``D`` or ``DEPRECATED`` (verbose) +will be output, and the exception will not be counted as an error or failure. +It is enabled by default, but can be turned off by using ``--no-deprecated``. +""" + +from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin + + +class DeprecatedTest(Exception): + """Raise this exception to mark a test as deprecated. + """ + pass + + +class Deprecated(ErrorClassPlugin): + """ + Installs a DEPRECATED error class for the DeprecatedTest exception. Enabled + by default. + """ + enabled = True + deprecated = ErrorClass(DeprecatedTest, + label='DEPRECATED', + isfailure=False) + + def options(self, parser, env): + """Register commandline options. + """ + env_opt = 'NOSE_WITHOUT_DEPRECATED' + parser.add_option('--no-deprecated', action='store_true', + dest='noDeprecated', default=env.get(env_opt, False), + help="Disable special handling of DeprecatedTest " + "exceptions.") + + def configure(self, options, conf): + """Configure plugin. + """ + if not self.can_configure: + return + self.conf = conf + disable = getattr(options, 'noDeprecated', False) + if disable: + self.enabled = False diff --git a/nose/plugins/doctests.py b/nose/plugins/doctests.py new file mode 100644 index 0000000..f07f641 --- /dev/null +++ b/nose/plugins/doctests.py @@ -0,0 +1,428 @@ +"""Use the Doctest plugin with ``--with-doctest`` or the NOSE_WITH_DOCTEST +environment variable to enable collection and execution of :mod:`doctests +`. Because doctests are usually included in the tested package +(instead of being grouped into packages or modules of their own), nose only +looks for them in the non-test packages it discovers in the working directory. + +Doctests may also be placed into files other than python modules, in which +case they can be collected and executed by using the ``--doctest-extension`` +switch or NOSE_DOCTEST_EXTENSION environment variable to indicate which file +extension(s) to load. + +When loading doctests from non-module files, use the ``--doctest-fixtures`` +switch to specify how to find modules containing fixtures for the tests. A +module name will be produced by appending the value of that switch to the base +name of each doctest file loaded. For example, a doctest file "widgets.rst" +with the switch ``--doctest_fixtures=_fixt`` will load fixtures from the module +``widgets_fixt.py``. + +A fixtures module may define any or all of the following functions: + +* setup([module]) or setup_module([module]) + + Called before the test runs. You may raise SkipTest to skip all tests. + +* teardown([module]) or teardown_module([module]) + + Called after the test runs, if setup/setup_module did not raise an + unhandled exception. + +* setup_test(test) + + Called before the test. NOTE: the argument passed is a + doctest.DocTest instance, *not* a unittest.TestCase. + +* teardown_test(test) + + Called after the test, if setup_test did not raise an exception. NOTE: the + argument passed is a doctest.DocTest instance, *not* a unittest.TestCase. + +Doctests are run like any other test, with the exception that output +capture does not work; doctest does its own output capture while running a +test. + +.. note :: + + See :doc:`../doc_tests/test_doctest_fixtures/doctest_fixtures` for + additional documentation and examples. + +""" +from __future__ import generators + +import logging +import os +import sys +import unittest +from inspect import getmodule +from nose.plugins.base import Plugin +from nose.suite import ContextList +from nose.util import anyp, getpackage, test_address, resolve_name, \ + src, tolist, isproperty +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO +import sys +import __builtin__ as builtin_mod + +log = logging.getLogger(__name__) + +try: + import doctest + doctest.DocTestCase + # system version of doctest is acceptable, but needs a monkeypatch +except (ImportError, AttributeError): + # system version is too old + import nose.ext.dtcompat as doctest + + +# +# Doctest and coverage don't get along, so we need to create +# a monkeypatch that will replace the part of doctest that +# interferes with coverage reports. +# +# The monkeypatch is based on this zope patch: +# http://svn.zope.org/Zope3/trunk/src/zope/testing/doctest.py?rev=28679&r1=28703&r2=28705 +# +_orp = doctest._OutputRedirectingPdb + +class NoseOutputRedirectingPdb(_orp): + def __init__(self, out): + self.__debugger_used = False + _orp.__init__(self, out) + + def set_trace(self): + self.__debugger_used = True + _orp.set_trace(self, sys._getframe().f_back) + + def set_continue(self): + # Calling set_continue unconditionally would break unit test + # coverage reporting, as Bdb.set_continue calls sys.settrace(None). + if self.__debugger_used: + _orp.set_continue(self) +doctest._OutputRedirectingPdb = NoseOutputRedirectingPdb + + +class DoctestSuite(unittest.TestSuite): + """ + Doctest suites are parallelizable at the module or file level only, + since they may be attached to objects that are not individually + addressable (like properties). This suite subclass is used when + loading doctests from a module to ensure that behavior. + + This class is used only if the plugin is not fully prepared; + in normal use, the loader's suiteClass is used. + + """ + can_split = False + + def __init__(self, tests=(), context=None, can_split=False): + self.context = context + self.can_split = can_split + unittest.TestSuite.__init__(self, tests=tests) + + def address(self): + return test_address(self.context) + + def __iter__(self): + # 2.3 compat + return iter(self._tests) + + def __str__(self): + return str(self._tests) + + +class Doctest(Plugin): + """ + Activate doctest plugin to find and run doctests in non-test modules. + """ + extension = None + suiteClass = DoctestSuite + + def options(self, parser, env): + """Register commmandline options. + """ + Plugin.options(self, parser, env) + parser.add_option('--doctest-tests', action='store_true', + dest='doctest_tests', + default=env.get('NOSE_DOCTEST_TESTS'), + help="Also look for doctests in test modules. " + "Note that classes, methods and functions should " + "have either doctests or non-doctest tests, " + "not both. [NOSE_DOCTEST_TESTS]") + parser.add_option('--doctest-extension', action="append", + dest="doctestExtension", + metavar="EXT", + help="Also look for doctests in files with " + "this extension [NOSE_DOCTEST_EXTENSION]") + parser.add_option('--doctest-result-variable', + dest='doctest_result_var', + default=env.get('NOSE_DOCTEST_RESULT_VAR'), + metavar="VAR", + help="Change the variable name set to the result of " + "the last interpreter command from the default '_'. " + "Can be used to avoid conflicts with the _() " + "function used for text translation. " + "[NOSE_DOCTEST_RESULT_VAR]") + parser.add_option('--doctest-fixtures', action="store", + dest="doctestFixtures", + metavar="SUFFIX", + help="Find fixtures for a doctest file in module " + "with this name appended to the base name " + "of the doctest file") + # Set the default as a list, if given in env; otherwise + # an additional value set on the command line will cause + # an error. + env_setting = env.get('NOSE_DOCTEST_EXTENSION') + if env_setting is not None: + parser.set_defaults(doctestExtension=tolist(env_setting)) + + def configure(self, options, config): + """Configure plugin. + """ + Plugin.configure(self, options, config) + self.doctest_result_var = options.doctest_result_var + self.doctest_tests = options.doctest_tests + self.extension = tolist(options.doctestExtension) + self.fixtures = options.doctestFixtures + self.finder = doctest.DocTestFinder() + + def prepareTestLoader(self, loader): + """Capture loader's suiteClass. + + This is used to create test suites from doctest files. + + """ + self.suiteClass = loader.suiteClass + + def loadTestsFromModule(self, module): + """Load doctests from the module. + """ + log.debug("loading from %s", module) + if not self.matches(module.__name__): + log.debug("Doctest doesn't want module %s", module) + return + try: + tests = self.finder.find(module) + except AttributeError: + log.exception("Attribute error loading from %s", module) + # nose allows module.__test__ = False; doctest does not and throws + # AttributeError + return + if not tests: + log.debug("No tests found in %s", module) + return + tests.sort() + module_file = src(module.__file__) + # FIXME this breaks the id plugin somehow (tests probably don't + # get wrapped in result proxy or something) + cases = [] + for test in tests: + if not test.examples: + continue + if not test.filename: + test.filename = module_file + cases.append(DocTestCase(test, result_var=self.doctest_result_var)) + if cases: + yield self.suiteClass(cases, context=module, can_split=False) + + def loadTestsFromFile(self, filename): + """Load doctests from the file. + + Tests are loaded only if filename's extension matches + configured doctest extension. + + """ + if self.extension and anyp(filename.endswith, self.extension): + name = os.path.basename(filename) + dh = open(filename) + try: + doc = dh.read() + finally: + dh.close() + + fixture_context = None + globs = {'__file__': filename} + if self.fixtures: + base, ext = os.path.splitext(name) + dirname = os.path.dirname(filename) + sys.path.append(dirname) + fixt_mod = base + self.fixtures + try: + fixture_context = __import__( + fixt_mod, globals(), locals(), ["nop"]) + except ImportError, e: + log.debug( + "Could not import %s: %s (%s)", fixt_mod, e, sys.path) + log.debug("Fixture module %s resolved to %s", + fixt_mod, fixture_context) + if hasattr(fixture_context, 'globs'): + globs = fixture_context.globs(globs) + parser = doctest.DocTestParser() + test = parser.get_doctest( + doc, globs=globs, name=name, + filename=filename, lineno=0) + if test.examples: + case = DocFileCase( + test, + setUp=getattr(fixture_context, 'setup_test', None), + tearDown=getattr(fixture_context, 'teardown_test', None), + result_var=self.doctest_result_var) + if fixture_context: + yield ContextList((case,), context=fixture_context) + else: + yield case + else: + yield False # no tests to load + + def makeTest(self, obj, parent): + """Look for doctests in the given object, which will be a + function, method or class. + """ + name = getattr(obj, '__name__', 'Unnammed %s' % type(obj)) + doctests = self.finder.find(obj, module=getmodule(parent), name=name) + if doctests: + for test in doctests: + if len(test.examples) == 0: + continue + yield DocTestCase(test, obj=obj, + result_var=self.doctest_result_var) + + def matches(self, name): + # FIXME this seems wrong -- nothing is ever going to + # fail this test, since we're given a module NAME not FILE + if name == '__init__.py': + return False + # FIXME don't think we need include/exclude checks here? + return ((self.doctest_tests or not self.conf.testMatch.search(name) + or (self.conf.include + and filter(None, + [inc.search(name) + for inc in self.conf.include]))) + and (not self.conf.exclude + or not filter(None, + [exc.search(name) + for exc in self.conf.exclude]))) + + def wantFile(self, file): + """Override to select all modules and any file ending with + configured doctest extension. + """ + # always want .py files + if file.endswith('.py'): + return True + # also want files that match my extension + if (self.extension + and anyp(file.endswith, self.extension) + and (not self.conf.exclude + or not filter(None, + [exc.search(file) + for exc in self.conf.exclude]))): + return True + return None + + +class DocTestCase(doctest.DocTestCase): + """Overrides DocTestCase to + provide an address() method that returns the correct address for + the doctest case. To provide hints for address(), an obj may also + be passed -- this will be used as the test object for purposes of + determining the test address, if it is provided. + """ + def __init__(self, test, optionflags=0, setUp=None, tearDown=None, + checker=None, obj=None, result_var='_'): + self._result_var = result_var + self._nose_obj = obj + super(DocTestCase, self).__init__( + test, optionflags=optionflags, setUp=setUp, tearDown=tearDown, + checker=checker) + + def address(self): + if self._nose_obj is not None: + return test_address(self._nose_obj) + obj = resolve_name(self._dt_test.name) + + if isproperty(obj): + # properties have no connection to the class they are in + # so we can't just look 'em up, we have to first look up + # the class, then stick the prop on the end + parts = self._dt_test.name.split('.') + class_name = '.'.join(parts[:-1]) + cls = resolve_name(class_name) + base_addr = test_address(cls) + return (base_addr[0], base_addr[1], + '.'.join([base_addr[2], parts[-1]])) + else: + return test_address(obj) + + # doctests loaded via find(obj) omit the module name + # so we need to override id, __repr__ and shortDescription + # bonus: this will squash a 2.3 vs 2.4 incompatiblity + def id(self): + name = self._dt_test.name + filename = self._dt_test.filename + if filename is not None: + pk = getpackage(filename) + if not name.startswith(pk): + name = "%s.%s" % (pk, name) + return name + + def __repr__(self): + name = self.id() + name = name.split('.') + return "%s (%s)" % (name[-1], '.'.join(name[:-1])) + __str__ = __repr__ + + def shortDescription(self): + return 'Doctest: %s' % self.id() + + def setUp(self): + if self._result_var is not None: + self._old_displayhook = sys.displayhook + sys.displayhook = self._displayhook + super(DocTestCase, self).setUp() + + def _displayhook(self, value): + if value is None: + return + setattr(builtin_mod, self._result_var, value) + print repr(value) + + def tearDown(self): + super(DocTestCase, self).tearDown() + if self._result_var is not None: + sys.displayhook = self._old_displayhook + delattr(builtin_mod, self._result_var) + + +class DocFileCase(doctest.DocFileCase): + """Overrides to provide address() method that returns the correct + address for the doc file case. + """ + def __init__(self, test, optionflags=0, setUp=None, tearDown=None, + checker=None, result_var='_'): + self._result_var = result_var + super(DocFileCase, self).__init__( + test, optionflags=optionflags, setUp=setUp, tearDown=tearDown, + checker=None) + + def address(self): + return (self._dt_test.filename, None, None) + + def setUp(self): + if self._result_var is not None: + self._old_displayhook = sys.displayhook + sys.displayhook = self._displayhook + super(DocFileCase, self).setUp() + + def _displayhook(self, value): + if value is None: + return + setattr(builtin_mod, self._result_var, value) + print repr(value) + + def tearDown(self): + super(DocFileCase, self).tearDown() + if self._result_var is not None: + sys.displayhook = self._old_displayhook + delattr(builtin_mod, self._result_var) diff --git a/nose/plugins/errorclass.py b/nose/plugins/errorclass.py new file mode 100644 index 0000000..663dffc --- /dev/null +++ b/nose/plugins/errorclass.py @@ -0,0 +1,210 @@ +""" +ErrorClass Plugins +------------------ + +ErrorClass plugins provide an easy way to add support for custom +handling of particular classes of exceptions. + +An ErrorClass plugin defines one or more ErrorClasses and how each is +handled and reported on. Each error class is stored in a different +attribute on the result, and reported separately. Each error class must +indicate the exceptions that fall under that class, the label to use +for reporting, and whether exceptions of the class should be +considered as failures for the whole test run. + +ErrorClasses use a declarative syntax. Assign an ErrorClass to the +attribute you wish to add to the result object, defining the +exceptions, label and isfailure attributes. For example, to declare an +ErrorClassPlugin that defines TodoErrors (and subclasses of TodoError) +as an error class with the label 'TODO' that is considered a failure, +do this: + + >>> class Todo(Exception): + ... pass + >>> class TodoError(ErrorClassPlugin): + ... todo = ErrorClass(Todo, label='TODO', isfailure=True) + +The MetaErrorClass metaclass translates the ErrorClass declarations +into the tuples used by the error handling and reporting functions in +the result. This is an internal format and subject to change; you +should always use the declarative syntax for attaching ErrorClasses to +an ErrorClass plugin. + + >>> TodoError.errorClasses # doctest: +ELLIPSIS + ((, ('todo', 'TODO', True)),) + +Let's see the plugin in action. First some boilerplate. + + >>> import sys + >>> import unittest + >>> try: + ... # 2.7+ + ... from unittest.runner import _WritelnDecorator + ... except ImportError: + ... from unittest import _WritelnDecorator + ... + >>> buf = _WritelnDecorator(sys.stdout) + +Now define a test case that raises a Todo. + + >>> class TestTodo(unittest.TestCase): + ... def runTest(self): + ... raise Todo("I need to test something") + >>> case = TestTodo() + +Prepare the result using our plugin. Normally this happens during the +course of test execution within nose -- you won't be doing this +yourself. For the purposes of this testing document, I'm stepping +through the internal process of nose so you can see what happens at +each step. + + >>> plugin = TodoError() + >>> from nose.result import _TextTestResult + >>> result = _TextTestResult(stream=buf, descriptions=0, verbosity=2) + >>> plugin.prepareTestResult(result) + +Now run the test. TODO is printed. + + >>> case(result) # doctest: +ELLIPSIS + runTest (....TestTodo) ... TODO: I need to test something + +Errors and failures are empty, but todo has our test: + + >>> result.errors + [] + >>> result.failures + [] + >>> result.todo # doctest: +ELLIPSIS + [(<....TestTodo testMethod=runTest>, '...Todo: I need to test something\\n')] + >>> result.printErrors() # doctest: +ELLIPSIS + + ====================================================================== + TODO: runTest (....TestTodo) + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + ...Todo: I need to test something + + +Since we defined a Todo as a failure, the run was not successful. + + >>> result.wasSuccessful() + False +""" + +from nose.pyversion import make_instancemethod +from nose.plugins.base import Plugin +from nose.result import TextTestResult +from nose.util import isclass + +class MetaErrorClass(type): + """Metaclass for ErrorClassPlugins that allows error classes to be + set up in a declarative manner. + """ + def __init__(self, name, bases, attr): + errorClasses = [] + for name, detail in attr.items(): + if isinstance(detail, ErrorClass): + attr.pop(name) + for cls in detail: + errorClasses.append( + (cls, (name, detail.label, detail.isfailure))) + super(MetaErrorClass, self).__init__(name, bases, attr) + self.errorClasses = tuple(errorClasses) + + +class ErrorClass(object): + def __init__(self, *errorClasses, **kw): + self.errorClasses = errorClasses + try: + for key in ('label', 'isfailure'): + setattr(self, key, kw.pop(key)) + except KeyError: + raise TypeError("%r is a required named argument for ErrorClass" + % key) + + def __iter__(self): + return iter(self.errorClasses) + + +class ErrorClassPlugin(Plugin): + """ + Base class for ErrorClass plugins. Subclass this class and declare the + exceptions that you wish to handle as attributes of the subclass. + """ + __metaclass__ = MetaErrorClass + score = 1000 + errorClasses = () + + def addError(self, test, err): + err_cls, a, b = err + if not isclass(err_cls): + return + classes = [e[0] for e in self.errorClasses] + if filter(lambda c: issubclass(err_cls, c), classes): + return True + + def prepareTestResult(self, result): + if not hasattr(result, 'errorClasses'): + self.patchResult(result) + for cls, (storage_attr, label, isfail) in self.errorClasses: + if cls not in result.errorClasses: + storage = getattr(result, storage_attr, []) + setattr(result, storage_attr, storage) + result.errorClasses[cls] = (storage, label, isfail) + + def patchResult(self, result): + result.printLabel = print_label_patch(result) + result._orig_addError, result.addError = \ + result.addError, add_error_patch(result) + result._orig_wasSuccessful, result.wasSuccessful = \ + result.wasSuccessful, wassuccessful_patch(result) + if hasattr(result, 'printErrors'): + result._orig_printErrors, result.printErrors = \ + result.printErrors, print_errors_patch(result) + if hasattr(result, 'addSkip'): + result._orig_addSkip, result.addSkip = \ + result.addSkip, add_skip_patch(result) + result.errorClasses = {} + + +def add_error_patch(result): + """Create a new addError method to patch into a result instance + that recognizes the errorClasses attribute and deals with + errorclasses correctly. + """ + return make_instancemethod(TextTestResult.addError, result) + + +def print_errors_patch(result): + """Create a new printErrors method that prints errorClasses items + as well. + """ + return make_instancemethod(TextTestResult.printErrors, result) + + +def print_label_patch(result): + """Create a new printLabel method that prints errorClasses items + as well. + """ + return make_instancemethod(TextTestResult.printLabel, result) + + +def wassuccessful_patch(result): + """Create a new wasSuccessful method that checks errorClasses for + exceptions that were put into other slots than error or failure + but that still count as not success. + """ + return make_instancemethod(TextTestResult.wasSuccessful, result) + + +def add_skip_patch(result): + """Create a new addSkip method to patch into a result instance + that delegates to addError. + """ + return make_instancemethod(TextTestResult.addSkip, result) + + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/nose/plugins/failuredetail.py b/nose/plugins/failuredetail.py new file mode 100644 index 0000000..c68b095 --- /dev/null +++ b/nose/plugins/failuredetail.py @@ -0,0 +1,43 @@ +""" +This plugin provides assert introspection. When the plugin is enabled +and a test failure occurs, the traceback is displayed with extra context +around the line in which the exception was raised. Simple variable +substitution is also performed in the context output to provide more +debugging information. +""" + +from nose.plugins import Plugin +from nose.inspector import inspect_traceback + +class FailureDetail(Plugin): + """ + Plugin that provides extra information in tracebacks of test failures. + """ + score = 600 # before capture + + def options(self, parser, env): + """Register commmandline options. + """ + parser.add_option( + "-d", "--detailed-errors", "--failure-detail", + action="store_true", + default=env.get('NOSE_DETAILED_ERRORS'), + dest="detailedErrors", help="Add detail to error" + " output by attempting to evaluate failed" + " asserts [NOSE_DETAILED_ERRORS]") + + def configure(self, options, conf): + """Configure plugin. + """ + if not self.can_configure: + return + self.enabled = options.detailedErrors + self.conf = conf + + def formatFailure(self, test, err): + """Add detail from traceback inspection to error message of a failure. + """ + ec, ev, tb = err + tbinfo = inspect_traceback(tb) + test.tbinfo = tbinfo + return (ec, '\n'.join([str(ev), tbinfo]), tb) diff --git a/nose/plugins/isolate.py b/nose/plugins/isolate.py new file mode 100644 index 0000000..13235df --- /dev/null +++ b/nose/plugins/isolate.py @@ -0,0 +1,103 @@ +"""The isolation plugin resets the contents of sys.modules after running +each test module or package. Use it by setting ``--with-isolation`` or the +NOSE_WITH_ISOLATION environment variable. + +The effects are similar to wrapping the following functions around the +import and execution of each test module:: + + def setup(module): + module._mods = sys.modules.copy() + + def teardown(module): + to_del = [ m for m in sys.modules.keys() if m not in + module._mods ] + for mod in to_del: + del sys.modules[mod] + sys.modules.update(module._mods) + +Isolation works only during lazy loading. In normal use, this is only +during discovery of modules within a directory, where the process of +importing, loading tests and running tests from each module is +encapsulated in a single loadTestsFromName call. This plugin +implements loadTestsFromNames to force the same lazy-loading there, +which allows isolation to work in directed mode as well as discovery, +at the cost of some efficiency: lazy-loading names forces full context +setup and teardown to run for each name, defeating the grouping that +is normally used to ensure that context setup and teardown are run the +fewest possible times for a given set of names. + +.. warning :: + + This plugin should not be used in conjunction with other plugins + that assume that modules, once imported, will stay imported; for + instance, it may cause very odd results when used with the coverage + plugin. + +""" + +import logging +import sys + +from nose.plugins import Plugin + + +log = logging.getLogger('nose.plugins.isolation') + +class IsolationPlugin(Plugin): + """ + Activate the isolation plugin to isolate changes to external + modules to a single test module or package. The isolation plugin + resets the contents of sys.modules after each test module or + package runs to its state before the test. PLEASE NOTE that this + plugin should not be used with the coverage plugin, or in any other case + where module reloading may produce undesirable side-effects. + """ + score = 10 # I want to be last + name = 'isolation' + + def configure(self, options, conf): + """Configure plugin. + """ + Plugin.configure(self, options, conf) + self._mod_stack = [] + + def beforeContext(self): + """Copy sys.modules onto my mod stack + """ + mods = sys.modules.copy() + self._mod_stack.append(mods) + + def afterContext(self): + """Pop my mod stack and restore sys.modules to the state + it was in when mod stack was pushed. + """ + mods = self._mod_stack.pop() + to_del = [ m for m in sys.modules.keys() if m not in mods ] + if to_del: + log.debug('removing sys modules entries: %s', to_del) + for mod in to_del: + del sys.modules[mod] + sys.modules.update(mods) + + def loadTestsFromNames(self, names, module=None): + """Create a lazy suite that calls beforeContext and afterContext + around each name. The side-effect of this is that full context + fixtures will be set up and torn down around each test named. + """ + # Fast path for when we don't care + if not names or len(names) == 1: + return + loader = self.loader + plugins = self.conf.plugins + def lazy(): + for name in names: + plugins.beforeContext() + yield loader.loadTestsFromName(name, module=module) + plugins.afterContext() + return (loader.suiteClass(lazy), []) + + def prepareTestLoader(self, loader): + """Get handle on test loader so we can use it in loadTestsFromNames. + """ + self.loader = loader + diff --git a/nose/plugins/logcapture.py b/nose/plugins/logcapture.py new file mode 100644 index 0000000..da3fe5d --- /dev/null +++ b/nose/plugins/logcapture.py @@ -0,0 +1,236 @@ +""" +This plugin captures logging statements issued during test execution. When an +error or failure occurs, the captured log messages are attached to the running +test in the test.capturedLogging attribute, and displayed with the error failure +output. It is enabled by default but can be turned off with the option +``--nologcapture``. + +You can filter captured logging statements with the ``--logging-filter`` option. +If set, it specifies which logger(s) will be captured; loggers that do not match +will be passed. Example: specifying ``--logging-filter=sqlalchemy,myapp`` +will ensure that only statements logged via sqlalchemy.engine, myapp +or myapp.foo.bar logger will be logged. + +You can remove other installed logging handlers with the +``--logging-clear-handlers`` option. +""" + +import logging +from logging.handlers import BufferingHandler +import threading + +from nose.plugins.base import Plugin +from nose.util import anyp, ln, safe_str + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +log = logging.getLogger(__name__) + +class FilterSet(object): + def __init__(self, filter_components): + self.inclusive, self.exclusive = self._partition(filter_components) + + # @staticmethod + def _partition(components): + inclusive, exclusive = [], [] + for component in components: + if component.startswith('-'): + exclusive.append(component[1:]) + else: + inclusive.append(component) + return inclusive, exclusive + _partition = staticmethod(_partition) + + def allow(self, record): + """returns whether this record should be printed""" + if not self: + # nothing to filter + return True + return self._allow(record) and not self._deny(record) + + # @staticmethod + def _any_match(matchers, record): + """return the bool of whether `record` starts with + any item in `matchers`""" + def record_matches_key(key): + return record == key or record.startswith(key + '.') + return anyp(bool, map(record_matches_key, matchers)) + _any_match = staticmethod(_any_match) + + def _allow(self, record): + if not self.inclusive: + return True + return self._any_match(self.inclusive, record) + + def _deny(self, record): + if not self.exclusive: + return False + return self._any_match(self.exclusive, record) + + +class MyMemoryHandler(BufferingHandler): + def __init__(self, capacity, logformat, logdatefmt, filters): + BufferingHandler.__init__(self, capacity) + fmt = logging.Formatter(logformat, logdatefmt) + self.setFormatter(fmt) + self.filterset = FilterSet(filters) + def flush(self): + pass # do nothing + def truncate(self): + self.buffer = [] + def filter(self, record): + return self.filterset.allow(record.name) + def __getstate__(self): + state = self.__dict__.copy() + del state['lock'] + return state + def __setstate__(self, state): + self.__dict__.update(state) + self.lock = threading.RLock() + + +class LogCapture(Plugin): + """ + Log capture plugin. Enabled by default. Disable with --nologcapture. + This plugin captures logging statements issued during test execution, + appending any output captured to the error or failure output, + should the test fail or raise an error. + """ + enabled = True + env_opt = 'NOSE_NOLOGCAPTURE' + name = 'logcapture' + score = 500 + logformat = '%(name)s: %(levelname)s: %(message)s' + logdatefmt = None + clear = False + filters = ['-nose'] + + def options(self, parser, env): + """Register commandline options. + """ + parser.add_option( + "--nologcapture", action="store_false", + default=not env.get(self.env_opt), dest="logcapture", + help="Disable logging capture plugin. " + "Logging configurtion will be left intact." + " [NOSE_NOLOGCAPTURE]") + parser.add_option( + "--logging-format", action="store", dest="logcapture_format", + default=env.get('NOSE_LOGFORMAT') or self.logformat, + metavar="FORMAT", + help="Specify custom format to print statements. " + "Uses the same format as used by standard logging handlers." + " [NOSE_LOGFORMAT]") + parser.add_option( + "--logging-datefmt", action="store", dest="logcapture_datefmt", + default=env.get('NOSE_LOGDATEFMT') or self.logdatefmt, + metavar="FORMAT", + help="Specify custom date/time format to print statements. " + "Uses the same format as used by standard logging handlers." + " [NOSE_LOGDATEFMT]") + parser.add_option( + "--logging-filter", action="store", dest="logcapture_filters", + default=env.get('NOSE_LOGFILTER'), + metavar="FILTER", + help="Specify which statements to filter in/out. " + "By default, everything is captured. If the output is too" + " verbose,\nuse this option to filter out needless output.\n" + "Example: filter=foo will capture statements issued ONLY to\n" + " foo or foo.what.ever.sub but not foobar or other logger.\n" + "Specify multiple loggers with comma: filter=foo,bar,baz.\n" + "If any logger name is prefixed with a minus, eg filter=-foo,\n" + "it will be excluded rather than included. Default: " + "exclude logging messages from nose itself (-nose)." + " [NOSE_LOGFILTER]\n") + parser.add_option( + "--logging-clear-handlers", action="store_true", + default=False, dest="logcapture_clear", + help="Clear all other logging handlers") + + def configure(self, options, conf): + """Configure plugin. + """ + self.conf = conf + # Disable if explicitly disabled, or if logging is + # configured via logging config file + if not options.logcapture or conf.loggingConfig: + self.enabled = False + self.logformat = options.logcapture_format + self.logdatefmt = options.logcapture_datefmt + self.clear = options.logcapture_clear + if options.logcapture_filters: + self.filters = options.logcapture_filters.split(',') + + def setupLoghandler(self): + # setup our handler with root logger + root_logger = logging.getLogger() + if self.clear: + if hasattr(root_logger, "handlers"): + for handler in root_logger.handlers: + root_logger.removeHandler(handler) + for logger in logging.Logger.manager.loggerDict.values(): + if hasattr(logger, "handlers"): + for handler in logger.handlers: + logger.removeHandler(handler) + # make sure there isn't one already + # you can't simply use "if self.handler not in root_logger.handlers" + # since at least in unit tests this doesn't work -- + # LogCapture() is instantiated for each test case while root_logger + # is module global + # so we always add new MyMemoryHandler instance + for handler in root_logger.handlers[:]: + if isinstance(handler, MyMemoryHandler): + root_logger.handlers.remove(handler) + root_logger.addHandler(self.handler) + # to make sure everything gets captured + root_logger.setLevel(logging.NOTSET) + + def begin(self): + """Set up logging handler before test run begins. + """ + self.start() + + def start(self): + self.handler = MyMemoryHandler(1000, self.logformat, self.logdatefmt, + self.filters) + self.setupLoghandler() + + def end(self): + pass + + def beforeTest(self, test): + """Clear buffers and handlers before test. + """ + self.setupLoghandler() + + def afterTest(self, test): + """Clear buffers after test. + """ + self.handler.truncate() + + def formatFailure(self, test, err): + """Add captured log messages to failure output. + """ + return self.formatError(test, err) + + def formatError(self, test, err): + """Add captured log messages to error output. + """ + # logic flow copied from Capture.formatError + test.capturedLogging = records = self.formatLogRecords() + if not records: + return err + ec, ev, tb = err + return (ec, self.addCaptureToErr(ev, records), tb) + + def formatLogRecords(self): + format = self.handler.format + return [safe_str(format(r)) for r in self.handler.buffer] + + def addCaptureToErr(self, ev, records): + return '\n'.join([safe_str(ev), ln('>> begin captured logging <<')] + \ + records + \ + [ln('>> end captured logging <<')]) diff --git a/nose/plugins/manager.py b/nose/plugins/manager.py new file mode 100644 index 0000000..ce3e48a --- /dev/null +++ b/nose/plugins/manager.py @@ -0,0 +1,446 @@ +""" +Plugin Manager +-------------- + +A plugin manager class is used to load plugins, manage the list of +loaded plugins, and proxy calls to those plugins. + +The plugin managers provided with nose are: + +:class:`PluginManager` + This manager doesn't implement loadPlugins, so it can only work + with a static list of plugins. + +:class:`BuiltinPluginManager` + This manager loads plugins referenced in ``nose.plugins.builtin``. + +:class:`EntryPointPluginManager` + This manager uses setuptools entrypoints to load plugins. + +:class:`DefaultPluginMananger` + This is the manager class that will be used by default. If + setuptools is installed, it is a subclass of + :class:`EntryPointPluginManager` and :class:`BuiltinPluginManager`; + otherwise, an alias to :class:`BuiltinPluginManager`. + +:class:`RestrictedPluginManager` + This manager is for use in test runs where some plugin calls are + not available, such as runs started with ``python setup.py test``, + where the test runner is the default unittest :class:`TextTestRunner`. It + is a subclass of :class:`DefaultPluginManager`. + +Writing a plugin manager +======================== + +If you want to load plugins via some other means, you can write a +plugin manager and pass an instance of your plugin manager class when +instantiating the :class:`nose.config.Config` instance that you pass to +:class:`TestProgram` (or :func:`main` or :func:`run`). + +To implement your plugin loading scheme, implement ``loadPlugins()``, +and in that method, call ``addPlugin()`` with an instance of each plugin +you wish to make available. Make sure to call +``super(self).loadPlugins()`` as well if have subclassed a manager +other than ``PluginManager``. + +""" +import inspect +import logging +import os +import sys +from warnings import warn +import nose.config +from nose.failure import Failure +from nose.plugins.base import IPluginInterface +from nose.pyversion import sort_list + +try: + import cPickle as pickle +except: + import pickle +try: + from cStringIO import StringIO +except: + from StringIO import StringIO + + +__all__ = ['DefaultPluginManager', 'PluginManager', 'EntryPointPluginManager', + 'BuiltinPluginManager', 'RestrictedPluginManager'] + +log = logging.getLogger(__name__) + + +class PluginProxy(object): + """Proxy for plugin calls. Essentially a closure bound to the + given call and plugin list. + + The plugin proxy also must be bound to a particular plugin + interface specification, so that it knows what calls are available + and any special handling that is required for each call. + """ + interface = IPluginInterface + def __init__(self, call, plugins): + try: + self.method = getattr(self.interface, call) + except AttributeError: + raise AttributeError("%s is not a valid %s method" + % (call, self.interface.__name__)) + self.call = self.makeCall(call) + self.plugins = [] + for p in plugins: + self.addPlugin(p, call) + + def __call__(self, *arg, **kw): + return self.call(*arg, **kw) + + def addPlugin(self, plugin, call): + """Add plugin to my list of plugins to call, if it has the attribute + I'm bound to. + """ + meth = getattr(plugin, call, None) + if meth is not None: + if call == 'loadTestsFromModule' and \ + len(inspect.getargspec(meth)[0]) == 2: + orig_meth = meth + meth = lambda module, path, **kwargs: orig_meth(module) + self.plugins.append((plugin, meth)) + + def makeCall(self, call): + if call == 'loadTestsFromNames': + # special case -- load tests from names behaves somewhat differently + # from other chainable calls, because plugins return a tuple, only + # part of which can be chained to the next plugin. + return self._loadTestsFromNames + + meth = self.method + if getattr(meth, 'generative', False): + # call all plugins and yield a flattened iterator of their results + return lambda *arg, **kw: list(self.generate(*arg, **kw)) + elif getattr(meth, 'chainable', False): + return self.chain + else: + # return a value from the first plugin that returns non-None + return self.simple + + def chain(self, *arg, **kw): + """Call plugins in a chain, where the result of each plugin call is + sent to the next plugin as input. The final output result is returned. + """ + result = None + # extract the static arguments (if any) from arg so they can + # be passed to each plugin call in the chain + static = [a for (static, a) + in zip(getattr(self.method, 'static_args', []), arg) + if static] + for p, meth in self.plugins: + result = meth(*arg, **kw) + arg = static[:] + arg.append(result) + return result + + def generate(self, *arg, **kw): + """Call all plugins, yielding each item in each non-None result. + """ + for p, meth in self.plugins: + result = None + try: + result = meth(*arg, **kw) + if result is not None: + for r in result: + yield r + except (KeyboardInterrupt, SystemExit): + raise + except: + exc = sys.exc_info() + yield Failure(*exc) + continue + + def simple(self, *arg, **kw): + """Call all plugins, returning the first non-None result. + """ + for p, meth in self.plugins: + result = meth(*arg, **kw) + if result is not None: + return result + + def _loadTestsFromNames(self, names, module=None): + """Chainable but not quite normal. Plugins return a tuple of + (tests, names) after processing the names. The tests are added + to a suite that is accumulated throughout the full call, while + names are input for the next plugin in the chain. + """ + suite = [] + for p, meth in self.plugins: + result = meth(names, module=module) + if result is not None: + suite_part, names = result + if suite_part: + suite.extend(suite_part) + return suite, names + + +class NoPlugins(object): + """Null Plugin manager that has no plugins.""" + interface = IPluginInterface + def __init__(self): + self._plugins = self.plugins = () + + def __iter__(self): + return () + + def _doNothing(self, *args, **kwds): + pass + + def _emptyIterator(self, *args, **kwds): + return () + + def __getattr__(self, call): + method = getattr(self.interface, call) + if getattr(method, "generative", False): + return self._emptyIterator + else: + return self._doNothing + + def addPlugin(self, plug): + raise NotImplementedError() + + def addPlugins(self, plugins): + raise NotImplementedError() + + def configure(self, options, config): + pass + + def loadPlugins(self): + pass + + def sort(self): + pass + + +class PluginManager(object): + """Base class for plugin managers. Does not implement loadPlugins, so it + may only be used with a static list of plugins. + + The basic functionality of a plugin manager is to proxy all unknown + attributes through a ``PluginProxy`` to a list of plugins. + + Note that the list of plugins *may not* be changed after the first plugin + call. + """ + proxyClass = PluginProxy + + def __init__(self, plugins=(), proxyClass=None): + self._plugins = [] + self._proxies = {} + if plugins: + self.addPlugins(plugins) + if proxyClass is not None: + self.proxyClass = proxyClass + + def __getattr__(self, call): + try: + return self._proxies[call] + except KeyError: + proxy = self.proxyClass(call, self._plugins) + self._proxies[call] = proxy + return proxy + + def __iter__(self): + return iter(self.plugins) + + def addPlugin(self, plug): + # allow, for instance, plugins loaded via entry points to + # supplant builtin plugins. + new_name = getattr(plug, 'name', object()) + self._plugins[:] = [p for p in self._plugins + if getattr(p, 'name', None) != new_name] + self._plugins.append(plug) + + def addPlugins(self, plugins): + for plug in plugins: + self.addPlugin(plug) + + def configure(self, options, config): + """Configure the set of plugins with the given options + and config instance. After configuration, disabled plugins + are removed from the plugins list. + """ + log.debug("Configuring plugins") + self.config = config + cfg = PluginProxy('configure', self._plugins) + cfg(options, config) + enabled = [plug for plug in self._plugins if plug.enabled] + self.plugins = enabled + self.sort() + log.debug("Plugins enabled: %s", enabled) + + def loadPlugins(self): + pass + + def sort(self): + return sort_list(self._plugins, lambda x: getattr(x, 'score', 1), reverse=True) + + def _get_plugins(self): + return self._plugins + + def _set_plugins(self, plugins): + self._plugins = [] + self.addPlugins(plugins) + + plugins = property(_get_plugins, _set_plugins, None, + """Access the list of plugins managed by + this plugin manager""") + + +class ZeroNinePlugin: + """Proxy for 0.9 plugins, adapts 0.10 calls to 0.9 standard. + """ + def __init__(self, plugin): + self.plugin = plugin + + def options(self, parser, env=os.environ): + self.plugin.add_options(parser, env) + + def addError(self, test, err): + if not hasattr(self.plugin, 'addError'): + return + # switch off to addSkip, addDeprecated if those types + from nose.exc import SkipTest, DeprecatedTest + ec, ev, tb = err + if issubclass(ec, SkipTest): + if not hasattr(self.plugin, 'addSkip'): + return + return self.plugin.addSkip(test.test) + elif issubclass(ec, DeprecatedTest): + if not hasattr(self.plugin, 'addDeprecated'): + return + return self.plugin.addDeprecated(test.test) + # add capt + capt = test.capturedOutput + return self.plugin.addError(test.test, err, capt) + + def loadTestsFromFile(self, filename): + if hasattr(self.plugin, 'loadTestsFromPath'): + return self.plugin.loadTestsFromPath(filename) + + def addFailure(self, test, err): + if not hasattr(self.plugin, 'addFailure'): + return + # add capt and tbinfo + capt = test.capturedOutput + tbinfo = test.tbinfo + return self.plugin.addFailure(test.test, err, capt, tbinfo) + + def addSuccess(self, test): + if not hasattr(self.plugin, 'addSuccess'): + return + capt = test.capturedOutput + self.plugin.addSuccess(test.test, capt) + + def startTest(self, test): + if not hasattr(self.plugin, 'startTest'): + return + return self.plugin.startTest(test.test) + + def stopTest(self, test): + if not hasattr(self.plugin, 'stopTest'): + return + return self.plugin.stopTest(test.test) + + def __getattr__(self, val): + return getattr(self.plugin, val) + + +class EntryPointPluginManager(PluginManager): + """Plugin manager that loads plugins from the `nose.plugins` and + `nose.plugins.0.10` entry points. + """ + entry_points = (('nose.plugins.0.10', None), + ('nose.plugins', ZeroNinePlugin)) + + def loadPlugins(self): + """Load plugins by iterating the `nose.plugins` entry point. + """ + super(EntryPointPluginManager, self).loadPlugins() + from pkg_resources import iter_entry_points + + loaded = {} + for entry_point, adapt in self.entry_points: + for ep in iter_entry_points(entry_point): + if ep.name in loaded: + continue + loaded[ep.name] = True + log.debug('%s load plugin %s', self.__class__.__name__, ep) + try: + plugcls = ep.load() + except KeyboardInterrupt: + raise + except Exception, e: + # never want a plugin load to kill the test run + # but we can't log here because the logger is not yet + # configured + warn("Unable to load plugin %s: %s" % (ep, e), + RuntimeWarning) + continue + if adapt: + plug = adapt(plugcls()) + else: + plug = plugcls() + self.addPlugin(plug) + + +class BuiltinPluginManager(PluginManager): + """Plugin manager that loads plugins from the list in + `nose.plugins.builtin`. + """ + def loadPlugins(self): + """Load plugins in nose.plugins.builtin + """ + from nose.plugins import builtin + for plug in builtin.plugins: + self.addPlugin(plug()) + super(BuiltinPluginManager, self).loadPlugins() + +try: + import pkg_resources + class DefaultPluginManager(BuiltinPluginManager, EntryPointPluginManager): + pass +except ImportError: + DefaultPluginManager = BuiltinPluginManager + + +class RestrictedPluginManager(DefaultPluginManager): + """Plugin manager that restricts the plugin list to those not + excluded by a list of exclude methods. Any plugin that implements + an excluded method will be removed from the manager's plugin list + after plugins are loaded. + """ + def __init__(self, plugins=(), exclude=(), load=True): + DefaultPluginManager.__init__(self, plugins) + self.load = load + self.exclude = exclude + self.excluded = [] + self._excludedOpts = None + + def excludedOption(self, name): + if self._excludedOpts is None: + from optparse import OptionParser + self._excludedOpts = OptionParser(add_help_option=False) + for plugin in self.excluded: + plugin.options(self._excludedOpts, env={}) + return self._excludedOpts.get_option('--' + name) + + def loadPlugins(self): + if self.load: + DefaultPluginManager.loadPlugins(self) + allow = [] + for plugin in self.plugins: + ok = True + for method in self.exclude: + if hasattr(plugin, method): + ok = False + self.excluded.append(plugin) + break + if ok: + allow.append(plugin) + self.plugins = allow diff --git a/nose/plugins/multiprocess.py b/nose/plugins/multiprocess.py new file mode 100644 index 0000000..260cbf8 --- /dev/null +++ b/nose/plugins/multiprocess.py @@ -0,0 +1,798 @@ +""" +Overview +======== + +The multiprocess plugin enables you to distribute your test run among a set of +worker processes that run tests in parallel. This can speed up CPU-bound test +runs (as long as the number of work processeses is around the number of +processors or cores available), but is mainly useful for IO-bound tests that +spend most of their time waiting for data to arrive from someplace else. + +.. note :: + + See :doc:`../doc_tests/test_multiprocess/multiprocess` for + additional documentation and examples. Use of this plugin on python + 2.5 or earlier requires the multiprocessing_ module, also available + from PyPI. + +.. _multiprocessing : http://code.google.com/p/python-multiprocessing/ + +How tests are distributed +========================= + +The ideal case would be to dispatch each test to a worker process +separately. This ideal is not attainable in all cases, however, because many +test suites depend on context (class, module or package) fixtures. + +The plugin can't know (unless you tell it -- see below!) if a context fixture +can be called many times concurrently (is re-entrant), or if it can be shared +among tests running in different processes. Therefore, if a context has +fixtures, the default behavior is to dispatch the entire suite to a worker as +a unit. + +Controlling distribution +^^^^^^^^^^^^^^^^^^^^^^^^ + +There are two context-level variables that you can use to control this default +behavior. + +If a context's fixtures are re-entrant, set ``_multiprocess_can_split_ = True`` +in the context, and the plugin will dispatch tests in suites bound to that +context as if the context had no fixtures. This means that the fixtures will +execute concurrently and multiple times, typically once per test. + +If a context's fixtures can be shared by tests running in different processes +-- such as a package-level fixture that starts an external http server or +initializes a shared database -- then set ``_multiprocess_shared_ = True`` in +the context. These fixtures will then execute in the primary nose process, and +tests in those contexts will be individually dispatched to run in parallel. + +How results are collected and reported +====================================== + +As each test or suite executes in a worker process, results (failures, errors, +and specially handled exceptions like SkipTest) are collected in that +process. When the worker process finishes, it returns results to the main +nose process. There, any progress output is printed (dots!), and the +results from the test run are combined into a consolidated result +set. When results have been received for all dispatched tests, or all +workers have died, the result summary is output as normal. + +Beware! +======= + +Not all test suites will benefit from, or even operate correctly using, this +plugin. For example, CPU-bound tests will run more slowly if you don't have +multiple processors. There are also some differences in plugin +interactions and behaviors due to the way in which tests are dispatched and +loaded. In general, test loading under this plugin operates as if it were +always in directed mode instead of discovered mode. For instance, doctests +in test modules will always be found when using this plugin with the doctest +plugin. + +But the biggest issue you will face is probably concurrency. Unless you +have kept your tests as religiously pure unit tests, with no side-effects, no +ordering issues, and no external dependencies, chances are you will experience +odd, intermittent and unexplainable failures and errors when using this +plugin. This doesn't necessarily mean the plugin is broken; it may mean that +your test suite is not safe for concurrency. + +New Features in 1.1.0 +===================== + +* functions generated by test generators are now added to the worker queue + making them multi-threaded. +* fixed timeout functionality, now functions will be terminated with a + TimedOutException exception when they exceed their execution time. The + worker processes are not terminated. +* added ``--process-restartworker`` option to restart workers once they are + done, this helps control memory usage. Sometimes memory leaks can accumulate + making long runs very difficult. +* added global _instantiate_plugins to configure which plugins are started + on the worker processes. + +""" + +import logging +import os +import sys +import time +import traceback +import unittest +import pickle +import signal +import nose.case +from nose.core import TextTestRunner +from nose import failure +from nose import loader +from nose.plugins.base import Plugin +from nose.pyversion import bytes_ +from nose.result import TextTestResult +from nose.suite import ContextSuite +from nose.util import test_address +try: + # 2.7+ + from unittest.runner import _WritelnDecorator +except ImportError: + from unittest import _WritelnDecorator +from Queue import Empty +from warnings import warn +try: + from cStringIO import StringIO +except ImportError: + import StringIO + +# this is a list of plugin classes that will be checked for and created inside +# each worker process +_instantiate_plugins = None + +log = logging.getLogger(__name__) + +Process = Queue = Pool = Event = Value = Array = None + +class TimedOutException(Exception): + def __init__(self, value = "Timed Out"): + self.value = value + def __str__(self): + return repr(self.value) + +def _import_mp(): + global Process, Queue, Pool, Event, Value, Array + try: + from multiprocessing import Manager, Process + m = Manager() + Queue, Pool, Event, Value, Array = ( + m.Queue, m.Pool, m.Event, m.Value, m.Array + ) + except ImportError: + warn("multiprocessing module is not available, multiprocess plugin " + "cannot be used", RuntimeWarning) + + +class TestLet: + def __init__(self, case): + try: + self._id = case.id() + except AttributeError: + pass + self._short_description = case.shortDescription() + self._str = str(case) + + def id(self): + return self._id + + def shortDescription(self): + return self._short_description + + def __str__(self): + return self._str + +class MultiProcess(Plugin): + """ + Run tests in multiple processes. Requires processing module. + """ + score = 1000 + status = {} + + def options(self, parser, env): + """ + Register command-line options. + """ + parser.add_option("--processes", action="store", + default=env.get('NOSE_PROCESSES', 0), + dest="multiprocess_workers", + metavar="NUM", + help="Spread test run among this many processes. " + "Set a number equal to the number of processors " + "or cores in your machine for best results. " + "[NOSE_PROCESSES]") + parser.add_option("--process-timeout", action="store", + default=env.get('NOSE_PROCESS_TIMEOUT', 10), + dest="multiprocess_timeout", + metavar="SECONDS", + help="Set timeout for return of results from each " + "test runner process. [NOSE_PROCESS_TIMEOUT]") + parser.add_option("--process-restartworker", action="store_true", + default=env.get('NOSE_PROCESS_RESTARTWORKER', False), + dest="multiprocess_restartworker", + help="If set, will restart each worker process once" + " their tests are done, this helps control memory " + "leaks from killing the system. " + "[NOSE_PROCESS_RESTARTWORKER]") + + def configure(self, options, config): + """ + Configure plugin. + """ + try: + self.status.pop('active') + except KeyError: + pass + if not hasattr(options, 'multiprocess_workers'): + self.enabled = False + return + # don't start inside of a worker process + if config.worker: + return + self.config = config + try: + workers = int(options.multiprocess_workers) + except (TypeError, ValueError): + workers = 0 + if workers: + _import_mp() + if Process is None: + self.enabled = False + return + self.enabled = True + self.config.multiprocess_workers = workers + t = float(options.multiprocess_timeout) + self.config.multiprocess_timeout = t + r = int(options.multiprocess_restartworker) + self.config.multiprocess_restartworker = r + self.status['active'] = True + + def prepareTestLoader(self, loader): + """Remember loader class so MultiProcessTestRunner can instantiate + the right loader. + """ + self.loaderClass = loader.__class__ + + def prepareTestRunner(self, runner): + """Replace test runner with MultiProcessTestRunner. + """ + # replace with our runner class + return MultiProcessTestRunner(stream=runner.stream, + verbosity=self.config.verbosity, + config=self.config, + loaderClass=self.loaderClass) + +class MultiProcessTestRunner(TextTestRunner): + waitkilltime = 5.0 # max time to wait to terminate a process that does not + # respond to SIGINT + def __init__(self, **kw): + self.loaderClass = kw.pop('loaderClass', loader.defaultTestLoader) + super(MultiProcessTestRunner, self).__init__(**kw) + + def run(self, test): + """ + Execute the test (which may be a test suite). If the test is a suite, + distribute it out among as many processes as have been configured, at + as fine a level as is possible given the context fixtures defined in + the suite or any sub-suites. + + """ + log.debug("%s.run(%s) (%s)", self, test, os.getpid()) + wrapper = self.config.plugins.prepareTest(test) + if wrapper is not None: + test = wrapper + + # plugins can decorate or capture the output stream + wrapped = self.config.plugins.setOutputStream(self.stream) + if wrapped is not None: + self.stream = wrapped + + testQueue = Queue() + resultQueue = Queue() + tasks = [] + completed = [] + workers = [] + to_teardown = [] + shouldStop = Event() + + result = self._makeResult() + start = time.time() + + # dispatch and collect results + # put indexes only on queue because tests aren't picklable + for case in self.nextBatch(test): + log.debug("Next batch %s (%s)", case, type(case)) + if (isinstance(case, nose.case.Test) and + isinstance(case.test, failure.Failure)): + log.debug("Case is a Failure") + case(result) # run here to capture the failure + continue + # handle shared fixtures + if isinstance(case, ContextSuite) and case.context is failure.Failure: + log.debug("Case is a Failure") + case(result) # run here to capture the failure + continue + elif isinstance(case, ContextSuite) and self.sharedFixtures(case): + log.debug("%s has shared fixtures", case) + try: + case.setUp() + except (KeyboardInterrupt, SystemExit): + raise + except: + log.debug("%s setup failed", sys.exc_info()) + result.addError(case, sys.exc_info()) + else: + to_teardown.append(case) + for _t in case: + test_addr = self.addtask(testQueue,tasks,_t) + log.debug("Queued shared-fixture test %s (%s) to %s", + len(tasks), test_addr, testQueue) + + else: + test_addr = self.addtask(testQueue,tasks,case) + log.debug("Queued test %s (%s) to %s", + len(tasks), test_addr, testQueue) + + log.debug("Starting %s workers", self.config.multiprocess_workers) + for i in range(self.config.multiprocess_workers): + currentaddr = Value('c',bytes_('')) + currentstart = Value('d',0.0) + keyboardCaught = Event() + p = Process(target=runner, args=(i, testQueue, resultQueue, + currentaddr, currentstart, + keyboardCaught, shouldStop, + self.loaderClass, + result.__class__, + pickle.dumps(self.config))) + p.currentaddr = currentaddr + p.currentstart = currentstart + p.keyboardCaught = keyboardCaught + # p.setDaemon(True) + p.start() + workers.append(p) + log.debug("Started worker process %s", i+1) + + total_tasks = len(tasks) + # need to keep track of the next time to check for timeouts in case + # more than one process times out at the same time. + nexttimeout=self.config.multiprocess_timeout + while tasks: + log.debug("Waiting for results (%s/%s tasks), next timeout=%.3fs", + len(completed), total_tasks,nexttimeout) + try: + iworker, addr, newtask_addrs, batch_result = resultQueue.get( + timeout=nexttimeout) + log.debug('Results received for worker %d, %s, new tasks: %d', + iworker,addr,len(newtask_addrs)) + try: + try: + tasks.remove(addr) + except ValueError: + log.warn('worker %s failed to remove from tasks: %s', + iworker,addr) + total_tasks += len(newtask_addrs) + for newaddr in newtask_addrs: + tasks.append(newaddr) + except KeyError: + log.debug("Got result for unknown task? %s", addr) + log.debug("current: %s",str(list(tasks)[0])) + else: + completed.append([addr,batch_result]) + self.consolidate(result, batch_result) + if (self.config.stopOnError + and not result.wasSuccessful()): + # set the stop condition + shouldStop.set() + break + if self.config.multiprocess_restartworker: + log.debug('joining worker %s',iworker) + # wait for working, but not that important if worker + # cannot be joined in fact, for workers that add to + # testQueue, they will not terminate until all their + # items are read + workers[iworker].join(timeout=1) + if not shouldStop.is_set() and not testQueue.empty(): + log.debug('starting new process on worker %s',iworker) + currentaddr = Value('c',bytes_('')) + currentstart = Value('d',time.time()) + keyboardCaught = Event() + workers[iworker] = Process(target=runner, + args=(iworker, testQueue, + resultQueue, + currentaddr, + currentstart, + keyboardCaught, + shouldStop, + self.loaderClass, + result.__class__, + pickle.dumps(self.config))) + workers[iworker].currentaddr = currentaddr + workers[iworker].currentstart = currentstart + workers[iworker].keyboardCaught = keyboardCaught + workers[iworker].start() + except Empty: + log.debug("Timed out with %s tasks pending " + "(empty testQueue=%d): %s", + len(tasks),testQueue.empty(),str(tasks)) + any_alive = False + for iworker, w in enumerate(workers): + if w.is_alive(): + worker_addr = bytes_(w.currentaddr.value,'ascii') + timeprocessing = time.time() - w.currentstart.value + if ( len(worker_addr) == 0 + and timeprocessing > self.config.multiprocess_timeout-0.1): + log.debug('worker %d has finished its work item, ' + 'but is not exiting? do we wait for it?', + iworker) + else: + any_alive = True + if (len(worker_addr) > 0 + and timeprocessing > self.config.multiprocess_timeout-0.1): + log.debug('timed out worker %s: %s', + iworker,worker_addr) + w.currentaddr.value = bytes_('') + # If the process is in C++ code, sending a SIGINT + # might not send a python KeybordInterrupt exception + # therefore, send multiple signals until an + # exception is caught. If this takes too long, then + # terminate the process + w.keyboardCaught.clear() + startkilltime = time.time() + while not w.keyboardCaught.is_set() and w.is_alive(): + if time.time()-startkilltime > self.waitkilltime: + # have to terminate... + log.error("terminating worker %s",iworker) + w.terminate() + currentaddr = Value('c',bytes_('')) + currentstart = Value('d',time.time()) + keyboardCaught = Event() + workers[iworker] = Process(target=runner, + args=(iworker, testQueue, resultQueue, + currentaddr, currentstart, + keyboardCaught, shouldStop, + self.loaderClass, + result.__class__, + pickle.dumps(self.config))) + workers[iworker].currentaddr = currentaddr + workers[iworker].currentstart = currentstart + workers[iworker].keyboardCaught = keyboardCaught + workers[iworker].start() + # there is a small probability that the + # terminated process might send a result, + # which has to be specially handled or + # else processes might get orphaned. + w = workers[iworker] + break + os.kill(w.pid, signal.SIGINT) + time.sleep(0.1) + if not any_alive and testQueue.empty(): + log.debug("All workers dead") + break + nexttimeout=self.config.multiprocess_timeout + for w in workers: + if w.is_alive() and len(w.currentaddr.value) > 0: + timeprocessing = time.time()-w.currentstart.value + if timeprocessing <= self.config.multiprocess_timeout: + nexttimeout = min(nexttimeout, + self.config.multiprocess_timeout-timeprocessing) + + log.debug("Completed %s tasks (%s remain)", len(completed), len(tasks)) + + for case in to_teardown: + log.debug("Tearing down shared fixtures for %s", case) + try: + case.tearDown() + except (KeyboardInterrupt, SystemExit): + raise + except: + result.addError(case, sys.exc_info()) + + stop = time.time() + + # first write since can freeze on shutting down processes + result.printErrors() + result.printSummary(start, stop) + self.config.plugins.finalize(result) + + log.debug("Tell all workers to stop") + for w in workers: + if w.is_alive(): + testQueue.put('STOP', block=False) + + # wait for the workers to end + try: + for iworker,worker in enumerate(workers): + if worker.is_alive(): + log.debug('joining worker %s',iworker) + worker.join()#10) + if worker.is_alive(): + log.debug('failed to join worker %s',iworker) + except KeyboardInterrupt: + log.info('parent received ctrl-c') + for worker in workers: + worker.terminate() + worker.join() + + return result + + def addtask(testQueue,tasks,case): + arg = None + if isinstance(case,nose.case.Test) and hasattr(case.test,'arg'): + # this removes the top level descriptor and allows real function + # name to be returned + case.test.descriptor = None + arg = case.test.arg + test_addr = MultiProcessTestRunner.address(case) + testQueue.put((test_addr,arg), block=False) + if arg is not None: + test_addr += str(arg) + if tasks is not None: + tasks.append(test_addr) + return test_addr + addtask = staticmethod(addtask) + + def address(case): + if hasattr(case, 'address'): + file, mod, call = case.address() + elif hasattr(case, 'context'): + file, mod, call = test_address(case.context) + else: + raise Exception("Unable to convert %s to address" % case) + parts = [] + if file is None: + if mod is None: + raise Exception("Unaddressable case %s" % case) + else: + parts.append(mod) + else: + # strip __init__.py(c) from end of file part + # if present, having it there confuses loader + dirname, basename = os.path.split(file) + if basename.startswith('__init__'): + file = dirname + parts.append(file) + if call is not None: + parts.append(call) + return ':'.join(map(str, parts)) + address = staticmethod(address) + + def nextBatch(self, test): + # allows tests or suites to mark themselves as not safe + # for multiprocess execution + if hasattr(test, 'context'): + if not getattr(test.context, '_multiprocess_', True): + return + + if ((isinstance(test, ContextSuite) + and test.hasFixtures(self.checkCanSplit)) + or not getattr(test, 'can_split', True) + or not isinstance(test, unittest.TestSuite)): + # regular test case, or a suite with context fixtures + + # special case: when run like nosetests path/to/module.py + # the top-level suite has only one item, and it shares + # the same context as that item. In that case, we want the + # item, not the top-level suite + if isinstance(test, ContextSuite): + contained = list(test) + if (len(contained) == 1 + and getattr(contained[0], + 'context', None) == test.context): + test = contained[0] + yield test + else: + # Suite is without fixtures at this level; but it may have + # fixtures at any deeper level, so we need to examine it all + # the way down to the case level + for case in test: + for batch in self.nextBatch(case): + yield batch + + def checkCanSplit(self, context, fixt): + """ + Callback that we use to check whether the fixtures found in a + context or ancestor are ones we care about. + + Contexts can tell us that their fixtures are reentrant by setting + _multiprocess_can_split_. So if we see that, we return False to + disregard those fixtures. + """ + if not fixt: + return False + if getattr(context, '_multiprocess_can_split_', False): + return False + return True + + def sharedFixtures(self, case): + context = getattr(case, 'context', None) + if not context: + return False + return getattr(context, '_multiprocess_shared_', False) + + def consolidate(self, result, batch_result): + log.debug("batch result is %s" , batch_result) + try: + output, testsRun, failures, errors, errorClasses = batch_result + except ValueError: + log.debug("result in unexpected format %s", batch_result) + failure.Failure(*sys.exc_info())(result) + return + self.stream.write(output) + result.testsRun += testsRun + result.failures.extend(failures) + result.errors.extend(errors) + for key, (storage, label, isfail) in errorClasses.items(): + if key not in result.errorClasses: + # Ordinarily storage is result attribute + # but it's only processed through the errorClasses + # dict, so it's ok to fake it here + result.errorClasses[key] = ([], label, isfail) + mystorage, _junk, _junk = result.errorClasses[key] + mystorage.extend(storage) + log.debug("Ran %s tests (total: %s)", testsRun, result.testsRun) + + +def runner(ix, testQueue, resultQueue, currentaddr, currentstart, + keyboardCaught, shouldStop, loaderClass, resultClass, config): + try: + try: + return __runner(ix, testQueue, resultQueue, currentaddr, currentstart, + keyboardCaught, shouldStop, loaderClass, resultClass, config) + except KeyboardInterrupt: + log.debug('Worker %s keyboard interrupt, stopping',ix) + except Empty: + log.debug("Worker %s timed out waiting for tasks", ix) + +def __runner(ix, testQueue, resultQueue, currentaddr, currentstart, + keyboardCaught, shouldStop, loaderClass, resultClass, config): + + config = pickle.loads(config) + dummy_parser = config.parserClass() + if _instantiate_plugins is not None: + for pluginclass in _instantiate_plugins: + plugin = pluginclass() + plugin.addOptions(dummy_parser,{}) + config.plugins.addPlugin(plugin) + config.plugins.configure(config.options,config) + config.plugins.begin() + log.debug("Worker %s executing, pid=%d", ix,os.getpid()) + loader = loaderClass(config=config) + loader.suiteClass.suiteClass = NoSharedFixtureContextSuite + + def get(): + return testQueue.get(timeout=config.multiprocess_timeout) + + def makeResult(): + stream = _WritelnDecorator(StringIO()) + result = resultClass(stream, descriptions=1, + verbosity=config.verbosity, + config=config) + plug_result = config.plugins.prepareTestResult(result) + if plug_result: + return plug_result + return result + + def batch(result): + failures = [(TestLet(c), err) for c, err in result.failures] + errors = [(TestLet(c), err) for c, err in result.errors] + errorClasses = {} + for key, (storage, label, isfail) in result.errorClasses.items(): + errorClasses[key] = ([(TestLet(c), err) for c, err in storage], + label, isfail) + return ( + result.stream.getvalue(), + result.testsRun, + failures, + errors, + errorClasses) + for test_addr, arg in iter(get, 'STOP'): + if shouldStop.is_set(): + log.exception('Worker %d STOPPED',ix) + break + result = makeResult() + test = loader.loadTestsFromNames([test_addr]) + test.testQueue = testQueue + test.tasks = [] + test.arg = arg + log.debug("Worker %s Test is %s (%s)", ix, test_addr, test) + try: + if arg is not None: + test_addr = test_addr + str(arg) + currentaddr.value = bytes_(test_addr) + currentstart.value = time.time() + test(result) + currentaddr.value = bytes_('') + resultQueue.put((ix, test_addr, test.tasks, batch(result))) + except KeyboardInterrupt: + keyboardCaught.set() + if len(currentaddr.value) > 0: + log.exception('Worker %s keyboard interrupt, failing ' + 'current test %s',ix,test_addr) + currentaddr.value = bytes_('') + failure.Failure(*sys.exc_info())(result) + resultQueue.put((ix, test_addr, test.tasks, batch(result))) + else: + log.debug('Worker %s test %s timed out',ix,test_addr) + resultQueue.put((ix, test_addr, test.tasks, batch(result))) + except SystemExit: + currentaddr.value = bytes_('') + log.exception('Worker %s system exit',ix) + raise + except: + currentaddr.value = bytes_('') + log.exception("Worker %s error running test or returning " + "results",ix) + failure.Failure(*sys.exc_info())(result) + resultQueue.put((ix, test_addr, test.tasks, batch(result))) + if config.multiprocess_restartworker: + break + log.debug("Worker %s ending", ix) + + +class NoSharedFixtureContextSuite(ContextSuite): + """ + Context suite that never fires shared fixtures. + + When a context sets _multiprocess_shared_, fixtures in that context + are executed by the main process. Using this suite class prevents them + from executing in the runner process as well. + + """ + testQueue = None + tasks = None + arg = None + def setupContext(self, context): + if getattr(context, '_multiprocess_shared_', False): + return + super(NoSharedFixtureContextSuite, self).setupContext(context) + + def teardownContext(self, context): + if getattr(context, '_multiprocess_shared_', False): + return + super(NoSharedFixtureContextSuite, self).teardownContext(context) + def run(self, result): + """Run tests in suite inside of suite fixtures. + """ + # proxy the result for myself + log.debug("suite %s (%s) run called, tests: %s", + id(self), self, self._tests) + if self.resultProxy: + result, orig = self.resultProxy(result, self), result + else: + result, orig = result, result + try: + self.setUp() + except KeyboardInterrupt: + raise + except: + self.error_context = 'setup' + result.addError(self, self._exc_info()) + return + try: + localtests = [test for test in self._tests] + if len(localtests) > 1 and self.testQueue is not None: + log.debug("queue %d tests"%len(localtests)) + for test in localtests: + if isinstance(test.test,nose.failure.Failure): + # proably failed in the generator, so execute directly + # to get the exception + test(orig) + else: + MultiProcessTestRunner.addtask(self.testQueue, + self.tasks, test) + else: + for test in localtests: + if (isinstance(test,nose.case.Test) + and self.arg is not None): + test.test.arg = self.arg + else: + test.arg = self.arg + test.testQueue = self.testQueue + test.tasks = self.tasks + if result.shouldStop: + log.debug("stopping") + break + # each nose.case.Test will create its own result proxy + # so the cases need the original result, to avoid proxy + # chains + try: + test(orig) + except KeyboardInterrupt,e: + err = (TimedOutException,TimedOutException(str(test)), + sys.exc_info()[2]) + test.config.plugins.addError(test,err) + orig.addError(test,err) + finally: + self.has_run = True + try: + self.tearDown() + except KeyboardInterrupt: + raise + except: + self.error_context = 'teardown' + result.addError(self, self._exc_info()) diff --git a/nose/plugins/plugintest.py b/nose/plugins/plugintest.py new file mode 100644 index 0000000..68e8941 --- /dev/null +++ b/nose/plugins/plugintest.py @@ -0,0 +1,416 @@ +""" +Testing Plugins +=============== + +The plugin interface is well-tested enough to safely unit test your +use of its hooks with some level of confidence. However, there is also +a mixin for unittest.TestCase called PluginTester that's designed to +test plugins in their native runtime environment. + +Here's a simple example with a do-nothing plugin and a composed suite. + + >>> import unittest + >>> from nose.plugins import Plugin, PluginTester + >>> class FooPlugin(Plugin): + ... pass + >>> class TestPluginFoo(PluginTester, unittest.TestCase): + ... activate = '--with-foo' + ... plugins = [FooPlugin()] + ... def test_foo(self): + ... for line in self.output: + ... # i.e. check for patterns + ... pass + ... + ... # or check for a line containing ... + ... assert "ValueError" in self.output + ... def makeSuite(self): + ... class TC(unittest.TestCase): + ... def runTest(self): + ... raise ValueError("I hate foo") + ... return unittest.TestSuite([TC()]) + ... + >>> res = unittest.TestResult() + >>> case = TestPluginFoo('test_foo') + >>> case(res) + >>> res.errors + [] + >>> res.failures + [] + >>> res.wasSuccessful() + True + >>> res.testsRun + 1 + +And here is a more complex example of testing a plugin that has extra +arguments and reads environment variables. + + >>> import unittest, os + >>> from nose.plugins import Plugin, PluginTester + >>> class FancyOutputter(Plugin): + ... name = "fancy" + ... def configure(self, options, conf): + ... Plugin.configure(self, options, conf) + ... if not self.enabled: + ... return + ... self.fanciness = 1 + ... if options.more_fancy: + ... self.fanciness = 2 + ... if 'EVEN_FANCIER' in self.env: + ... self.fanciness = 3 + ... + ... def options(self, parser, env=os.environ): + ... self.env = env + ... parser.add_option('--more-fancy', action='store_true') + ... Plugin.options(self, parser, env=env) + ... + ... def report(self, stream): + ... stream.write("FANCY " * self.fanciness) + ... + >>> class TestFancyOutputter(PluginTester, unittest.TestCase): + ... activate = '--with-fancy' # enables the plugin + ... plugins = [FancyOutputter()] + ... args = ['--more-fancy'] + ... env = {'EVEN_FANCIER': '1'} + ... + ... def test_fancy_output(self): + ... assert "FANCY FANCY FANCY" in self.output, ( + ... "got: %s" % self.output) + ... def makeSuite(self): + ... class TC(unittest.TestCase): + ... def runTest(self): + ... raise ValueError("I hate fancy stuff") + ... return unittest.TestSuite([TC()]) + ... + >>> res = unittest.TestResult() + >>> case = TestFancyOutputter('test_fancy_output') + >>> case(res) + >>> res.errors + [] + >>> res.failures + [] + >>> res.wasSuccessful() + True + >>> res.testsRun + 1 + +""" + +import re +import sys +from warnings import warn + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +__all__ = ['PluginTester', 'run'] + +from os import getpid +class MultiProcessFile(object): + """ + helper for testing multiprocessing + + multiprocessing poses a problem for doctests, since the strategy + of replacing sys.stdout/stderr with file-like objects then + inspecting the results won't work: the child processes will + write to the objects, but the data will not be reflected + in the parent doctest-ing process. + + The solution is to create file-like objects which will interact with + multiprocessing in a more desirable way. + + All processes can write to this object, but only the creator can read. + This allows the testing system to see a unified picture of I/O. + """ + def __init__(self): + # per advice at: + # http://docs.python.org/library/multiprocessing.html#all-platforms + self.__master = getpid() + self.__queue = Manager().Queue() + self.__buffer = StringIO() + self.softspace = 0 + + def buffer(self): + if getpid() != self.__master: + return + + from Queue import Empty + from collections import defaultdict + cache = defaultdict(str) + while True: + try: + pid, data = self.__queue.get_nowait() + except Empty: + break + if pid == (): + #show parent output after children + #this is what users see, usually + pid = ( 1e100, ) # googol! + cache[pid] += data + for pid in sorted(cache): + #self.__buffer.write( '%s wrote: %r\n' % (pid, cache[pid]) ) #DEBUG + self.__buffer.write( cache[pid] ) + def write(self, data): + # note that these pids are in the form of current_process()._identity + # rather than OS pids + from multiprocessing import current_process + pid = current_process()._identity + self.__queue.put((pid, data)) + def __iter__(self): + "getattr doesn't work for iter()" + self.buffer() + return self.__buffer + def seek(self, offset, whence=0): + self.buffer() + return self.__buffer.seek(offset, whence) + def getvalue(self): + self.buffer() + return self.__buffer.getvalue() + def __getattr__(self, attr): + return getattr(self.__buffer, attr) + +try: + from multiprocessing import Manager + Buffer = MultiProcessFile +except ImportError: + Buffer = StringIO + +class PluginTester(object): + """A mixin for testing nose plugins in their runtime environment. + + Subclass this and mix in unittest.TestCase to run integration/functional + tests on your plugin. When setUp() is called, the stub test suite is + executed with your plugin so that during an actual test you can inspect the + artifacts of how your plugin interacted with the stub test suite. + + - activate + + - the argument to send nosetests to activate the plugin + + - suitepath + + - if set, this is the path of the suite to test. Otherwise, you + will need to use the hook, makeSuite() + + - plugins + + - the list of plugins to make available during the run. Note + that this does not mean these plugins will be *enabled* during + the run -- only the plugins enabled by the activate argument + or other settings in argv or env will be enabled. + + - args + + - a list of arguments to add to the nosetests command, in addition to + the activate argument + + - env + + - optional dict of environment variables to send nosetests + + """ + activate = None + suitepath = None + args = None + env = {} + argv = None + plugins = [] + ignoreFiles = None + + def makeSuite(self): + """returns a suite object of tests to run (unittest.TestSuite()) + + If self.suitepath is None, this must be implemented. The returned suite + object will be executed with all plugins activated. It may return + None. + + Here is an example of a basic suite object you can return :: + + >>> import unittest + >>> class SomeTest(unittest.TestCase): + ... def runTest(self): + ... raise ValueError("Now do something, plugin!") + ... + >>> unittest.TestSuite([SomeTest()]) # doctest: +ELLIPSIS + ]> + + """ + raise NotImplementedError + + def _execPlugin(self): + """execute the plugin on the internal test suite. + """ + from nose.config import Config + from nose.core import TestProgram + from nose.plugins.manager import PluginManager + + suite = None + stream = Buffer() + conf = Config(env=self.env, + stream=stream, + plugins=PluginManager(plugins=self.plugins)) + if self.ignoreFiles is not None: + conf.ignoreFiles = self.ignoreFiles + if not self.suitepath: + suite = self.makeSuite() + + self.nose = TestProgram(argv=self.argv, config=conf, suite=suite, + exit=False) + self.output = AccessDecorator(stream) + + def setUp(self): + """runs nosetests with the specified test suite, all plugins + activated. + """ + self.argv = ['nosetests', self.activate] + if self.args: + self.argv.extend(self.args) + if self.suitepath: + self.argv.append(self.suitepath) + + self._execPlugin() + + +class AccessDecorator(object): + stream = None + _buf = None + def __init__(self, stream): + self.stream = stream + stream.seek(0) + self._buf = stream.read() + stream.seek(0) + def __contains__(self, val): + return val in self._buf + def __iter__(self): + return iter(self.stream) + def __str__(self): + return self._buf + + +def blankline_separated_blocks(text): + "a bunch of === characters is also considered a blank line" + block = [] + for line in text.splitlines(True): + block.append(line) + line = line.strip() + if not line or line.startswith('===') and not line.strip('='): + yield "".join(block) + block = [] + if block: + yield "".join(block) + + +def remove_stack_traces(out): + # this regexp taken from Python 2.5's doctest + traceback_re = re.compile(r""" + # Grab the traceback header. Different versions of Python have + # said different things on the first traceback line. + ^(?P Traceback\ \( + (?: most\ recent\ call\ last + | innermost\ last + ) \) : + ) + \s* $ # toss trailing whitespace on the header. + (?P .*?) # don't blink: absorb stuff until... + ^(?=\w) # a line *starts* with alphanum. + .*?(?P \w+ ) # exception name + (?P [:\n] .*) # the rest + """, re.VERBOSE | re.MULTILINE | re.DOTALL) + blocks = [] + for block in blankline_separated_blocks(out): + blocks.append(traceback_re.sub(r"\g\n...\n\g\g", block)) + return "".join(blocks) + + +def simplify_warnings(out): + warn_re = re.compile(r""" + # Cut the file and line no, up to the warning name + ^.*:\d+:\s + (?P\w+): \s+ # warning category + (?P.+) $ \n? # warning message + ^ .* $ # stack frame + """, re.VERBOSE | re.MULTILINE) + return warn_re.sub(r"\g: \g", out) + + +def remove_timings(out): + return re.sub( + r"Ran (\d+ tests?) in [0-9.]+s", r"Ran \1 in ...s", out) + + +def munge_nose_output_for_doctest(out): + """Modify nose output to make it easy to use in doctests.""" + out = remove_stack_traces(out) + out = simplify_warnings(out) + out = remove_timings(out) + return out.strip() + + +def run(*arg, **kw): + """ + Specialized version of nose.run for use inside of doctests that + test test runs. + + This version of run() prints the result output to stdout. Before + printing, the output is processed by replacing the timing + information with an ellipsis (...), removing traceback stacks, and + removing trailing whitespace. + + Use this version of run wherever you are writing a doctest that + tests nose (or unittest) test result output. + + Note: do not use doctest: +ELLIPSIS when testing nose output, + since ellipses ("test_foo ... ok") in your expected test runner + output may match multiple lines of output, causing spurious test + passes! + """ + from nose import run + from nose.config import Config + from nose.plugins.manager import PluginManager + + buffer = Buffer() + if 'config' not in kw: + plugins = kw.pop('plugins', []) + if isinstance(plugins, list): + plugins = PluginManager(plugins=plugins) + env = kw.pop('env', {}) + kw['config'] = Config(env=env, plugins=plugins) + if 'argv' not in kw: + kw['argv'] = ['nosetests', '-v'] + kw['config'].stream = buffer + + # Set up buffering so that all output goes to our buffer, + # or warn user if deprecated behavior is active. If this is not + # done, prints and warnings will either be out of place or + # disappear. + stderr = sys.stderr + stdout = sys.stdout + if kw.pop('buffer_all', False): + sys.stdout = sys.stderr = buffer + restore = True + else: + restore = False + warn("The behavior of nose.plugins.plugintest.run() will change in " + "the next release of nose. The current behavior does not " + "correctly account for output to stdout and stderr. To enable " + "correct behavior, use run_buffered() instead, or pass " + "the keyword argument buffer_all=True to run().", + DeprecationWarning, stacklevel=2) + try: + run(*arg, **kw) + finally: + if restore: + sys.stderr = stderr + sys.stdout = stdout + out = buffer.getvalue() + print munge_nose_output_for_doctest(out) + + +def run_buffered(*arg, **kw): + kw['buffer_all'] = True + run(*arg, **kw) + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/nose/plugins/prof.py b/nose/plugins/prof.py new file mode 100644 index 0000000..4d304a9 --- /dev/null +++ b/nose/plugins/prof.py @@ -0,0 +1,154 @@ +"""This plugin will run tests using the hotshot profiler, which is part +of the standard library. To turn it on, use the ``--with-profile`` option +or set the NOSE_WITH_PROFILE environment variable. Profiler output can be +controlled with the ``--profile-sort`` and ``--profile-restrict`` options, +and the profiler output file may be changed with ``--profile-stats-file``. + +See the `hotshot documentation`_ in the standard library documentation for +more details on the various output options. + +.. _hotshot documentation: http://docs.python.org/library/hotshot.html +""" + +try: + import hotshot + from hotshot import stats +except ImportError: + hotshot, stats = None, None +import logging +import os +import sys +import tempfile +from nose.plugins.base import Plugin +from nose.util import tolist + +log = logging.getLogger('nose.plugins') + +class Profile(Plugin): + """ + Use this plugin to run tests using the hotshot profiler. + """ + pfile = None + clean_stats_file = False + def options(self, parser, env): + """Register commandline options. + """ + if not self.available(): + return + Plugin.options(self, parser, env) + parser.add_option('--profile-sort', action='store', dest='profile_sort', + default=env.get('NOSE_PROFILE_SORT', 'cumulative'), + metavar="SORT", + help="Set sort order for profiler output") + parser.add_option('--profile-stats-file', action='store', + dest='profile_stats_file', + metavar="FILE", + default=env.get('NOSE_PROFILE_STATS_FILE'), + help='Profiler stats file; default is a new ' + 'temp file on each run') + parser.add_option('--profile-restrict', action='append', + dest='profile_restrict', + metavar="RESTRICT", + default=env.get('NOSE_PROFILE_RESTRICT'), + help="Restrict profiler output. See help for " + "pstats.Stats for details") + + def available(cls): + return hotshot is not None + available = classmethod(available) + + def begin(self): + """Create profile stats file and load profiler. + """ + if not self.available(): + return + self._create_pfile() + self.prof = hotshot.Profile(self.pfile) + + def configure(self, options, conf): + """Configure plugin. + """ + if not self.available(): + self.enabled = False + return + Plugin.configure(self, options, conf) + self.conf = conf + if options.profile_stats_file: + self.pfile = options.profile_stats_file + self.clean_stats_file = False + else: + self.pfile = None + self.clean_stats_file = True + self.fileno = None + self.sort = options.profile_sort + self.restrict = tolist(options.profile_restrict) + + def prepareTest(self, test): + """Wrap entire test run in :func:`prof.runcall`. + """ + if not self.available(): + return + log.debug('preparing test %s' % test) + def run_and_profile(result, prof=self.prof, test=test): + self._create_pfile() + prof.runcall(test, result) + return run_and_profile + + def report(self, stream): + """Output profiler report. + """ + log.debug('printing profiler report') + self.prof.close() + prof_stats = stats.load(self.pfile) + prof_stats.sort_stats(self.sort) + + # 2.5 has completely different stream handling from 2.4 and earlier. + # Before 2.5, stats objects have no stream attribute; in 2.5 and later + # a reference sys.stdout is stored before we can tweak it. + compat_25 = hasattr(prof_stats, 'stream') + if compat_25: + tmp = prof_stats.stream + prof_stats.stream = stream + else: + tmp = sys.stdout + sys.stdout = stream + try: + if self.restrict: + log.debug('setting profiler restriction to %s', self.restrict) + prof_stats.print_stats(*self.restrict) + else: + prof_stats.print_stats() + finally: + if compat_25: + prof_stats.stream = tmp + else: + sys.stdout = tmp + + def finalize(self, result): + """Clean up stats file, if configured to do so. + """ + if not self.available(): + return + try: + self.prof.close() + except AttributeError: + # TODO: is this trying to catch just the case where not + # hasattr(self.prof, "close")? If so, the function call should be + # moved out of the try: suite. + pass + if self.clean_stats_file: + if self.fileno: + try: + os.close(self.fileno) + except OSError: + pass + try: + os.unlink(self.pfile) + except OSError: + pass + return None + + def _create_pfile(self): + if not self.pfile: + self.fileno, self.pfile = tempfile.mkstemp() + self.clean_stats_file = True diff --git a/nose/plugins/skip.py b/nose/plugins/skip.py new file mode 100644 index 0000000..27e5162 --- /dev/null +++ b/nose/plugins/skip.py @@ -0,0 +1,56 @@ +""" +This plugin installs a SKIP error class for the SkipTest exception. +When SkipTest is raised, the exception will be logged in the skipped +attribute of the result, 'S' or 'SKIP' (verbose) will be output, and +the exception will not be counted as an error or failure. This plugin +is enabled by default but may be disabled with the ``--no-skip`` option. +""" + +from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin + + +try: + # 2.7 + from unittest.case import SkipTest +except ImportError: + # 2.6 and below + class SkipTest(Exception): + """Raise this exception to mark a test as skipped. + """ + pass + + +class Skip(ErrorClassPlugin): + """ + Plugin that installs a SKIP error class for the SkipTest + exception. When SkipTest is raised, the exception will be logged + in the skipped attribute of the result, 'S' or 'SKIP' (verbose) + will be output, and the exception will not be counted as an error + or failure. + """ + enabled = True + skipped = ErrorClass(SkipTest, + label='SKIP', + isfailure=False) + + def options(self, parser, env): + """ + Add my options to command line. + """ + env_opt = 'NOSE_WITHOUT_SKIP' + parser.add_option('--no-skip', action='store_true', + dest='noSkip', default=env.get(env_opt, False), + help="Disable special handling of SkipTest " + "exceptions.") + + def configure(self, options, conf): + """ + Configure plugin. Skip plugin is enabled by default. + """ + if not self.can_configure: + return + self.conf = conf + disable = getattr(options, 'noSkip', False) + if disable: + self.enabled = False + diff --git a/nose/plugins/testid.py b/nose/plugins/testid.py new file mode 100644 index 0000000..80f282d --- /dev/null +++ b/nose/plugins/testid.py @@ -0,0 +1,306 @@ +""" +This plugin adds a test id (like #1) to each test name output. After +you've run once to generate test ids, you can re-run individual +tests by activating the plugin and passing the ids (with or +without the # prefix) instead of test names. + +For example, if your normal test run looks like:: + + % nosetests -v + tests.test_a ... ok + tests.test_b ... ok + tests.test_c ... ok + +When adding ``--with-id`` you'll see:: + + % nosetests -v --with-id + #1 tests.test_a ... ok + #2 tests.test_b ... ok + #2 tests.test_c ... ok + +Then you can re-run individual tests by supplying just an id number:: + + % nosetests -v --with-id 2 + #2 tests.test_b ... ok + +You can also pass multiple id numbers:: + + % nosetests -v --with-id 2 3 + #2 tests.test_b ... ok + #3 tests.test_c ... ok + +Since most shells consider '#' a special character, you can leave it out when +specifying a test id. + +Note that when run without the -v switch, no special output is displayed, but +the ids file is still written. + +Looping over failed tests +------------------------- + +This plugin also adds a mode that will direct the test runner to record +failed tests. Subsequent test runs will then run only the tests that failed +last time. Activate this mode with the ``--failed`` switch:: + + % nosetests -v --failed + #1 test.test_a ... ok + #2 test.test_b ... ERROR + #3 test.test_c ... FAILED + #4 test.test_d ... ok + +On the second run, only tests #2 and #3 will run:: + + % nosetests -v --failed + #2 test.test_b ... ERROR + #3 test.test_c ... FAILED + +As you correct errors and tests pass, they'll drop out of subsequent runs. + +First:: + + % nosetests -v --failed + #2 test.test_b ... ok + #3 test.test_c ... FAILED + +Second:: + + % nosetests -v --failed + #3 test.test_c ... FAILED + +When all tests pass, the full set will run on the next invocation. + +First:: + + % nosetests -v --failed + #3 test.test_c ... ok + +Second:: + + % nosetests -v --failed + #1 test.test_a ... ok + #2 test.test_b ... ok + #3 test.test_c ... ok + #4 test.test_d ... ok + +.. note :: + + If you expect to use ``--failed`` regularly, it's a good idea to always run + run using the ``--with-id`` option. This will ensure that an id file is + always created, allowing you to add ``--failed`` to the command line as soon + as you have failing tests. Otherwise, your first run using ``--failed`` will + (perhaps surprisingly) run *all* tests, because there won't be an id file + containing the record of failed tests from your previous run. + +""" +__test__ = False + +import logging +import os +from nose.plugins import Plugin +from nose.util import src, set + +try: + from pickle import dump, load +except ImportError: + from pickle import dump, load + +log = logging.getLogger(__name__) + + +class TestId(Plugin): + """ + Activate to add a test id (like #1) to each test name output. Activate + with --failed to rerun failing tests only. + """ + name = 'id' + idfile = None + collecting = True + loopOnFailed = False + + def options(self, parser, env): + """Register commandline options. + """ + Plugin.options(self, parser, env) + parser.add_option('--id-file', action='store', dest='testIdFile', + default='.noseids', metavar="FILE", + help="Store test ids found in test runs in this " + "file. Default is the file .noseids in the " + "working directory.") + parser.add_option('--failed', action='store_true', + dest='failed', default=False, + help="Run the tests that failed in the last " + "test run.") + + def configure(self, options, conf): + """Configure plugin. + """ + Plugin.configure(self, options, conf) + if options.failed: + self.enabled = True + self.loopOnFailed = True + log.debug("Looping on failed tests") + self.idfile = os.path.expanduser(options.testIdFile) + if not os.path.isabs(self.idfile): + self.idfile = os.path.join(conf.workingDir, self.idfile) + self.id = 1 + # Ids and tests are mirror images: ids are {id: test address} and + # tests are {test address: id} + self.ids = {} + self.tests = {} + self.failed = [] + self.source_names = [] + # used to track ids seen when tests is filled from + # loaded ids file + self._seen = {} + self._write_hashes = conf.verbosity >= 2 + + def finalize(self, result): + """Save new ids file, if needed. + """ + if result.wasSuccessful(): + self.failed = [] + if self.collecting: + ids = dict(list(zip(list(self.tests.values()), list(self.tests.keys())))) + else: + ids = self.ids + fh = open(self.idfile, 'wb') + dump({'ids': ids, + 'failed': self.failed, + 'source_names': self.source_names}, fh) + fh.close() + log.debug('Saved test ids: %s, failed %s to %s', + ids, self.failed, self.idfile) + + def loadTestsFromNames(self, names, module=None): + """Translate ids in the list of requested names into their + test addresses, if they are found in my dict of tests. + """ + log.debug('ltfn %s %s', names, module) + try: + fh = open(self.idfile, 'rb') + data = load(fh) + if 'ids' in data: + self.ids = data['ids'] + self.failed = data['failed'] + self.source_names = data['source_names'] + else: + # old ids field + self.ids = data + self.failed = [] + self.source_names = names + if self.ids: + self.id = max(self.ids) + 1 + self.tests = dict(list(zip(list(self.ids.values()), list(self.ids.keys())))) + else: + self.id = 1 + log.debug( + 'Loaded test ids %s tests %s failed %s sources %s from %s', + self.ids, self.tests, self.failed, self.source_names, + self.idfile) + fh.close() + except IOError: + log.debug('IO error reading %s', self.idfile) + + if self.loopOnFailed and self.failed: + self.collecting = False + names = self.failed + self.failed = [] + # I don't load any tests myself, only translate names like '#2' + # into the associated test addresses + translated = [] + new_source = [] + really_new = [] + for name in names: + trans = self.tr(name) + if trans != name: + translated.append(trans) + else: + new_source.append(name) + # names that are not ids and that are not in the current + # list of source names go into the list for next time + if new_source: + new_set = set(new_source) + old_set = set(self.source_names) + log.debug("old: %s new: %s", old_set, new_set) + really_new = [s for s in new_source + if not s in old_set] + if really_new: + # remember new sources + self.source_names.extend(really_new) + if not translated: + # new set of source names, no translations + # means "run the requested tests" + names = new_source + else: + # no new names to translate and add to id set + self.collecting = False + log.debug("translated: %s new sources %s names %s", + translated, really_new, names) + return (None, translated + really_new or names) + + def makeName(self, addr): + log.debug("Make name %s", addr) + filename, module, call = addr + if filename is not None: + head = src(filename) + else: + head = module + if call is not None: + return "%s:%s" % (head, call) + return head + + def setOutputStream(self, stream): + """Get handle on output stream so the plugin can print id #s + """ + self.stream = stream + + def startTest(self, test): + """Maybe output an id # before the test name. + + Example output:: + + #1 test.test ... ok + #2 test.test_two ... ok + + """ + adr = test.address() + log.debug('start test %s (%s)', adr, adr in self.tests) + if adr in self.tests: + if adr in self._seen: + self.write(' ') + else: + self.write('#%s ' % self.tests[adr]) + self._seen[adr] = 1 + return + self.tests[adr] = self.id + self.write('#%s ' % self.id) + self.id += 1 + + def afterTest(self, test): + # None means test never ran, False means failed/err + if test.passed is False: + try: + key = str(self.tests[test.address()]) + except KeyError: + # never saw this test -- startTest didn't run + pass + else: + if key not in self.failed: + self.failed.append(key) + + def tr(self, name): + log.debug("tr '%s'", name) + try: + key = int(name.replace('#', '')) + except ValueError: + return name + log.debug("Got key %s", key) + # I'm running tests mapped from the ids file, + # not collecting new ones + if key in self.ids: + return self.makeName(self.ids[key]) + return name + + def write(self, output): + if self._write_hashes: + self.stream.write(output) diff --git a/nose/plugins/xunit.py b/nose/plugins/xunit.py new file mode 100644 index 0000000..ded973e --- /dev/null +++ b/nose/plugins/xunit.py @@ -0,0 +1,253 @@ +"""This plugin provides test results in the standard XUnit XML format. + +It's designed for the `Jenkins`_ (previously Hudson) continuous build +system, but will probably work for anything else that understands an +XUnit-formatted XML representation of test results. + +Add this shell command to your builder :: + + nosetests --with-xunit + +And by default a file named nosetests.xml will be written to the +working directory. + +In a Jenkins builder, tick the box named "Publish JUnit test result report" +under the Post-build Actions and enter this value for Test report XMLs:: + + **/nosetests.xml + +If you need to change the name or location of the file, you can set the +``--xunit-file`` option. + +Here is an abbreviated version of what an XML test report might look like:: + + + + + + Traceback (most recent call last): + ... + TypeError: oops, wrong type + + + + +.. _Jenkins: http://jenkins-ci.org/ + +""" +import codecs +import doctest +import os +import traceback +import re +import inspect +from time import time +from xml.sax import saxutils + +from nose.plugins.base import Plugin +from nose.exc import SkipTest +from nose.pyversion import UNICODE_STRINGS + +# Invalid XML characters, control characters 0-31 sans \t, \n and \r +CONTROL_CHARACTERS = re.compile(r"[\000-\010\013\014\016-\037]") + +TEST_ID = re.compile(r'^(.*?)(\(.*\))$') + +def xml_safe(value): + """Replaces invalid XML characters with '?'.""" + return CONTROL_CHARACTERS.sub('?', value) + +def escape_cdata(cdata): + """Escape a string for an XML CDATA section.""" + return xml_safe(cdata).replace(']]>', ']]>]]>>> nice_classname(Exception()) # doctest: +ELLIPSIS + '...Exception' + >>> nice_classname(Exception) # doctest: +ELLIPSIS + '...Exception' + + """ + if inspect.isclass(obj): + cls_name = obj.__name__ + else: + cls_name = obj.__class__.__name__ + mod = inspect.getmodule(obj) + if mod: + name = mod.__name__ + # jython + if name.startswith('org.python.core.'): + name = name[len('org.python.core.'):] + return "%s.%s" % (name, cls_name) + else: + return cls_name + +def exc_message(exc_info): + """Return the exception's message.""" + exc = exc_info[1] + if exc is None: + # str exception + result = exc_info[0] + else: + try: + result = str(exc) + except UnicodeEncodeError: + try: + result = unicode(exc) + except UnicodeError: + # Fallback to args as neither str nor + # unicode(Exception(u'\xe6')) work in Python < 2.6 + result = exc.args[0] + return xml_safe(result) + +class Xunit(Plugin): + """This plugin provides test results in the standard XUnit XML format.""" + name = 'xunit' + score = 2000 + encoding = 'UTF-8' + error_report_file = None + + def _timeTaken(self): + if hasattr(self, '_timer'): + taken = time() - self._timer + else: + # test died before it ran (probably error in setup()) + # or success/failure added before test started probably + # due to custom TestResult munging + taken = 0.0 + return taken + + def _quoteattr(self, attr): + """Escape an XML attribute. Value can be unicode.""" + attr = xml_safe(attr) + if isinstance(attr, unicode) and not UNICODE_STRINGS: + attr = attr.encode(self.encoding) + return saxutils.quoteattr(attr) + + def options(self, parser, env): + """Sets additional command line options.""" + Plugin.options(self, parser, env) + parser.add_option( + '--xunit-file', action='store', + dest='xunit_file', metavar="FILE", + default=env.get('NOSE_XUNIT_FILE', 'nosetests.xml'), + help=("Path to xml file to store the xunit report in. " + "Default is nosetests.xml in the working directory " + "[NOSE_XUNIT_FILE]")) + + def configure(self, options, config): + """Configures the xunit plugin.""" + Plugin.configure(self, options, config) + self.config = config + if self.enabled: + self.stats = {'errors': 0, + 'failures': 0, + 'passes': 0, + 'skipped': 0 + } + self.errorlist = [] + self.error_report_file = codecs.open(options.xunit_file, 'w', + self.encoding, 'replace') + + def report(self, stream): + """Writes an Xunit-formatted XML file + + The file includes a report of test errors and failures. + + """ + self.stats['encoding'] = self.encoding + self.stats['total'] = (self.stats['errors'] + self.stats['failures'] + + self.stats['passes'] + self.stats['skipped']) + self.error_report_file.write( + u'' + u'' % self.stats) + self.error_report_file.write(u''.join([self._forceUnicode(e) + for e in self.errorlist])) + self.error_report_file.write(u'') + self.error_report_file.close() + if self.config.verbosity > 1: + stream.writeln("-" * 70) + stream.writeln("XML: %s" % self.error_report_file.name) + + def startTest(self, test): + """Initializes a timer before starting a test.""" + self._timer = time() + + def addError(self, test, err, capt=None): + """Add error output to Xunit report. + """ + taken = self._timeTaken() + + if issubclass(err[0], SkipTest): + type = 'skipped' + self.stats['skipped'] += 1 + else: + type = 'error' + self.stats['errors'] += 1 + tb = ''.join(traceback.format_exception(*err)) + id = test.id() + self.errorlist.append( + '' + '<%(type)s type=%(errtype)s message=%(message)s>' + '' % + {'cls': self._quoteattr(id_split(id)[0]), + 'name': self._quoteattr(id_split(id)[-1]), + 'taken': taken, + 'type': type, + 'errtype': self._quoteattr(nice_classname(err[0])), + 'message': self._quoteattr(exc_message(err)), + 'tb': escape_cdata(tb), + }) + + def addFailure(self, test, err, capt=None, tb_info=None): + """Add failure output to Xunit report. + """ + taken = self._timeTaken() + tb = ''.join(traceback.format_exception(*err)) + self.stats['failures'] += 1 + id = test.id() + self.errorlist.append( + '' + '' + '' % + {'cls': self._quoteattr(id_split(id)[0]), + 'name': self._quoteattr(id_split(id)[-1]), + 'taken': taken, + 'errtype': self._quoteattr(nice_classname(err[0])), + 'message': self._quoteattr(exc_message(err)), + 'tb': escape_cdata(tb), + }) + + def addSuccess(self, test, capt=None): + """Add success output to Xunit report. + """ + taken = self._timeTaken() + self.stats['passes'] += 1 + id = test.id() + self.errorlist.append( + '' % + {'cls': self._quoteattr(id_split(id)[0]), + 'name': self._quoteattr(id_split(id)[-1]), + 'taken': taken, + }) + + def _forceUnicode(self, s): + if not UNICODE_STRINGS: + if isinstance(s, str): + s = s.decode(self.encoding, 'replace') + return s diff --git a/nose/proxy.py b/nose/proxy.py new file mode 100644 index 0000000..8723290 --- /dev/null +++ b/nose/proxy.py @@ -0,0 +1,191 @@ +""" +Result Proxy +------------ + +The result proxy wraps the result instance given to each test. It +performs two functions: enabling extended error/failure reporting +and calling plugins. + +As each result event is fired, plugins are called with the same event; +however, plugins are called with the nose.case.Test instance that +wraps the actual test. So when a test fails and calls +result.addFailure(self, err), the result proxy calls +addFailure(self.test, err) for each plugin. This allows plugins to +have a single stable interface for all test types, and also to +manipulate the test object itself by setting the `test` attribute of +the nose.case.Test that they receive. +""" +import logging +from nose.config import Config + + +log = logging.getLogger(__name__) + + +def proxied_attribute(local_attr, proxied_attr, doc): + """Create a property that proxies attribute ``proxied_attr`` through + the local attribute ``local_attr``. + """ + def fget(self): + return getattr(getattr(self, local_attr), proxied_attr) + def fset(self, value): + setattr(getattr(self, local_attr), proxied_attr, value) + def fdel(self): + delattr(getattr(self, local_attr), proxied_attr) + return property(fget, fset, fdel, doc) + + +class ResultProxyFactory(object): + """Factory for result proxies. Generates a ResultProxy bound to each test + and the result passed to the test. + """ + def __init__(self, config=None): + if config is None: + config = Config() + self.config = config + self.__prepared = False + self.__result = None + + def __call__(self, result, test): + """Return a ResultProxy for the current test. + + On first call, plugins are given a chance to replace the + result used for the remaining tests. If a plugin returns a + value from prepareTestResult, that object will be used as the + result for all tests. + """ + if not self.__prepared: + self.__prepared = True + plug_result = self.config.plugins.prepareTestResult(result) + if plug_result is not None: + self.__result = result = plug_result + if self.__result is not None: + result = self.__result + return ResultProxy(result, test, config=self.config) + + +class ResultProxy(object): + """Proxy to TestResults (or other results handler). + + One ResultProxy is created for each nose.case.Test. The result + proxy calls plugins with the nose.case.Test instance (instead of + the wrapped test case) as each result call is made. Finally, the + real result method is called, also with the nose.case.Test + instance as the test parameter. + + """ + def __init__(self, result, test, config=None): + if config is None: + config = Config() + self.config = config + self.plugins = config.plugins + self.result = result + self.test = test + + def __repr__(self): + return repr(self.result) + + def _prepareErr(self, err): + if not isinstance(err[1], Exception): + # Turn value back into an Exception (required in Python 3.x). + # Plugins do all sorts of crazy things with exception values. + try: + # The actual exception class is needed for failure detail + # but maybe other plugins? + value = err[0](err[1]) + except: + value = Exception(err[1]) + err = (err[0], value, err[2]) + return err + + def assertMyTest(self, test): + # The test I was called with must be my .test or my + # .test's .test. or my .test.test's .case + + case = getattr(self.test, 'test', None) + assert (test is self.test + or test is case + or test is getattr(case, '_nose_case', None)), ( + "ResultProxy for %r (%s) was called with test %r (%s)" + % (self.test, id(self.test), test, id(test))) + + def afterTest(self, test): + self.assertMyTest(test) + self.plugins.afterTest(self.test) + if hasattr(self.result, "afterTest"): + self.result.afterTest(self.test) + + def beforeTest(self, test): + self.assertMyTest(test) + self.plugins.beforeTest(self.test) + if hasattr(self.result, "beforeTest"): + self.result.beforeTest(self.test) + + def addError(self, test, err): + self.assertMyTest(test) + plugins = self.plugins + plugin_handled = plugins.handleError(self.test, err) + if plugin_handled: + return + # test.passed is set in result, to account for error classes + formatted = plugins.formatError(self.test, err) + if formatted is not None: + err = formatted + plugins.addError(self.test, err) + self.result.addError(self.test, self._prepareErr(err)) + if not self.result.wasSuccessful() and self.config.stopOnError: + self.shouldStop = True + + def addFailure(self, test, err): + self.assertMyTest(test) + plugins = self.plugins + plugin_handled = plugins.handleFailure(self.test, err) + if plugin_handled: + return + self.test.passed = False + formatted = plugins.formatFailure(self.test, err) + if formatted is not None: + err = formatted + plugins.addFailure(self.test, err) + self.result.addFailure(self.test, self._prepareErr(err)) + if self.config.stopOnError: + self.shouldStop = True + + def addSkip(self, test, reason): + # 2.7 compat shim + from nose.plugins.skip import SkipTest + self.assertMyTest(test) + plugins = self.plugins + if not isinstance(reason, Exception): + # for Python 3.2+ + reason = Exception(reason) + plugins.addError(self.test, (SkipTest, reason, None)) + self.result.addSkip(self.test, reason) + + def addSuccess(self, test): + self.assertMyTest(test) + self.plugins.addSuccess(self.test) + self.result.addSuccess(self.test) + + def startTest(self, test): + self.assertMyTest(test) + self.plugins.startTest(self.test) + self.result.startTest(self.test) + + def stop(self): + self.result.stop() + + def stopTest(self, test): + self.assertMyTest(test) + self.plugins.stopTest(self.test) + self.result.stopTest(self.test) + + # proxied attributes + shouldStop = proxied_attribute('result', 'shouldStop', + """Should the test run stop?""") + errors = proxied_attribute('result', 'errors', + """Tests that raised an exception""") + failures = proxied_attribute('result', 'failures', + """Tests that failed""") + testsRun = proxied_attribute('result', 'testsRun', + """Number of tests run""") diff --git a/nose/pyversion.py b/nose/pyversion.py new file mode 100644 index 0000000..36ddcfd --- /dev/null +++ b/nose/pyversion.py @@ -0,0 +1,130 @@ +""" +This module contains fixups for using nose under different versions of Python. +""" +import sys +import os +import types +import inspect +import nose.util + +__all__ = ['make_instancemethod', 'cmp_to_key', 'sort_list', 'ClassType', + 'TypeType', 'UNICODE_STRINGS', 'unbound_method', 'ismethod', + 'bytes_'] + +# In Python 3.x, all strings are unicode (the call to 'unicode()' in the 2.x +# source will be replaced with 'str()' when running 2to3, so this test will +# then become true) +UNICODE_STRINGS = (type(unicode()) == type(str())) + +# new.instancemethod() is obsolete for new-style classes (Python 3.x) +# We need to use descriptor methods instead. +try: + import new + def make_instancemethod(function, instance): + return new.instancemethod(function.im_func, instance, + instance.__class__) +except ImportError: + def make_instancemethod(function, instance): + return function.__get__(instance, instance.__class__) + +# To be forward-compatible, we do all list sorts using keys instead of cmp +# functions. However, part of the unittest.TestLoader API involves a +# user-provideable cmp function, so we need some way to convert that. +def cmp_to_key(mycmp): + 'Convert a cmp= function into a key= function' + class Key(object): + def __init__(self, obj): + self.obj = obj + def __lt__(self, other): + return mycmp(self.obj, other.obj) < 0 + def __gt__(self, other): + return mycmp(self.obj, other.obj) > 0 + def __eq__(self, other): + return mycmp(self.obj, other.obj) == 0 + return Key + +# Python 2.3 also does not support list-sorting by key, so we need to convert +# keys to cmp functions if we're running on old Python.. +if sys.version_info < (2, 4): + def sort_list(l, key, reverse=False): + if reverse: + return l.sort(lambda a, b: cmp(key(b), key(a))) + else: + return l.sort(lambda a, b: cmp(key(a), key(b))) +else: + def sort_list(l, key, reverse=False): + return l.sort(key=key, reverse=reverse) + +# In Python 3.x, all objects are "new style" objects descended from 'type', and +# thus types.ClassType and types.TypeType don't exist anymore. For +# compatibility, we make sure they still work. +if hasattr(types, 'ClassType'): + ClassType = types.ClassType + TypeType = types.TypeType +else: + ClassType = type + TypeType = type + +# The following emulates the behavior (we need) of an 'unbound method' under +# Python 3.x (namely, the ability to have a class associated with a function +# definition so that things can do stuff based on its associated class) +class UnboundMethod: + def __init__(self, cls, func): + # Make sure we have all the same attributes as the original function, + # so that the AttributeSelector plugin will work correctly... + self.__dict__ = func.__dict__.copy() + self._func = func + self.__self__ = UnboundSelf(cls) + + def address(self): + cls = self.__self__.cls + modname = cls.__module__ + module = sys.modules[modname] + filename = getattr(module, '__file__', None) + if filename is not None: + filename = os.path.abspath(filename) + return (nose.util.src(filename), modname, "%s.%s" % (cls.__name__, + self._func.__name__)) + + def __call__(self, *args, **kwargs): + return self._func(*args, **kwargs) + + def __getattr__(self, attr): + return getattr(self._func, attr) + + def __repr__(self): + return '' % (self.__self__.cls.__name__, + self._func.__name__) + +class UnboundSelf: + def __init__(self, cls): + self.cls = cls + + # We have to do this hackery because Python won't let us override the + # __class__ attribute... + def __getattribute__(self, attr): + if attr == '__class__': + return self.cls + else: + return object.__getattribute__(self, attr) + +def unbound_method(cls, func): + if inspect.ismethod(func): + return func + if not inspect.isfunction(func): + raise TypeError('%s is not a function' % (repr(func),)) + return UnboundMethod(cls, func) + +def ismethod(obj): + return inspect.ismethod(obj) or isinstance(obj, UnboundMethod) + + +# Make a pseudo-bytes function that can be called without the encoding arg: +if sys.version_info >= (3, 0): + def bytes_(s, encoding='utf8'): + if isinstance(s, bytes): + return s + return bytes(s, encoding) +else: + def bytes_(s, encoding=None): + return str(s) diff --git a/nose/result.py b/nose/result.py new file mode 100644 index 0000000..1267ba2 --- /dev/null +++ b/nose/result.py @@ -0,0 +1,200 @@ +""" +Test Result +----------- + +Provides a TextTestResult that extends unittest's _TextTestResult to +provide support for error classes (such as the builtin skip and +deprecated classes), and hooks for plugins to take over or extend +reporting. +""" + +import logging +try: + # 2.7+ + from unittest.runner import _TextTestResult +except ImportError: + from unittest import _TextTestResult +from nose.config import Config +from nose.util import isclass, ln as _ln # backwards compat + +log = logging.getLogger('nose.result') + + +def _exception_detail(exc): + # this is what stdlib module traceback does + try: + return str(exc) + except: + return '' % type(exc).__name__ + + +class TextTestResult(_TextTestResult): + """Text test result that extends unittest's default test result + support for a configurable set of errorClasses (eg, Skip, + Deprecated, TODO) that extend the errors/failures/success triad. + """ + def __init__(self, stream, descriptions, verbosity, config=None, + errorClasses=None): + if errorClasses is None: + errorClasses = {} + self.errorClasses = errorClasses + if config is None: + config = Config() + self.config = config + _TextTestResult.__init__(self, stream, descriptions, verbosity) + + def addSkip(self, test, reason): + # 2.7 skip compat + from nose.plugins.skip import SkipTest + if SkipTest in self.errorClasses: + storage, label, isfail = self.errorClasses[SkipTest] + storage.append((test, reason)) + self.printLabel(label, (SkipTest, reason, None)) + + def addError(self, test, err): + """Overrides normal addError to add support for + errorClasses. If the exception is a registered class, the + error will be added to the list for that class, not errors. + """ + ec, ev, tb = err + try: + exc_info = self._exc_info_to_string(err, test) + except TypeError: + # 2.3 compat + exc_info = self._exc_info_to_string(err) + for cls, (storage, label, isfail) in self.errorClasses.items(): + #if 'Skip' in cls.__name__ or 'Skip' in ec.__name__: + # from nose.tools import set_trace + # set_trace() + if isclass(ec) and issubclass(ec, cls): + if isfail: + test.passed = False + storage.append((test, exc_info)) + self.printLabel(label, err) + return + self.errors.append((test, exc_info)) + test.passed = False + self.printLabel('ERROR') + + # override to bypass changes in 2.7 + def getDescription(self, test): + if self.descriptions: + return test.shortDescription() or str(test) + else: + return str(test) + + def printLabel(self, label, err=None): + # Might get patched into a streamless result + stream = getattr(self, 'stream', None) + if stream is not None: + if self.showAll: + message = [label] + if err: + detail = _exception_detail(err[1]) + if detail: + message.append(detail) + stream.writeln(": ".join(message)) + elif self.dots: + stream.write(label[:1]) + + def printErrors(self): + """Overrides to print all errorClasses errors as well. + """ + _TextTestResult.printErrors(self) + for cls in self.errorClasses.keys(): + storage, label, isfail = self.errorClasses[cls] + if isfail: + self.printErrorList(label, storage) + # Might get patched into a result with no config + if hasattr(self, 'config'): + self.config.plugins.report(self.stream) + + def printSummary(self, start, stop): + """Called by the test runner to print the final summary of test + run results. + """ + write = self.stream.write + writeln = self.stream.writeln + taken = float(stop - start) + run = self.testsRun + plural = run != 1 and "s" or "" + + writeln(self.separator2) + writeln("Ran %s test%s in %.3fs" % (run, plural, taken)) + writeln() + + summary = {} + eckeys = self.errorClasses.keys() + for cls in eckeys: + storage, label, isfail = self.errorClasses[cls] + count = len(storage) + if not count: + continue + summary[label] = count + if len(self.failures): + summary['failures'] = len(self.failures) + if len(self.errors): + summary['errors'] = len(self.errors) + + if not self.wasSuccessful(): + write("FAILED") + else: + write("OK") + items = summary.items() + if items: + items.sort() + write(" (") + write(", ".join(["%s=%s" % (label, count) for + label, count in items])) + writeln(")") + else: + writeln() + + def wasSuccessful(self): + """Overrides to check that there are no errors in errorClasses + lists that are marked as errors and should cause a run to + fail. + """ + if self.errors or self.failures: + return False + for cls in self.errorClasses.keys(): + storage, label, isfail = self.errorClasses[cls] + if not isfail: + continue + if storage: + return False + return True + + def _addError(self, test, err): + try: + exc_info = self._exc_info_to_string(err, test) + except TypeError: + # 2.3: does not take test arg + exc_info = self._exc_info_to_string(err) + self.errors.append((test, exc_info)) + if self.showAll: + self.stream.write('ERROR') + elif self.dots: + self.stream.write('E') + + def _exc_info_to_string(self, err, test=None): + # 2.7 skip compat + from nose.plugins.skip import SkipTest + if issubclass(err[0], SkipTest): + return str(err[1]) + # 2.3/2.4 -- 2.4 passes test, 2.3 does not + try: + return _TextTestResult._exc_info_to_string(self, err, test) + except TypeError: + # 2.3: does not take test arg + return _TextTestResult._exc_info_to_string(self, err) + + +def ln(*arg, **kw): + from warnings import warn + warn("ln() has moved to nose.util from nose.result and will be removed " + "from nose.result in a future release. Please update your imports ", + DeprecationWarning) + return _ln(*arg, **kw) + + diff --git a/nose/selector.py b/nose/selector.py new file mode 100644 index 0000000..c4a006a --- /dev/null +++ b/nose/selector.py @@ -0,0 +1,251 @@ +""" +Test Selection +-------------- + +Test selection is handled by a Selector. The test loader calls the +appropriate selector method for each object it encounters that it +thinks may be a test. +""" +import logging +import os +import unittest +from nose.config import Config +from nose.util import split_test_name, src, getfilename, getpackage, ispackage + +log = logging.getLogger(__name__) + +__all__ = ['Selector', 'defaultSelector', 'TestAddress'] + + +# for efficiency and easier mocking +op_join = os.path.join +op_basename = os.path.basename +op_exists = os.path.exists +op_splitext = os.path.splitext +op_isabs = os.path.isabs +op_abspath = os.path.abspath + + +class Selector(object): + """Core test selector. Examines test candidates and determines whether, + given the specified configuration, the test candidate should be selected + as a test. + """ + def __init__(self, config): + if config is None: + config = Config() + self.configure(config) + + def configure(self, config): + self.config = config + self.exclude = config.exclude + self.ignoreFiles = config.ignoreFiles + self.include = config.include + self.plugins = config.plugins + self.match = config.testMatch + + def matches(self, name): + """Does the name match my requirements? + + To match, a name must match config.testMatch OR config.include + and it must not match config.exclude + """ + return ((self.match.search(name) + or (self.include and + filter(None, + [inc.search(name) for inc in self.include]))) + and ((not self.exclude) + or not filter(None, + [exc.search(name) for exc in self.exclude]) + )) + + def wantClass(self, cls): + """Is the class a wanted test class? + + A class must be a unittest.TestCase subclass, or match test name + requirements. Classes that start with _ are always excluded. + """ + declared = getattr(cls, '__test__', None) + if declared is not None: + wanted = declared + else: + wanted = (not cls.__name__.startswith('_') + and (issubclass(cls, unittest.TestCase) + or self.matches(cls.__name__))) + + plug_wants = self.plugins.wantClass(cls) + if plug_wants is not None: + log.debug("Plugin setting selection of %s to %s", cls, plug_wants) + wanted = plug_wants + log.debug("wantClass %s? %s", cls, wanted) + return wanted + + def wantDirectory(self, dirname): + """Is the directory a wanted test directory? + + All package directories match, so long as they do not match exclude. + All other directories must match test requirements. + """ + tail = op_basename(dirname) + if ispackage(dirname): + wanted = (not self.exclude + or not filter(None, + [exc.search(tail) for exc in self.exclude] + )) + else: + wanted = (self.matches(tail) + or (self.config.srcDirs + and tail in self.config.srcDirs)) + plug_wants = self.plugins.wantDirectory(dirname) + if plug_wants is not None: + log.debug("Plugin setting selection of %s to %s", + dirname, plug_wants) + wanted = plug_wants + log.debug("wantDirectory %s? %s", dirname, wanted) + return wanted + + def wantFile(self, file): + """Is the file a wanted test file? + + The file must be a python source file and match testMatch or + include, and not match exclude. Files that match ignore are *never* + wanted, regardless of plugin, testMatch, include or exclude settings. + """ + # never, ever load files that match anything in ignore + # (.* _* and *setup*.py by default) + base = op_basename(file) + ignore_matches = [ ignore_this for ignore_this in self.ignoreFiles + if ignore_this.search(base) ] + if ignore_matches: + log.debug('%s matches ignoreFiles pattern; skipped', + base) + return False + if not self.config.includeExe and os.access(file, os.X_OK): + log.info('%s is executable; skipped', file) + return False + dummy, ext = op_splitext(base) + pysrc = ext == '.py' + + wanted = pysrc and self.matches(base) + plug_wants = self.plugins.wantFile(file) + if plug_wants is not None: + log.debug("plugin setting want %s to %s", file, plug_wants) + wanted = plug_wants + log.debug("wantFile %s? %s", file, wanted) + return wanted + + def wantFunction(self, function): + """Is the function a test function? + """ + try: + if hasattr(function, 'compat_func_name'): + funcname = function.compat_func_name + else: + funcname = function.__name__ + except AttributeError: + # not a function + return False + declared = getattr(function, '__test__', None) + if declared is not None: + wanted = declared + else: + wanted = not funcname.startswith('_') and self.matches(funcname) + plug_wants = self.plugins.wantFunction(function) + if plug_wants is not None: + wanted = plug_wants + log.debug("wantFunction %s? %s", function, wanted) + return wanted + + def wantMethod(self, method): + """Is the method a test method? + """ + try: + method_name = method.__name__ + except AttributeError: + # not a method + return False + if method_name.startswith('_'): + # never collect 'private' methods + return False + declared = getattr(method, '__test__', None) + if declared is not None: + wanted = declared + else: + wanted = self.matches(method_name) + plug_wants = self.plugins.wantMethod(method) + if plug_wants is not None: + wanted = plug_wants + log.debug("wantMethod %s? %s", method, wanted) + return wanted + + def wantModule(self, module): + """Is the module a test module? + + The tail of the module name must match test requirements. One exception: + we always want __main__. + """ + declared = getattr(module, '__test__', None) + if declared is not None: + wanted = declared + else: + wanted = self.matches(module.__name__.split('.')[-1]) \ + or module.__name__ == '__main__' + plug_wants = self.plugins.wantModule(module) + if plug_wants is not None: + wanted = plug_wants + log.debug("wantModule %s? %s", module, wanted) + return wanted + +defaultSelector = Selector + + +class TestAddress(object): + """A test address represents a user's request to run a particular + test. The user may specify a filename or module (or neither), + and/or a callable (a class, function, or method). The naming + format for test addresses is: + + filename_or_module:callable + + Filenames that are not absolute will be made absolute relative to + the working dir. + + The filename or module part will be considered a module name if it + doesn't look like a file, that is, if it doesn't exist on the file + system and it doesn't contain any directory separators and it + doesn't end in .py. + + Callables may be a class name, function name, method name, or + class.method specification. + """ + def __init__(self, name, workingDir=None): + if workingDir is None: + workingDir = os.getcwd() + self.name = name + self.workingDir = workingDir + self.filename, self.module, self.call = split_test_name(name) + log.debug('Test name %s resolved to file %s, module %s, call %s', + name, self.filename, self.module, self.call) + if self.filename is None: + if self.module is not None: + self.filename = getfilename(self.module, self.workingDir) + if self.filename: + self.filename = src(self.filename) + if not op_isabs(self.filename): + self.filename = op_abspath(op_join(workingDir, + self.filename)) + if self.module is None: + self.module = getpackage(self.filename) + log.debug( + 'Final resolution of test name %s: file %s module %s call %s', + name, self.filename, self.module, self.call) + + def totuple(self): + return (self.filename, self.module, self.call) + + def __str__(self): + return self.name + + def __repr__(self): + return "%s: (%s, %s, %s)" % (self.name, self.filename, + self.module, self.call) diff --git a/nose/sphinx/__init__.py b/nose/sphinx/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/nose/sphinx/__init__.py @@ -0,0 +1 @@ +pass diff --git a/nose/sphinx/pluginopts.py b/nose/sphinx/pluginopts.py new file mode 100644 index 0000000..9b88eeb --- /dev/null +++ b/nose/sphinx/pluginopts.py @@ -0,0 +1,186 @@ +""" +Adds a sphinx directive that can be used to automatically document a plugin. + +this:: + + .. autoplugin :: nose.plugins.foo + :plugin: Pluggy + +produces:: + + .. automodule :: nose.plugins.foo + + Options + ------- + + .. cmdoption :: --foo=BAR, --fooble=BAR + + Do the foo thing to the new thing. + + Plugin + ------ + + .. autoclass :: nose.plugins.foo.Pluggy + :members: + + Source + ------ + + .. include :: path/to/nose/plugins/foo.py + :literal: + +""" +import os +try: + from docutils import nodes + from docutils.statemachine import ViewList + from docutils.parsers.rst import directives +except ImportError: + pass # won't run anyway + +from nose.util import resolve_name +from nose.plugins.base import Plugin +from nose.plugins.manager import BuiltinPluginManager +from nose.config import Config +from nose.core import TestProgram +from inspect import isclass + +def autoplugin_directive(dirname, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + mod_name = arguments[0] + mod = resolve_name(mod_name) + plug_name = options.get('plugin', None) + if plug_name: + obj = getattr(mod, plug_name) + else: + for entry in dir(mod): + obj = getattr(mod, entry) + if isclass(obj) and issubclass(obj, Plugin) and obj is not Plugin: + plug_name = '%s.%s' % (mod_name, entry) + break + + # mod docstring + rst = ViewList() + rst.append('.. automodule :: %s\n' % mod_name, '') + rst.append('', '') + + # options + rst.append('Options', '') + rst.append('-------', '') + rst.append('', '') + + plug = obj() + opts = OptBucket() + plug.options(opts, {}) + for opt in opts: + rst.append(opt.options(), '') + rst.append(' \n', '') + rst.append(' ' + opt.help + '\n', '') + rst.append('\n', '') + + # plugin class + rst.append('Plugin', '') + rst.append('------', '') + rst.append('', '') + + rst.append('.. autoclass :: %s\n' % plug_name, '') + rst.append(' :members:\n', '') + rst.append(' :show-inheritance:\n', '') + rst.append('', '') + + # source + rst.append('Source', '') + rst.append('------', '') + rst.append('.. include :: %s\n' % os.path.relpath( + mod.__file__.replace('.pyc', '.py'), os.getcwd()), + '') + rst.append(' :literal:\n', '') + rst.append('', '') + + node = nodes.section() + node.document = state.document + surrounding_title_styles = state.memo.title_styles + surrounding_section_level = state.memo.section_level + state.memo.title_styles = [] + state.memo.section_level = 0 + state.nested_parse(rst, 0, node, match_titles=1) + state.memo.title_styles = surrounding_title_styles + state.memo.section_level = surrounding_section_level + + return node.children + + +def autohelp_directive(dirname, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + """produces rst from nose help""" + config = Config(parserClass=OptBucket, + plugins=BuiltinPluginManager()) + parser = config.getParser(TestProgram.usage()) + rst = ViewList() + for line in parser.format_help().split('\n'): + rst.append(line, '') + + rst.append('Options', '') + rst.append('-------', '') + rst.append('', '') + for opt in parser: + rst.append(opt.options(), '') + rst.append(' \n', '') + rst.append(' ' + opt.help + '\n', '') + rst.append('\n', '') + node = nodes.section() + node.document = state.document + surrounding_title_styles = state.memo.title_styles + surrounding_section_level = state.memo.section_level + state.memo.title_styles = [] + state.memo.section_level = 0 + state.nested_parse(rst, 0, node, match_titles=1) + state.memo.title_styles = surrounding_title_styles + state.memo.section_level = surrounding_section_level + + return node.children + + +class OptBucket(object): + def __init__(self, doc=None, prog='nosetests'): + self.opts = [] + self.doc = doc + self.prog = prog + + def __iter__(self): + return iter(self.opts) + + def format_help(self): + return self.doc.replace('%prog', self.prog).replace(':\n', '::\n') + + def add_option(self, *arg, **kw): + self.opts.append(Opt(*arg, **kw)) + + +class Opt(object): + def __init__(self, *arg, **kw): + self.opts = arg + self.action = kw.pop('action', None) + self.default = kw.pop('default', None) + self.metavar = kw.pop('metavar', None) + self.help = kw.pop('help', None) + + def options(self): + buf = [] + for optstring in self.opts: + desc = optstring + if self.action not in ('store_true', 'store_false'): + desc += '=%s' % self.meta(optstring) + buf.append(desc) + return '.. cmdoption :: ' + ', '.join(buf) + + def meta(self, optstring): + # FIXME optparser default metavar? + return self.metavar or 'DEFAULT' + + +def setup(app): + app.add_directive('autoplugin', + autoplugin_directive, 1, (1, 0, 1), + plugin=directives.unchanged) + app.add_directive('autohelp', autohelp_directive, 0, (0, 0, 1)) diff --git a/nose/suite.py b/nose/suite.py new file mode 100644 index 0000000..3b68b23 --- /dev/null +++ b/nose/suite.py @@ -0,0 +1,607 @@ +""" +Test Suites +----------- + +Provides a LazySuite, which is a suite whose test list is a generator +function, and ContextSuite,which can run fixtures (setup/teardown +functions or methods) for the context that contains its tests. + +""" +from __future__ import generators + +import logging +import sys +import unittest +from nose.case import Test +from nose.config import Config +from nose.proxy import ResultProxyFactory +from nose.util import isclass, resolve_name, try_run + +if sys.platform == 'cli': + if sys.version_info[:2] < (2, 6): + import clr + clr.AddReference("IronPython") + from IronPython.Runtime.Exceptions import StringException + else: + class StringException(Exception): + pass + +log = logging.getLogger(__name__) +#log.setLevel(logging.DEBUG) + +# Singleton for default value -- see ContextSuite.__init__ below +_def = object() + + +def _strclass(cls): + return "%s.%s" % (cls.__module__, cls.__name__) + +class MixedContextError(Exception): + """Error raised when a context suite sees tests from more than + one context. + """ + pass + + +class LazySuite(unittest.TestSuite): + """A suite that may use a generator as its list of tests + """ + def __init__(self, tests=()): + """Initialize the suite. tests may be an iterable or a generator + """ + self._set_tests(tests) + + def __iter__(self): + return iter(self._tests) + + def __repr__(self): + return "<%s tests=generator (%s)>" % ( + _strclass(self.__class__), id(self)) + + def __hash__(self): + return object.__hash__(self) + + __str__ = __repr__ + + def addTest(self, test): + self._precache.append(test) + + # added to bypass run changes in 2.7's unittest + def run(self, result): + for test in self._tests: + if result.shouldStop: + break + test(result) + return result + + def __nonzero__(self): + log.debug("tests in %s?", id(self)) + if self._precache: + return True + if self.test_generator is None: + return False + try: + test = self.test_generator.next() + if test is not None: + self._precache.append(test) + return True + except StopIteration: + pass + return False + + def _get_tests(self): + log.debug("precache is %s", self._precache) + for test in self._precache: + yield test + if self.test_generator is None: + return + for test in self.test_generator: + yield test + + def _set_tests(self, tests): + self._precache = [] + is_suite = isinstance(tests, unittest.TestSuite) + if callable(tests) and not is_suite: + self.test_generator = tests() + elif is_suite: + # Suites need special treatment: they must be called like + # tests for their setup/teardown to run (if any) + self.addTests([tests]) + self.test_generator = None + else: + self.addTests(tests) + self.test_generator = None + + _tests = property(_get_tests, _set_tests, None, + "Access the tests in this suite. Access is through a " + "generator, so iteration may not be repeatable.") + + +class ContextSuite(LazySuite): + """A suite with context. + + A ContextSuite executes fixtures (setup and teardown functions or + methods) for the context containing its tests. + + The context may be explicitly passed. If it is not, a context (or + nested set of contexts) will be constructed by examining the tests + in the suite. + """ + failureException = unittest.TestCase.failureException + was_setup = False + was_torndown = False + classSetup = ('setup_class', 'setup_all', 'setupClass', 'setupAll', + 'setUpClass', 'setUpAll') + classTeardown = ('teardown_class', 'teardown_all', 'teardownClass', + 'teardownAll', 'tearDownClass', 'tearDownAll') + moduleSetup = ('setup_module', 'setupModule', 'setUpModule', 'setup', + 'setUp') + moduleTeardown = ('teardown_module', 'teardownModule', 'tearDownModule', + 'teardown', 'tearDown') + packageSetup = ('setup_package', 'setupPackage', 'setUpPackage') + packageTeardown = ('teardown_package', 'teardownPackage', + 'tearDownPackage') + + def __init__(self, tests=(), context=None, factory=None, + config=None, resultProxy=None, can_split=True): + log.debug("Context suite for %s (%s) (%s)", tests, context, id(self)) + self.context = context + self.factory = factory + if config is None: + config = Config() + self.config = config + self.resultProxy = resultProxy + self.has_run = False + self.can_split = can_split + self.error_context = None + LazySuite.__init__(self, tests) + + def __repr__(self): + return "<%s context=%s>" % ( + _strclass(self.__class__), + getattr(self.context, '__name__', self.context)) + __str__ = __repr__ + + def id(self): + if self.error_context: + return '%s:%s' % (repr(self), self.error_context) + else: + return repr(self) + + def __hash__(self): + return object.__hash__(self) + + # 2.3 compat -- force 2.4 call sequence + def __call__(self, *arg, **kw): + return self.run(*arg, **kw) + + def exc_info(self): + """Hook for replacing error tuple output + """ + return sys.exc_info() + + def _exc_info(self): + """Bottleneck to fix up IronPython string exceptions + """ + e = self.exc_info() + if sys.platform == 'cli': + if isinstance(e[0], StringException): + # IronPython throws these StringExceptions, but + # traceback checks type(etype) == str. Make a real + # string here. + e = (str(e[0]), e[1], e[2]) + + return e + + def run(self, result): + """Run tests in suite inside of suite fixtures. + """ + # proxy the result for myself + log.debug("suite %s (%s) run called, tests: %s", id(self), self, self._tests) + #import pdb + #pdb.set_trace() + if self.resultProxy: + result, orig = self.resultProxy(result, self), result + else: + result, orig = result, result + try: + self.setUp() + except KeyboardInterrupt: + raise + except: + self.error_context = 'setup' + result.addError(self, self._exc_info()) + return + try: + for test in self._tests: + if result.shouldStop: + log.debug("stopping") + break + # each nose.case.Test will create its own result proxy + # so the cases need the original result, to avoid proxy + # chains + test(orig) + finally: + self.has_run = True + try: + self.tearDown() + except KeyboardInterrupt: + raise + except: + self.error_context = 'teardown' + result.addError(self, self._exc_info()) + + def hasFixtures(self, ctx_callback=None): + context = self.context + if context is None: + return False + if self.implementsAnyFixture(context, ctx_callback=ctx_callback): + return True + # My context doesn't have any, but its ancestors might + factory = self.factory + if factory: + ancestors = factory.context.get(self, []) + for ancestor in ancestors: + if self.implementsAnyFixture( + ancestor, ctx_callback=ctx_callback): + return True + return False + + def implementsAnyFixture(self, context, ctx_callback): + if isclass(context): + names = self.classSetup + self.classTeardown + else: + names = self.moduleSetup + self.moduleTeardown + if hasattr(context, '__path__'): + names += self.packageSetup + self.packageTeardown + # If my context has any fixture attribute, I have fixtures + fixt = False + for m in names: + if hasattr(context, m): + fixt = True + break + if ctx_callback is None: + return fixt + return ctx_callback(context, fixt) + + def setUp(self): + log.debug("suite %s setUp called, tests: %s", id(self), self._tests) + if not self: + # I have no tests + log.debug("suite %s has no tests", id(self)) + return + if self.was_setup: + log.debug("suite %s already set up", id(self)) + return + context = self.context + if context is None: + return + # before running my own context's setup, I need to + # ask the factory if my context's contexts' setups have been run + factory = self.factory + if factory: + # get a copy, since we'll be destroying it as we go + ancestors = factory.context.get(self, [])[:] + while ancestors: + ancestor = ancestors.pop() + log.debug("ancestor %s may need setup", ancestor) + if ancestor in factory.was_setup: + continue + log.debug("ancestor %s does need setup", ancestor) + self.setupContext(ancestor) + if not context in factory.was_setup: + self.setupContext(context) + else: + self.setupContext(context) + self.was_setup = True + log.debug("completed suite setup") + + def setupContext(self, context): + self.config.plugins.startContext(context) + log.debug("%s setup context %s", self, context) + if self.factory: + if context in self.factory.was_setup: + return + # note that I ran the setup for this context, so that I'll run + # the teardown in my teardown + self.factory.was_setup[context] = self + if isclass(context): + names = self.classSetup + else: + names = self.moduleSetup + if hasattr(context, '__path__'): + names = self.packageSetup + names + try_run(context, names) + + def shortDescription(self): + if self.context is None: + return "test suite" + return "test suite for %s" % self.context + + def tearDown(self): + log.debug('context teardown') + if not self.was_setup or self.was_torndown: + log.debug( + "No reason to teardown (was_setup? %s was_torndown? %s)" + % (self.was_setup, self.was_torndown)) + return + self.was_torndown = True + context = self.context + if context is None: + log.debug("No context to tear down") + return + + # for each ancestor... if the ancestor was setup + # and I did the setup, I can do teardown + factory = self.factory + if factory: + ancestors = factory.context.get(self, []) + [context] + for ancestor in ancestors: + log.debug('ancestor %s may need teardown', ancestor) + if not ancestor in factory.was_setup: + log.debug('ancestor %s was not setup', ancestor) + continue + if ancestor in factory.was_torndown: + log.debug('ancestor %s already torn down', ancestor) + continue + setup = factory.was_setup[ancestor] + log.debug("%s setup ancestor %s", setup, ancestor) + if setup is self: + self.teardownContext(ancestor) + else: + self.teardownContext(context) + + def teardownContext(self, context): + log.debug("%s teardown context %s", self, context) + if self.factory: + if context in self.factory.was_torndown: + return + self.factory.was_torndown[context] = self + if isclass(context): + names = self.classTeardown + else: + names = self.moduleTeardown + if hasattr(context, '__path__'): + names = self.packageTeardown + names + try_run(context, names) + self.config.plugins.stopContext(context) + + # FIXME the wrapping has to move to the factory? + def _get_wrapped_tests(self): + for test in self._get_tests(): + if isinstance(test, Test) or isinstance(test, unittest.TestSuite): + yield test + else: + yield Test(test, + config=self.config, + resultProxy=self.resultProxy) + + _tests = property(_get_wrapped_tests, LazySuite._set_tests, None, + "Access the tests in this suite. Tests are returned " + "inside of a context wrapper.") + + +class ContextSuiteFactory(object): + """Factory for ContextSuites. Called with a collection of tests, + the factory decides on a hierarchy of contexts by introspecting + the collection or the tests themselves to find the objects + containing the test objects. It always returns one suite, but that + suite may consist of a hierarchy of nested suites. + """ + suiteClass = ContextSuite + def __init__(self, config=None, suiteClass=None, resultProxy=_def): + if config is None: + config = Config() + self.config = config + if suiteClass is not None: + self.suiteClass = suiteClass + # Using a singleton to represent default instead of None allows + # passing resultProxy=None to turn proxying off. + if resultProxy is _def: + resultProxy = ResultProxyFactory(config=config) + self.resultProxy = resultProxy + self.suites = {} + self.context = {} + self.was_setup = {} + self.was_torndown = {} + + def __call__(self, tests, **kw): + """Return ``ContextSuite`` for tests. ``tests`` may either + be a callable (in which case the resulting ContextSuite will + have no parent context and be evaluated lazily) or an + iterable. In that case the tests will wrapped in + nose.case.Test, be examined and the context of each found and a + suite of suites returned, organized into a stack with the + outermost suites belonging to the outermost contexts. + """ + log.debug("Create suite for %s", tests) + context = kw.pop('context', getattr(tests, 'context', None)) + log.debug("tests %s context %s", tests, context) + if context is None: + tests = self.wrapTests(tests) + try: + context = self.findContext(tests) + except MixedContextError: + return self.makeSuite(self.mixedSuites(tests), None, **kw) + return self.makeSuite(tests, context, **kw) + + def ancestry(self, context): + """Return the ancestry of the context (that is, all of the + packages and modules containing the context), in order of + descent with the outermost ancestor last. + This method is a generator. + """ + log.debug("get ancestry %s", context) + if context is None: + return + # Methods include reference to module they are defined in, we + # don't want that, instead want the module the class is in now + # (classes are re-ancestored elsewhere). + if hasattr(context, 'im_class'): + context = context.im_class + elif hasattr(context, '__self__'): + context = context.__self__.__class__ + if hasattr(context, '__module__'): + ancestors = context.__module__.split('.') + elif hasattr(context, '__name__'): + ancestors = context.__name__.split('.')[:-1] + else: + raise TypeError("%s has no ancestors?" % context) + while ancestors: + log.debug(" %s ancestors %s", context, ancestors) + yield resolve_name('.'.join(ancestors)) + ancestors.pop() + + def findContext(self, tests): + if callable(tests) or isinstance(tests, unittest.TestSuite): + return None + context = None + for test in tests: + # Don't look at suites for contexts, only tests + ctx = getattr(test, 'context', None) + if ctx is None: + continue + if context is None: + context = ctx + elif context != ctx: + raise MixedContextError( + "Tests with different contexts in same suite! %s != %s" + % (context, ctx)) + return context + + def makeSuite(self, tests, context, **kw): + suite = self.suiteClass( + tests, context=context, config=self.config, factory=self, + resultProxy=self.resultProxy, **kw) + if context is not None: + self.suites.setdefault(context, []).append(suite) + self.context.setdefault(suite, []).append(context) + log.debug("suite %s has context %s", suite, + getattr(context, '__name__', None)) + for ancestor in self.ancestry(context): + self.suites.setdefault(ancestor, []).append(suite) + self.context[suite].append(ancestor) + log.debug("suite %s has ancestor %s", suite, ancestor.__name__) + return suite + + def mixedSuites(self, tests): + """The complex case where there are tests that don't all share + the same context. Groups tests into suites with common ancestors, + according to the following (essentially tail-recursive) procedure: + + Starting with the context of the first test, if it is not + None, look for tests in the remaining tests that share that + ancestor. If any are found, group into a suite with that + ancestor as the context, and replace the current suite with + that suite. Continue this process for each ancestor of the + first test, until all ancestors have been processed. At this + point if any tests remain, recurse with those tests as the + input, returning a list of the common suite (which may be the + suite or test we started with, if no common tests were found) + plus the results of recursion. + """ + if not tests: + return [] + head = tests.pop(0) + if not tests: + return [head] # short circuit when none are left to combine + suite = head # the common ancestry suite, so far + tail = tests[:] + context = getattr(head, 'context', None) + if context is not None: + ancestors = [context] + [a for a in self.ancestry(context)] + for ancestor in ancestors: + common = [suite] # tests with ancestor in common, so far + remain = [] # tests that remain to be processed + for test in tail: + found_common = False + test_ctx = getattr(test, 'context', None) + if test_ctx is None: + remain.append(test) + continue + if test_ctx is ancestor: + common.append(test) + continue + for test_ancestor in self.ancestry(test_ctx): + if test_ancestor is ancestor: + common.append(test) + found_common = True + break + if not found_common: + remain.append(test) + if common: + suite = self.makeSuite(common, ancestor) + tail = self.mixedSuites(remain) + return [suite] + tail + + def wrapTests(self, tests): + log.debug("wrap %s", tests) + if callable(tests) or isinstance(tests, unittest.TestSuite): + log.debug("I won't wrap") + return tests + wrapped = [] + for test in tests: + log.debug("wrapping %s", test) + if isinstance(test, Test) or isinstance(test, unittest.TestSuite): + wrapped.append(test) + elif isinstance(test, ContextList): + wrapped.append(self.makeSuite(test, context=test.context)) + else: + wrapped.append( + Test(test, config=self.config, resultProxy=self.resultProxy) + ) + return wrapped + + +class ContextList(object): + """Not quite a suite -- a group of tests in a context. This is used + to hint the ContextSuiteFactory about what context the tests + belong to, in cases where it may be ambiguous or missing. + """ + def __init__(self, tests, context=None): + self.tests = tests + self.context = context + + def __iter__(self): + return iter(self.tests) + + +class FinalizingSuiteWrapper(unittest.TestSuite): + """Wraps suite and calls final function after suite has + executed. Used to call final functions in cases (like running in + the standard test runner) where test running is not under nose's + control. + """ + def __init__(self, suite, finalize): + self.suite = suite + self.finalize = finalize + + def __call__(self, *arg, **kw): + return self.run(*arg, **kw) + + # 2.7 compat + def __iter__(self): + return iter(self.suite) + + def run(self, *arg, **kw): + try: + return self.suite(*arg, **kw) + finally: + self.finalize(*arg, **kw) + + +# backwards compat -- sort of +class TestDir: + def __init__(*arg, **kw): + raise NotImplementedError( + "TestDir is not usable with nose 0.10. The class is present " + "in nose.suite for backwards compatibility purposes but it " + "may not be used.") + + +class TestModule: + def __init__(*arg, **kw): + raise NotImplementedError( + "TestModule is not usable with nose 0.10. The class is present " + "in nose.suite for backwards compatibility purposes but it " + "may not be used.") diff --git a/nose/tools.py b/nose/tools.py new file mode 100644 index 0000000..2fb3e75 --- /dev/null +++ b/nose/tools.py @@ -0,0 +1,194 @@ +""" +Tools for testing +----------------- + +nose.tools provides a few convenience functions to make writing tests +easier. You don't have to use them; nothing in the rest of nose depends +on any of these methods. +""" +import re +import time +import unittest + + +__all__ = ['ok_', 'eq_', 'make_decorator', 'raises', 'set_trace', 'timed', + 'with_setup', 'TimeExpired', 'istest', 'nottest'] + + +class TimeExpired(AssertionError): + pass + + +def ok_(expr, msg=None): + """Shorthand for assert. Saves 3 whole characters! + """ + assert expr, msg + + +def eq_(a, b, msg=None): + """Shorthand for 'assert a == b, "%r != %r" % (a, b) + """ + assert a == b, msg or "%r != %r" % (a, b) + + +def make_decorator(func): + """ + Wraps a test decorator so as to properly replicate metadata + of the decorated function, including nose's additional stuff + (namely, setup and teardown). + """ + def decorate(newfunc): + if hasattr(func, 'compat_func_name'): + name = func.compat_func_name + else: + name = func.__name__ + newfunc.__dict__ = func.__dict__ + newfunc.__doc__ = func.__doc__ + newfunc.__module__ = func.__module__ + if not hasattr(newfunc, 'compat_co_firstlineno'): + newfunc.compat_co_firstlineno = func.func_code.co_firstlineno + try: + newfunc.__name__ = name + except TypeError: + # can't set func name in 2.3 + newfunc.compat_func_name = name + return newfunc + return decorate + + +def raises(*exceptions): + """Test must raise one of expected exceptions to pass. + + Example use:: + + @raises(TypeError, ValueError) + def test_raises_type_error(): + raise TypeError("This test passes") + + @raises(Exception) + def test_that_fails_by_passing(): + pass + + If you want to test many assertions about exceptions in a single test, + you may want to use `assert_raises` instead. + """ + valid = ' or '.join([e.__name__ for e in exceptions]) + def decorate(func): + name = func.__name__ + def newfunc(*arg, **kw): + try: + func(*arg, **kw) + except exceptions: + pass + except: + raise + else: + message = "%s() did not raise %s" % (name, valid) + raise AssertionError(message) + newfunc = make_decorator(func)(newfunc) + return newfunc + return decorate + + +def set_trace(): + """Call pdb.set_trace in the calling frame, first restoring + sys.stdout to the real output stream. Note that sys.stdout is NOT + reset to whatever it was before the call once pdb is done! + """ + import pdb + import sys + stdout = sys.stdout + sys.stdout = sys.__stdout__ + pdb.Pdb().set_trace(sys._getframe().f_back) + + +def timed(limit): + """Test must finish within specified time limit to pass. + + Example use:: + + @timed(.1) + def test_that_fails(): + time.sleep(.2) + """ + def decorate(func): + def newfunc(*arg, **kw): + start = time.time() + func(*arg, **kw) + end = time.time() + if end - start > limit: + raise TimeExpired("Time limit (%s) exceeded" % limit) + newfunc = make_decorator(func)(newfunc) + return newfunc + return decorate + + +def with_setup(setup=None, teardown=None): + """Decorator to add setup and/or teardown methods to a test function:: + + @with_setup(setup, teardown) + def test_something(): + " ... " + + Note that `with_setup` is useful *only* for test functions, not for test + methods or inside of TestCase subclasses. + """ + def decorate(func, setup=setup, teardown=teardown): + if setup: + if hasattr(func, 'setup'): + _old_s = func.setup + def _s(): + setup() + _old_s() + func.setup = _s + else: + func.setup = setup + if teardown: + if hasattr(func, 'teardown'): + _old_t = func.teardown + def _t(): + _old_t() + teardown() + func.teardown = _t + else: + func.teardown = teardown + return func + return decorate + + +def istest(func): + """Decorator to mark a function or method as a test + """ + func.__test__ = True + return func + + +def nottest(func): + """Decorator to mark a function or method as *not* a test + """ + func.__test__ = False + return func + +# +# Expose assert* from unittest.TestCase +# - give them pep8 style names +# +caps = re.compile('([A-Z])') + +def pep8(name): + return caps.sub(lambda m: '_' + m.groups()[0].lower(), name) + +class Dummy(unittest.TestCase): + def nop(): + pass +_t = Dummy('nop') + +for at in [ at for at in dir(_t) + if at.startswith('assert') and not '_' in at ]: + pepd = pep8(at) + vars()[pepd] = getattr(_t, at) + __all__.append(pepd) + +del Dummy +del _t +del pep8 diff --git a/nose/twistedtools.py b/nose/twistedtools.py new file mode 100644 index 0000000..3720610 --- /dev/null +++ b/nose/twistedtools.py @@ -0,0 +1,168 @@ +""" +Twisted integration +------------------- + +This module provides a very simple way to integrate your tests with the +Twisted_ event loop. + +You must import this module *before* importing anything from Twisted itself! + +Example:: + + from nose.twistedtools import reactor, deferred + + @deferred() + def test_resolve(): + return reactor.resolve("www.python.org") + +Or, more realistically:: + + @deferred(timeout=5.0) + def test_resolve(): + d = reactor.resolve("www.python.org") + def check_ip(ip): + assert ip == "67.15.36.43" + d.addCallback(check_ip) + return d + +.. _Twisted: http://twistedmatrix.com/trac/ +""" + +import sys +from Queue import Queue, Empty +from nose.tools import make_decorator, TimeExpired + +__all__ = [ + 'threaded_reactor', 'reactor', 'deferred', 'TimeExpired', + 'stop_reactor' +] + +_twisted_thread = None + +def threaded_reactor(): + """ + Start the Twisted reactor in a separate thread, if not already done. + Returns the reactor. + The thread will automatically be destroyed when all the tests are done. + """ + global _twisted_thread + try: + from twisted.internet import reactor + except ImportError: + return None, None + if not _twisted_thread: + from twisted.python import threadable + from threading import Thread + _twisted_thread = Thread(target=lambda: reactor.run( \ + installSignalHandlers=False)) + _twisted_thread.setDaemon(True) + _twisted_thread.start() + return reactor, _twisted_thread + +# Export global reactor variable, as Twisted does +reactor, reactor_thread = threaded_reactor() + + +def stop_reactor(): + """Stop the reactor and join the reactor thread until it stops. + Call this function in teardown at the module or package level to + reset the twisted system after your tests. You *must* do this if + you mix tests using these tools and tests using twisted.trial. + """ + global _twisted_thread + reactor.stop() + reactor_thread.join() + for p in reactor.getDelayedCalls(): + if p.active(): + p.cancel() + _twisted_thread = None + + +def deferred(timeout=None): + """ + By wrapping a test function with this decorator, you can return a + twisted Deferred and the test will wait for the deferred to be triggered. + The whole test function will run inside the Twisted event loop. + + The optional timeout parameter specifies the maximum duration of the test. + The difference with timed() is that timed() will still wait for the test + to end, while deferred() will stop the test when its timeout has expired. + The latter is more desireable when dealing with network tests, because + the result may actually never arrive. + + If the callback is triggered, the test has passed. + If the errback is triggered or the timeout expires, the test has failed. + + Example:: + + @deferred(timeout=5.0) + def test_resolve(): + return reactor.resolve("www.python.org") + + Attention! If you combine this decorator with other decorators (like + "raises"), deferred() must be called *first*! + + In other words, this is good:: + + @raises(DNSLookupError) + @deferred() + def test_error(): + return reactor.resolve("xxxjhjhj.biz") + + and this is bad:: + + @deferred() + @raises(DNSLookupError) + def test_error(): + return reactor.resolve("xxxjhjhj.biz") + """ + reactor, reactor_thread = threaded_reactor() + if reactor is None: + raise ImportError("twisted is not available or could not be imported") + # Check for common syntax mistake + # (otherwise, tests can be silently ignored + # if one writes "@deferred" instead of "@deferred()") + try: + timeout is None or timeout + 0 + except TypeError: + raise TypeError("'timeout' argument must be a number or None") + + def decorate(func): + def wrapper(*args, **kargs): + q = Queue() + def callback(value): + q.put(None) + def errback(failure): + # Retrieve and save full exception info + try: + failure.raiseException() + except: + q.put(sys.exc_info()) + def g(): + try: + d = func(*args, **kargs) + try: + d.addCallbacks(callback, errback) + # Check for a common mistake and display a nice error + # message + except AttributeError: + raise TypeError("you must return a twisted Deferred " + "from your test case!") + # Catch exceptions raised in the test body (from the + # Twisted thread) + except: + q.put(sys.exc_info()) + reactor.callFromThread(g) + try: + error = q.get(timeout=timeout) + except Empty: + raise TimeExpired("timeout expired before end of test (%f s.)" + % timeout) + # Re-raise all exceptions + if error is not None: + exc_type, exc_value, tb = error + raise exc_type, exc_value, tb + wrapper = make_decorator(func)(wrapper) + return wrapper + return decorate + diff --git a/nose/usage.txt b/nose/usage.txt new file mode 100644 index 0000000..86caa2d --- /dev/null +++ b/nose/usage.txt @@ -0,0 +1,110 @@ +nose collects tests automatically from python source files, +directories and packages found in its working directory (which +defaults to the current working directory). Any python source file, +directory or package that matches the testMatch regular expression +(by default: `(?:^|[\b_\.-])[Tt]est)` will be collected as a test (or +source for collection of tests). In addition, all other packages +found in the working directory will be examined for python source files +or directories that match testMatch. Package discovery descends all +the way down the tree, so package.tests and package.sub.tests and +package.sub.sub2.tests will all be collected. + +Within a test directory or package, any python source file matching +testMatch will be examined for test cases. Within a test module, +functions and classes whose names match testMatch and TestCase +subclasses with any name will be loaded and executed as tests. Tests +may use the assert keyword or raise AssertionErrors to indicate test +failure. TestCase subclasses may do the same or use the various +TestCase methods available. + +Selecting Tests +--------------- + +To specify which tests to run, pass test names on the command line: + + %prog only_test_this.py + +Test names specified may be file or module names, and may optionally +indicate the test case to run by separating the module or file name +from the test case name with a colon. Filenames may be relative or +absolute. Examples: + + %prog test.module + %prog another.test:TestCase.test_method + %prog a.test:TestCase + %prog /path/to/test/file.py:test_function + +You may also change the working directory where nose looks for tests +by using the -w switch: + + %prog -w /path/to/tests + +Note, however, that support for multiple -w arguments is now deprecated +and will be removed in a future release. As of nose 0.10, you can get +the same behavior by specifying the target directories *without* +the -w switch: + + %prog /path/to/tests /another/path/to/tests + +Further customization of test selection and loading is possible +through the use of plugins. + +Test result output is identical to that of unittest, except for +the additional features (error classes, and plugin-supplied +features such as output capture and assert introspection) detailed +in the options below. + +Configuration +------------- + +In addition to passing command-line options, you may also put +configuration options in your project's *setup.cfg* file, or a .noserc +or nose.cfg file in your home directory. In any of these standard +.ini-style config files, you put your nosetests configuration in a +``[nosetests]`` section. Options are the same as on the command line, +with the -- prefix removed. For options that are simple switches, you +must supply a value: + + [nosetests] + verbosity=3 + with-doctest=1 + +All configuration files that are found will be loaded and their +options combined. You can override the standard config file loading +with the ``-c`` option. + +Using Plugins +------------- + +There are numerous nose plugins available via easy_install and +elsewhere. To use a plugin, just install it. The plugin will add +command line options to nosetests. To verify that the plugin is installed, +run: + + nosetests --plugins + +You can add -v or -vv to that command to show more information +about each plugin. + +If you are running nose.main() or nose.run() from a script, you +can specify a list of plugins to use by passing a list of plugins +with the plugins keyword argument. + +0.9 plugins +----------- + +nose 1.0 can use SOME plugins that were written for nose 0.9. The +default plugin manager inserts a compatibility wrapper around 0.9 +plugins that adapts the changed plugin api calls. However, plugins +that access nose internals are likely to fail, especially if they +attempt to access test case or test suite classes. For example, +plugins that try to determine if a test passed to startTest is an +individual test or a suite will fail, partly because suites are no +longer passed to startTest and partly because it's likely that the +plugin is trying to find out if the test is an instance of a class +that no longer exists. + +0.10 and 0.11 plugins +--------------------- + +All plugins written for nose 0.10 and 0.11 should work with nose 1.0. diff --git a/nose/util.py b/nose/util.py new file mode 100644 index 0000000..34920fb --- /dev/null +++ b/nose/util.py @@ -0,0 +1,663 @@ +"""Utility functions and classes used by nose internally. +""" +import inspect +import itertools +import logging +import os +import re +import sys +import types +import unittest +from nose.pyversion import ClassType, TypeType + +try: + from compiler.consts import CO_GENERATOR +except ImportError: + # IronPython doesn't have a complier module + CO_GENERATOR=0x20 + +log = logging.getLogger('nose') + +ident_re = re.compile(r'^[A-Za-z_][A-Za-z0-9_.]*$') +class_types = (ClassType, TypeType) +skip_pattern = r"(?:\.svn)|(?:[^.]+\.py[co])|(?:.*~)|(?:.*\$py\.class)" + +try: + set() + set = set # make from nose.util import set happy +except NameError: + try: + from sets import Set as set + except ImportError: + pass + + +def ls_tree(dir_path="", + skip_pattern=skip_pattern, + indent="|-- ", branch_indent="| ", + last_indent="`-- ", last_branch_indent=" "): + # TODO: empty directories look like non-directory files + return "\n".join(_ls_tree_lines(dir_path, skip_pattern, + indent, branch_indent, + last_indent, last_branch_indent)) + + +def _ls_tree_lines(dir_path, skip_pattern, + indent, branch_indent, last_indent, last_branch_indent): + if dir_path == "": + dir_path = os.getcwd() + + lines = [] + + names = os.listdir(dir_path) + names.sort() + dirs, nondirs = [], [] + for name in names: + if re.match(skip_pattern, name): + continue + if os.path.isdir(os.path.join(dir_path, name)): + dirs.append(name) + else: + nondirs.append(name) + + # list non-directories first + entries = list(itertools.chain([(name, False) for name in nondirs], + [(name, True) for name in dirs])) + def ls_entry(name, is_dir, ind, branch_ind): + if not is_dir: + yield ind + name + else: + path = os.path.join(dir_path, name) + if not os.path.islink(path): + yield ind + name + subtree = _ls_tree_lines(path, skip_pattern, + indent, branch_indent, + last_indent, last_branch_indent) + for x in subtree: + yield branch_ind + x + for name, is_dir in entries[:-1]: + for line in ls_entry(name, is_dir, indent, branch_indent): + yield line + if entries: + name, is_dir = entries[-1] + for line in ls_entry(name, is_dir, last_indent, last_branch_indent): + yield line + + +def absdir(path): + """Return absolute, normalized path to directory, if it exists; None + otherwise. + """ + if not os.path.isabs(path): + path = os.path.normpath(os.path.abspath(os.path.join(os.getcwd(), + path))) + if path is None or not os.path.isdir(path): + return None + return path + + +def absfile(path, where=None): + """Return absolute, normalized path to file (optionally in directory + where), or None if the file can't be found either in where or the current + working directory. + """ + orig = path + if where is None: + where = os.getcwd() + if isinstance(where, list) or isinstance(where, tuple): + for maybe_path in where: + maybe_abs = absfile(path, maybe_path) + if maybe_abs is not None: + return maybe_abs + return None + if not os.path.isabs(path): + path = os.path.normpath(os.path.abspath(os.path.join(where, path))) + if path is None or not os.path.exists(path): + if where != os.getcwd(): + # try the cwd instead + path = os.path.normpath(os.path.abspath(os.path.join(os.getcwd(), + orig))) + if path is None or not os.path.exists(path): + return None + if os.path.isdir(path): + # might want an __init__.py from pacakge + init = os.path.join(path,'__init__.py') + if os.path.isfile(init): + return init + elif os.path.isfile(path): + return path + return None + + +def anyp(predicate, iterable): + for item in iterable: + if predicate(item): + return True + return False + + +def file_like(name): + """A name is file-like if it is a path that exists, or it has a + directory part, or it ends in .py, or it isn't a legal python + identifier. + """ + return (os.path.exists(name) + or os.path.dirname(name) + or name.endswith('.py') + or not ident_re.match(os.path.splitext(name)[0])) + + +def func_lineno(func): + """Get the line number of a function. First looks for + compat_co_firstlineno, then func_code.co_first_lineno. + """ + try: + return func.compat_co_firstlineno + except AttributeError: + try: + return func.func_code.co_firstlineno + except AttributeError: + return -1 + + +def isclass(obj): + """Is obj a class? Inspect's isclass is too liberal and returns True + for objects that can't be subclasses of anything. + """ + obj_type = type(obj) + return obj_type in class_types or issubclass(obj_type, type) + + +def isgenerator(func): + try: + return func.func_code.co_flags & CO_GENERATOR != 0 + except AttributeError: + return False +# backwards compat (issue #64) +is_generator = isgenerator + + +def ispackage(path): + """ + Is this path a package directory? + + >>> ispackage('nose') + True + >>> ispackage('unit_tests') + False + >>> ispackage('nose/plugins') + True + >>> ispackage('nose/loader.py') + False + """ + if os.path.isdir(path): + # at least the end of the path must be a legal python identifier + # and __init__.py[co] must exist + end = os.path.basename(path) + if ident_re.match(end): + for init in ('__init__.py', '__init__.pyc', '__init__.pyo'): + if os.path.isfile(os.path.join(path, init)): + return True + if sys.platform.startswith('java') and \ + os.path.isfile(os.path.join(path, '__init__$py.class')): + return True + return False + + +def isproperty(obj): + """ + Is this a property? + + >>> class Foo: + ... def got(self): + ... return 2 + ... def get(self): + ... return 1 + ... get = property(get) + + >>> isproperty(Foo.got) + False + >>> isproperty(Foo.get) + True + """ + return type(obj) == property + + +def getfilename(package, relativeTo=None): + """Find the python source file for a package, relative to a + particular directory (defaults to current working directory if not + given). + """ + if relativeTo is None: + relativeTo = os.getcwd() + path = os.path.join(relativeTo, os.sep.join(package.split('.'))) + suffixes = ('/__init__.py', '.py') + for suffix in suffixes: + filename = path + suffix + if os.path.exists(filename): + return filename + return None + + +def getpackage(filename): + """ + Find the full dotted package name for a given python source file + name. Returns None if the file is not a python source file. + + >>> getpackage('foo.py') + 'foo' + >>> getpackage('biff/baf.py') + 'baf' + >>> getpackage('nose/util.py') + 'nose.util' + + Works for directories too. + + >>> getpackage('nose') + 'nose' + >>> getpackage('nose/plugins') + 'nose.plugins' + + And __init__ files stuck onto directories + + >>> getpackage('nose/plugins/__init__.py') + 'nose.plugins' + + Absolute paths also work. + + >>> path = os.path.abspath(os.path.join('nose', 'plugins')) + >>> getpackage(path) + 'nose.plugins' + """ + src_file = src(filename) + if not src_file.endswith('.py') and not ispackage(src_file): + return None + base, ext = os.path.splitext(os.path.basename(src_file)) + if base == '__init__': + mod_parts = [] + else: + mod_parts = [base] + path, part = os.path.split(os.path.split(src_file)[0]) + while part: + if ispackage(os.path.join(path, part)): + mod_parts.append(part) + else: + break + path, part = os.path.split(path) + mod_parts.reverse() + return '.'.join(mod_parts) + + +def ln(label): + """Draw a 70-char-wide divider, with label in the middle. + + >>> ln('hello there') + '---------------------------- hello there -----------------------------' + """ + label_len = len(label) + 2 + chunk = (70 - label_len) // 2 + out = '%s %s %s' % ('-' * chunk, label, '-' * chunk) + pad = 70 - len(out) + if pad > 0: + out = out + ('-' * pad) + return out + + +def resolve_name(name, module=None): + """Resolve a dotted name to a module and its parts. This is stolen + wholesale from unittest.TestLoader.loadTestByName. + + >>> resolve_name('nose.util') #doctest: +ELLIPSIS + + >>> resolve_name('nose.util.resolve_name') #doctest: +ELLIPSIS + + """ + parts = name.split('.') + parts_copy = parts[:] + if module is None: + while parts_copy: + try: + log.debug("__import__ %s", name) + module = __import__('.'.join(parts_copy)) + break + except ImportError: + del parts_copy[-1] + if not parts_copy: + raise + parts = parts[1:] + obj = module + log.debug("resolve: %s, %s, %s, %s", parts, name, obj, module) + for part in parts: + obj = getattr(obj, part) + return obj + + +def split_test_name(test): + """Split a test name into a 3-tuple containing file, module, and callable + names, any of which (but not all) may be blank. + + Test names are in the form: + + file_or_module:callable + + Either side of the : may be dotted. To change the splitting behavior, you + can alter nose.util.split_test_re. + """ + norm = os.path.normpath + file_or_mod = test + fn = None + if not ':' in test: + # only a file or mod part + if file_like(test): + return (norm(test), None, None) + else: + return (None, test, None) + + # could be path|mod:callable, or a : in the file path someplace + head, tail = os.path.split(test) + if not head: + # this is a case like 'foo:bar' -- generally a module + # name followed by a callable, but also may be a windows + # drive letter followed by a path + try: + file_or_mod, fn = test.split(':') + if file_like(fn): + # must be a funny path + file_or_mod, fn = test, None + except ValueError: + # more than one : in the test + # this is a case like c:\some\path.py:a_test + parts = test.split(':') + if len(parts[0]) == 1: + file_or_mod, fn = ':'.join(parts[:-1]), parts[-1] + else: + # nonsense like foo:bar:baz + raise ValueError("Test name '%s' could not be parsed. Please " + "format test names as path:callable or " + "module:callable.") + elif not tail: + # this is a case like 'foo:bar/' + # : must be part of the file path, so ignore it + file_or_mod = test + else: + if ':' in tail: + file_part, fn = tail.split(':') + else: + file_part = tail + file_or_mod = os.sep.join([head, file_part]) + if file_or_mod: + if file_like(file_or_mod): + return (norm(file_or_mod), None, fn) + else: + return (None, file_or_mod, fn) + else: + return (None, None, fn) +split_test_name.__test__ = False # do not collect + + +def test_address(test): + """Find the test address for a test, which may be a module, filename, + class, method or function. + """ + if hasattr(test, "address"): + return test.address() + # type-based polymorphism sucks in general, but I believe is + # appropriate here + t = type(test) + file = module = call = None + if t == types.ModuleType: + file = getattr(test, '__file__', None) + module = getattr(test, '__name__', None) + return (src(file), module, call) + if t == types.FunctionType or issubclass(t, type) or t == types.ClassType: + module = getattr(test, '__module__', None) + if module is not None: + m = sys.modules[module] + file = getattr(m, '__file__', None) + if file is not None: + file = os.path.abspath(file) + call = getattr(test, '__name__', None) + return (src(file), module, call) + if t == types.MethodType: + cls_adr = test_address(test.im_class) + return (src(cls_adr[0]), cls_adr[1], + "%s.%s" % (cls_adr[2], test.__name__)) + # handle unittest.TestCase instances + if isinstance(test, unittest.TestCase): + if (hasattr(test, '_FunctionTestCase__testFunc') # pre 2.7 + or hasattr(test, '_testFunc')): # 2.7 + # unittest FunctionTestCase + try: + return test_address(test._FunctionTestCase__testFunc) + except AttributeError: + return test_address(test._testFunc) + # regular unittest.TestCase + cls_adr = test_address(test.__class__) + # 2.5 compat: __testMethodName changed to _testMethodName + try: + method_name = test._TestCase__testMethodName + except AttributeError: + method_name = test._testMethodName + return (src(cls_adr[0]), cls_adr[1], + "%s.%s" % (cls_adr[2], method_name)) + if hasattr(test, '__class__') and test.__class__.__module__ != 'builtins': + return test_address(test.__class__) + raise TypeError("I don't know what %s is (%s)" % (test, t)) +test_address.__test__ = False # do not collect + + +def try_run(obj, names): + """Given a list of possible method names, try to run them with the + provided object. Keep going until something works. Used to run + setup/teardown methods for module, package, and function tests. + """ + for name in names: + func = getattr(obj, name, None) + if func is not None: + if type(obj) == types.ModuleType: + # py.test compatibility + try: + args, varargs, varkw, defaults = inspect.getargspec(func) + except TypeError: + # Not a function. If it's callable, call it anyway + if hasattr(func, '__call__'): + func = func.__call__ + try: + args, varargs, varkw, defaults = \ + inspect.getargspec(func) + args.pop(0) # pop the self off + except TypeError: + raise TypeError("Attribute %s of %r is not a python " + "function. Only functions or callables" + " may be used as fixtures." % + (name, obj)) + if len(args): + log.debug("call fixture %s.%s(%s)", obj, name, obj) + return func(obj) + log.debug("call fixture %s.%s", obj, name) + return func() + + +def src(filename): + """Find the python source file for a .pyc, .pyo or $py.class file on + jython. Returns the filename provided if it is not a python source + file. + """ + if filename is None: + return filename + if sys.platform.startswith('java') and filename.endswith('$py.class'): + return '.'.join((filename[:-9], 'py')) + base, ext = os.path.splitext(filename) + if ext in ('.pyc', '.pyo', '.py'): + return '.'.join((base, 'py')) + return filename + + +def regex_last_key(regex): + """Sort key function factory that puts items that match a + regular expression last. + + >>> from nose.config import Config + >>> from nose.pyversion import sort_list + >>> c = Config() + >>> regex = c.testMatch + >>> entries = ['.', '..', 'a_test', 'src', 'lib', 'test', 'foo.py'] + >>> sort_list(entries, regex_last_key(regex)) + >>> entries + ['.', '..', 'foo.py', 'lib', 'src', 'a_test', 'test'] + """ + def k(obj): + if regex.search(obj): + return (1, obj) + return (0, obj) + return k + + +def tolist(val): + """Convert a value that may be a list or a (possibly comma-separated) + string into a list. The exception: None is returned as None, not [None]. + + >>> tolist(["one", "two"]) + ['one', 'two'] + >>> tolist("hello") + ['hello'] + >>> tolist("separate,values, with, commas, spaces , are ,ok") + ['separate', 'values', 'with', 'commas', 'spaces', 'are', 'ok'] + """ + if val is None: + return None + try: + # might already be a list + val.extend([]) + return val + except AttributeError: + pass + # might be a string + try: + return re.split(r'\s*,\s*', val) + except TypeError: + # who knows... + return list(val) + + +class odict(dict): + """Simple ordered dict implementation, based on: + + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747 + """ + def __init__(self, *arg, **kw): + self._keys = [] + super(odict, self).__init__(*arg, **kw) + + def __delitem__(self, key): + super(odict, self).__delitem__(key) + self._keys.remove(key) + + def __setitem__(self, key, item): + super(odict, self).__setitem__(key, item) + if key not in self._keys: + self._keys.append(key) + + def __str__(self): + return "{%s}" % ', '.join(["%r: %r" % (k, v) for k, v in self.items()]) + + def clear(self): + super(odict, self).clear() + self._keys = [] + + def copy(self): + d = super(odict, self).copy() + d._keys = self._keys[:] + return d + + def items(self): + return zip(self._keys, self.values()) + + def keys(self): + return self._keys[:] + + def setdefault(self, key, failobj=None): + item = super(odict, self).setdefault(key, failobj) + if key not in self._keys: + self._keys.append(key) + return item + + def update(self, dict): + super(odict, self).update(dict) + for key in dict.keys(): + if key not in self._keys: + self._keys.append(key) + + def values(self): + return map(self.get, self._keys) + + +def transplant_func(func, module): + """ + Make a function imported from module A appear as if it is located + in module B. + + >>> from pprint import pprint + >>> pprint.__module__ + 'pprint' + >>> pp = transplant_func(pprint, __name__) + >>> pp.__module__ + 'nose.util' + + The original function is not modified. + + >>> pprint.__module__ + 'pprint' + + Calling the transplanted function calls the original. + + >>> pp([1, 2]) + [1, 2] + >>> pprint([1,2]) + [1, 2] + + """ + from nose.tools import make_decorator + def newfunc(*arg, **kw): + return func(*arg, **kw) + + newfunc = make_decorator(func)(newfunc) + newfunc.__module__ = module + return newfunc + + +def transplant_class(cls, module): + """ + Make a class appear to reside in `module`, rather than the module in which + it is actually defined. + + >>> from nose.failure import Failure + >>> Failure.__module__ + 'nose.failure' + >>> Nf = transplant_class(Failure, __name__) + >>> Nf.__module__ + 'nose.util' + >>> Nf.__name__ + 'Failure' + + """ + class C(cls): + pass + C.__module__ = module + C.__name__ = cls.__name__ + return C + + +def safe_str(val, encoding='utf-8'): + try: + return str(val) + except UnicodeEncodeError: + if isinstance(val, Exception): + return ' '.join([safe_str(arg, encoding) + for arg in val]) + return unicode(val).encode(encoding) + + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/nosetests.1 b/nosetests.1 new file mode 100644 index 0000000..aee8c77 --- /dev/null +++ b/nosetests.1 @@ -0,0 +1,480 @@ +.TH nosetests 1 "2009-04-23" "0.11" "User Commands" +.SH NAME +nosetests \- nicer testing for python +.\" Man page generated from reStructeredText. +.INDENT 0.0 +.UNINDENT + +.SH SYNOPSIS +.INDENT 0.0 +.INDENT 3.5 +nosetests [options] [names] + +.UNINDENT +.UNINDENT + +.SH DESCRIPTION +nose collects tests automatically from python source files, +directories and packages found in its working directory (which +defaults to the current working directory). Any python source file, +directory or package that matches the testMatch regular expression +(by default: \fI(?:^|[b_.\-])[Tt]est)\fP will be collected as a test (or +source for collection of tests). In addition, all other packages +found in the working directory will be examined for python source files +or directories that match testMatch. Package discovery descends all +the way down the tree, so package.tests and package.sub.tests and +package.sub.sub2.tests will all be collected. + +Within a test directory or package, any python source file matching +testMatch will be examined for test cases. Within a test module, +functions and classes whose names match testMatch and TestCase +subclasses with any name will be loaded and executed as tests. Tests +may use the assert keyword or raise AssertionErrors to indicate test +failure. TestCase subclasses may do the same or use the various +TestCase methods available. + + +.SS Selecting Tests +To specify which tests to run, pass test names on the command line: + + +.nf +nosetests only_test_this.py +.fi +Test names specified may be file or module names, and may optionally +indicate the test case to run by separating the module or file name +from the test case name with a colon. Filenames may be relative or +absolute. Examples: + + +.nf +nosetests test.module +nosetests another.test:TestCase.test_method +nosetests a.test:TestCase +nosetests /path/to/test/file.py:test_function +.fi +You may also change the working directory where nose looks for tests +by using the \-w switch: + + +.nf +nosetests \-w /path/to/tests +.fi +Note, however, that support for multiple \-w arguments is now deprecated +and will be removed in a future release. As of nose 0.10, you can get +the same behavior by specifying the target directories \fIwithout\fP +the \-w switch: + + +.nf +nosetests /path/to/tests /another/path/to/tests +.fi +Further customization of test selection and loading is possible +through the use of plugins. + +Test result output is identical to that of unittest, except for +the additional features (error classes, and plugin\-supplied +features such as output capture and assert introspection) detailed +in the options below. + + +.SS Configuration +In addition to passing command\-line options, you may also put +configuration options in your project\'s \fIsetup.cfg\fP file, or a .noserc +or nose.cfg file in your home directory. In any of these standard +.ini\-style config files, you put your nosetests configuration in a +\fB[nosetests]\fP section. Options are the same as on the command line, +with the \-\- prefix removed. For options that are simple switches, you +must supply a value: + + +.nf +[nosetests] +verbosity=3 +with\-doctest=1 +.fi +All configuration files that are found will be loaded and their +options combined. You can override the standard config file loading +with the \fB\-c\fP option. + + +.SS Using Plugins +There are numerous nose plugins available via easy_install and +elsewhere. To use a plugin, just install it. The plugin will add +command line options to nosetests. To verify that the plugin is installed, +run: + + +.nf +nosetests \-\-plugins +.fi +You can add \-v or \-vv to that command to show more information +about each plugin. + +If you are running nose.main() or nose.run() from a script, you +can specify a list of plugins to use by passing a list of plugins +with the plugins keyword argument. + + +.SS 0.9 plugins +nose 1.0 can use SOME plugins that were written for nose 0.9. The +default plugin manager inserts a compatibility wrapper around 0.9 +plugins that adapts the changed plugin api calls. However, plugins +that access nose internals are likely to fail, especially if they +attempt to access test case or test suite classes. For example, +plugins that try to determine if a test passed to startTest is an +individual test or a suite will fail, partly because suites are no +longer passed to startTest and partly because it\'s likely that the +plugin is trying to find out if the test is an instance of a class +that no longer exists. + + +.SS 0.10 and 0.11 plugins +All plugins written for nose 0.10 and 0.11 should work with nose 1.0. + + +.SS Options + +.TP +\fB\-V\fR\fR\fR, \fB\-\-version\fR\fR +Output nose version and exit + + +.TP +\fB\-p\fR\fR\fR, \fB\-\-plugins\fR\fR +Output list of available plugins and exit. Combine with higher verbosity for greater detail + + +.TP +\fB\-v\fR\fR=DEFAULT\fR, \fB\-\-verbose\fR\fR=DEFAULT +Be more verbose. [NOSE_VERBOSE] + + +.TP +\fB\-\-verbosity\fR\fR=VERBOSITY +Set verbosity; \-\-verbosity=2 is the same as \-v + + +.TP +\fB\-q\fR\fR=DEFAULT\fR, \fB\-\-quiet\fR\fR=DEFAULT +Be less verbose + + +.TP +\fB\-c\fR\fR=FILES\fR, \fB\-\-config\fR\fR=FILES +Load configuration from config file(s). May be specified multiple times; in that case, all config files will be loaded and combined + + +.TP +\fB\-w\fR\fR=WHERE\fR, \fB\-\-where\fR\fR=WHERE +Look for tests in this directory. May be specified multiple times. The first directory passed will be used as the working directory, in place of the current working directory, which is the default. Others will be added to the list of tests to execute. [NOSE_WHERE] + + +.TP +\fB\-\-py3where\fR\fR=PY3WHERE +Look for tests in this directory under Python 3.x. Functions the same as \'where\', but only applies if running under Python 3.x or above. Note that, if present under 3.x, this option completely replaces any directories specified with \'where\', so the \'where\' option becomes ineffective. [NOSE_PY3WHERE] + + +.TP +\fB\-m\fR\fR=REGEX\fR, \fB\-\-match\fR\fR=REGEX\fR, \fB\-\-testmatch\fR\fR=REGEX +Files, directories, function names, and class names that match this regular expression are considered tests. Default: (?:^|[b_./\-])[Tt]est [NOSE_TESTMATCH] + + +.TP +\fB\-\-tests\fR\fR=NAMES +Run these tests (comma\-separated list). This argument is useful mainly from configuration files; on the command line, just pass the tests to run as additional arguments with no switch. + + +.TP +\fB\-l\fR\fR=DEFAULT\fR, \fB\-\-debug\fR\fR=DEFAULT +Activate debug logging for one or more systems. Available debug loggers: nose, nose.importer, nose.inspector, nose.plugins, nose.result and nose.selector. Separate multiple names with a comma. + + +.TP +\fB\-\-debug\-log\fR\fR=FILE +Log debug messages to this file (default: sys.stderr) + + +.TP +\fB\-\-logging\-config\fR\fR=FILE\fR, \fB\-\-log\-config\fR\fR=FILE +Load logging config from this file \-\- bypasses all other logging config settings. + + +.TP +\fB\-I\fR\fR=REGEX\fR, \fB\-\-ignore\-files\fR\fR=REGEX +Completely ignore any file that matches this regular expression. Takes precedence over any other settings or plugins. Specifying this option will replace the default setting. Specify this option multiple times to add more regular expressions [NOSE_IGNORE_FILES] + + +.TP +\fB\-e\fR\fR=REGEX\fR, \fB\-\-exclude\fR\fR=REGEX +Don\'t run tests that match regular expression [NOSE_EXCLUDE] + + +.TP +\fB\-i\fR\fR=REGEX\fR, \fB\-\-include\fR\fR=REGEX +This regular expression will be applied to files, directories, function names, and class names for a chance to include additional tests that do not match TESTMATCH. Specify this option multiple times to add more regular expressions [NOSE_INCLUDE] + + +.TP +\fB\-x\fR\fR\fR, \fB\-\-stop\fR\fR +Stop running tests after the first error or failure + + +.TP +\fB\-P\fR\fR\fR, \fB\-\-no\-path\-adjustment\fR\fR +Don\'t make any changes to sys.path when loading tests [NOSE_NOPATH] + + +.TP +\fB\-\-exe\fR\fR +Look for tests in python modules that are executable. Normal behavior is to exclude executable modules, since they may not be import\-safe [NOSE_INCLUDE_EXE] + + +.TP +\fB\-\-noexe\fR\fR +DO NOT look for tests in python modules that are executable. (The default on the windows platform is to do so.) + + +.TP +\fB\-\-traverse\-namespace\fR\fR +Traverse through all path entries of a namespace package + + +.TP +\fB\-\-first\-package\-wins\fR\fR\fR, \fB\-\-first\-pkg\-wins\fR\fR\fR, \fB\-\-1st\-pkg\-wins\fR\fR +nose\'s importer will normally evict a package from sys.modules if it sees a package with the same name in a different location. Set this option to disable that behavior. + + +.TP +\fB\-a\fR\fR=ATTR\fR, \fB\-\-attr\fR\fR=ATTR +Run only tests that have attributes specified by ATTR [NOSE_ATTR] + + +.TP +\fB\-A\fR\fR=EXPR\fR, \fB\-\-eval\-attr\fR\fR=EXPR +Run only tests for whose attributes the Python expression EXPR evaluates to True [NOSE_EVAL_ATTR] + + +.TP +\fB\-s\fR\fR\fR, \fB\-\-nocapture\fR\fR +Don\'t capture stdout (any stdout output will be printed immediately) [NOSE_NOCAPTURE] + + +.TP +\fB\-\-nologcapture\fR\fR +Disable logging capture plugin. Logging configurtion will be left intact. [NOSE_NOLOGCAPTURE] + + +.TP +\fB\-\-logging\-format\fR\fR=FORMAT +Specify custom format to print statements. Uses the same format as used by standard logging handlers. [NOSE_LOGFORMAT] + + +.TP +\fB\-\-logging\-datefmt\fR\fR=FORMAT +Specify custom date/time format to print statements. Uses the same format as used by standard logging handlers. [NOSE_LOGDATEFMT] + + +.TP +\fB\-\-logging\-filter\fR\fR=FILTER +Specify which statements to filter in/out. By default, everything is captured. If the output is too verbose, +use this option to filter out needless output. +Example: filter=foo will capture statements issued ONLY to + foo or foo.what.ever.sub but not foobar or other logger. +Specify multiple loggers with comma: filter=foo,bar,baz. +If any logger name is prefixed with a minus, eg filter=\-foo, +it will be excluded rather than included. Default: exclude logging messages from nose itself (\-nose). [NOSE_LOGFILTER] + + +.TP +\fB\-\-logging\-clear\-handlers\fR\fR +Clear all other logging handlers + + +.TP +\fB\-\-with\-coverage\fR\fR +Enable plugin Coverage: +Activate a coverage report using Ned Batchelder\'s coverage module. + [NOSE_WITH_COVERAGE] + + +.TP +\fB\-\-cover\-package\fR\fR=PACKAGE +Restrict coverage output to selected packages [NOSE_COVER_PACKAGE] + + +.TP +\fB\-\-cover\-erase\fR\fR +Erase previously collected coverage statistics before run + + +.TP +\fB\-\-cover\-tests\fR\fR +Include test modules in coverage report [NOSE_COVER_TESTS] + + +.TP +\fB\-\-cover\-inclusive\fR\fR +Include all python files under working directory in coverage report. Useful for discovering holes in test coverage if not all files are imported by the test suite. [NOSE_COVER_INCLUSIVE] + + +.TP +\fB\-\-cover\-html\fR\fR +Produce HTML coverage information + + +.TP +\fB\-\-cover\-html\-dir\fR\fR=DIR +Produce HTML coverage information in dir + + +.TP +\fB\-\-pdb\fR\fR +Drop into debugger on errors + + +.TP +\fB\-\-pdb\-failures\fR\fR +Drop into debugger on failures + + +.TP +\fB\-\-no\-deprecated\fR\fR +Disable special handling of DeprecatedTest exceptions. + + +.TP +\fB\-\-with\-doctest\fR\fR +Enable plugin Doctest: +Activate doctest plugin to find and run doctests in non\-test modules. + [NOSE_WITH_DOCTEST] + + +.TP +\fB\-\-doctest\-tests\fR\fR +Also look for doctests in test modules. Note that classes, methods and functions should have either doctests or non\-doctest tests, not both. [NOSE_DOCTEST_TESTS] + + +.TP +\fB\-\-doctest\-extension\fR\fR=EXT +Also look for doctests in files with this extension [NOSE_DOCTEST_EXTENSION] + + +.TP +\fB\-\-doctest\-result\-variable\fR\fR=VAR +Change the variable name set to the result of the last interpreter command from the default \'_\'. Can be used to avoid conflicts with the _() function used for text translation. [NOSE_DOCTEST_RESULT_VAR] + + +.TP +\fB\-\-doctest\-fixtures\fR\fR=SUFFIX +Find fixtures for a doctest file in module with this name appended to the base name of the doctest file + + +.TP +\fB\-\-with\-isolation\fR\fR +Enable plugin IsolationPlugin: +Activate the isolation plugin to isolate changes to external +modules to a single test module or package. The isolation plugin +resets the contents of sys.modules after each test module or +package runs to its state before the test. PLEASE NOTE that this +plugin should not be used with the coverage plugin, or in any other case +where module reloading may produce undesirable side\-effects. + [NOSE_WITH_ISOLATION] + + +.TP +\fB\-d\fR\fR\fR, \fB\-\-detailed\-errors\fR\fR\fR, \fB\-\-failure\-detail\fR\fR +Add detail to error output by attempting to evaluate failed asserts [NOSE_DETAILED_ERRORS] + + +.TP +\fB\-\-with\-profile\fR\fR +Enable plugin Profile: +Use this plugin to run tests using the hotshot profiler. + [NOSE_WITH_PROFILE] + + +.TP +\fB\-\-profile\-sort\fR\fR=SORT +Set sort order for profiler output + + +.TP +\fB\-\-profile\-stats\-file\fR\fR=FILE +Profiler stats file; default is a new temp file on each run + + +.TP +\fB\-\-profile\-restrict\fR\fR=RESTRICT +Restrict profiler output. See help for pstats.Stats for details + + +.TP +\fB\-\-no\-skip\fR\fR +Disable special handling of SkipTest exceptions. + + +.TP +\fB\-\-with\-id\fR\fR +Enable plugin TestId: +Activate to add a test id (like #1) to each test name output. Activate +with \-\-failed to rerun failing tests only. + [NOSE_WITH_ID] + + +.TP +\fB\-\-id\-file\fR\fR=FILE +Store test ids found in test runs in this file. Default is the file .noseids in the working directory. + + +.TP +\fB\-\-failed\fR\fR +Run the tests that failed in the last test run. + + +.TP +\fB\-\-processes\fR\fR=NUM +Spread test run among this many processes. Set a number equal to the number of processors or cores in your machine for best results. [NOSE_PROCESSES] + + +.TP +\fB\-\-process\-timeout\fR\fR=SECONDS +Set timeout for return of results from each test runner process. [NOSE_PROCESS_TIMEOUT] + + +.TP +\fB\-\-process\-restartworker\fR\fR +If set, will restart each worker process once their tests are done, this helps control memory leaks from killing the system. [NOSE_PROCESS_RESTARTWORKER] + + +.TP +\fB\-\-with\-xunit\fR\fR +Enable plugin Xunit: This plugin provides test results in the standard XUnit XML format. [NOSE_WITH_XUNIT] + + +.TP +\fB\-\-xunit\-file\fR\fR=FILE +Path to xml file to store the xunit report in. Default is nosetests.xml in the working directory [NOSE_XUNIT_FILE] + + +.TP +\fB\-\-all\-modules\fR\fR +Enable plugin AllModules: Collect tests from all python modules. + [NOSE_ALL_MODULES] + + +.TP +\fB\-\-collect\-only\fR\fR +Enable collect\-only: +Collect and output test names only, don\'t run any tests. + [COLLECT_ONLY] + + +.SH AUTHOR +jpellerin+nose@gmail.com + +.SH COPYRIGHT +LGPL + +.\" Generated by docutils manpage writer on 2011-07-30 18:55. +.\" diff --git a/patch.py b/patch.py new file mode 100644 index 0000000..981097c --- /dev/null +++ b/patch.py @@ -0,0 +1,639 @@ +""" Patch utility to apply unified diffs + + Brute-force line-by-line non-recursive parsing + + Copyright (c) 2008-2010 anatoly techtonik + Available under the terms of MIT license + + NOTE: This version has been patched by Alex Stewart for + Python 3.x support and other misc fixups. + + Project home: http://code.google.com/p/python-patch/ + + + $Id: patch.py 92 2010-07-02 06:04:57Z techtonik $ + $HeadURL: http://python-patch.googlecode.com/svn/trunk/patch.py $ +""" + +__author__ = "techtonik.rainforce.org" +__version__ = "10.04-2.pAS1" + +import copy +import logging +import re +from logging import debug, info, warning +import sys + +try: + # cStringIO doesn't support unicode in 2.5 + from StringIO import StringIO +except ImportError: + # StringIO has been renamed to 'io' in 3.x + from io import StringIO + +from os.path import exists, isfile, abspath +from os import unlink + +_open = open + +if sys.version_info >= (3,): + # Open files with universal newline support but no newline translation (3.x) + def open(filename, mode='r'): + return _open(filename, mode, newline='') +else: + # Open files with universal newline support but no newline translation (2.x) + def open(filename, mode='r'): + return _open(filename, mode + 'b') + + # Python 3.x has changed iter.next() to be next(iter) instead, so for + # backwards compatibility, we'll just define a next() function under 2.x + def next(iter): + return iter.next() + + +#------------------------------------------------ +# Logging is controlled by "python_patch" logger + +debugmode = False + +logger = logging.getLogger("python_patch") +loghandler = logging.StreamHandler() +logger.addHandler(loghandler) + +debug = logger.debug +info = logger.info +warning = logger.warning + +# If called as a library, don't log info/debug messages by default. +logger.setLevel(logging.WARN) + +#------------------------------------------------ + +# constants for patch types + +DIFF = PLAIN = "plain" +HG = MERCURIAL = "mercurial" +SVN = SUBVERSION = "svn" + + +def fromfile(filename): + """ Parse patch file and return Patch() object + """ + info("reading patch from file %s" % filename) + fp = open(filename, "r") + patch = Patch(fp) + fp.close() + return patch + + +def fromstring(s): + """ Parse text string and return Patch() object + """ + return Patch( StringIO(s) ) + + + +class HunkInfo(object): + """ Parsed hunk data container (hunk starts with @@ -R +R @@) """ + + def __init__(self): + self.startsrc=None #: line count starts with 1 + self.linessrc=None + self.starttgt=None + self.linestgt=None + self.invalid=False + self.text=[] + + def copy(self): + return copy.copy(self) + +# def apply(self, estream): +# """ write hunk data into enumerable stream +# return strings one by one until hunk is +# over +# +# enumerable stream are tuples (lineno, line) +# where lineno starts with 0 +# """ +# pass + + + +class Patch(object): + + def __init__(self, stream=None): + + # define Patch data members + # table with a row for every source file + + #: list of source filenames + self.source=None + self.target=None + #: list of lists of hunks + self.hunks=None + #: file endings statistics for every hunk + self.hunkends=None + #: headers for each file + self.header=None + + #: patch type - one of constants + self.type = None + + if stream: + self.parse(stream) + + def copy(self): + return copy.copy(self) + + def parse(self, stream): + """ parse unified diff """ + self.header = [] + + self.source = [] + self.target = [] + self.hunks = [] + self.hunkends = [] + + # define possible file regions that will direct the parser flow + headscan = False # scanning header before the patch body + filenames = False # lines starting with --- and +++ + + hunkhead = False # @@ -R +R @@ sequence + hunkbody = False # + hunkskip = False # skipping invalid hunk mode + + headscan = True + lineends = dict(lf=0, crlf=0, cr=0) + nextfileno = 0 + nexthunkno = 0 #: even if index starts with 0 user messages number hunks from 1 + + # hunkinfo holds parsed values, hunkactual - calculated + hunkinfo = HunkInfo() + hunkactual = dict(linessrc=None, linestgt=None) + + + fe = enumerate(stream) + for lineno, line in fe: + + # read out header + if headscan: + header = '' + try: + while not line.startswith("--- "): + header += line + lineno, line = next(fe) + except StopIteration: + # this is actually a loop exit + continue + self.header.append(header) + + headscan = False + # switch to filenames state + filenames = True + + # hunkskip and hunkbody code skipped until definition of hunkhead is parsed + if hunkbody: + # process line first + if re.match(r"^[- \+\\]", line): + # gather stats about line endings + if line.endswith("\r\n"): + self.hunkends[nextfileno-1]["crlf"] += 1 + elif line.endswith("\n"): + self.hunkends[nextfileno-1]["lf"] += 1 + elif line.endswith("\r"): + self.hunkends[nextfileno-1]["cr"] += 1 + + if line.startswith("-"): + hunkactual["linessrc"] += 1 + elif line.startswith("+"): + hunkactual["linestgt"] += 1 + elif not line.startswith("\\"): + hunkactual["linessrc"] += 1 + hunkactual["linestgt"] += 1 + hunkinfo.text.append(line) + # todo: handle \ No newline cases + else: + warning("invalid hunk no.%d at %d for target file %s" % (nexthunkno, lineno+1, self.target[nextfileno-1])) + # add hunk status node + self.hunks[nextfileno-1].append(hunkinfo.copy()) + self.hunks[nextfileno-1][nexthunkno-1]["invalid"] = True + # switch to hunkskip state + hunkbody = False + hunkskip = True + + # check exit conditions + if hunkactual["linessrc"] > hunkinfo.linessrc or hunkactual["linestgt"] > hunkinfo.linestgt: + warning("extra hunk no.%d lines at %d for target %s" % (nexthunkno, lineno+1, self.target[nextfileno-1])) + # add hunk status node + self.hunks[nextfileno-1].append(hunkinfo.copy()) + self.hunks[nextfileno-1][nexthunkno-1]["invalid"] = True + # switch to hunkskip state + hunkbody = False + hunkskip = True + elif hunkinfo.linessrc == hunkactual["linessrc"] and hunkinfo.linestgt == hunkactual["linestgt"]: + self.hunks[nextfileno-1].append(hunkinfo.copy()) + # switch to hunkskip state + hunkbody = False + hunkskip = True + + # detect mixed window/unix line ends + ends = self.hunkends[nextfileno-1] + if ((ends["cr"]!=0) + (ends["crlf"]!=0) + (ends["lf"]!=0)) > 1: + warning("inconsistent line ends in patch hunks for %s" % self.source[nextfileno-1]) + if debugmode: + debuglines = dict(ends) + debuglines.update(file=self.target[nextfileno-1], hunk=nexthunkno) + debug("crlf: %(crlf)d lf: %(lf)d cr: %(cr)d\t - file: %(file)s hunk: %(hunk)d" % debuglines) + + if hunkskip: + match = re.match("^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))?", line) + if match: + # switch to hunkhead state + hunkskip = False + hunkhead = True + elif line.startswith("--- "): + # switch to filenames state + hunkskip = False + filenames = True + if debugmode and len(self.source) > 0: + debug("- %2d hunks for %s" % (len(self.hunks[nextfileno-1]), self.source[nextfileno-1])) + + if filenames: + if line.startswith("--- "): + if nextfileno in self.source: + warning("skipping invalid patch for %s" % self.source[nextfileno]) + del self.source[nextfileno] + # double source filename line is encountered + # attempt to restart from this second line + re_filename = "^--- ([^\t]+)" + match = re.match(re_filename, line) + # todo: support spaces in filenames + if match: + self.source.append(match.group(1).strip()) + else: + warning("skipping invalid filename at line %d" % lineno) + # switch back to headscan state + filenames = False + headscan = True + elif not line.startswith("+++ "): + if nextfileno in self.source: + warning("skipping invalid patch with no target for %s" % self.source[nextfileno]) + del self.source[nextfileno] + else: + # this should be unreachable + warning("skipping invalid target patch") + filenames = False + headscan = True + else: + if nextfileno in self.target: + warning("skipping invalid patch - double target at line %d" % lineno) + del self.source[nextfileno] + del self.target[nextfileno] + nextfileno -= 1 + # double target filename line is encountered + # switch back to headscan state + filenames = False + headscan = True + else: + re_filename = "^\+\+\+ ([^\t]+)" + match = re.match(re_filename, line) + if not match: + warning("skipping invalid patch - no target filename at line %d" % lineno) + # switch back to headscan state + filenames = False + headscan = True + else: + self.target.append(match.group(1).strip()) + nextfileno += 1 + # switch to hunkhead state + filenames = False + hunkhead = True + nexthunkno = 0 + self.hunks.append([]) + self.hunkends.append(lineends.copy()) + continue + + if hunkhead: + match = re.match("^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))?", line) + if not match: + if nextfileno-1 not in self.hunks: + warning("skipping invalid patch with no hunks for file %s" % self.target[nextfileno-1]) + # switch to headscan state + hunkhead = False + headscan = True + continue + else: + # switch to headscan state + hunkhead = False + headscan = True + else: + hunkinfo.startsrc = int(match.group(1)) + hunkinfo.linessrc = 1 + if match.group(3): hunkinfo.linessrc = int(match.group(3)) + hunkinfo.starttgt = int(match.group(4)) + hunkinfo.linestgt = 1 + if match.group(6): hunkinfo.linestgt = int(match.group(6)) + hunkinfo.invalid = False + hunkinfo.text = [] + + hunkactual["linessrc"] = hunkactual["linestgt"] = 0 + + # switch to hunkbody state + hunkhead = False + hunkbody = True + nexthunkno += 1 + continue + + if not hunkskip: + warning("patch file incomplete - %s" % filename) + # sys.exit(?) + else: + # duplicated message when an eof is reached + if debugmode and len(self.source) > 0: + debug("- %2d hunks for %s" % (len(self.hunks[nextfileno-1]), self.source[nextfileno-1])) + + info("total files: %d total hunks: %d" % (len(self.source), sum([len(hset) for hset in self.hunks]))) + + + def apply(self): + """ apply parsed patch """ + + total = len(self.source) + for fileno, filename in enumerate(self.source): + + f2patch = filename + if not exists(f2patch): + f2patch = self.target[fileno] + if not exists(f2patch): + warning("source/target file does not exist\n--- %s\n+++ %s" % (filename, f2patch)) + continue + if not isfile(f2patch): + warning("not a file - %s" % f2patch) + continue + filename = f2patch + + info("processing %d/%d:\t %s" % (fileno+1, total, filename)) + + # validate before patching + f2fp = open(filename) + hunkno = 0 + hunk = self.hunks[fileno][hunkno] + hunkfind = [] + hunkreplace = [] + validhunks = 0 + canpatch = False + for lineno, line in enumerate(f2fp): + if lineno+1 < hunk.startsrc: + continue + elif lineno+1 == hunk.startsrc: + hunkfind = [x[1:].rstrip("\r\n") for x in hunk.text if x[0] in " -"] + hunkreplace = [x[1:].rstrip("\r\n") for x in hunk.text if x[0] in " +"] + #pprint(hunkreplace) + hunklineno = 0 + + # todo \ No newline at end of file + + # check hunks in source file + if lineno+1 < hunk.startsrc+len(hunkfind)-1: + if line.rstrip("\r\n") == hunkfind[hunklineno]: + hunklineno+=1 + else: + debug("hunk no.%d doesn't match source file %s" % (hunkno+1, filename)) + # file may be already patched, but we will check other hunks anyway + hunkno += 1 + if hunkno < len(self.hunks[fileno]): + hunk = self.hunks[fileno][hunkno] + continue + else: + break + + # check if processed line is the last line + if lineno+1 == hunk.startsrc+len(hunkfind)-1: + debug("file %s hunk no.%d -- is ready to be patched" % (filename, hunkno+1)) + hunkno+=1 + validhunks+=1 + if hunkno < len(self.hunks[fileno]): + hunk = self.hunks[fileno][hunkno] + else: + if validhunks == len(self.hunks[fileno]): + # patch file + canpatch = True + break + else: + if hunkno < len(self.hunks[fileno]): + warning("premature end of source file %s at hunk %d" % (filename, hunkno+1)) + + f2fp.close() + + if validhunks < len(self.hunks[fileno]): + if self._match_file_hunks(filename, self.hunks[fileno]): + warning("already patched %s" % filename) + else: + warning("source file is different - %s" % filename) + if canpatch: + backupname = filename+".orig" + if exists(backupname): + warning("can't backup original file to %s - aborting" % backupname) + else: + import shutil + shutil.move(filename, backupname) + if self.write_hunks(backupname, filename, self.hunks[fileno]): + info("successfully patched %s" % filename) + unlink(backupname) + else: + warning("error patching file %s" % filename) + shutil.copy(filename, filename+".invalid") + warning("invalid version is saved to %s" % filename+".invalid") + # todo: proper rejects + shutil.move(backupname, filename) + + # todo: check for premature eof + + + def can_patch(self, filename): + """ Check if specified filename can be patched. Returns None if file can + not be found among source filenames. False if patch can not be applied + clearly. True otherwise. + + :returns: True, False or None + """ + idx = self._get_file_idx(filename, source=True) + if idx == None: + return None + return self._match_file_hunks(filename, self.hunks[idx]) + + + def _match_file_hunks(self, filepath, hunks): + matched = True + fp = open(abspath(filepath)) + + class NoMatch(Exception): + pass + + lineno = 1 + line = fp.readline() + hno = None + try: + for hno, h in enumerate(hunks): + # skip to first line of the hunk + while lineno < h.starttgt: + if not len(line): # eof + debug("check failed - premature eof before hunk: %d" % (hno+1)) + raise NoMatch + line = fp.readline() + lineno += 1 + for hline in h.text: + if hline.startswith("-"): + continue + if not len(line): + debug("check failed - premature eof on hunk: %d" % (hno+1)) + # todo: \ No newline at the end of file + raise NoMatch + if line.rstrip("\r\n") != hline[1:].rstrip("\r\n"): + debug("file is not patched - failed hunk: %d" % (hno+1)) + raise NoMatch + line = fp.readline() + lineno += 1 + + except NoMatch: + matched = False + # todo: display failed hunk, i.e. expected/found + + fp.close() + return matched + + + def patch_stream(self, instream, hunks): + """ Generator that yields stream patched with hunks iterable + + Converts lineends in hunk lines to the best suitable format + autodetected from input + """ + + # todo: At the moment substituted lineends may not be the same + # at the start and at the end of patching. Also issue a + # warning/throw about mixed lineends (is it really needed?) + + hunks = iter(hunks) + + srclineno = 1 + + lineends = {'\n':0, '\r\n':0, '\r':0} + def get_line(): + """ + local utility function - return line from source stream + collecting line end statistics on the way + """ + line = instream.readline() + # 'U' mode works only with text files + if line.endswith("\r\n"): + lineends["\r\n"] += 1 + elif line.endswith("\n"): + lineends["\n"] += 1 + elif line.endswith("\r"): + lineends["\r"] += 1 + return line + + for hno, h in enumerate(hunks): + debug("hunk %d" % (hno+1)) + # skip to line just before hunk starts + while srclineno < h.startsrc: + yield get_line() + srclineno += 1 + + for hline in h.text: + # todo: check \ No newline at the end of file + if hline.startswith("-") or hline.startswith("\\"): + get_line() + srclineno += 1 + continue + else: + if not hline.startswith("+"): + get_line() + srclineno += 1 + line2write = hline[1:] + # detect if line ends are consistent in source file + if sum([bool(lineends[x]) for x in lineends]) == 1: + newline = [x for x in lineends if lineends[x] != 0][0] + yield line2write.rstrip("\r\n")+newline + else: # newlines are mixed + yield line2write + + for line in instream: + yield line + + + def write_hunks(self, srcname, tgtname, hunks): + src = open(srcname, "r") + tgt = open(tgtname, "w") + + debug("processing target file %s" % tgtname) + + tgt.writelines(self.patch_stream(src, hunks)) + + tgt.close() + src.close() + return True + + + def _get_file_idx(self, filename, source=None): + """ Detect index of given filename within patch. + + :param filename: + :param source: search filename among sources (True), + targets (False), or both (None) + :returns: int or None + """ + filename = abspath(filename) + if source == True or source == None: + for i,fnm in enumerate(self.source): + if filename == abspath(fnm): + return i + if source == False or source == None: + for i,fnm in enumerate(self.target): + if filename == abspath(fnm): + return i + + + + +if __name__ == "__main__": + from optparse import OptionParser + from os.path import exists + import sys + + opt = OptionParser(usage="%prog [options] unipatch-file", version="python-patch %s" % __version__) + opt.add_option("-d", "--debug", action="store_true", dest="debugmode", help="Print debugging messages") + opt.add_option("-q", "--quiet", action="store_true", dest="quiet", help="Only print messages on warning/error") + (options, args) = opt.parse_args() + + if not args: + opt.print_version() + opt.print_help() + sys.exit() + debugmode = options.debugmode + patchfile = args[0] + if not exists(patchfile) or not isfile(patchfile): + sys.exit("patch file does not exist - %s" % patchfile) + + + if debugmode: + loglevel = logging.DEBUG + logformat = "%(levelname)8s %(message)s" + elif options.quiet: + loglevel = logging.WARN + logformat = "%(message)s" + else: + loglevel = logging.INFO + logformat = "%(message)s" + logger.setLevel(loglevel) + loghandler.setFormatter(logging.Formatter(logformat)) + + + + patch = fromfile(patchfile) + #pprint(patch) + patch.apply() + + # todo: document and test line ends handling logic - patch.py detects proper line-endings + # for inserted hunks and issues a warning if patched file has incosistent line ends diff --git a/selftest.py b/selftest.py new file mode 100755 index 0000000..b07d71f --- /dev/null +++ b/selftest.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +"""Test the copy of nose in this directory, by running that nose against itself. + +You can test nose using nose in other ways, but if you don't use this script, +you might have one installation of nose testing another installation, which is +not supported. +""" + +# More detail: + +# In the absence of some sort of deep renaming magic, nose can't reasonably +# test a different installation of itself, given the existence of the global +# module registry sys.modules . + +# If installed system-wide with setuptools, setuptools (via the site-packages +# easy-install.pth) takes you at your word and ensures that the installed nose +# comes first on sys.path . So the only way to test a copy of nose other than +# the installed one is to install that version (e.g. by running python setup.py +# develop). + +# This script provides a way of running nose on nose's own tests without +# installing the version to be tested, nor uninstalling the currently-installed +# version. + +import glob +import os +import sys + + +if __name__ == "__main__": + this_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__))) + lib_dirs = [this_dir] + test_dir = this_dir + if sys.version_info >= (3,): + # Under Python 3.x, we need to 'build' the source (using 2to3, etc) + # first. 'python3 setup.py build_tests' will put everything under + # build/tests (including nose itself, since some tests are inside the + # nose source) + # The 'py3where' argument in setup.cfg will take care of making sure we + # pull our tests only from the build/tests directory. We just need to + # make sure the right things are on sys.path. + lib_dirs = glob.glob(os.path.join(this_dir, 'build', 'lib*')) + test_dir = os.path.join(this_dir, 'build', 'tests') + if not os.path.isdir(test_dir): + raise AssertionError("Error: %s does not exist. Use the setup.py 'build_tests' command to create it." % (test_dir,)) + try: + import pkg_resources + env = pkg_resources.Environment(search_path=lib_dirs) + distributions = env["nose"] + assert len(distributions) == 1, ( + "Incorrect usage of selftest.py; please see DEVELOPERS.txt") + dist = distributions[0] + dist.activate() + except ImportError: + pass + # Always make sure our chosen test dir is first on the path + sys.path.insert(0, test_dir) + import nose + nose.run_exit() diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..790cd44 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,11 @@ +[nosetests] +with-doctest=1 +doctest-extension=.rst +doctest-fixtures=_fixtures +py3where=build/tests + +[bdist_rpm] +doc_files = man/man1/nosetests.1 README.txt +;; Uncomment if your platform automatically gzips man pages +;; See README.BDIST_RPM +;; install_script = install-rpm.sh diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d8615c3 --- /dev/null +++ b/setup.py @@ -0,0 +1,121 @@ +import sys +import os + +VERSION = '1.1.2' +py_vers_tag = '-%s.%s' % sys.version_info[:2] + +test_dirs = ['functional_tests', 'unit_tests', os.path.join('doc','doc_tests'), 'nose'] + +if sys.version_info >= (3,): + try: + import setuptools + except ImportError: + from distribute_setup import use_setuptools + use_setuptools() + + extra = {'use_2to3': True, + 'test_dirs': test_dirs, + 'test_build_dir': 'build/tests', + 'pyversion_patching': True, + } +else: + extra = {} + +try: + from setup3lib import setup + from setuptools import find_packages + addl_args = dict( + zip_safe = False, + packages = find_packages(), + entry_points = { + 'console_scripts': [ + 'nosetests = nose:run_exit', + 'nosetests%s = nose:run_exit' % py_vers_tag, + ], + 'distutils.commands': [ + ' nosetests = nose.commands:nosetests', + ], + }, + test_suite = 'nose.collector', + ) + addl_args.update(extra) + + # This is required by multiprocess plugin; on Windows, if + # the launch script is not import-safe, spawned processes + # will re-run it, resulting in an infinite loop. + if sys.platform == 'win32': + import re + from setuptools.command.easy_install import easy_install + + def wrap_write_script(self, script_name, contents, *arg, **kwarg): + bad_text = re.compile( + "\n" + "sys.exit\(\n" + " load_entry_point\(([^\)]+)\)\(\)\n" + "\)\n") + good_text = ( + "\n" + "if __name__ == '__main__':\n" + " sys.exit(\n" + r" load_entry_point(\1)()\n" + " )\n" + ) + contents = bad_text.sub(good_text, contents) + return self._write_script(script_name, contents, *arg, **kwarg) + easy_install._write_script = easy_install.write_script + easy_install.write_script = wrap_write_script + +except ImportError: + from distutils.core import setup + addl_args = dict( + packages = ['nose', 'nose.ext', 'nose.plugins', 'nose.sphinx'], + scripts = ['bin/nosetests'], + ) + +setup( + name = 'nose', + version = VERSION, + author = 'Jason Pellerin', + author_email = 'jpellerin+nose@gmail.com', + description = ('nose extends unittest to make testing easier'), + long_description = \ + """nose extends the test loading and running features of unittest, making + it easier to write, find and run tests. + + By default, nose will run tests in files or directories under the current + working directory whose names include "test" or "Test" at a word boundary + (like "test_this" or "functional_test" or "TestClass" but not + "libtest"). Test output is similar to that of unittest, but also includes + captured stdout output from failing tests, for easy print-style debugging. + + These features, and many more, are customizable through the use of + plugins. Plugins included with nose provide support for doctest, code + coverage and profiling, flexible attribute-based test selection, + output capture and more. More information about writing plugins may be + found on in the nose API documentation, here: + http://somethingaboutorange.com/mrl/projects/nose/ + + If you have recently reported a bug marked as fixed, or have a craving for + the very latest, you may want the unstable development version instead: + http://bitbucket.org/jpellerin/nose/get/tip.gz#egg=nose-dev + """, + license = 'GNU LGPL', + keywords = 'test unittest doctest automatic discovery', + url = 'http://readthedocs.org/docs/nose/', + data_files = [('man/man1', ['nosetests.1'])], + package_data = {'': ['*.txt', + 'examples/*.py', + 'examples/*/*.py']}, + classifiers = [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Topic :: Software Development :: Testing' + ], + **addl_args + ) + diff --git a/setup3lib.py b/setup3lib.py new file mode 100644 index 0000000..27bdb93 --- /dev/null +++ b/setup3lib.py @@ -0,0 +1,140 @@ +import sys +from setuptools import setup as _setup + +py3_args = ['use_2to3', 'convert_2to3_doctests', 'use_2to3_fixers', 'test_dirs', 'test_build_dir', 'doctest_exts', 'pyversion_patching'] + +if sys.version_info < (3,): + # Remove any Python-3.x-only arguments (so they don't generate complaints + # from 2.x setuptools) and then just pass through to the regular setup + # routine. + def setup(*args, **kwargs): + for a in py3_args: + if a in kwargs: + del kwargs[a] + return _setup(*args, **kwargs) +else: + import os + import re + import logging + from setuptools import Distribution as _Distribution + from distutils.core import Command + from setuptools.command.build_py import Mixin2to3 + from distutils import dir_util, file_util, log + import setuptools.command.test + from pkg_resources import normalize_path + try: + import patch + patch.logger.setLevel(logging.WARN) + except ImportError: + patch = None + + patchfile_re = re.compile(r'(.*)\.py([0-9.]+)\.patch$') + + def pyversion_patch(filename): + '''Find the best pyversion-fixup patch for a given filename and apply + it. + ''' + dir, file = os.path.split(filename) + best_ver = (0,) + patchfile = None + for dirfile in os.listdir(dir): + m = patchfile_re.match(dirfile) + if not m: + continue + base, ver = m.groups() + if base != file: + continue + ver = tuple([int(v) for v in ver.split('.')]) + if sys.version_info >= ver and ver > best_ver: + best_ver = ver + patchfile = dirfile + if not patchfile: + return False + log.info("Applying %s to %s..." % (patchfile, filename)) + cwd = os.getcwd() + os.chdir(dir) + try: + p = patch.fromfile(patchfile) + p.apply() + finally: + os.chdir(cwd) + return True + + class Distribution (_Distribution): + def __init__(self, attrs=None): + self.test_dirs = [] + self.test_build_dir = None + self.doctest_exts = ['.py', '.rst'] + self.pyversion_patching = False + _Distribution.__init__(self, attrs) + + class BuildTestsCommand (Command, Mixin2to3): + # Create mirror copy of tests, convert all .py files using 2to3 + user_options = [] + + def initialize_options(self): + self.test_base = None + + def finalize_options(self): + test_base = self.distribution.test_build_dir + if not test_base: + bcmd = self.get_finalized_command('build') + test_base = bcmd.build_base + self.test_base = test_base + + def run(self): + use_2to3 = getattr(self.distribution, 'use_2to3', False) + test_dirs = getattr(self.distribution, 'test_dirs', []) + test_base = self.test_base + bpy_cmd = self.get_finalized_command("build_py") + lib_base = normalize_path(bpy_cmd.build_lib) + modified = [] + py_modified = [] + doc_modified = [] + dir_util.mkpath(test_base) + for testdir in test_dirs: + for srcdir, dirnames, filenames in os.walk(testdir): + destdir = os.path.join(test_base, srcdir) + dir_util.mkpath(destdir) + for fn in filenames: + if fn.startswith("."): + # Skip .svn folders and such + continue + dstfile, copied = file_util.copy_file( + os.path.join(srcdir, fn), + os.path.join(destdir, fn), + update=True) + if copied: + modified.append(dstfile) + if fn.endswith('.py'): + py_modified.append(dstfile) + for ext in self.distribution.doctest_exts: + if fn.endswith(ext): + doc_modified.append(dstfile) + break + if use_2to3: + self.run_2to3(py_modified) + self.run_2to3(doc_modified, True) + if self.distribution.pyversion_patching: + if patch is not None: + for file in modified: + pyversion_patch(file) + else: + log.warn("Warning: pyversion_patching specified in setup config but patch module not found. Patching will not be performed.") + + dir_util.mkpath(lib_base) + self.reinitialize_command('egg_info', egg_base=lib_base) + self.run_command('egg_info') + + class TestCommand (setuptools.command.test.test): + # Override 'test' command to make sure 'build_tests' gets run first. + def run(self): + self.run_command('build_tests') + setuptools.command.test.test.run(self) + + def setup(*args, **kwargs): + kwargs.setdefault('distclass', Distribution) + cmdclass = kwargs.setdefault('cmdclass', {}) + cmdclass.setdefault('build_tests', BuildTestsCommand) + cmdclass.setdefault('test', TestCommand) + return _setup(*args, **kwargs) diff --git a/unit_tests/helpers$py.class b/unit_tests/helpers$py.class new file mode 100644 index 0000000000000000000000000000000000000000..b2e443134af38fa6b9c3681bbfb7e7b97cc5a802 GIT binary patch literal 3230 zcmbtW>r)$56#rck*d*PyG<^bn&=zY#fRt+07O15ZG?k>3Ahoo0UBV44-R#EQ4Q zjxvl#Monuro-aCcrV-DW7LSh=M=UkR`|36(<}9wxq#f1aebKtzvEupZd7g3l7K_1TKor-#!q3!be#${}R97G748CvVr zjTDTGqnU<;tqj4eGOL@@s!lpvH&VF`;UIaqonf=)a7)RUxxDIh^fClyDHt=O>J0lR znvIm~!fp}f9y+8bt{NqPR)#(GUY3EBu$N(zqL2yIaV$k)=!uNZs|#veSB=?t+Oafa zcB4$l*pCAt>_a=jo>g?!b~FZ-kZ}lyMMNEhEu~}lMuz7FEDXuj7AWE7b#+?z;6jxo zULn`i^~pGbheMQ&2*Z{^3PPJMIDFW$Oe=s+$^gYyPJxUr;i@0ql;3(pYAPkf85(n{ zlbI8cuMc>1N+zBk3*jgpVQ5kuZaa1W#~HS-Kv)YT4fW0I}%2z*+j}trb0;K zJV9-9Jxk;pT|t;Yynt~5Vw#~rq2Aq$swm_{Y?&R`u!?d|#ahDZ2?<{3_S)uZc5y^rGW+EHTW z4HQGTj+ZDlt6-F4J6^AE?J>`=WW0i#0=!p=7KGE9BMb*U09zwMPogUUK!GngclS4*Tq7xtwE6B1JQ5Yw~svNFjlOhA>k8-_O*WG@&t$#9~?_e zQfDU&2++m_RWEQK-Hd1+w|UAZCp5YeH3F>nrv38`k0G_{b|m3zVz&T`Ve2T}by9`g zG`HM#NM%nqsLZvbpkyd*7OV^xIbvubw;i%Fso5H(a?mhL8vNv;je`_)lA`}qTp&c7 zBv2tEk|68sXr0h35RMLX`x1vIW~Npv9qCaQZ(v>1v{V08tXUBzRa zcQMv^2NU7vmht=@yx{ZwiRollEIi}$`Ia!@W6Za$8OxZvgL%K--Sma^G)av3T?xM{ z!CZ;czmjf&BDJuJrDj?Mvi>E^3$%eCOyn!O?c{~n-Ej{Cm%=aJgFnxm`$gCS+TEw{N$?2RTf&EnC49Wd z%DESyCdrGv0?q*f(M5F?!0qd8bgZ}0fbU6Z11{nRT6fUfXJr2K1irwR;^iE^`VZjd BVvGO) literal 0 HcmV?d00001 diff --git a/unit_tests/helpers.py b/unit_tests/helpers.py new file mode 100644 index 0000000..0a5d68d --- /dev/null +++ b/unit_tests/helpers.py @@ -0,0 +1,6 @@ +def iter_compat(suite): + try: + suite.__iter__ + return suite + except AttributeError: + return suite._tests diff --git a/unit_tests/helpers.pyc b/unit_tests/helpers.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d952a01823323fe7f149dc108e9d6b0c3fafb052 GIT binary patch literal 346 zcmYjM!A`?447IxfQ_;$uFX*Y+5S#%au05;=5SOTQmIzwgC~=V{IPwvECtrY_Aw(oU zKgW5`PXE2k-@h*F4t@_xzLAJafi1u%z>^S!rp)A~)4s-$#GOQ33GC0{1Hd7mq~>8b zhdF2Pn!{KC%F*Twnjr&x6wjtqhq{~#vKqz7G4Vo}wZek6Tu5=t9NhE7bRWVP_*nZ) zkz>+tq=fPh_)4hp!M(LihI?;@F_I0mqgL{|T2~il8XO-i%nR*2g;@DLpNcY#?Z)1> htjon*jd6x2xWEGI!$jTnAJZpkflc~}>aLSm8$W`}JxKrn literal 0 HcmV?d00001 diff --git a/unit_tests/mock$py.class b/unit_tests/mock$py.class new file mode 100644 index 0000000000000000000000000000000000000000..8111d9ffb14140a55ff0bfb2f8c6d7a96213392a GIT binary patch literal 14001 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 literal 0 HcmV?d00001 diff --git a/unit_tests/mock.py b/unit_tests/mock.py new file mode 100644 index 0000000..98e7d43 --- /dev/null +++ b/unit_tests/mock.py @@ -0,0 +1,107 @@ +import imp +import sys +from nose.config import Config +from nose import proxy +from nose.plugins.manager import NoPlugins +from nose.util import odict + + +def mod(name): + m = imp.new_module(name) + sys.modules[name] = m + return m + +class ResultProxyFactory: + def __call__(self, result, test): + return ResultProxy(result, test) + + +class ResultProxy(proxy.ResultProxy): + called = [] + def __init__(self, result, test): + self.result = result + self.test = test + def afterTest(self, test): + self.assertMyTest(test) + self.called.append(('afterTest', test)) + def beforeTest(self, test): + self.assertMyTest(test) + self.called.append(('beforeTest', test)) + def startTest(self, test): + print "proxy startTest" + self.assertMyTest(test) + self.called.append(('startTest', test)) + def stopTest(self, test): + print "proxy stopTest" + self.assertMyTest(test) + self.called.append(('stopTest', test)) + def addDeprecated(self, test, err): + print "proxy addDeprecated" + self.assertMyTest(test) + self.called.append(('addDeprecated', test, err)) + def addError(self, test, err): + print "proxy addError" + self.assertMyTest(test) + self.called.append(('addError', test, err)) + def addFailure(self, test, err): + print "proxy addFailure" + self.assertMyTest(test) + self.called.append(('addFailure', test, err)) + def addSkip(self, test, err): + print "proxy addSkip" + self.assertMyTest(test) + self.called.append(('addSkip', test, err)) + def addSuccess(self, test): + self.assertMyTest(test) + self.called.append(('addSuccess', test)) + + +class RecordingPluginManager(object): + + def __init__(self): + self.reset() + + def __getattr__(self, call): + return RecordingPluginProxy(self, call) + + def null_call(self, call, *arg, **kw): + return getattr(self._nullPluginManager, call)(*arg, **kw) + + def reset(self): + self._nullPluginManager = NoPlugins() + self.called = odict() + + def calls(self): + return self.called.keys() + + +class RecordingPluginProxy(object): + + def __init__(self, manager, call): + self.man = manager + self.call = call + + def __call__(self, *arg, **kw): + self.man.called.setdefault(self.call, []).append((arg, kw)) + return self.man.null_call(self.call, *arg, **kw) + + +class Bucket(object): + def __init__(self, **kw): + self.__dict__['d'] = {} + self.__dict__['d'].update(kw) + + def __getattr__(self, attr): + if not self.__dict__.has_key('d'): + return None + return self.__dict__['d'].get(attr) + + def __setattr__(self, attr, val): + self.d[attr] = val + + +class MockOptParser(object): + def __init__(self): + self.opts = [] + def add_option(self, *args, **kw): + self.opts.append((args, kw)) diff --git a/unit_tests/mock.pyc b/unit_tests/mock.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b21df0a0cbc4e4e7e9d2633e2e57824597224d72 GIT binary patch literal 5524 zcmcIoS##V(5FYJiua6uU4CXZ9S}vQUxCszq2ozK{u1Q|v2ULor-6*@>-I8V#T!g3a zoM-+S|ASw^_jPL}c@rlEan?2ROlzj6`|D$RwExfQ((iwL+zn0stK}kg{d!C6rGpLzi-R#wk7uC$5VTMh!*TkT11}!sOFnbFaG|XVp4BKX}jX~4=2`)<} zYMCB*EpT|xnCpAXCR#Lc(|c7*EpiZwESTbLMz=IPE8+tei29lF)@@ zP(veTJ80P^9?qD=Gk2k(XZ}FPGp=EhCMoAJxGGG#9q#M6Fxj8DCHx=7cl`ZvG#$p6 zk72MMJ8rNqv$BKCEpQ|=vJSVk@*s~6nm*h)^tLByayxMGw%ea6!1Q-aQQcRM+Mhr+U>^j03^jw@?qm>znB8{U+L6*g- zyL`Y`BCU7^jT=YO4JMO#6lq|TDq*FXQVmEyeE}^Od-YhvCRjJ(-Z+g9%WWlk#G%(P z#XgJ2W8@>GFQGj`z7Oyk0RH&X5!k}ko~dF>gLE?nfaJB!%mt~Vq=I)bD)Igwa?{;j zMay$X6S?C-hxnh8GvH>Dk0&BH<1lkNcp|x<2ct7{auY8$dbT+7Had?*95L3bmkzC< z)9fmG5Jlg|lQa&ImLtambBH3xfrj2goJO*9oTeJIg>%~tu#xN}5A|?*_uFVyOB|y{ zr7zp9fExMmm=s|w zZ#_Ll=tqSjR|m;NY6NJ({xPZWGWK9mQFW+e$n{K`6m%Mfah5$%5WfOepHf0Si&ni3 zaj|q&%d}@D`_kx^`QI|hT4pTE@VAT@OOLa(mZiRyEX&BMgcpw}vdg%><+Z$aUH%&F z;?D`|I2@-@GU{vTcsUpa{WuM=NPb1F@Ck->ua>UWO^`{6Jf}Wop`v< z)o?uM>|p?QSw8Cav=Yzl%2D__NOK}cA?W-4*agm|zJCEzs?v}PGszXwnL|{XM2Z za+JzbY(fUcD{RG~(CKOr8-@aoevLckeb~H|oc5Bly10m_7;lo!mvS2sFF)F%uVLOL zv^+>wN^uB}h(nvI>i|UvGDEHoy6#BNmoj`1ALwmJ=<^6rCR#ZA8J$_xP3S8zDdBjA zG}eih;@S7{D0xXw-I1lmS7;ftMyoc9IlGBrh$cVkaS<(JECbf+Wu%BH1P;}Cbj7UR z1Ct;IOBiT>y@P2mi|TrD3P$1+mGhakj?&OkrOL?K1u3@!3TMhqnaE$f`8#w9JD_4_ zzhU}tl$u;5<=&K6c=^1=d=g9knz>(IGwFKxza#UD3DW|Qe8LN%v3 zfIBJ94@n7@DBvr~45mtP%ym0_!y>aVI%LAtXtJmYe3ajvjzim6SEBjC}~4j7PWd za%Klc+Podk&lb7?@Mwde{bi_0g zqk~q$GP;@_(*Eg-hmRT~R+pfaWgz|J739wuRw|h`1o+hFOi(c`e~`4AO;!J+HYni1Rad@ zoDS4=h4Uwuy(kxSH(%i*v-hddG71Grr!xcvcvG2?L`qQ1WzF&+?11I8f*r6@Q0Vks z^-ue0l^|{3MAowXn-kQqs8tQLx{B&)ji7ob-2Tb*h?UHw{j^R{)mVHyl^IT?fLUMO zMvd0fhALRN5gWCPoE1OO*lrJVjaituS&%1=pWHyy`E_X&q2?-Tq85a}SUi=;SxG^n zTccKLWAH6F|GrGx&}b{KR*xv9Iv1n%W=0L5ZK7?Awp~!=*~xU$+AZj)TP)j$wTQ>c zt>N;!4aSGIgLVcfOlM+!<&prMRd7$Dv4b7bq0u>N&$~4`TiN@Jf_@@E=ivn8T=PWZ zHlDCeqkU?^eu##qv&KHt%$NbX5TE0Qr4CEb7MBoP6$m-o(E)l6KH$qVQk+I{<(R`b6*G~{8AvX}f~w_==lGHkBAL8eyj2 z%EU*E6tYuAq+2H46ZAY@rs>*{i2&0q-)vK z7in~jGW9xm5xy8nWpa}y4&WtfnU@N>ZjlrJzuw+Oz|_TlIbZBo2wEHIE`LxealeY4 z_-c)&)$U%4qbhNIfL@16#XoR#+b5JU8&Q3WUO!W#H>k;P6jZ}{@uFgY-lWm%mGmuw zmN}3D^j1L0&>lU}xQl~wheo%m>wTL>Z&P#MUWotE69IY$PN0x=+R*`c;yX2Zmzwf! zjozuI+>M<`tqB0PBNcn%u=Kr3?R^@(N2#4e%oNlDlrI=F+}JKN;4Y0GP*Y|#x?fG1 zFJ!>s06ipVxmzwfAZVR4GZkvrP+~Y`_~~KvFcEcPj|S-x`T%ZB&Pa`6e?4pymnK}o z57CGD5`F|fN7|diED6I6A9YQBg=XX9sJUZkA^0wvX=C)i&$-2N_c(nrNS~lj!Air# zHU5mC&5<3=;UPBQ%A|E9yzsEYCCC0bL7O7mOLfzkbep=+XogBvzaVIRq_b4jRqab0 z&f6BK$$VD|^%ZtehwUQNr?2t%PWv0D^$mO@S=jka-Ve9S|8)NQ7JZu&@pm|S5NYSd zvS#u)(r3(?$i}$WLyk%r3GntU?OQrxlc?iyd33SdWR}~w*zn|d`_|5w{BcWmIzUfo z^gR_)KS1oAJ&_rmOc}ca^drPLjN|S=(5;bfw>Hnw3p!iD?#nNCx$_GmugeYFWjQ2T z7bv@Z`3e1$UGp=Igt`{LK=g7N4$v>vPSnS*@qv8Vn@FM{)JMu9V)lg8SrKtDrTIXB zHgnJN{vfWxA2s?7{gy|6cIgq>z@AJ3Wx!8=#r+&X({CArXsc7kd6|rrv&=-+wJP*a z%gs4$vX37h0|QOrJ;osaHR0y%>X|wn8G*XKN%xv zqchPAOs3PEV~RHWMWvu-;O|eS4DKBEX43G($wEfH7kxMQj>-kuiAP?J7(+m4khuNtsoNZSLbe@JjB}YM)9Pa_i@sN-l z=>62#mZ#1Zd-8PGX>N!}lp7U_%f4HjDEH;*UWGDKumv2p+@ers zinbKxF(=A{d74uwr)(6@0~E_s$4P{f5FAvyX(^9Ee2e{{YETVG2i1b=L1EBF&?Zm> z)BX+xX?3wS5%qq-&`E&#+5hTz!iZOv0Z;%}ZVe-ui}10Jb?Sl&@UToo1<6uMSWQFHN0jWuZb&NZq4ODufv+C5%!v07_m#% zMfDZ7!7pr`r5D37EBU+3@vOjkDCv4O5d%3gTD;m0nvH9}%hTbeM zKRZg^eYPHhl1;5;UDr+3NYfjz>Kf2m*n>i6+tXxkY-vKQb~-h zmz%g9e%RnTT^-_jKx_a6im4M~vxK+{5KW6fYz9OVAW(&!5K#$nFCcIzWltjth*m(L zd^;hwN{Bf?v@Zg&6%g%!KqIOkJgX7$hzyV3GDjb5nW4u*ADgF7&rmeh=ve6c^7DsYFN#txQsZr^9tcz zrqewAe1@JZ3cD($&?kjL3OkF!y}DmYD@vq$iqb=RwUkztNDunB6!x}yL%$lt(o?!F zRs1C?$9)w~tN21svEGe(t<(#Y%w}tqK{phad`e#~_4G=JssNMe!*;ro>u__`&-S zDO?orP_cte#~0rvU!I=iEXQg0cl3K(8b%bcwDuG=T^9Po9Q|pQ{xVPhYMB>ag;gbd z0=b?$7nutWx03?NruzRH7%`e4R^C9(UfAuS^9j?g`gf#KWGSa zDd-^RFlZEX1e5}0LDsBTdU94Q1Dyd{4q6FX1!@4T0j&eA2WdzNI5~OoFH3HV3iY0l@nZ5 fPT&!p@V1AZ5IewY$s=0e`)Idl6>U7YNNo8xvZ94| literal 0 HcmV?d00001 diff --git a/unit_tests/support/bug105/tests.py b/unit_tests/support/bug105/tests.py new file mode 100644 index 0000000..63a368b --- /dev/null +++ b/unit_tests/support/bug105/tests.py @@ -0,0 +1,49 @@ +from nose import tools + +def test_z(): + """(1) test z""" + pass + +def test_a(): + """(2) test a""" + pass + +def test_rz(): + """(3) Test with raises decorator""" + raise TypeError("err") +test_rz = tools.raises(TypeError)(test_rz) + +def decorate(func): + func.attr = 1 + return func + +def dec_replace(func): + def newfunc(): + func() + pass + return newfunc + +def dec_makedecorator(func): + def newfunc(): + pass + newfunc = tools.make_decorator(func)(newfunc) + return newfunc + +def test_dz(): + """(4) Test with non-replacing decorator""" + pass +test_dz = decorate(test_dz) + +def test_rz(): + """(5) Test with replacing decorator""" + pass +test_rz = dec_replace(test_rz) + +def test_mdz(): + """(6) Test with make_decorator decorator""" + pass +test_mdz = dec_makedecorator(test_mdz) + +def test_b(): + """(7) test b""" + pass diff --git a/unit_tests/support/bug105/tests.pyc b/unit_tests/support/bug105/tests.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8956736ff3b714ea94301ca6d19e586f7b0d4f5f GIT binary patch literal 1845 zcmb7E&2G~`5FRIK5+^A?61@OI0SV+*q9&zc z9)Kt037Bs-u|otUB=UH7c6Pq`=Eswto2~cX_xmH7|0?``$xr@+g(5m58as6CkfR*> zOmti!Rgp!NR8NL=tqAjqb$*&f??R(}GfDk8$@MVPV;%Clx7*v@^QUoShH#Sm`7}+F%=jnM z)83NhvpBz+h z;7L69_{4j<-917k&Li`-n}t!X^RCjPBnu5wf}PH?zU0`rygRx`_3JE4vXz|Km=K9D z&Sq_1O6rOGXM|$lDZ<+14HxAD(NLtVPCtT?6w07%Z>I4`7NIej1Q8&qCANia!kG

?W_k7B`EFw^^B^-sHHIt$$9=LpJpGm-{(R;>R|BQG8mq^%X-^G_aa=+1=_?q1a`{ z7Oj)t2lBx(@_(TQ$R46!6Ydekt@aPY!TTwt={j)6%$f hP{2t~i?Hp75MDs!cUf#!+#3I@TX@&`U+q*D&o7XK|2hBw literal 0 HcmV?d00001 diff --git a/unit_tests/support/config_defaults/a.cfg b/unit_tests/support/config_defaults/a.cfg new file mode 100644 index 0000000..4bc5e22 --- /dev/null +++ b/unit_tests/support/config_defaults/a.cfg @@ -0,0 +1,2 @@ +[nosetests] +verbosity = 3 diff --git a/unit_tests/support/config_defaults/b.cfg b/unit_tests/support/config_defaults/b.cfg new file mode 100644 index 0000000..e329464 --- /dev/null +++ b/unit_tests/support/config_defaults/b.cfg @@ -0,0 +1,2 @@ +[nosetests] +verbosity = 5 diff --git a/unit_tests/support/config_defaults/invalid.cfg b/unit_tests/support/config_defaults/invalid.cfg new file mode 100644 index 0000000..34b6a0c --- /dev/null +++ b/unit_tests/support/config_defaults/invalid.cfg @@ -0,0 +1 @@ +spam diff --git a/unit_tests/support/config_defaults/invalid_value.cfg b/unit_tests/support/config_defaults/invalid_value.cfg new file mode 100644 index 0000000..bc05d74 --- /dev/null +++ b/unit_tests/support/config_defaults/invalid_value.cfg @@ -0,0 +1,2 @@ +[nosetests] +verbosity = spam diff --git a/unit_tests/support/doctest/err_doctests$py.class b/unit_tests/support/doctest/err_doctests$py.class new file mode 100644 index 0000000000000000000000000000000000000000..c087b7104cf0780791c52c45ed094c45ffeec1b0 GIT binary patch literal 3427 zcmbtW`*Raj6#j10X4CB=A(T=cN{~v^rfp0W6bnVD6oYN3Z7ovJb(`F@Taw+dyD0?3 z7vdA&PkezoimxBAXz0l3AK>^mIi9--ownI_m@zY(dw0)0=brPO@0@%8{`Zf+0L1YN zgD=x^FT-$Tz_cb~`Jyvr8nKjV@z_wY-_mltr%Et7WpOP%;%E-IP@1jUn|87M>h=#n<%1;S`LV8dLdMC zU%{QYtDZdE!cdnn%@zVw%BWe*c60`oP;fW433&^Jlr#;lV7pLgLMubFhpT;NniK9@ z(I#+jXYlXV4c+NwnD*jG#f=q$UTzPM?+a4Wfle8l(Ip6KngI|;10pwOO3NzfE+_0h z`lzY~u~d+32Sq-~9r4Q0$>%-V5V=wkIG*BR>afVx_s*>4A+qi;9a6m}=DX>cA1n?-s+NB=pxx9o!#2K2H zmUCF}_n-#=9*~!7d_^AzFeu|OB*_a=yg4%+z>w=Op{+TNrK$>s1$=uk;%0<$loe6z zs)pq@n}>oYFeVHhVOZx8N!u91mZdOI$EsA&s5X)162@I1sfs&rT*fgxMKxgaY=&XY zz>;=ElvnYLpuFaWkkfQSkSgU#fO1!nKsYNI%giMVhg$|u-#+i=8{8zOWMn|anv@64 zbRo;br*vm3O#Ydc9oCI-CH3KqY2^rR>Wf{n9Hw7yZ*Ms6&RU%*oz~iNSTo9+t#SY- z1ed9B4#o18K11y~=H&7*!?d|-@hsP9jCaMmx?=?@aMf+hF}si#6pB?gjV*+!D_;!2 zP>?IvMxG(Kw9!hi2)^CK5elR?0Eb9p@Kd6xFkFr#j(N1^r8au1*CfWry|}wD+dW>k zJ+@0EyvSbZdNVjBQ)@h}fR+2-nQ~7VBsNeRjx4K6#1f2QXo`63*)|qHonYZnnF#ix zf@g6~%+7lp1HzL5Q%iG8!UZCKiVjD!Nfr`zSuos}j*) z#cMKN!Rr*8RWQo2?Wiu*#cG~mDR>hX1!8Xz7pNO7QeoKY0obA?bV=A&UB$x%!w>~L z@3VyWC?90MU(a&U-}_91a44i4(;DWPEL}I~*qmR^(7mu_^wD8xd)!nOd!pb2d?@36 ze8jNIZD5%Sy%Gn0gol!|*wd+&(8rP`9K3|? zXt05Vp+s~Jhe;)IOGR;25~LD(yoAw?5}rJA6~1WA@3?0ck!$2n4St~iCJ@5pq3{}z z>1m)x!H@JGq6p5TR)SxGBH??nlImqXvR0aj6t0d217g1g#+hr}Ps>3NG0|1%U%)X% z7g?cny!{5AZoiI+P@c;k- literal 0 HcmV?d00001 diff --git a/unit_tests/support/doctest/err_doctests.py b/unit_tests/support/doctest/err_doctests.py new file mode 100644 index 0000000..6d60696 --- /dev/null +++ b/unit_tests/support/doctest/err_doctests.py @@ -0,0 +1,12 @@ +""" +Module with errors in doctest formatting. + + >>> 1 + 'this is\n an error' +""" +def foo(): + pass + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/unit_tests/support/doctest/err_doctests.pyc b/unit_tests/support/doctest/err_doctests.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b86402ff73d7d03ad6316f403933e25200c9345 GIT binary patch literal 449 zcmZ8dJx{|h5IrZQ&`6zGy){#;ASAYc#D+wMcI#qAjY|!U9oZKUVuT;T@8lP77b1|b z5SH6 lb>bX--rV^%XTGt0%_M&hSyKeaJlAuA$P4FUkuJqTd;^!#ToC{O literal 0 HcmV?d00001 diff --git a/unit_tests/support/doctest/no_doctests$py.class b/unit_tests/support/doctest/no_doctests$py.class new file mode 100644 index 0000000000000000000000000000000000000000..6f3c244694adba972e17e7e17fd04d6c0744e3f1 GIT binary patch literal 3368 zcmbtW`*Raj6#j10W|Oc$2~Y}DDN-?Q+QwEvu^>XF7;HmrYmru6x5-UO+3beRrce+S z6(9J1i!V?|@$~~14ILT%1NfsH&)I}b+iW|`n3?3>&AIp7bH4MPZ|}{2|M~|&7k(4) zrCaV57>e{;_GBzK;~cZhSkkg(Y;dN}*0Zvw$}n=wmU?Q~(H+^-T4f!aIXp2XlTMF- zqG>5Bsc8ZD1Xh_AKRGh*p-iG+-lTUG1)~U?!c6Wh&TA8Kt#SLgdp6p>p^t z?!cWvig1fSUD~o*2v8-h8G7Ey2#C0fyRePzEtFEiGNp>`yrBX22sCXC1RPUrEO^%xs9}rsdy9z*xE;ll{+VZ#{||cjwqAODL6!&p^0TX zhnc?zJpk~4ywu{$#@LU65FST@qTuGuTB!gA-GB*g-EnM9Q!&Kw?ZvQL5vq~FS?sEY zls; zJ2Oqf8gsI_m}%vuW=lirGdn`c@o-oo3A*gM1p>gxqt1h#qr zwqOap?YC9;<50mgxfLrhEBJuwLH_$PhGeDgwM@dHkaR42NTv>lf==Z0t0h+L&; zYVZ^NHh}O2mw_Wx=*#&8@iYB~DgJY)Rp3{kD)^CCQnys{+D6i>pO^bye_w;etItQ8c+=ixS%KRe&c4Tm=t9%Tk6K@Yyvm@GFh$Xm*JX Ox*W&1_>L!s@cn-p)s9;L literal 0 HcmV?d00001 diff --git a/unit_tests/support/doctest/no_doctests.py b/unit_tests/support/doctest/no_doctests.py new file mode 100644 index 0000000..7e3750e --- /dev/null +++ b/unit_tests/support/doctest/no_doctests.py @@ -0,0 +1,9 @@ +""" +Module without doctests. +""" +def foo(): + pass + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/unit_tests/support/doctest/no_doctests.pyc b/unit_tests/support/doctest/no_doctests.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84fa6cf088034f35d5763f9121eb789fdeb78a9a GIT binary patch literal 396 zcmZ8cJx{|h5Ix5Q+DM&|m^x;PRfNO{5)%U%(yhxWs*P}I;wZj=5F`8uekZ?xJ1c4> zEcxAs-@SKkf0y~^&rP+1>0})5`NmrY3$SHW@Xd(=mx0Y-pFy|;Wi`BN*lKVZ{0zPT zRHssVQ_Mu*iXndrc09P%yQh~h(#nP%xk$0hkA{kA55kurHry1_sR{=82lU82(+~cw zA!n%VO&6lW!5v(~ab2#qv;{yE$X;ymXB1tdCmVjsouTrKocEz}?Q CBuuyf literal 0 HcmV?d00001 diff --git a/unit_tests/support/foo/__init__$py.class b/unit_tests/support/foo/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..acc8100a319421d8f9967f99592a63e226fa92ad GIT binary patch literal 2825 zcmbtWZBrXn6n-wD*<@WTP2VV>O$(MlfHl>Mm5Nd(!wK1NywuNsb(7w}?|EIiGidZNP11k#@)2FgP8 zSL+JSGPD`A5d{*Gs-Yh*CDDg-M1R?^P0zO&SYAU0gA#m*0yHNau3=beJAn~~t_Y*1 zM2U}UI0IdxQI5qgS&rpTFsw% zuHZeH6%?S$deyZysH}CrQq>*dal_>{H%aD3bED_=8pY3$9(cM}t5${U>*TO*7&5Si zFhk%ho)#u^0gEbS{0ADSf5`aWX!NLA({iY)GA*A{id2F-Co&N_2bU5! zD^vA#l`ucna0|+y5-mEe&h1BxCE83)IW^Jz+Q#LD;@4G(`HED`27K zIIgWpg<%ri4R0)QY}v7kJsGaYU_j?>fWzCyRfIdYl(AO!ix%p^8sc z2)GO#G(c=ac2|kZP{`z?((iwbPnlwEG6^zNl zvb2(H9?fdWf5Qhhy>Just|r!C~$Z3<6wqfpIb!Z278dyo#sD!su0TK{T_3glGpfUKUh35~5d`bVg zq2ZC9Ujt=GH$?EVi`Ko{xVyQH`X*~I{5|c>k`=vZSJ1~4DZWLF*5NYU>Y%^XK^uM` a&^BDdk0g)L>Pu38FORQqUoNiV>;C{Vi0u>r literal 0 HcmV?d00001 diff --git a/unit_tests/support/foo/__init__.py b/unit_tests/support/foo/__init__.py new file mode 100644 index 0000000..66e0a5e --- /dev/null +++ b/unit_tests/support/foo/__init__.py @@ -0,0 +1,7 @@ +boodle = True + +def somefunc(): + """This is a doctest in somefunc. + >>> 'a' + 'a' + """ diff --git a/unit_tests/support/foo/__init__.pyc b/unit_tests/support/foo/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a1ea8913ef3ac6cd99c9073c309c6f37f217cdfc GIT binary patch literal 339 zcmZ8d!EVAZ40Q`a6A~wWAUAqy1VZ9g5WDx#aoQy+tr3xE6E!Xp+I1hX@8k=xtFdXb zlILgp#fj70XVZ_bbzZ@@MeKLH!<+#D-Xo|1KAFT&NAQ-!od2VK74?|mryMpM9nLl= zUZtvCMHoogRt4AKsc)-vs@SqDQ;Tvj@TbocJ`4YV_MFqcv)7VPQ{#2(0-6plSh8QH z%XFpt){>Enf)2g+t|NVNP8(yTU{Zgh7jE->!brFb1LHiAfMkY4*CS1s9G$B#Sjcw_ VBM+<)-S689j(|k66&Sir;s+zUKePY< literal 0 HcmV?d00001 diff --git a/unit_tests/support/foo/bar/__init__$py.class b/unit_tests/support/foo/bar/__init__$py.class new file mode 100644 index 0000000000000000000000000000000000000000..1af5241841cf902253c8c1bdbc31b5aec733feb5 GIT binary patch literal 2124 zcmbtVZBrXn6n<_tu*tf#G^I3!QlQ0}1ez__R%@_I(dgEMMuO2&$8?k2(5;)@o!t$Z z5B?VY?57G-n$gi8;E!@VH_1RlmKkSchTOaNo|or5_q_c5&+mT$SOsU0noGACwo?Vi z+tb`m@Z7Pry5n)J)X95Bi|0lzcAk6OXp{pZ;JNh3ZK?CP_JY@g9K+=^Ov;cLWS<9? zX%m8IN>5i*jA2|s6f#{ktCryhCIiz|#G%CL{4IvWs681IWMIn?c4p4TqT)Oz<2Z*4 z4CCvjZ3Yh*E~N@DjD17140}&22cBu~<~`B69dwp|ZQ$KEk&7MF2}XiEvq7jap^ zG?IkD=T?(JEi@fRs~O&s+aU&&lP?)pRm@;EjyYUoh*XL7FvJ5O40+A<%zYwR3tFyb zJ3g;^+~NkUtJ&4;n%1T;ssZ-{U-R3p>v(}SaHmzPBF1Vp>vm$e&X61psqaU|5`k`d zPD{4~?%BM-kPYJ{gxdj96*uvYg13=Uv4BMpUxqw}C~P_nPNYnpW?jZDhS++`X|yf= zAO?+6z~(;)afY8$`qR;O`Y$K??rsQ(p{LA+*$#21>tTuBi$|q0T2m;s-c`Y{BEr8* z`1g2FG)$Y`ICZ8fWlgImGo1?YnXbe{%3Q>hLN?b`yodM2jxu-V^m7U%1;=P`PsYcT z*>$SWfS1jdYw?GU6ZnB=xD5Bhs=OD)C`NI7teR8t5St1%@Cn7{we5avcSr3FtA&Q8 zB9G4mW1mwl5KluY42vOz4T(?+-8fA^sk7a-ZBd=am}L|g(qunxTHK~iw&~c!L%SY0 z-ZpPq)T5}PkHs0*-pu<=>Og)-dmBcgVhfKI6j5TBs2i45Z4NpGLph~~rQiSRsUpQ9 zA_;avlHh~`8PvuWhP}63qQTQPN_vHRK1EJ6j_n&(n@cnbW*BBe<%>He&44k0t^VZy z@*|0zrv8m_AmcmAX+aUg`2scft#+%%y^Q7JoZug=BGFkGRh3&=~w z^i6ue4cm5Twh%%eixlcK#r!oSBt`cms7cXfdMlM1t>bi0tkC~6`npLw5zx8i=n-Pe zkylU;ka$Ta3vR3wj@ah zn3ounE=;0}JBPUU8)*tlRF(rfle7x!(F5EQ9znC~K3VU3Ss)+ccIpqzJxSzVwI3D4PfpnrLOL!A=U3}TMMq?V~!F_{9Vw9|1%dFg*{Z;r9%$)p99pS5& ztRHucj_(jJNknDLFG4q2;cq-5hXSP literal 0 HcmV?d00001 diff --git a/unit_tests/support/foo/bar/__init__.py b/unit_tests/support/foo/bar/__init__.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/unit_tests/support/foo/bar/__init__.py @@ -0,0 +1 @@ +pass diff --git a/unit_tests/support/foo/bar/__init__.pyc b/unit_tests/support/foo/bar/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb29a09d4f0f3a02d32c432c044270f68093ae45 GIT binary patch literal 150 zcmd1(#LHDZ!^AI{0SXv_v;z&q%*CFS8`RB(=DtSiiWmpdi1fL_aM*Uq30aNIyP46C@e0 QS5R33vcw)l6az5>0Fg%`4gdfE literal 0 HcmV?d00001 diff --git a/unit_tests/support/foo/bar/buz$py.class b/unit_tests/support/foo/bar/buz$py.class new file mode 100644 index 0000000000000000000000000000000000000000..dfe76050741c1424c17639e0b73fc1032ae63a9b GIT binary patch literal 2907 zcmbtWTXz#x6#h;p?Ihy>A>7)65~R|kLp!us6@t}LTMf3MHZ@2=<0Lt0r%Yzz%%l($ zZ%e%X2z~Ymi>}ME`b%B&2F~}bG zEz{-gOHVoY|>{UJQyQaM=<2i=DlH*h?9>qR}LwiM- zwYrQ03(9APjp1f$elJqyM(3*#TUsv2rf-Ds+IFfqh=hVT4lzW9QBK*g81xXC z-9dx{j^LhiEk8R5+o%g!2OJWzyo3X`B8O!+KaE zwjDfQCK!g7MJllrGBb~`-Xxk zyh+~hxK*XRns0fTY`%;cVe?xIBg<>1m!z+etT<(#d%lvS&(zdZaw4fE&jbL4lv`CQm8|V^*X9+5u`nw_ys;D6D|lBy7V{ztsG&^jb=TaW7NPsKx^6oj zFS^{~2Cd^`<6{$ggW^>bN_)E3sEbL_0C9HqRhVpRdh8rkMH?3Qhlop`&Cg_@;e z9k+zXsEfwQPrHsVoC6sMDY1?+_7D5_&G@6E~V=~;MdqlrzlB8WQOnPsgOiL0}?$cN^SX*L>> 2 + 3 + 5 + """ + pass diff --git a/unit_tests/support/foo/bar/buz.pyc b/unit_tests/support/foo/bar/buz.pyc new file mode 100644 index 0000000000000000000000000000000000000000..faf6f3a6ed7181c310461377d075292e9c95d77a GIT binary patch literal 344 zcmZ8c!EVAZ40Q_I0f`^jDaUG)8o}V&G40+%*@+8Eo1j&cCTd)yjVm9)ck%_ZqiIN3 z$#ZPKJoz22)5+;n8xzpKb;`2gnc(xQ36hSM#?@#8P9#O1=W;Gz~o6sL?7s zc-6UJx4mtxraa4L*<1}>gIm@PDh%Gc9@Wk{Rq0+;!%ybF@qtwIZs=;Z5qLQm7ts@P chuC>> 1 + 2 + 3 + + >>> ['a', 'b'] + ['c'] + ['a', 'b', 'c'] diff --git a/unit_tests/support/foo/test_foo.py b/unit_tests/support/foo/test_foo.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/unit_tests/support/foo/test_foo.py @@ -0,0 +1 @@ +pass diff --git a/unit_tests/support/foo/tests/dir_test_file.py b/unit_tests/support/foo/tests/dir_test_file.py new file mode 100644 index 0000000..79b86ec --- /dev/null +++ b/unit_tests/support/foo/tests/dir_test_file.py @@ -0,0 +1,3 @@ +# test file in test dir in a package +def test_foo(): + pass diff --git a/unit_tests/support/issue006/tests$py.class b/unit_tests/support/issue006/tests$py.class new file mode 100644 index 0000000000000000000000000000000000000000..ca42201ad606eaffb91c167c3c0b95db9e2ef49c GIT binary patch literal 6086 zcmbtX33OD|8UAi&CT}u4a2QdE;6hC`VF*JaxCClJ5Q&BunTSQSuS4<@24{2T4HCs# zYHe$4tzB%jTBWrut=%waNJT_jt<~=PqFw2}@5`wbzyH3;WG3?_M^ERRJNLeK|NGy2 z|L^;k_w@4*Jp!OZ`4oIZElU(OwDx54!?E0mvo)KE4P^6ntZ!s(K9RPU*G%+p&D)8= zxRY?~yeIplb*=7$o%N35RqtXEPH43(qN@i>Ye`|N!MiT)vhTw;xFt>J{tJ6S} zLSt6z4`K$D4BJkIhg4YH+OsXOJrPSKGQ+XBlTT)br#qa9nV2Qp9i+2=^Lec*1i#q>(Cv-`M6+0 zqxL3ug@GQ0nL}0~vE3fj7bT01_Rwsi7Z(W!`V?lItp`I+iEvAu>o;0 zNvR5azh{axIqSYnNWeUf5@-~Re73OZ&u5 zM?uyQMbolmO~&5gSeA+H?!?t5UhNuggdwKBp@X)f@k zCDE$pf_Pmy6lL@2M5-bbovARx;xp5c!z-A$L4XP24PtACC$B(P$`2;qh&Kr?Zx-35 zCf?%W@>UbCcTe)RGLxok8^qhWFL!0|lRQu`@eVP8AMZ4Aw7dA-BuqMSrM;Trk-l=R zN1~X;(Ic#T|<~V?~B| zORtX=in&}i@5GXYLeXBbJ~&5 z?rH6=jH9I!RRqbpZ@MqL%!3bAur2q}?Xg|AXU-~RUc;9!jxNXL(o_SLmz^4?1*t)X~)^ zG4VtED1>M6V+L-W&V(1DS0V0MLH&PU7Q(^?A;DpZwR-dmN&b5DE2$s#=r@v#^yqhz z&-LgJl4SJgPZH$y=r0~rnzW>WzbTwt?Pcj4jhTg(QU=L@_i!3b8qWpH(dE zr&I$a`StxteoqAe)p1>6_YRLrry}7S_^*MB=p15eCd(dvEAH4~7dec3tE#0$R*F7`8?pZqNp4nXY%%$vSa$my6_B@|9FjLYH z=!*Ks5cD5Js5crh#}IC}d{#UXX&=RHY8-cT`J<6YGiBzfE`K}UTo`S55S(4Im#Y6roGYHm5-QeLUKcBS&svCqE@)wr4nQ(0I&(c!4xQ zI3VC)4R7az&p@XEzk%oYYIF5tR^FFp4g3$vzQa|AJ+2)JOaxyT)*%_8n57eFDpu z?nB{-RxbZq`6zBGX}#G)D|ztJJ(KXRbN4H#R6Jvf!49Ptm9Clo@~^U^jdDP%uk=4)bUt+Q6Ta;QM5B`Xknv5(=~nu zUoHHEh&&b!YLQ@tzC@tvOj>SNc&3&MS8jZLSr-oP@VyH^H36VCJ#M qx_T2m^(Ol2P4w5BXwY|U;AgLt1ala#{y{VUvm5`yzh!U%{__I(Iwj2j literal 0 HcmV?d00001 diff --git a/unit_tests/support/issue006/tests.py b/unit_tests/support/issue006/tests.py new file mode 100644 index 0000000..5c8ee60 --- /dev/null +++ b/unit_tests/support/issue006/tests.py @@ -0,0 +1,19 @@ +class Test1(object): + def test_nested_generator(self): + def func(): + pass + yield func, + + def test_nested_generator_mult(self): + def f2(a): + pass + for b in range(1, 4): + yield f2, b + + def try_something(self, a): + pass + + def test_normal_generator(self): + yield self.try_something, 1 + yield 'try_something', 2 + diff --git a/unit_tests/support/issue006/tests.pyc b/unit_tests/support/issue006/tests.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30a7b9ca111d6460896243f302902c8e2f8ea5e5 GIT binary patch literal 1120 zcmaJfjN5S?T{$`-A(2RM`)w;a%>TOqC>+VdW~sEC!2l_t&-lrLfjQBh9iNANrO z1i6>W`>&VVl!kA@`x{>N8JmpA5aj`70sSPpC;G7GpOAJD zaBjxtow087H$7m_TVD1B;zWQ;?mk2w7uioJo5*2G*;EcQQo9JXwkh&MUf5C8K4If4 z{0QhS0GVs1wTplnX#;-={1kY^;n_xP=*YM$_Qh>id`+B*UDd{QV~y@iX%gNy>&^Ni z?(4#7$5*ql?c28LTwGY&oAdKmvAEamtwXQ8uTyAY!G{8At8f;A0f2gA&WZhQpv6*2{L zTo~i_pccC7OLxLii(Ck@qIr;l2o@S#@_O*$0Z`Hx({d42m&hcV`90q;i!ERP9}^U-uwQjYVn zV(>5@%ODcTA&%B{QW>os%4%ITSzq$6!t1C73>ax+pM` zojPTnHuJXWEar>8XE}@Oxt77j_=PfdiGVnq!bZ|?xPMe2Z5S1Ip>9jVNMVye$}p4x z_3Lu}QZBnC(-z3b80<`iI^Je%Q5Lrf=E%h?9~Leo#+gh!7s717dy}= zu(cg-vhI|8%XKvTN}#uFEZXk8X%lnndXW9NvX>t25xCeeNUiDno?-OwBLshi7#R$N z%nb@$6s)p*SyB89T^IhVr5yppc;DMBasfF#?bxF7x zzf+Omu`dnJa4U6CF_+{aGQ#Z-#$>?%*Zs~X%U~KaY0CdLfek_8*L};*Pk5f$sA;&J zMWSrkOw(-kL`z|acxfL|Ocil7jU%{2;M(>8waRT>*7oC0C7rwIfs#lH$Jv{49ht%1 zD!q2#9)ZhZs2i9Q*x5OgkDBwg)NmgQRW^+4d>ST7M6XG^%*r)&L6TIRL|LhBQJ~9U zagWRTs7MMN&R0F_G;4X@uT=AnTa$(-ZE3O{9T^?jm#9wWvJRW^vtSR8hYxk7CHi>gHf ze^9D^Far_h+(RkQaj}%bpTp~J+O!-7G&Xeu5DKCaths!R^rNE<%%yOhstQJ$3_h8` zBY0Hxo{qUj6x@_+E=W(qvlMNKx4tinR;6mo8(r70`JP#g&6e3l;7WUosLFJ5jm#p<9gMZ+kX}pHl zc@YO!U^%+Z1&Y~1oDlz~cbXDgmlDC-s?-O)ch$ue^xlsV#%$vPFB(1+=v(VirAlhN z{KRZwPI@)6L2A2Cn|56$_~hjCXIt$0>7y3kn%#gHeW>=%16=mgaJ1B54WCixiZ+2u zruc}QsaNKu7bGI{pHI`$(zFt1y6D!ul2p+W*h05`TA8zI7K1wBI4<8f^st6qJibXD zKH@8Mvnro-b+aloLCa(eT3&=f%S)YYitEW7zyI);%e#`^qnJxJkxG7p^h{3AG_hgW zNEk(3A6~|kSi$UpWKP$&aLqPxAUVvDxm=1HILNg_O&sI46T@nHCfD^P4ld)~vxF5; z|I;+5`k*PHf)yE`LJ!RYSLgK0a{3iSB0#R2xXaiz^EGxK%YA{X=)#0JkDF*=GP2Ob zEkC071jq`L!H49UL9TA%x1q1YKgCKAg|#NaI>T4D z(ttO39t(r#a8kEd;GKh?NPL6S(?dD^fkYy)jFSl=5*s&Q1%Et;Gpmz&GMG%ZOip!o z2Rjq`pNd0C{ox`pCcA@W-N7>QON@#C(yHQ()h_a716##oav5io*FY~G!{gCxAN^Fb zo4&^QG5v}2c&dqKR`C4b3SJ7adhs$|iB{{uTA)WCU%?w8n!uZQE1Lg^3@Q#s28V{X zeWGlE42ORzp4<8L@#}BmowH56$M1u)BIHt8D|2@rk{ZT^rkc-@U_a{icbdp{ni%Rd d(S>DN>%tPwvE9Vp$2`Hm3ivlZQJq`x=?@5Q;I04w literal 0 HcmV?d00001 diff --git a/unit_tests/support/issue065/tests.py b/unit_tests/support/issue065/tests.py new file mode 100644 index 0000000..d246458 --- /dev/null +++ b/unit_tests/support/issue065/tests.py @@ -0,0 +1,5 @@ +class D(dict): + def __getattr__(self, k): + return dict.__getitem__(self, k) + +test = D() diff --git a/unit_tests/support/issue065/tests.pyc b/unit_tests/support/issue065/tests.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb645c899a9771e5299a1ec2bd2652b736663747 GIT binary patch literal 481 zcmYLFOHRWu5FLk7K@k#cxB#21Vv2w*2)fR~UD1sdB~uiUpW+D;qB}SQ=i~&;OenGR zp6B~Kh9KRIO*P}0WzZAd)Xogff;|}h)wUCtVW)0p-$ZvL&dI}vpdbCmOJE_V}NX{ fN^_H{l#__2bW53F-0yfFpR`m)|KR>#=VJB?zyeNE literal 0 HcmV?d00001 diff --git a/unit_tests/support/issue270/__init__.py b/unit_tests/support/issue270/__init__.py new file mode 100644 index 0000000..264b0f9 --- /dev/null +++ b/unit_tests/support/issue270/__init__.py @@ -0,0 +1,2 @@ +def setup(): + pass diff --git a/unit_tests/support/issue270/__init__.pyc b/unit_tests/support/issue270/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21834830fec74545f4d54213df1ae405f4ba44ed GIT binary patch literal 321 zcmcckiI=N-hKXM?0~9aWd3=Ay{3{gM^BSWwT6Ho+2oij)s3q)P821uu$ z29RJtBZ}RDV)_L|nPrJ3srn_k1^RjU#i{W{sX3{MK-y5xP|rxeG%vFxz9hA{q*%YW zw4flrs6;=rxVSXc$lO3bK0Xs98?RSTS;7i5t2ni!v;bsXF~~^_jDBFdgFtLrTFn78 R-zGOdr8Fni4s1KfLIBskNDKe~ literal 0 HcmV?d00001 diff --git a/unit_tests/support/issue270/foo_test.py b/unit_tests/support/issue270/foo_test.py new file mode 100644 index 0000000..5a629d3 --- /dev/null +++ b/unit_tests/support/issue270/foo_test.py @@ -0,0 +1,7 @@ +class Foo_Test: + + def test_foo(self): + pass + + def test_bar(self): + pass diff --git a/unit_tests/support/issue270/foo_test.pyc b/unit_tests/support/issue270/foo_test.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b21f1faae33cc94dd0bfce62169e9a9814ebf03 GIT binary patch literal 723 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 literal 0 HcmV?d00001 diff --git a/unit_tests/test_attribute_plugin.py b/unit_tests/test_attribute_plugin.py new file mode 100644 index 0000000..0df0e99 --- /dev/null +++ b/unit_tests/test_attribute_plugin.py @@ -0,0 +1,53 @@ +# There are more attribute plugin unit tests in unit_tests/test_plugins.py +from nose.tools import eq_ +from nose.plugins.attrib import attr + +def test_flags(): + # @attr('one','two') + def test(): + pass + test = attr('one','two')(test) + + eq_(test.one, 1) + eq_(test.two, 1) + +def test_values(): + # @attr(mood="hohum", colors=['red','blue']) + def test(): + pass + test = attr(mood="hohum", colors=['red','blue'])(test) + + eq_(test.mood, "hohum") + eq_(test.colors, ['red','blue']) + +def test_mixed(): + # @attr('slow', 'net', role='integration') + def test(): + pass + test = attr('slow', 'net', role='integration')(test) + + eq_(test.slow, 1) + eq_(test.net, 1) + eq_(test.role, 'integration') + +def test_class_attrs(): + # @attr('slow', 'net', role='integration') + class MyTest: + def setUp(): + pass + def test_one(self): + pass + def test_two(self): + pass + + class SubClass(MyTest): + pass + + MyTest = attr('slow', 'net', role='integration')(MyTest) + eq_(MyTest.slow, 1) + eq_(MyTest.net, 1) + eq_(MyTest.role, 'integration') + eq_(SubClass.slow, 1) + + assert not hasattr(MyTest.setUp, 'slow') + assert not hasattr(MyTest.test_two, 'slow') diff --git a/unit_tests/test_attribute_plugin.pyc b/unit_tests/test_attribute_plugin.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72ac36177c0841fe854b6eb9ec143af8030fc438 GIT binary patch literal 2199 zcmb_d$!-%t5Un03P8!4 zAv+Ch68R>wvm|*#7t4|_>7pt5vMyF6Z_0Li1!~%c3L7BqeHu4Q*kmEpYL{Y4H}OQf zhAO^@1S7Bn8{*6R2t{eKp?x6B6tzGFdydQNQbsl#|gCeBC`QW>xDMKlCN5Q4TaV2FrYIx= zp0L7GMp+GrDH4Mc;xLZ9~CNoZ?RZ}-#9U6AlE!qrYH7*aara63&YzCVGcuIR>uk#>Q3RRtI`F=T?~TT zud`58gII~gSMxB3;W~MQ#a;wi4CJ&9`^5*B-^5u@7VB^WI8G{&eoc}5fenz{1J!Xd z7I}xJN4o>WJ+!`#6y+L0m&XFlF}lUB3GG8eQ1KUVm)47kKU2xfewB|(Z0%LZ>~)eGATzbPieuyo z1jO94n46}B`cswLqutFi^Zw5a(O}FF(hz|mewH@-WSEUzr=Ed5R#WS;Y1;|y%N=ia-T4f?XKt2#!l&P^50oYVLN4Qs}Fj8?x){w{c) zHzc{bdZV#{Zq$FWVYb>DKI@p@xlX96((CjL1Y3FWo8~+5D;FcsQE?t8>}8TWAZ_Kb c#Nzaw3heaoX=jP{Q^upSV%@AKE&kSi0dasI&Hw-a literal 0 HcmV?d00001 diff --git a/unit_tests/test_bug105$py.class b/unit_tests/test_bug105$py.class new file mode 100644 index 0000000000000000000000000000000000000000..b315d048e9ee21a713e48fe1c8e489408c518bc9 GIT binary patch literal 6259 zcmbtY33yyp6+S03llL;bw$qoUO-olAFv(;xNoWgg%F>n=gK3DJF1Dc0N%E3Tnareb z-js$#Tu|H>P!zXTQ4mBdDNG{;+)xx$R74avRNVJ{NB?u*n>6VxA8Nku=Dxd~dzSzH z_uP5-nR_1~qE6nz6c}#mVA|H)n{`IwW8?1LY$iUKb?kWm_!cKQYIm0mY~SnH$)SXs zbnWhzlF|P0iwE}EgKjs|f(jxv3NUFo+fAo3HdC;A*u~ikTj;6Y)+>-ruviD zr$)y#I)$kYA6dt}SXN)y+`BJ%AQ?|5Gb8bY>!dOxr79O~siLkyvuJjh44T8Fi*DJ$ zWSS}d?8N&viyd>x6g%o6CZ9>UuAOsrYG7(S*_xuKG+F?6jM#1l?q+H)vtw1g7t$ix z@KkKL9h+@R=4_poFr8vq5Snyd$FdAsCbJr8Ia63>ZOo5!cC6OvG$!LjyQveymNl9k z%BOA1(&=;vwJcFgTF+rJORdnT1wuBDj=6s5b4;gRQW+QqMX9ZhS}De~s8rmRd}h#1 zWiuMZ5$R!TB%K{drXh1tHIbdvRR<@W2|tU+t#mdyWTi6JkUeZ=ogv$4>Viiy*__=j zL$;&S8m7p}(9!5@-$A|ptaWI6aNFW!N0}}y6P`={3kdeKRCUdII!8cwF4NNHp2`M$ zKs#UJz5!lBoIN)gw8=xm1vpn@Nq4VKIOAG!Am=rni|&2dR7R(Z&^?rLB(ys9sxS|y zyrB1~7T}+@ET%*$HB4uKDmqpkGN_-PFL2z(G+V`MK;f}zY+R%5U;qn1EEjM8fE%=v zcGb}idV$RCGH8$2yoBkx|5e?}+EyN^dM^9vg<|51;7!EG=aVHMyUOwmdNCy>r~^!E zs~b{PNlFdK(X4O_j;&2B+WGjz0YgDUz%qb z^Z~E=L1~_C(5+tcL(*Jp(CuFH!%Xu_Z>-)_&eiB6LfKXPDre{h`e>N$q$9(q+t-Zke8FOhn{_UICWEgkDdzCcj^0rWMukl<6!&)$lc@a=vZ75BFnMU zHj+lGv%RyuD=tZ^rId_MGoyWMT&JfE`l093A4A%@%e?7BjK&RjOx4-X>QSry`XUez9OPEX+0yL_+h*gA)BPo#Cm%SjWD!%Cg;dcnL2J^I~2Gqnv!kGir6| z1vGP6nK*z`c?F*)?p}$L3DOh&!*&9X^O@CkJL)LZb0`U~q9D)0|%XGSQbG z9k3n6_8<@Z-Z6ODR)J|akK2^ z-2SRYVu-p9kwI*r%?7ld!W|B;(4lCsK)M-@PEw>VYKCJ4n#X<7kaW)XJLmhI8ajjb zQzPchFVJFhX@OS6tbmm;SH>pjL7t?~tqVrYl?x$q9=DD^`>BrSo!qh2>$n&*3Y3_j-4k?af#AsmLIq7X zB4iOm#A1^{abjPzw%9*Q`eEvzxmSrnYzMK@+$SwHRVz-NEmL8};3Q?DkvYYdIp$B@ z6^+bA=Se=h-t(DWl(In9neTwIZBZSymjp%~4 z2|BDWHbFPXte^$sZ;4qUsc(x}wNl>^votGlobKEmJ4SaY&|$M((;?)Aua&|GpN3M0 zVxY`InT;|BWiHABltz?=2r-z-N6#d%BY?Xq8Znh=Ic1utt0E9A8eWgl2|CcHq98!O z!UuX+G%`n1OgNEWPmv^ zH$s!lM;Sf-;7Uym22TvqgTaWVMg#R6Otb}Yg4#m$T$^aCt!F*arqy#e(Wck4k!Xvc zYjy&gJP@f>f3oJiU4kNiwdRgVZu7)3 z$GF`ef5?Z{n%1`1skf`CctU%Ba<45%X+mj6X)AEYkpg$2tU_6Hggs^w2D}Cw)TDyX z9tBch3J6#%s3(@$CG4-%PSU-Oi6+kB3LJ{5AOfl8=4QA|d)rG_DmzV?~J~t;l#Z zVcnyjL<^!%;|A#_B@H_DsMsI{mlkeRu1Il~QPAXcL)T$>psm}R9`8PU@~^03^By02 zorU9yQizj}JqkyG$JXHyoK}`+pWpU33+{(IOcl+2QqCjOAk(C!(z;N5ax1iuj=9MN zKOE4YnU5alO`0{ybZ91avnIwC&06&K9ShSHBuNu!=N`FDnt8Zgcr;fuQ*a|PTTqi0 zB|ar9l&n(HrepD{836WS02KCuh7U!Y=!$z|+q5i;$~m9)!h!{7#d)>xp;ep-`$Wd`7+ zunuU;*qz8s;zKJsjHiXk#h92Xk2CGic@#}ds-s9a`*xx$EgYuq8egK$0r0=6itLrC zBzLaQoTLH5tVkr%YWJ}Wkx;nF;!?~)g+y7MP6R+~E$_?gFVi>;ur*;1;x+`rLvR{y zL2N?+W5A~tAtKyO2n+=MMJA6DJ&B5n_YkXRaZRf0Jy4roTWzbB>UeE`li#vKOW0t5 zVIzMOF^nl#2=DUeWjWELW{8|1Q5MkmmPxXht49IKhM10+u{LN=J`Pyf=arHK;XU{u f4FaW`+P-s<;IeiN65T?QBf38LDt=MmvvS4f%{}1?yjr*Ki|DGnPl#ScKO-g z@67kz?>qN==R5Cn(-(e!;u#`p;a8a|`|Fl5b=SA2G6T)&A^Sio*&Iz}tmdwvEt$xm zwYFrV_dv#q#Cq(AZLMu6+3gzI)_2f~+H09+l_BY;N+y5SvJ>&7#pJ7RYuKSv6;%hw zM}DxB9m;A{!xWlGAU>G((-fu}Oi~$po4kHyefz=4kw|kQk{oF6u`}`HKq=4p%S^(r zPE+Ze0O@otlP1iiqLBns^O=!%FD)0DMnSnq2xM}}xNTcmTca6xN~P^|B$Kr?n#E*H zWZF-2nDlLFJDy5*30g*@c}xN4s#`LtL8faxnAv+4*KcdsTV7#0Euiy60OvDJNv5*a z(r7B#A0N?G`qX{pDhCe>4z8b!cO5NR)j+Zqv(HiK!?1ks3XXG~M47QtFV%bBJ*Sc=Uq#kEE& zVO=H5buEy$X%41hxrAk!8eM`TOjFpDr%RcFrFZfZOkLkRn6_QZ&oa%MRLqyr`WjkC zmov>RCATG)jEV{PX#?!M-yBG!`XUL?oI9E3T4_@aM6%iOtL^DJT)PIsaNgEwt0S^D zC^BouQaM=U)wmllTr*z)C!Fkn4(b${Z^Hp*p29Us)3qVfv8lTBke{xD0eLXgnu=L#b-I$w8rnzu zai4W@IN5rpgI*!bb<0WAQSJ3X_B=ng@>F8>q0rk>L422XmNfe;GZxP(CDg5g>LRh26MB((KIqABvG_gsnuKL{OBC~A5#UE~ zo=OB}Br|~XZeyx25qi8l+)ZdYeT?o9i~qRbF4yT3&f_O>r_vbG9?#l-`jqH@63drd z)kmKW(A{)TJ|E@mc%pejCKDOT`sp+9*Zz0{Nw6Mt#`2YWpVAef_*wc~fbOIFVHde% zIubq9V;#;}$tVK->Pd{?Ei%*R=|R!-7htDOQc@Y`A*R7nnrEh{|F;Ss5fy$(;-HyK z+2S4Z>n9DJkJ4Avb+A{hkJlL2~)?$PN3j+;D#9PY;5ah{;jH^+QLrOT`v?CZcw zRo0GVkT{+b-Y(JU+YYPWfhf|by6i-fX+fzv3Vtru@m>0!=~*pY>TugWo1^UzZI+S(ckIxH_mx)z_ul7wjyyN9^d3q6;0yI z9(JNqrN{c2M5=$Fe-u6alafx0PXFRa=U>5NApmRiZ)zLS=s%q8|MbjTV+P>COo5sT z=l>;#{x`}gB-O$WQ%A8Z;NGe0C2)Hs4yR@Da>oKW9P~T-UpeUa$hg)GQ8S+GPid%B zC^Q!13a9wF0;+Qq?BqB0$+hFK_KPR8c8|;E69HGVA0dHpD2#7~&H*Jqo`Rt1mO`;@ zIXjIqji)M{l{%m6;21jnN*N3XQQ4MQpT^VkcIz5t!2ulWY(clDIX2nSqVp_=)@*gk zDxK##r_58&WdhLlWIU?MFkZmtApr6DI?r_Ipi`Jz%+vV8HD09C#SZ3$>V!*lzQ{ST zUY@PPIxlf#6GrkGjKq@~qt2gEYyfUbY}f%`FAaP7`ngw3#Lu`biWkXryga}yyaHsi zRsz-9?Dk^s9xFZ9>b#0CmTMtY1XWE```p8)?>_wye(yW|n8xsBkKV>%%g<|JzWwOd zBurFy!^e>Yt-Wx!&TT!L&CYGD z8@hV8cW>4hZ3n7wF+3E)jO1R?l+`%Gw9s)#rGoxcE*YzZ4wOsP4qDl4WWdrmioz~w zUGFAnRepPkx{$!x;(i1o9zdcmB9C?|5)VQJGuMWSYO}H{ZF}~3#Hexpv(}5Y$8l|*&7;$+@gOJVk|`Y#R=PGmtii*GqZ-@L ztg3`F8Xv)}@P!0z1O$Eq)U1kQz7Y;=C68$Q4!jc6_?_~c0sgy@i<}T=TUdWDRAqtW zEiwZAR*ZI^#vcTJzifP1O|bJ(naI}dI1xFy1Ik9&@+{)i6;Kn6tAAaIHtf*oIteVz z0bJ^nI^V$`m)WO4Y@$yJo%U2DW@Y^RX~eTAKB3uG55Dartc|IZowYNObeZg_cqp|a zSuVVapmi^QCcyXbeYjL6mvk<*sx(23f6YUe2$uXgzF$=K0NMfwXxuCC1P@7#LqP;w zG*JLuL*2QgYI+Mi`}vDZ4WPdzoHUy&ZK&CCAZ)<;jV@d`15$0X6I+1KQ2g_<3euL5c| zEFG(wJt0N8nvF<#t7fB8gR0pHN%m^?l+=l8_KYN3HT$NdZ#6sZVRB;<_w%=yYRBJo zFb$z+nm2T{?XWUg$QeqjIuc3btV(e~pI?Hji_)0RZO8ZE&fH+1l~LInK@VST&2LB|F-BDApoI{A3o(D2&ZpUu$#I?8Qr4&u1&fT*b*T|`ty)I{QqSlL z)QFZtjVNr@h+apHXx`L_Zb6M`*JP9;Mm?h~SI;Qu)H6CXHKKc0>nP&XGa{iHQ4FaO z-?P++uSROb_Y5_nVN)X-A2p&kQ6ow~HKKQrQOYkhqMXAx4OR#{gcN=ZO%0&U5RJ_Z zSB;Ps41`C?=nMw~O(Qg&JHyqoIMZF6=`Q+ZaklSin%mh_8JyqK6s{PhTFjTP@r8r) z=lJAZOf|u~Adgo!0k5urXQ)#a=Mn2CAl8o|3PqqT0f#e3XlZcS2(4%`E6twZs-|IH z%%k+r!osTj8%|W| z#ks}Bxiyb7y%gt^i}P$AXGJN_t1iyZ@;DIcM4o@);`}m?v!MisLk>=eXXKG~lp2$V z&T!=jbp^X|%68a%k=~t?szHZn3VsoxOR1eB)Eo+mm`iwaYUh9&qP$D(R+qQ#urW>X zRuA695Ie&@(6Pal5mXyTu%Rq>VHj@K6?_Md#Phv04W=3nz7yN#D82Vo@hu$d4d68K zexjhV(jYi!gMAjP6gw>W#^K5LqWsYfW?mIGW|ZRzUIRhSFECq`XLgosC{%Ixq&ajr(C?p6~dkEk{ zwc%j{i)CYi4Mi92ye##z@mF>c>`!rJfD>Tgw-n^_lKK$`v4?Y za(~AMnq}vYaCM}^x4e3bVz+ShvkoKsXchv+48Tl)%x44U0_Fi00Hlbj1uO(C0@ML6 z1S|$z1gHlz0K$MqKog)Dunf?mLQ^;SxRR>JLX)3=C`HQ4rE8TwkG0>Gt|7FcfOf;} zgGdCD3T_M=E0s7r)Viu5%Wp{>k)j}MT#UtGdVZ9?A2u#2AiOw+uv!*XE_g{rA7%bO z#&nooQD$F~TxO~wVkNj)1(1UD62NNki!#5I-?egpmm97tuv5hEWmp`hpDTXX7Z8g0 zy<8T(`F(9Ne%FG>b%4tN>j9U8Uo;~QzZJi7Qb)zIu(45ba7AbEbyPFIaSJpAauvXR zZhf{X&zh6+3Rz^ctQTFjIv^aWJWixjlgDz?oe>yYaOh0QFGvEsM(7l&RI2N)uu<4veywv1G(Wak&BvIy#(&$lfQRx~i9!{&VAfhz79CAKO zjY7=$6f(J{b_x3)n%V_5<0GE)a>X0yHK68-*OnaTl}nEC>JYCPzYBhN?#GMKUe$cH}ejegpHF+h(JJjU2A>O4X&xd%Anj8)BwQBNOi1(?< zNQn2#gd0NKr&jI?aZIh~zEM7Kj7|ifRqJ<#_@G*!R#}NAy`wQ4;zVU-}SRbg=rM?^{OZ8FO4-0YB6KK@ELkBW5L&Z8o}4Q6koUw_wP_q23UPR2CRJm z1DO2~hMEw61VbRiw_%tP;*a45Xnu_i&S^z=;7~y=uNxbk?HYAOr8WA^1Sw6%434NX zb~rQwV`Ab9ijLUnY)=_Gg6BLAZ$luIZ`Q8Y7;pn=f_=lBkrtfrKQNp4PIq@9oI-YI zoTPobL;Q*3eAfuyJ<6Y5GRmKKh}H0e`~??XI#2mm9fW0n*&)aLD1XJ-=a<~_?8uVF zrg@JlR7yJox^O}#@1z4s0}}qb0KI@+fNKH!0WrV%RENyYha9Rbl%&W((mH!=w3plHD!0*FZev%ujcdzo x>@T+wE4Oi=+(x3@M!MXFU2da-Ux837xPyO+aXMz-fry`PY}j6umS4PMS6}t=dwcE<#{FlBlH{5E8U$)UKRp+EOr;Mq|&|bz{%Ce$Sy1 z@up#m#10m0+3}xP(I3Ds;GFxWu@!~30zs^~^SOAFuqe687${I7ub z7kJELh*)Gtq~#i(v^vlL=PcxH*<7ALTcwbq& zvUYWSlJ;$A5figc8FpN#!jZ;iZM0`g&|(sMZ?wP$4HxftKi~oUu>LWAsA&N$59nM# zHOJW;a5r0}NTTu0?Avq+?fG5#B~@tj-r_>V&J+OzT6`G+U5MJ#=YW}ujpR?qBh6lY~0{p&T^jKEV9nZa~RW@2FUKl6cXfm)oRYPyZtGQtD z{5YUyPq6!;*@)MH$h4sL7iGU72e2KlKClG{`qIM+PagO(Ey>i^bye9fiLLR=10Sp5 z`huKL-xt;QL-n~LAglO~6g`C9$WVa0@Y%HZBD434&X(tv_q=~$_DiDjs=DVQ5+Bls zJ%fYD(Vm9wB#iq;F`zhGHTJ^a%!h+4?U_Z)uCAt8o+=8Q=GL9fu(`Eyef#dcU=vvY zxDdGkp&SrB_9$IxN{^JeEK@6rW5;GjyE+p_YLODYV`Q!p4updHY3j+-?IVz}llc^CZ^@3ddb-=}nR**ovKQLOOU zR%&446!0k8KS5&#M@>?|)&sV0-6n7Wcyvh~CB-(F6o3b+^~9*AsU&ne84NN*XSB7T zwO}s5N>nx)+uZS3eYOlmq_ME}oD7G19W?Zh>3#!`VYUP6xH+KW zbF`_k*Mb&?X*5j2P_sJ>hgm!spgT)FZE0x<>iAR@5}{>JLBI8|KHR&k1kld1p}CBy zt%)5?Y(33vy_-$axZY3e2}@Ufm>3iF5<@u5t%|ITIuDb$ZnHYiyrE=J$0AE}<5@Z> znowaxHlK77Wgjiw>PzyZ6ZX??<}z(O)+Fm@<6&gAa_V=kZ&6z5MbdGvU47ZMe%i%o z0h8Ti*S&3f4!cTXEXz65#?Fxjap#uR*`kfXI~4Cxd_eIUEclS(BZ^gsS;$=> zCl_i0IEzD#;B^d|I)r#DAU$S!0lB^mx$K?xPI;#a%V?dOm3KO_fyay>{=ZZ}@(`Ou zWmAFk%CHOwq2O(ZSwxP2@IEHBDguNIdz`c)0xRk1Nmaq%nrO?yk{(5?IRjbaiBd5#*o>}eAkxJ*!@sP&!K)=MS zA*UaApbf2>hw%usWb(ckDA=@=^RgLSqPRkF6=G(Awp{XJT&j5VZeR9m1^kM&OSKAq zXLBkTB0|W0Rn7?LFz@2DUppxHdIbpdKLmz~xA!$y@&mnl(it6emvyt2=ZPk2COd>tcC*MOYS2Xkn*a$3AmD)riiL36;-gs2NWAVp|hVulnes+aa=)=%#n$ZhEDncm);Nl#xsusGA&ou21uXxWfS zw{|q=T65`nQ#`H92M%pJHr<|^C&k!+AtNFrMI@Wfb#?Znr3hCqnR0|F%0zjE2#bgm zMt@IdE|<>c3^78Ac=0Hm-F*=;Qi>6%^k#C0(ijI-FFm&Pgx2X@tv&0fH|H{)J?p&- zw|7)x(Wclz>{ua8F$&9|7528bc1baP`-Zo6PP>VTO4>~v1n%o-GsI4)WCyZ_7>kAX z^k&o3+FP?}LyX7%>Cq57OJQ=uy4KFF{tVviBE^pUrf=YcbSB%`+hd5`q=**pG$JMe zv%jaUx4)+&)tv?pI}EXh6cw66i!;65QXJ*8hc*8O)s{?I6U=N=Ocr|*-S(1Vq~2a{ z&$`a_hL|En`9iCn#AdsD+mACutrV3j(%Js5-11ECrh&z+?YZ8}fFY)%yX(Cn>ZBM~ zEC9BMMZ`?V(fV|*2XZRKG@lKYWObvMMLe1fR<}Uf7BWNj#tKp?4BDE@Wl||q%poT6{%ErZ%2PuI+ZfS0t}r>(emhNAvW#ZOGGRJ=NEPN zrf$ z4wYg&DPC%wHA;$~NK_N`7!G97nF--4TnZ5xSgo1mHNQeCatT&EOri78GLCy|7m4C*!Y_M}a5Dv8`; zaT*ldXS{{I9ciq#N1Q>cJyVKfy{onDz`PRd3>wQuf%+*^zo*+T0dWdGhsnCo6e9mSd_{1W(;v1q^7&|xb&h; z?dd*x?<)lkT}u$jr^y)@K4hPWSSP*unB1Ivg3+PiwQIL%G*ke>d#n1)!e zkQOY(l|F~oe^s1I3|4YK4~s{LgWrb~En|;@$;wCM52fh#3ai_&`$&gv9=!(I*1O|Im5tylL~I?YpUt?kFb*Ye@14!TV7H}Q8mFaIIMm%UQvSm+Yk?K3tX zsy^>64J>?yeTX7`gb;E zOxF<6nW+>Ok?rbiM@%5aaV3}=%&GsR1uT@i$toBFIZ2Aq#YCcKL+*i-*smo~2ixaT z!TK4d+*9vqFH=s|wJD}}Q`eGkvU3>F3P$q>ddgNTLm~v+gK@kbfrFK(r-+K<$y{%5 zSJsd)4i#+1wFpY6=S1(3a$qONK&_d9h-|2kjpWLgWpjyopod#B{b@r&1XOCaDd%dQ zBVB>84};q7*zEs^F+}8jjwuh+Q!X;)LXB_`CMPPS z(wo{7^=w!6Hsv82?MskVI*GIkCzp|%R|oPRs+*Uaa)};g1+{ecn6iZ(KzX<+r|XuL zQdE5&sSUXbQlYVQ0wSfuKB#rXPDx0FSP@b<%1=ndR24_GcJ&k!+Eej2q17BJeCh8#yWEc+bOVmTkfjZNan>6YGI%C1ivo;Z%7VS%TAf&<0bUsRupFlpFO{5nzxlJsgJX91t_p z-$VOL%!HI<8L*1y>mC>IGc43s^z&Cud7d8OVywsNX2?q|dwd|-*s4sdAS}0VF4Rx>SxHWneqhv{zlRkEXa^w$29PKdUME#$Xm$OZIHL3mn9fr z@a?AT)4lGrlL&s?FyuGvW*o1`|D^gT0hH>`f>Sf0o>($u|C@S=_i7H!gy!Ul1N{i4 z$N&v$q^+jZF1*}k$}M^g4?y%Z`wjVRs_P7td=M1U^K#zFkZ{M>d%5WLWlNlj?(p9_ z@%;`5>v)A1D<3ApJ%SBX^NA{dfINeQKaDIA3=id-8J;vETUq5+zt zTN^-(zw6e2ke|4*wdsBk`G#Cd4V_lPQoL%S}OZ9O5~VF#0Ox9HgNp$s(wKA)<9g+%0d zP`W3bX+>UiNl#xtY}IH4pq_M-sZ6y40#CIg5UsLzd`G&R z*2Wp8+E2IcZ>oKD?Ev6eGaBkZJm^Gl)=-O0)u3?>wioLl9%9?W>;~LsAe*FVfaFY~ z5hk3L-G~j1V8*m28et1VshOBxI{<3AshafID`4m}mZ4hg9&L$6HkdQ(Otn&z?nqM| zp?j>hd$bt}N4uV*J<-@`s$(<;TvR<~yP;Z5wM@Uo$&NMDn<}kGT*uEdO|?Nkck(l& zZhQbMIAeosZf6h>S+y^sfbRT4MPUTlnz!UVVjiy?v7cgL3B@fXI zbrLeOOoslROu7$tDczGJNlz88AkheMVEV#CK;Aw}3I9YR+yQJ3B5}H&^$b&;re{41 zJhjjag|pkSUX75;b2QGmraD#E&NtO5x^|%ylecR{hQi6-t2k+Z4 zuOSGks4Du?vY|6uh5vIK(p4@&_23x_u7l6LjXoqJ>g&*gb-0zHv^37Qj_tp^u>)&$ zi@LQ!-K=htV)txkcYhadBdf?#RdplZkNjFyXAhQ%wYDRZY^Xb=n7yP2Ej{h&Dmw_N zYR#mp;D5lo>>8-j{zBEd-b~d-b~!uPX@>eD$vCoQVHJC7Rg>}JZdkt-7|Ll(JhqnK zgj1R8Fw}i8-c7w#+5Ywo+`TH>*V=8UZ$VmG7EZHWHMJG!aIDleaDrTaL&0Sk8$e$} zeH&>wtbn3ua?*Ga@MZML++L;SotZeTW#JSA z$&75Sw@;JkMNEbipmn3dw^M#jpL64)ey?6a6rhm9GUn?OVP8X$XM|BGBO}F))l1y| zfFO)19ZG-h-gEk!dm1`)KWGhw%L!bg+Y6Lw$h0>kRb~5J~@l_)kb= zYE50^oK)SMqYd>55TG5O0fAjXBP1a@>$;3k2rZpPs0=Oa#TlUp-n1E^5s*QSJz9;> zNV|^rJJ|JhBQy&1hmW-k!ifx?Z)%^n4jn7F<+C_31P& z&{&^4SsG_)Z!2ygBBAjx-R<~%BA0H)-F#R2fZpC*HV11HkaFTRc*ToeJ6-3Qp`Ak$ zD?$@O@Vh7&)SGJbhBbxVd}IJYNocnaazu+mlW=5%Km}cpF7=^Q0m?jNb-ZWLivFG+ zI)0orM?#aOn1b;a!&H#0EbN7G&h*n~04vh#y6^!B93Tf>ihB2f$9E?_-<}k%#0>2f zs;&s_8JYs4M#AOXYSXRP5EfpcvkW!<>8h%%;JJBZTFT*J-lC%X0} z%B=FcWt5NP+M$%AU-x5G7ruf4j~ zO6dT8*G}mKt|14sQPC=xpoqTBV0R$B0;X5MxhSZ&Y*ygYiChR!?iDym(I0wC?4S2c@!gX?E;Di zxb_wD__=lwMFCv9gj`duT}ECq*RCMXoNHH+$IZ2C$g}3!b>tp$?Q7&Ra_vTPHM#b6 zawxfWi;qcJK45E9By^h;RfV$L-3O)0Okc2k$r1XqAt+_p36!V{;j6C+o-YI}{ADej z_}r^Z2w8VZ_NGlf@uVv)5(&ZKDTrb%%NPmW7g4alVNyo&FY%36Q-60GJPfh`F#gTB ze>-j&nY@bT-u_H`nrr}qAZ#gz6&%r-?F8b29%P$Z$qHn}B>1D(2pPIvbck`}pkf>2 zDE{FRZaS5e-QyCeJARKqk)M%n_@lgtlBF#^w@HJ(q~pxkN z9uh`nMRHg~o063kwL@aGY)Y2V%Q4o=G1kj+dO6N|InH_+p_db^mlLg*2EE+1=w+1q zSB1YTc5kW;RVJEilWJI0qh3EZoUBZY$4W*^RHHoRR2#9T2(c!Q$n#7qvqqfJTpO;e zFT$Q-W7mdxntJ;=8Lhx7F-ZgMdq~WwoI51;sZE7a&6WGr4$3Y$EPhW5+t2P%i#MIg z=*FOKaoJNls4lVk;i<4=OvcbNS$QDlY8q6R*QSQVwX$hQTzrk#H5rqY+)68nt1GdT znjx`zZ%NM(Xn- zZLpD!wUCCyw~4Hh$vT0_N2DDKNB|lEs{wtW(qRsj>Mbh0lZ@_wIaqpHDvz*O)|3om z@S~wb(NWF{Qmbko5bZ2LP%gVYigqo4(6??DJr{}3(u*y(vEw|~#39jAc`oLvJf9{^ zLaDlk#6VLrV&jb_Jg8824e2&Du1rRE;?5VMGs{~oX?XXbxNJyV)ifwh$jjojA_iGY zR$dR()UdeeVXydUI_&_ui~>XfalmN6PAq1Xpw=HDV2+rfMUt1RrA+J%ktLy>l#H@Q zgs_xKaBH6$tb~{Al6cLUatt8w?X)Bp*%H<@ka99%W0KKvygR6lFB>MHRf}%{8$ux1 zoy4Y{(Qes+Z(s;*0`7s3KPc(--0=8tD2YXWuX%iUSo{Dsp^P;9XxJ*tB<#lX=UMY8 zv!)XA-crMq=D#Yp;q{RC3A4*#>m=$+Ty(Q*cOKBiE=<4W3$be)=r;kdGhjEsB*5;D zRoKC66%c_~{5sN-_x%g40=8?BjTTody<4^PR&E28=|m~eG|kS5U?;qIuT<P* zE~(fB!yq)Wq~aP&DwZdslbMJ?Qn8n_{K{WCTz>*wf59@~U^@wj!{n*xP)dxjMSn4R zP2z;I#0W!@7==sA(+pnuev;^y;L#!?tox zB};3iHlDw2VjEYRKMAyaPQkE>M@O`+-~)lhGCEg(8GjkKpYrN^=vXai-{AY9<$L}O4{c*TdXWF zn$on?r`SgB&(r;9GJOE`Etctp^pY4Fmpj_b-BM2e+@N@m&C#GVYE$7<^Pm*9sj^h_ zgTi$Hz!2kHT;y!SIfCOd3ioeUYa5XwQ@;cq{OAHc<9oK+A-OvpDTmP7Dg!o4XEPtr z{s+QR>RY_oW%ROmvwUdToAue;q3BfPU&Lhso11EF&M4X(oP+Jy+~KsApv}Q0?@rQR z{+nA#eTz4DB)u%&+`q|5kIQfXdnU7>NV5TR0Q&0$2)I1~?SZ0yrG7 z5^y9m2YE9~0iT3JO=jg=h5Em@Lo&xVexKb2#5M4hKft!W*9_{~nxgbN<6hu_*}?~7q#&M?PVJX>7Iv&CLQP7ETX%`+eW zWijSLmegAEQp>BIk>xv}BiY@PbaBXfnD6 zBgADLOcP}S;&NtT%+8J%dDqG4Q8X7t=WfT|iJy)^E8Ak(i1zm!08XMBHklBJrlf;4ik!fYTsI%(h2$8ZY2qEYcWBrJBad*e%+iOtHZ(>IUv7S7F^A6rvFN)5X)ZJ_8 zcJ%7;^jiG+y<8gXPHU`z;21o-s0mlN419Snp0X^(&HsWW)*A+RN_`a<$ z&!OP=eScVB*NWX?X)A`p-G_Im zcJEM&OApnKp>TO(4Yk}_RYNkm&O6jnYpCdY-PWYd3e?umtgOTC)?;*B$XKI4iyhOR zuTMsgrxA+P%t>QpxRsolxI7v{7?;RgA@vZKpjFGD?0^8fvKm=JCpmNnv^gHbk*nQV z2jUXj!l`z#R}fvK*1}QAYds$8%>c51oRe(0tT@>~mrZ(?GGV#x9~K?~o3Nh3KttX& zD6^c@ip#s9u5{$a#8RO z$u^dRz8CF4eZ|JQ$igZ(v~=2L6Dappz|DYX0dGSQaCFk8oJFyBKr!ftyf7IZpgCj~ ze)5T^1xv_D&RSd!Hna!J;0((tXxEOY7BYAkI@21L{cuNb@(&qe;#{89aiilBWQO3e za3MR&cF;D1F>xMYYcojE5u3r7fZ23gv|6|1$=g`?#=G9RGhZ=rfq&;aNNkHb)4RgX zkoF+AR&KTel#8qY<=TJ%#iIv%iV@)20N_NxNq|!T=K{_HoDaAFa3SkPLMWuo3eBF} zr|1QEk84=VCOl&~Oxp^z z)vZ9(wWE6~2yq5rGq#BPWowHoEI(~lGI|aG-n#YLgF~W8w;0eDP6adwem0ixD)}&`}2i(3H$;|5e;lY%4g5)D9Q$S?SnB zu-YXnk!a(ASo`Q6iIcr?`4Hvk;u4V?B4qjfe0Du1KukNmPIkz$GS~6!&iSVE>(=?^ z7z)DI?+OjY_5Pg;t&HyMwK6xLGsXT7$VXSP$RbkAQ-xZSE>aK4pETVerr?f^Lw~T9 zF)Z(qZr-e00c&5>3P0m9;@4_s;|8AE=m!O0l;gk|e~ZP~it zFtrEebG4~*O3u7kn~L})XFRrVU%qg-2m*UC;BvqffGYu40j>pH2cVAtZt#H{*(M$- z!ZPhPfsqjMeudX!`qlF;tcYm9|3XGYO1Uj6y@zK>@1a8YIK|S@LoKgkc7Yv@iAzAH zB11Z49oo`1fCZLJN41w=@O-Ac&ayD$!Hr9a?oLQ~Ke$#9QtIxPqB}m4(Q7N8ixULr zd)e>N{T6hO%Rg8*sB!s6T%pG0p911BeTC{I_R#-U^e!gp0wP<@aK|3hK&vpz(a`KZfw zIoPeRU5;pe0pBHbx7Lyiz_C|N~+2F&{ttZud0mgC1)jL z;$i<)&tyt?%OoFj!?@ZTAEwK78hCsd z`yC&KbaZM%O#3LZ!D=tI!D@T{=41=8dGR$u6`gwf( z)fXYn^gz-)^+ieJ>;^K+X&RQ)&lBc?t;>(J2=5gj!q5CKw(dGLb&}!`QHj&=7Qn55 zI{|kC?gcyucnI)a0Hq-x0sH{)DB#C{#{iE5eg$|6@C?ood}8@IXWVVrRnZyu=60NM zw_$L6wP_9RKI3jDGJW0|cRRY{Q%|dVHEQ&UK@?}yUDVw}6@4<^g6Bp3t? zW5M{+)rUA+N^Elrdf}^B-3veNEysUChVKw>fT~+PE0Uk+71?>Cn85cA#JJDM$;QVE zuqb35*#o`6`i24{4H1?u*aVfayFR{*4hufBQMJub8J!ztbZ#WW_$n$|{#_NBi#O%9 z&Ew19(l#f@mu*ucwO`<;={x8P#J&-|q;(MA5K5N{OrmGtql51Zi_2I|25LehuMKE1wOqSv?4 z3m--6Uih4leM#7d|?&+M_6I`8xxMD9hvOMpKBUIn}c z_!Hm_z?+Z?d?0VhQcx81JXTP|={c4)D-{JjkHPWHy^o;2KwI)GS)2G5&STv+LFDxZFU=`S&ZS52hk4o)6rCLaKC z)eF@i&5bKuRp2{8mE*a5E)HT6hYaR)4M9Tv?+pQC;2vTX`+xZ%nyhmwuKJ<4`lK3E zn|#lyQnzg`iPfix{BrN@Q!xyKKA0RwbVnk zSy%9USox}72Lf2D!dVRU%EkE&Pob#nHvKz|>D;3qad zgM7e)tfE0aEHTIj8013?f?wxYgSef`fBN>+YW`33!Y_HOUJpTG$yc}@pL*kfDW$L% z1Sl+h=M<<;_POS0s~knx?sJV!ro0p*2SVcOZ|<2BNNWkmnvkQg6Z{g%+R1<=K1Uad z4}Bj{EIylUcAsqvg&gU{I?uM5z9}%~rNkHQawL|4pFvs6aD@o6g0`yfBVioQgmLhu z!DI!xZx-Wt=86$lgIwu|tNR&ea^=6M6z|2=5Vy>a z%Nw|IZCnj=WocY}hbk(EkAwJ)h^vRVa#dV?kDE5d)gxS~iK`!Q)34*|QLfw{S3l;; zi*fZBS1ydp%eZn;Ts=+|wF=ib{AP1p{hTXfr&&1Vl`OOn? z^&D3~j2F0aUtIl;E6>GM8&wnp@Mx|aTQ;m-*{W1r9nR0|v6WZ0iW5RyTowu;8^4Sa zb$*J@yR~w^Xa%>1^R2QgjKnkghT`ha%{Ae;daJpnEUw;Z zt|8FwRlk73i3JI2vW7au?EBe&bi(gW+l52+x$P z{8@A?_l&qll;K!Z&-7Dg3-?5KW<-PTxSYA|y^06RD3{{e4K1v4TXZ6Kin_xurEb@uyUK-uV6I#3wiP+P<%Y^lo(@n6RQdDJb@HjO&gANAlo>W9&^Mjhvm+8p*#!MOUE zMeW#VpYA+9FSK%lGq~)DZq)4)^7K`g_?a7gIN{hEOOEf3Zql81_9x9<`FZ-Ir)boP z`FZ3`g?z*b5It36?c%@Hvx_@ihF@;#9=rPYxTLtp4KcA<_t?$9$Mqgz!M<%_!8NTm z=8%dIOT+CMIznPt42t;R`|Bh z3UAYBlk?-qHyyN4IT0kJaPy|o_H?1Wo1g9@T)Sz6y<7#+po8lVhgCgAs8ALF>HZ?XT26dOI++`A<^g9)A!9eO!qa|G*~EEPm+G5yfnfzM$j(GTxa3r zguoU+UmM-A~Lf1N(hPBQ&^{(NVMvb$CqtMWZ#k#%V4_I~w7nMw{hA zTT_hI784(9wAn7SwM7EOJhW8nqL?h#J?6N2e8s&F8PN!PGlJ&Z1;sO;9g~Jeo9h~9 za}gTYD@SUyc`mf`zJ-VnkqiAZFjm|VMLdCsUW5nL8}y4KAx5YxLilMe{Vp3n%L{Sq zP7jLAs(5Jh1EDcPp>e~ZU8fF*cGrWA2<;I{Sm^XqOZrn@nHdgE)x$~rSg}U8M?OQG zk4RtsV`@$9&d6osk9?O4{tW{8~^>1XKYMfNB7KlO$&Vkam{% zIfa}L*dK5p;9vlLMj@92S^!4^jsmO&bO1I0@X;>5G88fg*aSEka5`W!;9S6kfQtc_ z1Fi;K54Z_%E8tGRJ%BBM`vJp%hX9WN9tAuG_&MMSz|(+d0WShx2D}P*9q`srXxf&c z&; z2Iv5+18e{s3+Mv$0QvwKKn`#MU=!d(z{!A90jC4b1Z)PJ4LBEYKHx&YR{<9TE(Kf; zxDs$R;99`-fExfe0d5A|x`n6(IRL%NKEOv-kObT-Af~6Ee!H#>!2Ey)GpHdN)G#`z zVO&td#Gr<%poTI?O;E#(poWH^hB-kE^Me}p4{A6tsNvwChNVFb%YzzPf*SA{ zl)o?>71XdcsG%dMVM99=Xt;P_`kN+|M)LH-|D*bSHa&GQQ{w<>N$7ExqWK{Za+{@==MW3DsF#8ja4-Q zcc8ad+0;V8iWixWT5|-$d7ni=69kxkJEQbsswS(7AW^ zFSuT{G_aNrY`f^9>($)*p&MOr0oPvQl3*ABm~(G7>%h0w42b##RMX+XXgch*kE7)F zsAv6E^zV*_hy9~w$a$Ah`6!+ak~b!!dnd1Ty2)sA5^v0QCtG;li{hj;ilg059K|`Y zxZBGp@o%C^Kq(w1AkHLEs7A%%^y~qpR@7Kc54>OHVL7zs{F}`+fM|6IaTSI5?V`kQ zqXOi9z|%gcxFghoyKfr>?!k&nR$ag9`fKj~ihGEj(A~%X3flqag>DBZfN*2nOHy;+ z%qX@3(6%FY&3C+nhx^(QXCaUt$;;suuy9Vzgso0Oigw21XlNAn8b_aNvPymx)vfWQ z|MgB1-Aazfw}v2LdlC&Ikg@&3%@=O|;;rehpR^N@H@?NX-6e%@j!zPzcQ_q(FXC^^ z?Oo7kZAkFCaTs27KjFe;9S^)FyV0bA$4L|mB~MraNCed2HVHw$hAoo*Xvn3!-Gtop zQXEGU_P#oqj3!Oqh*rhX;Lsp)f6qG;Y#mpX=f26$u;OWW0K(A;b+%0m#jISg8CHYM z%H?1^$nTwq5<)yeHFxjdfgA?J12Xe%v_tnT6B{ytIG|s1Nu7Lv|NV9MFg(Q&kFc%+ zhFHlNVxwRP97r+u$rB+@g(Ds- z&Mg?W4*|{LY_zzB0Wq2BxPryVeNuwPVmm9_!FI-E#GeNdlRYAYl5!z?Riu)oydFX& ze5@mnxuuqI>BZM_XqPtcfmWP zPInM%kn98<>)Zh~lZJZTL=5M5(CwdKMZWQ5NV_UDoH5mnv@c^+g7#EAo|56S;Zy5~ z{qra(Gm5=(f{^K1V(y(wBjoT=W*GKYe_Apr9{z<4Q;4EOq&T6jfi@Jv7$u!9)yipk z6vPF$qJtyk4mWPp+0Ell+%U=4Y{m@$N$y{4kTj#p+*@ z2?rom{}o#QLjB8I0Po8mfUs{mh&`kkHUOU25gdgOHonS5sbN{mMZ3z4F}*osUoaQu zCbuukHaLR&K+v@k=z0zsTQ2~;>h8mx`AE>SxYIwO^>57z2^mI0@>w)YNWLwT^(`q5 zwq-&gHf&dj8r(D}6t0~0jCjw|J|M;-Qziug=V3ZF_lWG&?FAr z?YWDBI&}03-Kd5EN2mVB_L zwp>62AFu|{+V|I}?$aO?5s4haz8peGh6Sx@SPHxI$hr5c7Lsn74_IB$x3-{<^2Jl6 z!9Eqg%&fF;&cHmM&PY`O<&XVv(l1FmdG9S86%;V_B|mR(GL3XEO$;p7)1St`dnlR0 zL{q=>ZI<**SAGU-K4K#}JO?OCH%k4!^i)gq4^^~6&6kFaC|5K@*5^1_xCj5&B3+Cv zJi7dH2D5DfB3uG@;4n|3C_Kks)0U9)aj_aoyioET`s0mP9=)~9hPOYvbl0CV3>Xt z|0G4(h3|6qi>Sb1a0?#FS3`)&X@L?tu`@z47nYCQLU?x?I5@4aLhSk3R#KyW-yk`1180SvU+= z9CvsCHZb!%ZY@3OZ=j?qs%|1X zEp-1>({XQPaXqav}nMKKL0E5`H5H!UD@-;z>Gn_hh@y> z{c@7j6MxUCCd2a48rh=>GJWy~D4Abg*{t~4wXLRtaY|Pym)5{zp88=H))P`;1*f=)?&~*&o?qN_b3ZQD} zWra^}Q0LvizuceV9=n|PZ>YfPrY-npBTu8jV>-evvJ6d*ugX}!O@h}1npI2wl6rcG z;$c280hu)FkT5kOO7aIx@NT8L;)2q>$fQwMpnrR&e_Q?7d<7nyA@It(m-LzdJ{x2a z3AxK3e9wJjm9H15Ypyb0M?#>t#y2j#y6dgGBjC$eegn=Ig;5&kr3Rc(ygi|kxp&g& z$Bk(mL9L9WVH(NpD4Rg719Fh~f(q6@ z1dROUBZL!j0-3~uPW}v;y!DmrHy_ao)*;3?cO?nq4YR^Q5gO*Z4 zqpZq_(O;uvN8`jwT8LTv0xD>f4;9?qd)k3aKn7@lm0x-Hq68oMA#jJGd;5bO_chU$ zL@sj3AB`F_Qgg(TqD>|z{p4?nhmpRICVgnkCYJv9QEB3&GdzmCz-U0qn2(w=54y%> z)VW8|@NdvgBf5Hc5&6MxcK$QbE_UHGG0(yLHuRx!f_6OOyAh;d|DJonQUW9*> z)mx|vNNSn6Cb#?$CG{`sm3lR1;JCMYGxf_oq39)DXL5v)91on(!&e)V{utkP#IzJN zr#E?g5i*vipLLNU5>Ks`l2FTNPKY)jt3_Z+bWD@}K=DDUON0V-$*j5dZ(xCN6z9v@ zx8Za06}2Lk6O83WMD1!9d2Q0(hv>i!4%1w9|Jmr7X!l2i{?&ooRlxv*GZE zcq|BQ^3F}g8E{Fs-(TW6{v0Y7T!9S+%_~>7F2f3cGW-O}XF@Aa<70+8vLxHV#qjA6 zHrf;qkI%A;68{+068{)#Tvi!AC@eDVZ~j#f%r_XE-?-?*hKFTq3;PReD>DFT4Dxwt z1K~Bqv}|Fs1!jWrZO_i4br)Q+Eu(Mw&^eo!!GKr5#S(XVT&@3u;W$&$IJi)E(FXhz zN+wYf$%~v#Z(0#salIuuA{+c;whr=+rhaE)!5IZK6G%$iKcl1wEK%n~W=@@-db@Jw z)=LS%{r|Ptf8IuNxIno?04kDy$<|earf4=Ns$6WcQe%svUc{M_NO=57bT3hk(ggm* zk_7hENh+#zHuSoAJ~ky1CWpL_0?#=)On3PQ%+aeVc*2xH~iLhP%V))M5tMc64FCMW)gy6v)r#pla3lZ-*P`3UYoCMKZNfgR3dz^D*N@3nSE0P z(K};MX)mlqh?%_zD1C}VVlNY?XE8R%O+T@75WooxNv1ENT5rt#_HHAV#eYVyndQJc zx>NH?zSAP{RPbf0^gp>5H*}RQuPQ6LD!K}|suUyg!DC0@;J*RY5D1qbK);x1o%>MT z|H)wn^R~Q;ArHlf?<-LitgV)*4)Xkepk%hZT3_|E%VK`Zv4j+H9yE3vP0^UyoA~D5 z?x%IJt+OW3-p?E68v7*U6iW-}nGdp$9F-b6D0;j4b}+s@nk28Jt_2uG<{TeG#(W`V zSt_}KqhVB%eIDH3W_830wys1Vll}Z~+M!+I!GFi>^xLkTZDyfvgii%enfzBU#ps25oT^-Gv*ExIk@E>%WG#&+#? zoJ`v78d&?W@V|`eMh?xsJS>G}$Jq01!@I`C>W^3*uo|!$ zqAEyU(B1QO#9b77pA*!t*RR&UP`_5+N&Vg9PJh^LHP_YVAfeBgoPQ&Z?f z3*Jv*fP8^7awv~)+}rr%sKJC zoj(2%xK3Y_m`LMx9CU2^&q z)|78sj6)y}eTt=aXrb+dL3?Jq8+}hCdxc-PS1J}GwY)a;oMD8q)HUi_mODnhc3{%t4K5vhu`qcoRI|xF-mKzFYB2Lx+hNbqY z{rm+44Kv=?w+t@7vVv&1#E!IRsc`LA-oGHAI=BWBd}J4X?R6D&N-QtT#47t@QEdL zqFKFPeZbdfE2G00s;s}nabtu9{us349Co$xK}$WL<@*HmBl7ix_+i9anMe+#Q5wZK z?c}}a*M{_C#4fPZN3={oiU^~Py~uUU$9VNZOMODGev;JWT8VE;<_kgf>1OpAmg4xh zIP-=B#dyk8pMxEwoV2OFfE7)^M*hC3zQjjg1loL9?MfFM)B^CQuc)uG1%8dxP^}xC znJj(I_Z+7F5=;3TEah)n>M1SQZYin_sxRl9 zv8y#|q96i1nU?yyFhkA0YVyc6351;^?08c#H}bVD6G|_#XriV(B;%zPh4t9BXoCI* zs^+tO&oJp6Z%G?Q`S9fyP1T#H;p!lpt1(QP;T0}!#oX43t547!m^2&K(CeU-HtAf8 zX6eIFW$U0{MniSoBFFO|uG;c#ok#=F(K3tLH7BU3F$PHyJzl`$#fvOjsK+RB$MN=V z-fj}M57cU?(mBTn!Ie?f%{yaS=QXqh)=4m+^EHcbEuEvw44DK~>GXGK(8)P%ov838 zAnX;KT=~L6D2l;Ur$wtYQ^?fe^-ebDB;s6g?9?|KlTa1|Z?Q#dHQqYpr?{jTleqQ; zx-*c;4V#48w@KHNi658rYX%ja?-lSz=L- z{>V0LiudOA2|HvF9PiSLs79L)WpeuwTGl6XChfIog_gxNUTzh>O@gz}5oP1vF(4&+ zDwEltkEN3PomfUHtF}&50$>mx*sG6E$QX9+=y5-^ebCAEX7b4bxE!=7uD`aA)a=uo zZc+;OHjN~g!x%1;h2geNWckLv;!qamlR-C?iT9y`s?c zRbN{tG8y=QKOWX}qlgTG?mU!f(o5tchj70n6niL5(iYtyVU&)^K3MYyVb13O4 z&G|>*utRZn*ryT4eJGp&8lpK1-iN1w^0cSPw78tCmS$^nEkX;WJ2IM$qraQwUd4xwL~@;+Z}ic!fWGmA=+Y zU!kwV3UtwC(l2C)Qi`&>WtN@cSNPmZ0%3Q2U_!k znPRH1H3N6dCHtJ#!l2XYUcXy$lPK^L(n6C^7M&H1g3E-18Zy6Xu}MGR55NYWB{*xV z``>1_xTyV7-uqOq)2hwa>1!=yV0lOc3p@hwx$;@Qq?A`pLNPWW8a!Q)4d=XnV7WJbWMOX+kUV12^Hun9(?CTS%onr#Mrs-jxQ`z;jYiIt z2v4RF#A(qe-#u5YF^zHXr2b6CG{&RT8_$`>St5r-CY8c7u4zn!FSuek%uJa^%Snay zWfIJtY1k;OT&x5eDIOwC165o@w5!)NP_s3sokQ-WL|$wBl0f;=VoWurA$l0oK|8k3 zL<7Q6pP3iy^57EFI2TOfOpZ~c?r-n%DT695m#$yd&UEeB;}hYk!p%y*vwc3-<@-LQ zpEZmZ8Ewqbd6puH*2eiJ0+XVIY0L*-@y&bs3L1QojP4Mk`{j;Xqddo|+@Q{9BiJbk=b9XBqvjAh1h z?xCuyc|hcIHlg5fazW$8h~o+TB2aL8@P?dn)@Cw=d?6Rl)+qvnhFb0Obb0Ga%UEly zYc|#x>%mm6nAS{PSX(1kZ}U+Kl7z9*xRhmo8N3Yws@g*8TpvkQp{ykJqS}PCwU|zG zE-KR;G&Yf%hx0cg263ghE|Z2Fiitudx7F!qXF?rSMklqnGW$u+_IduRro=M3jVqgt z&BhkQd3iwXcOP>3rKHQxkpHdUaM)N4HiB(jWJnLMGuw4<7e7eJ99~Qx(7mhqt}k=f z^5s{0*YV9!dcAzVlU^TR)}+_ZH!bO*EIwcN_Hj8bb15#BrH5)Wt$SH6mt-#IW13rr zJa z^)S#*HHeD4TF^KeRImlx#~Opi4RB~yD&plP{QT8j9O`v)Qq{ok@uON||7Lcb=AKM3 zmvGpYP_Tog0w~y#%qL-TYtndzkX(^h=c2Irq#~oJR;npn>41$X{MaNdULU#T3!1dJ zk4TGakhHk3aLbP>(mv?Uqr#PSRKha<4|k06RpwFgN{dQb)<5EoQUA+4Dsq|sfjdUJ zlX+Cy(&D}^Es8>EaVwS#Ip{=P!o2<#Q$usfL6}3pyJ*1i5Nj@L$yc+59y$XbhH-fgopH~i!?%`Y>HH2iY*vV2FwJ^ z0n7(10jvRB0q6nj0>l9sz#+gd z_XvZsI2@zw9#|o+Of;=@u1o7zBB66mPwNyB>D{27X}kw4ypCCzTFt@&cbQozsk_Ib zKZForYV`YD(d>+brdOZmisT+Gk~g>_d9#F@GZ4vSi6-ZO@hO0*fN6l~BAz)AMDY~y zeEO6zarx$|_S-m%Rr{7mXqFH;rfO%GnR^E_*T`YEq#ly+T2c>3V}V%D6Y4#?qmQZg zNn9OKqhPcGWDtHam~bs(S9ha;h-AQTA;>R2WukLf|n870615Op+Z52{v2iHvBhkv*wKEh~8Y zX)KH&UszaZW%*OAmhNcdD2-bYm>NJ0));A=3akXJXq*}d@VLnvbNIVh_;|q@TUEX7 z%rzE5jRrfe_#i6KvWaM2`TQuoh*LZ7;x}P{#j%463Vo z+rME@bCd~#SqoSPSP$3$*a)~3a2cS>GbZ(XtwGRopSi5h+{3kVFeW^*2C?}1nI$5w0Nq(=1O1LJS)`-+|Ix1aR{sbFQO|sY`MAosC`jOzO>TT7Q z@`574G!E-(6JRr-8*n9H3*f49qWE5QVxY;@V<-S`Tv`G6XHB|x9*%^z36WzeV0$$m zb3_?7p@4!X`h~z9yn!{Qk!-@%OkYkbOALK8)wR<$u(BPnL)hVRdRqWUE><4ZR@ZiRN(u5#iU<>s10*BG`9z^7jO;WT0jg2hX+(QeHgCBz8DE5g!>sN zLTy4$U$ej!-)i}0#3mROZet?C^`aCYcr0)1L1|#qWoR00y4)22WI7{W-72`O(GtPhzg1BvCToy)3UaveTY5j|56BMt<$FzDt+`K* z@N==Chq9kh@RsFPU}m+A?h$cFA>mpnW?-daYe8RYP4R&h*2-tw#1~^ZMu)v2UnDkZ z*A8IHt{GNbD*Ws~XO;x?J^&a5Bmw&X`vEDy5bOcZ=xzXdr+dS@B@)8r`*hcZ0;8~Y zv;LRH37hr+|4V;FD*6yk>n*UKD8mE+>i zBcbbYbcv4OaRXr*Bpk)@HocTTf=5E!Zs|t%A#qwHglVV%df&C6M91;{QM&c1NazNc zZo_oNvx-;-UP~ESPJ)3{#H7}mnAD<<;hfNTF==|atF${^55LPb=-0RgeQz`dgD%m1 z(U{5Yo1(FhSo;0w$jSrg$kK!8jANX4q0@Yd7o7Cq76p#L-bEsZhg}KCD^M%mP;cf% z7Q0RF;k6RI4+4-^)E0FMFS6dW;RznEV&AQDsIPuK$8S-$RUl~v^JPF3?74o6x?PYQ zkS9d7OC0br=lemEZ<4Jr44p{l%k%B^FbaNXxB+ksa2#+m;1+=ri+A&=+j_(kOl7g!)wY9%=8k z>08{P)i!-cIs-O+S2}xa`o47fYY4E4(mrI<4`t;+n|>sn`)ztomY%oiCo=Os zn|>ypciHrF>D*`2F9qjmn|>`b$8Gwpbnde0ce3=XO@EY`Kil+I>AcCNze{J5P5+e6 zM{Pq%XOV3f(rK}cfONLoMiY0`!?t0{%n{pYmd1Crxr z%GX*nPf%zd{% z{w*|L@I#eE{3U=I&v!$UZFFE8UhqRp^u7v`XuSWv)}95}H%s{sihB zGecMC9pg^2MAQA4w`p#uN8>hE;I{eWE)VU}xZ^8umj+NdRC3MF;G3;M`A3N9C64X%Zu}ph`T|Az_d3;3ei;IB{6mG$!qxQ_ zuB*4uS8rjk-a@M0Lbl#Qq29vbdJ9MEEgY-2aJ=5aZS@xJsJC!ey#^Ll>h4Z zeTc{Y9Ze{5D$;iyPx_vY)TCe2PF?zS?KGs{(9VkVSG2P#{Z;L(Nq=2JPfnYXw4~n> zFRaPwh9sNP-^5E@PPZi4mi{(g8WPnd-jQQ`Xhr@a@$w6iJUrgpX@+|tgrgxlKL5p&qt0V%y9DXW#wWBl45paF%k zCvlB1z4qi4`VA>o2w}D+Ua=~Fo?*xRJw>o+pgRHYm=+l-0NQm}6kIth2w@P0&w}E_ z>W-s)>Y#gMZIl;rI{7@$)4W5Xbb*bMW5csMcwAHb6urAy9=`~R=x#C2?((~S9wkwL z-+Kr54nDa%oy3J-KpyrkTi;Q1kewG>*sVjMleFE#faNE|+wtCpGQ{(@?dc*kAzQn^zVc&W;=j7L?LX%<4JhRUhzUcnn-QQq9XZ{gKa z{m9%0bH+%DGIk%GrHp~8ktfO$q_HZH3VB=S5ZHTDQ)hG7A;9*xXj<*^-_c3bE)}Wm zbZnXj)Z?~2NvBEJJ_%k#Z5zFa@*ru)qe+@aZ8{8$6-7K5*@NmV)rBliM|m)g6~7Ke zxD+i;%)S??^b4X9s2P3!r)iKBJRylDI_^IFb9~ocf$2VWDW-0VPg<>CviK1uEmckG z#CX?X%n$K-jK_Y3<^skwc~zqaaf&knn$(;MX3RO5>k2d1!oM|SzjQrtZ7bZOu4kh% zFAT%NJlR|rhkHC!8?p~U#G|$aya3uU(p5&<$4M}ni6(_m@;Hpz#extkv6$;*XBSPk zyLiNd0NxSg;}O;AK-)pDvx$*q!ZJ5Fp%!|RJsP38g~s{Ar$I41f%BvNjoEjqPek3O zZ?d<{wr}$c+V%xmIkxG^ErXH(?j7&Cx9@Gi-iM@}XYo;i{R5o~0-L#fCosgN5kzxA zW|e6)oXqt7k2QIX|8*5N;?P`bn@(zwEQS}PR*oYi&&E}l7l!o|CuBE17qSK6mF+x5 z%#O2V(zY{k52ALEWzi&z!gi64B1k>|VY^fUs7iI#Qgx_i->nyxBI|xJK?YFgKTmtnY%-U<*(s1r_ zA6jzDyZ!ZaWHU00+m^&BX0PA){!c&bHbu$gb}+a`yj9v-!IGS z{lvwy=CWQYCe;Mmo2tWQG0`Qcm}ZjkOF|!ELjBKD`B2yRs15%=o$i0ddNB8u%Df`= zevVQBrY6)gd*EvU=nMt{!8@g}-eOgXW z^Xg#8lvKMeRnXWnM6Nwb^K%^^r{NTt9`~@EI&jIQ>a3k+S(+E-dzf<$mA2A7GvCKg zNBtM&br$3kCbU`#BikyXyc%Mlc@kxLgl~e9-?^oLQzUhjV1}AdzZS+hrKA-4ntuAe z7CQ*nk#?S^@x*koRo4dt%e{`et5rq|qO&wV1*uQs+^7ao?h7H6XPcj}AcjxU@-UifT`EbUmx+ zzfk7K=hSMeUiRx6jhY%#-$82Q;`|Eh`3~u2ipjc!a`BTiJ+*!kpGJO~QJ5)~IB!(j zsh2#HP?D)UFL9=jBMb)&mWG2@lw6u;B8gO9oilxB0Yul5#lurvy-<4Lbevh$r(vc8 zNg9OZ!0>X)^avZwr)VyVmRA9eTRqv2##wQGhQZ3)mNx|aBuz#6`xiQJtczlOzAEW1 z?n>YX)XZ^X!V4$IHVRC5S;!CacmC8l6}Sxe1!wuNdEDgS4hOe4xVIT?sDEiOYr?K{ z4~7r-gXtah!91MmvQ>iU!3c2|RsswkgV#NE8E`o+dAlz04Odm&oL}g$-03+Z3tj}4 z74`G;;soXFNnqQPv<)?2A(gjXC^8>mg_arJ?pa)%bi;J08p#sA92zpk#xUv@Ti;`rb!YF3rln{l_X{Pr6dDh zzu?$11y`QcoSlMKR|p~ENa@FLb+U^(;N!-Yw}-&EgTD^-qV&k>1blU)zVD)j?;AQr z!%%DJ>J2MXL+vqiAg0acLpI-KL$fpVgyue*&)D?P%*|1ITE-ZR3$@AKL9<$Gt~A&1 ztT&s@?Pd$VE84?vN#8h#C%)g&{EsdukBmAX%Cdk#Q+;(Qb%wfX4lrXB_9t9(DFmwA zRoWUCOY9HE?rPZll3VCEF3eio6PsTwouc}gUvY{Q9f5$yxVBU_&2KnN-$^E1n#`4M zQpsfM=V(1*k6$;W+Hj3iQGQlg?x3?+5PEq1l#ON9+{FD~^;c^`()g?Izuxlx1IJzi Al>h($ literal 0 HcmV?d00001 diff --git a/unit_tests/test_config_defaults.rst b/unit_tests/test_config_defaults.rst new file mode 100644 index 0000000..944d370 --- /dev/null +++ b/unit_tests/test_config_defaults.rst @@ -0,0 +1,146 @@ + >>> from optparse import OptionParser + >>> import os + >>> from cStringIO import StringIO + + >>> import nose.config + +All commandline options to fall back to values configured in +configuration files. The configuration lives in a single section +("nosetests") in each configuration file. + + >>> support = os.path.join(os.path.dirname(__file__), "support", + ... "config_defaults") + + >>> def error(msg): + ... print "error: %s" % msg + + >>> def get_parser(): + ... parser = OptionParser() + ... parser.add_option( + ... "-v", "--verbose", + ... action="count", dest="verbosity", + ... default=1) + ... parser.add_option( + ... "--verbosity", action="store", dest="verbosity", + ... type="int") + ... return nose.config.ConfiguredDefaultsOptionParser(parser, + ... "nosetests", + ... error) + + >>> def parse(args, config_files): + ... argv = ["nosetests"] + list(args) + ... return get_parser().parseArgsAndConfigFiles(argv, config_files) + + +Options on the command line combine with the defaults from the config +files and the options' own defaults (here, -v adds 1 to verbosity of 3 +from a.cfg). Config file defaults take precedence over options' +defaults. + + >>> options, args = parse([], []) + >>> options.verbosity + 1 + >>> options, args = parse([], os.path.join(support, "a.cfg")) + >>> options.verbosity + 3 + >>> options, args = parse(["-v"], os.path.join(support, "a.cfg")) + >>> options.verbosity + 4 + +Command line arguments take precedence + + >>> options, args = parse(["--verbosity=7"], os.path.join(support, "a.cfg")) + >>> options.verbosity + 7 + +Where options appear in several config files, the last config file wins + + >>> files = [os.path.join(support, "b.cfg"), os.path.join(support, "a.cfg")] + >>> options, args = parse([], files) + >>> options.verbosity + 3 + + +Invalid values should cause an error specifically about configuration +files (not about a commandline option) + + >>> options, arguments = parse([], StringIO("""\ + ... [nosetests] + ... verbosity = spam + ... """)) + error: Error reading config file '': option 'verbosity': invalid integer value: 'spam' + +Unrecognised option in nosetests config section + + >>> options, args = parse([], StringIO("[nosetests]\nspam=eggs\n")) + error: Error reading config file '': no such option 'spam' + +If there were multiple config files, the error message tells us which +file contains the bad option name or value + + >>> options, args = parse([], [os.path.join(support, "a.cfg"), + ... os.path.join(support, "invalid_value.cfg"), + ... os.path.join(support, "b.cfg")]) + ... # doctest: +ELLIPSIS + error: Error reading config file '...invalid_value.cfg': option 'verbosity': invalid integer value: 'spam' + + +Invalid config files + +(file-like object) + + >>> options, args = parse([], StringIO("spam")) + error: Error reading config file '': File contains no section headers. + file: , line: 1 + 'spam' + +(filename) + + >>> options, args = parse([], os.path.join(support, "invalid.cfg")) + ... # doctest: +ELLIPSIS + error: Error reading config file '...invalid.cfg': File contains no section headers. + file: ...invalid.cfg, line: 1 + 'spam\n' + +(filenames, length == 1) + + >>> options, args = parse([], [os.path.join(support, "invalid.cfg")]) + ... # doctest: +ELLIPSIS + error: Error reading config file '...invalid.cfg': File contains no section headers. + file: ...invalid.cfg, line: 1 + 'spam\n' + +(filenames, length > 1) + +If there were multiple config files, the error message tells us which +file is bad + + >>> options, args = parse([], [os.path.join(support, "a.cfg"), + ... os.path.join(support, "invalid.cfg"), + ... os.path.join(support, "b.cfg")]) + ... # doctest: +ELLIPSIS + error: Error reading config file '...invalid.cfg': File contains no section headers. + file: ...invalid.cfg, line: 1 + 'spam\n' + + +Missing config files don't deserve an error or warning + +(filename) + + >>> options, args = parse([], os.path.join(support, "nonexistent.cfg")) + >>> print options.__dict__ + {'verbosity': 1} + +(filenames) + + >>> options, args = parse([], [os.path.join(support, "nonexistent.cfg")]) + >>> print options.__dict__ + {'verbosity': 1} + + +The same goes for missing config file section ("nosetests") + + >>> options, args = parse([], StringIO("[spam]\nfoo=bar\n")) + >>> print options.__dict__ + {'verbosity': 1} diff --git a/unit_tests/test_core$py.class b/unit_tests/test_core$py.class new file mode 100644 index 0000000000000000000000000000000000000000..3fd7cb6a90776e98847f7f009d79fcf041b296b5 GIT binary patch literal 11906 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" + + existing_loader = getattr(nose, '__loader__', Undefined) + try: + nose.__loader__ = fake_zipimporter() + usage_txt = nose.core.TestProgram.usage() + self.assertEqual(usage_txt, '') + self.assertEqual(requested_data, ['nose%susage.txt' % os.sep]) + finally: + if existing_loader is not Undefined: + nose.__loader__ = existing_loader + else: + del nose.__loader__ + + def test_from_zip_with_prefix(self): + requested_data = [] + + # simulates importing nose from a zip archive + # with a zipimport.zipimporter instance + class fake_zipimporter(object): + + prefix = 'PREFIX' + zipfile = '' + + def get_data(self, path): + requested_data.append(path) + return "" + + existing_loader = getattr(nose, '__loader__', Undefined) + try: + nose.__loader__ = fake_zipimporter() + usage_txt = nose.core.TestProgram.usage() + self.assertEqual(usage_txt, '') + self.assertEqual(requested_data, + ['PREFIX%snose%susage.txt' % (os.sep, os.sep)]) + finally: + if existing_loader is not Undefined: + nose.__loader__ = existing_loader + else: + del nose.__loader__ + +if __name__ == '__main__': + unittest.main() diff --git a/unit_tests/test_core.pyc b/unit_tests/test_core.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1db5b5f01d31b865d1a850f2c85dd16e5784082 GIT binary patch literal 3865 zcmb_f>yi`46+Rd%f(&-PHX{1P-3GsHmOwEl%3^bn<__8y->mWGZ|}I%b?MW zJl!IImvWU!h;*hVgPNp~jO&s&WYCZ(t;u*v@}>-$7+}+~3|1tq%eW=^ zsti^!+mP`Q$=77ChQX4I+mf%#U|sSJ8EjyxDI=V-EPoVvCi2zbsH7`mYmyz45zcKb z7LH50D)xlV9$C!3A?ey7_V!}oO|hGjuFHd(lC^ZG!(xy-U3hwkSNNbhkws56jB-rjPw`?9E{t&vTzu<)lyH^n;`75_RjKs$U%Q z;5b&F#POs|XE}z3{yWe{dxmNnG+|U!QZw>L(C0bZw{OPPtavqN(uq8Y&QWK6hYo!H z`4{jg-1)R2dku~No4P!$348{$WA2$mPnU>sEH))umJEn*2;7Wq05|}>i|{SWeGFG* zZwd2_r*$HEAuf_V#H}rRkcjBj=73amXmGZ_xb+pJ!&qP0Wa4P|_SfaicYN7_vFbE> zJ^)GgB-QRi^{hsOOm*zC#X}V|RMe`V>t$gnqyXHz>)_E{`|$Ug5W3erc5@|C9If2| zem##8wL^T&XDX{3AzZ^122fMu<|!pkM&4G2s%Zohhvf1%zL_O?mj>&Kd5gU_NKTUw zR%R3A02qo1e*$yCTpR)n#!e3*zvFg^mZFxnjoMOl5v>&oij2HsAKw>1*GZG=v z#5zz6p$$0$kQ3NIa!oeB-$yH>)put=34qS9%yT>RuETh^PBQc5B=OlW$@9nV6u9md z_R$m^Hto!4+((@Y=UuPYyO3c~vJXHU6<+PqljbDJPe2xle!D7nt7M`@OC@MTv={qF zUUS0vr1I_$nZNIe>}^FCiujGHDl5;RS9Y537wZ7FvzR@I&2zvSp@_w4RzdNydVC(N zj=q)Xc%<&7YuV~(?(ZPGB7fCVf<(dv_Gk2K@~3dQp{gJykSlO3%y@>%QkN%RhO)x> zc$pLxC2L8xips&#v#1q_1#3kZ|3#|}tjsoK6BUOmze`2-RP=+Elx?u;X^>hhBdiVi z{j%Hx&yJ>A-xJ-VWNhOn*)*F>;h{s95`n|!Iv@+o@d<#3KwS;1(=a>)_5lYj@YC^E zD7FK!qb#?d4EJU6Up>W(4i*52EP%X`BWT6tzGIZGXjnf9*98Su_L~`iWz#rKe4@1| znNDqy!YYwz;_vJ35Ycxrr738B!5*4~u&$uDi~E`~E~7t>WjpFJh6kyt@~0J2{gHxZ zZismgTq^Em(#-{|Zc!>zqgiW4&D#8SN}g!s5^ZhUt0-v2#W6g-B}Fq#Ea?Y}35g1S!& zLScI$8O%k>=#l(5{%=yy0sa+?KR`X}da(Vx`;X{1@AEX~let8qmjMB96qY}ZaPuLV znc}YjZr)Zu0C&s_B@XrAIu;#YCC>xBwSf9_#l-v^%;r}ld|QWnLg!FnGVfq`4sGtb z)>7;A{FOXOvLcSVOs!xP-#msFrCxu-{db$uZwD5*a_Xo2X3Rlz8d@ literal 0 HcmV?d00001 diff --git a/unit_tests/test_deprecated_plugin$py.class b/unit_tests/test_deprecated_plugin$py.class new file mode 100644 index 0000000000000000000000000000000000000000..1a50a092592c6cd929a2cc44058ed36d5eb88be7 GIT binary patch literal 14121 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 VqFwId-^IV?-$u;5Q`@jz@4qI@ebxW~ literal 0 HcmV?d00001 diff --git a/unit_tests/test_doctest_error_handling$py.class b/unit_tests/test_doctest_error_handling$py.class new file mode 100644 index 0000000000000000000000000000000000000000..722e8a6d195f6cc804f9ca2ad764988925b38ea3 GIT binary patch literal 8020 zcmb_h349dg6@M?g$qvf^%Puzr0t5(|O#FbzC-+uwk>i%TwdtdhdzL`xn>~7Fje|(v5zwdo--uu7v z8z217-S-mFV!oWo(^b2OX=mNGq}|<=>dPEVCYqv2+iKd;x7CjHT5F0nb{({>NN0N{ zlCjn-EZW`CxBWnm70s++np}pZk33Aiw3Ug+5*CxUu4Q3|PL)*UColP!Dw1i9MlprX zJRj>#`DirLC`^)eX1km|w{BZcnEMYF=--O zG8&09HN80YuFj&zcnXRhAwZk%gCY~bFPn&EGFCdH(PXC4iDcSpj3yIZv2Kl~G6l|5 zz(>=Wsy4Y4)l7az;jMPEm+9gXBJF<(AhaypU*2ju&7vBCVK$Sl1Wm=W-LV7|nF~ca zlTpzFzvjVky~*eyjTS)GjoIiS3+gRonsTPq@*d$M17`2GG6}d9Q)3A=Mzl`@H43mz z5Mvi)*c3@y8hn^WnG8x-6by^}+VOk17gle-R@D|%n+UG?Qop3pTzMN@Vx!qc@ z^qXdHvNIdEOjDy(c%f;E(DDm@9W2(+N3Q^0TY6I&*ZtE>(?^8)99ln$*3kw?Qp|2^ zHWAIlk_jJeg12{>-SOmsNE|$;jbynkv~?5!I@bxV-KpAU90oMbX`Rk@0K5Q}OGPpV zHEM;-3cPeY7KL_9J*D!1>)hpEU4CaogKnoCVyX*)FQjw>t{T~(JU^YGoo!3v*m~X+{+EGKz>4 z2bgBowT!?+rHoF|NpfLNGM3P&8;2y$OgG+4OZ?Y7=sIw1s?SF;BuW#$TFNEdJgCzl zijSfmLe{7-n{`S$KT}NC{I5Ef*QET;S#YuiCpdQ%(u`#zi)2OIC5WIBEL##8tJ|{I z=ya4W7v1`pdS8BJ&LZ-aBJwNYU~YcV=v7RsMi%2NEUy-puYn8fva@j5*D;NWMiQn| zC$ct{-hgl}_<0Qzr!;yqr06o^k#q(g!7Vzyh2AP@;5x8sOC~Ix-Y$7z9lZl7u>_!- zlARXNY@v4vn(u}g*2Yk-){UGDM#ydD-ce$`plsei@A1=SdN1M{p}ZT4-H1G#u_E^7 zZ^xH_*rYbc{X@Tq3EImFU#y6KGB1$w;SEkhIj~R#huB`jkp5 zv5bXE#5ALBM2mlhj*G?bR5@7AB&^FbQo&9*8+UX6sikyzSFn^s=f+GT>(a-F{GVD&v%3a&Z$ zUZ&Q<76v6Fc{Fk$UZP_jsr?*e2Q|J=KM*y32)FHs#Iu$QuX`OyAF6gB{=(N(|^entt{9_Zc7xMp=g>bqh?B&ffCEUnOK{`E8 ze-iNjobSi8nOMAOgKbCp(mwjD;1!GGLQq#%*lXuoqanx&Qrh3?AAb59{ZkNLsnfq5 zg#UvmHLb%slL{PWua&MwVhIQqb}-EzoWMZy?vlo;gjpso4bT%dTRE=v9(FyCQ+q}_~HFMu<09p16lQD>dT z^Idf*T5mybZJ`l?JE%n!`x|2Dc&Vs*HW@qcRC6VbA#y6s1vJj z=S3QPj=TpgG&79$&L?1_L)RFY)&n&)UV$ofIdAYfG)g>C^-o-!?*DNe9 zSzjdZnef>r zlG#3PL7+zQ5hP=^qq&G%8bTI6ecR=ns-JH5aBgmGw*X7Q-dcc@JOY=MLVh8e-hoy4=gL zG!reQJo*3nY$e`WAx?0KlqoesPuAkhBGQGbwWzcjYK8{;5od;5TanxB=*y$#lVMj}cWv77^fUi6vrQd_^bJL1$KrVgjt{6VdRTA;ytJ1gPBVJK^P;h_s z@nHm=fXXy(8@{-;WqS`;wkjJ4CVa<>9%>cW^|vRpcGMDEGEIfp8N8(8EvLyjhJ zn^7r?libBg?qZcJPH`8fxQjknoaViUX0$bUf-~D2jEVu8gL(66uMwO%73?Q4rkc>z z=IQ32K{tPpE}-60mnW=0gRp*xP&k4t46&!=Xi=~^N6Q*akJ%nv(a=xN@c{irL|BnO z(SVhI8G!{jW(3#bL|Z?-&|v1cg4=R*?~PP#1nShLunRFHUlz{MYxf)@k5O@w-qug= zJPsf$ID^0QX%zmVGXbPT;)7lcKuT2@8pZ;QI?m#62%yiWN+0$3$m?UfQpi|h#<2!2 z236|#ZANglBO6dN0)9x-PaE4#)8@U#U34DQx}Hzb`=IenF8qNB#WXj&G`HkwCKuB@ zQQcdD^9U#-+*8A4Pvw`UjQ3;?NT?CSz1XB?Q0gU>(2$i0~XEFdD3l;G`UF z3j*k6XybCUv(512s3Uk0c<+b91$0FOjUA%kC4#;YG_jDQXq({$VV4k&IfL+^B80Iz z`YF**hjWyH|K_R34u5y>h*0;_72@F?Il5|(5g4aP=F6L3ExRyzn^Bc#5Kz0X$1WB{ z;x}SPNjzS5)Y6+AS?fV0r{VVQ%y~gDPw47=t;?anryNY@D8CsEEOm@A7-KQUVFWP7 zV+4UQ(yHq=TY<26U8510DDQ9(g)n)DUC_(DuS)svMEpg9cJ0;+yNP=~YXlGmF3)Sh zv%myXWh3|@QL>+ISLPW6d>Zyn)3Lp$=#IU?PpWgiJeIO2`KBZIX@!_$gJ_QW+m6vJ zLz77DG6ISWd8H#PSp!yz!b-9TJ||aur+OcF;n5+gfdXSqSAyLQ!BjvmQ0B?e7x3#Q zn%YnIy9-}6f)9eo9H4LAR~*`o8KwY)sTk8rjOi+@HzMp07lgHs!uUtX_{Zov1bRN) z2N$0xAVxL_gELD-bfLe?h5mUX00$}!{Vb)S3;mk1M+l#sJeY>ZgnC7F*{@dT7|Z~Wk&Fx`t zh?lm9D?_}zJzN#MwjG)Frhs1^s#J$qSkm=gsl%;d=a3P+B``tJR29(N> zkTIp*K~?VO#Zt8h|C`jqo7~;mFq7;~JWV}&L%jJEZ_V*}1Kd_Wz&jmgqqv=Sx#W^| z<-e+6&j9ap*fH$3Bo*l#$TOH#z^u&1(sCOs%WbSJx3Rw5#^!Px6?_EZ YSMUrzisuB(4ndiC3-@wDCg*eVzqqzSxc~qF literal 0 HcmV?d00001 diff --git a/unit_tests/test_doctest_error_handling.py b/unit_tests/test_doctest_error_handling.py new file mode 100644 index 0000000..fcdf388 --- /dev/null +++ b/unit_tests/test_doctest_error_handling.py @@ -0,0 +1,40 @@ +import os +import sys +import unittest +from nose.config import Config +from nose.plugins import doctests +from mock import Bucket + +class TestDoctestErrorHandling(unittest.TestCase): + + def setUp(self): + self._path = sys.path[:] + here = os.path.dirname(__file__) + testdir = os.path.join(here, 'support', 'doctest') + sys.path.insert(0, testdir) + p = doctests.Doctest() + p.can_configure = True + p.configure(Bucket(), Config()) + self.p = p + + def tearDown(self): + sys.path = self._path[:] + + def test_no_doctests_in_file(self): + p = self.p + mod = __import__('no_doctests') + loaded = [ t for t in p.loadTestsFromModule(mod) ] + assert not loaded, "Loaded %s from empty module" % loaded + + def test_err_doctests_raises_exception(self): + p = self.p + mod = __import__('err_doctests') + try: + loaded = [ t for t in p.loadTestsFromModule(mod) ] + except ValueError: + pass + else: + self.fail("Error doctests file did not raise ValueError") + +if __name__ == '__main__': + unittest.main() diff --git a/unit_tests/test_doctest_error_handling.pyc b/unit_tests/test_doctest_error_handling.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e43f43c4a0d010471800c3daa28d757f41c509a8 GIT binary patch literal 2031 zcmbVNU2hvj6uq;%c4F5~+XO=SY7J76iz-_|yaP=@Ab}q?rBI`4wDOLViPyW!&KL+d zJT&|WekZ?xd+u&y3-N$(skb3e{GGgZwePKP_3elk?3q|N5I$mjN~p6{ZvV&;wWZsYM}HCq_(@A2kS z-Z`lqKCh~x`ZCGW%;sk|O(N_Q{<_C(Ja@>&DHMms0vUwFePAV`G?GD!;?rS^4@_n_ zA2r`&{&g?ea2%`X{+dXKyWMkQvOy0 z$5lXnkJbU{+pzWy`MZG0@592CSCOy}VorysY2pE$Ta{%|c^NlYSze~$0UIB}VeY{6*bMnw8~+uL z#${!HO1v5SWjW3ZXLMyUlko4+=xFrOc$HhPMNwl|-C(8-^rO!k0gcK_LC6{ZZHasi z`AGHEzPhIlR8Mu(p%QtZIY21#xXS~k3G(nvCTk09!DS+x)eWGl7}UgQO15QV(@K2E z8v>{|Np({EoWI5k4tc0fix@a9X`l0;P75K)orM-#bjD6BNl2lPGx@}dtlvN&0U z`#(OS-`O*wbws_AZzv~a#q#X%C96)#N2L>%Dp6j%!pph0dH!{gq$V9abc1>sB z&@CgmO);+%XccbTL8sdZJMAX&GGX%d5HC}VS5 zRA#F)egx6Q`AbfOXPGye?-XOzn${BDpp_)sF)n Wl}}*gY3i_6JN!KHU-0IGzWN(XO>m+B literal 0 HcmV?d00001 diff --git a/unit_tests/test_doctest_munging.rst b/unit_tests/test_doctest_munging.rst new file mode 100644 index 0000000..fdbce64 --- /dev/null +++ b/unit_tests/test_doctest_munging.rst @@ -0,0 +1,105 @@ +doctest output normalization for plugin testing support +======================================================= + +nose.plugins.plugintest.run() is used for testing nose plugins in +doctests, so it needs to normalise nose output to remove information +that is not of interest to most plugin tests. + +We strip stack trace from formatted exceptions, using a regexp copied +from ``doctest.py``. That regexp always matches to the end of a +string, so we split on blank lines before running the regexp on each +resulting block. + + >>> from nose.plugins.plugintest import blankline_separated_blocks + >>> list(blankline_separated_blocks("spam\neggs\n\nfoo\nbar\n\n")) + ['spam\neggs\n\n', 'foo\nbar\n\n'] + >>> list(blankline_separated_blocks("spam\neggs\n\nfoo\nbar\n")) + ['spam\neggs\n\n', 'foo\nbar\n'] + >>> list(blankline_separated_blocks("spam\neggs\n\nfoo\nbar")) + ['spam\neggs\n\n', 'foo\nbar'] + >>> list(blankline_separated_blocks("")) + [] + >>> list(blankline_separated_blocks("spam")) + ['spam'] + +``remove_stack_traces`` removes the stack traces, replacing them with +an ellipsis. Note the first line here is chosen not to be "Traceback +(most recent...", since doctest would interpret that as meaning that +the example should raise an exception! + + >>> from nose.plugins.plugintest import remove_stack_traces + >>> print remove_stack_traces("""\ + ... Ceci n'est pas une traceback. + ... Traceback (most recent call last): + ... File "/some/dir/foomodule.py", line 15, in runTest + ... File "/some/dir/spam.py", line 293, in who_knows_what + ... AssertionError: something bad happened + ... """) + Ceci n'est pas une traceback. + Traceback (most recent call last): + ... + AssertionError: something bad happened + + +Multiple tracebacks in an example are all replaced, as long as they're +separated by blank lines. + + >>> print remove_stack_traces("""\ + ... Ceci n'est pas une traceback. + ... Traceback (most recent call last): + ... File spam + ... AttributeError: eggs + ... + ... Traceback (most recent call last): + ... File eggs + ... AttributeError: spam + ... """) + Ceci n'est pas une traceback. + Traceback (most recent call last): + ... + AttributeError: eggs + + Traceback (most recent call last): + ... + AttributeError: spam + + + +Putting it together, ``munge_nose_output_for_doctest()`` removes stack +traces, removes test timings from "Ran n test(s)" output, and strips +trailing blank lines. + + >>> from nose.plugins.plugintest import munge_nose_output_for_doctest + >>> print munge_nose_output_for_doctest("""\ + ... runTest (foomodule.PassingTest) ... ok + ... runTest (foomodule.FailingTest) ... FAIL + ... + ... ====================================================================== + ... FAIL: runTest (foomodule.FailingTest) + ... ---------------------------------------------------------------------- + ... Traceback (most recent call last): + ... File "/some/dir/foomodule.py", line 15, in runTest + ... File "/some/dir/spam.py", line 293, in who_knows_what + ... AssertionError: something bad happened + ... + ... ---------------------------------------------------------------------- + ... Ran 1 test in 0.082s + ... + ... FAILED (failures=1) + ... + ... + ... """) + runTest (foomodule.PassingTest) ... ok + runTest (foomodule.FailingTest) ... FAIL + + ====================================================================== + FAIL: runTest (foomodule.FailingTest) + ---------------------------------------------------------------------- + Traceback (most recent call last): + ... + AssertionError: something bad happened + + ---------------------------------------------------------------------- + Ran 1 test in ...s + + FAILED (failures=1) diff --git a/unit_tests/test_id_plugin$py.class b/unit_tests/test_id_plugin$py.class new file mode 100644 index 0000000000000000000000000000000000000000..25407e2ccbdaaf1dc66cf25e2f3fa977182c5b83 GIT binary patch literal 5368 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 Df%e#s literal 0 HcmV?d00001 diff --git a/unit_tests/test_id_plugin.py b/unit_tests/test_id_plugin.py new file mode 100644 index 0000000..d70fc07 --- /dev/null +++ b/unit_tests/test_id_plugin.py @@ -0,0 +1,20 @@ +import unittest +from nose.config import Config +from nose.plugins.builtin import TestId +import mock + +class TestTestIdPlugin(unittest.TestCase): + + def test_default_id_file_is_in_working_dir(self): + tid = TestId() + c = Config() + opt = mock.Bucket() + opt.testIdFile = '.noseids' + tid.configure(opt, c) + print tid.idfile + assert tid.idfile.startswith(c.workingDir), \ + "%s is not under %s" % (tid.idfile, c.workingDir) + + +if __name__ == '__main__': + unittest.main() diff --git a/unit_tests/test_id_plugin.pyc b/unit_tests/test_id_plugin.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37d4d41ccb9c6ba3a505b348a3b9ebe391c85996 GIT binary patch literal 1006 zcmYLHOOMkq5FRJ(w&}LZf)Em%dRdfHt3@1ZSO=g?0J{+K9;|z0 zP)Z-xeJ~+d0CxhJ0@H(E0Ji`i*8{LaFn#zEKy?O+QzDM2Qc3YDKA}O&Sn*6&7gd?L z?a{ur9^V^0rx2^1`5|w&uKW+0i?6uBT@px$34&q>1UFg50eP1jPU;=jf_CpoP%?q} z5dsFH1b{toebJ@KUV$FKJ_I)e()W}`6tEq^+jrgWM(lT|aI2tx1PaGM9CfiSkOlDI zCsPNoibI;a^h1)fvhvm$@9&Xu;it||%L=D$X>2pS@bQEZGARcVxDuTaSH~0bsWyZe zpr|%GiCndtokis2szw}fk&yVti+aw7){V_=O8e#P za`t52mJT(8^mCTl8C}a2&+2dZm>P)J*eq>xi85mCymqBNfYin{%&BLjpd$Lmw9eH~ z3FF8?t;@8qTFdFQE-KSfAD!^)IzkpLxkJ8$7VrglKGaEYXi}s!SI5FEw`KT@5KqbA zJm=niOd?Vf`lI0CWTO58a4N%= literal 0 HcmV?d00001 diff --git a/unit_tests/test_importer$py.class b/unit_tests/test_importer$py.class new file mode 100644 index 0000000000000000000000000000000000000000..11b3a9cde58254a3706829858ef8a28e395f28c4 GIT binary patch literal 7923 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 P%Wa*!ozIhpExhBu2LSuv literal 0 HcmV?d00001 diff --git a/unit_tests/test_importer.py b/unit_tests/test_importer.py new file mode 100644 index 0000000..91de8a9 --- /dev/null +++ b/unit_tests/test_importer.py @@ -0,0 +1,55 @@ +import os +import sys +import unittest +import nose.config +import nose.importer + +class TestImporter(unittest.TestCase): + + def setUp(self): + self.p = sys.path[:] + + def tearDown(self): + sys.path = self.p[:] + + def test_add_paths(self): + where = os.path.abspath(os.path.join(os.path.dirname(__file__), + 'support')) + foo = os.path.join(where, 'foo') + foobar = os.path.join(foo, 'bar') + nose.importer.add_path(foobar) + + assert not foobar in sys.path + assert not foo in sys.path + assert where in sys.path + assert sys.path[0] == where, "%s first should be %s" % (sys.path, where) + + def test_import(self): + where = os.path.abspath(os.path.join(os.path.dirname(__file__), + 'support')) + foo = os.path.join(where, 'foo') + foobar = os.path.join(foo, 'bar') + + imp = nose.importer.Importer() + mod = imp.importFromDir(foobar, 'buz') + assert where in sys.path + # buz has an intra-package import that sets boodle + assert mod.boodle + + def test_module_no_file(self): + where = os.path.abspath(os.path.join(os.path.dirname(__file__), + 'support')) + foo = os.path.join(where, 'foo') + foobar = os.path.join(foo, 'bar') + + # something that's not a real module and has no __file__ + sys.modules['buz'] = 'Whatever' + + imp = nose.importer.Importer() + mod = imp.importFromDir(foobar, 'buz') + assert where in sys.path + # buz has an intra-package import that sets boodle + assert mod.boodle + +if __name__ == '__main__': + unittest.main() diff --git a/unit_tests/test_importer.pyc b/unit_tests/test_importer.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6f7d5fa8ebb11a4a94f24c2efff8d71266cb29f GIT binary patch literal 2249 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 2') + lc = { 'a': 2} + gb = {} + exp = Expander(lc, gb) + + for tok in tokenize.generate_tokens(src.readline): + exp(*tok) + # print "'%s'" % exp.expanded_source + self.assertEqual(exp.expanded_source.strip(), '2 > 2') + + def test_inspect_traceback_continued(self): + a = 6 + out = '' + try: + assert a < 1, \ + "This is a multline expression" + except AssertionError: + et, ev, tb = sys.exc_info() + out = inspect_traceback(tb) + # print "'%s'" % out.strip() + self.assertEqual(out.strip(), + '>> assert 6 < 1, \\\n ' + '"This is a multline expression"') + + def test_get_tb_source_simple(self): + # no func frame + try: + assert False + except AssertionError: + et, ev, tb = sys.exc_info() + lines, lineno = tbsource(tb, 1) + self.assertEqual(''.join(lines).strip(), 'assert False') + self.assertEqual(lineno, 0) + + def test_get_tb_source_func(self): + # func frame + def check_even(n): + print n + assert n % 2 == 0 + try: + check_even(1) + except AssertionError: + et, ev, tb = sys.exc_info() + lines, lineno = tbsource(tb) + out = textwrap.dedent(''.join(lines)) + if sys.version_info < (3,): + first_line = ' print n\n' + else: + first_line = ' print(n)\n' + self.assertEqual(out, + first_line + + ' assert n % 2 == 0\n' + 'try:\n' + ' check_even(1)\n' + 'except AssertionError:\n' + ' et, ev, tb = sys.exc_info()\n' + ) + self.assertEqual(lineno, 3) + + # FIXME 2 func frames + + def test_pick_tb_lines(self): + try: + val = "fred" + def defred(n): + return n.replace('fred','') + assert defred(val) == 'barney', "Fred - fred != barney?" + except AssertionError: + et, ev, tb = sys.exc_info() + out = inspect_traceback(tb) + # print "'%s'" % out.strip() + self.assertEqual(out.strip(), + ">> assert defred('fred') == 'barney', " + '"Fred - fred != barney?"') + try: + val = "fred" + def defred(n): + return n.replace('fred','') + assert defred(val) == 'barney', \ + "Fred - fred != barney?" + def refred(n): + return n + 'fred' + except AssertionError: + et, ev, tb = sys.exc_info() + out = inspect_traceback(tb) + #print "'%s'" % out.strip() + self.assertEqual(out.strip(), + ">> assert defred('fred') == 'barney', " + '\\\n "Fred - fred != barney?"') + + S = {'setup':1} + def check_even(n, nn): + assert S['setup'] + print n, nn + assert n % 2 == 0 or nn % 2 == 0 + try: + check_even(1, 3) + except AssertionError: + et, ev, tb = sys.exc_info() + out = inspect_traceback(tb) + print "'%s'" % out.strip() + if sys.version_info < (3,): + print_line = " print 1, 3\n" + else: + print_line = " print(1, 3)\n" + self.assertEqual(out.strip(), + "assert {'setup': 1}['setup']\n" + + print_line + + ">> assert 1 % 2 == 0 or 3 % 2 == 0") + + def test_bug_95(self): + """Test that inspector can handle multi-line docstrings""" + try: + """docstring line 1 + docstring line 2 + """ + a = 2 + assert a == 4 + except AssertionError: + et, ev, tb = sys.exc_info() + out = inspect_traceback(tb) + print "'%s'" % out.strip() + self.assertEqual(out.strip(), + "2 = 2\n" + ">> assert 2 == 4") + +if __name__ == '__main__': + #import logging + #logging.basicConfig() + #logging.getLogger('').setLevel(10) + unittest.main() + diff --git a/unit_tests/test_inspector.pyc b/unit_tests/test_inspector.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08b3b94540e2417bdc38edb95156dca9af0d4b2f GIT binary patch literal 5018 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