bee_python 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +202 -0
- data/build/README +23 -0
- data/egg/egg/build.yml +70 -0
- data/egg/egg/ez_setup.py +280 -0
- data/egg/egg/script.py +12 -0
- data/egg/egg/setup.erb +11 -0
- data/egg/egg/suite.py +37 -0
- data/egg/egg/test.py +31 -0
- data/egg/egg.yml +69 -0
- data/egg/http/build.erb +53 -0
- data/egg/http/server.py +34 -0
- data/egg/http.yml +50 -0
- data/egg/mysql/mysql.py +43 -0
- data/egg/mysql.yml +44 -0
- data/egg/project/build.erb +77 -0
- data/egg/project/script.py +42 -0
- data/egg/project/test.py +31 -0
- data/egg/project.yml +59 -0
- data/egg/script/build.erb +35 -0
- data/egg/script/script.py +42 -0
- data/egg/script.yml +50 -0
- data/egg/soap/build.erb +52 -0
- data/egg/soap/client.py +18 -0
- data/egg/soap/server.py +30 -0
- data/egg/soap.yml +58 -0
- data/egg/source/source.py +42 -0
- data/egg/source.yml +47 -0
- data/egg/test/test.py +28 -0
- data/egg/test.yml +44 -0
- data/egg/xmlrpc/build.erb +52 -0
- data/egg/xmlrpc/client.py +22 -0
- data/egg/xmlrpc/server.py +24 -0
- data/egg/xmlrpc.yml +51 -0
- data/lib/bee_task_python.rb +390 -0
- data/test/build.yml +16 -0
- data/test/tc_bee_task_python.rb +27 -0
- data/test/test_build.rb +26 -0
- data/test/test_build_listener.rb +62 -0
- data/test/ts_bee_python.rb +26 -0
- data/tools/common/__init__.py +5 -0
- data/tools/common/common/__init__.py +140 -0
- data/tools/common/common/__pkginfo__.py +43 -0
- data/tools/common/common/adbh.py +35 -0
- data/tools/common/common/cache.py +114 -0
- data/tools/common/common/changelog.py +234 -0
- data/tools/common/common/clcommands.py +181 -0
- data/tools/common/common/cli.py +212 -0
- data/tools/common/common/compat.py +328 -0
- data/tools/common/common/configuration.py +1087 -0
- data/tools/common/common/contexts.py +58 -0
- data/tools/common/common/corbautils.py +117 -0
- data/tools/common/common/daemon.py +171 -0
- data/tools/common/common/date.py +279 -0
- data/tools/common/common/db.py +49 -0
- data/tools/common/common/dbf.py +229 -0
- data/tools/common/common/debugger.py +208 -0
- data/tools/common/common/decorators.py +190 -0
- data/tools/common/common/deprecation.py +118 -0
- data/tools/common/common/fileutils.py +409 -0
- data/tools/common/common/graph.py +259 -0
- data/tools/common/common/html.py +142 -0
- data/tools/common/common/interface.py +76 -0
- data/tools/common/common/logging_ext.py +166 -0
- data/tools/common/common/modutils.py +670 -0
- data/tools/common/common/optik_ext.py +383 -0
- data/tools/common/common/optparser.py +92 -0
- data/tools/common/common/pdf_ext.py +111 -0
- data/tools/common/common/proc.py +276 -0
- data/tools/common/common/pyro_ext.py +146 -0
- data/tools/common/common/pytest.py +754 -0
- data/tools/common/common/shellutils.py +383 -0
- data/tools/common/common/sphinx_ext.py +87 -0
- data/tools/common/common/sphinxutils.py +122 -0
- data/tools/common/common/sqlgen.py +31 -0
- data/tools/common/common/table.py +930 -0
- data/tools/common/common/tasksqueue.py +97 -0
- data/tools/common/common/test/__init__.py +1 -0
- data/tools/common/common/test/data/ChangeLog +184 -0
- data/tools/common/common/test/data/MyPyPa-0.1.0-py2.5.egg +0 -0
- data/tools/common/common/test/data/__init__.py +1 -0
- data/tools/common/common/test/data/content_differ_dir/NOTHING +0 -0
- data/tools/common/common/test/data/content_differ_dir/README +1 -0
- data/tools/common/common/test/data/content_differ_dir/subdir/coin +1 -0
- data/tools/common/common/test/data/content_differ_dir/subdir/toto.txt +53 -0
- data/tools/common/common/test/data/file_differ_dir/NOTHING +0 -0
- data/tools/common/common/test/data/file_differ_dir/README +1 -0
- data/tools/common/common/test/data/file_differ_dir/subdir/toto.txt +53 -0
- data/tools/common/common/test/data/file_differ_dir/subdirtwo/Hello +0 -0
- data/tools/common/common/test/data/find_test/__init__.py +0 -0
- data/tools/common/common/test/data/find_test/foo.txt +0 -0
- data/tools/common/common/test/data/find_test/module.py +0 -0
- data/tools/common/common/test/data/find_test/module2.py +0 -0
- data/tools/common/common/test/data/find_test/newlines.txt +0 -0
- data/tools/common/common/test/data/find_test/noendingnewline.py +0 -0
- data/tools/common/common/test/data/find_test/nonregr.py +0 -0
- data/tools/common/common/test/data/find_test/normal_file.txt +0 -0
- data/tools/common/common/test/data/find_test/spam.txt +0 -0
- data/tools/common/common/test/data/find_test/sub/doc.txt +0 -0
- data/tools/common/common/test/data/find_test/sub/momo.py +0 -0
- data/tools/common/common/test/data/find_test/test.ini +0 -0
- data/tools/common/common/test/data/find_test/test1.msg +0 -0
- data/tools/common/common/test/data/find_test/test2.msg +0 -0
- data/tools/common/common/test/data/find_test/write_protected_file.txt +0 -0
- data/tools/common/common/test/data/foo.txt +9 -0
- data/tools/common/common/test/data/module.py +88 -0
- data/tools/common/common/test/data/module2.py +77 -0
- data/tools/common/common/test/data/newlines.txt +3 -0
- data/tools/common/common/test/data/noendingnewline.py +36 -0
- data/tools/common/common/test/data/nonregr.py +14 -0
- data/tools/common/common/test/data/normal_file.txt +0 -0
- data/tools/common/common/test/data/reference_dir/NOTHING +0 -0
- data/tools/common/common/test/data/reference_dir/README +1 -0
- data/tools/common/common/test/data/reference_dir/subdir/coin +1 -0
- data/tools/common/common/test/data/reference_dir/subdir/toto.txt +53 -0
- data/tools/common/common/test/data/same_dir/NOTHING +0 -0
- data/tools/common/common/test/data/same_dir/README +1 -0
- data/tools/common/common/test/data/same_dir/subdir/coin +1 -0
- data/tools/common/common/test/data/same_dir/subdir/toto.txt +53 -0
- data/tools/common/common/test/data/spam.txt +9 -0
- data/tools/common/common/test/data/sub/doc.txt +1 -0
- data/tools/common/common/test/data/sub/momo.py +1 -0
- data/tools/common/common/test/data/subdir_differ_dir/NOTHING +0 -0
- data/tools/common/common/test/data/subdir_differ_dir/README +1 -0
- data/tools/common/common/test/data/subdir_differ_dir/subdir/coin +1 -0
- data/tools/common/common/test/data/subdir_differ_dir/subdir/toto.txt +53 -0
- data/tools/common/common/test/data/test.ini +20 -0
- data/tools/common/common/test/data/test1.msg +30 -0
- data/tools/common/common/test/data/test2.msg +42 -0
- data/tools/common/common/test/data/write_protected_file.txt +0 -0
- data/tools/common/common/test/foomod.py +17 -0
- data/tools/common/common/test/unittest_cache.py +129 -0
- data/tools/common/common/test/unittest_changelog.py +37 -0
- data/tools/common/common/test/unittest_compat.py +239 -0
- data/tools/common/common/test/unittest_configuration.py +348 -0
- data/tools/common/common/test/unittest_date.py +154 -0
- data/tools/common/common/test/unittest_decorators.py +62 -0
- data/tools/common/common/test/unittest_deprecation.py +76 -0
- data/tools/common/common/test/unittest_fileutils.py +133 -0
- data/tools/common/common/test/unittest_graph.py +50 -0
- data/tools/common/common/test/unittest_html.py +76 -0
- data/tools/common/common/test/unittest_interface.py +87 -0
- data/tools/common/common/test/unittest_modutils.py +244 -0
- data/tools/common/common/test/unittest_pytest.py +50 -0
- data/tools/common/common/test/unittest_shellutils.py +248 -0
- data/tools/common/common/test/unittest_table.py +448 -0
- data/tools/common/common/test/unittest_taskqueue.py +71 -0
- data/tools/common/common/test/unittest_testlib.py +956 -0
- data/tools/common/common/test/unittest_textutils.py +247 -0
- data/tools/common/common/test/unittest_tree.py +248 -0
- data/tools/common/common/test/unittest_umessage.py +55 -0
- data/tools/common/common/test/unittest_ureports_html.py +64 -0
- data/tools/common/common/test/unittest_ureports_text.py +105 -0
- data/tools/common/common/test/unittest_xmlutils.py +75 -0
- data/tools/common/common/test/utils.py +87 -0
- data/tools/common/common/testlib.py +1927 -0
- data/tools/common/common/textutils.py +476 -0
- data/tools/common/common/tree.py +372 -0
- data/tools/common/common/umessage.py +161 -0
- data/tools/common/common/ureports/__init__.py +174 -0
- data/tools/common/common/ureports/docbook_writer.py +139 -0
- data/tools/common/common/ureports/html_writer.py +131 -0
- data/tools/common/common/ureports/nodes.py +201 -0
- data/tools/common/common/ureports/text_writer.py +140 -0
- data/tools/common/common/vcgutils.py +216 -0
- data/tools/common/common/visitor.py +107 -0
- data/tools/common/common/xmlrpcutils.py +136 -0
- data/tools/common/common/xmlutils.py +61 -0
- data/tools/compile/compile.py +16 -0
- data/tools/coverage/coverage.py +602 -0
- data/tools/epydoc/__init__.py +227 -0
- data/tools/epydoc/__init__.pyc +0 -0
- data/tools/epydoc/apidoc.py +2203 -0
- data/tools/epydoc/apidoc.pyc +0 -0
- data/tools/epydoc/checker.py +349 -0
- data/tools/epydoc/checker.pyc +0 -0
- data/tools/epydoc/cli.py +1470 -0
- data/tools/epydoc/cli.pyc +0 -0
- data/tools/epydoc/compat.py +250 -0
- data/tools/epydoc/compat.pyc +0 -0
- data/tools/epydoc/docbuilder.py +1358 -0
- data/tools/epydoc/docbuilder.pyc +0 -0
- data/tools/epydoc/docintrospecter.py +1056 -0
- data/tools/epydoc/docintrospecter.pyc +0 -0
- data/tools/epydoc/docparser.py +2113 -0
- data/tools/epydoc/docparser.pyc +0 -0
- data/tools/epydoc/docstringparser.py +1111 -0
- data/tools/epydoc/docstringparser.pyc +0 -0
- data/tools/epydoc/docwriter/__init__.py +12 -0
- data/tools/epydoc/docwriter/__init__.pyc +0 -0
- data/tools/epydoc/docwriter/dotgraph.py +1351 -0
- data/tools/epydoc/docwriter/dotgraph.pyc +0 -0
- data/tools/epydoc/docwriter/html.py +3491 -0
- data/tools/epydoc/docwriter/html.pyc +0 -0
- data/tools/epydoc/docwriter/html_colorize.py +909 -0
- data/tools/epydoc/docwriter/html_colorize.pyc +0 -0
- data/tools/epydoc/docwriter/html_css.py +550 -0
- data/tools/epydoc/docwriter/html_css.pyc +0 -0
- data/tools/epydoc/docwriter/html_help.py +190 -0
- data/tools/epydoc/docwriter/html_help.pyc +0 -0
- data/tools/epydoc/docwriter/latex.py +1187 -0
- data/tools/epydoc/docwriter/latex.pyc +0 -0
- data/tools/epydoc/docwriter/plaintext.py +276 -0
- data/tools/epydoc/docwriter/plaintext.pyc +0 -0
- data/tools/epydoc/docwriter/xlink.py +505 -0
- data/tools/epydoc/docwriter/xlink.pyc +0 -0
- data/tools/epydoc/gui.py +1148 -0
- data/tools/epydoc/gui.pyc +0 -0
- data/tools/epydoc/log.py +204 -0
- data/tools/epydoc/log.pyc +0 -0
- data/tools/epydoc/markup/__init__.py +623 -0
- data/tools/epydoc/markup/__init__.pyc +0 -0
- data/tools/epydoc/markup/doctest.py +311 -0
- data/tools/epydoc/markup/doctest.pyc +0 -0
- data/tools/epydoc/markup/epytext.py +2116 -0
- data/tools/epydoc/markup/epytext.pyc +0 -0
- data/tools/epydoc/markup/javadoc.py +250 -0
- data/tools/epydoc/markup/javadoc.pyc +0 -0
- data/tools/epydoc/markup/plaintext.py +78 -0
- data/tools/epydoc/markup/plaintext.pyc +0 -0
- data/tools/epydoc/markup/pyval_repr.py +532 -0
- data/tools/epydoc/markup/pyval_repr.pyc +0 -0
- data/tools/epydoc/markup/restructuredtext.py +906 -0
- data/tools/epydoc/markup/restructuredtext.pyc +0 -0
- data/tools/epydoc/test/__init__.py +97 -0
- data/tools/epydoc/test/__init__.pyc +0 -0
- data/tools/epydoc/test/util.py +226 -0
- data/tools/epydoc/test/util.pyc +0 -0
- data/tools/epydoc/util.py +289 -0
- data/tools/epydoc/util.pyc +0 -0
- data/tools/logilab/logilab/__init__.py +5 -0
- data/tools/logilab/logilab/astng/__init__.py +82 -0
- data/tools/logilab/logilab/astng/__pkginfo__.py +76 -0
- data/tools/logilab/logilab/astng/_exceptions.py +64 -0
- data/tools/logilab/logilab/astng/_nodes_ast.py +667 -0
- data/tools/logilab/logilab/astng/_nodes_compiler.py +758 -0
- data/tools/logilab/logilab/astng/bases.py +608 -0
- data/tools/logilab/logilab/astng/builder.py +239 -0
- data/tools/logilab/logilab/astng/inference.py +426 -0
- data/tools/logilab/logilab/astng/inspector.py +289 -0
- data/tools/logilab/logilab/astng/manager.py +421 -0
- data/tools/logilab/logilab/astng/mixins.py +165 -0
- data/tools/logilab/logilab/astng/node_classes.py +848 -0
- data/tools/logilab/logilab/astng/nodes.py +85 -0
- data/tools/logilab/logilab/astng/nodes_as_string.py +389 -0
- data/tools/logilab/logilab/astng/patchcomptransformer.py +159 -0
- data/tools/logilab/logilab/astng/protocols.py +333 -0
- data/tools/logilab/logilab/astng/raw_building.py +212 -0
- data/tools/logilab/logilab/astng/rebuilder.py +307 -0
- data/tools/logilab/logilab/astng/scoped_nodes.py +951 -0
- data/tools/logilab/logilab/astng/test/__init__.py +19 -0
- data/tools/logilab/logilab/astng/test/data/MyPyPa-0.1.0-py2.5.egg +0 -0
- data/tools/logilab/logilab/astng/test/data/MyPyPa-0.1.0-py2.5.zip +0 -0
- data/tools/logilab/logilab/astng/test/data/SSL1/Connection1.py +33 -0
- data/tools/logilab/logilab/astng/test/data/SSL1/__init__.py +20 -0
- data/tools/logilab/logilab/astng/test/data/__init__.py +20 -0
- data/tools/logilab/logilab/astng/test/data/all.py +29 -0
- data/tools/logilab/logilab/astng/test/data/appl/__init__.py +23 -0
- data/tools/logilab/logilab/astng/test/data/appl/myConnection.py +30 -0
- data/tools/logilab/logilab/astng/test/data/format.py +34 -0
- data/tools/logilab/logilab/astng/test/data/module.py +90 -0
- data/tools/logilab/logilab/astng/test/data/module2.py +112 -0
- data/tools/logilab/logilab/astng/test/data/noendingnewline.py +57 -0
- data/tools/logilab/logilab/astng/test/data/nonregr.py +76 -0
- data/tools/logilab/logilab/astng/test/data/notall.py +28 -0
- data/tools/logilab/logilab/astng/test/data2/__init__.py +20 -0
- data/tools/logilab/logilab/astng/test/data2/clientmodule_test.py +51 -0
- data/tools/logilab/logilab/astng/test/data2/suppliermodule_test.py +32 -0
- data/tools/logilab/logilab/astng/test/regrtest.py +135 -0
- data/tools/logilab/logilab/astng/test/regrtest_data/absimport.py +22 -0
- data/tools/logilab/logilab/astng/test/regrtest_data/descriptor_crash.py +31 -0
- data/tools/logilab/logilab/astng/test/regrtest_data/import_package_subpackage_module.py +68 -0
- data/tools/logilab/logilab/astng/test/regrtest_data/package/__init__.py +24 -0
- data/tools/logilab/logilab/astng/test/regrtest_data/package/subpackage/__init__.py +20 -0
- data/tools/logilab/logilab/astng/test/regrtest_data/package/subpackage/module.py +20 -0
- data/tools/logilab/logilab/astng/test/unittest_builder.py +684 -0
- data/tools/logilab/logilab/astng/test/unittest_inference.py +1112 -0
- data/tools/logilab/logilab/astng/test/unittest_inspector.py +105 -0
- data/tools/logilab/logilab/astng/test/unittest_lookup.py +302 -0
- data/tools/logilab/logilab/astng/test/unittest_manager.py +98 -0
- data/tools/logilab/logilab/astng/test/unittest_nodes.py +302 -0
- data/tools/logilab/logilab/astng/test/unittest_scoped_nodes.py +501 -0
- data/tools/logilab/logilab/astng/test/unittest_utils.py +104 -0
- data/tools/logilab/logilab/astng/utils.py +342 -0
- data/tools/logilab/logilab/common/__init__.py +140 -0
- data/tools/logilab/logilab/common/__pkginfo__.py +43 -0
- data/tools/logilab/logilab/common/adbh.py +35 -0
- data/tools/logilab/logilab/common/cache.py +114 -0
- data/tools/logilab/logilab/common/changelog.py +234 -0
- data/tools/logilab/logilab/common/clcommands.py +181 -0
- data/tools/logilab/logilab/common/cli.py +212 -0
- data/tools/logilab/logilab/common/compat.py +328 -0
- data/tools/logilab/logilab/common/configuration.py +1087 -0
- data/tools/logilab/logilab/common/contexts.py +58 -0
- data/tools/logilab/logilab/common/corbautils.py +117 -0
- data/tools/logilab/logilab/common/daemon.py +171 -0
- data/tools/logilab/logilab/common/date.py +279 -0
- data/tools/logilab/logilab/common/db.py +49 -0
- data/tools/logilab/logilab/common/dbf.py +229 -0
- data/tools/logilab/logilab/common/debugger.py +208 -0
- data/tools/logilab/logilab/common/decorators.py +190 -0
- data/tools/logilab/logilab/common/deprecation.py +118 -0
- data/tools/logilab/logilab/common/fileutils.py +409 -0
- data/tools/logilab/logilab/common/graph.py +259 -0
- data/tools/logilab/logilab/common/html.py +142 -0
- data/tools/logilab/logilab/common/interface.py +76 -0
- data/tools/logilab/logilab/common/logging_ext.py +166 -0
- data/tools/logilab/logilab/common/modutils.py +670 -0
- data/tools/logilab/logilab/common/optik_ext.py +383 -0
- data/tools/logilab/logilab/common/optparser.py +92 -0
- data/tools/logilab/logilab/common/pdf_ext.py +111 -0
- data/tools/logilab/logilab/common/proc.py +276 -0
- data/tools/logilab/logilab/common/pyro_ext.py +146 -0
- data/tools/logilab/logilab/common/pytest.py +754 -0
- data/tools/logilab/logilab/common/shellutils.py +383 -0
- data/tools/logilab/logilab/common/sphinx_ext.py +87 -0
- data/tools/logilab/logilab/common/sphinxutils.py +122 -0
- data/tools/logilab/logilab/common/sqlgen.py +31 -0
- data/tools/logilab/logilab/common/table.py +930 -0
- data/tools/logilab/logilab/common/tasksqueue.py +97 -0
- data/tools/logilab/logilab/common/test/__init__.py +1 -0
- data/tools/logilab/logilab/common/test/data/ChangeLog +184 -0
- data/tools/logilab/logilab/common/test/data/MyPyPa-0.1.0-py2.5.egg +0 -0
- data/tools/logilab/logilab/common/test/data/__init__.py +1 -0
- data/tools/logilab/logilab/common/test/data/content_differ_dir/NOTHING +0 -0
- data/tools/logilab/logilab/common/test/data/content_differ_dir/README +1 -0
- data/tools/logilab/logilab/common/test/data/content_differ_dir/subdir/coin +1 -0
- data/tools/logilab/logilab/common/test/data/content_differ_dir/subdir/toto.txt +53 -0
- data/tools/logilab/logilab/common/test/data/file_differ_dir/NOTHING +0 -0
- data/tools/logilab/logilab/common/test/data/file_differ_dir/README +1 -0
- data/tools/logilab/logilab/common/test/data/file_differ_dir/subdir/toto.txt +53 -0
- data/tools/logilab/logilab/common/test/data/file_differ_dir/subdirtwo/Hello +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/__init__.py +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/foo.txt +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/module.py +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/module2.py +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/newlines.txt +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/noendingnewline.py +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/nonregr.py +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/normal_file.txt +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/spam.txt +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/sub/doc.txt +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/sub/momo.py +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/test.ini +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/test1.msg +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/test2.msg +0 -0
- data/tools/logilab/logilab/common/test/data/find_test/write_protected_file.txt +0 -0
- data/tools/logilab/logilab/common/test/data/foo.txt +9 -0
- data/tools/logilab/logilab/common/test/data/module.py +88 -0
- data/tools/logilab/logilab/common/test/data/module2.py +77 -0
- data/tools/logilab/logilab/common/test/data/newlines.txt +3 -0
- data/tools/logilab/logilab/common/test/data/noendingnewline.py +36 -0
- data/tools/logilab/logilab/common/test/data/nonregr.py +14 -0
- data/tools/logilab/logilab/common/test/data/normal_file.txt +0 -0
- data/tools/logilab/logilab/common/test/data/reference_dir/NOTHING +0 -0
- data/tools/logilab/logilab/common/test/data/reference_dir/README +1 -0
- data/tools/logilab/logilab/common/test/data/reference_dir/subdir/coin +1 -0
- data/tools/logilab/logilab/common/test/data/reference_dir/subdir/toto.txt +53 -0
- data/tools/logilab/logilab/common/test/data/same_dir/NOTHING +0 -0
- data/tools/logilab/logilab/common/test/data/same_dir/README +1 -0
- data/tools/logilab/logilab/common/test/data/same_dir/subdir/coin +1 -0
- data/tools/logilab/logilab/common/test/data/same_dir/subdir/toto.txt +53 -0
- data/tools/logilab/logilab/common/test/data/spam.txt +9 -0
- data/tools/logilab/logilab/common/test/data/sub/doc.txt +1 -0
- data/tools/logilab/logilab/common/test/data/sub/momo.py +1 -0
- data/tools/logilab/logilab/common/test/data/subdir_differ_dir/NOTHING +0 -0
- data/tools/logilab/logilab/common/test/data/subdir_differ_dir/README +1 -0
- data/tools/logilab/logilab/common/test/data/subdir_differ_dir/subdir/coin +1 -0
- data/tools/logilab/logilab/common/test/data/subdir_differ_dir/subdir/toto.txt +53 -0
- data/tools/logilab/logilab/common/test/data/test.ini +20 -0
- data/tools/logilab/logilab/common/test/data/test1.msg +30 -0
- data/tools/logilab/logilab/common/test/data/test2.msg +42 -0
- data/tools/logilab/logilab/common/test/data/write_protected_file.txt +0 -0
- data/tools/logilab/logilab/common/test/foomod.py +17 -0
- data/tools/logilab/logilab/common/test/unittest_cache.py +129 -0
- data/tools/logilab/logilab/common/test/unittest_changelog.py +37 -0
- data/tools/logilab/logilab/common/test/unittest_compat.py +239 -0
- data/tools/logilab/logilab/common/test/unittest_configuration.py +348 -0
- data/tools/logilab/logilab/common/test/unittest_date.py +154 -0
- data/tools/logilab/logilab/common/test/unittest_decorators.py +62 -0
- data/tools/logilab/logilab/common/test/unittest_deprecation.py +76 -0
- data/tools/logilab/logilab/common/test/unittest_fileutils.py +133 -0
- data/tools/logilab/logilab/common/test/unittest_graph.py +50 -0
- data/tools/logilab/logilab/common/test/unittest_html.py +76 -0
- data/tools/logilab/logilab/common/test/unittest_interface.py +87 -0
- data/tools/logilab/logilab/common/test/unittest_modutils.py +244 -0
- data/tools/logilab/logilab/common/test/unittest_pytest.py +50 -0
- data/tools/logilab/logilab/common/test/unittest_shellutils.py +248 -0
- data/tools/logilab/logilab/common/test/unittest_table.py +448 -0
- data/tools/logilab/logilab/common/test/unittest_taskqueue.py +71 -0
- data/tools/logilab/logilab/common/test/unittest_testlib.py +956 -0
- data/tools/logilab/logilab/common/test/unittest_textutils.py +247 -0
- data/tools/logilab/logilab/common/test/unittest_tree.py +248 -0
- data/tools/logilab/logilab/common/test/unittest_umessage.py +55 -0
- data/tools/logilab/logilab/common/test/unittest_ureports_html.py +64 -0
- data/tools/logilab/logilab/common/test/unittest_ureports_text.py +105 -0
- data/tools/logilab/logilab/common/test/unittest_xmlutils.py +75 -0
- data/tools/logilab/logilab/common/test/utils.py +87 -0
- data/tools/logilab/logilab/common/testlib.py +1927 -0
- data/tools/logilab/logilab/common/textutils.py +476 -0
- data/tools/logilab/logilab/common/tree.py +372 -0
- data/tools/logilab/logilab/common/umessage.py +161 -0
- data/tools/logilab/logilab/common/ureports/__init__.py +174 -0
- data/tools/logilab/logilab/common/ureports/docbook_writer.py +139 -0
- data/tools/logilab/logilab/common/ureports/html_writer.py +131 -0
- data/tools/logilab/logilab/common/ureports/nodes.py +201 -0
- data/tools/logilab/logilab/common/ureports/text_writer.py +140 -0
- data/tools/logilab/logilab/common/vcgutils.py +216 -0
- data/tools/logilab/logilab/common/visitor.py +107 -0
- data/tools/logilab/logilab/common/xmlrpcutils.py +136 -0
- data/tools/logilab/logilab/common/xmlutils.py +61 -0
- data/tools/pychecker/COPYRIGHT +31 -0
- data/tools/pychecker/ChangeLog +349 -0
- data/tools/pychecker/CodeChecks.py +1969 -0
- data/tools/pychecker/CodeChecks.pyc +0 -0
- data/tools/pychecker/CodeChecks.pyo +0 -0
- data/tools/pychecker/Config.py +475 -0
- data/tools/pychecker/Config.pyc +0 -0
- data/tools/pychecker/Config.pyo +0 -0
- data/tools/pychecker/KNOWN_BUGS +100 -0
- data/tools/pychecker/MAINTAINERS +81 -0
- data/tools/pychecker/NEWS +406 -0
- data/tools/pychecker/OP.py +131 -0
- data/tools/pychecker/OP.pyc +0 -0
- data/tools/pychecker/OP.pyo +0 -0
- data/tools/pychecker/OptionTypes.py +117 -0
- data/tools/pychecker/OptionTypes.pyc +0 -0
- data/tools/pychecker/OptionTypes.pyo +0 -0
- data/tools/pychecker/README +152 -0
- data/tools/pychecker/Stack.py +115 -0
- data/tools/pychecker/Stack.pyc +0 -0
- data/tools/pychecker/Stack.pyo +0 -0
- data/tools/pychecker/TODO +101 -0
- data/tools/pychecker/VERSION +1 -0
- data/tools/pychecker/Warning.py +50 -0
- data/tools/pychecker/Warning.pyc +0 -0
- data/tools/pychecker/Warning.pyo +0 -0
- data/tools/pychecker/__init__.py +17 -0
- data/tools/pychecker/__init__.pyc +0 -0
- data/tools/pychecker/__init__.pyo +0 -0
- data/tools/pychecker/checker.py +961 -0
- data/tools/pychecker/checker.pyc +0 -0
- data/tools/pychecker/checker.pyo +0 -0
- data/tools/pychecker/function.py +159 -0
- data/tools/pychecker/function.pyc +0 -0
- data/tools/pychecker/function.pyo +0 -0
- data/tools/pychecker/msgs.py +175 -0
- data/tools/pychecker/msgs.pyc +0 -0
- data/tools/pychecker/msgs.pyo +0 -0
- data/tools/pychecker/options.py +275 -0
- data/tools/pychecker/options.pyc +0 -0
- data/tools/pychecker/options.pyo +0 -0
- data/tools/pychecker/pcmodules.py +19 -0
- data/tools/pychecker/pcmodules.pyc +0 -0
- data/tools/pychecker/pcmodules.pyo +0 -0
- data/tools/pychecker/printer.py +47 -0
- data/tools/pychecker/printer.pyc +0 -0
- data/tools/pychecker/printer.pyo +0 -0
- data/tools/pychecker/python.py +427 -0
- data/tools/pychecker/python.pyc +0 -0
- data/tools/pychecker/python.pyo +0 -0
- data/tools/pychecker/utils.py +102 -0
- data/tools/pychecker/utils.pyc +0 -0
- data/tools/pychecker/utils.pyo +0 -0
- data/tools/pychecker/warn.py +778 -0
- data/tools/pychecker/warn.pyc +0 -0
- data/tools/pychecker/warn.pyo +0 -0
- data/tools/pylint2/pylint/__init__.py +16 -0
- data/tools/pylint2/pylint/__pkginfo__.py +67 -0
- data/tools/pylint2/pylint/checkers/__init__.py +155 -0
- data/tools/pylint2/pylint/checkers/base.py +749 -0
- data/tools/pylint2/pylint/checkers/classes.py +527 -0
- data/tools/pylint2/pylint/checkers/design_analysis.py +344 -0
- data/tools/pylint2/pylint/checkers/exceptions.py +183 -0
- data/tools/pylint2/pylint/checkers/format.py +367 -0
- data/tools/pylint2/pylint/checkers/imports.py +379 -0
- data/tools/pylint2/pylint/checkers/logging.py +98 -0
- data/tools/pylint2/pylint/checkers/misc.py +128 -0
- data/tools/pylint2/pylint/checkers/newstyle.py +107 -0
- data/tools/pylint2/pylint/checkers/raw_metrics.py +125 -0
- data/tools/pylint2/pylint/checkers/similar.py +333 -0
- data/tools/pylint2/pylint/checkers/string_format.py +239 -0
- data/tools/pylint2/pylint/checkers/typecheck.py +364 -0
- data/tools/pylint2/pylint/checkers/utils.py +208 -0
- data/tools/pylint2/pylint/checkers/variables.py +498 -0
- data/tools/pylint2/pylint/config.py +149 -0
- data/tools/pylint2/pylint/epylint.py +149 -0
- data/tools/pylint2/pylint/gui.py +433 -0
- data/tools/pylint2/pylint/interfaces.py +98 -0
- data/tools/pylint2/pylint/lint.py +914 -0
- data/tools/pylint2/pylint/pyreverse/__init__.py +5 -0
- data/tools/pylint2/pylint/pyreverse/diadefslib.py +229 -0
- data/tools/pylint2/pylint/pyreverse/diagrams.py +247 -0
- data/tools/pylint2/pylint/pyreverse/main.py +123 -0
- data/tools/pylint2/pylint/pyreverse/utils.py +131 -0
- data/tools/pylint2/pylint/pyreverse/writer.py +196 -0
- data/tools/pylint2/pylint/reporters/__init__.py +67 -0
- data/tools/pylint2/pylint/reporters/guireporter.py +36 -0
- data/tools/pylint2/pylint/reporters/html.py +69 -0
- data/tools/pylint2/pylint/reporters/text.py +156 -0
- data/tools/pylint2/pylint/utils.py +518 -0
- data/tools/pylint2/pylint.py +16 -0
- data/tools/test/suite.py +35 -0
- metadata +566 -0
@@ -0,0 +1,1927 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
|
3
|
+
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
4
|
+
#
|
5
|
+
# This file is part of logilab-common.
|
6
|
+
#
|
7
|
+
# logilab-common is free software: you can redistribute it and/or modify it under
|
8
|
+
# the terms of the GNU Lesser General Public License as published by the Free
|
9
|
+
# Software Foundation, either version 2.1 of the License, or (at your option) any
|
10
|
+
# later version.
|
11
|
+
#
|
12
|
+
# logilab-common is distributed in the hope that it will be useful, but WITHOUT
|
13
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
14
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
15
|
+
# details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License along
|
18
|
+
# with logilab-common. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
"""Run tests.
|
20
|
+
|
21
|
+
This will find all modules whose name match a given prefix in the test
|
22
|
+
directory, and run them. Various command line options provide
|
23
|
+
additional facilities.
|
24
|
+
|
25
|
+
Command line options:
|
26
|
+
|
27
|
+
-v verbose -- run tests in verbose mode with output to stdout
|
28
|
+
-q quiet -- don't print anything except if a test fails
|
29
|
+
-t testdir -- directory where the tests will be found
|
30
|
+
-x exclude -- add a test to exclude
|
31
|
+
-p profile -- profiled execution
|
32
|
+
-c capture -- capture standard out/err during tests
|
33
|
+
-d dbc -- enable design-by-contract
|
34
|
+
-m match -- only run test matching the tag pattern which follow
|
35
|
+
|
36
|
+
If no non-option arguments are present, prefixes used are 'test',
|
37
|
+
'regrtest', 'smoketest' and 'unittest'.
|
38
|
+
|
39
|
+
"""
|
40
|
+
__docformat__ = "restructuredtext en"
|
41
|
+
# modified copy of some functions from test/regrtest.py from PyXml
|
42
|
+
# disable camel case warning
|
43
|
+
# pylint: disable-msg=C0103
|
44
|
+
|
45
|
+
import sys
|
46
|
+
import os, os.path as osp
|
47
|
+
import re
|
48
|
+
import time
|
49
|
+
import getopt
|
50
|
+
import traceback
|
51
|
+
import inspect
|
52
|
+
import unittest
|
53
|
+
import difflib
|
54
|
+
import types
|
55
|
+
import tempfile
|
56
|
+
import math
|
57
|
+
from shutil import rmtree
|
58
|
+
from operator import itemgetter
|
59
|
+
import warnings
|
60
|
+
from compiler.consts import CO_GENERATOR
|
61
|
+
from ConfigParser import ConfigParser
|
62
|
+
from itertools import dropwhile
|
63
|
+
from functools import wraps
|
64
|
+
|
65
|
+
try:
|
66
|
+
from test import test_support
|
67
|
+
except ImportError:
|
68
|
+
# not always available
|
69
|
+
class TestSupport:
|
70
|
+
def unload(self, test):
|
71
|
+
pass
|
72
|
+
test_support = TestSupport()
|
73
|
+
|
74
|
+
# pylint: disable-msg=W0622
|
75
|
+
from logilab.common.compat import set, enumerate, any, sorted
|
76
|
+
# pylint: enable-msg=W0622
|
77
|
+
from logilab.common.modutils import load_module_from_name
|
78
|
+
from logilab.common.debugger import Debugger, colorize_source
|
79
|
+
from logilab.common.decorators import cached, classproperty
|
80
|
+
from logilab.common import textutils
|
81
|
+
|
82
|
+
|
83
|
+
__all__ = ['main', 'unittest_main', 'find_tests', 'run_test', 'spawn']
|
84
|
+
|
85
|
+
DEFAULT_PREFIXES = ('test', 'regrtest', 'smoketest', 'unittest',
|
86
|
+
'func', 'validation')
|
87
|
+
|
88
|
+
ENABLE_DBC = False
|
89
|
+
|
90
|
+
FILE_RESTART = ".pytest.restart"
|
91
|
+
|
92
|
+
# used by unittest to count the number of relevant levels in the traceback
|
93
|
+
__unittest = 1
|
94
|
+
|
95
|
+
|
96
|
+
def with_tempdir(callable):
|
97
|
+
"""A decorator ensuring no temporary file left when the function return
|
98
|
+
Work only for temporary file create with the tempfile module"""
|
99
|
+
@wraps(callable)
|
100
|
+
def proxy(*args, **kargs):
|
101
|
+
|
102
|
+
old_tmpdir = tempfile.gettempdir()
|
103
|
+
new_tmpdir = tempfile.mkdtemp(prefix="temp-lgc-")
|
104
|
+
tempfile.tempdir = new_tmpdir
|
105
|
+
try:
|
106
|
+
return callable(*args, **kargs)
|
107
|
+
finally:
|
108
|
+
try:
|
109
|
+
rmtree(new_tmpdir, ignore_errors=True)
|
110
|
+
finally:
|
111
|
+
tempfile.tempdir = old_tmpdir
|
112
|
+
return proxy
|
113
|
+
|
114
|
+
def in_tempdir(callable):
|
115
|
+
"""A decorator moving the enclosed function inside the tempfile.tempfdir
|
116
|
+
"""
|
117
|
+
@wraps(callable)
|
118
|
+
def proxy(*args, **kargs):
|
119
|
+
|
120
|
+
old_cwd = os.getcwd()
|
121
|
+
os.chdir(tempfile.tempdir)
|
122
|
+
try:
|
123
|
+
return callable(*args, **kargs)
|
124
|
+
finally:
|
125
|
+
os.chdir(old_cwd)
|
126
|
+
return proxy
|
127
|
+
|
128
|
+
def within_tempdir(callable):
|
129
|
+
"""A decorator run the enclosed function inside a tmpdir removed after execution
|
130
|
+
"""
|
131
|
+
proxy = with_tempdir(in_tempdir(callable))
|
132
|
+
proxy.__name__ = callable.__name__
|
133
|
+
return proxy
|
134
|
+
|
135
|
+
def run_tests(tests, quiet, verbose, runner=None, capture=0):
|
136
|
+
"""Execute a list of tests.
|
137
|
+
|
138
|
+
:rtype: tuple
|
139
|
+
:return: tuple (list of passed tests, list of failed tests, list of skipped tests)
|
140
|
+
"""
|
141
|
+
good = []
|
142
|
+
bad = []
|
143
|
+
skipped = []
|
144
|
+
all_result = None
|
145
|
+
for test in tests:
|
146
|
+
if not quiet:
|
147
|
+
print
|
148
|
+
print '-'*80
|
149
|
+
print "Executing", test
|
150
|
+
result = run_test(test, verbose, runner, capture)
|
151
|
+
if type(result) is type(''):
|
152
|
+
# an unexpected error occurred
|
153
|
+
skipped.append( (test, result))
|
154
|
+
else:
|
155
|
+
if all_result is None:
|
156
|
+
all_result = result
|
157
|
+
else:
|
158
|
+
all_result.testsRun += result.testsRun
|
159
|
+
all_result.failures += result.failures
|
160
|
+
all_result.errors += result.errors
|
161
|
+
all_result.skipped += result.skipped
|
162
|
+
if result.errors or result.failures:
|
163
|
+
bad.append(test)
|
164
|
+
if verbose:
|
165
|
+
print "test", test, \
|
166
|
+
"failed -- %s errors, %s failures" % (
|
167
|
+
len(result.errors), len(result.failures))
|
168
|
+
else:
|
169
|
+
good.append(test)
|
170
|
+
|
171
|
+
return good, bad, skipped, all_result
|
172
|
+
|
173
|
+
def find_tests(testdir,
|
174
|
+
prefixes=DEFAULT_PREFIXES, suffix=".py",
|
175
|
+
excludes=(),
|
176
|
+
remove_suffix=True):
|
177
|
+
"""
|
178
|
+
Return a list of all applicable test modules.
|
179
|
+
"""
|
180
|
+
tests = []
|
181
|
+
for name in os.listdir(testdir):
|
182
|
+
if not suffix or name.endswith(suffix):
|
183
|
+
for prefix in prefixes:
|
184
|
+
if name.startswith(prefix):
|
185
|
+
if remove_suffix and name.endswith(suffix):
|
186
|
+
name = name[:-len(suffix)]
|
187
|
+
if name not in excludes:
|
188
|
+
tests.append(name)
|
189
|
+
tests.sort()
|
190
|
+
return tests
|
191
|
+
|
192
|
+
|
193
|
+
def run_test(test, verbose, runner=None, capture=0):
|
194
|
+
"""
|
195
|
+
Run a single test.
|
196
|
+
|
197
|
+
test -- the name of the test
|
198
|
+
verbose -- if true, print more messages
|
199
|
+
"""
|
200
|
+
test_support.unload(test)
|
201
|
+
try:
|
202
|
+
m = load_module_from_name(test, path=sys.path)
|
203
|
+
# m = __import__(test, globals(), locals(), sys.path)
|
204
|
+
try:
|
205
|
+
suite = m.suite
|
206
|
+
if callable(suite):
|
207
|
+
suite = suite()
|
208
|
+
except AttributeError:
|
209
|
+
loader = unittest.TestLoader()
|
210
|
+
suite = loader.loadTestsFromModule(m)
|
211
|
+
if runner is None:
|
212
|
+
runner = SkipAwareTextTestRunner(capture=capture) # verbosity=0)
|
213
|
+
return runner.run(suite)
|
214
|
+
except KeyboardInterrupt, v:
|
215
|
+
raise KeyboardInterrupt, v, sys.exc_info()[2]
|
216
|
+
except:
|
217
|
+
# raise
|
218
|
+
type, value = sys.exc_info()[:2]
|
219
|
+
msg = "test %s crashed -- %s : %s" % (test, type, value)
|
220
|
+
if verbose:
|
221
|
+
traceback.print_exc()
|
222
|
+
return msg
|
223
|
+
|
224
|
+
def _count(n, word):
|
225
|
+
"""format word according to n"""
|
226
|
+
if n == 1:
|
227
|
+
return "%d %s" % (n, word)
|
228
|
+
else:
|
229
|
+
return "%d %ss" % (n, word)
|
230
|
+
|
231
|
+
|
232
|
+
|
233
|
+
|
234
|
+
## PostMortem Debug facilities #####
|
235
|
+
def start_interactive_mode(result):
|
236
|
+
"""starts an interactive shell so that the user can inspect errors
|
237
|
+
"""
|
238
|
+
debuggers = result.debuggers
|
239
|
+
descrs = result.error_descrs + result.fail_descrs
|
240
|
+
if len(debuggers) == 1:
|
241
|
+
# don't ask for test name if there's only one failure
|
242
|
+
debuggers[0].start()
|
243
|
+
else:
|
244
|
+
while True:
|
245
|
+
testindex = 0
|
246
|
+
print "Choose a test to debug:"
|
247
|
+
# order debuggers in the same way than errors were printed
|
248
|
+
print "\n".join(['\t%s : %s' % (i, descr) for i, (_, descr)
|
249
|
+
in enumerate(descrs)])
|
250
|
+
print "Type 'exit' (or ^D) to quit"
|
251
|
+
print
|
252
|
+
try:
|
253
|
+
todebug = raw_input('Enter a test name: ')
|
254
|
+
if todebug.strip().lower() == 'exit':
|
255
|
+
print
|
256
|
+
break
|
257
|
+
else:
|
258
|
+
try:
|
259
|
+
testindex = int(todebug)
|
260
|
+
debugger = debuggers[descrs[testindex][0]]
|
261
|
+
except (ValueError, IndexError):
|
262
|
+
print "ERROR: invalid test number %r" % (todebug, )
|
263
|
+
else:
|
264
|
+
debugger.start()
|
265
|
+
except (EOFError, KeyboardInterrupt):
|
266
|
+
print
|
267
|
+
break
|
268
|
+
|
269
|
+
|
270
|
+
# test utils ##################################################################
|
271
|
+
from cStringIO import StringIO
|
272
|
+
|
273
|
+
class SkipAwareTestResult(unittest._TextTestResult):
|
274
|
+
|
275
|
+
def __init__(self, stream, descriptions, verbosity,
|
276
|
+
exitfirst=False, capture=0, printonly=None,
|
277
|
+
pdbmode=False, cvg=None, colorize=False):
|
278
|
+
super(SkipAwareTestResult, self).__init__(stream,
|
279
|
+
descriptions, verbosity)
|
280
|
+
self.skipped = []
|
281
|
+
self.debuggers = []
|
282
|
+
self.fail_descrs = []
|
283
|
+
self.error_descrs = []
|
284
|
+
self.exitfirst = exitfirst
|
285
|
+
self.capture = capture
|
286
|
+
self.printonly = printonly
|
287
|
+
self.pdbmode = pdbmode
|
288
|
+
self.cvg = cvg
|
289
|
+
self.colorize = colorize
|
290
|
+
self.pdbclass = Debugger
|
291
|
+
self.verbose = verbosity > 1
|
292
|
+
|
293
|
+
def descrs_for(self, flavour):
|
294
|
+
return getattr(self, '%s_descrs' % flavour.lower())
|
295
|
+
|
296
|
+
def _create_pdb(self, test_descr, flavour):
|
297
|
+
self.descrs_for(flavour).append( (len(self.debuggers), test_descr) )
|
298
|
+
if self.pdbmode:
|
299
|
+
self.debuggers.append(self.pdbclass(sys.exc_info()[2]))
|
300
|
+
|
301
|
+
|
302
|
+
def _iter_valid_frames(self, frames):
|
303
|
+
"""only consider non-testlib frames when formatting traceback"""
|
304
|
+
lgc_testlib = osp.abspath(__file__)
|
305
|
+
std_testlib = osp.abspath(unittest.__file__)
|
306
|
+
invalid = lambda fi: osp.abspath(fi[1]) in (lgc_testlib, std_testlib)
|
307
|
+
for frameinfo in dropwhile(invalid, frames):
|
308
|
+
yield frameinfo
|
309
|
+
|
310
|
+
def _exc_info_to_string(self, err, test):
|
311
|
+
"""Converts a sys.exc_info()-style tuple of values into a string.
|
312
|
+
|
313
|
+
This method is overridden here because we want to colorize
|
314
|
+
lines if --color is passed, and display local variables if
|
315
|
+
--verbose is passed
|
316
|
+
"""
|
317
|
+
exctype, exc, tb = err
|
318
|
+
output = ['Traceback (most recent call last)']
|
319
|
+
frames = inspect.getinnerframes(tb)
|
320
|
+
colorize = self.colorize
|
321
|
+
frames = enumerate(self._iter_valid_frames(frames))
|
322
|
+
for index, (frame, filename, lineno, funcname, ctx, ctxindex) in frames:
|
323
|
+
filename = osp.abspath(filename)
|
324
|
+
if ctx is None: # pyc files or C extensions for instance
|
325
|
+
source = '<no source available>'
|
326
|
+
else:
|
327
|
+
source = ''.join(ctx)
|
328
|
+
if colorize:
|
329
|
+
filename = textutils.colorize_ansi(filename, 'magenta')
|
330
|
+
source = colorize_source(source)
|
331
|
+
output.append(' File "%s", line %s, in %s' % (filename, lineno, funcname))
|
332
|
+
output.append(' %s' % source.strip())
|
333
|
+
if self.verbose:
|
334
|
+
output.append('%r == %r' % (dir(frame), test.__module__))
|
335
|
+
output.append('')
|
336
|
+
output.append(' ' + ' local variables '.center(66, '-'))
|
337
|
+
for varname, value in sorted(frame.f_locals.items()):
|
338
|
+
output.append(' %s: %r' % (varname, value))
|
339
|
+
if varname == 'self': # special handy processing for self
|
340
|
+
for varname, value in sorted(vars(value).items()):
|
341
|
+
output.append(' self.%s: %r' % (varname, value))
|
342
|
+
output.append(' ' + '-' * 66)
|
343
|
+
output.append('')
|
344
|
+
output.append(''.join(traceback.format_exception_only(exctype, exc)))
|
345
|
+
return '\n'.join(output)
|
346
|
+
|
347
|
+
def addError(self, test, err):
|
348
|
+
"""err == (exc_type, exc, tcbk)"""
|
349
|
+
exc_type, exc, _ = err #
|
350
|
+
if exc_type == TestSkipped:
|
351
|
+
self.addSkipped(test, exc)
|
352
|
+
else:
|
353
|
+
if self.exitfirst:
|
354
|
+
self.shouldStop = True
|
355
|
+
descr = self.getDescription(test)
|
356
|
+
super(SkipAwareTestResult, self).addError(test, err)
|
357
|
+
self._create_pdb(descr, 'error')
|
358
|
+
|
359
|
+
def addFailure(self, test, err):
|
360
|
+
if self.exitfirst:
|
361
|
+
self.shouldStop = True
|
362
|
+
descr = self.getDescription(test)
|
363
|
+
super(SkipAwareTestResult, self).addFailure(test, err)
|
364
|
+
self._create_pdb(descr, 'fail')
|
365
|
+
|
366
|
+
def addSkipped(self, test, reason):
|
367
|
+
self.skipped.append((test, self.getDescription(test), reason))
|
368
|
+
if self.showAll:
|
369
|
+
self.stream.writeln("SKIPPED")
|
370
|
+
elif self.dots:
|
371
|
+
self.stream.write('S')
|
372
|
+
|
373
|
+
def printErrors(self):
|
374
|
+
super(SkipAwareTestResult, self).printErrors()
|
375
|
+
self.printSkippedList()
|
376
|
+
|
377
|
+
def printSkippedList(self):
|
378
|
+
for _, descr, err in self.skipped: # test, descr, err
|
379
|
+
self.stream.writeln(self.separator1)
|
380
|
+
self.stream.writeln("%s: %s" % ('SKIPPED', descr))
|
381
|
+
self.stream.writeln("\t%s" % err)
|
382
|
+
|
383
|
+
def printErrorList(self, flavour, errors):
|
384
|
+
for (_, descr), (test, err) in zip(self.descrs_for(flavour), errors):
|
385
|
+
self.stream.writeln(self.separator1)
|
386
|
+
if self.colorize:
|
387
|
+
self.stream.writeln("%s: %s" % (
|
388
|
+
textutils.colorize_ansi(flavour, color='red'), descr))
|
389
|
+
else:
|
390
|
+
self.stream.writeln("%s: %s" % (flavour, descr))
|
391
|
+
|
392
|
+
self.stream.writeln(self.separator2)
|
393
|
+
self.stream.writeln(err)
|
394
|
+
try:
|
395
|
+
output, errput = test.captured_output()
|
396
|
+
except AttributeError:
|
397
|
+
pass # original unittest
|
398
|
+
else:
|
399
|
+
if output:
|
400
|
+
self.stream.writeln(self.separator2)
|
401
|
+
self.stream.writeln("captured stdout".center(
|
402
|
+
len(self.separator2)))
|
403
|
+
self.stream.writeln(self.separator2)
|
404
|
+
self.stream.writeln(output)
|
405
|
+
else:
|
406
|
+
self.stream.writeln('no stdout'.center(
|
407
|
+
len(self.separator2)))
|
408
|
+
if errput:
|
409
|
+
self.stream.writeln(self.separator2)
|
410
|
+
self.stream.writeln("captured stderr".center(
|
411
|
+
len(self.separator2)))
|
412
|
+
self.stream.writeln(self.separator2)
|
413
|
+
self.stream.writeln(errput)
|
414
|
+
else:
|
415
|
+
self.stream.writeln('no stderr'.center(
|
416
|
+
len(self.separator2)))
|
417
|
+
|
418
|
+
|
419
|
+
def run(self, result, runcondition=None, options=None):
|
420
|
+
for test in self._tests:
|
421
|
+
if result.shouldStop:
|
422
|
+
break
|
423
|
+
try:
|
424
|
+
test(result, runcondition, options)
|
425
|
+
except TypeError:
|
426
|
+
# this might happen if a raw unittest.TestCase is defined
|
427
|
+
# and used with python (and not pytest)
|
428
|
+
warnings.warn("%s should extend lgc.testlib.TestCase instead of unittest.TestCase"
|
429
|
+
% test)
|
430
|
+
test(result)
|
431
|
+
return result
|
432
|
+
unittest.TestSuite.run = run
|
433
|
+
|
434
|
+
# backward compatibility: TestSuite might be imported from lgc.testlib
|
435
|
+
TestSuite = unittest.TestSuite
|
436
|
+
|
437
|
+
# python2.3 compat
|
438
|
+
def __call__(self, *args, **kwds):
|
439
|
+
return self.run(*args, **kwds)
|
440
|
+
unittest.TestSuite.__call__ = __call__
|
441
|
+
|
442
|
+
|
443
|
+
class SkipAwareTextTestRunner(unittest.TextTestRunner):
|
444
|
+
|
445
|
+
def __init__(self, stream=sys.stderr, verbosity=1,
|
446
|
+
exitfirst=False, capture=False, printonly=None,
|
447
|
+
pdbmode=False, cvg=None, test_pattern=None,
|
448
|
+
skipped_patterns=(), colorize=False, batchmode=False,
|
449
|
+
options=None):
|
450
|
+
super(SkipAwareTextTestRunner, self).__init__(stream=stream,
|
451
|
+
verbosity=verbosity)
|
452
|
+
self.exitfirst = exitfirst
|
453
|
+
self.capture = capture
|
454
|
+
self.printonly = printonly
|
455
|
+
self.pdbmode = pdbmode
|
456
|
+
self.cvg = cvg
|
457
|
+
self.test_pattern = test_pattern
|
458
|
+
self.skipped_patterns = skipped_patterns
|
459
|
+
self.colorize = colorize
|
460
|
+
self.batchmode = batchmode
|
461
|
+
self.options = options
|
462
|
+
|
463
|
+
def _this_is_skipped(self, testedname):
|
464
|
+
return any([(pat in testedname) for pat in self.skipped_patterns])
|
465
|
+
|
466
|
+
def _runcondition(self, test, skipgenerator=True):
|
467
|
+
if isinstance(test, InnerTest):
|
468
|
+
testname = test.name
|
469
|
+
else:
|
470
|
+
if isinstance(test, TestCase):
|
471
|
+
meth = test._get_test_method()
|
472
|
+
func = meth.im_func
|
473
|
+
testname = '%s.%s' % (meth.im_class.__name__, func.__name__)
|
474
|
+
elif isinstance(test, types.FunctionType):
|
475
|
+
func = test
|
476
|
+
testname = func.__name__
|
477
|
+
elif isinstance(test, types.MethodType):
|
478
|
+
func = test.im_func
|
479
|
+
testname = '%s.%s' % (test.im_class.__name__, func.__name__)
|
480
|
+
else:
|
481
|
+
return True # Not sure when this happens
|
482
|
+
|
483
|
+
if is_generator(func) and skipgenerator:
|
484
|
+
return self.does_match_tags(func) # Let inner tests decide at run time
|
485
|
+
|
486
|
+
# print 'testname', testname, self.test_pattern
|
487
|
+
if self._this_is_skipped(testname):
|
488
|
+
return False # this was explicitly skipped
|
489
|
+
if self.test_pattern is not None:
|
490
|
+
try:
|
491
|
+
classpattern, testpattern = self.test_pattern.split('.')
|
492
|
+
klass, name = testname.split('.')
|
493
|
+
if classpattern not in klass or testpattern not in name:
|
494
|
+
return False
|
495
|
+
except ValueError:
|
496
|
+
if self.test_pattern not in testname:
|
497
|
+
return False
|
498
|
+
|
499
|
+
return self.does_match_tags(test)
|
500
|
+
|
501
|
+
def does_match_tags(self, test):
|
502
|
+
if self.options is not None:
|
503
|
+
tags_pattern = getattr(self.options, 'tags_pattern', None)
|
504
|
+
if tags_pattern is not None:
|
505
|
+
tags = getattr(test, 'tags', None)
|
506
|
+
if tags is not None:
|
507
|
+
return tags.match(tags_pattern)
|
508
|
+
if isinstance(test, types.MethodType):
|
509
|
+
tags = getattr(test.im_class, 'tags', Tags())
|
510
|
+
return tags.match(tags_pattern)
|
511
|
+
return False
|
512
|
+
return True # no pattern
|
513
|
+
|
514
|
+
def _makeResult(self):
|
515
|
+
return SkipAwareTestResult(self.stream, self.descriptions,
|
516
|
+
self.verbosity, self.exitfirst, self.capture,
|
517
|
+
self.printonly, self.pdbmode, self.cvg,
|
518
|
+
self.colorize)
|
519
|
+
|
520
|
+
def run(self, test):
|
521
|
+
"Run the given test case or test suite."
|
522
|
+
result = self._makeResult()
|
523
|
+
startTime = time.time()
|
524
|
+
test(result, self._runcondition, self.options)
|
525
|
+
stopTime = time.time()
|
526
|
+
timeTaken = stopTime - startTime
|
527
|
+
result.printErrors()
|
528
|
+
if not self.batchmode:
|
529
|
+
self.stream.writeln(result.separator2)
|
530
|
+
run = result.testsRun
|
531
|
+
self.stream.writeln("Ran %d test%s in %.3fs" %
|
532
|
+
(run, run != 1 and "s" or "", timeTaken))
|
533
|
+
self.stream.writeln()
|
534
|
+
if not result.wasSuccessful():
|
535
|
+
if self.colorize:
|
536
|
+
self.stream.write(textutils.colorize_ansi("FAILED", color='red'))
|
537
|
+
else:
|
538
|
+
self.stream.write("FAILED")
|
539
|
+
else:
|
540
|
+
if self.colorize:
|
541
|
+
self.stream.write(textutils.colorize_ansi("OK", color='green'))
|
542
|
+
else:
|
543
|
+
self.stream.write("OK")
|
544
|
+
failed, errored, skipped = map(len, (result.failures, result.errors,
|
545
|
+
result.skipped))
|
546
|
+
|
547
|
+
det_results = []
|
548
|
+
for name, value in (("failures", result.failures),
|
549
|
+
("errors",result.errors),
|
550
|
+
("skipped", result.skipped)):
|
551
|
+
if value:
|
552
|
+
det_results.append("%s=%i" % (name, len(value)))
|
553
|
+
if det_results:
|
554
|
+
self.stream.write(" (")
|
555
|
+
self.stream.write(', '.join(det_results))
|
556
|
+
self.stream.write(")")
|
557
|
+
self.stream.writeln("")
|
558
|
+
return result
|
559
|
+
|
560
|
+
|
561
|
+
class keywords(dict):
|
562
|
+
"""Keyword args (**kwargs) support for generative tests."""
|
563
|
+
|
564
|
+
class starargs(tuple):
|
565
|
+
"""Variable arguments (*args) for generative tests."""
|
566
|
+
def __new__(cls, *args):
|
567
|
+
return tuple.__new__(cls, args)
|
568
|
+
|
569
|
+
|
570
|
+
|
571
|
+
class NonStrictTestLoader(unittest.TestLoader):
|
572
|
+
"""
|
573
|
+
Overrides default testloader to be able to omit classname when
|
574
|
+
specifying tests to run on command line.
|
575
|
+
|
576
|
+
For example, if the file test_foo.py contains ::
|
577
|
+
|
578
|
+
class FooTC(TestCase):
|
579
|
+
def test_foo1(self): # ...
|
580
|
+
def test_foo2(self): # ...
|
581
|
+
def test_bar1(self): # ...
|
582
|
+
|
583
|
+
class BarTC(TestCase):
|
584
|
+
def test_bar2(self): # ...
|
585
|
+
|
586
|
+
'python test_foo.py' will run the 3 tests in FooTC
|
587
|
+
'python test_foo.py FooTC' will run the 3 tests in FooTC
|
588
|
+
'python test_foo.py test_foo' will run test_foo1 and test_foo2
|
589
|
+
'python test_foo.py test_foo1' will run test_foo1
|
590
|
+
'python test_foo.py test_bar' will run FooTC.test_bar1 and BarTC.test_bar2
|
591
|
+
"""
|
592
|
+
|
593
|
+
def __init__(self):
|
594
|
+
self.skipped_patterns = []
|
595
|
+
|
596
|
+
def loadTestsFromNames(self, names, module=None):
|
597
|
+
suites = []
|
598
|
+
for name in names:
|
599
|
+
suites.extend(self.loadTestsFromName(name, module))
|
600
|
+
return self.suiteClass(suites)
|
601
|
+
|
602
|
+
def _collect_tests(self, module):
|
603
|
+
tests = {}
|
604
|
+
for obj in vars(module).values():
|
605
|
+
if (issubclass(type(obj), (types.ClassType, type)) and
|
606
|
+
issubclass(obj, unittest.TestCase)):
|
607
|
+
classname = obj.__name__
|
608
|
+
if classname[0] == '_' or self._this_is_skipped(classname):
|
609
|
+
continue
|
610
|
+
methodnames = []
|
611
|
+
# obj is a TestCase class
|
612
|
+
for attrname in dir(obj):
|
613
|
+
if attrname.startswith(self.testMethodPrefix):
|
614
|
+
attr = getattr(obj, attrname)
|
615
|
+
if callable(attr):
|
616
|
+
methodnames.append(attrname)
|
617
|
+
# keep track of class (obj) for convenience
|
618
|
+
tests[classname] = (obj, methodnames)
|
619
|
+
return tests
|
620
|
+
|
621
|
+
def loadTestsFromSuite(self, module, suitename):
|
622
|
+
try:
|
623
|
+
suite = getattr(module, suitename)()
|
624
|
+
except AttributeError:
|
625
|
+
return []
|
626
|
+
assert hasattr(suite, '_tests'), \
|
627
|
+
"%s.%s is not a valid TestSuite" % (module.__name__, suitename)
|
628
|
+
# python2.3 does not implement __iter__ on suites, we need to return
|
629
|
+
# _tests explicitly
|
630
|
+
return suite._tests
|
631
|
+
|
632
|
+
def loadTestsFromName(self, name, module=None):
|
633
|
+
parts = name.split('.')
|
634
|
+
if module is None or len(parts) > 2:
|
635
|
+
# let the base class do its job here
|
636
|
+
return [super(NonStrictTestLoader, self).loadTestsFromName(name)]
|
637
|
+
tests = self._collect_tests(module)
|
638
|
+
# import pprint
|
639
|
+
# pprint.pprint(tests)
|
640
|
+
collected = []
|
641
|
+
if len(parts) == 1:
|
642
|
+
pattern = parts[0]
|
643
|
+
if callable(getattr(module, pattern, None)
|
644
|
+
) and pattern not in tests:
|
645
|
+
# consider it as a suite
|
646
|
+
return self.loadTestsFromSuite(module, pattern)
|
647
|
+
if pattern in tests:
|
648
|
+
# case python unittest_foo.py MyTestTC
|
649
|
+
klass, methodnames = tests[pattern]
|
650
|
+
for methodname in methodnames:
|
651
|
+
collected = [klass(methodname)
|
652
|
+
for methodname in methodnames]
|
653
|
+
else:
|
654
|
+
# case python unittest_foo.py something
|
655
|
+
for klass, methodnames in tests.values():
|
656
|
+
collected += [klass(methodname)
|
657
|
+
for methodname in methodnames]
|
658
|
+
elif len(parts) == 2:
|
659
|
+
# case "MyClass.test_1"
|
660
|
+
classname, pattern = parts
|
661
|
+
klass, methodnames = tests.get(classname, (None, []))
|
662
|
+
for methodname in methodnames:
|
663
|
+
collected = [klass(methodname) for methodname in methodnames]
|
664
|
+
return collected
|
665
|
+
|
666
|
+
def _this_is_skipped(self, testedname):
|
667
|
+
return any([(pat in testedname) for pat in self.skipped_patterns])
|
668
|
+
|
669
|
+
def getTestCaseNames(self, testCaseClass):
|
670
|
+
"""Return a sorted sequence of method names found within testCaseClass
|
671
|
+
"""
|
672
|
+
is_skipped = self._this_is_skipped
|
673
|
+
classname = testCaseClass.__name__
|
674
|
+
if classname[0] == '_' or is_skipped(classname):
|
675
|
+
return []
|
676
|
+
testnames = super(NonStrictTestLoader, self).getTestCaseNames(
|
677
|
+
testCaseClass)
|
678
|
+
return [testname for testname in testnames if not is_skipped(testname)]
|
679
|
+
|
680
|
+
|
681
|
+
class SkipAwareTestProgram(unittest.TestProgram):
|
682
|
+
# XXX: don't try to stay close to unittest.py, use optparse
|
683
|
+
USAGE = """\
|
684
|
+
Usage: %(progName)s [options] [test] [...]
|
685
|
+
|
686
|
+
Options:
|
687
|
+
-h, --help Show this message
|
688
|
+
-v, --verbose Verbose output
|
689
|
+
-i, --pdb Enable test failure inspection
|
690
|
+
-x, --exitfirst Exit on first failure
|
691
|
+
-c, --capture Captures and prints standard out/err only on errors
|
692
|
+
-p, --printonly Only prints lines matching specified pattern
|
693
|
+
(implies capture)
|
694
|
+
-s, --skip skip test matching this pattern (no regexp for now)
|
695
|
+
-q, --quiet Minimal output
|
696
|
+
--color colorize tracebacks
|
697
|
+
|
698
|
+
-m, --match Run only test whose tag match this pattern
|
699
|
+
|
700
|
+
-P, --profile FILE: Run the tests using cProfile and saving results
|
701
|
+
in FILE
|
702
|
+
|
703
|
+
Examples:
|
704
|
+
%(progName)s - run default set of tests
|
705
|
+
%(progName)s MyTestSuite - run suite 'MyTestSuite'
|
706
|
+
%(progName)s MyTestCase.testSomething - run MyTestCase.testSomething
|
707
|
+
%(progName)s MyTestCase - run all 'test*' test methods
|
708
|
+
in MyTestCase
|
709
|
+
"""
|
710
|
+
def __init__(self, module='__main__', defaultTest=None, batchmode=False,
|
711
|
+
cvg=None, options=None, outstream=sys.stderr):
|
712
|
+
self.batchmode = batchmode
|
713
|
+
self.cvg = cvg
|
714
|
+
self.options = options
|
715
|
+
self.outstream = outstream
|
716
|
+
super(SkipAwareTestProgram, self).__init__(
|
717
|
+
module=module, defaultTest=defaultTest,
|
718
|
+
testLoader=NonStrictTestLoader())
|
719
|
+
|
720
|
+
def parseArgs(self, argv):
|
721
|
+
self.pdbmode = False
|
722
|
+
self.exitfirst = False
|
723
|
+
self.capture = 0
|
724
|
+
self.printonly = None
|
725
|
+
self.skipped_patterns = []
|
726
|
+
self.test_pattern = None
|
727
|
+
self.tags_pattern = None
|
728
|
+
self.colorize = False
|
729
|
+
self.profile_name = None
|
730
|
+
import getopt
|
731
|
+
try:
|
732
|
+
options, args = getopt.getopt(argv[1:], 'hHvixrqcp:s:m:P:',
|
733
|
+
['help', 'verbose', 'quiet', 'pdb',
|
734
|
+
'exitfirst', 'restart', 'capture', 'printonly=',
|
735
|
+
'skip=', 'color', 'match=', 'profile='])
|
736
|
+
for opt, value in options:
|
737
|
+
if opt in ('-h', '-H', '--help'):
|
738
|
+
self.usageExit()
|
739
|
+
if opt in ('-i', '--pdb'):
|
740
|
+
self.pdbmode = True
|
741
|
+
if opt in ('-x', '--exitfirst'):
|
742
|
+
self.exitfirst = True
|
743
|
+
if opt in ('-r', '--restart'):
|
744
|
+
self.restart = True
|
745
|
+
self.exitfirst = True
|
746
|
+
if opt in ('-q', '--quiet'):
|
747
|
+
self.verbosity = 0
|
748
|
+
if opt in ('-v', '--verbose'):
|
749
|
+
self.verbosity = 2
|
750
|
+
if opt in ('-c', '--capture'):
|
751
|
+
self.capture += 1
|
752
|
+
if opt in ('-p', '--printonly'):
|
753
|
+
self.printonly = re.compile(value)
|
754
|
+
if opt in ('-s', '--skip'):
|
755
|
+
self.skipped_patterns = [pat.strip() for pat in
|
756
|
+
value.split(', ')]
|
757
|
+
if opt == '--color':
|
758
|
+
self.colorize = True
|
759
|
+
if opt in ('-m', '--match'):
|
760
|
+
#self.tags_pattern = value
|
761
|
+
self.options["tag_pattern"] = value
|
762
|
+
if opt in ('-P', '--profile'):
|
763
|
+
self.profile_name = value
|
764
|
+
self.testLoader.skipped_patterns = self.skipped_patterns
|
765
|
+
if self.printonly is not None:
|
766
|
+
self.capture += 1
|
767
|
+
if len(args) == 0 and self.defaultTest is None:
|
768
|
+
suitefunc = getattr(self.module, 'suite', None)
|
769
|
+
if isinstance(suitefunc, (types.FunctionType,
|
770
|
+
types.MethodType)):
|
771
|
+
self.test = self.module.suite()
|
772
|
+
else:
|
773
|
+
self.test = self.testLoader.loadTestsFromModule(self.module)
|
774
|
+
return
|
775
|
+
if len(args) > 0:
|
776
|
+
self.test_pattern = args[0]
|
777
|
+
self.testNames = args
|
778
|
+
else:
|
779
|
+
self.testNames = (self.defaultTest, )
|
780
|
+
self.createTests()
|
781
|
+
except getopt.error, msg:
|
782
|
+
self.usageExit(msg)
|
783
|
+
|
784
|
+
|
785
|
+
def runTests(self):
|
786
|
+
if self.profile_name:
|
787
|
+
import cProfile
|
788
|
+
cProfile.runctx('self._runTests()', globals(), locals(), self.profile_name )
|
789
|
+
else:
|
790
|
+
return self._runTests()
|
791
|
+
|
792
|
+
def _runTests(self):
|
793
|
+
if hasattr(self.module, 'setup_module'):
|
794
|
+
try:
|
795
|
+
self.module.setup_module(self.options)
|
796
|
+
except Exception, exc:
|
797
|
+
print 'setup_module error:', exc
|
798
|
+
sys.exit(1)
|
799
|
+
self.testRunner = SkipAwareTextTestRunner(verbosity=self.verbosity,
|
800
|
+
stream=self.outstream,
|
801
|
+
exitfirst=self.exitfirst,
|
802
|
+
capture=self.capture,
|
803
|
+
printonly=self.printonly,
|
804
|
+
pdbmode=self.pdbmode,
|
805
|
+
cvg=self.cvg,
|
806
|
+
test_pattern=self.test_pattern,
|
807
|
+
skipped_patterns=self.skipped_patterns,
|
808
|
+
colorize=self.colorize,
|
809
|
+
batchmode=self.batchmode,
|
810
|
+
options=self.options)
|
811
|
+
|
812
|
+
def removeSucceededTests(obj, succTests):
|
813
|
+
""" Recursive function that removes succTests from
|
814
|
+
a TestSuite or TestCase
|
815
|
+
"""
|
816
|
+
if isinstance(obj, TestSuite):
|
817
|
+
removeSucceededTests(obj._tests, succTests)
|
818
|
+
if isinstance(obj, list):
|
819
|
+
for el in obj[:]:
|
820
|
+
if isinstance(el, TestSuite):
|
821
|
+
removeSucceededTests(el, succTests)
|
822
|
+
elif isinstance(el, TestCase):
|
823
|
+
descr = '.'.join((el.__class__.__module__,
|
824
|
+
el.__class__.__name__,
|
825
|
+
el._testMethodName))
|
826
|
+
if descr in succTests:
|
827
|
+
obj.remove(el)
|
828
|
+
# take care, self.options may be None
|
829
|
+
if getattr(self.options, 'restart', False):
|
830
|
+
# retrieve succeeded tests from FILE_RESTART
|
831
|
+
try:
|
832
|
+
restartfile = open(FILE_RESTART, 'r')
|
833
|
+
try:
|
834
|
+
try:
|
835
|
+
succeededtests = list(elem.rstrip('\n\r') for elem in
|
836
|
+
restartfile.readlines())
|
837
|
+
removeSucceededTests(self.test, succeededtests)
|
838
|
+
except Exception, e:
|
839
|
+
raise e
|
840
|
+
finally:
|
841
|
+
restartfile.close()
|
842
|
+
except Exception ,e:
|
843
|
+
raise "Error while reading \
|
844
|
+
succeeded tests into", osp.join(os.getcwd(),FILE_RESTART)
|
845
|
+
|
846
|
+
result = self.testRunner.run(self.test)
|
847
|
+
# help garbage collection: we want TestSuite, which hold refs to every
|
848
|
+
# executed TestCase, to be gc'ed
|
849
|
+
del self.test
|
850
|
+
if hasattr(self.module, 'teardown_module'):
|
851
|
+
try:
|
852
|
+
self.module.teardown_module(self.options, result)
|
853
|
+
except Exception, exc:
|
854
|
+
print 'teardown_module error:', exc
|
855
|
+
sys.exit(1)
|
856
|
+
if result.debuggers and self.pdbmode:
|
857
|
+
start_interactive_mode(result)
|
858
|
+
if not self.batchmode:
|
859
|
+
sys.exit(not result.wasSuccessful())
|
860
|
+
self.result = result
|
861
|
+
|
862
|
+
|
863
|
+
|
864
|
+
|
865
|
+
class FDCapture:
|
866
|
+
"""adapted from py lib (http://codespeak.net/py)
|
867
|
+
Capture IO to/from a given os-level filedescriptor.
|
868
|
+
"""
|
869
|
+
def __init__(self, fd, attr='stdout', printonly=None):
|
870
|
+
self.targetfd = fd
|
871
|
+
self.tmpfile = os.tmpfile() # self.maketempfile()
|
872
|
+
self.printonly = printonly
|
873
|
+
# save original file descriptor
|
874
|
+
self._savefd = os.dup(fd)
|
875
|
+
# override original file descriptor
|
876
|
+
os.dup2(self.tmpfile.fileno(), fd)
|
877
|
+
# also modify sys module directly
|
878
|
+
self.oldval = getattr(sys, attr)
|
879
|
+
setattr(sys, attr, self) # self.tmpfile)
|
880
|
+
self.attr = attr
|
881
|
+
|
882
|
+
def write(self, msg):
|
883
|
+
# msg might be composed of several lines
|
884
|
+
for line in msg.splitlines():
|
885
|
+
line += '\n' # keepdend=True is not enough
|
886
|
+
if self.printonly is None or self.printonly.search(line) is None:
|
887
|
+
self.tmpfile.write(line)
|
888
|
+
else:
|
889
|
+
os.write(self._savefd, line)
|
890
|
+
|
891
|
+
## def maketempfile(self):
|
892
|
+
## tmpf = os.tmpfile()
|
893
|
+
## fd = os.dup(tmpf.fileno())
|
894
|
+
## newf = os.fdopen(fd, tmpf.mode, 0) # No buffering
|
895
|
+
## tmpf.close()
|
896
|
+
## return newf
|
897
|
+
|
898
|
+
def restore(self):
|
899
|
+
"""restore original fd and returns captured output"""
|
900
|
+
#XXX: hack hack hack
|
901
|
+
self.tmpfile.flush()
|
902
|
+
try:
|
903
|
+
ref_file = getattr(sys, '__%s__' % self.attr)
|
904
|
+
ref_file.flush()
|
905
|
+
except AttributeError:
|
906
|
+
pass
|
907
|
+
if hasattr(self.oldval, 'flush'):
|
908
|
+
self.oldval.flush()
|
909
|
+
# restore original file descriptor
|
910
|
+
os.dup2(self._savefd, self.targetfd)
|
911
|
+
# restore sys module
|
912
|
+
setattr(sys, self.attr, self.oldval)
|
913
|
+
# close backup descriptor
|
914
|
+
os.close(self._savefd)
|
915
|
+
# go to beginning of file and read it
|
916
|
+
self.tmpfile.seek(0)
|
917
|
+
return self.tmpfile.read()
|
918
|
+
|
919
|
+
|
920
|
+
def _capture(which='stdout', printonly=None):
|
921
|
+
"""private method, should not be called directly
|
922
|
+
(cf. capture_stdout() and capture_stderr())
|
923
|
+
"""
|
924
|
+
assert which in ('stdout', 'stderr'
|
925
|
+
), "Can only capture stdout or stderr, not %s" % which
|
926
|
+
if which == 'stdout':
|
927
|
+
fd = 1
|
928
|
+
else:
|
929
|
+
fd = 2
|
930
|
+
return FDCapture(fd, which, printonly)
|
931
|
+
|
932
|
+
def capture_stdout(printonly=None):
|
933
|
+
"""captures the standard output
|
934
|
+
|
935
|
+
returns a handle object which has a `restore()` method.
|
936
|
+
The restore() method returns the captured stdout and restores it
|
937
|
+
"""
|
938
|
+
return _capture('stdout', printonly)
|
939
|
+
|
940
|
+
def capture_stderr(printonly=None):
|
941
|
+
"""captures the standard error output
|
942
|
+
|
943
|
+
returns a handle object which has a `restore()` method.
|
944
|
+
The restore() method returns the captured stderr and restores it
|
945
|
+
"""
|
946
|
+
return _capture('stderr', printonly)
|
947
|
+
|
948
|
+
|
949
|
+
def unittest_main(module='__main__', defaultTest=None,
|
950
|
+
batchmode=False, cvg=None, options=None,
|
951
|
+
outstream=sys.stderr):
|
952
|
+
"""use this function if you want to have the same functionality
|
953
|
+
as unittest.main"""
|
954
|
+
return SkipAwareTestProgram(module, defaultTest, batchmode,
|
955
|
+
cvg, options, outstream)
|
956
|
+
|
957
|
+
class TestSkipped(Exception):
|
958
|
+
"""raised when a test is skipped"""
|
959
|
+
|
960
|
+
class InnerTestSkipped(TestSkipped):
|
961
|
+
"""raised when a test is skipped"""
|
962
|
+
|
963
|
+
def is_generator(function):
|
964
|
+
flags = function.func_code.co_flags
|
965
|
+
return flags & CO_GENERATOR
|
966
|
+
|
967
|
+
|
968
|
+
def parse_generative_args(params):
|
969
|
+
args = []
|
970
|
+
varargs = ()
|
971
|
+
kwargs = {}
|
972
|
+
flags = 0 # 2 <=> starargs, 4 <=> kwargs
|
973
|
+
for param in params:
|
974
|
+
if isinstance(param, starargs):
|
975
|
+
varargs = param
|
976
|
+
if flags:
|
977
|
+
raise TypeError('found starargs after keywords !')
|
978
|
+
flags |= 2
|
979
|
+
args += list(varargs)
|
980
|
+
elif isinstance(param, keywords):
|
981
|
+
kwargs = param
|
982
|
+
if flags & 4:
|
983
|
+
raise TypeError('got multiple keywords parameters')
|
984
|
+
flags |= 4
|
985
|
+
elif flags & 2 or flags & 4:
|
986
|
+
raise TypeError('found parameters after kwargs or args')
|
987
|
+
else:
|
988
|
+
args.append(param)
|
989
|
+
|
990
|
+
return args, kwargs
|
991
|
+
|
992
|
+
class InnerTest(tuple):
|
993
|
+
def __new__(cls, name, *data):
|
994
|
+
instance = tuple.__new__(cls, data)
|
995
|
+
instance.name = name
|
996
|
+
return instance
|
997
|
+
|
998
|
+
|
999
|
+
class TestCase(unittest.TestCase):
|
1000
|
+
"""unittest.TestCase with some additional methods"""
|
1001
|
+
|
1002
|
+
capture = False
|
1003
|
+
pdbclass = Debugger
|
1004
|
+
|
1005
|
+
def __init__(self, methodName='runTest'):
|
1006
|
+
super(TestCase, self).__init__(methodName)
|
1007
|
+
# internal API changed in python2.5
|
1008
|
+
if sys.version_info >= (2, 5):
|
1009
|
+
self.__exc_info = self._exc_info
|
1010
|
+
self.__testMethodName = self._testMethodName
|
1011
|
+
else:
|
1012
|
+
# let's give easier access to _testMethodName to every subclasses
|
1013
|
+
self._testMethodName = self.__testMethodName
|
1014
|
+
self._captured_stdout = ""
|
1015
|
+
self._captured_stderr = ""
|
1016
|
+
self._out = []
|
1017
|
+
self._err = []
|
1018
|
+
self._current_test_descr = None
|
1019
|
+
self._options_ = None
|
1020
|
+
|
1021
|
+
def datadir(cls): # pylint: disable-msg=E0213
|
1022
|
+
"""helper attribute holding the standard test's data directory
|
1023
|
+
|
1024
|
+
NOTE: this is a logilab's standard
|
1025
|
+
"""
|
1026
|
+
mod = __import__(cls.__module__)
|
1027
|
+
return osp.join(osp.dirname(osp.abspath(mod.__file__)), 'data')
|
1028
|
+
# cache it (use a class method to cache on class since TestCase is
|
1029
|
+
# instantiated for each test run)
|
1030
|
+
datadir = classproperty(cached(datadir))
|
1031
|
+
|
1032
|
+
def datapath(cls, *fname):
|
1033
|
+
"""joins the object's datadir and `fname`"""
|
1034
|
+
return osp.join(cls.datadir, *fname)
|
1035
|
+
datapath = classmethod(datapath)
|
1036
|
+
|
1037
|
+
def set_description(self, descr):
|
1038
|
+
"""sets the current test's description.
|
1039
|
+
This can be useful for generative tests because it allows to specify
|
1040
|
+
a description per yield
|
1041
|
+
"""
|
1042
|
+
self._current_test_descr = descr
|
1043
|
+
|
1044
|
+
# override default's unittest.py feature
|
1045
|
+
def shortDescription(self):
|
1046
|
+
"""override default unitest shortDescription to handle correctly
|
1047
|
+
generative tests
|
1048
|
+
"""
|
1049
|
+
if self._current_test_descr is not None:
|
1050
|
+
return self._current_test_descr
|
1051
|
+
return super(TestCase, self).shortDescription()
|
1052
|
+
|
1053
|
+
|
1054
|
+
def captured_output(self):
|
1055
|
+
"""return a two tuple with standard output and error stripped"""
|
1056
|
+
return self._captured_stdout.strip(), self._captured_stderr.strip()
|
1057
|
+
|
1058
|
+
def _start_capture(self):
|
1059
|
+
"""start_capture if enable"""
|
1060
|
+
if self.capture:
|
1061
|
+
warnings.simplefilter('ignore', DeprecationWarning)
|
1062
|
+
self.start_capture()
|
1063
|
+
|
1064
|
+
def _stop_capture(self):
|
1065
|
+
"""stop_capture and restore previous output"""
|
1066
|
+
self._force_output_restore()
|
1067
|
+
|
1068
|
+
def start_capture(self, printonly=None):
|
1069
|
+
"""start_capture"""
|
1070
|
+
self._out.append(capture_stdout(printonly or self._printonly))
|
1071
|
+
self._err.append(capture_stderr(printonly or self._printonly))
|
1072
|
+
|
1073
|
+
def printonly(self, pattern, flags=0):
|
1074
|
+
"""set the pattern of line to print"""
|
1075
|
+
rgx = re.compile(pattern, flags)
|
1076
|
+
if self._out:
|
1077
|
+
self._out[-1].printonly = rgx
|
1078
|
+
self._err[-1].printonly = rgx
|
1079
|
+
else:
|
1080
|
+
self.start_capture(printonly=rgx)
|
1081
|
+
|
1082
|
+
def stop_capture(self):
|
1083
|
+
"""stop output and error capture"""
|
1084
|
+
if self._out:
|
1085
|
+
_out = self._out.pop()
|
1086
|
+
_err = self._err.pop()
|
1087
|
+
return _out.restore(), _err.restore()
|
1088
|
+
return '', ''
|
1089
|
+
|
1090
|
+
def _force_output_restore(self):
|
1091
|
+
"""remove all capture set"""
|
1092
|
+
while self._out:
|
1093
|
+
self._captured_stdout += self._out.pop().restore()
|
1094
|
+
self._captured_stderr += self._err.pop().restore()
|
1095
|
+
|
1096
|
+
def quiet_run(self, result, func, *args, **kwargs):
|
1097
|
+
self._start_capture()
|
1098
|
+
try:
|
1099
|
+
func(*args, **kwargs)
|
1100
|
+
except (KeyboardInterrupt, SystemExit):
|
1101
|
+
self._stop_capture()
|
1102
|
+
raise
|
1103
|
+
except:
|
1104
|
+
self._stop_capture()
|
1105
|
+
result.addError(self, self.__exc_info())
|
1106
|
+
return False
|
1107
|
+
self._stop_capture()
|
1108
|
+
return True
|
1109
|
+
|
1110
|
+
def _get_test_method(self):
|
1111
|
+
"""return the test method"""
|
1112
|
+
return getattr(self, self.__testMethodName)
|
1113
|
+
|
1114
|
+
|
1115
|
+
def optval(self, option, default=None):
|
1116
|
+
"""return the option value or default if the option is not define"""
|
1117
|
+
return getattr(self._options_, option, default)
|
1118
|
+
|
1119
|
+
def __call__(self, result=None, runcondition=None, options=None):
|
1120
|
+
"""rewrite TestCase.__call__ to support generative tests
|
1121
|
+
This is mostly a copy/paste from unittest.py (i.e same
|
1122
|
+
variable names, same logic, except for the generative tests part)
|
1123
|
+
"""
|
1124
|
+
if result is None:
|
1125
|
+
result = self.defaultTestResult()
|
1126
|
+
result.pdbclass = self.pdbclass
|
1127
|
+
# if self.capture is True here, it means it was explicitly specified
|
1128
|
+
# in the user's TestCase class. If not, do what was asked on cmd line
|
1129
|
+
self.capture = self.capture or getattr(result, 'capture', False)
|
1130
|
+
self._options_ = options
|
1131
|
+
self._printonly = getattr(result, 'printonly', None)
|
1132
|
+
# if result.cvg:
|
1133
|
+
# result.cvg.start()
|
1134
|
+
testMethod = self._get_test_method()
|
1135
|
+
if runcondition and not runcondition(testMethod):
|
1136
|
+
return # test is skipped
|
1137
|
+
result.startTest(self)
|
1138
|
+
try:
|
1139
|
+
if not self.quiet_run(result, self.setUp):
|
1140
|
+
return
|
1141
|
+
generative = is_generator(testMethod.im_func)
|
1142
|
+
# generative tests
|
1143
|
+
if generative:
|
1144
|
+
self._proceed_generative(result, testMethod,
|
1145
|
+
runcondition)
|
1146
|
+
else:
|
1147
|
+
status = self._proceed(result, testMethod)
|
1148
|
+
success = (status == 0)
|
1149
|
+
if not self.quiet_run(result, self.tearDown):
|
1150
|
+
return
|
1151
|
+
if not generative and success:
|
1152
|
+
if hasattr(options, "exitfirst") and options.exitfirst:
|
1153
|
+
# add this test to restart file
|
1154
|
+
try:
|
1155
|
+
restartfile = open(FILE_RESTART, 'a')
|
1156
|
+
try:
|
1157
|
+
try:
|
1158
|
+
descr = '.'.join((self.__class__.__module__,
|
1159
|
+
self.__class__.__name__,
|
1160
|
+
self._testMethodName))
|
1161
|
+
restartfile.write(descr+os.linesep)
|
1162
|
+
except Exception, e:
|
1163
|
+
raise e
|
1164
|
+
finally:
|
1165
|
+
restartfile.close()
|
1166
|
+
except Exception, e:
|
1167
|
+
print >> sys.__stderr__, "Error while saving \
|
1168
|
+
succeeded test into", osp.join(os.getcwd(),FILE_RESTART)
|
1169
|
+
raise e
|
1170
|
+
result.addSuccess(self)
|
1171
|
+
finally:
|
1172
|
+
# if result.cvg:
|
1173
|
+
# result.cvg.stop()
|
1174
|
+
result.stopTest(self)
|
1175
|
+
|
1176
|
+
|
1177
|
+
|
1178
|
+
def _proceed_generative(self, result, testfunc, runcondition=None):
|
1179
|
+
# cancel startTest()'s increment
|
1180
|
+
result.testsRun -= 1
|
1181
|
+
self._start_capture()
|
1182
|
+
success = True
|
1183
|
+
try:
|
1184
|
+
for params in testfunc():
|
1185
|
+
if runcondition and not runcondition(testfunc,
|
1186
|
+
skipgenerator=False):
|
1187
|
+
if not (isinstance(params, InnerTest)
|
1188
|
+
and runcondition(params)):
|
1189
|
+
continue
|
1190
|
+
if not isinstance(params, (tuple, list)):
|
1191
|
+
params = (params, )
|
1192
|
+
func = params[0]
|
1193
|
+
args, kwargs = parse_generative_args(params[1:])
|
1194
|
+
# increment test counter manually
|
1195
|
+
result.testsRun += 1
|
1196
|
+
status = self._proceed(result, func, args, kwargs)
|
1197
|
+
if status == 0:
|
1198
|
+
result.addSuccess(self)
|
1199
|
+
success = True
|
1200
|
+
else:
|
1201
|
+
success = False
|
1202
|
+
if status == 2:
|
1203
|
+
result.shouldStop = True
|
1204
|
+
if result.shouldStop: # either on error or on exitfirst + error
|
1205
|
+
break
|
1206
|
+
except:
|
1207
|
+
# if an error occurs between two yield
|
1208
|
+
result.addError(self, self.__exc_info())
|
1209
|
+
success = False
|
1210
|
+
self._stop_capture()
|
1211
|
+
return success
|
1212
|
+
|
1213
|
+
def _proceed(self, result, testfunc, args=(), kwargs=None):
|
1214
|
+
"""proceed the actual test
|
1215
|
+
returns 0 on success, 1 on failure, 2 on error
|
1216
|
+
|
1217
|
+
Note: addSuccess can't be called here because we have to wait
|
1218
|
+
for tearDown to be successfully executed to declare the test as
|
1219
|
+
successful
|
1220
|
+
"""
|
1221
|
+
self._start_capture()
|
1222
|
+
kwargs = kwargs or {}
|
1223
|
+
try:
|
1224
|
+
testfunc(*args, **kwargs)
|
1225
|
+
self._stop_capture()
|
1226
|
+
except self.failureException:
|
1227
|
+
self._stop_capture()
|
1228
|
+
result.addFailure(self, self.__exc_info())
|
1229
|
+
return 1
|
1230
|
+
except KeyboardInterrupt:
|
1231
|
+
self._stop_capture()
|
1232
|
+
raise
|
1233
|
+
except InnerTestSkipped, e:
|
1234
|
+
result.addSkipped(self, e)
|
1235
|
+
return 1
|
1236
|
+
except:
|
1237
|
+
self._stop_capture()
|
1238
|
+
result.addError(self, self.__exc_info())
|
1239
|
+
return 2
|
1240
|
+
return 0
|
1241
|
+
|
1242
|
+
def defaultTestResult(self):
|
1243
|
+
"""return a new instance of the defaultTestResult"""
|
1244
|
+
return SkipAwareTestResult()
|
1245
|
+
|
1246
|
+
def skip(self, msg=None):
|
1247
|
+
"""mark a test as skipped for the <msg> reason"""
|
1248
|
+
msg = msg or 'test was skipped'
|
1249
|
+
raise TestSkipped(msg)
|
1250
|
+
|
1251
|
+
def innerSkip(self, msg=None):
|
1252
|
+
"""mark a generative test as skipped for the <msg> reason"""
|
1253
|
+
msg = msg or 'test was skipped'
|
1254
|
+
raise InnerTestSkipped(msg)
|
1255
|
+
|
1256
|
+
def assertIn(self, object, set):
|
1257
|
+
"""assert <object> are in <set>"""
|
1258
|
+
self.assert_(object in set, "%s not in %s" % (object, set))
|
1259
|
+
|
1260
|
+
def assertNotIn(self, object, set):
|
1261
|
+
"""assert <object> are not in <set>"""
|
1262
|
+
self.assert_(object not in set, "%s in %s" % (object, set))
|
1263
|
+
|
1264
|
+
def assertDictEquals(self, dict1, dict2):
|
1265
|
+
"""compares two dicts
|
1266
|
+
|
1267
|
+
If the two dict differ, the first difference is shown in the error
|
1268
|
+
message
|
1269
|
+
"""
|
1270
|
+
dict1 = dict(dict1)
|
1271
|
+
msgs = []
|
1272
|
+
for key, value in dict2.items():
|
1273
|
+
try:
|
1274
|
+
if dict1[key] != value:
|
1275
|
+
msgs.append('%r != %r for key %r' % (dict1[key], value,
|
1276
|
+
key))
|
1277
|
+
del dict1[key]
|
1278
|
+
except KeyError:
|
1279
|
+
msgs.append('missing %r key' % key)
|
1280
|
+
if dict1:
|
1281
|
+
msgs.append('dict2 is lacking %r' % dict1)
|
1282
|
+
if msgs:
|
1283
|
+
self.fail('\n'.join(msgs))
|
1284
|
+
assertDictEqual = assertDictEquals
|
1285
|
+
|
1286
|
+
|
1287
|
+
|
1288
|
+
def assertUnorderedIterableEquals(self, got, expected, msg=None):
|
1289
|
+
"""compares two iterable and shows difference between both"""
|
1290
|
+
got, expected = list(got), list(expected)
|
1291
|
+
self.assertSetEqual(set(got), set(expected), msg)
|
1292
|
+
if len(got) != len(expected):
|
1293
|
+
if msg is None:
|
1294
|
+
msg = ['Iterable have the same elements but not the same number',
|
1295
|
+
'\t<element>\t<expected>i\t<got>']
|
1296
|
+
got_count = {}
|
1297
|
+
expected_count = {}
|
1298
|
+
for element in got:
|
1299
|
+
got_count[element] = got_count.get(element,0) + 1
|
1300
|
+
for element in expected:
|
1301
|
+
expected_count[element] = expected_count.get(element,0) + 1
|
1302
|
+
# we know that got_count.key() == expected_count.key()
|
1303
|
+
# because of assertSetEquals
|
1304
|
+
for element, count in got_count.iteritems():
|
1305
|
+
other_count = expected_count[element]
|
1306
|
+
if other_count != count:
|
1307
|
+
msg.append('\t%s\t%s\t%s' % (element, other_count, count))
|
1308
|
+
|
1309
|
+
self.fail(msg)
|
1310
|
+
|
1311
|
+
assertUnorderedIterableEqual = assertUnorderedIterableEquals
|
1312
|
+
assertUnordIterEquals = assertUnordIterEqual = assertUnorderedIterableEqual
|
1313
|
+
|
1314
|
+
def assertSetEquals(self,got,expected, msg=None):
|
1315
|
+
if not(isinstance(got, set) and isinstance(expected, set)):
|
1316
|
+
warnings.warn("the assertSetEquals function if now intended for set only."\
|
1317
|
+
"use assertUnorderedIterableEquals instead.",
|
1318
|
+
DeprecationWarning, 2)
|
1319
|
+
return self.assertUnorderedIterableEquals(got,expected, msg)
|
1320
|
+
|
1321
|
+
items={}
|
1322
|
+
items['missing'] = expected - got
|
1323
|
+
items['unexpected'] = got - expected
|
1324
|
+
if any(items.itervalues()):
|
1325
|
+
if msg is None:
|
1326
|
+
msg = '\n'.join('%s:\n\t%s' % (key,"\n\t".join(str(value) for value in values))
|
1327
|
+
for key, values in items.iteritems() if values)
|
1328
|
+
self.fail(msg)
|
1329
|
+
|
1330
|
+
|
1331
|
+
assertSetEqual = assertSetEquals
|
1332
|
+
|
1333
|
+
def assertListEquals(self, list_1, list_2, msg=None):
|
1334
|
+
"""compares two lists
|
1335
|
+
|
1336
|
+
If the two list differ, the first difference is shown in the error
|
1337
|
+
message
|
1338
|
+
"""
|
1339
|
+
_l1 = list_1[:]
|
1340
|
+
for i, value in enumerate(list_2):
|
1341
|
+
try:
|
1342
|
+
if _l1[0] != value:
|
1343
|
+
from pprint import pprint
|
1344
|
+
pprint(list_1)
|
1345
|
+
pprint(list_2)
|
1346
|
+
self.fail('%r != %r for index %d' % (_l1[0], value, i))
|
1347
|
+
del _l1[0]
|
1348
|
+
except IndexError:
|
1349
|
+
if msg is None:
|
1350
|
+
msg = 'list_1 has only %d elements, not %s '\
|
1351
|
+
'(at least %r missing)'% (i, len(list_2), value)
|
1352
|
+
self.fail(msg)
|
1353
|
+
if _l1:
|
1354
|
+
if msg is None:
|
1355
|
+
msg = 'list_2 is lacking %r' % _l1
|
1356
|
+
self.fail(msg)
|
1357
|
+
assertListEqual = assertListEquals
|
1358
|
+
|
1359
|
+
def assertLinesEquals(self, list_1, list_2, msg=None, striplines=False):
|
1360
|
+
"""assert list of lines are equal"""
|
1361
|
+
lines1 = list_1.splitlines()
|
1362
|
+
if striplines:
|
1363
|
+
lines1 = [l.strip() for l in lines1]
|
1364
|
+
lines2 = list_2.splitlines()
|
1365
|
+
if striplines:
|
1366
|
+
lines2 = [l.strip() for l in lines2]
|
1367
|
+
self.assertListEquals(lines1, lines2, msg)
|
1368
|
+
assertLineEqual = assertLinesEquals
|
1369
|
+
|
1370
|
+
def assertXMLWellFormed(self, stream, msg=None, context=2):
|
1371
|
+
"""asserts the XML stream is well-formed (no DTD conformance check)
|
1372
|
+
:context: number of context lines in standard msg. all data if negativ
|
1373
|
+
only available with element tree
|
1374
|
+
"""
|
1375
|
+
try:
|
1376
|
+
from xml.etree.ElementTree import parse
|
1377
|
+
self._assertETXMLWellFormed(stream, parse, msg)
|
1378
|
+
except ImportError:
|
1379
|
+
from xml.sax import make_parser, SAXParseException
|
1380
|
+
parser = make_parser()
|
1381
|
+
try:
|
1382
|
+
parser.parse(stream)
|
1383
|
+
except SAXParseException, ex:
|
1384
|
+
if msg is None:
|
1385
|
+
stream.seek(0)
|
1386
|
+
for _ in xrange(ex.getLineNumber()):
|
1387
|
+
line = stream.readline()
|
1388
|
+
pointer = ('' * (ex.getLineNumber() - 1)) + '^'
|
1389
|
+
msg = 'XML stream not well formed: %s\n%s%s' % (ex, line, pointer)
|
1390
|
+
self.fail(msg)
|
1391
|
+
|
1392
|
+
def assertXMLStringWellFormed(self, xml_string, msg=None, context=2):
|
1393
|
+
"""asserts the XML string is well-formed (no DTD conformance check)
|
1394
|
+
:context: number of context lines in standard msg. all data if negativ
|
1395
|
+
only available with element tree
|
1396
|
+
"""
|
1397
|
+
try:
|
1398
|
+
from xml.etree.ElementTree import fromstring
|
1399
|
+
self._assertETXMLWellFormed(xml_string, fromstring, msg)
|
1400
|
+
except ImportError:
|
1401
|
+
raise
|
1402
|
+
stream = StringIO(xml_string)
|
1403
|
+
self.assertXMLWellFormed(stream, msg)
|
1404
|
+
|
1405
|
+
def _assertETXMLWellFormed(self, data, parse, msg=None, context=2):
|
1406
|
+
"""internal function used by /assertXML(String)?WellFormed/ functions
|
1407
|
+
:data: xml_data
|
1408
|
+
:parse: appropriate parser function for this data
|
1409
|
+
:msg: error message
|
1410
|
+
:context: number of context lines in standard msg. all data if negativ
|
1411
|
+
only available with element tree
|
1412
|
+
"""
|
1413
|
+
from xml.parsers.expat import ExpatError
|
1414
|
+
try:
|
1415
|
+
parse(data)
|
1416
|
+
except ExpatError, ex:
|
1417
|
+
if msg is None:
|
1418
|
+
if hasattr(data, 'readlines'): #file like object
|
1419
|
+
stream.seek(0)
|
1420
|
+
lines = stream.readlines()
|
1421
|
+
else:
|
1422
|
+
lines =data.splitlines(True)
|
1423
|
+
nb_lines = len(lines)
|
1424
|
+
context_lines = []
|
1425
|
+
|
1426
|
+
if context < 0:
|
1427
|
+
start = 1
|
1428
|
+
end = nb_lines
|
1429
|
+
else:
|
1430
|
+
start = max(ex.lineno-context, 1)
|
1431
|
+
end = min(ex.lineno+context, nb_lines)
|
1432
|
+
line_number_length = len('%i' % end)
|
1433
|
+
line_pattern = " %%%ii: %%s" % line_number_length
|
1434
|
+
|
1435
|
+
for line_no in xrange(start, ex.lineno):
|
1436
|
+
context_lines.append(line_pattern % (line_no, lines[line_no-1]))
|
1437
|
+
context_lines.append(line_pattern % (ex.lineno, lines[ex.lineno-1]))
|
1438
|
+
context_lines.append('%s^\n' % (' ' * (1 + line_number_length + 2 +ex.offset)))
|
1439
|
+
for line_no in xrange(ex.lineno+1, end+1):
|
1440
|
+
context_lines.append(line_pattern % (line_no, lines[line_no-1]))
|
1441
|
+
|
1442
|
+
rich_context = ''.join(context_lines)
|
1443
|
+
msg = 'XML stream not well formed: %s\n%s' % (ex, rich_context)
|
1444
|
+
self.fail(msg)
|
1445
|
+
|
1446
|
+
|
1447
|
+
def assertXMLEqualsTuple(self, element, tup):
|
1448
|
+
"""compare an ElementTree Element to a tuple formatted as follow:
|
1449
|
+
(tagname, [attrib[, children[, text[, tail]]]])"""
|
1450
|
+
# check tag
|
1451
|
+
self.assertTextEquals(element.tag, tup[0])
|
1452
|
+
# check attrib
|
1453
|
+
if len(element.attrib) or len(tup)>1:
|
1454
|
+
if len(tup)<=1:
|
1455
|
+
self.fail( "tuple %s has no attributes (%s expected)"%(tup,
|
1456
|
+
dict(element.attrib)))
|
1457
|
+
self.assertDictEquals(element.attrib, tup[1])
|
1458
|
+
# check children
|
1459
|
+
if len(element) or len(tup)>2:
|
1460
|
+
if len(tup)<=2:
|
1461
|
+
self.fail( "tuple %s has no children (%i expected)"%(tup,
|
1462
|
+
len(element)))
|
1463
|
+
if len(element) != len(tup[2]):
|
1464
|
+
self.fail( "tuple %s has %i children%s (%i expected)"%(tup,
|
1465
|
+
len(tup[2]),
|
1466
|
+
('', 's')[len(tup[2])>1], len(element)))
|
1467
|
+
for index in xrange(len(tup[2])):
|
1468
|
+
self.assertXMLEqualsTuple(element[index], tup[2][index])
|
1469
|
+
#check text
|
1470
|
+
if element.text or len(tup)>3:
|
1471
|
+
if len(tup)<=3:
|
1472
|
+
self.fail( "tuple %s has no text value (%r expected)"%(tup,
|
1473
|
+
element.text))
|
1474
|
+
self.assertTextEquals(element.text, tup[3])
|
1475
|
+
#check tail
|
1476
|
+
if element.tail or len(tup)>4:
|
1477
|
+
if len(tup)<=4:
|
1478
|
+
self.fail( "tuple %s has no tail value (%r expected)"%(tup,
|
1479
|
+
element.tail))
|
1480
|
+
self.assertTextEquals(element.tail, tup[4])
|
1481
|
+
|
1482
|
+
def _difftext(self, lines1, lines2, junk=None, msg_prefix='Texts differ'):
|
1483
|
+
junk = junk or (' ', '\t')
|
1484
|
+
# result is a generator
|
1485
|
+
result = difflib.ndiff(lines1, lines2, charjunk=lambda x: x in junk)
|
1486
|
+
read = []
|
1487
|
+
for line in result:
|
1488
|
+
read.append(line)
|
1489
|
+
# lines that don't start with a ' ' are diff ones
|
1490
|
+
if not line.startswith(' '):
|
1491
|
+
self.fail('\n'.join(['%s\n'%msg_prefix]+read + list(result)))
|
1492
|
+
|
1493
|
+
def assertTextEquals(self, text1, text2, junk=None,
|
1494
|
+
msg_prefix='Text differ', striplines=False):
|
1495
|
+
"""compare two multiline strings (using difflib and splitlines())"""
|
1496
|
+
msg = []
|
1497
|
+
if not isinstance(text1, basestring):
|
1498
|
+
msg.append('text1 is not a string (%s)'%(type(text1)))
|
1499
|
+
if not isinstance(text2, basestring):
|
1500
|
+
msg.append('text2 is not a string (%s)'%(type(text2)))
|
1501
|
+
if msg:
|
1502
|
+
self.fail('\n'.join(msg))
|
1503
|
+
lines1 = text1.strip().splitlines(True)
|
1504
|
+
lines2 = text2.strip().splitlines(True)
|
1505
|
+
if striplines:
|
1506
|
+
lines1 = [line.strip() for line in lines1]
|
1507
|
+
lines2 = [line.strip() for line in lines2]
|
1508
|
+
self._difftext(lines1, lines2, junk, msg_prefix)
|
1509
|
+
assertTextEqual = assertTextEquals
|
1510
|
+
|
1511
|
+
def assertStreamEquals(self, stream1, stream2, junk=None,
|
1512
|
+
msg_prefix='Stream differ'):
|
1513
|
+
"""compare two streams (using difflib and readlines())"""
|
1514
|
+
# if stream2 is stream2, readlines() on stream1 will also read lines
|
1515
|
+
# in stream2, so they'll appear different, although they're not
|
1516
|
+
if stream1 is stream2:
|
1517
|
+
return
|
1518
|
+
# make sure we compare from the beginning of the stream
|
1519
|
+
stream1.seek(0)
|
1520
|
+
stream2.seek(0)
|
1521
|
+
# compare
|
1522
|
+
self._difftext(stream1.readlines(), stream2.readlines(), junk,
|
1523
|
+
msg_prefix)
|
1524
|
+
|
1525
|
+
assertStreamEqual = assertStreamEquals
|
1526
|
+
def assertFileEquals(self, fname1, fname2, junk=(' ', '\t')):
|
1527
|
+
"""compares two files using difflib"""
|
1528
|
+
self.assertStreamEqual(file(fname1), file(fname2), junk,
|
1529
|
+
msg_prefix='Files differs\n-:%s\n+:%s\n'%(fname1, fname2))
|
1530
|
+
assertFileEqual = assertFileEquals
|
1531
|
+
|
1532
|
+
|
1533
|
+
def assertDirEquals(self, path_a, path_b):
|
1534
|
+
"""compares two files using difflib"""
|
1535
|
+
assert osp.exists(path_a), "%s doesn't exists" % path_a
|
1536
|
+
assert osp.exists(path_b), "%s doesn't exists" % path_b
|
1537
|
+
|
1538
|
+
all_a = [ (ipath[len(path_a):].lstrip('/'), idirs, ifiles)
|
1539
|
+
for ipath, idirs, ifiles in os.walk(path_a)]
|
1540
|
+
all_a.sort(key=itemgetter(0))
|
1541
|
+
|
1542
|
+
all_b = [ (ipath[len(path_b):].lstrip('/'), idirs, ifiles)
|
1543
|
+
for ipath, idirs, ifiles in os.walk(path_b)]
|
1544
|
+
all_b.sort(key=itemgetter(0))
|
1545
|
+
|
1546
|
+
iter_a, iter_b = iter(all_a), iter(all_b)
|
1547
|
+
partial_iter = True
|
1548
|
+
ipath_a, idirs_a, ifiles_a = data_a = None, None, None
|
1549
|
+
while True:
|
1550
|
+
try:
|
1551
|
+
ipath_a, idirs_a, ifiles_a = datas_a = iter_a.next()
|
1552
|
+
partial_iter = False
|
1553
|
+
ipath_b, idirs_b, ifiles_b = datas_b = iter_b.next()
|
1554
|
+
partial_iter = True
|
1555
|
+
|
1556
|
+
|
1557
|
+
self.assert_(ipath_a == ipath_b,
|
1558
|
+
"unexpected %s in %s while looking %s from %s" %
|
1559
|
+
(ipath_a, path_a, ipath_b, path_b))
|
1560
|
+
|
1561
|
+
|
1562
|
+
errors = {}
|
1563
|
+
sdirs_a = set(idirs_a)
|
1564
|
+
sdirs_b = set(idirs_b)
|
1565
|
+
errors["unexpected directories"] = sdirs_a - sdirs_b
|
1566
|
+
errors["missing directories"] = sdirs_b - sdirs_a
|
1567
|
+
|
1568
|
+
sfiles_a = set(ifiles_a)
|
1569
|
+
sfiles_b = set(ifiles_b)
|
1570
|
+
errors["unexpected files"] = sfiles_a - sfiles_b
|
1571
|
+
errors["missing files"] = sfiles_b - sfiles_a
|
1572
|
+
|
1573
|
+
|
1574
|
+
msgs = [ "%s: %s"% (name, items)
|
1575
|
+
for name, items in errors.iteritems() if items]
|
1576
|
+
|
1577
|
+
if msgs:
|
1578
|
+
msgs.insert(0,"%s and %s differ :" % (
|
1579
|
+
osp.join(path_a, ipath_a),
|
1580
|
+
osp.join(path_b, ipath_b),
|
1581
|
+
))
|
1582
|
+
self.fail("\n".join(msgs))
|
1583
|
+
|
1584
|
+
for files in (ifiles_a, ifiles_b):
|
1585
|
+
files.sort()
|
1586
|
+
|
1587
|
+
for index, path in enumerate(ifiles_a):
|
1588
|
+
self.assertFileEquals(osp.join(path_a, ipath_a, path),
|
1589
|
+
osp.join(path_b, ipath_b, ifiles_b[index]))
|
1590
|
+
|
1591
|
+
except StopIteration:
|
1592
|
+
break
|
1593
|
+
|
1594
|
+
|
1595
|
+
assertDirEqual = assertDirEquals
|
1596
|
+
|
1597
|
+
|
1598
|
+
def assertIsInstance(self, obj, klass, msg=None, strict=False):
|
1599
|
+
"""compares two files using difflib"""
|
1600
|
+
if msg is None:
|
1601
|
+
if strict:
|
1602
|
+
msg = '%r is not of class %s but of %s'
|
1603
|
+
else:
|
1604
|
+
msg = '%r is not an instance of %s but of %s'
|
1605
|
+
msg = msg % (obj, klass, type(obj))
|
1606
|
+
if strict:
|
1607
|
+
self.assert_(obj.__class__ is klass, msg)
|
1608
|
+
else:
|
1609
|
+
self.assert_(isinstance(obj, klass), msg)
|
1610
|
+
|
1611
|
+
def assertIs(self, obj, other, msg=None):
|
1612
|
+
"""compares identity of two reference"""
|
1613
|
+
if msg is None:
|
1614
|
+
msg = "%r is not %r"%(obj, other)
|
1615
|
+
self.assert_(obj is other, msg)
|
1616
|
+
|
1617
|
+
|
1618
|
+
def assertIsNot(self, obj, other, msg=None):
|
1619
|
+
"""compares identity of two reference"""
|
1620
|
+
if msg is None:
|
1621
|
+
msg = "%r is %r"%(obj, other)
|
1622
|
+
self.assert_(obj is not other, msg )
|
1623
|
+
|
1624
|
+
def assertNone(self, obj, msg=None):
|
1625
|
+
"""assert obj is None"""
|
1626
|
+
if msg is None:
|
1627
|
+
msg = "reference to %r when None expected"%(obj,)
|
1628
|
+
self.assert_( obj is None, msg )
|
1629
|
+
|
1630
|
+
def assertNotNone(self, obj, msg=None):
|
1631
|
+
"""assert obj is not None"""
|
1632
|
+
if msg is None:
|
1633
|
+
msg = "unexpected reference to None"
|
1634
|
+
self.assert_( obj is not None, msg )
|
1635
|
+
|
1636
|
+
def assertFloatAlmostEquals(self, obj, other, prec=1e-5, msg=None):
|
1637
|
+
"""compares two floats"""
|
1638
|
+
if msg is None:
|
1639
|
+
msg = "%r != %r" % (obj, other)
|
1640
|
+
self.assert_(math.fabs(obj - other) < prec, msg)
|
1641
|
+
|
1642
|
+
def failUnlessRaises(self, excClass, callableObj, *args, **kwargs):
|
1643
|
+
"""override default failUnlessRaise method to return the raised
|
1644
|
+
exception instance.
|
1645
|
+
|
1646
|
+
Fail unless an exception of class excClass is thrown
|
1647
|
+
by callableObj when invoked with arguments args and keyword
|
1648
|
+
arguments kwargs. If a different type of exception is
|
1649
|
+
thrown, it will not be caught, and the test case will be
|
1650
|
+
deemed to have suffered an error, exactly as for an
|
1651
|
+
unexpected exception.
|
1652
|
+
"""
|
1653
|
+
try:
|
1654
|
+
callableObj(*args, **kwargs)
|
1655
|
+
except excClass, exc:
|
1656
|
+
return exc
|
1657
|
+
else:
|
1658
|
+
if hasattr(excClass, '__name__'):
|
1659
|
+
excName = excClass.__name__
|
1660
|
+
else:
|
1661
|
+
excName = str(excClass)
|
1662
|
+
raise self.failureException, "%s not raised" % excName
|
1663
|
+
|
1664
|
+
assertRaises = failUnlessRaises
|
1665
|
+
|
1666
|
+
import doctest
|
1667
|
+
|
1668
|
+
class SkippedSuite(unittest.TestSuite):
|
1669
|
+
def test(self):
|
1670
|
+
"""just there to trigger test execution"""
|
1671
|
+
self.skipped_test('doctest module has no DocTestSuite class')
|
1672
|
+
|
1673
|
+
|
1674
|
+
# DocTestFinder was introduced in python2.4
|
1675
|
+
if sys.version_info >= (2, 4):
|
1676
|
+
class DocTestFinder(doctest.DocTestFinder):
|
1677
|
+
|
1678
|
+
def __init__(self, *args, **kwargs):
|
1679
|
+
self.skipped = kwargs.pop('skipped', ())
|
1680
|
+
doctest.DocTestFinder.__init__(self, *args, **kwargs)
|
1681
|
+
|
1682
|
+
def _get_test(self, obj, name, module, globs, source_lines):
|
1683
|
+
"""override default _get_test method to be able to skip tests
|
1684
|
+
according to skipped attribute's value
|
1685
|
+
|
1686
|
+
Note: Python (<=2.4) use a _name_filter which could be used for that
|
1687
|
+
purpose but it's no longer available in 2.5
|
1688
|
+
Python 2.5 seems to have a [SKIP] flag
|
1689
|
+
"""
|
1690
|
+
if getattr(obj, '__name__', '') in self.skipped:
|
1691
|
+
return None
|
1692
|
+
return doctest.DocTestFinder._get_test(self, obj, name, module,
|
1693
|
+
globs, source_lines)
|
1694
|
+
else:
|
1695
|
+
# this is a hack to make skipped work with python <= 2.3
|
1696
|
+
class DocTestFinder(object):
|
1697
|
+
def __init__(self, skipped):
|
1698
|
+
self.skipped = skipped
|
1699
|
+
self.original_find_tests = doctest._find_tests
|
1700
|
+
doctest._find_tests = self._find_tests
|
1701
|
+
|
1702
|
+
def _find_tests(self, module, prefix=None):
|
1703
|
+
tests = []
|
1704
|
+
for testinfo in self.original_find_tests(module, prefix):
|
1705
|
+
testname, _, _, _ = testinfo
|
1706
|
+
# testname looks like A.B.C.function_name
|
1707
|
+
testname = testname.split('.')[-1]
|
1708
|
+
if testname not in self.skipped:
|
1709
|
+
tests.append(testinfo)
|
1710
|
+
return tests
|
1711
|
+
|
1712
|
+
|
1713
|
+
class DocTest(TestCase):
|
1714
|
+
"""trigger module doctest
|
1715
|
+
I don't know how to make unittest.main consider the DocTestSuite instance
|
1716
|
+
without this hack
|
1717
|
+
"""
|
1718
|
+
skipped = ()
|
1719
|
+
def __call__(self, result=None, runcondition=None, options=None):\
|
1720
|
+
# pylint: disable-msg=W0613
|
1721
|
+
try:
|
1722
|
+
finder = DocTestFinder(skipped=self.skipped)
|
1723
|
+
if sys.version_info >= (2, 4):
|
1724
|
+
suite = doctest.DocTestSuite(self.module, test_finder=finder)
|
1725
|
+
else:
|
1726
|
+
suite = doctest.DocTestSuite(self.module)
|
1727
|
+
except AttributeError:
|
1728
|
+
suite = SkippedSuite()
|
1729
|
+
return suite.run(result)
|
1730
|
+
run = __call__
|
1731
|
+
|
1732
|
+
def test(self):
|
1733
|
+
"""just there to trigger test execution"""
|
1734
|
+
|
1735
|
+
MAILBOX = None
|
1736
|
+
|
1737
|
+
class MockSMTP:
|
1738
|
+
"""fake smtplib.SMTP"""
|
1739
|
+
|
1740
|
+
def __init__(self, host, port):
|
1741
|
+
self.host = host
|
1742
|
+
self.port = port
|
1743
|
+
global MAILBOX
|
1744
|
+
self.reveived = MAILBOX = []
|
1745
|
+
|
1746
|
+
def set_debuglevel(self, debuglevel):
|
1747
|
+
"""ignore debug level"""
|
1748
|
+
|
1749
|
+
def sendmail(self, fromaddr, toaddres, body):
|
1750
|
+
"""push sent mail in the mailbox"""
|
1751
|
+
self.reveived.append((fromaddr, toaddres, body))
|
1752
|
+
|
1753
|
+
def quit(self):
|
1754
|
+
"""ignore quit"""
|
1755
|
+
|
1756
|
+
|
1757
|
+
class MockConfigParser(ConfigParser):
|
1758
|
+
"""fake ConfigParser.ConfigParser"""
|
1759
|
+
|
1760
|
+
def __init__(self, options):
|
1761
|
+
ConfigParser.__init__(self)
|
1762
|
+
for section, pairs in options.iteritems():
|
1763
|
+
self.add_section(section)
|
1764
|
+
for key, value in pairs.iteritems():
|
1765
|
+
self.set(section,key,value)
|
1766
|
+
def write(self, _):
|
1767
|
+
raise NotImplementedError()
|
1768
|
+
|
1769
|
+
|
1770
|
+
class MockConnection:
|
1771
|
+
"""fake DB-API 2.0 connexion AND cursor (i.e. cursor() return self)"""
|
1772
|
+
|
1773
|
+
def __init__(self, results):
|
1774
|
+
self.received = []
|
1775
|
+
self.states = []
|
1776
|
+
self.results = results
|
1777
|
+
|
1778
|
+
def cursor(self):
|
1779
|
+
"""Mock cursor method"""
|
1780
|
+
return self
|
1781
|
+
def execute(self, query, args=None):
|
1782
|
+
"""Mock execute method"""
|
1783
|
+
self.received.append( (query, args) )
|
1784
|
+
def fetchone(self):
|
1785
|
+
"""Mock fetchone method"""
|
1786
|
+
return self.results[0]
|
1787
|
+
def fetchall(self):
|
1788
|
+
"""Mock fetchall method"""
|
1789
|
+
return self.results
|
1790
|
+
def commit(self):
|
1791
|
+
"""Mock commiy method"""
|
1792
|
+
self.states.append( ('commit', len(self.received)) )
|
1793
|
+
def rollback(self):
|
1794
|
+
"""Mock rollback method"""
|
1795
|
+
self.states.append( ('rollback', len(self.received)) )
|
1796
|
+
def close(self):
|
1797
|
+
"""Mock close method"""
|
1798
|
+
pass
|
1799
|
+
|
1800
|
+
|
1801
|
+
def mock_object(**params):
|
1802
|
+
"""creates an object using params to set attributes
|
1803
|
+
>>> option = mock_object(verbose=False, index=range(5))
|
1804
|
+
>>> option.verbose
|
1805
|
+
False
|
1806
|
+
>>> option.index
|
1807
|
+
[0, 1, 2, 3, 4]
|
1808
|
+
"""
|
1809
|
+
return type('Mock', (), params)()
|
1810
|
+
|
1811
|
+
|
1812
|
+
def create_files(paths, chroot):
|
1813
|
+
"""Creates directories and files found in <path>.
|
1814
|
+
|
1815
|
+
:param paths: list of relative paths to files or directories
|
1816
|
+
:param chroot: the root directory in which paths will be created
|
1817
|
+
|
1818
|
+
>>> from os.path import isdir, isfile
|
1819
|
+
>>> isdir('/tmp/a')
|
1820
|
+
False
|
1821
|
+
>>> create_files(['a/b/foo.py', 'a/b/c/', 'a/b/c/d/e.py'], '/tmp')
|
1822
|
+
>>> isdir('/tmp/a')
|
1823
|
+
True
|
1824
|
+
>>> isdir('/tmp/a/b/c')
|
1825
|
+
True
|
1826
|
+
>>> isfile('/tmp/a/b/c/d/e.py')
|
1827
|
+
True
|
1828
|
+
>>> isfile('/tmp/a/b/foo.py')
|
1829
|
+
True
|
1830
|
+
"""
|
1831
|
+
dirs, files = set(), set()
|
1832
|
+
for path in paths:
|
1833
|
+
path = osp.join(chroot, path)
|
1834
|
+
filename = osp.basename(path)
|
1835
|
+
# path is a directory path
|
1836
|
+
if filename == '':
|
1837
|
+
dirs.add(path)
|
1838
|
+
# path is a filename path
|
1839
|
+
else:
|
1840
|
+
dirs.add(osp.dirname(path))
|
1841
|
+
files.add(path)
|
1842
|
+
for dirpath in dirs:
|
1843
|
+
if not osp.isdir(dirpath):
|
1844
|
+
os.makedirs(dirpath)
|
1845
|
+
for filepath in files:
|
1846
|
+
file(filepath, 'w').close()
|
1847
|
+
|
1848
|
+
def enable_dbc(*args):
|
1849
|
+
"""
|
1850
|
+
Without arguments, return True if contracts can be enabled and should be
|
1851
|
+
enabled (see option -d), return False otherwise.
|
1852
|
+
|
1853
|
+
With arguments, return False if contracts can't or shouldn't be enabled,
|
1854
|
+
otherwise weave ContractAspect with items passed as arguments.
|
1855
|
+
"""
|
1856
|
+
if not ENABLE_DBC:
|
1857
|
+
return False
|
1858
|
+
try:
|
1859
|
+
from logilab.aspects.weaver import weaver
|
1860
|
+
from logilab.aspects.lib.contracts import ContractAspect
|
1861
|
+
except ImportError:
|
1862
|
+
sys.stderr.write(
|
1863
|
+
'Warning: logilab.aspects is not available. Contracts disabled.')
|
1864
|
+
return False
|
1865
|
+
for arg in args:
|
1866
|
+
weaver.weave_module(arg, ContractAspect)
|
1867
|
+
return True
|
1868
|
+
|
1869
|
+
|
1870
|
+
class AttrObject: # XXX cf mock_object
|
1871
|
+
def __init__(self, **kwargs):
|
1872
|
+
self.__dict__.update(kwargs)
|
1873
|
+
|
1874
|
+
def tag(*args):
|
1875
|
+
"""descriptor adding tag to a function"""
|
1876
|
+
def desc(func):
|
1877
|
+
assert not hasattr(func, 'tags')
|
1878
|
+
func.tags = Tags(args)
|
1879
|
+
return func
|
1880
|
+
return desc
|
1881
|
+
|
1882
|
+
class Tags(set):
|
1883
|
+
"""A set of tag able validate an expression"""
|
1884
|
+
def __getitem__(self, key):
|
1885
|
+
return key in self
|
1886
|
+
|
1887
|
+
def match(self, exp):
|
1888
|
+
return eval(exp, {}, self)
|
1889
|
+
|
1890
|
+
def require_version(version):
|
1891
|
+
""" Compare version of python interpreter to the given one. Skip the test
|
1892
|
+
if older.
|
1893
|
+
"""
|
1894
|
+
def check_require_version(f):
|
1895
|
+
version_elements = version.split('.')
|
1896
|
+
try:
|
1897
|
+
compare = tuple([int(v) for v in version_elements])
|
1898
|
+
except ValueError:
|
1899
|
+
raise ValueError('%s is not a correct version : should be X.Y[.Z].' % version)
|
1900
|
+
current = sys.version_info[:3]
|
1901
|
+
#print 'comp', current, compare
|
1902
|
+
if current < compare:
|
1903
|
+
#print 'version too old'
|
1904
|
+
def new_f(self, *args, **kwargs):
|
1905
|
+
self.skip('Need at least %s version of python. Current version is %s.' % (version, '.'.join([str(element) for element in current])))
|
1906
|
+
new_f.__name__ = f.__name__
|
1907
|
+
return new_f
|
1908
|
+
else:
|
1909
|
+
#print 'version young enough'
|
1910
|
+
return f
|
1911
|
+
return check_require_version
|
1912
|
+
|
1913
|
+
def require_module(module):
|
1914
|
+
""" Check if the given module is loaded. Skip the test if not.
|
1915
|
+
"""
|
1916
|
+
def check_require_module(f):
|
1917
|
+
try:
|
1918
|
+
__import__(module)
|
1919
|
+
#print module, 'imported'
|
1920
|
+
return f
|
1921
|
+
except ImportError:
|
1922
|
+
#print module, 'can not be imported'
|
1923
|
+
def new_f(self, *args, **kwargs):
|
1924
|
+
self.skip('%s can not be imported.' % module)
|
1925
|
+
new_f.__name__ = f.__name__
|
1926
|
+
return new_f
|
1927
|
+
return check_require_module
|