summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Pellerin <jpellerin@gmail.com>2009-04-18 19:00:45 +0000
committerJason Pellerin <jpellerin@gmail.com>2009-04-18 19:00:45 +0000
commitc5bd03442781ce1c974920708e958d58d1ff6289 (patch)
tree04041c48ada439b6ca6510bb34007f7dd94b33be
parent296fee10bf942cf860326c88d5c5df6e906a8c7b (diff)
downloadnose-c5bd03442781ce1c974920708e958d58d1ff6289.tar.gz
Committed PyCon sprint work.
-rw-r--r--.hgignore11
-rw-r--r--AUTHORS2
-rw-r--r--CHANGELOG6
-rw-r--r--doc/.static/nose.css74
-rw-r--r--doc/.templates/index.html52
-rw-r--r--doc/.templates/indexsidebar.html35
-rw-r--r--doc/.templates/layout.html5
-rw-r--r--doc/.templates/page.html7
-rw-r--r--doc/Makefile75
-rw-r--r--doc/api.rst20
-rw-r--r--doc/api/commands.rst2
-rw-r--r--doc/api/config.rst5
-rw-r--r--doc/api/core.rst5
-rw-r--r--doc/api/importer.rst5
-rw-r--r--doc/api/inspector.rst5
-rw-r--r--doc/api/loader.rst2
-rw-r--r--doc/api/plugin_manager.rst2
-rw-r--r--doc/api/proxy.rst2
-rw-r--r--doc/api/result.rst2
-rw-r--r--doc/api/selector.rst2
-rw-r--r--doc/api/suite.rst2
-rw-r--r--doc/api/test_cases.rst8
-rw-r--r--doc/api/twistedtools.rst2
-rw-r--r--doc/api/util.rst5
-rw-r--r--doc/conf.py232
-rw-r--r--doc/developing.rst18
-rw-r--r--doc/doc_tests1
-rw-r--r--doc/docstring.py25
-rw-r--r--doc/finding_tests.rst32
-rw-r--r--doc/further_reading.rst35
-rw-r--r--doc/index.rst55
-rw-r--r--doc/more_info.rst48
-rw-r--r--doc/news.rst78
-rw-r--r--doc/plugins.rst69
-rw-r--r--doc/plugins/allmodules.rst4
-rw-r--r--doc/plugins/attrib.rst4
-rw-r--r--doc/plugins/builtin.rst22
-rw-r--r--doc/plugins/capture.rst5
-rw-r--r--doc/plugins/collect.rst4
-rw-r--r--doc/plugins/cover.rst4
-rw-r--r--doc/plugins/debug.rst4
-rw-r--r--doc/plugins/deprecated.rst4
-rw-r--r--doc/plugins/doctests.rst4
-rw-r--r--doc/plugins/errorclasses.rst2
-rw-r--r--doc/plugins/failuredetail.rst4
-rw-r--r--doc/plugins/interface.rst124
-rw-r--r--doc/plugins/isolate.rst4
-rw-r--r--doc/plugins/logcapture.rst4
-rw-r--r--doc/plugins/multiprocess.rst5
-rw-r--r--doc/plugins/prof.rst4
-rw-r--r--doc/plugins/skip.rst5
-rw-r--r--doc/plugins/testid.rst4
-rw-r--r--doc/plugins/testing.rst2
-rw-r--r--doc/plugins/writing.rst1
-rw-r--r--doc/plugins/xunit.rst4
-rw-r--r--doc/setuptools_integration.rst29
-rw-r--r--doc/testing.rst55
-rw-r--r--doc/testing_tools.rst11
-rw-r--r--doc/usage.rst42
-rw-r--r--doc/writing_tests.rst170
-rw-r--r--functional_tests/doc_tests/test_addplugins/support/test.py2
-rw-r--r--functional_tests/doc_tests/test_addplugins/test_addplugins.rst80
-rw-r--r--functional_tests/doc_tests/test_allmodules/support/mod.py5
-rw-r--r--functional_tests/doc_tests/test_allmodules/support/test.py2
-rw-r--r--functional_tests/doc_tests/test_allmodules/test_allmodules.rst67
-rw-r--r--functional_tests/doc_tests/test_coverage_html/coverage_html.rst22
-rw-r--r--functional_tests/doc_tests/test_init_plugin/init_plugin.rst2
-rw-r--r--functional_tests/doc_tests/test_issue089/unwanted_package.rst10
-rw-r--r--functional_tests/doc_tests/test_issue097/plugintest_environment.rst4
-rw-r--r--functional_tests/doc_tests/test_issue107/plugin_exceptions.rst2
-rw-r--r--functional_tests/doc_tests/test_issue145/imported_tests.rst2
-rw-r--r--functional_tests/doc_tests/test_multiprocess/multiprocess.rst30
-rw-r--r--functional_tests/doc_tests/test_multiprocess/multiprocess_fixtures.py30
-rw-r--r--functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst2
-rw-r--r--functional_tests/doc_tests/test_selector_plugin/selector_plugin.rst2
-rw-r--r--functional_tests/doc_tests/test_xunit_plugin/support/test_skip.py13
-rw-r--r--functional_tests/doc_tests/test_xunit_plugin/test_skips.rst40
-rw-r--r--functional_tests/support/test_buggy_generators.py29
-rw-r--r--functional_tests/support/xunit/test_xunit_as_suite.py20
-rw-r--r--functional_tests/test_buggy_generators.py35
-rw-r--r--functional_tests/test_generator_fixtures.py58
-rw-r--r--functional_tests/test_id_plugin.py7
-rw-r--r--functional_tests/test_importer.py35
-rw-r--r--functional_tests/test_namespace_pkg.py16
-rw-r--r--functional_tests/test_xunit.py36
-rw-r--r--nose/__init__.py393
-rw-r--r--nose/case.py1
-rw-r--r--nose/config.py34
-rw-r--r--nose/core.py185
-rw-r--r--nose/importer.py4
-rw-r--r--nose/loader.py53
-rw-r--r--nose/plugins/__init__.py121
-rw-r--r--nose/plugins/allmodules.py45
-rw-r--r--nose/plugins/attrib.py28
-rw-r--r--nose/plugins/base.py182
-rw-r--r--nose/plugins/builtin.py6
-rw-r--r--nose/plugins/capture.py29
-rw-r--r--nose/plugins/collect.py93
-rw-r--r--nose/plugins/cover.py41
-rw-r--r--nose/plugins/debug.py13
-rw-r--r--nose/plugins/deprecated.py21
-rw-r--r--nose/plugins/doctests.py35
-rw-r--r--nose/plugins/errorclass.py4
-rw-r--r--nose/plugins/failuredetail.py7
-rw-r--r--nose/plugins/isolate.py2
-rw-r--r--nose/plugins/logcapture.py52
-rw-r--r--nose/plugins/manager.py24
-rw-r--r--nose/plugins/multiprocess.py99
-rw-r--r--nose/plugins/plugintest.py97
-rw-r--r--nose/plugins/prof.py20
-rw-r--r--nose/plugins/skip.py9
-rw-r--r--nose/plugins/testid.py212
-rw-r--r--nose/plugins/xunit.py192
-rw-r--r--nose/proxy.py2
-rw-r--r--nose/result.py3
-rw-r--r--nose/sphinx/__init__.py1
-rw-r--r--nose/sphinx/pluginopts.py184
-rw-r--r--nose/suite.py22
-rw-r--r--nose/tools.py4
-rw-r--r--nose/usage.txt103
-rw-r--r--nose/util.py47
-rwxr-xr-xscripts/mkdocs.py554
-rwxr-xr-xscripts/mkrelease.py84
-rw-r--r--setup.py11
-rw-r--r--unit_tests/test_logcapture_plugin.py36
-rw-r--r--unit_tests/test_multiprocess_runner.py10
-rw-r--r--unit_tests/test_xunit.py205
127 files changed, 3575 insertions, 1698 deletions
diff --git a/.hgignore b/.hgignore
index fcea5ca..3f4f480 100644
--- a/.hgignore
+++ b/.hgignore
@@ -1,9 +1,18 @@
syntax: glob
*~
*.pyc
+*.pyo
+.DS_Store
*.egg-info
*.bak
*.rej
*$py.class
*.orig
-.coverage \ No newline at end of file
+example.cfg
+.coverage
+nosetests.xml
+xunit.xml
+support/cover/index.html
+_env
+doc/.build
+dist
diff --git a/AUTHORS b/AUTHORS
index 0c5e4bd..dffc9e0 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -9,3 +9,5 @@ John J Lee
Allen Bierbaum
Pam Zerbinos
Augie Fackler
+Peter Fein
+Kevin Mitchell \ No newline at end of file
diff --git a/CHANGELOG b/CHANGELOG
index 0487278..86ede2c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,13 +6,15 @@
failing tests. Thanks to Max Ischenko for the implementation.
- Added optional HTML coverage reports to coverage plugin. Thanks to Augie
Fackler for the patch.
-- Deprecated support for `python setup.py test` via setuptools. Use
- `python setup.py nosetests` instead.
+- Added plugin that enables collection of tests in all modules. Thanks to
+ Peter Fein for the patch (#137).
- Made it possible to 'yield test' in addition to 'yield test,' from test
generators. Thanks to Chad Whitacre for the patch (#230).
- Fixed bug that caused traceback inspector to fail when source code file
could not be found. Thanks to Philip Jenvey for the bug report and patch
(#236).
+- Fixed some issues limiting compatibility with IronPython. Thanks to Kevin
+ Mitchell for the patch.
0.10.4
diff --git a/doc/.static/nose.css b/doc/.static/nose.css
new file mode 100644
index 0000000..38602e2
--- /dev/null
+++ b/doc/.static/nose.css
@@ -0,0 +1,74 @@
+@import url(default.css);
+
+body {
+ padding-left: 20px;
+ background-color: #fff;
+ font: x-small Georgia,Serif;
+ font-size/* */:/**/small;
+ font-size: /**/small;
+}
+
+div.body { border-right: 1px solid #ccc; }
+
+div.body h1 { margin-top: 0; font-size: 130%; margin-right: 0px; }
+div.body h2 { font-size: 120%; }
+div.body h3 { font-size: 115%; }
+div.body h4 { font-size: 110%; }
+div.body h5 { font-size: 106%; }
+div.body h6 { font-size: 103%; }
+
+div.body h2,
+div.body h3,
+div.body h4,
+div.body h5,
+div.body h6 {
+ border: none;
+ background-color: #fff;
+}
+
+.new {
+ color: #f00;
+ font-weight: bold;
+}
+
+pre, tt {
+ background-color: #ffe;
+}
+
+div.body h1.big {
+ font-family: courier, "courier new", monospace;
+ font-weight: bold;
+ font-size: 800%;
+ background-color: #fff;
+ margin: 0px -20px 0px -25px;
+ padding: 0px 0px 0px 10px;
+ border: none;
+ color: #000;
+}
+
+div.body h2.big {
+ font-weight: bold;
+ margin: -10px -20px 0px -20px;
+}
+
+p.big {
+ font-family: 'Trebuchet MS', sans-serif;
+ font-weight: bold;
+ font-size: 200%;
+ margin-left: -20px;
+ padding-left: 10px;
+}
+
+span.biglink {
+ font-size: 1.3em
+}
+
+table.contentstable td {
+ vertical-align: top
+ padding-left: 0px;
+ padding-right: 10px;
+}
+
+table.contentstable td p {
+ text-align: left;
+} \ No newline at end of file
diff --git a/doc/.templates/index.html b/doc/.templates/index.html
new file mode 100644
index 0000000..8615c93
--- /dev/null
+++ b/doc/.templates/index.html
@@ -0,0 +1,52 @@
+<h1 class="big">nose</h1>
+<h2 class="big">is nicer testing for python</h2>
+
+<p class="big">nose extends unittest to make testing easier.</p>
+
+{{ body }}
+
+<h1>Documentation</h1>
+
+<table class="contentstable">
+ <tr>
+ <td width="50%">
+ <a class="biglink" href="{{ pathto("testing") }}">
+ Testing with nose</a>
+ <p>Find out how to write, find and run test using nose.
+ <a href="{{ pathto("testing") }}">More &gt;</a></p>
+ </td>
+ <td width="50%">
+ <a class="biglink" href="{{ pathto("developing") }}">
+ Developing with nose</a>
+ <p>Find out how to write your own plugins, and about nose
+ internals. <a href="{{ pathto("developing") }}">More &gt;</a></p>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <a class="biglink" href="{{ pathto("news") }}">
+ News</a>
+ <p>What's new in this release?
+ <a href="{{ pathto("news") }}">More &gt;</a></p>
+ </td>
+ <td>
+ <a class="biglink" href="{{ pathto("further_reading") }}">
+ Further reading</a>
+ <p>Plugin recipes and usage examples, trivia and other
+ uncategorizable items.
+ <a href="{{ pathto("further_reading") }}">More &gt;</a></p>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span class="biglink">Indices and tables</span>
+ <ul>
+ <li><a href="{{ pathto("genindex") }}">
+ Complete index</a></li>
+ <li><a href="{{ pathto("modindex") }}">
+ Index of documented modules</a></li>
+ </ul>
+ </td>
+ <td></td>
+ </tr>
+</table>
diff --git a/doc/.templates/indexsidebar.html b/doc/.templates/indexsidebar.html
new file mode 100644
index 0000000..30815ab
--- /dev/null
+++ b/doc/.templates/indexsidebar.html
@@ -0,0 +1,35 @@
+<h3><a href="/mrl/projects/nose/nose-{{ version }}.tar.gz">Download</a></h3>
+<ul>
+ <li><a href="/mrl/projects/nose/nose-{{ version }}.tar.gz">
+ Current version: {{ version }}</a>
+ <br />({{ last_updated }})
+ </li>
+</ul>
+
+<h3>Install</h3>
+<ul>
+ <li>Current version:<br />
+ <tt>easy_install nose=={{ version }}</tt></li>
+ <li>Unstable (trunk):<br />
+ <tt>easy_install nose==dev</tt>
+ </li>
+</ul>
+
+<h3><a href="http://groups.google.com/group/nose-announce">
+ Announcement list</a></h3>
+<ul><li>Sign up to receive email announcements
+ of new releases</li></ul>
+
+<h3><a href="http://code.google.com/p/python-nose/">Tracker</a></h3>
+<ul><li>Report bugs, request features, wik the wiki, browse source.</li></ul>
+
+<h3>Other links</h3>
+<ul>
+ <li>
+ <a href="http://codespeak.net/py/current/doc/test.html">py.test</a>
+ </li>
+ <li>
+ <a href="http://peak.telecommunity.com/DevCenter/setuptools">
+ setuptools</a>
+ </li>
+</ul>
diff --git a/doc/.templates/layout.html b/doc/.templates/layout.html
new file mode 100644
index 0000000..4237ef7
--- /dev/null
+++ b/doc/.templates/layout.html
@@ -0,0 +1,5 @@
+{% extends "!layout.html" %}
+
+{%- block relbar1 %}
+{% if pagename != 'index' %}{{ super() }}{% endif %}
+{% endblock %}
diff --git a/doc/.templates/page.html b/doc/.templates/page.html
new file mode 100644
index 0000000..3416437
--- /dev/null
+++ b/doc/.templates/page.html
@@ -0,0 +1,7 @@
+{% extends "!page.html" %}
+{% block body %}
+{% if pagename == 'index' %}
+{% include "index.html" %}
+{% else %}
+{{ super() }}
+{% endif %}{% endblock %}
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..ef87680
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,75 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html web pickle htmlhelp latex changes linkcheck
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " changes to make an overview over all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+
+clean:
+ -rm -rf .build/*
+
+html:
+ mkdir -p .build/html .build/doctrees
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html
+ @echo
+ @echo "Build finished. The HTML pages are in .build/html."
+
+pickle:
+ mkdir -p .build/pickle .build/doctrees
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+web: pickle
+
+json:
+ mkdir -p .build/json .build/doctrees
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) .build/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ mkdir -p .build/htmlhelp .build/doctrees
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in .build/htmlhelp."
+
+latex:
+ mkdir -p .build/latex .build/doctrees
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in .build/latex."
+ @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+ "run these through (pdf)latex."
+
+changes:
+ mkdir -p .build/changes .build/doctrees
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes
+ @echo
+ @echo "The overview file is in .build/changes."
+
+linkcheck:
+ mkdir -p .build/linkcheck .build/doctrees
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in .build/linkcheck/output.txt."
diff --git a/doc/api.rst b/doc/api.rst
new file mode 100644
index 0000000..b2dd665
--- /dev/null
+++ b/doc/api.rst
@@ -0,0 +1,20 @@
+nose internals
+==============
+
+.. toctree ::
+ :maxdepth: 2
+
+ api/core
+ api/loader
+ api/selector
+ api/config
+ api/test_cases
+ api/suite
+ api/result
+ api/proxy
+ api/plugin_manager
+ api/importer
+ api/commands
+ api/twistedtools
+ api/inspector
+ api/util
diff --git a/doc/api/commands.rst b/doc/api/commands.rst
new file mode 100644
index 0000000..c9ae14a
--- /dev/null
+++ b/doc/api/commands.rst
@@ -0,0 +1,2 @@
+.. automodule :: nose.commands
+ :members: \ No newline at end of file
diff --git a/doc/api/config.rst b/doc/api/config.rst
new file mode 100644
index 0000000..077cf9b
--- /dev/null
+++ b/doc/api/config.rst
@@ -0,0 +1,5 @@
+Configuration
+=============
+
+.. automodule :: nose.config
+ :members: \ No newline at end of file
diff --git a/doc/api/core.rst b/doc/api/core.rst
new file mode 100644
index 0000000..6e58329
--- /dev/null
+++ b/doc/api/core.rst
@@ -0,0 +1,5 @@
+Test runner and main()
+======================
+
+.. automodule :: nose.core
+ :members:
diff --git a/doc/api/importer.rst b/doc/api/importer.rst
new file mode 100644
index 0000000..956fdb3
--- /dev/null
+++ b/doc/api/importer.rst
@@ -0,0 +1,5 @@
+Importer
+========
+
+.. automodule :: nose.importer
+ :members: \ No newline at end of file
diff --git a/doc/api/inspector.rst b/doc/api/inspector.rst
new file mode 100644
index 0000000..e204985
--- /dev/null
+++ b/doc/api/inspector.rst
@@ -0,0 +1,5 @@
+Traceback inspector
+===================
+
+.. automodule :: nose.inspector
+ :members: \ No newline at end of file
diff --git a/doc/api/loader.rst b/doc/api/loader.rst
new file mode 100644
index 0000000..741dd22
--- /dev/null
+++ b/doc/api/loader.rst
@@ -0,0 +1,2 @@
+.. automodule :: nose.loader
+ :members: \ No newline at end of file
diff --git a/doc/api/plugin_manager.rst b/doc/api/plugin_manager.rst
new file mode 100644
index 0000000..5c1e393
--- /dev/null
+++ b/doc/api/plugin_manager.rst
@@ -0,0 +1,2 @@
+.. automodule :: nose.plugins.manager
+ :members: \ No newline at end of file
diff --git a/doc/api/proxy.rst b/doc/api/proxy.rst
new file mode 100644
index 0000000..1802074
--- /dev/null
+++ b/doc/api/proxy.rst
@@ -0,0 +1,2 @@
+.. automodule :: nose.proxy
+ :members: \ No newline at end of file
diff --git a/doc/api/result.rst b/doc/api/result.rst
new file mode 100644
index 0000000..75b110d
--- /dev/null
+++ b/doc/api/result.rst
@@ -0,0 +1,2 @@
+.. automodule :: nose.result
+ :members: \ No newline at end of file
diff --git a/doc/api/selector.rst b/doc/api/selector.rst
new file mode 100644
index 0000000..d3de5a4
--- /dev/null
+++ b/doc/api/selector.rst
@@ -0,0 +1,2 @@
+.. automodule :: nose.selector
+ :members: \ No newline at end of file
diff --git a/doc/api/suite.rst b/doc/api/suite.rst
new file mode 100644
index 0000000..9b764b0
--- /dev/null
+++ b/doc/api/suite.rst
@@ -0,0 +1,2 @@
+.. automodule :: nose.suite
+ :members: \ No newline at end of file
diff --git a/doc/api/test_cases.rst b/doc/api/test_cases.rst
new file mode 100644
index 0000000..2508f54
--- /dev/null
+++ b/doc/api/test_cases.rst
@@ -0,0 +1,8 @@
+Test Cases
+==========
+
+.. automodule :: nose.case
+ :members:
+
+.. autoclass :: nose.failure.Failure
+ :members:
diff --git a/doc/api/twistedtools.rst b/doc/api/twistedtools.rst
new file mode 100644
index 0000000..584d9c7
--- /dev/null
+++ b/doc/api/twistedtools.rst
@@ -0,0 +1,2 @@
+.. automodule :: nose.twistedtools
+ :members: \ No newline at end of file
diff --git a/doc/api/util.rst b/doc/api/util.rst
new file mode 100644
index 0000000..f4b683e
--- /dev/null
+++ b/doc/api/util.rst
@@ -0,0 +1,5 @@
+Utility functions
+=================
+
+.. automodule :: nose.util
+ :members: \ No newline at end of file
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644
index 0000000..55fe365
--- /dev/null
+++ b/doc/conf.py
@@ -0,0 +1,232 @@
+# -*- coding: utf-8 -*-
+#
+# nose documentation build configuration file, created by
+# sphinx-quickstart on Thu Mar 26 16:49:00 2009.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# The contents of this file are pickled, so don't put values in the namespace
+# that aren't pickleable (module imports are okay, they're removed automatically).
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If your extensions are in another directory, add it here. If the directory
+# is relative to the documentation root, use os.path.abspath to make it
+# absolute, like shown here.
+sys.path.extend([
+ os.path.abspath('.'),
+ os.path.abspath(
+ os.path.join(os.path.dirname(__file__), '..'))])
+
+# General configuration
+# ---------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'docstring',
+ 'nose.sphinx.pluginopts']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['.templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'nose'
+copyright = u'2009, Jason Pellerin'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.11'
+# The full version, including alpha/beta/rc tags.
+release = '0.11'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['.build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'trac'
+
+
+# Options for HTML output
+# -----------------------
+
+# The style sheet to use for HTML and HTML Help pages. A file of that name
+# must exist either in Sphinx' static/ path, or in one of the custom paths
+# given in html_static_path.
+html_style = 'nose.css'
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['.static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+html_sidebars = {
+ 'index': 'indexsidebar.html'
+ }
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {'index': 'index.html'}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, the reST sources are included in the HTML build as _sources/<name>.
+#html_copy_source = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'nosedoc'
+
+"""
+footerbgcolor (CSS color): Background color for the footer line.
+footertextcolor (CSS color): Text color for the footer line.
+sidebarbgcolor (CSS color): Background color for the sidebar.
+sidebartextcolor (CSS color): Text color for the sidebar.
+sidebarlinkcolor (CSS color): Link color for the sidebar.
+relbarbgcolor (CSS color): Background color for the relation bar.
+relbartextcolor (CSS color): Text color for the relation bar.
+relbarlinkcolor (CSS color): Link color for the relation bar.
+bgcolor (CSS color): Body background color.
+textcolor (CSS color): Body text color.
+linkcolor (CSS color): Body link color.
+headbgcolor (CSS color): Background color for headings.
+headtextcolor (CSS color): Text color for headings.
+headlinkcolor (CSS color): Link color for headings.
+codebgcolor (CSS color): Background color for code blocks.
+codetextcolor (CSS color): Default text color for code blocks, if not set differently by the highlighting style.
+bodyfont (CSS font-family): Font for normal text.
+headfont (CSS font-family): Font for headings.
+"""
+html_theme_options = {
+ 'rightsidebar': 'true',
+ 'sidebarbgcolor': '#fff',
+ 'sidebartextcolor': '#20435c',
+ 'sidebarlinkcolor': '#355f7c',
+ 'bgcolor': '#fff',
+ 'codebgcolor': '#ffe',
+ 'headbgcolor': '#fff',
+ 'relbarbgcolor': '#fff',
+ 'relbartextcolor': '#20435c',
+ 'relbarlinkcolor': '#355f7c',
+}
+
+# Options for LaTeX output
+# ------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, document class [howto/manual]).
+latex_documents = [
+ ('index', 'nose.tex', ur'nose Documentation',
+ ur'Jason Pellerin', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/dev': None}
diff --git a/doc/developing.rst b/doc/developing.rst
new file mode 100644
index 0000000..8f720f1
--- /dev/null
+++ b/doc/developing.rst
@@ -0,0 +1,18 @@
+Developing with nose
+====================
+
+Get the code
+------------
+
+.. code-block:: none
+
+ svn co http://python-nose.googlecode.com/svn/trunk/ nose
+
+Read
+----
+
+.. toctree ::
+ :maxdepth: 2
+
+ plugins
+ api \ No newline at end of file
diff --git a/doc/doc_tests b/doc/doc_tests
new file mode 100644
index 0000000..d02ed6f
--- /dev/null
+++ b/doc/doc_tests
@@ -0,0 +1 @@
+../functional_tests/doc_tests \ No newline at end of file
diff --git a/doc/docstring.py b/doc/docstring.py
new file mode 100644
index 0000000..5652bd2
--- /dev/null
+++ b/doc/docstring.py
@@ -0,0 +1,25 @@
+from docutils import nodes
+from docutils.statemachine import ViewList
+from nose.util import resolve_name
+
+
+def docstring_directive(dirname, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ obj_name = arguments[0]
+ obj = resolve_name(obj_name)
+ rst = ViewList()
+ rst.append(obj.__doc__, '<docstring>')
+ print "CALLED", obj_name, obj, rst
+ node = nodes.section()
+ surrounding_title_styles = state.memo.title_styles
+ surrounding_section_level = state.memo.section_level
+ state.memo.title_styles = []
+ state.memo.section_level = 0
+ state.nested_parse(rst, 0, node, match_titles=1)
+ state.memo.title_styles = surrounding_title_styles
+ state.memo.section_level = surrounding_section_level
+ return node.children
+
+
+def setup(app):
+ app.add_directive('docstring', docstring_directive, 1, (1, 0, 1))
diff --git a/doc/finding_tests.rst b/doc/finding_tests.rst
new file mode 100644
index 0000000..5f9cb74
--- /dev/null
+++ b/doc/finding_tests.rst
@@ -0,0 +1,32 @@
+Finding and running tests
+-------------------------
+
+nose, by default, follows a few simple rules for test discovery.
+
+* If it looks like a test, it's a test. Names of directories, modules,
+ classes and functions are compared against the testMatch regular
+ expression, and those that match are considered tests. Any class that is a
+ `unittest.TestCase` subclass is also collected, so long as it is inside of a
+ module that looks like a test.
+
+* Directories that don't look like tests and aren't packages are not
+ inspected.
+
+* Packages are always inspected, but they are only collected if they look
+ like tests. This means that you can include your tests inside of your
+ packages (somepackage/tests) and nose will collect the tests without
+ running package code inappropriately.
+
+* When a project appears to have library and test code organized into
+ separate directories, library directories are examined first.
+
+* When nose imports a module, it adds that module's directory to sys.path;
+ when the module is inside of a package, like package.module, it will be
+ loaded as package.module and the directory of *package* will be added to
+ sys.path.
+
+* If an object defines a __test__ attribute that does not evaluate to
+ True, that object will not be collected, nor will any objects it
+ contains.
+
+Be aware that plugins and command line options can change any of those rules. \ No newline at end of file
diff --git a/doc/further_reading.rst b/doc/further_reading.rst
new file mode 100644
index 0000000..73ff365
--- /dev/null
+++ b/doc/further_reading.rst
@@ -0,0 +1,35 @@
+Further reading
+===============
+
+.. toctree ::
+ :maxdepth: 2
+
+ doc_tests/test_addplugins/test_addplugins.rst
+ doc_tests/test_coverage_html/coverage_html.rst
+ doc_tests/test_doctest_fixtures/doctest_fixtures.rst
+ doc_tests/test_init_plugin/init_plugin.rst
+ doc_tests/test_issue089/unwanted_package.rst
+ doc_tests/test_issue097/plugintest_environment.rst
+ doc_tests/test_issue107/plugin_exceptions.rst
+ doc_tests/test_issue119/empty_plugin.rst
+ doc_tests/test_issue142/errorclass_failure.rst
+ doc_tests/test_issue145/imported_tests.rst
+ doc_tests/test_multiprocess/multiprocess.rst
+ doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst
+ doc_tests/test_selector_plugin/selector_plugin.rst
+ doc_tests/test_xunit_plugin/test_skips.rst
+ doc_tests/test_allmodules/test_allmodules.rst
+ more_info
+
+Articles, etc
+-------------
+
+* `An Extended Introduction to the nose Unit Testing Framework`_:
+ Titus Brown's excellent article provides a great overview of
+ nose and its uses.
+* `My blog`_
+* `Tweets`_
+
+.. _`An Extended Introduction to the nose Unit Testing Framework` : http://ivory.idyll.org/articles/nose-intro.html
+.. _`My blog` : http://somethingaboutorange.com/mrl/
+.. _`Tweets` : http://twitter.com/jpellerin \ No newline at end of file
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644
index 0000000..008ddc6
--- /dev/null
+++ b/doc/index.rst
@@ -0,0 +1,55 @@
+.. nose documentation master file, created by sphinx-quickstart on Thu Mar 26 16:49:00 2009.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+
+Installation and quick start
+============================
+
+*On most UNIX-like systems, you'll probably need to run these commands as root
+or using sudo.*
+
+Install nose using setuptools::
+
+ easy_install nose
+
+Or, if you don't have setuptools installed, use the download link at right to
+download the source package, and install it in the normal fashion: Ungzip and
+untar the source package, cd to the new directory, and::
+
+ python setup.py install
+
+However, **please note** that without setuptools installed, you will not be
+able to use 3rd-party nose plugins.
+
+This will install the nose libraries, as well as the :doc:`nosetests <usage>`
+script, which you can use to automatically discover and run tests.
+
+Now you can run tests for your project::
+
+ cd path/to/project
+ nosetests
+
+You should see output something like this::
+
+ ..................................
+ ----------------------------------------------------------------------
+ Ran 34 tests in 1.440s
+
+ OK
+
+Indicating that nose found and ran your tests.
+
+For help with nosetests' many command-line options, try::
+
+ nosetests -h
+
+or visit the :doc:`usage documentation <usage>`.
+
+.. toctree::
+ :hidden:
+
+ testing
+ developing
+ news
+ further_reading
diff --git a/doc/more_info.rst b/doc/more_info.rst
new file mode 100644
index 0000000..a37aeb2
--- /dev/null
+++ b/doc/more_info.rst
@@ -0,0 +1,48 @@
+About the name
+==============
+
+* nose is the least silly short synonym for discover in the dictionary.com
+ thesaurus that does not contain the word 'spy.'
+* Pythons have noses
+* The nose knows where to find your tests
+* Nose Obviates Suite Employment
+
+Contact the author
+==================
+
+You can email me at jpellerin+nose at gmail dot com.
+
+To report bugs, ask questions, or request features, please use the *issues*
+tab at the Google code site: http://code.google.com/p/python-nose/issues/list.
+Patches are welcome!
+
+Similar test runners
+====================
+
+nose was inspired mainly by py.test_, which is a great test runner, but
+formerly was not all that easy to install, and is not based on unittest.
+
+Test suites written for use with nose should work equally well with py.test,
+and vice versa, except for the differences in output capture and command line
+arguments for the respective tools.
+
+.. _py.test: http://codespeak.net/py/current/doc/test.html
+
+License and copyright
+=====================
+
+nose is copyright Jason Pellerin 2005-2009
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA \ No newline at end of file
diff --git a/doc/news.rst b/doc/news.rst
new file mode 100644
index 0000000..7493141
--- /dev/null
+++ b/doc/news.rst
@@ -0,0 +1,78 @@
+What's new
+==========
+
+0.11 is a major release of nose, featuring several new or improved plugins,
+completely revised documentation, some small, mostly backwards-compatible
+changes in behavior, and numerous bug fixes.
+
+New plugins
+-----------
+
+* :doc:`Multiprocess <plugins/multiprocess>`
+
+ The multiprocess plugin enables splitting test runs across multiple
+ processes. For some test suites, this can shorten run time tremendously.
+ See also: :doc:`doc_tests/test_multiprocess/multiprocess`
+
+* :doc:`Log capture <plugins/logcapture>`
+
+ The log capture plugin does for logging what the
+ :doc:`capture <plugins/capture>` plugin does for stdout. All :mod:`logging`
+ messages produced during a failing test are appended to the error
+ output printed at the end of the test run.
+
+* :doc:`Xunit <plugins/xunit>`
+
+ The Xunit plugin produces a test result file in ant/junit xml format,
+ suitable for use with `hudson`_ and other continuous integration systems
+ that consume this format.
+
+* :doc:`Collect only <plugins/collect>`
+
+ The collect only plugin just collects all of your tests, it doesn't run
+ them. Fixtures are also skipped, so collection should be quick.
+
+* :doc:`Collect tests from all modules <plugins/allmodules>`
+
+ This plugin enables collecting tests from all python modules, not just those
+ that match testMatch.
+
+.. _`hudson` : https://hudson.dev.java.net/
+
+Plugin improvements
+-------------------
+
+* Doctest fixtures
+
+ The :doc:`doctest plugin <plugins/doctests>` now supports using fixtures with
+ doctest files. You can specify a module prefix (eg, "_fixture") and if a
+ module exists with that prefix appended to the name of the doctest file (eg,
+ "test_pillow_fixture.py" for the doctest file "test_pillow.txt"), then
+ fixtures for the doctest will be extracted from the module. See
+ :doc:`doc_tests/test_doctest_fixtures/doctest_fixtures` for more.
+
+* Looping over failed tests
+
+* HTML coverage reports
+
+Changes
+-------
+
+* New docs
+
+* Namespace packages
+
+* To make it easier to use custom plugins without needing setuptools,
+ :func:`nose.core.main` and :func:`nose.core.run` now support an
+ :doc:`addplugins <doc_tests/test_addplugins/test_addplugins>` keyword
+ argument that takes a list of additional plugins to make available. **Note**
+ that adding a plugin to this list **does not** activate or enable the
+ plugin, only makes it available to be enabled via command-line or
+ config file settings.
+
+* Limited IronPython support
+
+Detailed changes
+----------------
+
+.. include :: ../CHANGELOG \ No newline at end of file
diff --git a/doc/plugins.rst b/doc/plugins.rst
new file mode 100644
index 0000000..55c9ebf
--- /dev/null
+++ b/doc/plugins.rst
@@ -0,0 +1,69 @@
+Extending and customizing nose with plugins
+===========================================
+
+nose has plugin hooks for loading, running, watching and reporting on tests and
+test runs. If you don't like the default collection scheme, or it doesn't suit
+the layout of your project, or you need reports in a format different from the
+unittest standard, or you need to collect some additional information about
+tests (like code coverage or profiling data), you can write a plugin to do so.
+See the section on `writing plugins`_ for more.
+
+nose also comes with a number of built-in plugins, such as:
+
+* Output capture
+
+ Unless called with the -s (--nocapture) switch, nose will capture stdout
+ during each test run, and print the captured output only for tests that fail
+ or have errors. The captured output is printed immediately following the
+ error or failure output for the test. (Note that output in teardown methods
+ is captured, but can't be output with failing tests, because teardown has not
+ yet run at the time of the failure.)
+
+* Assert introspection
+
+ When run with the -d (--detailed-errors) switch, nose will try to output
+ additional information about the assert expression that failed with each
+ failing test. Currently, this means that names in the assert expression will
+ be expanded into any values found for them in the locals or globals in the
+ frame in which the expression executed.
+
+ In other words, if you have a test like::
+
+ def test_integers():
+ a = 2
+ assert a == 4, "assert 2 is 4"
+
+ You will get output like::
+
+ File "/path/to/file.py", line XX, in test_integers:
+ assert a == 4, "assert 2 is 4"
+ AssertionError: assert 2 is 4
+ >> assert 2 == 4, "assert 2 is 4"
+
+ Please note that dotted names are not expanded, and callables are not called
+ in the expansion.
+
+See below for the rest of the built-in plugins.
+
+Using Builtin plugins
+---------------------
+
+See :doc:`plugins/builtin`
+
+Writing plugins
+---------------
+
+.. toctree ::
+ :maxdepth: 2
+
+ plugins/writing
+ plugins/interface
+ plugins/errorclasses
+
+Testing plugins
+---------------
+
+.. toctree ::
+ :maxdepth: 2
+
+ plugins/testing \ No newline at end of file
diff --git a/doc/plugins/allmodules.rst b/doc/plugins/allmodules.rst
new file mode 100644
index 0000000..ad6d034
--- /dev/null
+++ b/doc/plugins/allmodules.rst
@@ -0,0 +1,4 @@
+AllModules: collect tests in all modules
+========================================
+
+.. autoplugin :: nose.plugins.allmodules \ No newline at end of file
diff --git a/doc/plugins/attrib.rst b/doc/plugins/attrib.rst
new file mode 100644
index 0000000..beaa834
--- /dev/null
+++ b/doc/plugins/attrib.rst
@@ -0,0 +1,4 @@
+Attrib: tag and select tests with attributes
+============================================
+
+.. autoplugin :: nose.plugins.attrib
diff --git a/doc/plugins/builtin.rst b/doc/plugins/builtin.rst
new file mode 100644
index 0000000..7f44f93
--- /dev/null
+++ b/doc/plugins/builtin.rst
@@ -0,0 +1,22 @@
+Batteries included: builtin nose plugins
+========================================
+
+.. toctree ::
+ :maxdepth: 2
+
+ allmodules
+ attrib
+ capture
+ collect
+ cover
+ debug
+ deprecated
+ doctests
+ failuredetail
+ isolate
+ logcapture
+ multiprocess
+ prof
+ skip
+ testid
+ xunit
diff --git a/doc/plugins/capture.rst b/doc/plugins/capture.rst
new file mode 100644
index 0000000..27a0c2e
--- /dev/null
+++ b/doc/plugins/capture.rst
@@ -0,0 +1,5 @@
+Capture: capture stdout during tests
+====================================
+
+.. autoplugin :: nose.plugins.capture
+
diff --git a/doc/plugins/collect.rst b/doc/plugins/collect.rst
new file mode 100644
index 0000000..011a96d
--- /dev/null
+++ b/doc/plugins/collect.rst
@@ -0,0 +1,4 @@
+Collect: Collect tests quickly
+==============================
+
+.. autoplugin :: nose.plugins.collect \ No newline at end of file
diff --git a/doc/plugins/cover.rst b/doc/plugins/cover.rst
new file mode 100644
index 0000000..938031c
--- /dev/null
+++ b/doc/plugins/cover.rst
@@ -0,0 +1,4 @@
+Cover: code coverage
+====================
+
+.. autoplugin :: nose.plugins.cover
diff --git a/doc/plugins/debug.rst b/doc/plugins/debug.rst
new file mode 100644
index 0000000..cac67f3
--- /dev/null
+++ b/doc/plugins/debug.rst
@@ -0,0 +1,4 @@
+Debug: drop into pdb on errors or failures
+==========================================
+
+.. autoplugin :: nose.plugins.debug \ No newline at end of file
diff --git a/doc/plugins/deprecated.rst b/doc/plugins/deprecated.rst
new file mode 100644
index 0000000..ebb8140
--- /dev/null
+++ b/doc/plugins/deprecated.rst
@@ -0,0 +1,4 @@
+Deprecated: mark tests as deprecated
+====================================
+
+.. autoplugin :: nose.plugins.deprecated
diff --git a/doc/plugins/doctests.rst b/doc/plugins/doctests.rst
new file mode 100644
index 0000000..9763765
--- /dev/null
+++ b/doc/plugins/doctests.rst
@@ -0,0 +1,4 @@
+Doctests: run doctests with nose
+================================
+
+.. autoplugin :: nose.plugins.doctests
diff --git a/doc/plugins/errorclasses.rst b/doc/plugins/errorclasses.rst
new file mode 100644
index 0000000..c8a960e
--- /dev/null
+++ b/doc/plugins/errorclasses.rst
@@ -0,0 +1,2 @@
+.. automodule :: nose.plugins.errorclass
+ :members: ErrorClassPlugin \ No newline at end of file
diff --git a/doc/plugins/failuredetail.rst b/doc/plugins/failuredetail.rst
new file mode 100644
index 0000000..5a3d0df
--- /dev/null
+++ b/doc/plugins/failuredetail.rst
@@ -0,0 +1,4 @@
+Failure Detail: introspect asserts
+==================================
+
+.. autoplugin :: nose.plugins.failuredetail
diff --git a/doc/plugins/interface.rst b/doc/plugins/interface.rst
new file mode 100644
index 0000000..dfff764
--- /dev/null
+++ b/doc/plugins/interface.rst
@@ -0,0 +1,124 @@
+Plugin Interface
+================
+
+Plugin base class
+-----------------
+
+.. autoclass :: nose.plugins.base.Plugin
+ :members:
+
+Nose plugin API
+---------------
+
+While it is recommended that plugins subclass nose.plugins.Plugin, the only
+requirements for a plugin are that it implement the methods `options(self,
+parser, env)` and `configure(self, options, conf)`, and have the attributes
+`enabled`, `name` and `score`.
+
+Plugins may implement any or all of the methods documented below. Please note
+that they *must not* subclass `IPluginInterface`; `IPluginInterface` is a only
+description of the plugin API.
+
+When plugins are called, the first plugin that implements a method and returns
+a non-None value wins, and plugin processing ends. The exceptions to this are
+methods marked as `generative` or `chainable`. `generative` methods combine
+the output of all plugins that respond with an iterable into a single
+flattened iterable response (a generator, really). `chainable` methods pass
+the results of calling plugin A as the input to plugin B, where the positions
+in the chain are determined by the plugin sort order, which is in order by
+`score` descending.
+
+In general, plugin methods correspond directly to methods of
+`nose.selector.Selector`, `nose.loader.TestLoader` and
+`nose.result.TextTestResult` are called by those methods when they are
+called. In some cases, the plugin hook doesn't neatly match the method in
+which it is called; for those, the documentation for the hook will tell you
+where in the test process it is called.
+
+Plugin hooks fall into four broad categories: selecting and loading tests,
+handling errors raised by tests, preparing objects used in the testing
+process, and watching and reporting on test results.
+
+Selecting and loading tests
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To alter test selection behavior, implement any necessary `want*` methods as
+outlined below. Keep in mind, though, that when your plugin returns True from
+a `want*` method, you will send the requested object through the normal test
+collection process. If the object represents something from which normal tests
+can't be collected, you must also implement a loader method to load the tests.
+
+Examples:
+
+* The builtin doctests plugin implements `wantFile` to enable
+ loading of doctests from files that are not python modules. It
+ also implements `loadTestsFromModule` to load doctests from
+ python modules, and `loadTestsFromFile` to load tests from the
+ non-module files selected by `wantFile`.
+
+* The builtin attrib plugin implements `wantFunction` and
+ `wantMethod` so that it can reject tests that don't match the
+ specified attributes.
+
+Handling errors
+^^^^^^^^^^^^^^^
+
+To alter error handling behavior -- for instance to catch a certain class of
+exception and handle it differently from the normal error or failure handling
+-- you should subclass :class:`nose.plugins.errorclass.ErrorClassPlugin`. See `the section on ErrorClass plugins`_ for more details.
+
+Examples:
+
+* The builtin skip and deprecated plugins are ErrorClass plugins.
+
+.. _the section on ErrorClass plugins: errorclasses.html
+
+Preparing test objects
+^^^^^^^^^^^^^^^^^^^^^^
+
+To alter, get a handle on, or replace test framework objects such
+as the loader, result, runner, and test cases, use the appropriate
+prepare methods. The simplest reason to use prepare is if you need
+to use an object yourself. For example, the isolate plugin
+implements `prepareTestLoader` so that it can use the test loader
+later on to load tests. If you return a value from a prepare
+method, that value will be used in place of the loader, result,
+runner or test case, respectively. When replacing test cases, be
+aware that you are replacing the entire test case -- including the
+whole `run(result)` method of the `unittest.TestCase` -- so if you
+want normal unittest test result reporting, you must implement the
+same calls to result as `unittest.TestCase.run`.
+
+Examples:
+
+* The builtin isolate plugin implements `prepareTestLoader` but
+ does not replace the test loader.
+
+* The builtin profile plugin implements `prepareTest` and does
+ replace the top-level test case by returning the case wrapped in
+ the profiler function.
+
+Watching or reporting on tests
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To record information about tests or other modules imported during
+the testing process, output additional reports, or entirely change
+test report output, implement any of the methods outlined below that
+correspond to TextTestResult methods.
+
+Examples:
+
+* The builtin cover plugin implements `begin` and `report` to
+ capture and report code coverage metrics for all or selected modules
+ loaded during testing.
+
+* The builtin profile plugin implements `begin`, `prepareTest` and
+ `report` to record and output profiling information. In this
+ case, the plugin's `prepareTest` method constructs a function that
+ runs the test through the hotshot profiler's runcall() method.
+
+Plugin interface methods
+------------------------
+
+.. autoclass :: nose.plugins.base.IPluginInterface
+ :members: \ No newline at end of file
diff --git a/doc/plugins/isolate.rst b/doc/plugins/isolate.rst
new file mode 100644
index 0000000..94b5641
--- /dev/null
+++ b/doc/plugins/isolate.rst
@@ -0,0 +1,4 @@
+Isolate: protect tests from (some) side-effects
+-----------------------------------------------
+
+.. autoplugin :: nose.plugins.isolate \ No newline at end of file
diff --git a/doc/plugins/logcapture.rst b/doc/plugins/logcapture.rst
new file mode 100644
index 0000000..4bf09c3
--- /dev/null
+++ b/doc/plugins/logcapture.rst
@@ -0,0 +1,4 @@
+Logcapture: capture logging during tests
+========================================
+
+.. autoplugin :: nose.plugins.logcapture \ No newline at end of file
diff --git a/doc/plugins/multiprocess.rst b/doc/plugins/multiprocess.rst
new file mode 100644
index 0000000..c9f4aa7
--- /dev/null
+++ b/doc/plugins/multiprocess.rst
@@ -0,0 +1,5 @@
+------------------------------
+Multiprocess: parallel testing
+------------------------------
+
+.. autoplugin :: nose.plugins.multiprocess
diff --git a/doc/plugins/prof.rst b/doc/plugins/prof.rst
new file mode 100644
index 0000000..f778942
--- /dev/null
+++ b/doc/plugins/prof.rst
@@ -0,0 +1,4 @@
+Prof: enable profiling using the hotshot profiler
+=================================================
+
+.. autoplugin :: nose.plugins.prof
diff --git a/doc/plugins/skip.rst b/doc/plugins/skip.rst
new file mode 100644
index 0000000..07f9207
--- /dev/null
+++ b/doc/plugins/skip.rst
@@ -0,0 +1,5 @@
+Skip: mark tests as skipped
+===========================
+
+.. autoplugin :: nose.plugins.skip
+ :plugin: Skip \ No newline at end of file
diff --git a/doc/plugins/testid.rst b/doc/plugins/testid.rst
new file mode 100644
index 0000000..377e2e7
--- /dev/null
+++ b/doc/plugins/testid.rst
@@ -0,0 +1,4 @@
+Testid: add a test id to each test name output
+==============================================
+
+.. autoplugin :: nose.plugins.testid
diff --git a/doc/plugins/testing.rst b/doc/plugins/testing.rst
new file mode 100644
index 0000000..6c0bb6e
--- /dev/null
+++ b/doc/plugins/testing.rst
@@ -0,0 +1,2 @@
+.. automodule :: nose.plugins.plugintest
+ :members: \ No newline at end of file
diff --git a/doc/plugins/writing.rst b/doc/plugins/writing.rst
new file mode 100644
index 0000000..ed73b9f
--- /dev/null
+++ b/doc/plugins/writing.rst
@@ -0,0 +1 @@
+.. automodule :: nose.plugins \ No newline at end of file
diff --git a/doc/plugins/xunit.rst b/doc/plugins/xunit.rst
new file mode 100644
index 0000000..5602e5d
--- /dev/null
+++ b/doc/plugins/xunit.rst
@@ -0,0 +1,4 @@
+Xunit: output test results in xunit format
+==========================================
+
+.. autoplugin :: nose.plugins.xunit \ No newline at end of file
diff --git a/doc/setuptools_integration.rst b/doc/setuptools_integration.rst
new file mode 100644
index 0000000..d055664
--- /dev/null
+++ b/doc/setuptools_integration.rst
@@ -0,0 +1,29 @@
+Setuptools integration
+======================
+
+nose may be used with the setuptools_ test command. Simply specify
+nose.collector as the test suite in your setup file::
+
+ setup (
+ # ...
+ test_suite = 'nose.collector'
+ )
+
+Then to find and run tests, you can run::
+
+ python setup.py test
+
+When running under setuptools, you can configure nose settings via the
+environment variables detailed in the nosetests script usage message,
+or the setup.cfg or ~/.noserc or ~/.nose.cfg config files.
+
+Please note that when run under the setuptools test command, some plugins will
+not be available, including the builtin coverage, and profiler plugins.
+
+nose also includes its own setuptools command, ``nosetests``, that
+provides support for all plugins and command line options. See
+nose.commands_ for more information about the ``nosetests`` command.
+
+.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools
+.. _nose.commands: #commands
+
diff --git a/doc/testing.rst b/doc/testing.rst
new file mode 100644
index 0000000..fb85d6a
--- /dev/null
+++ b/doc/testing.rst
@@ -0,0 +1,55 @@
+Testing with nose
+=================
+
+Writing tests is easier
+-----------------------
+
+nose collects tests from :class:`unittest.TestCase` subclasses, of course. But
+you can also write simple test functions, as well as test classes that are
+*not* subclasses of :class:`unittest.TestCase`. nose also supplies a number of
+helpful functions for writing timed tests, testing for exceptions, and other
+common use cases. See :doc:`writing_tests` and :doc:`testing_tools` for more.
+
+Running tests is easier
+-----------------------
+
+nose collects tests automatically, as long as you follow some simple
+guidelines for organizing your library and test code. There's no need
+to manually collect test cases into test suites. Running tests is
+responsive, since nose begins running tests as soon as the first test
+module is loaded. See :doc:`finding_tests` for more.
+
+Setting up your test environment is easier
+------------------------------------------
+
+nose supports fixtures at the package, module, class, and test case
+level, so expensive initialization can be done as infrequently as
+possible. See :ref:`fixtures` for more.
+
+Doing what you want to do is easier
+-----------------------------------
+
+nose comes with a number of :doc:`builtin plugins <plugins/builtin>` to help
+you with output capture, error introspection, code coverage, doctests, and
+more. It also comes with plugin hooks for loading, running, watching and
+reporting on tests and test runs. If you don't like the default collection
+scheme, or it doesn't suit the layout of your project, or you need reports in
+a format different from the unittest standard, or you need to collect some
+additional information about tests (like code coverage or profiling data), you
+can write a plugin to make nose do what you want. See the section on
+:doc:`plugins/writing` for more. There are also many
+`third party nose plugins <http://nose-plugins.jottit.com/>`_ available.
+
+Details
+-------
+
+.. toctree ::
+ :maxdepth: 2
+
+ usage
+ writing_tests
+ finding_tests
+ testing_tools
+ plugins/builtin
+ Third party nose plugins <http://nose-plugins.jottit.com/>
+ setuptools_integration
diff --git a/doc/testing_tools.rst b/doc/testing_tools.rst
new file mode 100644
index 0000000..45e2958
--- /dev/null
+++ b/doc/testing_tools.rst
@@ -0,0 +1,11 @@
+Testing tools
+-------------
+
+The nose.tools module provides a number of testing aids that you may
+find useful, including decorators for restricting test execution time
+and testing for exceptions, and all of the same assertX methods found
+in `unittest.TestCase` (only spelled in pep08 fashion, so `assert_equal`
+rather than `assertEqual`).
+
+.. automodule :: nose.tools
+ :members: \ No newline at end of file
diff --git a/doc/usage.rst b/doc/usage.rst
new file mode 100644
index 0000000..73f3de7
--- /dev/null
+++ b/doc/usage.rst
@@ -0,0 +1,42 @@
+Basic usage
+-----------
+
+Use the nosetests script (after installation by setuptools)::
+
+ nosetests [options] [(optional) test files or directories]
+
+In addition to passing command-line options, you may also put configuration
+options in a .noserc or nose.cfg file in your home directory. These are
+standard .ini-style config files. Put your nosetests configuration in a
+[nosetests] section, with the -- prefix removed::
+
+ [nosetests]
+ verbosity=3
+ with-doctest=1
+
+There are several other ways to use the nose test runner besides the
+`nosetests` script. You may use nose in a test script::
+
+ import nose
+ nose.main()
+
+If you don't want the test script to exit with 0 on success and 1 on failure
+(like unittest.main), use nose.run() instead::
+
+ import nose
+ result = nose.run()
+
+`result` will be true if the test run succeeded, or false if any test failed
+or raised an uncaught exception. Lastly, you can run nose.core directly, which
+will run nose.main()::
+
+ python /path/to/nose/core.py
+
+Please see the usage message for the nosetests script for information
+about how to control which tests nose runs, which plugins are loaded,
+and the test output.
+
+Extended usage
+^^^^^^^^^^^^^^
+
+.. autohelp :: \ No newline at end of file
diff --git a/doc/writing_tests.rst b/doc/writing_tests.rst
new file mode 100644
index 0000000..b1e20ce
--- /dev/null
+++ b/doc/writing_tests.rst
@@ -0,0 +1,170 @@
+Writing tests
+-------------
+
+As with py.test_, nose tests need not be subclasses of
+:class:`unittest.TestCase`. Any function or class that matches the configured
+testMatch regular expression (``(?:^|[\\b_\\.-])[Tt]est)`` by default -- that
+is, has test or Test at a word boundary or following a - or _) and lives in a
+module that also matches that expression will be run as a test. For the sake
+of compatibility with legacy unittest test cases, nose will also load tests
+from :class:`unittest.TestCase` subclasses just like unittest does. Like
+py.test, functional tests will be run in the order in which they appear in the
+module file. TestCase derived tests and other test classes are run in
+alphabetical order.
+
+.. _py.test: http://codespeak.net/py/current/doc/test.html
+
+.. _fixtures:
+
+Fixtures
+========
+
+nose supports fixtures (setup and teardown methods) at the package,
+module, class, and test level. As with py.test or unittest fixtures,
+setup always runs before any test (or collection of tests for test
+packages and modules); teardown runs if setup has completed
+successfully, whether or not the test or tests pass. For more detail
+on fixtures at each level, see below.
+
+Test packages
+=============
+
+nose allows tests to be grouped into test packages. This allows
+package-level setup; for instance, if you need to create a test database
+or other data fixture for your tests, you may create it in package setup
+and remove it in package teardown once per test run, rather than having to
+create and tear it down once per test module or test case.
+
+To create package-level setup and teardown methods, define setup and/or
+teardown functions in the ``__init__.py`` of a test package. Setup methods may
+be named `setup`, `setup_package`, `setUp`, or `setUpPackage`; teardown may
+be named `teardown`, `teardown_package`, `tearDown` or `tearDownPackage`.
+Execution of tests in a test package begins as soon as the first test
+module is loaded from the test package.
+
+Test modules
+============
+
+A test module is a python module that matches the testMatch regular
+expression. Test modules offer module-level setup and teardown; define the
+method `setup`, `setup_module`, `setUp` or `setUpModule` for setup,
+`teardown`, `teardown_module`, or `tearDownModule` for teardown. Execution
+of tests in a test module begins after all tests are collected.
+
+Test classes
+============
+
+A test class is a class defined in a test module that is either a subclass of
+:class:`unittest.TestCase`, or matches testMatch. Test classes that don't descend
+from `unittest.TestCase` are run in the same way as those that do: methods in
+the class that match testMatch are discovered, and a test case constructed to
+run each with a fresh instance of the test class. Like :class:`unittest.TestCase`
+subclasses, other test classes may define setUp and tearDown methods that will
+be run before and after each test method. Test classes that do not descend
+from `unittest.TestCase` may also include generator methods, and class-level
+fixtures. Class level fixtures may be named `setup_class`, `setupClass`,
+`setUpClass`, `setupAll` or `setUpAll` for set up and `teardown_class`,
+`teardownClass`, `tearDownClass`, `teardownAll` or `tearDownAll` for teardown
+and must be class methods.
+
+Test functions
+==============
+
+Any function in a test module that matches testMatch will be wrapped in a
+`FunctionTestCase` and run as a test. The simplest possible failing test is
+therefore::
+
+ def test():
+ assert False
+
+And the simplest passing test::
+
+ def test():
+ pass
+
+Test functions may define setup and/or teardown attributes, which will be
+run before and after the test function, respectively. A convenient way to
+do this, especially when several test functions in the same module need
+the same setup, is to use the provided with_setup decorator::
+
+ def setup_func():
+ "set up test fixtures"
+
+ def teardown_func():
+ "tear down test fixtures"
+
+ @with_setup(setup_func, teardown_func)
+ def test():
+ "test ..."
+
+For python 2.3 or earlier, add the attributes by calling the decorator
+function like so::
+
+ def test():
+ "test ... "
+ test = with_setup(setup_func, teardown_func)(test)
+
+or by direct assignment::
+
+ test.setup = setup_func
+ test.teardown = teardown_func
+
+Please note that `with_setup` is useful *only* for test functions, not
+for test methods in `unittest.TestCase` subclasses or other test
+classes. For those cases, define `setUp` and `tearDown` methods in the
+class.
+
+Test generators
+===============
+
+nose supports test functions and methods that are generators. A simple
+example from nose's selftest suite is probably the best explanation::
+
+ def test_evens():
+ for i in range(0, 5):
+ yield check_even, i, i*3
+
+ def check_even(n, nn):
+ assert n % 2 == 0 or nn % 2 == 0
+
+This will result in 4 tests. nose will iterate the generator, creating a
+function test case wrapper for each tuple it yields. As in the example, test
+generators must yield tuples, the first element of which must be a callable
+and the remaining elements the arguments to be passed to the callable.
+
+By default, the test name output for a generated test in verbose mode
+will be the name of the generator function or method, followed by the
+args passed to the yielded callable. If you want to show a different test
+name, set the ``description`` attribute of the yielded callable.
+
+Setup and teardown functions may be used with test generators. However, please
+note that setup and teardown attributes attached to the *generator function*
+will execute only once. To *execute fixtures for each yielded test*, attach
+the setup and teardown attributes to the function that is yielded, or yield a
+callable object instance with setup and teardown attributes.
+
+For example::
+
+ @with_setup(setup_func, teardown_func)
+ def test_generator():
+ # ...
+ yield func, arg, arg # ...
+
+Here, the setup and teardown functions will be executed *once*. Compare to::
+
+ def test_generator():
+ # ...
+ yield func, arg, arg # ...
+
+ @with_setup(setup_func, teardown_func)
+ def func(arg):
+ assert something_about(arg)
+
+In the latter case the setup and teardown functions will execute once for each
+yielded test.
+
+For generator methods, the setUp and tearDown methods of the class (if any)
+will be run before and after each generated test case.
+
+Please note that method generators *are not* supported in `unittest.TestCase`
+subclasses. \ No newline at end of file
diff --git a/functional_tests/doc_tests/test_addplugins/support/test.py b/functional_tests/doc_tests/test_addplugins/support/test.py
new file mode 100644
index 0000000..f174823
--- /dev/null
+++ b/functional_tests/doc_tests/test_addplugins/support/test.py
@@ -0,0 +1,2 @@
+def test():
+ pass
diff --git a/functional_tests/doc_tests/test_addplugins/test_addplugins.rst b/functional_tests/doc_tests/test_addplugins/test_addplugins.rst
new file mode 100644
index 0000000..14746b0
--- /dev/null
+++ b/functional_tests/doc_tests/test_addplugins/test_addplugins.rst
@@ -0,0 +1,80 @@
+Using custom plugins without setuptools
+---------------------------------------
+
+If you have one or more custom plugins that you'd like to use with nose, but
+can't or don't want to register that plugin as a setuptools entrypoint, you
+can use the ``addplugins`` keyword argument to :func:`nose.core.main` or
+:func:`nose.core.run` to make the plugins available.
+
+To do this you would constuct a launcher script for nose, something like::
+
+ from nose import main
+ from yourpackage import YourPlugin, YourOtherPlugin
+
+ if __name__ == '__main__':
+ nose.main(addplugins=[YourPlugin(), YourOtherPlugin()])
+
+Here's an example. Say that you don't like the fact that the collect only
+plugin outputs 'ok' for each test it finds; instead you want it to output
+'maybe'. You could modify the plugin itself, or instead, create a Maybe plugin
+that transforms the output into your desired shape.
+
+Without the plugin, we get 'ok'.
+
+>>> import os
+>>> support = os.path.join(os.path.dirname(__file__), 'support')
+>>> from nose.plugins.plugintest import run_buffered as run
+>>> argv = [__file__, '-v', support] # --collect-only
+>>> run(argv=argv)
+test.test ... ok
+<BLANKLINE>
+----------------------------------------------------------------------
+Ran 1 test in ...s
+<BLANKLINE>
+OK
+
+Without '-v', we get a dot.
+
+>>> run(argv=[__file__, support])
+.
+----------------------------------------------------------------------
+Ran 1 test in ...s
+<BLANKLINE>
+OK
+
+The plugin is simple. It captures and wraps the test result output stream and
+replaces 'ok' with 'maybe' and '.' with '?'.
+
+>>> from nose.plugins.base import Plugin
+>>> class Maybe(Plugin):
+... def setOutputStream(self, stream):
+... self.stream = stream
+... return self
+... def flush(self):
+... self.stream.flush()
+... def writeln(self, out=""):
+... self.write(out + "\n")
+... def write(self, out):
+... if out == "ok\n":
+... out = "maybe\n"
+... elif out == ".":
+... out = "?"
+... self.stream.write(out)
+
+To activate the plugin, we pass an instance in the addplugins list.
+
+>>> run(argv=argv + ['--with-maybe'], addplugins=[Maybe()])
+test.test ... maybe
+<BLANKLINE>
+----------------------------------------------------------------------
+Ran 1 test in ...s
+<BLANKLINE>
+OK
+
+>>> run(argv=[__file__, support, '--with-maybe'], addplugins=[Maybe()])
+?
+----------------------------------------------------------------------
+Ran 1 test in ...s
+<BLANKLINE>
+OK
+
diff --git a/functional_tests/doc_tests/test_allmodules/support/mod.py b/functional_tests/doc_tests/test_allmodules/support/mod.py
new file mode 100644
index 0000000..e136d56
--- /dev/null
+++ b/functional_tests/doc_tests/test_allmodules/support/mod.py
@@ -0,0 +1,5 @@
+def test():
+ pass
+
+def test_fails():
+ assert False, "This test fails"
diff --git a/functional_tests/doc_tests/test_allmodules/support/test.py b/functional_tests/doc_tests/test_allmodules/support/test.py
new file mode 100644
index 0000000..f174823
--- /dev/null
+++ b/functional_tests/doc_tests/test_allmodules/support/test.py
@@ -0,0 +1,2 @@
+def test():
+ pass
diff --git a/functional_tests/doc_tests/test_allmodules/test_allmodules.rst b/functional_tests/doc_tests/test_allmodules/test_allmodules.rst
new file mode 100644
index 0000000..b541987
--- /dev/null
+++ b/functional_tests/doc_tests/test_allmodules/test_allmodules.rst
@@ -0,0 +1,67 @@
+Finding tests in all modules
+============================
+
+Normally, nose only looks for tests in modules whose names match testMatch. By
+default that means modules with 'test' or 'Test' at the start of the name
+after an underscore (_) or dash (-) or other non-alphanumeric character.
+
+If you want to collect tests from all modules, use the ``--all-modules``
+command line argument to activate the :doc:`allmodules plugin
+<../../plugins/allmodules>`.
+
+.. Note ::
+
+ The function :func:`nose.plugins.plugintest.run` reformats test result
+ output to remove timings, which will vary from run to run, and
+ redirects the output to stdout.
+
+ >>> from nose.plugins.plugintest import run_buffered as run
+
+..
+
+ >>> import os
+ >>> support = os.path.join(os.path.dirname(__file__), 'support')
+ >>> argv = [__file__, '-v', support]
+
+The target directory contains a test module and a normal module.
+
+ >>> support_files = [d for d in os.listdir(support)
+ ... if not d.startswith('.')
+ ... and d.endswith('.py')]
+ >>> support_files.sort()
+ >>> support_files
+ ['mod.py', 'test.py']
+
+When run without ``--all-modules``, only the test module is examined for tests.
+
+ >>> run(argv=argv)
+ test.test ... ok
+ <BLANKLINE>
+ ----------------------------------------------------------------------
+ Ran 1 test in ...s
+ <BLANKLINE>
+ OK
+
+When ``--all-modules`` is active, both modules are examined.
+
+ >>> from nose.plugins.allmodules import AllModules
+ >>> argv = [__file__, '-v', '--all-modules', support]
+ >>> run(argv=argv, plugins=[AllModules()]) # doctest: +REPORT_NDIFF
+ mod.test ... ok
+ mod.test_fails ... FAIL
+ test.test ... ok
+ <BLANKLINE>
+ ======================================================================
+ FAIL: mod.test_fails
+ ----------------------------------------------------------------------
+ Traceback (most recent call last):
+ ...
+ AssertionError: This test fails
+ <BLANKLINE>
+ ----------------------------------------------------------------------
+ Ran 3 tests in ...s
+ <BLANKLINE>
+ FAILED (failures=1)
+
+
+
diff --git a/functional_tests/doc_tests/test_coverage_html/coverage_html.rst b/functional_tests/doc_tests/test_coverage_html/coverage_html.rst
index 067e37a..a10608b 100644
--- a/functional_tests/doc_tests/test_coverage_html/coverage_html.rst
+++ b/functional_tests/doc_tests/test_coverage_html/coverage_html.rst
@@ -4,8 +4,10 @@ Generating HTML Coverage with nose
.. Note ::
HTML coverage requires Ned Batchelder's coverage.py module.
-
..
+
+FIXME explain this.
+
>>> from nose.plugins.plugintest import run_buffered as run
>>> import os
>>> support = os.path.join(os.path.dirname(__file__), 'support')
@@ -13,7 +15,7 @@ Generating HTML Coverage with nose
>>> from nose.plugins.cover import Coverage
>>> run(argv=[__file__, '-v', '--with-coverage', '--cover-package=blah',
... '--cover-html', '--cover-html-dir=' + cover_html_dir, support, ],
- ... plugins=[Coverage()])
+ ... plugins=[Coverage()]) # doctest: +REPORT_NDIFF
test_covered.test_blah ... hi
ok
<BLANKLINE>
@@ -24,13 +26,13 @@ Generating HTML Coverage with nose
Ran 1 test in ...s
<BLANKLINE>
OK
- >>> print open(os.path.join(cover_html_dir, 'index.html')).read()
+ >>> print open(os.path.join(cover_html_dir, 'index.html')).read() # doctest: +REPORT_NDIFF
<html><head><title>Coverage Index</title></head><body><p>Covered: 3 lines<br/>
Missed: 1 lines<br/>
Skipped 3 lines<br/>
Percent: 75 %<br/>
<table><tr><td>File</td><td>Covered</td><td>Missed</td><td>Skipped</td><td>Percent</td></tr><tr><td><a href="blah.html">blah</a></td><td>3</td><td>1</td><td>3</td><td>75 %</td></tr></table></p></html
- >>> print open(os.path.join(cover_html_dir, 'blah.html')).read()
+ >>> print open(os.path.join(cover_html_dir, 'blah.html')).read() # doctest: +REPORT_NDIFF
<html>
<head>
<title>blah</title>
@@ -38,10 +40,11 @@ Generating HTML Coverage with nose
<body>
blah
<style>
- pre {float: left; margin: 0px 1em }
+ .coverage pre {float: left; margin: 0px 1em; border: none;
+ padding: 0px; line-height: 95% }
.num pre { margin: 0px }
- .nocov {background-color: #faa}
- .cov {background-color: #cfc}
+ .nocov, .nocov pre {background-color: #faa}
+ .cov, .cov pre {background-color: #cfc}
div.coverage div { clear: both; height: 1em}
</style>
<div class="stats">
@@ -63,3 +66,8 @@ Generating HTML Coverage with nose
</body>
</html>
<BLANKLINE>
+
+The html coverage output for a module looks like:
+
+.. raw :: html
+ :file: support/cover/blah.html
diff --git a/functional_tests/doc_tests/test_init_plugin/init_plugin.rst b/functional_tests/doc_tests/test_init_plugin/init_plugin.rst
index f20ebc4..fd2d268 100644
--- a/functional_tests/doc_tests/test_init_plugin/init_plugin.rst
+++ b/functional_tests/doc_tests/test_init_plugin/init_plugin.rst
@@ -45,7 +45,7 @@ the tests fail.
.. Note ::
- The run() function in `nose.plugins.plugintest`_ reformats test result
+ The function :func:`nose.plugins.plugintest.run` reformats test result
output to remove timings, which will vary from run to run, and
redirects the output to stdout.
diff --git a/functional_tests/doc_tests/test_issue089/unwanted_package.rst b/functional_tests/doc_tests/test_issue089/unwanted_package.rst
index 4b0264e..c7efc27 100644
--- a/functional_tests/doc_tests/test_issue089/unwanted_package.rst
+++ b/functional_tests/doc_tests/test_issue089/unwanted_package.rst
@@ -2,7 +2,7 @@ Excluding Unwanted Packages
---------------------------
Normally, nose discovery descends into all packages. Plugins can
-change this behavior by implementing `wantDirectory()`_.
+change this behavior by implementing :meth:`IPluginInterface.wantDirectory()`.
In this example, we have a wanted package called ``wanted_package``
and an unwanted package called ``unwanted_package``.
@@ -19,7 +19,7 @@ When we run nose normally, tests are loaded from both packages.
.. Note ::
- The run() function in `nose.plugins.plugintest`_ reformats test result
+ The function :func:`nose.plugins.plugintest.run` reformats test result
output to remove timings, which will vary from run to run, and
redirects the output to stdout.
@@ -38,7 +38,7 @@ When we run nose normally, tests are loaded from both packages.
OK
To exclude the tests in the unwanted package, we can write a simple
-plugin that implements `wantDirectory()`_ and returns ``False`` if
+plugin that implements :meth:`IPluginInterface.wantDirectory()` and returns ``False`` if
the basename of the directory is ``"unwanted_package"``. This will
prevent nose from descending into the unwanted package.
@@ -67,6 +67,4 @@ not discovered.
----------------------------------------------------------------------
Ran 1 test in ...s
<BLANKLINE>
- OK
-
-.. _`wantDirectory()` : plugin_interface.html#wantDirectory
+ OK \ No newline at end of file
diff --git a/functional_tests/doc_tests/test_issue097/plugintest_environment.rst b/functional_tests/doc_tests/test_issue097/plugintest_environment.rst
index 9aac790..514de79 100644
--- a/functional_tests/doc_tests/test_issue097/plugintest_environment.rst
+++ b/functional_tests/doc_tests/test_issue097/plugintest_environment.rst
@@ -1,8 +1,8 @@
nose.plugins.plugintest, os.environ and sys.argv
------------------------------------------------
-`nose.plugins.plugintest.PluginTester`_ and
-`nose.plugins.plugintest.run()`_ are utilities for testing nose
+:class:`nose.plugins.plugintest.PluginTester` and
+:func:`nose.plugins.plugintest.run` are utilities for testing nose
plugins. When testing plugins, it should be possible to control the
environment seen plugins under test, and that environment should never
be affected by ``os.environ`` or ``sys.argv``.
diff --git a/functional_tests/doc_tests/test_issue107/plugin_exceptions.rst b/functional_tests/doc_tests/test_issue107/plugin_exceptions.rst
index 160ee91..169ba9a 100644
--- a/functional_tests/doc_tests/test_issue107/plugin_exceptions.rst
+++ b/functional_tests/doc_tests/test_issue107/plugin_exceptions.rst
@@ -3,7 +3,7 @@ When Plugins Fail
Plugin methods should not fail silently. When a plugin method raises
an exception before or during the execution of a test, the exception
-will be wrapped in a `nose.failure.Failure`_ instance and appear as a
+will be wrapped in a :class:`nose.failure.Failure` instance and appear as a
failing test. Exceptions raised at other times, such as in the
preparation phase with ``prepareTestLoader`` or ``prepareTestResult``,
or after a test executes, in ``afterTest`` will stop the entire test
diff --git a/functional_tests/doc_tests/test_issue145/imported_tests.rst b/functional_tests/doc_tests/test_issue145/imported_tests.rst
index 4a99c0a..8010fb9 100644
--- a/functional_tests/doc_tests/test_issue145/imported_tests.rst
+++ b/functional_tests/doc_tests/test_issue145/imported_tests.rst
@@ -27,7 +27,7 @@ into package2f and package2c.
.. Note ::
- The run() function in `nose.plugins.plugintest`_ reformats test result
+ The run() function in :mod:`nose.plugins.plugintest` reformats test result
output to remove timings, which will vary from run to run, and
redirects the output to stdout.
diff --git a/functional_tests/doc_tests/test_multiprocess/multiprocess.rst b/functional_tests/doc_tests/test_multiprocess/multiprocess.rst
index 3a75a40..8df08ee 100644
--- a/functional_tests/doc_tests/test_multiprocess/multiprocess.rst
+++ b/functional_tests/doc_tests/test_multiprocess/multiprocess.rst
@@ -3,8 +3,8 @@ Parallel Testing with nose
.. Note ::
- The multiprocess plugin requires the processing_ module, available from
- PyPI and at http://pyprocessing.berlios.de/.
+ The multiprocess plugin requires the multiprocessing_ module, available from
+ PyPI and at http://code.google.com/p/python-multiprocessing/.
..
@@ -16,7 +16,7 @@ but is mainly useful for IO-bound tests which can benefit from greater
parallelization, since most of the tests spend most of their time
waiting for data to arrive from someplace else.
-.. _processing : http://pyprocessing.berlios.de/
+.. _multiprocessing : http://code.google.com/p/python-multiprocessing/
How tests are distributed
=========================
@@ -117,17 +117,18 @@ all tests pass.
.. Note ::
- The sh() function below executes a shell command.
+ FIXME: note about run()
..
>>> import os
+ >>> from nose.plugins.plugintest import run_buffered as run
>>> support = os.path.join(os.path.dirname(__file__), 'support')
>>> test_not_shared = os.path.join(support, 'test_not_shared.py')
>>> test_shared = os.path.join(support, 'test_shared.py')
>>> test_can_split = os.path.join(support, 'test_can_split.py')
- >>> sh('nosetests -v ' + test_shared) #doctest: +REPORT_NDIFF
+ >>> run(argv=['nosetests', '-v', test_shared]) #doctest: +REPORT_NDIFF
setup called
test_shared.TestMe.test_one ... ok
test_shared.test_a ... ok
@@ -139,7 +140,7 @@ all tests pass.
<BLANKLINE>
OK
- >>> sh('nosetests -v ' + test_not_shared) #doctest: +REPORT_NDIFF
+ >>> run(argv=['nosetests', '-v', test_not_shared]) #doctest: +REPORT_NDIFF
setup called
test_not_shared.TestMe.test_one ... ok
test_not_shared.test_a ... ok
@@ -151,7 +152,7 @@ all tests pass.
<BLANKLINE>
OK
- >>> sh('nosetests -v ' + test_can_split) #doctest: +REPORT_NDIFF
+ >>> run(argv=['nosetests', '-v', test_can_split]) #doctest: +REPORT_NDIFF
setup called
test_can_split.TestMe.test_one ... ok
test_can_split.test_a ... ok
@@ -166,12 +167,20 @@ all tests pass.
However, when run with the `--processes=2` switch, each test module
behaves differently.
+ >>> from nose.plugins.multiprocess import MultiProcess
+
The module marked `_multiprocess_shared_` executes correctly, although as with
any use of the multiprocess plugin, the order in which the tests execute is
indeterminate.
+ # First we have to reset all of the test modules
+ >>> import sys
+ >>> sys.modules['test_shared'].called[:] = []
+ >>> sys.modules['test_not_shared'].called[:] = []
+ >>> sys.modules['test_can_split'].called[:] = []
- >>> sh('nosetests -v --processes=2 ' + test_shared) #doctest: +ELLIPSIS
+ >>> run(argv=['nosetests', '-v', '--processes=2', test_shared],
+ ... plugins=[MultiProcess()]) #doctest: +ELLIPSIS
setup called
test_shared.... ok
teardown called
@@ -191,9 +200,8 @@ the fixtures *are not reentrant*. A module such as this *must not* be
marked `_multiprocess_can_split_`, or tests will fail in one or more
runner processes as fixtures are re-executed.
- >>> sh('nosetests -v --processes=2 ' + test_can_split) #doctest: +ELLIPSIS +REPORT_NDIFF
- setup called
- ...
+ >>> run(argv=['nosetests', '-v', '--processes=2', test_can_split],
+ ... plugins=[MultiProcess()]) #doctest: +ELLIPSIS
test_can_split....
...
FAILED (failures=...)
diff --git a/functional_tests/doc_tests/test_multiprocess/multiprocess_fixtures.py b/functional_tests/doc_tests/test_multiprocess/multiprocess_fixtures.py
index 52b2d96..6ba4aeb 100644
--- a/functional_tests/doc_tests/test_multiprocess/multiprocess_fixtures.py
+++ b/functional_tests/doc_tests/test_multiprocess/multiprocess_fixtures.py
@@ -11,35 +11,9 @@ def setup_module():
import multiprocessing
if 'active' in MultiProcess.status:
raise SkipTest("Multiprocess plugin is active. Skipping tests of "
- "plugin itself. multiprocessing module in python "
- "2.6 is not re-entrant")
+ "plugin itself.")
except ImportError:
- try:
- import processing
- except ImportError:
- raise SkipTest("processing module not available")
- try:
- import subprocess
- except ImportError:
- raise SkipTest("subprocess module not available")
-
-
-
-def globs(globs):
- globs.update({'sh': sh})
- return globs
+ raise SkipTest("multiprocessing module not available")
-def sh(cmd):
- import subprocess
- root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(
- os.path.abspath(__file__)))))
- cmd = cmd.replace('nosetests ', '')
- output = subprocess.Popen(
- [executable(), 'selftest.py'] + cmd.split(' '),
- cwd=root, stderr=subprocess.PIPE, close_fds=True).communicate()[1]
- print munge_nose_output_for_doctest(output)
-
-def executable():
- return sys.executable
diff --git a/functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst b/functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst
index c800f84..ca28fbe 100644
--- a/functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst
+++ b/functional_tests/doc_tests/test_restricted_plugin_options/restricted_plugin_options.rst
@@ -22,7 +22,7 @@ method.
.. Note ::
- The run() function in `nose.plugins.plugintest`_ reformats test result
+ The run() function in :mod:`nose.plugins.plugintest` reformats test result
output to remove timings, which will vary from run to run, and
redirects the output to stdout.
diff --git a/functional_tests/doc_tests/test_selector_plugin/selector_plugin.rst b/functional_tests/doc_tests/test_selector_plugin/selector_plugin.rst
index b31f2cf..f5f7913 100644
--- a/functional_tests/doc_tests/test_selector_plugin/selector_plugin.rst
+++ b/functional_tests/doc_tests/test_selector_plugin/selector_plugin.rst
@@ -41,7 +41,7 @@ nose's default selector is unable to discover this project's tests.
.. Note ::
- The run() function in `nose.plugins.plugintest`_ reformats test result
+ The run() function in :mod:`nose.plugins.plugintest` reformats test result
output to remove timings, which will vary from run to run, and
redirects the output to stdout.
diff --git a/functional_tests/doc_tests/test_xunit_plugin/support/test_skip.py b/functional_tests/doc_tests/test_xunit_plugin/support/test_skip.py
new file mode 100644
index 0000000..cb26c41
--- /dev/null
+++ b/functional_tests/doc_tests/test_xunit_plugin/support/test_skip.py
@@ -0,0 +1,13 @@
+from nose.exc import SkipTest
+
+def test_ok():
+ pass
+
+def test_err():
+ raise Exception("oh no")
+
+def test_fail():
+ assert False, "bye"
+
+def test_skip():
+ raise SkipTest("not me")
diff --git a/functional_tests/doc_tests/test_xunit_plugin/test_skips.rst b/functional_tests/doc_tests/test_xunit_plugin/test_skips.rst
new file mode 100644
index 0000000..e49852c
--- /dev/null
+++ b/functional_tests/doc_tests/test_xunit_plugin/test_skips.rst
@@ -0,0 +1,40 @@
+XUnit output supports skips
+---------------------------
+
+>>> import os
+>>> from nose.plugins.xunit import Xunit
+>>> from nose.plugins.skip import SkipTest, Skip
+>>> support = os.path.join(os.path.dirname(__file__), 'support')
+>>> outfile = os.path.join(support, 'nosetests.xml')
+>>> from nose.plugins.plugintest import run_buffered as run
+>>> argv = [__file__, '-v', '--with-xunit', support,
+... '--xunit-file=%s' % outfile]
+>>> run(argv=argv, plugins=[Xunit(), Skip()]) # doctest: +ELLIPSIS
+test_skip.test_ok ... ok
+test_skip.test_err ... ERROR
+test_skip.test_fail ... FAIL
+test_skip.test_skip ... SKIP: not me
+<BLANKLINE>
+======================================================================
+ERROR: test_skip.test_err
+----------------------------------------------------------------------
+Traceback (most recent call last):
+...
+Exception: oh no
+<BLANKLINE>
+======================================================================
+FAIL: test_skip.test_fail
+----------------------------------------------------------------------
+Traceback (most recent call last):
+...
+AssertionError: bye
+<BLANKLINE>
+----------------------------------------------------------------------
+XML: .../support/nosetests.xml
+----------------------------------------------------------------------
+Ran 4 tests in ...s
+<BLANKLINE>
+FAILED (SKIP=1, errors=1, failures=1)
+
+>>> open(outfile, 'r').read() # doctest: +ELLIPSIS
+'<?xml version="1.0" encoding="UTF-8"?><testsuite name="nosetests" tests="4" errors="1" failures="1" skip="1"><testcase classname="test_skip" name="test_skip.test_ok" time="..." /><testcase classname="test_skip" name="test_skip.test_err" time="..."><error type="exceptions.Exception">.../error></testcase><testcase classname="test_skip" name="test_skip.test_fail" time="..."><failure type="exceptions.AssertionError">...</failure></testcase></testsuite>'
diff --git a/functional_tests/support/test_buggy_generators.py b/functional_tests/support/test_buggy_generators.py
new file mode 100644
index 0000000..9e40c10
--- /dev/null
+++ b/functional_tests/support/test_buggy_generators.py
@@ -0,0 +1,29 @@
+def test_generator_fails_before_yield():
+ a = 1 / 0
+ yield lambda: True
+
+
+def test_generator_fails_during_iteration():
+ for i in [1, 2, 3, 0, 5, 6]:
+ a = 1 / i
+ yield lambda: True
+
+
+def test_ok():
+ pass
+
+
+class TestBuggyGenerators(object):
+
+ def test_generator_fails_before_yield(self):
+ a = 1 / 0
+ yield lambda: True
+
+ def test_generator_fails_during_iteration(self):
+ for i in [1, 2, 3, 0, 5, 6]:
+ a = 1 / i
+ yield lambda: True
+
+ def test_ok(self):
+ pass
+
diff --git a/functional_tests/support/xunit/test_xunit_as_suite.py b/functional_tests/support/xunit/test_xunit_as_suite.py
new file mode 100644
index 0000000..dc68728
--- /dev/null
+++ b/functional_tests/support/xunit/test_xunit_as_suite.py
@@ -0,0 +1,20 @@
+import sys
+from nose.exc import SkipTest
+import unittest
+
+class TestForXunit(unittest.TestCase):
+
+ def test_success(self):
+ pass
+
+ def test_fail(self):
+ self.assertEqual("this","that")
+
+ def test_error(self):
+ raise TypeError("oops, wrong type")
+
+ def test_output(self):
+ sys.stdout.write("test-generated output\n")
+
+ def test_skip(self):
+ raise SkipTest("skipit")
diff --git a/functional_tests/test_buggy_generators.py b/functional_tests/test_buggy_generators.py
new file mode 100644
index 0000000..e710e4a
--- /dev/null
+++ b/functional_tests/test_buggy_generators.py
@@ -0,0 +1,35 @@
+import os
+import unittest
+from cStringIO import StringIO
+from nose.core import TestProgram
+from nose.config import Config
+
+here = os.path.dirname(__file__)
+support = os.path.join(here, 'support')
+
+
+class TestRunner(unittest.TextTestRunner):
+ def _makeResult(self):
+ self.result = unittest._TextTestResult(
+ self.stream, self.descriptions, self.verbosity)
+ return self.result
+
+
+class TestBuggyGenerators(unittest.TestCase):
+ def test_run_buggy_generators(self):
+ stream = StringIO()
+ runner = TestRunner(stream=stream)
+ prog = TestProgram(
+ argv=['nosetests',
+ os.path.join(support, 'test_buggy_generators.py')],
+ testRunner=runner,
+ config=Config(),
+ exit=False)
+ res = runner.result
+ print stream.getvalue()
+ self.assertEqual(res.testsRun, 12,
+ "Expected to run 12 tests, ran %s" % res.testsRun)
+ assert not res.wasSuccessful()
+ assert len(res.errors) == 4
+ assert not res.failures
+
diff --git a/functional_tests/test_generator_fixtures.py b/functional_tests/test_generator_fixtures.py
new file mode 100644
index 0000000..3240141
--- /dev/null
+++ b/functional_tests/test_generator_fixtures.py
@@ -0,0 +1,58 @@
+from nose.tools import eq_
+called = []
+
+def outer_setup():
+ called.append('outer_setup')
+
+def outer_teardown():
+ called.append('outer_teardown')
+
+def inner_setup():
+ called.append('inner_setup')
+
+def inner_teardown():
+ called.append('inner_teardown')
+
+def test_gen():
+ called[:] = []
+ for i in range(0, 5):
+ yield check, i
+
+def check(i):
+ expect = ['outer_setup']
+ for x in range(0, i):
+ expect.append('inner_setup')
+ expect.append('inner_teardown')
+ expect.append('inner_setup')
+ eq_(called, expect)
+
+
+test_gen.setup = outer_setup
+test_gen.teardown = outer_teardown
+check.setup = inner_setup
+check.teardown = inner_teardown
+
+
+class TestClass(object):
+ def setup(self):
+ print "setup called in", self
+ self.called = ['setup']
+
+ def teardown(self):
+ print "teardown called in", self
+ eq_(self.called, ['setup'])
+ self.called.append('teardown')
+
+ def test(self):
+ print "test called in", self
+ for i in range(0, 5):
+ yield self.check, i
+
+ def check(self, i):
+ print "check called in", self
+ expect = ['setup']
+ #for x in range(0, i):
+ # expect.append('setup')
+ # expect.append('teardown')
+ #expect.append('setup')
+ eq_(self.called, expect)
diff --git a/functional_tests/test_id_plugin.py b/functional_tests/test_id_plugin.py
index 7dd558c..fa9f245 100644
--- a/functional_tests/test_id_plugin.py
+++ b/functional_tests/test_id_plugin.py
@@ -46,7 +46,7 @@ class TestDiscoveryMode(PluginTester, unittest.TestCase):
def test_id_file_contains_ids_seen(self):
assert os.path.exists(idfile)
fh = open(idfile, 'r')
- ids = load(fh)
+ ids = load(fh)['ids']
fh.close()
assert ids
assert ids.keys()
@@ -82,6 +82,7 @@ class TestLoadNamesMode(PluginTester, unittest.TestCase):
fh.close()
assert ids
assert ids.keys()
+ ids = ids['ids']
self.assertEqual(filter(lambda i: int(i), ids.keys()), ids.keys())
assert len(ids.keys()) > 2
@@ -138,7 +139,7 @@ class TestWithDoctest_1(PluginTester, unittest.TestCase):
last = name
fh = open(idfile, 'r')
- ids = load(fh)
+ ids = load(fh)['ids']
fh.close()
for key, (file, mod, call) in ids.items():
assert mod != 'doctest', \
@@ -204,7 +205,7 @@ class TestWithDoctestFileTests_1(PluginTester, unittest.TestCase):
last = name
fh = open(idfile, 'r')
- ids = load(fh)
+ ids = load(fh)['ids']
fh.close()
for key, (file, mod, call) in ids.items():
assert mod != 'doctest', \
diff --git a/functional_tests/test_importer.py b/functional_tests/test_importer.py
index 9a934fa..c24fdcf 100644
--- a/functional_tests/test_importer.py
+++ b/functional_tests/test_importer.py
@@ -12,7 +12,11 @@ class TestImporter(unittest.TestCase):
self.imp = Importer()
self._mods = sys.modules.copy()
self._path = sys.path[:]
-
+ sys.modules.pop('mod', None)
+ sys.modules.pop('pak', None)
+ sys.modules.pop('pak.mod', None)
+ sys.modules.pop('pak.sub', None)
+
def tearDown(self):
to_del = [ m for m in sys.modules.keys() if
m not in self._mods ]
@@ -128,6 +132,35 @@ class TestImporter(unittest.TestCase):
mod_nose_imported2 = imp.importFromDir(d2, 'mod')
assert mod_nose_imported2 != mod_sys_imported, \
"nose failed to reimport same name, different dir"
+
+ def test_import_pkg_from_path_fpw(self):
+ imp = self.imp
+ imp.config.firstPackageWins = True
+ jn = os.path.join
+ d1 = jn(self.dir, 'dir1')
+ d2 = jn(self.dir, 'dir2')
+
+ # dotted name
+ p1 = imp.importFromPath(jn(d1, 'pak', 'mod.py'), 'pak.mod')
+ p2 = imp.importFromPath(jn(d2, 'pak', 'mod.py'), 'pak.mod')
+ self.assertEqual(p1, p2)
+ self.assertEqual(p1.__file__, p2.__file__)
+
+ # simple name -- package
+ sp1 = imp.importFromPath(jn(d1, 'pak'), 'pak')
+ sp2 = imp.importFromPath(jn(d2, 'pak'), 'pak')
+ self.assertEqual(sp1, sp2)
+ assert sp1.__path__
+ assert sp2.__path__
+ self.assertEqual(sp1.__path__, sp2.__path__)
+
+ # dotted name -- package
+ dp1 = imp.importFromPath(jn(d1, 'pak', 'sub'), 'pak.sub')
+ dp2 = imp.importFromPath(jn(d2, 'pak', 'sub'), 'pak.sub')
+ self.assertEqual(dp1, dp2)
+ assert dp1.__path__
+ assert dp2.__path__
+ self.assertEqual(dp1.__path__, dp2.__path__)
if __name__ == '__main__':
import logging
diff --git a/functional_tests/test_namespace_pkg.py b/functional_tests/test_namespace_pkg.py
index ba366b3..2db051e 100644
--- a/functional_tests/test_namespace_pkg.py
+++ b/functional_tests/test_namespace_pkg.py
@@ -30,25 +30,25 @@ class TestNamespacePackages(unittest.TestCase):
testRunner=runner,
exit=False)
res = runner.result
- self.assertEqual(res.testsRun, 2,
- "Expected to run 2 tests, ran %s" % res.testsRun)
+ self.assertEqual(res.testsRun, 1,
+ "Expected to run 1 test, ran %s" % res.testsRun)
assert res.wasSuccessful()
assert not res.errors
assert not res.failures
- def test_no_traverse_namespace(self):
- """Ensure the --no-traverse-namespace option only tests the specified
- module, not its other namespace package sibling.
+ def test_traverse_namespace(self):
+ """Ensure the --traverse-namespace option tests the other
+ namespace package sibling also.
"""
stream = StringIO()
runner = TestRunner(stream=stream)
runner.verbosity = 2
- prog = TestProgram(argv=['', '--no-traverse-namespace'],
+ prog = TestProgram(argv=['', '--traverse-namespace'],
testRunner=runner,
exit=False)
res = runner.result
- self.assertEqual(res.testsRun, 1,
- "Expected to run 1 tests, ran %s" % res.testsRun)
+ self.assertEqual(res.testsRun, 2,
+ "Expected to run 2 tests, ran %s" % res.testsRun)
assert res.wasSuccessful()
assert not res.errors
assert not res.failures
diff --git a/functional_tests/test_xunit.py b/functional_tests/test_xunit.py
new file mode 100644
index 0000000..77c3e7e
--- /dev/null
+++ b/functional_tests/test_xunit.py
@@ -0,0 +1,36 @@
+import os
+import unittest
+from nose.plugins.xunit import Xunit
+from nose.plugins.skip import Skip
+from nose.plugins import PluginTester
+
+support = os.path.join(os.path.dirname(__file__), 'support')
+xml_results_filename = os.path.join(support, "xunit.xml")
+
+# the plugin is tested better in unit tests.
+# this is just here for a sanity check
+
+class TestXUnitPlugin(PluginTester, unittest.TestCase):
+ activate = '--with-xunit'
+ args = ['-v','--xunit-file=%s' % xml_results_filename]
+ plugins = [Xunit(), Skip()]
+ suitepath = os.path.join(support, 'xunit')
+
+ def runTest(self):
+ print str(self.output)
+
+ assert "ERROR: test_error" in self.output
+ assert "FAIL: test_fail" in self.output
+ assert "test_skip (test_xunit_as_suite.TestForXunit) ... SKIP: skipit" in self.output
+ assert "XML: %s" % xml_results_filename in self.output
+
+ f = open(xml_results_filename,'r')
+ result = f.read()
+ f.close()
+ print result
+
+ assert '<?xml version="1.0" encoding="UTF-8"?>' in result
+ assert '<testsuite name="nosetests" tests="5" errors="1" failures="1" skip="1">' in result
+ assert '<testcase classname="test_xunit_as_suite.TestForXunit" name="test_xunit_as_suite.TestForXunit.test_error" time="0">' in result
+ assert '</testcase>' in result
+ assert '</testsuite>' in result \ No newline at end of file
diff --git a/nose/__init__.py b/nose/__init__.py
index 6f1da47..262fa17 100644
--- a/nose/__init__.py
+++ b/nose/__init__.py
@@ -1,396 +1,3 @@
-"""nose: a discovery-based unittest extension.
-
-nose provides extended test discovery and running features for
-unittest.
-
-Basic usage
------------
-
-Use the nosetests script (after installation by setuptools)::
-
- nosetests [options] [(optional) test files or directories]
-
-In addition to passing command-line options, you may also put configuration
-options in a .noserc or nose.cfg file in your home directory. These are
-standard .ini-style config files. Put your nosetests configuration in a
-[nosetests] section, with the -- prefix removed::
-
- [nosetests]
- verbosity=3
- with-doctest=1
-
-There are several other ways to use the nose test runner besides the
-`nosetests` script. You may use nose in a test script::
-
- import nose
- nose.main()
-
-If you don't want the test script to exit with 0 on success and 1 on failure
-(like unittest.main), use nose.run() instead::
-
- import nose
- result = nose.run()
-
-`result` will be true if the test run succeeded, or false if any test failed
-or raised an uncaught exception. Lastly, you can run nose.core directly, which
-will run nose.main()::
-
- python /path/to/nose/core.py
-
-Please see the usage message for the nosetests script for information
-about how to control which tests nose runs, which plugins are loaded,
-and the test output.
-
-Features
---------
-
-Writing tests is easier
-=======================
-
-nose collects tests from `unittest.TestCase` subclasses, of course. But you can
-also write simple test functions, and test classes that are not subclasses of
-`unittest.TestCase`. nose also supplies a number of helpful functions for
-writing timed tests, testing for exceptions, and other common use cases. See
-`Writing tests`_ and `Testing tools`_ for more.
-
-Running tests is easier
-=======================
-
-nose collects tests automatically, as long as you follow some simple
-guidelines for organizing your library and test code. There's no need
-to manually collect test cases into test suites. Running tests is
-responsive, since nose begins running tests as soon as the first test
-module is loaded. See `Finding and running tests`_ for more.
-
-Setting up your test environment is easier
-==========================================
-
-nose supports fixtures at the package, module, class, and test case
-level, so expensive initialization can be done as infrequently as
-possible. See Fixtures_ for more.
-
-Doing what you want to do is easier
-===================================
-
-nose has plugin hooks for loading, running, watching and reporting on
-tests and test runs. If you don't like the default collection scheme,
-or it doesn't suit the layout of your project, or you need reports in
-a format different from the unittest standard, or you need to collect
-some additional information about tests (like code coverage or
-profiling data), you can write a plugin to do so. See `Writing plugins`_
-for more. nose comes with a number of builtin plugins, for
-instance:
-
-* Output capture
-
- Unless called with the -s (--nocapture) switch, nose will capture stdout
- during each test run, and print the captured output only for tests that
- fail or have errors. The captured output is printed immediately
- following the error or failure output for the test. (Note that output in
- teardown methods is captured, but can't be output with failing tests,
- because teardown has not yet run at the time of the failure.)
-
-* Assert introspection
-
- When run with the -d (--detailed-errors) switch, nose will try to output
- additional information about the assert expression that failed with each
- failing test. Currently, this means that names in the assert expression
- will be expanded into any values found for them in the locals or globals
- in the frame in which the expression executed.
-
- In other words if you have a test like::
-
- def test_integers():
- a = 2
- assert a == 4, "assert 2 is 4"
-
- You will get output like::
-
- File "/path/to/file.py", line XX, in test_integers:
- assert a == 4, "assert 2 is 4"
- AssertionError: assert 2 is 4
- >> assert 2 == 4, "assert 2 is 4"
-
- Please note that dotted names are not expanded, and callables are not called
- in the expansion.
-
-Setuptools integration
-======================
-
-nose may be used with the setuptools_ test command. Simply specify
-nose.collector as the test suite in your setup file::
-
- setup (
- # ...
- test_suite = 'nose.collector'
- )
-
-Then to find and run tests, you can run::
-
- python setup.py test
-
-When running under setuptools, you can configure nose settings via the
-environment variables detailed in the nosetests script usage message,
-or the setup.cfg or ~/.noserc or ~/.nose.cfg config files.
-
-Please note that when run under the setuptools test command, some plugins will
-not be available, including the builtin coverage, and profiler plugins.
-
-nose also includes its own setuptools command, ``nosetests``, that
-provides support for all plugins and command line options. See
-nose.commands_ for more information about the ``nosetests`` command.
-
-.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools
-.. _nose.commands: #commands
-
-Writing tests
--------------
-
-As with py.test_, nose tests need not be subclasses of
-`unittest.TestCase`. Any function or class that matches the configured
-testMatch regular expression (`(?:^|[\\b_\\.-])[Tt]est)` by default --
-that is, has test or Test at a word boundary or following a - or _)
-and lives in a module that also matches that expression will be run as
-a test. For the sake of compatibility with legacy unittest test cases,
-nose will also load tests from `unittest.TestCase` subclasses just
-like unittest does. Like py.test, functional tests will be run in the
-order in which they appear in the module file. TestCase derived tests
-and other test classes are run in alphabetical order.
-
-.. _py.test: http://codespeak.net/py/current/doc/test.html
-
-Fixtures
-========
-
-nose supports fixtures (setup and teardown methods) at the package,
-module, class, and test level. As with py.test or unittest fixtures,
-setup always runs before any test (or collection of tests for test
-packages and modules); teardown runs if setup has completed
-successfully, whether or not the test or tests pass. For more detail
-on fixtures at each level, see below.
-
-Test packages
-=============
-
-nose allows tests to be grouped into test packages. This allows
-package-level setup; for instance, if you need to create a test database
-or other data fixture for your tests, you may create it in package setup
-and remove it in package teardown once per test run, rather than having to
-create and tear it down once per test module or test case.
-
-To create package-level setup and teardown methods, define setup and/or
-teardown functions in the `__init__.py` of a test package. Setup methods may
-be named `setup`, `setup_package`, `setUp`, or `setUpPackage`; teardown may
-be named `teardown`, `teardown_package`, `tearDown` or `tearDownPackage`.
-Execution of tests in a test package begins as soon as the first test
-module is loaded from the test package.
-
-Test modules
-============
-
-A test module is a python module that matches the testMatch regular
-expression. Test modules offer module-level setup and teardown; define the
-method `setup`, `setup_module`, `setUp` or `setUpModule` for setup,
-`teardown`, `teardown_module`, or `tearDownModule` for teardown. Execution
-of tests in a test module begins after all tests are collected.
-
-Test classes
-============
-
-A test class is a class defined in a test module that is either a subclass of
-`unittest.TestCase`, or matches testMatch. Test classes that don't descend
-from `unittest.TestCase` are run in the same way as those that do: methods in
-the class that match testMatch are discovered, and a test case constructed to
-run each with a fresh instance of the test class. Like `unittest.TestCase`
-subclasses, other test classes may define setUp and tearDown methods that will
-be run before and after each test method. Test classes that do not descend
-from `unittest.TestCase` may also include generator methods, and class-level
-fixtures. Class level fixtures may be named `setup_class`, `setupClass`,
-`setUpClass`, `setupAll` or `setUpAll` for set up and `teardown_class`,
-`teardownClass`, `tearDownClass`, `teardownAll` or `tearDownAll` for teardown
-and must be class methods.
-
-Test functions
-==============
-
-Any function in a test module that matches testMatch will be wrapped in a
-`FunctionTestCase` and run as a test. The simplest possible failing test is
-therefore::
-
- def test():
- assert False
-
-And the simplest passing test::
-
- def test():
- pass
-
-Test functions may define setup and/or teardown attributes, which will be
-run before and after the test function, respectively. A convenient way to
-do this, especially when several test functions in the same module need
-the same setup, is to use the provided with_setup decorator::
-
- def setup_func():
- # ...
-
- def teardown_func():
- # ...
-
- @with_setup(setup_func, teardown_func)
- def test():
- # ...
-
-For python 2.3 or earlier, add the attributes by calling the decorator
-function like so::
-
- def test():
- # ...
- test = with_setup(setup_func, teardown_func)(test)
-
-or by direct assignment::
-
- test.setup = setup_func
- test.teardown = teardown_func
-
-Please note that `with_setup` is useful *only* for test functions, not
-for test methods in `unittest.TestCase` subclasses or other test
-classes. For those cases, define `setUp` and `tearDown` methods in the
-class.
-
-Test generators
-===============
-
-nose supports test functions and methods that are generators. A simple
-example from nose's selftest suite is probably the best explanation::
-
- def test_evens():
- for i in range(0, 5):
- yield check_even, i, i*3
-
- def check_even(n, nn):
- assert n % 2 == 0 or nn % 2 == 0
-
-This will result in 4 tests. nose will iterate the generator, creating a
-function test case wrapper for each tuple it yields. As in the example, test
-generators must yield tuples, the first element of which must be a callable
-and the remaining elements the arguments to be passed to the callable.
-
-By default, the test name output for a generated test in verbose mode
-will be the name of the generator function or method, followed by the
-args passed to the yielded callable. If you want to show a different test
-name, set the ``description`` attribute of the yielded callable.
-
-Setup and teardown functions may be used with test generators. The setup and
-teardown attributes must be attached to the generator function::
-
- @with_setup(setup_func, teardown_func)
- def test_generator():
- ...
- yield func, arg, arg ...
-
-The setup and teardown functions will be executed for each test that the
-generator returns.
-
-For generator methods, the setUp and tearDown methods of the class (if any)
-will be run before and after each generated test case.
-
-Please note that method generators *are not* supported in `unittest.TestCase`
-subclasses.
-
-Finding and running tests
--------------------------
-
-nose, by default, follows a few simple rules for test discovery.
-
-* If it looks like a test, it's a test. Names of directories, modules,
- classes and functions are compared against the testMatch regular
- expression, and those that match are considered tests. Any class that is a
- `unittest.TestCase` subclass is also collected, so long as it is inside of a
- module that looks like a test.
-
-* Directories that don't look like tests and aren't packages are not
- inspected.
-
-* Packages are always inspected, but they are only collected if they look
- like tests. This means that you can include your tests inside of your
- packages (somepackage/tests) and nose will collect the tests without
- running package code inappropriately.
-
-* When a project appears to have library and test code organized into
- separate directories, library directories are examined first.
-
-* When nose imports a module, it adds that module's directory to sys.path;
- when the module is inside of a package, like package.module, it will be
- loaded as package.module and the directory of *package* will be added to
- sys.path.
-
-* If an object defines a __test__ attribute that does not evaluate to
- True, that object will not be collected, nor will any objects it
- contains.
-
-Be aware that plugins and command line options can change any of those rules.
-
-Testing tools
--------------
-
-The nose.tools module provides a number of testing aids that you may
-find useful, including decorators for restricting test execution time
-and testing for exceptions, and all of the same assertX methods found
-in `unittest.TestCase` (only spelled in pep08 fashion, so `assert_equal`
-rather than `assertEqual`). See `nose.tools`_ for a complete list.
-
-.. _nose.tools: http://code.google.com/p/python-nose/wiki/TestingTools
-
-About the name
---------------
-
-* nose is the least silly short synonym for discover in the dictionary.com
- thesaurus that does not contain the word 'spy'.
-* Pythons have noses
-* The nose knows where to find your tests
-* Nose Obviates Suite Employment
-
-Contact the author
-------------------
-
-You can email me at jpellerin+nose at gmail dot com.
-
-To report bugs, ask questions, or request features, please use the *issues*
-tab at the Google code site: http://code.google.com/p/python-nose/issues/list.
-Patches are welcome!
-
-Similar test runners
---------------------
-
-nose was inspired mainly by py.test_, which is a great test runner, but
-formerly was not all that easy to install, and is not based on unittest.
-
-Test suites written for use with nose should work equally well with py.test,
-and vice versa, except for the differences in output capture and command line
-arguments for the respective tools.
-
-.. _py.test: http://codespeak.net/py/current/doc/test.html
-
-License and copyright
----------------------
-
-nose is copyright Jason Pellerin 2005-2008
-
-This program is free software; you can redistribute it and/or modify it
-under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2 of the License, or (at your
-option) any later version.
-
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
-License for more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this program; if not, write to the Free Software Foundation,
-Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-"""
-
from nose.core import collector, main, run, run_exit, runmodule
# backwards compatibility
from nose.exc import SkipTest, DeprecatedTest
diff --git a/nose/case.py b/nose/case.py
index 0bd6137..076d9d7 100644
--- a/nose/case.py
+++ b/nose/case.py
@@ -37,6 +37,7 @@ class Test(unittest.TestCase):
self.capturedOutput = None
self.resultProxy = resultProxy
self.plugins = config.plugins
+ self.passed = None
unittest.TestCase.__init__(self)
def __call__(self, *arg, **kwarg):
diff --git a/nose/config.py b/nose/config.py
index a3a752d..5259a10 100644
--- a/nose/config.py
+++ b/nose/config.py
@@ -203,7 +203,9 @@ class Config(object):
self.verbosity = int(env.get('NOSE_VERBOSE', 1))
self.where = ()
self.workingDir = os.getcwd()
- self.traverseNamespace = True
+ self.traverseNamespace = False
+ self.firstPackageWins = False
+ self.parserClass = OptionParser
self._default = self.__dict__.copy()
self.update(kw)
@@ -275,6 +277,7 @@ class Config(object):
self.debug = options.debug
self.debugLog = options.debugLog
self.loggingConfig = options.loggingConfig
+ self.firstPackageWins = options.firstPackageWins
self.configureLogging()
if options.where is not None:
@@ -384,7 +387,7 @@ class Config(object):
if self.parser:
return self.parser
env = self.env
- parser = OptionParser(doc)
+ parser = self.parserClass(doc)
parser.add_option(
"-V","--version", action="store_true",
dest="version", default=False,
@@ -404,7 +407,8 @@ class Config(object):
type="int", help="Set verbosity; --verbosity=2 is "
"the same as -v")
parser.add_option(
- "-q", "--quiet", action="store_const", const=0, dest="verbosity")
+ "-q", "--quiet", action="store_const", const=0, dest="verbosity",
+ help="Be less verbose")
parser.add_option(
"-c", "--config", action="append", dest="files",
help="Load configuration from config file(s). May be specified "
@@ -418,12 +422,13 @@ class Config(object):
"working directory, which is the default. Others will be added "
"to the list of tests to execute. [NOSE_WHERE]"
)
- parser.add_option("-m", "--match", "--testmatch", action="store",
- dest="testMatch",
- help="Files, directories, function names, and class names "
- "that match this regular expression are considered tests. "
- "Default: %s [NOSE_TESTMATCH]" % self.testMatchPat,
- default=self.testMatchPat)
+ parser.add_option(
+ "-m", "--match", "--testmatch", action="store",
+ dest="testMatch",
+ help="Files, directories, function names, and class names "
+ "that match this regular expression are considered tests. "
+ "Default: %s [NOSE_TESTMATCH]" % self.testMatchPat,
+ default=self.testMatchPat)
parser.add_option(
"--tests", action="store", dest="testNames", default=None,
help="Run these tests (comma-separated list). This argument is "
@@ -482,10 +487,15 @@ class Config(object):
"executable. (The default on the windows platform is to "
"do so.)")
parser.add_option(
- "--no-traverse-namespace", action="store_false",
+ "--traverse-namespace", action="store_true",
default=self.traverseNamespace, dest="traverseNamespace",
- help="DO NOT traverse through all path entries of a "
- "namespace package")
+ help="Traverse through all path entries of a namespace package")
+ parser.add_option(
+ "--first-package-wins", "--first-pkg-wins", "--1st-pkg-wins",
+ default=False, dest="firstPackageWins",
+ help="nose's importer will normally evict a package from sys."
+ "modules if it sees a package with the same name in a different "
+ "location. Set this option to disable that behavior.")
self.plugins.loadPlugins()
self.pluginOpts(parser)
diff --git a/nose/core.py b/nose/core.py
index 62e2dcd..5648ed2 100644
--- a/nose/core.py
+++ b/nose/core.py
@@ -7,7 +7,6 @@ import os
import sys
import time
import unittest
-from warnings import warn
from nose.config import Config, all_config_files
from nose.loader import defaultTestLoader
@@ -68,149 +67,44 @@ class TextTestRunner(unittest.TextTestRunner):
class TestProgram(unittest.TestProgram):
- r"""usage: %prog [options] [names]
-
- nose provides extended test discovery and running features for
- unittest.
-
- nose collects tests automatically from python source files,
- directories and packages found in its working directory (which
- defaults to the current working directory). Any python source file,
- directory or package that matches the testMatch regular expression
- (by default: (?:^|[\b_\.-])[Tt]est) will be collected as a test (or
- source for collection of tests). In addition, all other packages
- found in the working directory will be examined for python source files
- or directories that match testMatch. Package discovery descends all
- the way down the tree, so package.tests and package.sub.tests and
- package.sub.sub2.tests will all be collected.
-
- Within a test directory or package, any python source file matching
- testMatch will be examined for test cases. Within a test module,
- functions and classes whose names match testMatch and TestCase
- subclasses with any name will be loaded and executed as tests. Tests
- may use the assert keyword or raise AssertionErrors to indicate test
- failure. TestCase subclasses may do the same or use the various
- TestCase methods available.
-
- Selecting Tests
- ---------------
-
- To specify which tests to run, pass test names on the command line:
-
- %prog only_test_this.py
+ """Collect and run tests, returning success or failure.
- Test names specified may be file or module names, and may optionally
- indicate the test case to run by separating the module or file name
- from the test case name with a colon. Filenames may be relative or
- absolute. Examples:
+ The arguments to TestProgram() are the same as to
+ :func:`main()` and :func:`run()`:
- %prog test.module
- %prog another.test:TestCase.test_method
- %prog a.test:TestCase
- %prog /path/to/test/file.py:test_function
-
- You may also change the working directory where nose looks for tests,
- use the -w switch:
-
- %prog -w /path/to/tests
-
- Note however that support for multiple -w arguments is deprecated
- in this version and will be removed in a future release, since as
- of nose 0.10 you can get the same behavior by specifying the
- target directories *without* the -w switch:
-
- %prog /path/to/tests /another/path/to/tests
-
- Further customization of test selection and loading is possible
- through the use of plugins.
-
- Test result output is identical to that of unittest, except for
- the additional features (error classes, and plugin-supplied
- features such as output capture and assert introspection) detailed
- in the options below.
-
- Configuration
- -------------
-
- In addition to passing command-line options, you may also put
- configuration options in a .noserc or nose.cfg file in your home
- directory. These are standard .ini-style config files. Put your
- nosetests configuration in a [nosetests] section. Options are the
- same as on the command line, with the -- prefix removed. For
- options that are simple switches, you must supply a value:
-
- [nosetests]
- verbosity=3
- with-doctest=1
-
- All configuration files that are found will be loaded and their options
- combined.
-
- Using Plugins
- -------------
-
- There are numerous nose plugins available via easy_install and
- elsewhere. To use a plugin, just install it. The plugin will add
- command line options to nosetests. To verify that the plugin is installed,
- run:
-
- nosetests --plugins
-
- You can add -v or -vv to that command to show more information
- about each plugin.
-
- If you are running nose.main() or nose.run() from a script, you
- can specify a list of plugins to use by passing a list of plugins
- with the plugins keyword argument.
-
- 0.9 plugins
- -----------
-
- nose 0.10 can use SOME plugins that were written for nose 0.9. The
- default plugin manager inserts a compatibility wrapper around 0.9
- plugins that adapts the changed plugin api calls. However, plugins
- that access nose internals are likely to fail, especially if they
- attempt to access test case or test suite classes. For example,
- plugins that try to determine if a test passed to startTest is an
- individual test or a suite will fail, partly because suites are no
- longer passed to startTest and partly because it's likely that the
- plugin is trying to find out if the test is an instance of a class
- that no longer exists.
+ * module: All tests are in this module (default: None)
+ * defaultTest: Tests to load (default: '.')
+ * argv: Command line arguments (default: None; sys.argv is read)
+ * testRunner: Test runner instance (default: None)
+ * testLoader: Test loader instance (default: None)
+ * env: Environment; ignored if config is provided (default: None;
+ os.environ is read)
+ * config: :class:`nose.config.Config` instance (default: None)
+ * suite: Suite or list of tests to run (default: None). Passing a
+ suite or lists of tests will bypass all test discovery and
+ loading. *ALSO NOTE* that if you pass a unittest.TestSuite
+ instance as the suite, context fixtures at the class, module and
+ package level will not be used, and many plugin hooks will not
+ be called. If you want normal nose behavior, either pass a list
+ of tests, or a fully-configured :class:`nose.suite.ContextSuite`.
+ * exit: Exit after running tests and printing report (default: True)
+ * plugins: List of plugins to use; ignored if config is provided
+ (default: load plugins with DefaultPluginManager)
+ * addplugins: List of **extra** plugins to use. Pass a list of plugin
+ instances in this argument to make custom plugins available while
+ still using the DefaultPluginManager.
"""
verbosity = 1
def __init__(self, module=None, defaultTest='.', argv=None,
testRunner=None, testLoader=None, env=None, config=None,
- suite=None, exit=True, plugins=None):
- """
- Collect and run tests, returning success or failure.
-
- The arguments to __init__ are the same as to `main()` and `run()`:
-
- * module: All tests are in this module (default: None)
- * defaultTest: Tests to load (default: '.')
- * argv: Command line arguments (default: None; sys.argv is read)
- * testRunner: Test runner instance (default: None)
- * testLoader: Test loader instance (default: None)
- * env: Environment; ignored if config is provided (default: None;
- os.environ is read)
- * config: `nose.config.Config`_ instance (default: None)
- * suite: Suite or list of tests to run (default: None). Passing a
- suite or lists of tests will bypass all test discovery and
- loading. *ALSO NOTE* that if you pass a unittest.TestSuite
- instance as the suite, context fixtures at the class, module and
- package level will not be used, and many plugin hooks will not
- be called. If you want normal nose behavior, either pass a list
- of tests, or a fully-configured `nose.suite.ContextSuite`_.
- * exit: Exit after running tests and printing report (default: True)
- * plugins: List of plugins to use; ignored if config is provided
- (default: load plugins with DefaultPluginManager)
-
- """
+ suite=None, exit=True, plugins=None, addplugins=None):
if env is None:
env = os.environ
if config is None:
config = self.makeConfig(env, plugins)
+ if addplugins:
+ config.plugins.addPlugins(addplugins)
self.config = config
self.suite = suite
self.exit = exit
@@ -233,7 +127,7 @@ class TestProgram(unittest.TestProgram):
def parseArgs(self, argv):
"""Parse argv and env and configure running environment.
"""
- self.config.configure(argv, doc=TestProgram.__doc__)
+ self.config.configure(argv, doc=self.usage())
log.debug("configured %s", self.config)
# quick outs: version, plugins (optparse would have already
@@ -334,6 +228,12 @@ class TestProgram(unittest.TestProgram):
initial_indent=' ',
subsequent_indent=' '))
print
+
+ def usage(cls):
+ return open(os.path.join(
+ os.path.dirname(__file__), 'usage.txt'), 'r').read()
+ usage = classmethod(usage)
+
# backwards compatibility
run_exit = main = TestProgram
@@ -350,17 +250,20 @@ def run(*arg, **kw):
* testLoader: Test loader instance (default: None)
* env: Environment; ignored if config is provided (default: None;
os.environ is read)
- * config: `nose.config.Config`_ instance (default: None)
+ * config: :class:`nose.config.Config` instance (default: None)
* suite: Suite or list of tests to run (default: None). Passing a
suite or lists of tests will bypass all test discovery and
loading. *ALSO NOTE* that if you pass a unittest.TestSuite
instance as the suite, context fixtures at the class, module and
package level will not be used, and many plugin hooks will not
be called. If you want normal nose behavior, either pass a list
- of tests, or a fully-configured `nose.suite.ContextSuite`_.
+ of tests, or a fully-configured :class:`nose.suite.ContextSuite`.
* plugins: List of plugins to use; ignored if config is provided
(default: load plugins with DefaultPluginManager)
-
+ * addplugins: List of **extra** plugins to use. Pass a list of plugin
+ instances in this argument to make custom plugins available while
+ still using the DefaultPluginManager.
+
With the exception that the ``exit`` argument is always set
to False.
"""
@@ -381,13 +284,7 @@ def collector():
unittest.TestSuite. The collector will, by default, load options from
all config files and execute loader.loadTestsFromNames() on the
configured testNames, or '.' if no testNames are configured.
- """
-
- warn("Support for `python setup.py test` is deprecated and will be "
- "removed in the next release of nose. "
- "Use `python setup.py nosetests` instead",
- DeprecationWarning)
-
+ """
# plugins that implement any of these methods are disabled, since
# we don't control the test runner and won't be able to run them
# finalize() is also not called, but plugins that use it aren't disabled,
diff --git a/nose/importer.py b/nose/importer.py
index d0c9156..e4b867b 100644
--- a/nose/importer.py
+++ b/nose/importer.py
@@ -75,7 +75,9 @@ class Importer(object):
# we get a fresh copy of anything we are trying to load
# from a new path
log.debug("sys.modules has %s as %s", part_fqname, old)
- if self.sameModule(old, filename):
+ if (self.sameModule(old, filename)
+ or (self.config.firstPackageWins and
+ getattr(old, '__path__', None))):
mod = old
else:
del sys.modules[part_fqname]
diff --git a/nose/loader.py b/nose/loader.py
index d3930fc..b6bc828 100644
--- a/nose/loader.py
+++ b/nose/loader.py
@@ -218,11 +218,17 @@ class TestLoader(unittest.TestLoader):
* a function name resolvable within the same module
"""
def generate(g=generator, m=module):
- for test in g():
- test_func, arg = self.parseGeneratedTest(test)
- if not callable(test_func):
- test_func = getattr(m, test_func)
- yield FunctionTestCase(test_func, arg=arg, descriptor=g)
+ try:
+ for test in g():
+ test_func, arg = self.parseGeneratedTest(test)
+ if not callable(test_func):
+ test_func = getattr(m, test_func)
+ yield FunctionTestCase(test_func, arg=arg, descriptor=g)
+ except KeyboardInterrupt:
+ raise
+ except:
+ exc = sys.exc_info()
+ yield Failure(*exc)
return self.suiteClass(generate, context=generator, can_split=False)
def loadTestsFromGeneratorMethod(self, generator, cls):
@@ -243,21 +249,28 @@ class TestLoader(unittest.TestLoader):
generator = getattr(inst, method)
def generate(g=generator, c=cls):
- for test in g():
- test_func, arg = self.parseGeneratedTest(test)
- if not callable(test_func):
- test_func = getattr(c, test_func)
- if ismethod(test_func):
- yield MethodTestCase(test_func, arg=arg, descriptor=g)
- elif isfunction(test_func):
- # In this case we're forcing the 'MethodTestCase'
- # to run the inline function as its test call,
- # but using the generator method as the 'method of
- # record' (so no need to pass it as the descriptor)
- yield MethodTestCase(g, test=test_func, arg=arg)
- else:
- yield Failure(TypeError,
- "%s is not a function or method" % test_func)
+ try:
+ for test in g():
+ test_func, arg = self.parseGeneratedTest(test)
+ if not callable(test_func):
+ test_func = getattr(c, test_func)
+ if ismethod(test_func):
+ yield MethodTestCase(test_func, arg=arg, descriptor=g)
+ elif isfunction(test_func):
+ # In this case we're forcing the 'MethodTestCase'
+ # to run the inline function as its test call,
+ # but using the generator method as the 'method of
+ # record' (so no need to pass it as the descriptor)
+ yield MethodTestCase(g, test=test_func, arg=arg)
+ else:
+ yield Failure(
+ TypeError,
+ "%s is not a function or method" % test_func)
+ except KeyboardInterrupt:
+ raise
+ except:
+ exc = sys.exc_info()
+ yield Failure(*exc)
return self.suiteClass(generate, context=generator, can_split=False)
def loadTestsFromModule(self, module, path=None, discovered=False):
diff --git a/nose/plugins/__init__.py b/nose/plugins/__init__.py
index 8f4329a..91ef8aa 100644
--- a/nose/plugins/__init__.py
+++ b/nose/plugins/__init__.py
@@ -6,18 +6,21 @@ nose supports plugins for test collection, selection, observation and
reporting. There are two basic rules for plugins:
* Plugin classes should subclass `nose.plugins.Plugin`_.
+
* Plugins may implement any of the methods described in the class
`IPluginInterface`_ in nose.plugins.base. Please note that this class is for
documentary purposes only; plugins may not subclass IPluginInterface.
.. _nose.plugins.Plugin: http://python-nose.googlecode.com/svn/trunk/nose/plugins/base.py
+.. _IPluginInterface: plugins.interface
Registering
===========
-.. Note:: Important note: the following applies only to the default \
-plugin manager. Other plugin managers may use different means to \
-locate and load plugins.
+.. Note::
+ Important note: the following applies only to the default
+ plugin manager. Other plugin managers may use different means to
+ locate and load plugins.
For nose to find a plugin, it must be part of a package that uses
setuptools_, and the plugin must be included in the entry points defined
@@ -119,111 +122,29 @@ Recipes
Examples: `nose.plugins.attrib`_, `nose.plugins.doctests`_,
`nose.plugins.testid`_
+
+.. _ErrorClassPlugin: plugins.errorclasses
+.. _nose.plugins.deprecated: plugins.deprecated
+.. _nose.plugins.skip: plugins.skip
+.. _nose.plugins.capture: plugins.capture
+.. _nose.plugins.failuredetail: plugins.failuredetail
+.. _nose.plugins.doctests: plugins.doctests
+.. _nose.plugins.cover: plugins.cover
+.. _nose.plugins.prof: plugins.prof
+.. _nose.plugins.attrib: plugins.attrib
+.. _nose.plugins.testid: plugins.testid
More Examples
=============
See any builtin plugin or example plugin in the examples_ directory in
-the nose source distribution.
+the nose source distribution. There is a list of third-party plugins
+`on jottit`_.
.. _examples/html_plugin/htmlplug.py: http://python-nose.googlecode.com/svn/trunk/examples/html_plugin/htmlplug.py
.. _examples: http://python-nose.googlecode.com/svn/trunk/examples
-
-
-Testing Plugins
-===============
-
-The plugin interface is well-tested enough so that you can safely unit
-test your use of its hooks with some level of confidence. However,
-there is a mixin for unittest.TestCase called PluginTester that's
-designed to test plugins in their native runtime environment.
-
-Here's a simple example with a do-nothing plugin and a composed suite.
-
- >>> import unittest
- >>> from nose.plugins import Plugin, PluginTester
- >>> class FooPlugin(Plugin):
- ... pass
- >>> class TestPluginFoo(PluginTester, unittest.TestCase):
- ... activate = '--with-foo'
- ... plugins = [FooPlugin()]
- ... def test_foo(self):
- ... for line in self.output:
- ... # i.e. check for patterns
- ... pass
- ...
- ... # or check for a line containing ...
- ... assert "ValueError" in self.output
- ... def makeSuite(self):
- ... class TC(unittest.TestCase):
- ... def runTest(self):
- ... raise ValueError("I hate foo")
- ... return unittest.TestSuite([TC()])
- ...
- >>> res = unittest.TestResult()
- >>> case = TestPluginFoo('test_foo')
- >>> case(res)
- >>> res.errors
- []
- >>> res.failures
- []
- >>> res.wasSuccessful()
- True
- >>> res.testsRun
- 1
-
-And here is a more complex example of testing a plugin that has extra
-arguments and reads environment variables.
-
- >>> import unittest, os
- >>> from nose.plugins import Plugin, PluginTester
- >>> class FancyOutputter(Plugin):
- ... name = "fancy"
- ... def configure(self, options, conf):
- ... Plugin.configure(self, options, conf)
- ... if not self.enabled:
- ... return
- ... self.fanciness = 1
- ... if options.more_fancy:
- ... self.fanciness = 2
- ... if 'EVEN_FANCIER' in self.env:
- ... self.fanciness = 3
- ...
- ... def options(self, parser, env=os.environ):
- ... self.env = env
- ... parser.add_option('--more-fancy', action='store_true')
- ... Plugin.options(self, parser, env=env)
- ...
- ... def report(self, stream):
- ... stream.write("FANCY " * self.fanciness)
- ...
- >>> class TestFancyOutputter(PluginTester, unittest.TestCase):
- ... activate = '--with-fancy' # enables the plugin
- ... plugins = [FancyOutputter()]
- ... args = ['--more-fancy']
- ... env = {'EVEN_FANCIER': '1'}
- ...
- ... def test_fancy_output(self):
- ... assert "FANCY FANCY FANCY" in self.output, (
- ... "got: %s" % self.output)
- ... def makeSuite(self):
- ... class TC(unittest.TestCase):
- ... def runTest(self):
- ... raise ValueError("I hate fancy stuff")
- ... return unittest.TestSuite([TC()])
- ...
- >>> res = unittest.TestResult()
- >>> case = TestFancyOutputter('test_fancy_output')
- >>> case(res)
- >>> res.errors
- []
- >>> res.failures
- []
- >>> res.wasSuccessful()
- True
- >>> res.testsRun
- 1
-
+.. _on jottit: http://nose-plugins.jottit.com/
+
"""
from nose.plugins.base import Plugin
from nose.plugins.manager import *
diff --git a/nose/plugins/allmodules.py b/nose/plugins/allmodules.py
new file mode 100644
index 0000000..320b21a
--- /dev/null
+++ b/nose/plugins/allmodules.py
@@ -0,0 +1,45 @@
+"""Use the AllModules plugin by passing :option:`--all-modules` or setting the
+NOSE_ALL_MODULES environment variable to enable collection and execution of
+tests in all python modules. Normal nose behavior is to look for tests only in
+modules that match testMatch.
+
+See also: :doc:`../doc_tests/test_allmodules/test_allmodules`
+
+.. warning ::
+
+ This plugin can have surprising interactions with plugins that load tests
+ from what nose normally considers non-test modules, such as
+ the :doc:`doctest plugin <doctests>`. This is because any given
+ object in a module can't be loaded both by a plugin and the normal nose
+ :class:`test loader <nose.loader.TestLoader>`. Also, if you have test-like
+ functions or classes in non-test modules that are not tests, you will
+ likely see errors as nose attempts to run them as tests.
+
+"""
+
+import os
+from nose.plugins.base import Plugin
+
+class AllModules(Plugin):
+ """Collect tests from all python modules.
+ """
+ def options(self, parser, env):
+ """Register commandline options.
+ """
+ env_opt = 'NOSE_ALL_MODULES'
+ parser.add_option('--all-modules',
+ action="store_true",
+ dest=self.enableOpt,
+ default=env.get(env_opt),
+ help="Enable plugin %s: %s [%s]" %
+ (self.__class__.__name__, self.help(), env_opt))
+
+ def wantFile(self, file):
+ """Override to return True for all files ending with .py"""
+ # always want .py files
+ if file.endswith('.py'):
+ return True
+
+ def wantModule(self, module):
+ """Override return True for all modules"""
+ return True
diff --git a/nose/plugins/attrib.py b/nose/plugins/attrib.py
index f9650d5..bf7c165 100644
--- a/nose/plugins/attrib.py
+++ b/nose/plugins/attrib.py
@@ -2,34 +2,33 @@
Simple syntax (-a, --attr) examples:
-* `nosetests -a status=stable`:
+* ``nosetests -a status=stable``
Only test cases with attribute "status" having value "stable"
-* `nosetests -a priority=2,status=stable`:
+* ``nosetests -a priority=2,status=stable``
Both attributes must match
-* `nosetests -a priority=2 -a slow`:
+* ``nosetests -a priority=2 -a slow``
Either attribute must match
-* `nosetests -a tags=http`:
+* ``nosetests -a tags=http``
Attribute list "tags" must contain value "http" (see test_foobar()
below for definition)
-* `nosetests -a slow`:
+* ``nosetests -a slow``
Attribute "slow" must be defined and its value cannot equal to False
(False, [], "", etc...)
-* `nosetests -a '!slow'`:
+* ``nosetests -a '!slow'``
Attribute "slow" must NOT be defined or its value must be equal to False.
Note the quotes around the value -- this may be necessary if your shell
- interprets `!' as a special character.
+ interprets '!' as a special character.
Eval expression syntax (-A, --eval-attr) examples:
-* `nosetests -A "not slow"`
-
-* `nosetests -A "(priority > 5) and not slow"`
+* ``nosetests -A "not slow"``
+* ``nosetests -A "(priority > 5) and not slow"``
"""
import logging
import os
@@ -80,9 +79,8 @@ class AttributeSelector(Plugin):
Plugin.__init__(self)
self.attribs = []
- def options(self, parser, env=os.environ):
- """Add command-line options for this plugin."""
-
+ def options(self, parser, env):
+ """Register command line options"""
parser.add_option("-a", "--attr",
dest="attr", action="append",
default=env.get('NOSE_ATTR'),
@@ -207,8 +205,12 @@ class AttributeSelector(Plugin):
return False
def wantFunction(self, function):
+ """Accept the function if its attributes match.
+ """
return self.validateAttrib(function.__dict__)
def wantMethod(self, method):
+ """Accept the method if its attributes match.
+ """
attribs = AttributeGetter(method.im_class, method)
return self.validateAttrib(attribs)
diff --git a/nose/plugins/base.py b/nose/plugins/base.py
index dbab752..7870ae4 100644
--- a/nose/plugins/base.py
+++ b/nose/plugins/base.py
@@ -36,7 +36,7 @@ class Plugin(object):
if self.enableOpt is None:
self.enableOpt = "enable_plugin_%s" % self.name.replace('-', '_')
- def addOptions(self, parser, env=os.environ):
+ def addOptions(self, parser, env=None):
"""Add command-line options for this plugin.
The base plugin class adds --with-$name by default, used to enable the
@@ -44,10 +44,12 @@ class Plugin(object):
"""
self.add_options(parser, env)
- def add_options(self, parser, env=os.environ):
+ def add_options(self, parser, env=None):
"""Non-camel-case version of func name for backwards compatibility.
"""
# FIXME raise deprecation warning if wasn't called by wrapper
+ if env is None:
+ env = os.environ
try:
self.options(parser, env)
self.can_configure = True
@@ -57,7 +59,7 @@ class Plugin(object):
self.enabled = False
self.can_configure = False
- def options(self, parser, env=os.environ):
+ def options(self, parser, env):
"""New plugin API: override to just set options. Implement
this method instead of addOptions or add_options for normal
options behavior with protection from OptionConflictErrors.
@@ -101,124 +103,13 @@ class Plugin(object):
class IPluginInterface(object):
"""
- Nose plugin API
- ---------------
-
- While it is recommended that plugins subclass
- nose.plugins.Plugin, the only requirements for a plugin are
- that it implement the methods `options(self, parser, env)` and
- `configure(self, options, conf)`, and have the attributes
- `enabled`, `name` and `score`.
-
- Plugins may implement any or all of the methods documented
- below. Please note that they *must not* subclass `IPluginInterface`;
- `IPluginInterface` is a only description of the plugin API.
-
- When plugins are called, the first plugin that implements a method
- and returns a non-None value wins, and plugin processing ends. The
- exceptions to this are methods marked as `generative` or
- `chainable`. `generative` methods combine the output of all
- plugins that respond with an iterable into a single flattened
- iterable response (a generator, really). `chainable` methods pass
- the results of calling plugin A as the input to plugin B, where
- the positions in the chain are determined by the plugin sort
- order, which is in order by `score` descending.
-
- In general, plugin methods correspond directly to methods of
- `nose.selector.Selector`, `nose.loader.TestLoader` and
- `nose.result.TextTestResult` are called by those methods when they are
- called. In some cases, the plugin hook doesn't neatly match the
- method in which it is called; for those, the documentation for the
- hook will tell you where in the test process it is called.
-
- Plugin hooks fall into four broad categories: selecting and
- loading tests, handling errors raised by tests, preparing objects
- used in the testing process, and watching and reporting on test
- results.
-
- Selecting and loading tests
- ===========================
-
- To alter test selection behavior, implement any necessary `want*`
- methods as outlined below. Keep in mind, though, that when your
- plugin returns True from a `want*` method, you will send the requested
- object through the normal test collection process. If the object
- represents something from which normal tests can't be collected, you
- must also implement a loader method to load the tests.
-
- Examples:
-
- * The builtin doctests plugin implements `wantFile` to enable
- loading of doctests from files that are not python modules. It
- also implements `loadTestsFromModule` to load doctests from
- python modules, and `loadTestsFromFile` to load tests from the
- non-module files selected by `wantFile`.
-
- * The builtin attrib plugin implements `wantFunction` and
- `wantMethod` so that it can reject tests that don't match the
- specified attributes.
-
- Handling errors
- ===============
-
- To alter error handling behavior -- for instance to catch a
- certain class of exception and handle it differently from the
- normal error or failure handling -- you should subclass
- `ErrorClassPlugin`. See the documentation for `ErrorClassPlugin`_ for
- more details.
-
- Examples:
-
- * The builtin skip and deprecated plugins are ErrorClass plugins.
-
- Preparing test objects
- ======================
-
- To alter, get a handle on, or replace test framework objects such
- as the loader, result, runner, and test cases, use the appropriate
- prepare methods. The simplest reason to use prepare is if you need
- to use an object yourself. For example, the isolate plugin
- implements `prepareTestLoader` so that it can use the test loader
- later on to load tests. If you return a value from a prepare
- method, that value will be used in place of the loader, result,
- runner or test case, respectively. When replacing test cases, be
- aware that you are replacing the entire test case -- including the
- whole `run(result)` method of the `unittest.TestCase` -- so if you
- want normal unittest test result reporting, you must implement the
- same calls to result as `unittest.TestCase.run`.
-
- Examples:
-
- * The builtin isolate plugin implements `prepareTestLoader` but
- does not replace the test loader.
-
- * The builtin profile plugin implements `prepareTest` and does
- replace the top-level test case by returning the case wrapped in
- the profiler function.
-
- Watching or reporting on tests
- ==============================
-
- To record information about tests or other modules imported during
- the testing process, output additional reports, or entirely change
- test report output, implement any of the methods outlined below that
- correspond to TextTestResult methods.
-
- Examples:
-
- * The builtin cover plugin implements `begin` and `report` to
- capture and report code coverage metrics for all or selected modules
- loaded during testing.
-
- * The builtin profile plugin implements `begin`, `prepareTest` and
- `report` to record and output profiling information. In this
- case, the plugin's `prepareTest` method constructs a function that
- runs the test through the hotshot profiler's runcall() method.
+ IPluginInteface describes the plugin API. Do not subclass or use this
+ class directly.
"""
def __new__(cls, *arg, **kw):
raise TypeError("IPluginInterface class is for documentation only")
- def addOptions(self, parser, env=os.environ):
+ def addOptions(self, parser, env):
"""Called to allow plugin to register command line
options with the parser.
@@ -239,7 +130,7 @@ class IPluginInterface(object):
.. Note:: DEPRECATED -- check error class in addError instead
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
"""
pass
@@ -251,7 +142,7 @@ class IPluginInterface(object):
test has raised an error.
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
err : 3-tuple
sys.exc_info() tuple
@@ -268,7 +159,7 @@ class IPluginInterface(object):
want to stop other plugins from seeing that the test has failed.
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
err : 3-tuple
sys.exc_info() tuple
@@ -291,7 +182,7 @@ class IPluginInterface(object):
.. Note:: DEPRECATED -- check error class in addError instead
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
"""
pass
@@ -302,7 +193,7 @@ class IPluginInterface(object):
want to stop other plugins from seeing the passing test.
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
capt : string
Captured output, if any.
@@ -349,7 +240,7 @@ class IPluginInterface(object):
(after stopTest).
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
"""
pass
@@ -398,7 +289,7 @@ class IPluginInterface(object):
"""Called before the test is run (before startTest).
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
"""
pass
@@ -429,7 +320,7 @@ class IPluginInterface(object):
them.
.. Note:: When tests are run under a test runner other than
- `nose.core.TextTestRunner`_, for example when tests are run
+ :class:`nose.core.TextTestRunner`, for example when tests are run
via ``python setup.py test``, this method may be called
**before** the default report output is sent.
"""
@@ -440,7 +331,7 @@ class IPluginInterface(object):
`nose.case.Test.shortDescription`.
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
"""
pass
@@ -452,7 +343,7 @@ class IPluginInterface(object):
tuple.
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
err : 3-tuple
sys.exc_info() tuple
@@ -472,7 +363,7 @@ class IPluginInterface(object):
return (test, err)
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
err : 3-tuple
sys.exc_info() tuple
@@ -488,7 +379,7 @@ class IPluginInterface(object):
error processing, return a true value.
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
err : 3-tuple
sys.exc_info() tuple
@@ -501,7 +392,7 @@ class IPluginInterface(object):
prevent normal failure processing, return a true value.
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
err : 3-tuple
sys.exc_info() tuple
@@ -644,12 +535,19 @@ class IPluginInterface(object):
makeTest._new = True
makeTest.generative = True
- def options(self, parser, env=os.environ):
+ def options(self, parser, env):
"""Called to allow plugin to register command line
options with the parser.
Do *not* return a value from this method unless you want to stop
all other plugins from setting their options.
+
+ :Parameters:
+ parser : :class:`ConfigParser`
+ options parserinstance
+
+ env : dict
+ environment, defaults to os.environ
"""
pass
options._new = True
@@ -666,7 +564,7 @@ class IPluginInterface(object):
instead.
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
"""
pass
@@ -685,7 +583,7 @@ class IPluginInterface(object):
exception handling and result calls, etc.
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
"""
pass
@@ -698,8 +596,8 @@ class IPluginInterface(object):
loader. Only valid when using nose.TestProgram.
:Parameters:
- loader : `nose.loader.TestLoader` or other loader instance
- the test loader
+ loader : :class:`nose.loader.TestLoader`
+ (or other loader instance) the test loader
"""
pass
prepareTestLoader._new = True
@@ -719,8 +617,8 @@ class IPluginInterface(object):
and return the patched result.
:Parameters:
- result : `nose.result.TextTestResult` or other result instance
- the test result
+ result : :class:`nose.result.TextTestResult`
+ (or other result instance) the test result
"""
pass
prepareTestResult._new = True
@@ -731,8 +629,8 @@ class IPluginInterface(object):
test runner, return None. Only valid when using nose.TestProgram.
:Parameters:
- runner : `nose.core.TextTestRunner` or other runner instance
- the test runner
+ runner : :class:`nose.core.TextTestRunner`
+ (or other runner instance) the test runner
"""
pass
prepareTestRunner._new = True
@@ -777,7 +675,7 @@ class IPluginInterface(object):
you want to stop other plugins from seeing the test start.
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
"""
pass
@@ -798,7 +696,7 @@ class IPluginInterface(object):
you want to stop other plugins from seeing that the test has stopped.
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
"""
pass
@@ -807,7 +705,7 @@ class IPluginInterface(object):
"""Return a short test name. Called by `nose.case.Test.__str__`.
:Parameters:
- test : `nose.case.Test`_
+ test : :class:`nose.case.Test`
the test case
"""
pass
diff --git a/nose/plugins/builtin.py b/nose/plugins/builtin.py
index 870cef3..4fcc001 100644
--- a/nose/plugins/builtin.py
+++ b/nose/plugins/builtin.py
@@ -15,7 +15,10 @@ builtins = (
('nose.plugins.prof', 'Profile'),
('nose.plugins.skip', 'Skip'),
('nose.plugins.testid', 'TestId'),
- ('nose.plugins.multiprocess', 'MultiProcess')
+ ('nose.plugins.multiprocess', 'MultiProcess'),
+ ('nose.plugins.xunit', 'Xunit'),
+ ('nose.plugins.allmodules', 'AllModules'),
+ ('nose.plugins.collect', 'CollectOnly'),
)
for module, cls in builtins:
@@ -28,3 +31,4 @@ for module, cls in builtins:
plug = getattr(plugmod, cls)
plugins.append(plug)
globals()[cls] = plug
+
diff --git a/nose/plugins/capture.py b/nose/plugins/capture.py
index 0748a44..9826f95 100644
--- a/nose/plugins/capture.py
+++ b/nose/plugins/capture.py
@@ -2,7 +2,12 @@
This plugin captures stdout during test execution, appending any
output captured to the error or failure output, should the test fail
or raise an error. It is enabled by default but may be disabled with
-the options -s or --nocapture.
+the options ``-s`` or ``--nocapture``.
+
+:Options:
+ ``--nocapture``
+ Don't capture stdout (any stdout output will be printed immediately)
+
"""
import logging
import os
@@ -16,8 +21,8 @@ log = logging.getLogger(__name__)
class Capture(Plugin):
"""
- Output capture plugin. Enabled by default. Disable with -s or
- --nocapture. This plugin captures stdout during test execution,
+ Output capture plugin. Enabled by default. Disable with ``-s`` or
+ ``--nocapture``. This plugin captures stdout during test execution,
appending any output captured to the error or failure output,
should the test fail or raise an error.
"""
@@ -30,7 +35,9 @@ class Capture(Plugin):
self.stdout = []
self._buf = None
- def options(self, parser, env=os.environ):
+ def options(self, parser, env):
+ """Register commandline options
+ """
parser.add_option(
"-s", "--nocapture", action="store_false",
default=not env.get(self.env_opt), dest="capture",
@@ -38,21 +45,31 @@ class Capture(Plugin):
"will be printed immediately) [NOSE_NOCAPTURE]")
def configure(self, options, conf):
+ """Configure plugin. Plugin is enabled by default.
+ """
self.conf = conf
if not options.capture:
self.enabled = False
def afterTest(self, test):
+ """Clear capture buffer.
+ """
self.end()
self._buf = None
def begin(self):
+ """Replace sys.stdout with capture buffer.
+ """
self.start() # get an early handle on sys.stdout
def beforeTest(self, test):
+ """Flush capture buffer.
+ """
self.start()
def formatError(self, test, err):
+ """Add captured output to error report.
+ """
test.capturedOutput = output = self.buffer
self._buf = None
if not output:
@@ -64,6 +81,8 @@ class Capture(Plugin):
return (ec, self.addCaptureToErr(ev, output), tb)
def formatFailure(self, test, err):
+ """Add captured output to failure report.
+ """
return self.formatError(test, err)
def addCaptureToErr(self, ev, output):
@@ -80,6 +99,8 @@ class Capture(Plugin):
sys.stdout = self.stdout.pop()
def finalize(self, result):
+ """Restore stdout.
+ """
while self.stdout:
self.end()
diff --git a/nose/plugins/collect.py b/nose/plugins/collect.py
new file mode 100644
index 0000000..0685971
--- /dev/null
+++ b/nose/plugins/collect.py
@@ -0,0 +1,93 @@
+"""
+This plugin bypasses the actual execution of tests, instead just collecting
+test names. Fixtures are also bypassed, so execution should be very quick.
+
+This plugin is useful in combination with the testid plugin (--with-id). Run
+both together to get an indexed list of all tests that will enable you to run
+individual tests by index number.
+
+This plugin is also useful for counting tests in a test suite, and making
+people watching your demo think all of your tests pass.
+"""
+from nose.plugins.base import Plugin
+from nose.case import Test
+import logging
+import unittest
+
+log = logging.getLogger(__name__)
+
+
+class CollectOnly(Plugin):
+ """
+ Collect and output test names only, don't run any tests.
+ """
+ name = "collect-only"
+ enableOpt = 'collect_only'
+
+ def options(self, parser, env):
+ """Register commandline options.
+ """
+ parser.add_option('--collect-only',
+ action='store_true',
+ dest=self.enableOpt,
+ default=env.get('NOSE_COLLECT_ONLY'),
+ help="Enable collect-only: %s [COLLECT_ONLY]" %
+ (self.help()))
+
+ def prepareTestLoader(self, loader):
+ """Install collect-only suite class in TestLoader.
+ """
+ # Disable context awareness
+ log.debug("Preparing test loader")
+ loader.suiteClass = TestSuiteFactory(self.conf)
+
+ def prepareTestCase(self, test):
+ """Replace actual test with dummy that always passes.
+ """
+ # Return something that always passes
+ log.debug("Preparing test case %s", test)
+ if not isinstance(test, Test):
+ return
+ def run(result):
+ # We need to make these plugin calls because there won't be
+ # a result proxy, due to using a stripped-down test suite
+ self.conf.plugins.startTest(test)
+ result.startTest(test)
+ self.conf.plugins.addSuccess(test)
+ result.addSuccess(test)
+ self.conf.plugins.stopTest(test)
+ result.stopTest(test)
+ return run
+
+
+class TestSuiteFactory:
+ """
+ Factory for producing configured test suites.
+ """
+ def __init__(self, conf):
+ self.conf = conf
+
+ def __call__(self, tests=()):
+ return TestSuite(tests, conf=self.conf)
+
+
+class TestSuite(unittest.TestSuite):
+ """
+ Basic test suite that bypasses most proxy and plugin calls, but does
+ wrap tests in a nose.case.Test so prepareTestCase will be called.
+ """
+ def __init__(self, tests=(), conf=None):
+ self.conf = conf
+ # Exec lazy suites: makes discovery depth-first
+ if callable(tests):
+ tests = tests()
+ log.debug("TestSuite(%r)", tests)
+ unittest.TestSuite.__init__(self, tests)
+
+ def addTest(self, test):
+ log.debug("Add test %s", test)
+ if isinstance(test, unittest.TestSuite):
+ self._tests.append(test)
+ else:
+ self._tests.append(Test(test, config=self.conf))
+
diff --git a/nose/plugins/cover.py b/nose/plugins/cover.py
index ed0ccbd..8d66463 100644
--- a/nose/plugins/cover.py
+++ b/nose/plugins/cover.py
@@ -8,6 +8,24 @@ restrict the coverage report to modules from a particular package or packages,
use the --cover-packages switch or the NOSE_COVER_PACKAGES environment
variable.
+:Options:
+ ``--with-coverage``:
+ Activate code coverage report
+ ``--cover-package=PACKAGE``:
+ Restrict coverage output to selected packages
+ ``--cover-erase``:
+ Erase previously collected coverage statistics before run
+ ``--cover-tests``:
+ Include test modules in coverage report
+ ``--cover-inclusive``:
+ Include all python files under working directory in coverage report.
+ Useful for discovering holes in test coverage if not all
+ files are imported by the test suite
+ ``--cover-html``:
+ Produce HTML coverage information
+ ``--cover-html-dir=DIR``:
+ Produce HTML coverage informaion in dir
+
.. _coverage: http://www.nedbatchelder.com/code/modules/coverage.html
"""
import logging
@@ -25,10 +43,11 @@ COVERAGE_TEMPLATE = '''<html>
<body>
%(header)s
<style>
-pre {float: left; margin: 0px 1em }
+.coverage pre {float: left; margin: 0px 1em; border: none;
+ padding: 0px; line-height: 95%% }
.num pre { margin: 0px }
-.nocov {background-color: #faa}
-.cov {background-color: #cfc}
+.nocov, .nocov pre {background-color: #faa}
+.cov, .cov pre {background-color: #cfc}
div.coverage div { clear: both; height: 1em}
</style>
<div class="stats">
@@ -64,7 +83,10 @@ class Coverage(Plugin):
score = 200
status = {}
- def options(self, parser, env=os.environ):
+ def options(self, parser, env):
+ """
+ Add options to command line.
+ """
Plugin.options(self, parser, env)
parser.add_option("--cover-package", action="append",
default=env.get('NOSE_COVER_PACKAGE'),
@@ -99,6 +121,9 @@ class Coverage(Plugin):
help='Produce HTML coverage informaion in dir')
def configure(self, options, config):
+ """
+ Configure plugin.
+ """
try:
self.status.pop('active')
except KeyError:
@@ -131,6 +156,9 @@ class Coverage(Plugin):
self.status['active'] = True
def begin(self):
+ """
+ Begin recording coverage information.
+ """
log.debug("Coverage begin")
import coverage
self.skipModules = sys.modules.keys()[:]
@@ -141,6 +169,9 @@ class Coverage(Plugin):
coverage.start()
def report(self, stream):
+ """
+ Output code coverage report.
+ """
log.debug("Coverage report")
import coverage
coverage.stop()
@@ -221,7 +252,7 @@ class Coverage(Plugin):
stats['skipped'] += 1
stats['percent'] = self.computePercent(stats['covered'],
stats['missed'])
- html = COVERAGE_TEMPLATE % {'title': '<title>%s</title>' % (name, ),
+ html = COVERAGE_TEMPLATE % {'title': '<title>%s</title>' % name,
'header': name,
'body': '\n'.join(rows),
'stats': COVERAGE_STATS_TEMPLATE % stats,
diff --git a/nose/plugins/debug.py b/nose/plugins/debug.py
index b9f9ce4..373b0fd 100644
--- a/nose/plugins/debug.py
+++ b/nose/plugins/debug.py
@@ -4,7 +4,6 @@ test runner to drop into pdb if it encounters an error or failure,
respectively.
"""
-import os
import pdb
from nose.plugins.base import Plugin
@@ -17,7 +16,9 @@ class Pdb(Plugin):
enabled_for_failures = False
score = 5 # run last, among builtins
- def options(self, parser, env=os.environ):
+ def options(self, parser, env):
+ """Register commandline options.
+ """
parser.add_option(
"--pdb", action="store_true", dest="debugErrors",
default=env.get('NOSE_PDB', False),
@@ -29,23 +30,29 @@ class Pdb(Plugin):
help="Drop into debugger on failures")
def configure(self, options, conf):
+ """Configure which kinds of exceptions trigger plugin.
+ """
self.conf = conf
self.enabled = options.debugErrors or options.debugFailures
self.enabled_for_errors = options.debugErrors
self.enabled_for_failures = options.debugFailures
def addError(self, test, err):
+ """Enter pdb if configured to debug errors.
+ """
if not self.enabled_for_errors:
return
self.debug(err)
def addFailure(self, test, err):
+ """Enter pdb if configured to debug failures.
+ """
if not self.enabled_for_failures:
return
self.debug(err)
def debug(self, err):
- import sys
+ import sys # FIXME why is this import here?
ec, ev, tb = err
stdout = sys.stdout
sys.stdout = sys.__stdout__
diff --git a/nose/plugins/deprecated.py b/nose/plugins/deprecated.py
index 3be76a1..e54a151 100644
--- a/nose/plugins/deprecated.py
+++ b/nose/plugins/deprecated.py
@@ -1,12 +1,11 @@
"""
-This plugin installs a DEPRECATED error class for the DeprecatedTest
-exception. It is enabled by default. When DeprecatedTest is raised, the
-exception will be logged in the deprecated attribute of the result,
-'D' or 'DEPRECATED' (verbose) will be output, and the exception will
-not be counted as an error or failure.
+This plugin installs a DEPRECATED error class for the :class:`DeprecatedTest`
+exception. It is enabled by default. When :class:`DeprecatedTest` is raised,
+the exception will be logged in the deprecated attribute of the result, ``D``
+or ``DEPRECATED`` (verbose) will be output, and the exception will not be
+counted as an error or failure.
"""
-import os
from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin
@@ -20,8 +19,8 @@ class Deprecated(ErrorClassPlugin):
"""
Plugin that installs a DEPRECATED error class for the DeprecatedTest
exception. Enabled by default. When DeprecatedTest is raised, the
- exception will be logged in the deprecated attribute of the result, 'D' or
- 'DEPRECATED' (verbose) will be output, and the exception will not be
+ exception will be logged in the deprecated attribute of the result, ``D``
+ or ``DEPRECATED`` (verbose) will be output, and the exception will not be
counted as an error or failure.
"""
enabled = True
@@ -29,7 +28,9 @@ class Deprecated(ErrorClassPlugin):
label='DEPRECATED',
isfailure=False)
- def options(self, parser, env=os.environ):
+ def options(self, parser, env):
+ """Register commandline options.
+ """
env_opt = 'NOSE_WITHOUT_DEPRECATED'
parser.add_option('--no-deprecated', action='store_true',
dest='noDeprecated', default=env.get(env_opt, False),
@@ -37,6 +38,8 @@ class Deprecated(ErrorClassPlugin):
"exceptions.")
def configure(self, options, conf):
+ """Configure plugin.
+ """
if not self.can_configure:
return
self.conf = conf
diff --git a/nose/plugins/doctests.py b/nose/plugins/doctests.py
index fde92e8..c0ab6f7 100644
--- a/nose/plugins/doctests.py
+++ b/nose/plugins/doctests.py
@@ -137,7 +137,9 @@ class Doctest(Plugin):
extension = None
suiteClass = DoctestSuite
- def options(self, parser, env=os.environ):
+ def options(self, parser, env):
+ """Register commmandline options.
+ """
Plugin.options(self, parser, env)
parser.add_option('--doctest-tests', action='store_true',
dest='doctest_tests',
@@ -171,6 +173,8 @@ class Doctest(Plugin):
parser.set_defaults(doctestExtension=tolist(env_setting))
def configure(self, options, config):
+ """Configure plugin.
+ """
Plugin.configure(self, options, config)
self.doctest_result_var = options.doctest_result_var
self.doctest_tests = options.doctest_tests
@@ -179,9 +183,16 @@ class Doctest(Plugin):
self.finder = doctest.DocTestFinder()
def prepareTestLoader(self, loader):
+ """Capture loader's suiteClass.
+
+ This is used to create test suites from doctest files.
+
+ """
self.suiteClass = loader.suiteClass
def loadTestsFromModule(self, module):
+ """Load doctests from the module.
+ """
log.debug("loading from %s", module)
if not self.matches(module.__name__):
log.debug("Doctest doesn't want module %s", module)
@@ -211,6 +222,12 @@ class Doctest(Plugin):
yield self.suiteClass(cases, context=module, can_split=False)
def loadTestsFromFile(self, filename):
+ """Load doctests from the file.
+
+ Tests are loaded only if filename's extension matches
+ configured doctest extension.
+
+ """
if self.extension and anyp(filename.endswith, self.extension):
name = os.path.basename(filename)
dh = open(filename)
@@ -267,8 +284,6 @@ class Doctest(Plugin):
result_var=self.doctest_result_var)
def matches(self, name):
- """Doctest wants only non-test modules in general.
- """
# FIXME this seems wrong -- nothing is ever going to
# fail this test, since we're given a module NAME not FILE
if name == '__init__.py':
@@ -285,6 +300,9 @@ class Doctest(Plugin):
for exc in self.conf.exclude])))
def wantFile(self, file):
+ """Override to select all modules and any file ending with
+ configured doctest extension.
+ """
# always want .py files
if file.endswith('.py'):
return True
@@ -403,14 +421,3 @@ class DocFileCase(doctest.DocFileCase):
if self._result_var is not None:
sys.displayhook = self._old_displayhook
delattr(__builtin__, self._result_var)
-
-
-def run(*arg, **kw):
- """DEPRECATED: moved to nose.plugins.plugintest.
- """
- import warnings
- warnings.warn("run() has been moved to nose.plugins.plugintest. Please "
- "update your imports.", category=DeprecationWarning,
- stacklevel=2)
- from nose.plugins.plugintest import run
- run(*arg, **kw)
diff --git a/nose/plugins/errorclass.py b/nose/plugins/errorclass.py
index b06e014..a55cb93 100644
--- a/nose/plugins/errorclass.py
+++ b/nose/plugins/errorclass.py
@@ -122,6 +122,10 @@ class ErrorClass(object):
class ErrorClassPlugin(Plugin):
+ """
+ Base class for ErrorClass plugins. Subclass this class and declare the
+ exceptions that you wish to handle as attributes of the subclass.
+ """
__metaclass__ = MetaErrorClass
score = 1000
errorClasses = ()
diff --git a/nose/plugins/failuredetail.py b/nose/plugins/failuredetail.py
index 0a3d3fd..6251e01 100644
--- a/nose/plugins/failuredetail.py
+++ b/nose/plugins/failuredetail.py
@@ -6,7 +6,6 @@ exception was raised. Simple variable substitution is also performed
in the context output to provide more debugging information.
"""
-import os
from nose.plugins import Plugin
from nose.inspector import inspect_traceback
@@ -18,7 +17,9 @@ class FailureDetail(Plugin):
"""
score = 600 # before capture
- def options(self, parser, env=os.environ):
+ def options(self, parser, env):
+ """Register commmandline options.
+ """
parser.add_option(
"-d", "--detailed-errors", "--failure-detail",
action="store_true",
@@ -28,6 +29,8 @@ class FailureDetail(Plugin):
" asserts [NOSE_DETAILED_ERRORS]")
def configure(self, options, conf):
+ """Configure plugin.
+ """
if not self.can_configure:
return
self.enabled = options.detailedErrors
diff --git a/nose/plugins/isolate.py b/nose/plugins/isolate.py
index e965bed..bbad530 100644
--- a/nose/plugins/isolate.py
+++ b/nose/plugins/isolate.py
@@ -53,6 +53,8 @@ class IsolationPlugin(Plugin):
name = 'isolation'
def configure(self, options, conf):
+ """Configure plugin.
+ """
Plugin.configure(self, options, conf)
self._mod_stack = []
diff --git a/nose/plugins/logcapture.py b/nose/plugins/logcapture.py
index 9035583..e7d7016 100644
--- a/nose/plugins/logcapture.py
+++ b/nose/plugins/logcapture.py
@@ -18,12 +18,11 @@ will ensure that only statements logged via sqlalchemy.engine or myapp
or myapp.foo.bar logger will be logged.
"""
-import os
import logging
from logging.handlers import BufferingHandler
from nose.plugins.base import Plugin
-from nose.util import ln
+from nose.util import ln, safe_str
try:
from cStringIO import StringIO
@@ -34,9 +33,9 @@ log = logging.getLogger(__name__)
class MyMemoryHandler(BufferingHandler):
- def __init__(self, capacity, logformat, filters):
+ def __init__(self, capacity, logformat, logdatefmt, filters):
BufferingHandler.__init__(self, capacity)
- fmt = logging.Formatter(logformat)
+ fmt = logging.Formatter(logformat, logdatefmt)
self.setFormatter(fmt)
self.filters = filters
def flush(self):
@@ -70,24 +69,33 @@ class LogCapture(Plugin):
name = 'logcapture'
score = 500
logformat = '%(name)s: %(levelname)s: %(message)s'
+ logdatefmt = None
clear = False
filters = []
- def options(self, parser, env=os.environ):
+ def options(self, parser, env):
+ """Register commandline options.
+ """
parser.add_option(
- "", "--nologcapture", action="store_false",
+ "--nologcapture", action="store_false",
default=not env.get(self.env_opt), dest="logcapture",
help="Disable logging capture plugin. "
"Logging configurtion will be left intact."
" [NOSE_NOLOGCAPTURE]")
parser.add_option(
- "", "--logging-format", action="store", dest="logcapture_format",
+ "--logging-format", action="store", dest="logcapture_format",
default=env.get('NOSE_LOGFORMAT') or self.logformat,
help="Specify custom format to print statements. "
"Uses the same format as used by standard logging handlers."
" [NOSE_LOGFORMAT]")
parser.add_option(
- "", "--logging-filter", action="store", dest="logcapture_filters",
+ "--logging-datefmt", action="store", dest="logcapture_datefmt",
+ default=env.get('NOSE_LOGDATEFMT') or self.logdatefmt,
+ help="Specify custom date/time format to print statements. "
+ "Uses the same format as used by standard logging handlers."
+ " [NOSE_LOGDATEFMT]")
+ parser.add_option(
+ "--logging-filter", action="store", dest="logcapture_filters",
default=env.get('NOSE_LOGFILTER'),
help="Specify which statements to filter in/out. "
"By default everything is captured. If the output is too"
@@ -97,17 +105,20 @@ class LogCapture(Plugin):
"Specify multiple loggers with comma: filter=foo,bar,baz."
" [NOSE_LOGFILTER]\n")
parser.add_option(
- "", "--logging-clear-handlers", action="store_true",
+ "--logging-clear-handlers", action="store_true",
default=False, dest="logcapture_clear",
help="Clear all other logging handlers")
def configure(self, options, conf):
+ """Configure plugin.
+ """
self.conf = conf
# Disable if explicitly disabled, or if logging is
# configured via logging config file
if not options.logcapture or conf.loggingConfig:
self.enabled = False
self.logformat = options.logcapture_format
+ self.logdatefmt = options.logcapture_datefmt
self.clear = options.logcapture_clear
if options.logcapture_filters:
self.filters = options.logcapture_filters.split(',')
@@ -116,8 +127,10 @@ class LogCapture(Plugin):
# setup our handler with root logger
root_logger = logging.getLogger()
if self.clear:
- for handler in root_logger.handlers:
- root_logger.removeHandler(handler)
+ for logger in logging.Logger.manager.loggerDict.values():
+ if hasattr(logger, "handlers"):
+ for handler in logger.handlers:
+ logger.removeHandler(handler)
# make sure there isn't one already
# you can't simply use "if self.handler not in root_logger.handlers"
# since at least in unit tests this doesn't work --
@@ -132,25 +145,36 @@ class LogCapture(Plugin):
root_logger.setLevel(logging.NOTSET)
def begin(self):
+ """Set up logging handler before test run begins.
+ """
self.start()
def start(self):
- self.handler = MyMemoryHandler(1000, self.logformat, self.filters)
+ self.handler = MyMemoryHandler(1000, self.logformat, self.logdatefmt,
+ self.filters)
self.setupLoghandler()
def end(self):
pass
def beforeTest(self, test):
+ """Clear buffers and handlers before test.
+ """
self.setupLoghandler()
def afterTest(self, test):
+ """Clear buffers after test.
+ """
self.handler.truncate()
def formatFailure(self, test, err):
+ """Add captured log messages to failure output.
+ """
return self.formatError(test, err)
def formatError(self, test, err):
+ """Add captured log messages to error output.
+ """
# logic flow copied from Capture.formatError
test.capturedLogging = records = self.formatLogRecords()
if not records:
@@ -160,9 +184,9 @@ class LogCapture(Plugin):
def formatLogRecords(self):
format = self.handler.format
- return [format(r) for r in self.handler.buffer]
+ return [safe_str(format(r)) for r in self.handler.buffer]
def addCaptureToErr(self, ev, records):
- return '\n'.join([str(ev), ln('>> begin captured logging <<')] + \
+ return '\n'.join([safe_str(ev), ln('>> begin captured logging <<')] + \
records + \
[ln('>> end captured logging <<')])
diff --git a/nose/plugins/manager.py b/nose/plugins/manager.py
index 31a9571..9471576 100644
--- a/nose/plugins/manager.py
+++ b/nose/plugins/manager.py
@@ -7,35 +7,35 @@ loaded plugins, and proxy calls to those plugins.
The plugin managers provided with nose are:
-``PluginManager``
+:class:`PluginManager`
This manager doesn't implement loadPlugins, so it can only work
with a static list of plugins.
-``BuiltinPluginManager``
+:class:`BuiltinPluginManager`
This manager loads plugins referenced in ``nose.plugins.builtin``.
-``EntryPointPluginManager``
+:class:`EntryPointPluginManager`
This manager uses setuptools entrypoints to load plugins.
-``DefaultPluginMananger``
+:class:`DefaultPluginMananger`
This is the manager class that will be used by default. If
setuptools is installed, it is a subclass of
- ``EntryPointPluginManager`` and ``BuiltinPluginManager``; otherwise, an
- alias to ``BuiltinPluginManager``.
+ :class:`EntryPointPluginManager` and :class:`BuiltinPluginManager`;
+ otherwise, an alias to :class:`BuiltinPluginManager`.
-``RestrictedPluginManager``
+:class:`RestrictedPluginManager`
This manager is for use in test runs where some plugin calls are
- not available, such as runs started with `python setup.py test`,
- where the test runner is the default unittest ``TextTestRunner``. It
- is a subclass of ``DefaultPluginManager``.
+ not available, such as runs started with ``python setup.py test``,
+ where the test runner is the default unittest :class:`TextTestRunner`. It
+ is a subclass of :class:`DefaultPluginManager`.
Writing a plugin manager
========================
If you want to load plugins via some other means, you can write a
plugin manager and pass an instance of your plugin manager class when
-instantiating the `nose.config.Config`_ instance that you pass to
-``TestProgram`` (or ``main`` or ``run``).
+instantiating the :class:`nose.config.Config` instance that you pass to
+:class:`TestProgram` (or :func:`main` or :func:`run`).
To implement your plugin loading scheme, implement ``loadPlugins()``,
and in that method, call ``addPlugin()`` with an instance each plugin
diff --git a/nose/plugins/multiprocess.py b/nose/plugins/multiprocess.py
index da688cb..adc2778 100644
--- a/nose/plugins/multiprocess.py
+++ b/nose/plugins/multiprocess.py
@@ -1,6 +1,6 @@
"""
-Mutltiprocess: parallel testing
--------------------------------
+Overview
+========
The multiprocess plugin enables you to distribute your test run among a set of
worker processes that run tests in parallel. This can speed up CPU-bound test
@@ -9,6 +9,11 @@ processors or cores available), but is mainly useful for IO-bound tests which
can benefit from massive parallelization, since most of the tests spend most
of their time waiting for data to arrive from someplace else.
+.. note ::
+
+ See :doc:`../doc_tests/test_multiprocess/multiprocess` for additional
+ documentation and examples.
+
How tests are distributed
=========================
@@ -88,46 +93,26 @@ from nose.suite import ContextSuite
from nose.util import test_address
from Queue import Empty
from warnings import warn
+try:
+ from cStringIO import StringIO
+except ImportError:
+ import StringIO
log = logging.getLogger(__name__)
Process = Queue = Pool = Event = None
-is_set = lambda e: None
-def do_processing_imports():
- global Process, Queue, Pool, Event, is_set
+def _import_mp():
+ global Process, Queue, Pool, Event
try:
- # 2.6
from multiprocessing import Process as Process_, \
- Queue as Queue_, Pool as Pool_, Event as Event_
- class Process26(Process_):
- def setDaemon(self, daemon):
- self.daemon = daemon
- def isAlive(self):
- return self.is_alive()
- def is_set_26(event):
- return event.is_set()
- Process, Queue, Pool, Event = Process26, Queue_, Pool_, Event_
- is_set = is_set_26
- except ImportError:
- # Earlier
- try:
- from processing import Process as Process_, \
Queue as Queue_, Pool as Pool_, Event as Event_
- def is_set_(event):
- return event.isSet()
- Process, Queue, Pool, Event = Process_, Queue_, Pool_, Event_
- is_set = is_set_
- except ImportError:
- warn("processing module is not available, multiprocess plugin "
- "cannot be used", RuntimeWarning)
-
-try:
- from cStringIO import StringIO
-except ImportError:
- import StringIO
-
+ Process, Queue, Pool, Event = Process_, Queue_, Pool_, Event_
+ except ImportError:
+ warn("multiprocessing module is not available, multiprocess plugin "
+ "cannot be used", RuntimeWarning)
+
class TestLet:
def __init__(self, case):
try:
@@ -154,7 +139,10 @@ class MultiProcess(Plugin):
score = 1000
status = {}
- def options(self, parser, env=os.environ):
+ def options(self, parser, env):
+ """
+ Register command-line options.
+ """
parser.add_option("--processes", action="store",
default=env.get('NOSE_PROCESSES', 0),
dest="multiprocess_workers",
@@ -169,6 +157,9 @@ class MultiProcess(Plugin):
"test runner process. [NOSE_PROCESS_TIMEOUT]")
def configure(self, options, config):
+ """
+ Configure plugin.
+ """
try:
self.status.pop('active')
except KeyError:
@@ -182,7 +173,7 @@ class MultiProcess(Plugin):
except (TypeError, ValueError):
workers = 0
if workers:
- do_processing_imports()
+ _import_mp()
if Process is None:
self.enabled = False
return
@@ -192,9 +183,14 @@ class MultiProcess(Plugin):
self.status['active'] = True
def prepareTestLoader(self, loader):
+ """Remember loader class so MultiProcessTestRunner can instantiate
+ the right loader.
+ """
self.loaderClass = loader.__class__
def prepareTestRunner(self, runner):
+ """Replace test runner with MultiProcessTestRunner.
+ """
# replace with our runner class
return MultiProcessTestRunner(stream=runner.stream,
verbosity=self.config.verbosity,
@@ -238,7 +234,7 @@ class MultiProcessTestRunner(TextTestRunner):
# dispatch and collect results
# put indexes only on queue because tests aren't picklable
- for case in self.next_batch(test):
+ for case in self.nextBatch(test):
log.debug("Next batch %s (%s)", case, type(case))
if (isinstance(case, nose.case.Test) and
isinstance(case.test, failure.Failure)):
@@ -309,7 +305,7 @@ class MultiProcessTestRunner(TextTestRunner):
log.debug("Timed out with %s tasks pending", len(tasks))
any_alive = False
for w in workers:
- if w.isAlive():
+ if w.is_alive():
any_alive = True
break
if not any_alive:
@@ -335,22 +331,14 @@ class MultiProcessTestRunner(TextTestRunner):
# Tell all workers to stop
for w in workers:
- if w.isAlive():
+ if w.is_alive():
testQueue.put('STOP', block=False)
return result
def address(self, case):
if hasattr(case, 'address'):
- try:
- file, mod, call = case.address()
- except:
- import sys
- import pdb
- ec, ev, tb = sys.exc_info()
- sys.stdout = sys.__stdout__
- pdb.post_mortem(tb)
- raise
+ file, mod, call = case.address()
elif hasattr(case, 'context'):
file, mod, call = test_address(case.context)
else:
@@ -367,8 +355,7 @@ class MultiProcessTestRunner(TextTestRunner):
parts.append(call)
return ':'.join(map(str, parts))
- # FIXME camel for consistency
- def next_batch(self, test):
+ def nextBatch(self, test):
# allows tests or suites to mark themselves as not safe
# for multiprocess execution
if hasattr(test, 'context'):
@@ -376,7 +363,7 @@ class MultiProcessTestRunner(TextTestRunner):
return
if ((isinstance(test, ContextSuite)
- and test.hasFixtures(self.check_can_split))
+ and test.hasFixtures(self.checkCanSplit))
or not getattr(test, 'can_split', True)
or not isinstance(test, unittest.TestSuite)):
# regular test case, or a suite with context fixtures
@@ -396,11 +383,10 @@ class MultiProcessTestRunner(TextTestRunner):
# fixtures at any deeper level, so we need to examine it all
# the way down to the case level
for case in test:
- for batch in self.next_batch(case):
+ for batch in self.nextBatch(case):
yield batch
- # FIXME camel for consistency
- def check_can_split(self, context, fixt):
+ def checkCanSplit(self, context, fixt):
"""
Callback that we use to check whether the fixtures found in a
context or ancestor are ones we care about.
@@ -443,8 +429,7 @@ class MultiProcessTestRunner(TextTestRunner):
mystorage.extend(storage)
log.debug("Ran %s tests (%s)", testsRun, result.testsRun)
-# FIXME it looks like the error classes that should be on the
-# result disappear after the first test batch
+
def runner(ix, testQueue, resultQueue, shouldStop,
loaderClass, resultClass, config):
log.debug("Worker %s executing", ix)
@@ -481,8 +466,7 @@ def runner(ix, testQueue, resultQueue, shouldStop,
try:
try:
for test_addr in iter(get, 'STOP'):
- # 2.6 names changed
- if is_set(shouldStop):
+ if shouldStop.is_set():
break
result = makeResult()
test = loader.loadTestsFromNames([test_addr])
@@ -502,7 +486,6 @@ def runner(ix, testQueue, resultQueue, shouldStop,
finally:
testQueue.close()
resultQueue.close()
- testQueue.close()
log.debug("Worker %s ending", ix)
diff --git a/nose/plugins/plugintest.py b/nose/plugins/plugintest.py
index ec1f02e..f07fc7b 100644
--- a/nose/plugins/plugintest.py
+++ b/nose/plugins/plugintest.py
@@ -1,8 +1,97 @@
"""
-Plugin tester
--------------
+Testing Plugins
+===============
-Utilities for testing plugins.
+The plugin interface is well-tested enough to safely unit test your
+use of its hooks with some level of confidence. However, there is also
+a mixin for unittest.TestCase called PluginTester that's designed to
+test plugins in their native runtime environment.
+
+Here's a simple example with a do-nothing plugin and a composed suite.
+
+ >>> import unittest
+ >>> from nose.plugins import Plugin, PluginTester
+ >>> class FooPlugin(Plugin):
+ ... pass
+ >>> class TestPluginFoo(PluginTester, unittest.TestCase):
+ ... activate = '--with-foo'
+ ... plugins = [FooPlugin()]
+ ... def test_foo(self):
+ ... for line in self.output:
+ ... # i.e. check for patterns
+ ... pass
+ ...
+ ... # or check for a line containing ...
+ ... assert "ValueError" in self.output
+ ... def makeSuite(self):
+ ... class TC(unittest.TestCase):
+ ... def runTest(self):
+ ... raise ValueError("I hate foo")
+ ... return unittest.TestSuite([TC()])
+ ...
+ >>> res = unittest.TestResult()
+ >>> case = TestPluginFoo('test_foo')
+ >>> case(res)
+ >>> res.errors
+ []
+ >>> res.failures
+ []
+ >>> res.wasSuccessful()
+ True
+ >>> res.testsRun
+ 1
+
+And here is a more complex example of testing a plugin that has extra
+arguments and reads environment variables.
+
+ >>> import unittest, os
+ >>> from nose.plugins import Plugin, PluginTester
+ >>> class FancyOutputter(Plugin):
+ ... name = "fancy"
+ ... def configure(self, options, conf):
+ ... Plugin.configure(self, options, conf)
+ ... if not self.enabled:
+ ... return
+ ... self.fanciness = 1
+ ... if options.more_fancy:
+ ... self.fanciness = 2
+ ... if 'EVEN_FANCIER' in self.env:
+ ... self.fanciness = 3
+ ...
+ ... def options(self, parser, env=os.environ):
+ ... self.env = env
+ ... parser.add_option('--more-fancy', action='store_true')
+ ... Plugin.options(self, parser, env=env)
+ ...
+ ... def report(self, stream):
+ ... stream.write("FANCY " * self.fanciness)
+ ...
+ >>> class TestFancyOutputter(PluginTester, unittest.TestCase):
+ ... activate = '--with-fancy' # enables the plugin
+ ... plugins = [FancyOutputter()]
+ ... args = ['--more-fancy']
+ ... env = {'EVEN_FANCIER': '1'}
+ ...
+ ... def test_fancy_output(self):
+ ... assert "FANCY FANCY FANCY" in self.output, (
+ ... "got: %s" % self.output)
+ ... def makeSuite(self):
+ ... class TC(unittest.TestCase):
+ ... def runTest(self):
+ ... raise ValueError("I hate fancy stuff")
+ ... return unittest.TestSuite([TC()])
+ ...
+ >>> res = unittest.TestResult()
+ >>> case = TestFancyOutputter('test_fancy_output')
+ >>> case(res)
+ >>> res.errors
+ []
+ >>> res.failures
+ []
+ >>> res.wasSuccessful()
+ True
+ >>> res.testsRun
+ 1
"""
@@ -26,8 +115,6 @@ class PluginTester(object):
executed with your plugin so that during an actual test you can inspect the
artifacts of how your plugin interacted with the stub test suite.
- Class Variables
- ---------------
- activate
- the argument to send nosetests to activate the plugin
diff --git a/nose/plugins/prof.py b/nose/plugins/prof.py
index b287a50..3efce9e 100644
--- a/nose/plugins/prof.py
+++ b/nose/plugins/prof.py
@@ -1,7 +1,7 @@
-"""Use the profile plugin with --with-profile or NOSE_WITH_PROFILE to
+"""Use the profile plugin with ``--with-profile`` or NOSE_WITH_PROFILE to
enable profiling using the hotshot profiler. Profiler output can be
-controlled with the --profile-sort and --profile-restrict, and the
-profiler output file may be changed with --profile-stats-file.
+controlled with the ``--profile-sort`` and ``--profile-restrict``, and the
+profiler output file may be changed with ``--profile-stats-file``.
See the hotshot documentation in the standard library documentation for
more details on the various output options.
@@ -27,7 +27,9 @@ class Profile(Plugin):
"""
pfile = None
clean_stats_file = False
- def options(self, parser, env=os.environ):
+ def options(self, parser, env):
+ """Register commandline options.
+ """
if not self.available():
return
Plugin.options(self, parser, env)
@@ -50,12 +52,16 @@ class Profile(Plugin):
available = classmethod(available)
def begin(self):
+ """Create profile stats file and load profiler.
+ """
if not self.available():
return
self._create_pfile()
self.prof = hotshot.Profile(self.pfile)
def configure(self, options, conf):
+ """Configure plugin.
+ """
if not self.available():
self.enabled = False
return
@@ -72,6 +78,8 @@ class Profile(Plugin):
self.restrict = tolist(options.profile_restrict)
def prepareTest(self, test):
+ """Wrap entire test run in :func:`prof.runcall`.
+ """
if not self.available():
return
log.debug('preparing test %s' % test)
@@ -81,6 +89,8 @@ class Profile(Plugin):
return run_and_profile
def report(self, stream):
+ """Output profiler report.
+ """
log.debug('printing profiler report')
self.prof.close()
prof_stats = stats.load(self.pfile)
@@ -109,6 +119,8 @@ class Profile(Plugin):
sys.stdout = tmp
def finalize(self, result):
+ """Clean up stats file, if configured to do so.
+ """
if not self.available():
return
try:
diff --git a/nose/plugins/skip.py b/nose/plugins/skip.py
index fd15d6f..18750e1 100644
--- a/nose/plugins/skip.py
+++ b/nose/plugins/skip.py
@@ -6,7 +6,6 @@ the exception will not be counted as an error or failure. This plugin
is enabled by default but may be disabled with the --no-skip option.
"""
-import os
from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin
@@ -29,7 +28,10 @@ class Skip(ErrorClassPlugin):
label='SKIP',
isfailure=False)
- def options(self, parser, env=os.environ):
+ def options(self, parser, env):
+ """
+ Add my options to command line.
+ """
env_opt = 'NOSE_WITHOUT_SKIP'
parser.add_option('--no-skip', action='store_true',
dest='noSkip', default=env.get(env_opt, False),
@@ -37,6 +39,9 @@ class Skip(ErrorClassPlugin):
"exceptions.")
def configure(self, options, conf):
+ """
+ Configure plugin. Skip plugin is enabled by default.
+ """
if not self.can_configure:
return
self.conf = conf
diff --git a/nose/plugins/testid.py b/nose/plugins/testid.py
index 54033b5..d90d556 100644
--- a/nose/plugins/testid.py
+++ b/nose/plugins/testid.py
@@ -31,13 +31,75 @@ Then you can rerun individual tests by supplying just the id numbers::
Since most shells consider '#' a special character, you can leave it out when
specifying a test id.
+
+Note that when run without the -v switch, no special output is displayed, but
+the ids file is still written.
+
+Looping over failed tests
+-------------------------
+
+This plugin also adds a mode where it will direct the test run to record
+failed tests, and on subsequent runs, include only the tests that failed the
+last time. Activate this mode with the --failed switch::
+
+ % nosetests -v --failed
+ #1 test.test_a ... ok
+ #2 test.test_b ... ERROR
+ #3 test.test_c ... FAILED
+ #4 test.test_d ... ok
+
+And on the 2nd run, only tests #2 and #3 will run::
+
+ % nosetests -v --failed
+ #2 test.test_b ... ERROR
+ #3 test.test_c ... FAILED
+
+Then as you correct errors and tests pass, they'll drop out of subsequent
+runs.
+
+First::
+
+ % nosetests -v --failed
+ #2 test.test_b ... ok
+ #3 test.test_c ... FAILED
+
+Second::
+
+ % nosetests -v --failed
+ #3 test.test_c ... FAILED
+
+Until finally when all tests pass, the full set will run again on the next
+invocation.
+
+First::
+
+ % nosetests -v --failed
+ #3 test.test_c ... ok
+
+Second::
+
+ % nosetests -v --failed
+ #1 test.test_a ... ok
+ #2 test.test_b ... ok
+ #3 test.test_c ... ok
+ #4 test.test_d ... ok
+
+.. note ::
+
+ If you expect to want to use --failed often, a good practice is to always
+ run with the --with-id option active, so that an ids file is always recorded
+ and you can then add --failed to the command line as soon as you have
+ failing tests. If --with-id is not active, your first invocation with
+ --failed will (perhaps surprisingly) run all tests, because there will be
+ no ids file recording the failed tests from the previous run, during which
+ this plugin was not active.
"""
__test__ = False
import logging
import os
from nose.plugins import Plugin
-from nose.util import src
+from nose.util import src, set
try:
from cPickle import dump, load
@@ -46,27 +108,42 @@ except ImportError:
log = logging.getLogger(__name__)
+
class TestId(Plugin):
"""
Activate to add a test id (like #1) to each test name output. After
you've run once to generate test ids, you can re-run individual
tests by activating the plugin and passing the ids (with or
- without the # prefix) instead of test names.
+ without the # prefix) instead of test names. Activate with --failed
+ to rerun failing tests only.
"""
name = 'id'
idfile = None
- shouldSave = True
+ collecting = True
+ loopOnFailed = False
def options(self, parser, env):
+ """Register commandline options.
+ """
Plugin.options(self, parser, env)
parser.add_option('--id-file', action='store', dest='testIdFile',
default='.noseids',
help="Store test ids found in test runs in this "
"file. Default is the file .noseids in the "
"working directory.")
+ parser.add_option('--failed', action='store_true',
+ dest='failed', default=False,
+ help="Run the tests that failed in the last "
+ "test run.")
def configure(self, options, conf):
+ """Configure plugin.
+ """
Plugin.configure(self, options, conf)
+ if options.failed:
+ self.enabled = True
+ self.loopOnFailed = True
+ log.debug("Looping on failed tests")
self.idfile = os.path.expanduser(options.testIdFile)
if not os.path.isabs(self.idfile):
self.idfile = os.path.join(conf.workingDir, self.idfile)
@@ -75,41 +152,103 @@ class TestId(Plugin):
# tests are {test address: id}
self.ids = {}
self.tests = {}
+ self.failed = []
+ self.source_names = []
# used to track ids seen when tests is filled from
# loaded ids file
self._seen = {}
+ self._write_hashes = options.verbosity >= 2
def finalize(self, result):
- if self.shouldSave:
- fh = open(self.idfile, 'w')
- # save as {id: test address}
- ids = dict(zip(self.tests.values(), self.tests.keys()))
- dump(ids, fh)
- fh.close()
- log.debug('Saved test ids: %s to %s', ids, self.idfile)
+ """Save new ids file, if needed.
+ """
+ if result.wasSuccessful():
+ self.failed = []
+ if self.collecting:
+ ids = dict(zip(self.tests.values(), self.tests.keys()))
+ else:
+ ids = self.ids
+ fh = open(self.idfile, 'w')
+ dump({'ids': ids,
+ 'failed': self.failed,
+ 'source_names': self.source_names}, fh)
+ fh.close()
+ log.debug('Saved test ids: %s, failed %s to %s',
+ ids, self.failed, self.idfile)
def loadTestsFromNames(self, names, module=None):
"""Translate ids in the list of requested names into their
test addresses, if they are found in my dict of tests.
- """
+ """
log.debug('ltfn %s %s', names, module)
try:
fh = open(self.idfile, 'r')
- self.ids = load(fh)
- log.debug('Loaded test ids %s from %s', self.ids, self.idfile)
+ data = load(fh)
+ if 'ids' in data:
+ self.ids = data['ids']
+ self.failed = data['failed']
+ self.source_names = data['source_names']
+ self.id = max(self.ids) + 1
+ # got some ids in names, so make sure that the ids line
+ # up in output with what I said they were last time
+ self.tests = dict(zip(self.ids.values(), self.ids.keys()))
+ else:
+ # old ids field
+ self.ids = data
+ self.failed = []
+ self.source_names = names
+ log.debug('Loaded test ids %s/failed %s sources %s from %s',
+ self.ids, self.failed, self.source_names, self.idfile)
fh.close()
except IOError:
log.debug('IO error reading %s', self.idfile)
- return
+ if self.loopOnFailed and self.failed:
+ self.collecting = False
+ names = self.failed
+ self.failed = []
# I don't load any tests myself, only translate names like '#2'
# into the associated test addresses
- result = (None, map(self.tr, names))
- if not self.shouldSave:
- # got some ids in names, so make sure that the ids line
- # up in output with what I said they were last time
- self.tests = dict(zip(self.ids.values(), self.ids.keys()))
- return result
+ translated = []
+ new_source = []
+ really_new = []
+ for name in names:
+ trans = self.tr(name)
+ if trans != name:
+ translated.append(trans)
+ else:
+ new_source.append(name)
+ # names that are not ids and that are not in the current
+ # list of source names go into the list for next time
+ if new_source:
+ new_set = set(new_source)
+ old_set = set(self.source_names)
+ log.debug("old: %s new: %s", old_set, new_set)
+ really_new = [s for s in new_source
+ if not s in old_set]
+ missing = old_set - new_set
+ if translated:
+ # add any new names, don't worry about missing in this case
+ self.source_names.extend(really_new)
+ else:
+ if missing:
+ # some sources from old set are not present in new set
+ # reset the ids file to avoid confusion
+ log.debug("resetting source names to %s", new_source)
+ self.source_names = new_source
+ self.tests = {}
+ self.ids = {}
+ self.id = 1
+ else:
+ # just some new
+ log.debug("appending new sources %s", really_new)
+ self.source_names.extend(really_new)
+ else:
+ # no new names to translate and add to id set
+ self.collecting = False
+ log.debug("translated: %s new sources %s names %s",
+ translated, really_new, names)
+ return (None, translated + really_new or names)
def makeName(self, addr):
log.debug("Make name %s", addr)
@@ -123,22 +262,39 @@ class TestId(Plugin):
return head
def setOutputStream(self, stream):
+ """Get handle on output stream so the plugin can print id #s
+ """
self.stream = stream
def startTest(self, test):
+ """Maybe output an id # before the test name.
+
+ Example output::
+
+ #1 test.test ... ok
+ #2 test.test_two ... ok
+
+ """
adr = test.address()
log.debug('start test %s (%s)', adr, adr in self.tests)
if adr in self.tests:
- if self.shouldSave or adr in self._seen:
- self.stream.write(' ')
+ if adr in self._seen:
+ self.write(' ')
else:
- self.stream.write('#%s ' % self.tests[adr])
+ self.write('#%s ' % self.tests[adr])
self._seen[adr] = 1
return
self.tests[adr] = self.id
- self.stream.write('#%s ' % self.id)
+ self.write('#%s ' % self.id)
self.id += 1
+ def afterTest(self, test):
+ # None means test never ran, False means failed/err
+ if test.passed is False:
+ key = str(self.tests[test.address()])
+ if key not in self.failed:
+ self.failed.append(key)
+
def tr(self, name):
log.debug("tr '%s'", name)
try:
@@ -146,8 +302,12 @@ class TestId(Plugin):
except ValueError:
return name
log.debug("Got key %s", key)
- self.shouldSave = False
+ # I'm running tests mapped from the ids file,
+ # not collecting new ones
if key in self.ids:
return self.makeName(self.ids[key])
return name
-
+
+ def write(self, output):
+ if self._write_hashes:
+ self.stream.write(output)
diff --git a/nose/plugins/xunit.py b/nose/plugins/xunit.py
new file mode 100644
index 0000000..558c2e4
--- /dev/null
+++ b/nose/plugins/xunit.py
@@ -0,0 +1,192 @@
+
+"""This plugin provides test results in the standard XUnit XML format.
+
+It was designed for the `Hudson`_ continuous build system but will
+probably work for anything else that understands an XUnit
+formatted XML representation of test results.
+
+Add this shell command to your builder ::
+
+ nosetests --with-xunit
+
+And by default a file named nosetests.xml will be written to the
+working directory.
+
+In a Hudson builder, tick the box named Publish JUnit test result report
+under the Post-build Actions and enter this value for Test report XMLs ::
+
+ **/nosetests.xml
+
+If you need to change the name or location of the file, you can set the
+``--xunit-file`` option.
+
+Here is an abbreviated version of what an XML test report might look like::
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <testsuite name="nosetests" tests="1" errors="1" failures="0" skip="0">
+ <testcase classname="path_to_test_suite.TestSomething"
+ name="path_to_test_suite.TestSomething.test_it" time="0">
+ <error type="exceptions.TypeError">
+ Traceback (most recent call last):
+ ...
+ TypeError: oops, wrong type
+ </error>
+ </testcase>
+ </testsuite>
+
+.. _Hudson: https://hudson.dev.java.net/
+
+"""
+
+import os
+import traceback
+import re
+import inspect
+from nose.plugins.base import Plugin
+from nose.exc import SkipTest
+from time import time
+import doctest
+
+def xmlsafe(s, encoding="utf-8"):
+ """Used internally to escape XML."""
+ if isinstance(s, unicode):
+ s = s.encode(encoding)
+ s = str(s)
+ for src, rep in [('&', '&amp;', ),
+ ('<', '&gt;', ),
+ ('>', '&lt;', ),
+ ('"', '&quot;', ),
+ ("'", '&quot;', ),
+ ]:
+ s = s.replace(src, rep)
+ return s
+
+def nice_classname(obj):
+ """Returns a nice name for class object or class instance.
+
+ >>> nice_classname(Exception()) # doctest: +ELLIPSIS
+ '...Exception'
+ >>> nice_classname(Exception)
+ 'exceptions.Exception'
+
+ """
+ if inspect.isclass(obj):
+ cls_name = obj.__name__
+ else:
+ cls_name = obj.__class__.__name__
+ mod = inspect.getmodule(obj)
+ if mod:
+ return "%s.%s" % (mod.__name__, cls_name)
+ else:
+ return cls_name
+
+class Xunit(Plugin):
+ """This plugin provides test results in the standard XUnit XML format."""
+ name = 'xunit'
+ score = 2000
+ encoding = 'UTF-8'
+
+ def _xmlsafe(self, s):
+ return xmlsafe(s, encoding=self.encoding)
+
+ def options(self, parser, env):
+ """Sets additional command line options."""
+ Plugin.options(self, parser, env)
+ parser.add_option(
+ '--xunit-file', action='store',
+ dest='xunit_file',
+ default=env.get('NOSE_XUNIT_FILE', 'nosetests.xml'),
+ help=("Path to xml file to store the xunit report in. "
+ "Default is nosetests.xml in the working directory "
+ "[NOSE_XUNIT_FILE]"))
+
+ def configure(self, options, config):
+ """Configures the xunit plugin."""
+ Plugin.configure(self, options, config)
+ self.config = config
+ if self.enabled:
+ self.stats = {'errors': 0,
+ 'failures': 0,
+ 'passes': 0,
+ 'skipped': 0
+ }
+ self.errorlist = []
+ self.error_report_file = open(options.xunit_file, 'w')
+
+ def report(self, stream):
+ """Writes an Xunit-formatted XML file
+
+ The file includes a report of test errors and failures.
+
+ """
+ self.stats['encoding'] = self.encoding
+ self.stats['total'] = (self.stats['errors'] + self.stats['failures']
+ + self.stats['passes'] + self.stats['skipped'])
+ self.error_report_file.write(
+ '<?xml version="1.0" encoding="%(encoding)s"?>'
+ '<testsuite name="nosetests" tests="%(total)d" '
+ 'errors="%(errors)d" failures="%(failures)d" '
+ 'skip="%(skipped)d">' % self.stats)
+ self.error_report_file.write(''.join(self.errorlist))
+ self.error_report_file.write('</testsuite>')
+ self.error_report_file.close()
+ if self.config.verbosity > 1:
+ stream.writeln("-" * 70)
+ stream.writeln("XML: %s" % self.error_report_file.name)
+
+ def startTest(self, test):
+ """Initializes a timer before starting a test."""
+ self._timer = time()
+
+ def addError(self, test, err, capt=None):
+ """Add error output to Xunit report.
+ """
+ taken = time() - self._timer
+ if issubclass(err[0], SkipTest):
+ self.stats['skipped'] +=1
+ return
+ tb = ''.join(traceback.format_exception(*err))
+ self.stats['errors'] += 1
+ id = test.id()
+ self.errorlist.append(
+ '<testcase classname="%(cls)s" name="%(name)s" time="%(taken)d">'
+ '<error type="%(errtype)s">%(tb)s</error></testcase>' %
+ {'cls': self._xmlsafe('.'.join(id.split('.')[:-1])),
+ 'name': self._xmlsafe(id),
+ 'errtype': self._xmlsafe(nice_classname(err[0])),
+ 'tb': self._xmlsafe(tb),
+ 'taken': taken,
+ })
+
+ def addFailure(self, test, err, capt=None, tb_info=None):
+ """Add failure output to Xunit report.
+ """
+ taken = time() - self._timer
+ tb = ''.join(traceback.format_exception(*err))
+ self.stats['failures'] += 1
+ id = test.id()
+ self.errorlist.append(
+ '<testcase classname="%(cls)s" name="%(name)s" time="%(taken)d">'
+ '<failure type="%(errtype)s">%(tb)s</failure></testcase>' %
+ {'cls': self._xmlsafe('.'.join(id.split('.')[:-1])),
+ 'name': self._xmlsafe(id),
+ 'errtype': self._xmlsafe(nice_classname(err[0])),
+ 'tb': self._xmlsafe(tb),
+ 'taken': taken,
+ })
+
+ def addSuccess(self, test, capt=None):
+ """Add success output to Xunit report.
+ """
+ taken = time() - self._timer
+ self.stats['passes'] += 1
+ id = test.id()
+ self.errorlist.append(
+ '<testcase classname="%(cls)s" name="%(name)s" '
+ 'time="%(taken)d" />' %
+ {'cls': self._xmlsafe('.'.join(id.split('.')[:-1])),
+ 'name': self._xmlsafe(id),
+ 'taken': taken,
+ })
+
+
diff --git a/nose/proxy.py b/nose/proxy.py
index 2e09944..c2c99cb 100644
--- a/nose/proxy.py
+++ b/nose/proxy.py
@@ -114,6 +114,7 @@ class ResultProxy(object):
plugin_handled = plugins.handleError(self.test, err)
if plugin_handled:
return
+ # test.passed is set in result, to account for error classes
formatted = plugins.formatError(self.test, err)
if formatted is not None:
err = formatted
@@ -128,6 +129,7 @@ class ResultProxy(object):
plugin_handled = plugins.handleFailure(self.test, err)
if plugin_handled:
return
+ self.test.passed = False
formatted = plugins.formatFailure(self.test, err)
if formatted is not None:
err = formatted
diff --git a/nose/result.py b/nose/result.py
index acd9bb6..a5db931 100644
--- a/nose/result.py
+++ b/nose/result.py
@@ -53,6 +53,8 @@ class TextTestResult(_TextTestResult):
exc_info = self._exc_info_to_string(err)
for cls, (storage, label, isfail) in self.errorClasses.items():
if isclass(ec) and issubclass(ec, cls):
+ if isfail:
+ test.passed = False
storage.append((test, exc_info))
# Might get patched into a streamless result
if stream is not None:
@@ -66,6 +68,7 @@ class TextTestResult(_TextTestResult):
stream.write(label[:1])
return
self.errors.append((test, exc_info))
+ test.passed = False
if stream is not None:
if self.showAll:
self.stream.writeln('ERROR')
diff --git a/nose/sphinx/__init__.py b/nose/sphinx/__init__.py
new file mode 100644
index 0000000..2ae2839
--- /dev/null
+++ b/nose/sphinx/__init__.py
@@ -0,0 +1 @@
+pass
diff --git a/nose/sphinx/pluginopts.py b/nose/sphinx/pluginopts.py
new file mode 100644
index 0000000..587a4e8
--- /dev/null
+++ b/nose/sphinx/pluginopts.py
@@ -0,0 +1,184 @@
+"""
+Adds a sphinx directive that can be used to automatically document a plugin.
+
+this::
+
+ .. autoplugin :: nose.plugins.foo
+ :plugin: Pluggy
+
+produces::
+
+ .. automodule :: nose.plugins.foo
+
+ Options
+ -------
+
+ .. cmdoption :: --foo=BAR, --fooble=BAR
+
+ Do the foo thing to the new thing.
+
+ Plugin
+ ------
+
+ .. autoclass :: nose.plugins.foo.Pluggy
+ :members:
+
+ Source
+ ------
+
+ .. include :: path/to/nose/plugins/foo.py
+ :literal:
+
+"""
+try:
+ from docutils import nodes
+ from docutils.statemachine import ViewList
+ from docutils.parsers.rst import directives
+except ImportError:
+ pass # won't run anyway
+
+from nose.util import resolve_name
+from nose.plugins.base import Plugin
+from nose.plugins.manager import BuiltinPluginManager
+from nose.config import Config
+from nose.core import TestProgram
+from inspect import isclass
+
+def autoplugin_directive(dirname, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ mod_name = arguments[0]
+ mod = resolve_name(mod_name)
+ plug_name = options.get('plugin', None)
+ if plug_name:
+ obj = getattr(mod, plug_name)
+ else:
+ for entry in dir(mod):
+ obj = getattr(mod, entry)
+ if isclass(obj) and issubclass(obj, Plugin) and obj is not Plugin:
+ plug_name = '%s.%s' % (mod_name, entry)
+ break
+
+ # mod docstring
+ rst = ViewList()
+ rst.append('.. automodule :: %s\n' % mod_name, '<autodoc>')
+ rst.append('', '<autodoc>')
+
+ # options
+ rst.append('Options', '<autodoc>')
+ rst.append('-------', '<autodoc>')
+ rst.append('', '<autodoc>')
+
+ plug = obj()
+ opts = OptBucket()
+ plug.options(opts, {})
+ for opt in opts:
+ rst.append(opt.options(), '<autodoc>')
+ rst.append(' \n', '<autodoc>')
+ rst.append(' ' + opt.help + '\n', '<autodoc>')
+ rst.append('\n', '<autodoc>')
+
+ # plugin class
+ rst.append('Plugin', '<autodoc>')
+ rst.append('------', '<autodoc>')
+ rst.append('', '<autodoc>')
+
+ rst.append('.. autoclass :: %s\n' % plug_name, '<autodoc>')
+ rst.append(' :members:\n', '<autodoc>')
+ rst.append(' :show-inheritance:\n', '<autodoc>')
+ rst.append('', '<autodoc>')
+
+ # source
+ rst.append('Source', '<autodoc>')
+ rst.append('------', '<autodoc>')
+ rst.append('.. include :: %s\n' % mod.__file__.replace('.pyc', '.py'),
+ '<autodoc>')
+ rst.append(' :literal:\n', '<autodoc>')
+ rst.append('', '<autodoc>')
+
+ node = nodes.section()
+ node.document = state.document
+ surrounding_title_styles = state.memo.title_styles
+ surrounding_section_level = state.memo.section_level
+ state.memo.title_styles = []
+ state.memo.section_level = 0
+ state.nested_parse(rst, 0, node, match_titles=1)
+ state.memo.title_styles = surrounding_title_styles
+ state.memo.section_level = surrounding_section_level
+
+ return node.children
+
+
+def autohelp_directive(dirname, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ """produces rst from nose help"""
+ config = Config(parserClass=OptBucket,
+ plugins=BuiltinPluginManager())
+ parser = config.getParser(TestProgram.usage())
+ rst = ViewList()
+ for line in parser.format_help().split('\n'):
+ rst.append(line, '<autodoc>')
+
+ rst.append('Options', '<autodoc>')
+ rst.append('-------', '<autodoc>')
+ rst.append('', '<autodoc>')
+ for opt in parser:
+ rst.append(opt.options(), '<autodoc>')
+ rst.append(' \n', '<autodoc>')
+ rst.append(' ' + opt.help + '\n', '<autodoc>')
+ rst.append('\n', '<autodoc>')
+ node = nodes.section()
+ node.document = state.document
+ surrounding_title_styles = state.memo.title_styles
+ surrounding_section_level = state.memo.section_level
+ state.memo.title_styles = []
+ state.memo.section_level = 0
+ state.nested_parse(rst, 0, node, match_titles=1)
+ state.memo.title_styles = surrounding_title_styles
+ state.memo.section_level = surrounding_section_level
+
+ return node.children
+
+
+class OptBucket(object):
+ def __init__(self, doc=None, prog='nosetests'):
+ self.opts = []
+ self.doc = doc
+ self.prog = prog
+
+ def __iter__(self):
+ return iter(self.opts)
+
+ def format_help(self):
+ return self.doc.replace('%prog', self.prog).replace(':\n', '::\n')
+
+ def add_option(self, *arg, **kw):
+ self.opts.append(Opt(*arg, **kw))
+
+
+class Opt(object):
+ def __init__(self, *arg, **kw):
+ self.opts = arg
+ self.action = kw.pop('action', None)
+ self.default = kw.pop('default', None)
+ self.metavar = kw.pop('metavar', None)
+ self.help = kw.pop('help', None)
+
+ def options(self):
+ buf = []
+ for optstring in self.opts:
+ desc = optstring
+ if self.action not in ('store_true', 'store_false'):
+ desc += '=%s' % self.meta(optstring)
+ buf.append(desc)
+ return '.. cmdoption :: ' + ', '.join(buf)
+
+ def meta(self, optstring):
+ # FIXME optparser default metavar?
+ return self.metavar or 'DEFAULT'
+
+
+def setup(app):
+ app.add_directive('autoplugin',
+ autoplugin_directive, 1, (1, 0, 1),
+ plugin=directives.unchanged)
+ app.add_directive('autohelp', autohelp_directive, 0, (0, 0, 1))
diff --git a/nose/suite.py b/nose/suite.py
index 29331af..1f46ea6 100644
--- a/nose/suite.py
+++ b/nose/suite.py
@@ -18,6 +18,11 @@ from nose.config import Config
from nose.proxy import ResultProxyFactory
from nose.util import isclass, resolve_name, try_run
+if sys.platform == 'cli':
+ import clr
+ clr.AddReference("IronPython")
+ from IronPython.Runtime.Exceptions import StringException
+
log = logging.getLogger(__name__)
#log.setLevel(logging.DEBUG)
@@ -152,6 +157,19 @@ class ContextSuite(LazySuite):
"""Hook for replacing error tuple output
"""
return sys.exc_info()
+
+ def _exc_info(self):
+ """Bottleneck to fix up IronPython string exceptions
+ """
+ e = self.exc_info()
+ if sys.platform == 'cli':
+ if isinstance(e[0], StringException):
+ # IronPython throws these StringExceptions, but
+ # traceback checks type(etype) == str. Make a real
+ # string here.
+ e = (str(e[0]), e[1], e[2])
+
+ return e
def run(self, result):
"""Run tests in suite inside of suite fixtures.
@@ -166,7 +184,7 @@ class ContextSuite(LazySuite):
except KeyboardInterrupt:
raise
except:
- result.addError(self, self.exc_info())
+ result.addError(self, self._exc_info())
return
try:
for test in self._tests:
@@ -184,7 +202,7 @@ class ContextSuite(LazySuite):
except KeyboardInterrupt:
raise
except:
- result.addError(self, self.exc_info())
+ result.addError(self, self._exc_info())
def hasFixtures(self, ctx_callback=None):
context = self.context
diff --git a/nose/tools.py b/nose/tools.py
index b964c1b..aa5412c 100644
--- a/nose/tools.py
+++ b/nose/tools.py
@@ -65,7 +65,7 @@ def raises(*exceptions):
def test_raises_type_error():
raise TypeError("This test passes")
- @raises(Exception):
+ @raises(Exception)
def test_that_fails_by_passing():
pass
@@ -128,7 +128,7 @@ def with_setup(setup=None, teardown=None):
@with_setup(setup, teardown)
def test_something():
- # ...
+ " ... "
Note that `with_setup` is useful *only* for test functions, not for test
methods or inside of TestCase subclasses.
diff --git a/nose/usage.txt b/nose/usage.txt
new file mode 100644
index 0000000..f1291e3
--- /dev/null
+++ b/nose/usage.txt
@@ -0,0 +1,103 @@
+nose collects tests automatically from python source files,
+directories and packages found in its working directory (which
+defaults to the current working directory). Any python source file,
+directory or package that matches the testMatch regular expression
+(by default: `(?:^|[\b_\.-])[Tt]est)` will be collected as a test (or
+source for collection of tests). In addition, all other packages
+found in the working directory will be examined for python source files
+or directories that match testMatch. Package discovery descends all
+the way down the tree, so package.tests and package.sub.tests and
+package.sub.sub2.tests will all be collected.
+
+Within a test directory or package, any python source file matching
+testMatch will be examined for test cases. Within a test module,
+functions and classes whose names match testMatch and TestCase
+subclasses with any name will be loaded and executed as tests. Tests
+may use the assert keyword or raise AssertionErrors to indicate test
+failure. TestCase subclasses may do the same or use the various
+TestCase methods available.
+
+Selecting Tests
+---------------
+
+To specify which tests to run, pass test names on the command line:
+
+ %prog only_test_this.py
+
+Test names specified may be file or module names, and may optionally
+indicate the test case to run by separating the module or file name
+from the test case name with a colon. Filenames may be relative or
+absolute. Examples:
+
+ %prog test.module
+ %prog another.test:TestCase.test_method
+ %prog a.test:TestCase
+ %prog /path/to/test/file.py:test_function
+
+You may also change the working directory where nose looks for tests,
+use the -w switch:
+
+ %prog -w /path/to/tests
+
+Note however that support for multiple -w arguments is deprecated
+in this version and will be removed in a future release, since as
+of nose 0.10 you can get the same behavior by specifying the
+target directories *without* the -w switch:
+
+ %prog /path/to/tests /another/path/to/tests
+
+Further customization of test selection and loading is possible
+through the use of plugins.
+
+Test result output is identical to that of unittest, except for
+the additional features (error classes, and plugin-supplied
+features such as output capture and assert introspection) detailed
+in the options below.
+
+Configuration
+-------------
+
+In addition to passing command-line options, you may also put
+configuration options in a .noserc or nose.cfg file in your home
+directory. These are standard .ini-style config files. Put your
+nosetests configuration in a [nosetests] section. Options are the
+same as on the command line, with the -- prefix removed. For
+options that are simple switches, you must supply a value:
+
+ [nosetests]
+ verbosity=3
+ with-doctest=1
+
+All configuration files that are found will be loaded and their options
+combined.
+
+Using Plugins
+-------------
+
+There are numerous nose plugins available via easy_install and
+elsewhere. To use a plugin, just install it. The plugin will add
+command line options to nosetests. To verify that the plugin is installed,
+run:
+
+ nosetests --plugins
+
+You can add -v or -vv to that command to show more information
+about each plugin.
+
+If you are running nose.main() or nose.run() from a script, you
+can specify a list of plugins to use by passing a list of plugins
+with the plugins keyword argument.
+
+0.9 plugins
+-----------
+
+nose 0.10 can use SOME plugins that were written for nose 0.9. The
+default plugin manager inserts a compatibility wrapper around 0.9
+plugins that adapts the changed plugin api calls. However, plugins
+that access nose internals are likely to fail, especially if they
+attempt to access test case or test suite classes. For example,
+plugins that try to determine if a test passed to startTest is an
+individual test or a suite will fail, partly because suites are no
+longer passed to startTest and partly because it's likely that the
+plugin is trying to find out if the test is an instance of a class
+that no longer exists.
diff --git a/nose/util.py b/nose/util.py
index 736d55f..9e13324 100644
--- a/nose/util.py
+++ b/nose/util.py
@@ -8,15 +8,30 @@ import re
import sys
import types
import unittest
-from compiler.consts import CO_GENERATOR
from types import ClassType, TypeType
+try:
+ from compiler.consts import CO_GENERATOR
+except ImportError:
+ # IronPython doesn't have a complier module
+ CO_GENERATOR=0x20
+
log = logging.getLogger('nose')
ident_re = re.compile(r'^[A-Za-z_][A-Za-z0-9_.]*$')
class_types = (ClassType, TypeType)
skip_pattern = r"(?:\.svn)|(?:[^.]+\.py[co])|(?:.*~)|(?:.*\$py\.class)"
+try:
+ set()
+ set = set # make from nose.util import set happy
+except NameError:
+ try:
+ from sets import Set as set
+ except ImportError:
+ pass
+
+
def ls_tree(dir_path="",
skip_pattern=skip_pattern,
indent="|-- ", branch_indent="| ",
@@ -134,7 +149,7 @@ def file_like(name):
def cmp_lineno(a, b):
"""Compare functions by their line numbers.
-
+
>>> cmp_lineno(isgenerator, ispackage)
-1
>>> cmp_lineno(ispackage, isgenerator)
@@ -235,13 +250,13 @@ def getfilename(package, relativeTo=None):
if os.path.exists(filename):
return filename
return None
-
+
def getpackage(filename):
"""
Find the full dotted package name for a given python source file
name. Returns None if the file is not a python source file.
-
+
>>> getpackage('foo.py')
'foo'
>>> getpackage('biff/baf.py')
@@ -392,7 +407,7 @@ def split_test_name(test):
return (None, None, fn)
split_test_name.__test__ = False # do not collect
-
+
def test_address(test):
"""Find the test address for a test, which may be a module, filename,
class, method or function.
@@ -464,9 +479,9 @@ def try_run(obj, names):
raise TypeError("Attribute %s of %r is not a python "
"function. Only functions or callables"
" may be used as fixtures." %
- (name, obj))
+ (name, obj))
if len(args):
- log.debug("call fixture %s.%s(%s)", obj, name, obj)
+ log.debug("call fixture %s.%s(%s)", obj, name, obj)
return func(obj)
log.debug("call fixture %s.%s", obj, name)
return func()
@@ -505,7 +520,7 @@ def match_last(a, b, regex):
return -1
return cmp(a, b)
-
+
def tolist(val):
"""Convert a value that may be a list or a (possibly comma-separated)
string into a list. The exception: None is returned as None, not [None].
@@ -529,7 +544,7 @@ def tolist(val):
try:
return re.split(r'\s*,\s*', val)
except TypeError:
- # who knows...
+ # who knows...
return list(val)
@@ -613,7 +628,7 @@ def transplant_func(func, module):
from nose.tools import make_decorator
def newfunc(*arg, **kw):
return func(*arg, **kw)
-
+
newfunc = make_decorator(func)(newfunc)
newfunc.__module__ = module
return newfunc
@@ -632,7 +647,7 @@ def transplant_class(cls, module):
'nose.util'
>>> Nf.__name__
'Failure'
-
+
"""
class C(cls):
pass
@@ -641,6 +656,16 @@ def transplant_class(cls, module):
return C
+def safe_str(val, encoding='utf-8'):
+ try:
+ return str(val)
+ except UnicodeEncodeError:
+ if isinstance(val, Exception):
+ return ' '.join([safe_str(arg, encoding)
+ for arg in val])
+ return unicode(val).encode(encoding)
+
+
if __name__ == '__main__':
import doctest
doctest.testmod()
diff --git a/scripts/mkdocs.py b/scripts/mkdocs.py
deleted file mode 100755
index a1671ec..0000000
--- a/scripts/mkdocs.py
+++ /dev/null
@@ -1,554 +0,0 @@
-#!/usr/bin/env python
-
-import os
-import re
-import time
-from docutils.core import publish_string, publish_parts
-from docutils.readers.standalone import Reader
-from docutils.writers.html4css1 import Writer, HTMLTranslator
-from pudge.browser import Browser
-import inspect
-import nose
-import textwrap
-from optparse import OptionParser
-from nose.util import resolve_name, odict
-from pygments import highlight
-from pygments.lexers import PythonLexer, PythonConsoleLexer
-from pygments.formatters import HtmlFormatter
-
-
-remove_at = re.compile(r' at 0x[0-9a-f]+')
-root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
-doc = os.path.join(root, 'doc')
-tpl = open(os.path.join(doc, 'doc.html.tpl'), 'r').read()
-api_tpl = open(os.path.join(doc, 'plugin_api.html.tpl'), 'r').read()
-plug_tpl = open(os.path.join(doc, 'plugin.html.tpl'), 'r').read()
-std_info = {
- 'version': nose.__version__,
- 'date': time.ctime()
- }
-to_write = []
-
-
-def defining_class(cls, attr):
- if hasattr(cls, '__bases__'):
- try:
- bases = list(cls.__bases__)
- except (ValueError, TypeError):
- return cls
- for base in bases:
- if attr in base.__dict__:
- return base
- return cls
-
-
-def write(filename, tpl, ctx):
- print filename
- ctx.setdefault('submenu', '')
- fp = open(filename, 'w')
- try:
- fp.write(tpl % ctx)
- except UnicodeEncodeError:
- print ctx
- fp.close()
-
-
-def doc_word(node):
-
- # handle links like package.module and module.Class
- # as wellas 'foo bar'
-
- orig = name = node.astext()
- if '.' in name:
- parts = name.split('.')
- # if the first letter of a part is capitalized, assume it's
- # a class name, and put all parts from that part on into
- # the anchor part of the link
- link, anchor = [], []
- addto = link
- while parts:
- part = parts.pop(0)
- if addto == link:
- if part[0].upper() == part[0]:
- addto = anchor
- elif part.endswith('()'):
- addto = anchor
- part = part[:-2]
- addto.append(part)
- if (name.startswith('nose.plugins')
- and 'plugintest' not in name):
- base = 'plugin_'
- link = link[-1:]
- else:
- base = 'module_'
- node['refuri'] = base + '.'.join(link) + '.html'
- if anchor:
- node['refuri'] += '#' + '.'.join(anchor)
- else:
- # pad out wiki-words
- name = name[0].lower() + name[1:]
- name = re.sub(r'([A-Z])', r' \1', name).lower()
- node['refuri'] = '_'.join(name.split(' ')) + '.html'
-
- print "Unknown ref %s -> %s" % (orig, node['refuri'])
- del node['refname']
- node.resolved = True
- return True
-doc_word.priority = 100
-
-
-
-class DocReader(Reader):
- unknown_reference_resolvers = (doc_word,)
-
-
-class PygHTMLTranslator(HTMLTranslator):
- """HTML translator that uses pygments to highlight doctests.
- """
- def visit_doctest_block(self, node):
- self.body.append(
- highlight(node.rawsource, PythonConsoleLexer(), HtmlFormatter()))
- # hacky way of capturing children -- we've processed the whole node
- self._body = self.body
- self.body = []
-
- def depart_doctest_block(self, node):
- # restore the real body, doctest node is done
- self.body = self._body
- del self._body
-
-class PygWriter(Writer):
- def __init__(self):
- Writer.__init__(self)
- self.translator_class = PygHTMLTranslator
-
-def formatargspec(func, exclude=()):
- try:
- args, varargs, varkw, defaults = inspect.getargspec(func)
- except TypeError:
- return "(...)"
- if defaults:
- defaults = map(clean_default, defaults)
- for a in exclude:
- if a in args:
- ix = args.index(a)
- args.remove(a)
- try:
- defaults.pop(ix)
- except AttributeError:
- pass
- return inspect.formatargspec(
- args, varargs, varkw, defaults).replace("'os.environ'", 'os.environ')
-
-
-def clean_default(val):
- if isinstance(val, os._Environ):
- return 'os.environ'
- return val
-
-
-def to_html_parts(rst):
- return publish_parts(rst, reader=DocReader(), writer=PygWriter())
-
-
-def to_html(rst):
- return to_html_parts(rst)['body']
-
-
-def document_module(mod):
- name = mod.qualified_name()
- print name
- body = to_html(mod.doc())
-
- # for classes: prepend with note on what highlighted means
- cls_note = "<p>Highlighted methods are defined in this class.</p>"
-
- submenu = []
-
- # classes
- mod_classes = get_classes(mod)
- classes = [document_class(cls) for cls in mod_classes]
- if classes:
- body += '<h2>Classes</h2>\n' + cls_note + '\n'.join(classes)
- submenu.extend(make_submenu('Classes', mod_classes))
-
- # functions
- mod_funcs = list(mod.routines())
- funcs = [document_function(func) for func in mod_funcs]
- if funcs:
- body += '<h2>Functions</h2>\n' + '\n'.join(funcs)
- submenu.extend(make_submenu('Functions', mod_funcs))
-
- # attributes
- mod_attrs = list(mod.attributes())
- attrs = [document_attribute(attr) for attr in mod_attrs]
- if attrs:
- body += '<h2>Attributes</h2>\n' + '\n'.join(attrs)
- submenu.extend(make_submenu('Attributes', mod_attrs))
-
- pg = {'body': body,
- 'title': name,
- 'submenu': ''.join(submenu)}
- pg.update(std_info)
- to_write.append((
- 'Modules',
- 'Module: %s' % name,
- os.path.join(doc, 'module_%s.html' % name),
- tpl, pg))
-
-
-def make_submenu(name, objs):
- sm = ['<h2>%s</h2>' % name, '<ul>']
- sm.extend(['<li><a href="#%s">%s</a></li>' % (o.name, o.name)
- for o in objs] + ['</ul>'])
- return sm
-
-def get_classes(mod):
- # some "invisible" items I do want, but not others
- # really only those classes defined in the module itself
- # with some per-module alias list handling (eg,
- # TestLoader and defaultTestLoader shouldn't both be fully doc'd)
- all = list(mod.classes(visible_only=0))
-
- # sort by place in __all__
- names = odict() # list comprehension didn't work? possible bug in odict
- for c in all:
- if c is not None and not c.isalias():
- names[c.name] = c
- ordered = []
- try:
- for name in mod.obj.__all__:
- if name in names:
- cls = names[name]
- del names[name]
- if cls is not None:
- ordered.append(cls)
- except AttributeError:
- pass
- for name, cls in names.items():
- ordered.append(cls)
- wanted = []
- classes = set([])
- for cls in ordered:
- if cls.obj in classes:
- cls.alias_for = cls.obj
- else:
- classes.add(cls.obj)
- wanted.append(cls)
- return wanted
-
-
-def document_class(cls):
- print " %s" % cls.qualified_name()
- alias = False
- if hasattr(cls, 'alias_for'):
- alias = True
- doc = '(Alias for %s)' % link_to_class(cls.alias_for)
- else:
- doc = to_html(cls.doc())
- bases = ', '.join(link_to_class(c) for c in cls.obj.__bases__)
- html = ['<a name="%s"></a><div class="cls section">' % cls.name,
- '<span class="cls name">%s</span> (%s)' % (cls.name, bases),
- '<div class="cls doc">%s' % doc]
-
- if not alias:
- real_class = cls.obj
- methods = list(cls.routines(visible_only=False))
- if methods:
- methods.sort(lambda a, b: cmp(a.name, b.name))
- html.append('<h3>Methods</h3>')
- for method in methods:
- print " %s" % method.qualified_name()
- defined_in = defining_class(real_class, method.name)
- if defined_in == real_class:
- inherited = ''
- inh_cls = ''
- else:
- inherited = '<span class="method inherited">' \
- '(inherited from %s)</span>' \
- % defined_in.__name__
- inh_cls = ' inherited'
- html.extend([
- '<div class="method section%s">' % inh_cls,
- '<span class="method name">%s' % method.name,
- '<span class="args">%s</span></span>'
- % formatargspec(method.obj),
- inherited,
- '<div class="method doc">%s</div>' % to_html(method.doc()),
- '</div>'])
-
- attrs = list(cls.attributes(visible_only=False))
- if attrs:
- attrs.sort(lambda a, b: cmp(a.name, b.name))
- html.append('<h3>Attributes</h3>')
- for attr in attrs:
- print " a %s" % attr.qualified_name()
- defined_in = defining_class(real_class, attr.name)
- if defined_in == real_class:
- inherited = ''
- inh_cls = ''
- else:
- inherited = '<span class="attr inherited">' \
- '(inherited from %s)</span>' \
- % defined_in.__name__
- inh_cls = ' inherited'
- val = format_attr(attr.parent.obj, attr.name)
- html.extend([
- '<div class="attr section%s">' % inh_cls,
- '<span class="attr name">%s</span>' % attr.name,
- '<pre class="attr value">Default value: %(a)s</pre>' %
- {'a': val},
- '<div class="attr doc">%s</div>' % to_html(attr.doc()),
- '</div>'])
-
- html.append('</div></div>')
-
- return ''.join(html)
-
-
-def document_function(func):
- print " %s" % func.name
- html = [
- '<a name="%s"></a><div class="func section">' % func.name,
- '<span class="func name">%s' % func.name,
- '<span class="args">%s</span></span>'
- % formatargspec(func.obj, exclude=['self']),
- '<div class="func doc">%s</div>' % to_html(func.doc()),
- '</div>']
- return ''.join(html)
-
-
-def document_attribute(attr):
- print " %s" % attr.name
- value = format_attr(attr.parent.obj, attr.name)
- html = [
- '<a name="%s"></a><div class="attr section">' % attr.name,
- '<span class="attr name">%s</span>' % attr.name,
- '<pre class="attr value">Default value: %(a)s</pre>' %
- {'a': value},
- '<div class="attr doc">%s</div>' % to_html(attr.doc()),
- '</div>']
- return ''.join(html)
-
-
-def format_attr(obj, attr):
- val = getattr(obj, attr)
- if isinstance(val, property):
- # value makes no sense when it's a property
- return '(property)'
- val = str(val).replace(
- '&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace(
- '"', '&quot;').replace("'", '&#39;')
- val = remove_at.sub('', val)
- return val
-
-
-def link_to_class(cls):
- mod = cls.__module__
- name = cls.__name__
- if mod.startswith('_'):
- qname = name
- else:
- qname = "%s.%s" % (mod, name)
- if not mod.startswith('nose'):
- return qname
- return '<a href="module_%s.html#%s">%s</a>' % (mod, name, name)
-
-
-def plugin_example_tests():
- dt_root = os.path.join(root, 'functional_tests', 'doc_tests')
- for dirpath, dirnames, filenames in os.walk(dt_root):
- for filename in filenames:
- if filename.startswith('.'):
- continue
- base, ext = os.path.splitext(filename)
- if ext == '.rst':
- yield os.path.join(dirpath, filename)
- if '.svn' in dirnames:
- dirnames.remove('.svn')
-
-
-def document_rst_test(filename, section):
- base, ext = os.path.splitext(os.path.basename(filename))
- rst = open(filename, 'r').read()
- parts = to_html_parts(rst)
- parts.update(std_info)
- to_write.append((section,
- parts['title'],
- os.path.join(doc, base + '.html'),
- tpl,
- parts))
-
-def main():
- # plugins
- from nose import plugins
- from nose.plugins.base import IPluginInterface
- from nose.plugins import errorclass
-
- # writing plugins guide
- writing_plugins = {'body': to_html(plugins.__doc__)}
- writing_plugins.update(std_info)
- writing_plugins['title'] = 'Writing Plugins'
- to_write.append(
- ('Plugins',
- 'Writing Plugins',
- os.path.join(doc, 'writing_plugins.html'), tpl, writing_plugins))
-
-
- # error class plugins
- ecp = {'body': to_html(errorclass.__doc__)}
- ecp.update(std_info)
- ecp['title'] = 'ErrorClass Plugins'
- to_write.append(
- ('Plugins',
- 'ErrorClass Plugins',
- os.path.join(doc, 'error_class_plugin.html'), tpl, ecp))
-
- # interface
- itf = {'body': to_html(textwrap.dedent(IPluginInterface.__doc__))}
-
- # methods
- attr = [(a, getattr(IPluginInterface, a)) for a in dir(IPluginInterface)]
- methods = [m for m in attr if inspect.ismethod(m[1])]
- methods.sort()
- # print "Documenting methods", [a[0] for a in methods]
-
- method_html = []
- method_tpl = """
- <div class="method %(extra_class)s">
- <a name="%(name)s">
- <span class="name">%(name)s</span><span class="arg">%(arg)s</span></a>
- <div class="doc">%(body)s</div>
- </div>
- """
-
- menu_links = {}
-
- m_attrs = ('_new', 'changed', 'deprecated', 'generative', 'chainable')
- for m in methods:
- name, meth = m
- ec = []
- for att in m_attrs:
- if hasattr(meth, att):
- ec.append(att.replace('_', ''))
- menu_links.setdefault(att.replace('_', ''), []).append(name)
- # padding evens the lines
- print name
- mdoc = {'body': to_html(textwrap.dedent(' ' + meth.__doc__))}
- argspec = formatargspec(meth)
- mdoc.update({'name': name,
- 'extra_class': ' '.join(ec),
- 'arg': argspec})
- method_html.append(method_tpl % mdoc)
-
- itf['methods'] = ''.join(method_html)
- itf.update(std_info)
- itf['title'] = 'Plugin Interface'
-
- menu = []
- for section in ('new', 'changed', 'deprecated'):
- menu.append('<h2>%s methods</h2>' % section.title())
- menu.append('<ul><li>')
- menu.append('</li><li>'.join([
- '<a href="#%(name)s">%(name)s</a>' % {'name': n}
- for n in menu_links[section]]))
- menu.append('</li></ul>')
- itf['sub_menu'] = ''.join(menu)
-
- to_write.append(
- ('Plugins',
- 'Plugin Interface',
- os.path.join(doc, 'plugin_interface.html'), api_tpl, itf))
-
-
- # individual plugin usage docs
- from nose.plugins.builtin import builtins
-
- pmeths = [m[0] for m in methods[:]
- if not 'options' in m[0].lower()]
- pmeths.append('options')
- pmeths.sort()
-
- for modulename, clsname in builtins:
- _, _, modname = modulename.split('.')
- mod = resolve_name(modulename)
- cls = getattr(mod, clsname)
- filename = os.path.join(doc, 'plugin_%s.html' % modname)
- print modname, filename
- if not mod.__doc__:
- print "No docs"
- continue
- pdoc = {'body': to_html(mod.__doc__)}
- pdoc.update(std_info)
- pdoc['title'] = 'builtin plugin: %s' % modname
-
- # options
- parser = OptionParser(add_help_option=False)
- plug = cls()
- plug.addOptions(parser)
- options = parser.format_option_help()
- pdoc['options'] = options
-
- # hooks used
- hooks = []
- for m in pmeths:
- if getattr(cls, m, None):
- hooks.append('<li><a href="plugin_interface.html#%(name)s">'
- '%(name)s</a></li>' % {'name': m})
- pdoc['hooks'] = ''.join(hooks)
-
- source = inspect.getsource(mod)
- pdoc['source'] = highlight(source, PythonLexer(), HtmlFormatter())
- to_write.append(
- ('Plugins',
- 'Builtin Plugin: %s' % modname,
- os.path.join(doc, filename), plug_tpl, pdoc))
-
-
- # individual module docs
- b = Browser(['nose','nose.plugins.manager', 'nose.plugins.plugintest'],
- exclude_modules=['nose.plugins', 'nose.ext'])
- for mod in b.modules(recursive=1):
- if mod.name == 'nose':
- # no need to regenerate, this is the source of the doc index page
- continue
- print mod.qualified_name()
- document_module(mod)
-
-
- # plugin examples doctests
- for testfile in plugin_example_tests():
- document_rst_test(testfile, "Plugin Examples")
-
-
- # finally build the menu and write all pages
- menu = []
- sections = odict()
- for page in to_write:
- section, _, _, _, _ = page
- sections.setdefault(section, []).append(page)
-
- for section, pages in sections.items():
- menu.append('<h2>%s</h2>' % section)
- menu.append('<ul>')
- pages.sort()
- menu.extend([
- '<li><a href="%s">%s</a></li>' % (os.path.basename(filename), title)
- for _, title, filename, _, _ in pages ])
- menu.append('</ul>')
-
- menu = ''.join(menu)
- for section, title, filename, template, ctx in to_write:
- ctx['menu'] = menu
- write(filename, template, ctx)
-
- # doc section index page
- idx_tpl = open(os.path.join(doc, 'index.html.tpl'), 'r').read()
- idx = {
- 'title': 'API documentation',
- 'menu': menu}
- idx.update(std_info)
- write(os.path.join(doc, 'index.html'), idx_tpl, idx)
-
-if __name__ == '__main__':
- main()
diff --git a/scripts/mkrelease.py b/scripts/mkrelease.py
index 76b4a30..0331b70 100755
--- a/scripts/mkrelease.py
+++ b/scripts/mkrelease.py
@@ -9,25 +9,11 @@ from commands import getstatusoutput
success = 0
-current = os.getcwd()
-version = nose.__version__
-here = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
-parts = here.split('/')
-if 'branches' in parts:
- lindex = parts.index('branches')
-elif 'tags' in parts:
- lindex = parts.index('tags')
-elif 'trunk' in parts:
- lindex = parts.index('trunk')
-else:
- raise Exception("Unable to find svnroot from %s" % here)
-svnroot = os.path.join('/', *parts[:lindex])
-branchroot = os.path.join(svnroot, 'branches')
-tagroot = os.path.join(svnroot, 'tags')
-svntrunk = os.path.join(svnroot, 'trunk')
+version = nose.__version__
svn_base_url = 'https://python-nose.googlecode.com/svn'
svn_trunk_url = 'https://python-nose.googlecode.com/svn/trunk'
+svn_tags_url = 'https://python-nose.googlecode.com/svn/tags'
SIMULATE = 'exec' not in sys.argv
if SIMULATE:
@@ -50,60 +36,37 @@ def cd(dir):
def main():
- cd(svnroot)
- branch = 'branches/%s-stable' % version
- tag = 'tags/%s-release' % version
-
- svn_branch_url = '%s/%s' % (svn_base_url, branch)
- svn_tag_url = '%s/%s' % (svn_base_url, tag)
-
- if os.path.isdir(tag):
- raise Exception(
- "Tag path %s already exists. Can't release same version twice!"
- % tag)
-
- # make branch, if needed
- if not os.path.isdir(os.path.join(svnroot, branch)):
- # make branch
- runcmd("svn copy %s %s -m 'Release branch for %s'"
- % (svn_trunk_url, svn_branch_url, version))
- # clean up setup.cfg and check in tag
- cd(branchroot)
- runcmd('svn co %s' % svn_branch_url)
- else:
- # re-releasing branch
- cd(branch)
- runcmd('svn up')
-
- # make tag from branch
- runcmd('svn copy %s %s -m "Release tag for %s"'
- % (svn_branch_url, svn_tag_url, version))
+ tag = 'nose_rel_%s' % version
+ svn_tag_url = "%s/%s" % (svn_tags_url, tag)
+ # create tag
+ runcmd("svn copy %s %s -m 'Tagged release %s'" %
+ (svn_trunk_url, svn_tag_url, version))
+
# check out tag
- cd(tagroot)
+ cd('/tmp')
runcmd('svn co %s' % svn_tag_url)
- cd(svnroot)
cd(tag)
# remove dev tag from setup
runcmd('cp setup.cfg.release setup.cfg')
runcmd('svn rm setup.cfg.release --force')
runcmd("svn ci -m 'Updated setup.cfg to release status'")
-
- # wiki pages must be built from tag checkout
- runcmd('./scripts/mkwiki.py')
-
+ runcmd("rm -rf /tmp/%s" % tag)
+
# need to build dist from an *export* to limit files included
# (setuptools includes too many files when run under a checkout)
# export tag
cd('/tmp')
- runcmd('svn export %s nose_rel_%s' % (svn_tag_url, version))
- cd('nose_rel_%s' % version)
+ runcmd('svn export %s %s' % (svn_tag_url, tag))
+ cd(tag)
# make docs
- runcmd('./scripts/mkindex.py')
- runcmd('./scripts/mkdocs.py')
+ #runcmd('./scripts/mkindex.py')
+ cd('doc')
+ runcmd('make html')
+ cd('..')
# make sdist
runcmd('python setup.py sdist')
@@ -113,7 +76,7 @@ def main():
up = os.environ['NOSE_UPLOAD']
cv = {
'host': up[:up.index(':')],
- 'path': up[up.index(':')+1:],
+ 'path': up[up.index(':')+1:-1],
'version':version,
'upload': up,
'upload_docs': "%s/%s" % (up, version) }
@@ -126,19 +89,18 @@ def main():
cmd = 'ssh %(host)s "mkdir -p %(docpath)s"' % cv
runcmd(cmd)
- cmd = 'scp -C index.html %(upload_docs)s' % cv
- runcmd(cmd)
+ #cmd = 'scp -C index.html %(upload_docs)s' % cv
+ #runcmd(cmd)
cmd = ('scp -C doc/*.html doc/*.css doc/*.png '
'%(upload_docs)s/doc' % cv)
runcmd(cmd)
cmd = ('ssh %(host)s '
- '"ln -nfs %(versionpath)s/index.html %(path)s/index.html; '
- 'ln -nfs %(docpath)s %(path)s/doc"' % cv)
+ 'ln -nfs %(docpath)s %(path)s/doc"; '
+ '"ln -nfs %(path)s/doc/main_index.html %(path)s/index.html'
+ % cv)
runcmd(cmd)
-
- cd(current)
if __name__ == '__main__':
main()
diff --git a/setup.py b/setup.py
index 0d12f3b..101ebd6 100644
--- a/setup.py
+++ b/setup.py
@@ -1,15 +1,20 @@
from nose import __version__ as VERSION
+import sys
+
+py_vers_tag = '-%s.%s' % sys.version_info[:2]
try:
from setuptools import setup, find_packages
addl_args = dict(
+ zip_safe = False,
packages = find_packages(),
entry_points = {
'console_scripts': [
- 'nosetests = nose:run_exit'
+ 'nosetests = nose:run_exit',
+ 'nosetests%s = nose:run_exit' % py_vers_tag,
],
'distutils.commands': [
- ' nosetests = nose.commands:nosetests'
+ ' nosetests = nose.commands:nosetests',
],
},
test_suite = 'nose.collector',
@@ -18,7 +23,7 @@ except ImportError:
from distutils.core import setup
addl_args = dict(
packages = ['nose', 'nose.ext', 'nose.plugins'],
- scripts = ['bin/nosetests']
+ scripts = ['bin/nosetests'],
)
setup(
diff --git a/unit_tests/test_logcapture_plugin.py b/unit_tests/test_logcapture_plugin.py
index e6d3b34..c540491 100644
--- a/unit_tests/test_logcapture_plugin.py
+++ b/unit_tests/test_logcapture_plugin.py
@@ -50,11 +50,20 @@ class TestLogCapturePlugin(object):
c.configure(options, Config())
eq_('++%(message)s++', c.logformat)
+ def test_logging_datefmt_option(self):
+ env = {'NOSE_LOGDATEFMT': '%H:%M:%S'}
+ c = LogCapture()
+ parser = OptionParser()
+ c.addOptions(parser, env)
+ options, args = parser.parse_args(['logging_datefmt'])
+ c.configure(options, Config())
+ eq_('%H:%M:%S', c.logdatefmt)
+
def test_captures_logging(self):
c = LogCapture()
parser = OptionParser()
c.addOptions(parser, {})
- options, args = parser.parse_args()
+ options, args = parser.parse_args([])
c.configure(options, Config())
c.start()
log = logging.getLogger("foobar.something")
@@ -73,7 +82,7 @@ class TestLogCapturePlugin(object):
records = c.formatLogRecords()
eq_(1, len(records))
eq_("++Hello++", records[0])
-
+
def test_logging_filter(self):
env = {'NOSE_LOGFILTER': 'foo,bar'}
c = LogCapture()
@@ -93,3 +102,26 @@ class TestLogCapturePlugin(object):
assert records[1].startswith('foo.x:'), records[1]
assert records[2].startswith('bar.quux:'), records[2]
+ def test_unicode_messages_handled(self):
+ msg = u'Ivan Krsti\u0107'
+ c = LogCapture()
+ parser = OptionParser()
+ c.addOptions(parser, {})
+ options, args = parser.parse_args([])
+ c.configure(options, Config())
+ c.start()
+ log = logging.getLogger("foobar.something")
+ log.debug(msg)
+ log.debug("ordinary string log")
+ c.end()
+
+ class Dummy:
+ pass
+ test = Dummy()
+ try:
+ raise Exception(msg)
+ except:
+ err = sys.exc_info()
+ (ec, ev, tb) = c.formatError(test, err)
+ print ev
+ assert msg.encode('utf-8') in ev
diff --git a/unit_tests/test_multiprocess_runner.py b/unit_tests/test_multiprocess_runner.py
index 9e2ee8d..71ee398 100644
--- a/unit_tests/test_multiprocess_runner.py
+++ b/unit_tests/test_multiprocess_runner.py
@@ -28,7 +28,7 @@ class TestMultiProcessTestRunner(unittest.TestCase):
def test_next_batch_with_classes(self):
r = multiprocess.MultiProcessTestRunner()
l = TestLoader()
- tests = list(r.next_batch(ContextSuite(
+ tests = list(r.nextBatch(ContextSuite(
tests=[l.makeTest(T_fixt), l.makeTest(T)])))
print tests
self.assertEqual(len(tests), 3)
@@ -49,7 +49,7 @@ class TestMultiProcessTestRunner(unittest.TestCase):
r = multiprocess.MultiProcessTestRunner()
l = TestLoader()
- tests = list(r.next_batch(l.loadTestsFromModule(mod_with_fixt)))
+ tests = list(r.nextBatch(l.loadTestsFromModule(mod_with_fixt)))
print tests
self.assertEqual(len(tests), 1)
@@ -70,7 +70,7 @@ class TestMultiProcessTestRunner(unittest.TestCase):
r = multiprocess.MultiProcessTestRunner()
l = TestLoader()
- tests = list(r.next_batch(l.loadTestsFromModule(mod_no_fixt)))
+ tests = list(r.nextBatch(l.loadTestsFromModule(mod_no_fixt)))
print tests
self.assertEqual(len(tests), 3)
@@ -83,7 +83,7 @@ class TestMultiProcessTestRunner(unittest.TestCase):
pass
r = multiprocess.MultiProcessTestRunner()
l = TestLoader()
- tests = list(r.next_batch(l.makeTest(Tg)))
+ tests = list(r.nextBatch(l.makeTest(Tg)))
print tests
print [r.address(t) for t in tests]
self.assertEqual(len(tests), 1)
@@ -111,7 +111,7 @@ class TestMultiProcessTestRunner(unittest.TestCase):
r = multiprocess.MultiProcessTestRunner()
l = TestLoader()
- tests = list(r.next_batch(l.loadTestsFromModule(mod_with_fixt2)))
+ tests = list(r.nextBatch(l.loadTestsFromModule(mod_with_fixt2)))
print tests
self.assertEqual(len(tests), 3)
diff --git a/unit_tests/test_xunit.py b/unit_tests/test_xunit.py
new file mode 100644
index 0000000..a10b2a0
--- /dev/null
+++ b/unit_tests/test_xunit.py
@@ -0,0 +1,205 @@
+
+import sys
+import os
+import optparse
+import unittest
+from nose.tools import eq_
+from nose.plugins.xunit import Xunit
+from nose.exc import SkipTest
+from nose.config import Config
+
+def mktest():
+ class TC(unittest.TestCase):
+ def runTest(self):
+ pass
+ test = TC()
+ return test
+
+mktest.__test__ = False
+
+class TestEscaping(unittest.TestCase):
+
+ def setUp(self):
+ self.x = Xunit()
+
+ def test_all(self):
+ eq_(self.x._xmlsafe('<baz src="http://foo?f=1&b=2" />'),
+ '&gt;baz src=&quot;http://foo?f=1&amp;b=2&quot; /&lt;')
+
+
+ def test_unicode_is_utf8_by_default(self):
+ eq_(self.x._xmlsafe(u'Ivan Krsti\u0107'),
+ 'Ivan Krsti\xc4\x87')
+
+
+ def test_unicode_custom_utf16_madness(self):
+ self.x.encoding = 'utf-16'
+ utf16 = self.x._xmlsafe(u'Ivan Krsti\u0107')
+
+ # to avoid big/little endian bytes, assert that we can put it back:
+ eq_(utf16.decode('utf16'), u'Ivan Krsti\u0107')
+
+class TestOptions(unittest.TestCase):
+
+ def test_defaults(self):
+ parser = optparse.OptionParser()
+ x = Xunit()
+ x.add_options(parser, env={})
+ (options, args) = parser.parse_args([])
+ eq_(options.xunit_file, "nosetests.xml")
+
+ def test_file_from_environ(self):
+ parser = optparse.OptionParser()
+ x = Xunit()
+ x.add_options(parser, env={'NOSE_XUNIT_FILE': "kangaroo.xml"})
+ (options, args) = parser.parse_args([])
+ eq_(options.xunit_file, "kangaroo.xml")
+
+ def test_file_from_opt(self):
+ parser = optparse.OptionParser()
+ x = Xunit()
+ x.add_options(parser, env={})
+ (options, args) = parser.parse_args(["--xunit-file=blagojevich.xml"])
+ eq_(options.xunit_file, "blagojevich.xml")
+
+class TestXMLOutputWithXML(unittest.TestCase):
+
+ def setUp(self):
+ self.xmlfile = os.path.abspath(
+ os.path.join(os.path.dirname(__file__),
+ 'support', 'xunit.xml'))
+ parser = optparse.OptionParser()
+ self.x = Xunit()
+ self.x.add_options(parser, env={})
+ (options, args) = parser.parse_args([
+ "--with-xunit",
+ "--xunit-file=%s" % self.xmlfile
+ ])
+ self.x.configure(options, Config())
+
+ try:
+ import xml.etree.ElementTree
+ except ImportError:
+ self.ET = False
+ else:
+ self.ET = xml.etree.ElementTree
+
+ def tearDown(self):
+ os.unlink(self.xmlfile)
+
+ def get_xml_report(self):
+ class DummyStream:
+ pass
+ self.x.report(DummyStream())
+ f = open(self.xmlfile, 'r')
+ return f.read()
+ f.close()
+
+ def test_addFailure(self):
+ test = mktest()
+ self.x.startTest(test)
+ try:
+ raise AssertionError("one is not equal to two")
+ except AssertionError:
+ some_err = sys.exc_info()
+
+ self.x.addFailure(test, some_err)
+
+ result = self.get_xml_report()
+ print result
+
+ if self.ET:
+ tree = self.ET.fromstring(result)
+ eq_(tree.attrib['name'], "nosetests")
+ eq_(tree.attrib['tests'], "1")
+ eq_(tree.attrib['errors'], "0")
+ eq_(tree.attrib['failures'], "1")
+ eq_(tree.attrib['skip'], "0")
+
+ tc = tree.find("testcase")
+ eq_(tc.attrib['classname'], "test_xunit.TC")
+ eq_(tc.attrib['name'], "test_xunit.TC.runTest")
+ assert int(tc.attrib['time']) >= 0
+
+ err = tc.find("failure")
+ eq_(err.attrib['type'], "exceptions.AssertionError")
+ err_lines = err.text.strip().split("\n")
+ eq_(err_lines[0], 'Traceback (most recent call last):')
+ eq_(err_lines[-1], 'AssertionError: one is not equal to two')
+ eq_(err_lines[-2], ' raise AssertionError("one is not equal to two")')
+ else:
+ # this is a dumb test for 2.4-
+ assert '<?xml version="1.0" encoding="UTF-8"?>' in result
+ assert '<testsuite name="nosetests" tests="1" errors="0" failures="1" skip="0">' in result
+ assert '<testcase classname="test_xunit.TC" name="test_xunit.TC.runTest"' in result
+ assert '<failure type="exceptions.AssertionError">' in result
+ assert 'AssertionError: one is not equal to two' in result
+ assert '</failure></testcase></testsuite>' in result
+
+ def test_addError(self):
+ test = mktest()
+ self.x.startTest(test)
+ try:
+ raise RuntimeError("some error happened")
+ except RuntimeError:
+ some_err = sys.exc_info()
+
+ self.x.addError(test, some_err)
+
+ result = self.get_xml_report()
+ print result
+
+ if self.ET:
+ tree = self.ET.fromstring(result)
+ eq_(tree.attrib['name'], "nosetests")
+ eq_(tree.attrib['tests'], "1")
+ eq_(tree.attrib['errors'], "1")
+ eq_(tree.attrib['failures'], "0")
+ eq_(tree.attrib['skip'], "0")
+
+ tc = tree.find("testcase")
+ eq_(tc.attrib['classname'], "test_xunit.TC")
+ eq_(tc.attrib['name'], "test_xunit.TC.runTest")
+ assert int(tc.attrib['time']) >= 0
+
+ err = tc.find("error")
+ eq_(err.attrib['type'], "exceptions.RuntimeError")
+ err_lines = err.text.strip().split("\n")
+ eq_(err_lines[0], 'Traceback (most recent call last):')
+ eq_(err_lines[-1], 'RuntimeError: some error happened')
+ eq_(err_lines[-2], ' raise RuntimeError("some error happened")')
+ else:
+ # this is a dumb test for 2.4-
+ assert '<?xml version="1.0" encoding="UTF-8"?>' in result
+ assert '<testsuite name="nosetests" tests="1" errors="1" failures="0" skip="0">' in result
+ assert '<testcase classname="test_xunit.TC" name="test_xunit.TC.runTest"' in result
+ assert '<error type="exceptions.RuntimeError">' in result
+ assert 'RuntimeError: some error happened' in result
+ assert '</error></testcase></testsuite>' in result
+
+ def test_addSuccess(self):
+ test = mktest()
+ self.x.startTest(test)
+ self.x.addSuccess(test, (None,None,None))
+
+ result = self.get_xml_report()
+ print result
+
+ if self.ET:
+ tree = self.ET.fromstring(result)
+ eq_(tree.attrib['name'], "nosetests")
+ eq_(tree.attrib['tests'], "1")
+ eq_(tree.attrib['errors'], "0")
+ eq_(tree.attrib['failures'], "0")
+ eq_(tree.attrib['skip'], "0")
+
+ tc = tree.find("testcase")
+ eq_(tc.attrib['classname'], "test_xunit.TC")
+ eq_(tc.attrib['name'], "test_xunit.TC.runTest")
+ assert int(tc.attrib['time']) >= 0
+ else:
+ # this is a dumb test for 2.4-
+ assert '<?xml version="1.0" encoding="UTF-8"?>' in result
+ assert '<testsuite name="nosetests" tests="1" errors="0" failures="0" skip="0">' in result
+ assert '<testcase classname="test_xunit.TC" name="test_xunit.TC.runTest"' in result
+ assert '</testsuite>' in result