You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by di...@apache.org on 2022/11/02 20:49:30 UTC

[allura] branch pytest-finalize updated (7cadb3268 -> 92fe31557)

This is an automated email from the ASF dual-hosted git repository.

dill0wn pushed a change to branch pytest-finalize
in repository https://gitbox.apache.org/repos/asf/allura.git


 discard 7cadb3268 [#8445] remove 'test_suite' and 'tests_require' from setup.py as they are deprecated
 discard 6d32684f5 [#8445] update docker test run to use pytest
 discard 46bddf6c2 [#8445] updated test docs, removed various old references to nose and replaced with pytest
 discard 285f5c33f [#8445] remove @with_nose_compatability
 discard 671c49cbd [#8445] update test runner to use pytest and pytest-xdist for parallelization
 discard 4472132d2 [#8445] remove unused tox.ini
     new 7d88365ee [#8455] remove unused tox.ini
     new d5d42dcc7 [#8455] update test runner to use pytest and pytest-xdist for parallelization
     new 2786c7fc5 [#8455] remove @with_nose_compatability
     new 954da7fdc [#8455] updated test docs, removed various old references to nose and replaced with pytest
     new be65c00db [#8455] update docker test run to use pytest
     new 169ddb77d [#8455] remove 'test_suite' and 'tests_require' from setup.py as they are deprecated
     new 92fe31557 [#8455] misc fixes to tests from pytest conversion

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (7cadb3268)
            \
             N -- N -- N   refs/heads/pytest-finalize (92fe31557)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 Allura/allura/tests/test_globals.py | 28 ++++++++++++++++------------
 run_tests                           |  1 +
 2 files changed, 17 insertions(+), 12 deletions(-)


[allura] 01/07: [#8455] remove unused tox.ini

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dill0wn pushed a commit to branch pytest-finalize
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 7d88365ee178a2f1ca298975a3fdc5fb62672d54
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Tue Nov 1 14:48:21 2022 +0000

    [#8455] remove unused tox.ini
---
 tox.ini | 32 --------------------------------
 1 file changed, 32 deletions(-)

diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index 5eff60b89..000000000
--- a/tox.ini
+++ /dev/null
@@ -1,32 +0,0 @@
-;       Licensed to the Apache Software Foundation (ASF) under one
-;       or more contributor license agreements.  See the NOTICE file
-;       distributed with this work for additional information
-;       regarding copyright ownership.  The ASF licenses this file
-;       to you under the Apache License, Version 2.0 (the
-;       "License"); you may not use this file except in compliance
-;       with the License.  You may obtain a copy of the License at
-;
-;         http://www.apache.org/licenses/LICENSE-2.0
-;
-;       Unless required by applicable law or agreed to in writing,
-;       software distributed under the License is distributed on an
-;       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-;       KIND, either express or implied.  See the License for the
-;       specific language governing permissions and limitations
-;       under the License.
-
-# this is helpful for running individual nosetests like:
-#       tox -q allura.tests.functional.test_admin:TestInstallableTools.test_installable_tools_response
-# add -p2 if its safe to run in parallel (doesn't use filesystem path like a repo)
-# for full test suite runs, use ./run_tests within the virtualenv
-
-[tox]
-envlist = py{37}
-# since we don't have one top-level setup.py:
-skipsdist = True
-
-[testenv]
-deps = -rrequirements.txt
-# can comment out this line after venvs have been built the first time, for faster runs:
-commands_pre = ./rebuild-all.bash
-commands = nosetests {posargs}


[allura] 02/07: [#8455] update test runner to use pytest and pytest-xdist for parallelization

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dill0wn pushed a commit to branch pytest-finalize
in repository https://gitbox.apache.org/repos/asf/allura.git

commit d5d42dcc7117e3a66e18e8d60104c1e53538f1be
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Tue Nov 1 14:53:18 2022 +0000

    [#8455] update test runner to use pytest and pytest-xdist for parallelization
---
 requirements-dev.txt |  4 ++--
 requirements.in      |  1 +
 requirements.txt     |  6 ++++++
 run_tests            | 33 ++++++++++++++++++---------------
 4 files changed, 27 insertions(+), 17 deletions(-)

diff --git a/requirements-dev.txt b/requirements-dev.txt
index de987cb82..71ace7eda 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -7,7 +7,7 @@ sphinx-argparse
 sphinx-rtd-theme
 sphinxcontrib-programoutput
 coverage
-nose
-nose-xunitmp
+pytest
+pytest-xdist
 pycodestyle
 pyflakes
diff --git a/requirements.in b/requirements.in
index 8929d5532..1ab4dec87 100644
--- a/requirements.in
+++ b/requirements.in
@@ -55,6 +55,7 @@ pyflakes
 testfixtures
 WebTest
 pytest
+pytest-xdist
 
 # deployment
 gunicorn
diff --git a/requirements.txt b/requirements.txt
index b1a0a6191..adcc4ece2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -40,6 +40,8 @@ easywidgets==0.4.1
     # via -r requirements.in
 emoji==1.7.0
     # via -r requirements.in
+execnet==1.9.0
+    # via pytest-xdist
 feedgenerator==2.0.0
     # via -r requirements.in
 feedparser==6.0.10
@@ -139,6 +141,10 @@ pypeline[creole,markdown,rst,textile]==0.6.0
 pysolr==3.9.0
     # via -r requirements.in
 pytest==7.1.3
+    # via
+    #   -r requirements.in
+    #   pytest-xdist
+pytest-xdist==3.0.2
     # via -r requirements.in
 python-dateutil==2.8.2
     # via
diff --git a/run_tests b/run_tests
index 23b8d3d4f..98c39f88c 100755
--- a/run_tests
+++ b/run_tests
@@ -31,7 +31,6 @@ import six
 CPUS = multiprocessing.cpu_count()
 CONCURRENT_SUITES = (CPUS // 4) or CPUS
 CONCURRENT_TESTS = CPUS // CONCURRENT_SUITES
-PROC_TIMEOUT = 360
 
 ALT_PKG_PATHS = {
     'Allura': 'allura/tests/',
@@ -117,17 +116,21 @@ def check_packages(packages):
             yield pkg
 
 
-def run_tests_in_parallel(options, nosetests_args):
+def run_tests_in_parallel(options, runner_args):
+    default_args = [
+        '-c /dev/null',  # pytest's equivalent of nose's NOSE_IGNORE_CONFIG_FILES='1' is '-c /dev/null/'
+        '--disable-warnings',
+    ]
+
     def get_pkg_path(pkg):
         return ALT_PKG_PATHS.get(pkg, '')
 
     def get_multiproc_args(pkg):
         if options.concurrent_tests == 1:
             return ''
-        return ('--processes={procs_per_suite} --process-timeout={proc_timeout}'.format(
-            procs_per_suite=options.concurrent_tests,
-            proc_timeout=PROC_TIMEOUT)
-            if pkg not in NOT_MULTIPROC_SAFE else '')
+        return '-n {procs_per_suite}'.format(
+            procs_per_suite=options.concurrent_tests
+        ) if pkg not in NOT_MULTIPROC_SAFE else ''
 
     def get_concurrent_suites():
         if '-n' in sys.argv:
@@ -135,13 +138,12 @@ def run_tests_in_parallel(options, nosetests_args):
         return CPUS
 
     cmds = []
-    env = dict(os.environ,
-               NOSE_IGNORE_CONFIG_FILES='1')
+    env = dict(os.environ)
     for package in check_packages(options.packages):
-        runner = 'nosetests'
+        runner = 'pytest'
         if options.coverage:
-            # This is the recommended way to run coverage + nose  https://coverage.readthedocs.io/en/latest/faq.html
-            runner = 'coverage run $(which nosetests)'
+            # This is the recommended way to run coverage + pytest  https://coverage.readthedocs.io/en/6.5.0/
+            runner = f'coverage run -m {runner}'
             """
             And using config settings in setup.cfg seems to work well with parallel processes
             Otherwise need to run with a complex setup like:
@@ -154,10 +156,10 @@ def run_tests_in_parallel(options, nosetests_args):
             """
 
         multiproc_args = get_multiproc_args(package)
-        cmd = "{runner} {pkg_path} {nosetests_args} {multiproc_args}".format(
+        cmd = "{runner} {pkg_path} {args} {multiproc_args}".format(
             runner=runner,
             pkg_path=get_pkg_path(package),
-            nosetests_args=' '.join(nosetests_args),
+            args=' '.join(default_args + runner_args),
             multiproc_args=multiproc_args,
         )
         if options.coverage:
@@ -184,12 +186,13 @@ def run_tests_in_parallel(options, nosetests_args):
 def parse_args():
     parser = argparse.ArgumentParser(
         formatter_class=argparse.RawDescriptionHelpFormatter,
-        epilog='''All additional arguments are passed along to nosetests (e.g. -v)''')
+        epilog='''All additional arguments are passed along to pytest (e.g. -v)''')
     parser.add_argument('-n', help='Number of test suites to run concurrently in separate '
                                    'processes. Default: # CPUs / 4',
                         dest='concurrent_suites', type=int, default=CONCURRENT_SUITES)
     parser.add_argument('-m', help='Number of tests to run concurrently in separate '
-                                   'processes, per suite. Default: # CPUs / # concurrent suites',
+                                   'processes, per suite. Default: # CPUs / # concurrent suites. '
+                                   '(equivalent to pytest-xdist\'s -n option)',
                         dest='concurrent_tests', type=int, default=CONCURRENT_TESTS)
     parser.add_argument('--coverage', action='store_true',
                         help='Collect code coverage details, and report')


[allura] 03/07: [#8455] remove @with_nose_compatability

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dill0wn pushed a commit to branch pytest-finalize
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 2786c7fc5db1fd62d5cc4151edbb66d85d1ed422
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Tue Nov 1 15:28:04 2022 +0000

    [#8455] remove @with_nose_compatability
---
 Allura/allura/tests/functional/test_admin.py       |  9 ----
 Allura/allura/tests/functional/test_auth.py        | 10 -----
 Allura/allura/tests/functional/test_discuss.py     |  4 --
 Allura/allura/tests/functional/test_feeds.py       |  2 -
 Allura/allura/tests/functional/test_gravatar.py    |  2 -
 Allura/allura/tests/functional/test_home.py        |  2 -
 Allura/allura/tests/functional/test_nav.py         |  2 -
 .../allura/tests/functional/test_neighborhood.py   |  4 --
 Allura/allura/tests/functional/test_newforge.py    |  2 -
 .../tests/functional/test_personal_dashboard.py    |  4 --
 Allura/allura/tests/functional/test_rest.py        |  5 ---
 Allura/allura/tests/functional/test_root.py        |  3 --
 Allura/allura/tests/functional/test_search.py      |  2 -
 Allura/allura/tests/functional/test_site_admin.py  |  7 ----
 Allura/allura/tests/functional/test_static.py      |  2 -
 Allura/allura/tests/functional/test_subscriber.py  |  2 -
 Allura/allura/tests/functional/test_tool_list.py   |  2 -
 .../allura/tests/functional/test_trovecategory.py  |  3 --
 .../allura/tests/functional/test_user_profile.py   |  3 --
 Allura/allura/tests/model/test_auth.py             |  2 -
 Allura/allura/tests/model/test_filesystem.py       |  2 -
 Allura/allura/tests/model/test_notification.py     |  5 ---
 Allura/allura/tests/model/test_repo.py             |  5 ---
 Allura/allura/tests/model/test_timeline.py         |  2 -
 .../tests/scripts/test_create_sitemap_files.py     |  2 -
 .../allura/tests/scripts/test_delete_projects.py   |  2 -
 Allura/allura/tests/scripts/test_misc_scripts.py   |  2 -
 Allura/allura/tests/scripts/test_reindexes.py      |  3 --
 .../tests/templates/jinja_master/test_lib.py       |  2 -
 Allura/allura/tests/test_commands.py               |  7 ----
 Allura/allura/tests/test_decorators.py             |  3 --
 Allura/allura/tests/test_diff.py                   |  2 -
 Allura/allura/tests/test_dispatch.py               |  2 -
 Allura/allura/tests/test_globals.py                |  7 ----
 Allura/allura/tests/test_helpers.py                |  5 ---
 Allura/allura/tests/test_mail_util.py              |  6 ---
 Allura/allura/tests/test_markdown.py               |  6 ---
 Allura/allura/tests/test_middlewares.py            |  2 -
 Allura/allura/tests/test_multifactor.py            | 11 -----
 Allura/allura/tests/test_plugin.py                 |  8 ----
 Allura/allura/tests/test_scripttask.py             |  2 -
 Allura/allura/tests/test_security.py               |  2 -
 Allura/allura/tests/test_tasks.py                  |  9 ----
 Allura/allura/tests/test_utils.py                  | 11 -----
 Allura/allura/tests/test_validators.py             | 12 ------
 Allura/allura/tests/test_webhooks.py               |  8 ----
 .../test_discussion_moderation_controller.py       |  4 --
 Allura/allura/tests/unit/phone/test_nexmo.py       |  2 -
 .../allura/tests/unit/phone/test_phone_service.py  |  2 -
 Allura/allura/tests/unit/spam/test_akismet.py      |  2 -
 Allura/allura/tests/unit/spam/test_spam_filter.py  |  4 --
 .../allura/tests/unit/spam/test_stopforumspam.py   |  2 -
 Allura/allura/tests/unit/test_app.py               |  5 ---
 Allura/allura/tests/unit/test_artifact.py          |  2 -
 Allura/allura/tests/unit/test_discuss.py           |  2 -
 Allura/allura/tests/unit/test_helpers/test_ago.py  |  2 -
 .../tests/unit/test_helpers/test_set_context.py    |  7 ----
 .../allura/tests/unit/test_ldap_auth_provider.py   |  2 -
 Allura/allura/tests/unit/test_mixins.py            |  2 -
 .../allura/tests/unit/test_package_path_loader.py  |  2 -
 Allura/allura/tests/unit/test_post_model.py        |  2 -
 Allura/allura/tests/unit/test_project.py           |  2 -
 Allura/allura/tests/unit/test_repo.py              |  8 ----
 Allura/allura/tests/unit/test_session.py           |  5 ---
 Allura/allura/tests/unit/test_sitemapentry.py      |  2 -
 Allura/allura/tests/unit/test_solr.py              |  4 --
 AlluraTest/alluratest/controller.py                |  5 ---
 AlluraTest/alluratest/pytest_helpers.py            | 49 ----------------------
 AlluraTest/alluratest/test_syntax.py               |  2 +-
 69 files changed, 1 insertion(+), 318 deletions(-)

diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index 20fc7a554..11ac71129 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -30,7 +30,6 @@ import mock
 
 import allura
 from allura.tests import TestController
-from alluratest.pytest_helpers import with_nose_compatibility
 from allura.tests import decorators as td
 from allura.tests.decorators import audits
 from alluratest.controller import TestRestApiBase, setup_trove_categories
@@ -46,7 +45,6 @@ from forgewiki.wiki_main import ForgeWikiApp
 log = logging.getLogger(__name__)
 
 
-@with_nose_compatibility
 class TestProjectAdmin(TestController):
 
     def test_admin_controller(self):
@@ -959,7 +957,6 @@ class TestProjectAdmin(TestController):
         r.mustcontain('Neighborhood Invitation(s) for test')
 
 
-@with_nose_compatibility
 class TestExport(TestController):
 
     def setup_method(self, method):
@@ -1086,7 +1083,6 @@ class TestExport(TestController):
         assert 'Check All</label>' in r
 
 
-@with_nose_compatibility
 class TestRestExport(TestRestApiBase):
 
     @mock.patch('allura.model.project.MonQTask')
@@ -1154,7 +1150,6 @@ class TestRestExport(TestRestApiBase):
             ['tickets', 'discussion'], 'test.zip', send_email=False, with_attachments=False)
 
 
-@with_nose_compatibility
 class TestRestInstallTool(TestRestApiBase):
 
     def test_missing_mount_info(self):
@@ -1328,7 +1323,6 @@ class TestRestInstallTool(TestRestApiBase):
             get_labels() == ['t1', 'Admin', 'Search', 'Activity', 'A Subproject', 'ta', 'tb', 'tc'])
 
 
-@with_nose_compatibility
 class TestRestAdminOptions(TestRestApiBase):
     def test_no_mount_point(self):
         r = self.api_get('/rest/p/test/admin/admin_options/', status=400)
@@ -1344,7 +1338,6 @@ class TestRestAdminOptions(TestRestApiBase):
         assert r.json['options'] is not None
 
 
-@with_nose_compatibility
 class TestRestMountOrder(TestRestApiBase):
     def test_no_kw(self):
         r = self.api_post('/rest/p/test/admin/mount_order/', status=400)
@@ -1394,7 +1387,6 @@ class TestRestMountOrder(TestRestApiBase):
         assert b > a
 
 
-@with_nose_compatibility
 class TestRestToolGrouping(TestRestApiBase):
     def test_invalid_grouping_threshold(self):
         for invalid_value in ('100', 'asdf'):
@@ -1421,7 +1413,6 @@ class TestRestToolGrouping(TestRestApiBase):
         assert 'wiki' in [tool['mount_point'] for tool in result2.json['menu']]
 
 
-@with_nose_compatibility
 class TestInstallableTools(TestRestApiBase):
     def test_installable_tools_response(self):
         r = self.api_get('/rest/p/test/admin/installable_tools', status=200)
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index c016a39b1..c1030a3cb 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -38,7 +38,6 @@ from tg import tmpl_context as c, app_globals as g
 from allura.tests import TestController
 from allura.tests import decorators as td
 from allura.tests.decorators import audits, out_audits, assert_logmsg
-from alluratest.pytest_helpers import with_nose_compatibility
 from alluratest.controller import setup_trove_categories, TestRestApiBase, oauth1_webtest
 from allura import model as M
 from allura.model.oauth import dummy_oauths
@@ -51,7 +50,6 @@ def unentity(s):
     return s.replace('&quot;', '"').replace('&#34;', '"')
 
 
-@with_nose_compatibility
 class TestAuth(TestController):
     def test_login(self):
         self.app.get('/auth/')
@@ -1134,7 +1132,6 @@ class TestAuth(TestController):
         assert r.content_length != 777
 
 
-@with_nose_compatibility
 class TestAuthRest(TestRestApiBase):
 
     def test_tools_list_anon(self):
@@ -1174,7 +1171,6 @@ class TestAuthRest(TestRestApiBase):
         }
 
 
-@with_nose_compatibility
 class TestPreferences(TestController):
     @td.with_user_project('test-admin')
     def test_personal_data(self):
@@ -1558,7 +1554,6 @@ class TestPreferences(TestController):
             self.app.get('/auth/not_page', status=404)
 
 
-@with_nose_compatibility
 class TestPasswordReset(TestController):
     test_primary_email = 'testprimaryaddr@mail.com'
 
@@ -1810,7 +1805,6 @@ To update your password on %s, please visit the following URL:
         assert 'Log Out' in r, r
 
 
-@with_nose_compatibility
 class TestOAuth(TestController):
     def test_register_deregister_app(self):
         # register
@@ -2145,7 +2139,6 @@ class TestOAuthAccessToken(TestController):
         self.test_access_token_ok(signature_type='query')
 
 
-@with_nose_compatibility
 class TestDisableAccount(TestController):
     def test_not_authenticated(self):
         r = self.app.get(
@@ -2190,7 +2183,6 @@ class TestDisableAccount(TestController):
         assert user.disabled == True
 
 
-@with_nose_compatibility
 class TestPasswordExpire(TestController):
     def login(self, username='test-user', pwd='foo', query_string=''):
         extra = {'username': '*anonymous', 'REMOTE_ADDR': '127.0.0.1'}
@@ -2386,7 +2378,6 @@ class TestPasswordExpire(TestController):
             assert r.location == 'http://localhost/p/test/tickets/?milestone=1.0&page=2'
 
 
-@with_nose_compatibility
 class TestCSRFProtection(TestController):
     def test_blocks_invalid(self):
         # so test-admin isn't automatically logged in for all requests
@@ -2421,7 +2412,6 @@ class TestCSRFProtection(TestController):
         assert r.form['_session_id'].value
 
 
-@with_nose_compatibility
 class TestTwoFactor(TestController):
 
     sample_key = b'\x00K\xda\xbfv\xc2B\xaa\x1a\xbe\xa5\x96b\xb2\xa0Z:\xc9\xcf\x8a'
diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py
index 3c0ed26ef..8035fb93f 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -25,10 +25,8 @@ from allura.tests import TestController
 from allura import model as M
 from allura.lib import helpers as h
 from tg import config
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestDiscussBase(TestController):
 
     def _thread_link(self):
@@ -44,7 +42,6 @@ class TestDiscussBase(TestController):
         return thread_link.split('/')[-2]
 
 
-@with_nose_compatibility
 class TestDiscuss(TestDiscussBase):
 
     def _is_subscribed(self, user, thread):
@@ -399,7 +396,6 @@ class TestDiscuss(TestDiscussBase):
         r = self.app.get(post_link, status=404)
 
 
-@with_nose_compatibility
 class TestAttachment(TestDiscussBase):
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/functional/test_feeds.py b/Allura/allura/tests/functional/test_feeds.py
index e795f95a9..d3d2eaa1f 100644
--- a/Allura/allura/tests/functional/test_feeds.py
+++ b/Allura/allura/tests/functional/test_feeds.py
@@ -20,10 +20,8 @@ from formencode.variabledecode import variable_encode
 from allura.tests import TestController
 from allura.tests import decorators as td
 from allura.lib import helpers as h
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestFeeds(TestController):
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/functional/test_gravatar.py b/Allura/allura/tests/functional/test_gravatar.py
index b42f64a71..7f8364518 100644
--- a/Allura/allura/tests/functional/test_gravatar.py
+++ b/Allura/allura/tests/functional/test_gravatar.py
@@ -22,10 +22,8 @@ from mock import patch
 
 from allura.tests import TestController
 import allura.lib.gravatar as gravatar
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestGravatar(TestController):
 
     def test_id(self):
diff --git a/Allura/allura/tests/functional/test_home.py b/Allura/allura/tests/functional/test_home.py
index 81c11a16a..6b62fe97b 100644
--- a/Allura/allura/tests/functional/test_home.py
+++ b/Allura/allura/tests/functional/test_home.py
@@ -26,10 +26,8 @@ import allura
 from allura.tests import TestController
 from allura.tests import decorators as td
 from allura import model as M
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestProjectHome(TestController):
 
     @td.with_wiki
diff --git a/Allura/allura/tests/functional/test_nav.py b/Allura/allura/tests/functional/test_nav.py
index e7113cc77..376fad571 100644
--- a/Allura/allura/tests/functional/test_nav.py
+++ b/Allura/allura/tests/functional/test_nav.py
@@ -22,10 +22,8 @@ from tg import app_globals as g
 
 from allura.tests import TestController
 from allura.lib import helpers as h
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestNavigation(TestController):
     """
     Test div-logo and nav-left:
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index 7261e0ce0..636830de4 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -36,10 +36,8 @@ from allura.tests import decorators as td
 from allura.lib import helpers as h
 from allura.lib import utils
 from alluratest.controller import setup_trove_categories
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestNeighborhood(TestController):
 
     def setup_method(self, method):
@@ -978,7 +976,6 @@ class TestNeighborhood(TestController):
         self.app.get('/p/_nav.json')
 
 
-@with_nose_compatibility
 class TestPhoneVerificationOnProjectRegistration(TestController):
     def test_phone_verification_fragment_renders(self):
         self.app.get('/p/phone_verification_fragment', status=200)
@@ -1114,7 +1111,6 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
             assert iframe.get('src') == '/p/phone_verification_fragment'
 
 
-@with_nose_compatibility
 class TestProjectImport(TestController):
 
     def test_not_found(self):
diff --git a/Allura/allura/tests/functional/test_newforge.py b/Allura/allura/tests/functional/test_newforge.py
index 83470c1e4..9dfabe614 100644
--- a/Allura/allura/tests/functional/test_newforge.py
+++ b/Allura/allura/tests/functional/test_newforge.py
@@ -21,10 +21,8 @@ from six.moves.urllib.parse import quote
 from allura.tests import TestController
 from allura.tests import decorators as td
 from allura import model as M
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestNewForgeController(TestController):
 
     @td.with_wiki
diff --git a/Allura/allura/tests/functional/test_personal_dashboard.py b/Allura/allura/tests/functional/test_personal_dashboard.py
index 06c73dea7..8cdae92ab 100644
--- a/Allura/allura/tests/functional/test_personal_dashboard.py
+++ b/Allura/allura/tests/functional/test_personal_dashboard.py
@@ -29,10 +29,8 @@ from allura.tests import TestController
 from allura.tests import decorators as td
 from alluratest.controller import setup_global_objects, setup_unit_test
 from forgetracker.tests.functional.test_root import TrackerTestController
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestPersonalDashboard(TestController):
 
     def test_dashboard(self):
@@ -69,7 +67,6 @@ class TestPersonalDashboard(TestController):
                 assert 'Section f' not in r.text
 
 
-@with_nose_compatibility
 class TestTicketsSection(TrackerTestController):
 
     @td.with_tracker
@@ -85,7 +82,6 @@ class TestTicketsSection(TrackerTestController):
         assert 'foo' in str(ticket_rows)
 
 
-@with_nose_compatibility
 class TestMergeRequestsSection(TestController):
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index 26d3b5620..265cfcd12 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -30,10 +30,8 @@ from alluratest.controller import TestRestApiBase
 from allura.lib import helpers as h
 from allura.lib.exceptions import Invalid
 from allura import model as M
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestRestHome(TestRestApiBase):
 
     def _patch_token(self, OAuthAccessToken):
@@ -416,7 +414,6 @@ class TestRestHome(TestRestApiBase):
         assert r.json == {}
 
 
-@with_nose_compatibility
 class TestRestNbhdAddProject(TestRestApiBase):
 
     def setup_method(self, method):
@@ -559,7 +556,6 @@ class TestRestNbhdAddProject(TestRestApiBase):
         }
 
 
-@with_nose_compatibility
 class TestDoap(TestRestApiBase):
     validate_skip = True
     ns = '{http://usefulinc.com/ns/doap#}'
@@ -625,7 +621,6 @@ class TestDoap(TestRestApiBase):
         assert ('Tickets', 'http://localhost/p/test/private-bugs/') not in tools
 
 
-@with_nose_compatibility
 class TestUserProfile(TestRestApiBase):
     @td.with_user_project('test-admin')
     def test_profile_data(self):
diff --git a/Allura/allura/tests/functional/test_root.py b/Allura/allura/tests/functional/test_root.py
index ff196261e..6b5f56ca3 100644
--- a/Allura/allura/tests/functional/test_root.py
+++ b/Allura/allura/tests/functional/test_root.py
@@ -40,10 +40,8 @@ from allura.tests import TestController
 from allura import model as M
 from allura.lib import helpers as h
 from alluratest.controller import setup_trove_categories
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestRootController(TestController):
 
     def setup_method(self, method):
@@ -215,7 +213,6 @@ class TestRootController(TestController):
                in resp.headers.getall('Content-Security-Policy')[0]
 
 
-@with_nose_compatibility
 class TestRootWithSSLPattern(TestController):
     def setup_method(self, method):
         with td.patch_middleware_config({'force_ssl.pattern': '^/auth'}):
diff --git a/Allura/allura/tests/functional/test_search.py b/Allura/allura/tests/functional/test_search.py
index a24bc0497..3a1a72b44 100644
--- a/Allura/allura/tests/functional/test_search.py
+++ b/Allura/allura/tests/functional/test_search.py
@@ -24,10 +24,8 @@ from allura.tests import TestController
 from allura.tests.decorators import with_tool
 
 from forgewiki.model import Page
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestSearch(TestController):
 
     @patch('allura.lib.search.search')
diff --git a/Allura/allura/tests/functional/test_site_admin.py b/Allura/allura/tests/functional/test_site_admin.py
index 788fb4fed..f4cb04855 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -31,10 +31,8 @@ from allura.tests import decorators as td
 from allura.lib import helpers as h
 from allura.lib.decorators import task
 from allura.lib.plugin import LocalAuthenticationProvider
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestSiteAdmin(TestController):
 
     def test_access(self):
@@ -184,7 +182,6 @@ class TestSiteAdmin(TestController):
         assert json.loads(r.text)['doc'] == 'test_task doc string'
 
 
-@with_nose_compatibility
 class TestSiteAdminNotifications(TestController):
 
     def test_site_notifications_access(self):
@@ -338,7 +335,6 @@ class TestSiteAdminNotifications(TestController):
         assert M.notification.SiteNotification.query.get(_id=bson.ObjectId(note._id)) is None
 
 
-@with_nose_compatibility
 class TestProjectsSearch(TestController):
 
     TEST_HIT = MagicMock(hits=1, docs=[{
@@ -393,7 +389,6 @@ class TestProjectsSearch(TestController):
         assert ths == ['Short name', 'Full name', 'Registered', 'Deleted?', 'url', 'Details']
 
 
-@with_nose_compatibility
 class TestUsersSearch(TestController):
 
     TEST_HIT = MagicMock(hits=1, docs=[{
@@ -449,7 +444,6 @@ class TestUsersSearch(TestController):
                            'Status', 'url', 'Details']
 
 
-@with_nose_compatibility
 class TestUserDetails(TestController):
 
     def test_404(self):
@@ -745,7 +739,6 @@ To update your password on %s, please visit the following URL:
         assert hash in r.text
 
 
-@with_nose_compatibility
 class TestDeleteProjects(TestController):
 
     def confirm_form(self, r):
diff --git a/Allura/allura/tests/functional/test_static.py b/Allura/allura/tests/functional/test_static.py
index 38e4ba760..4d530a57a 100644
--- a/Allura/allura/tests/functional/test_static.py
+++ b/Allura/allura/tests/functional/test_static.py
@@ -17,10 +17,8 @@
 
 
 from allura.tests import TestController
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestStaticFilesMiddleware(TestController):
 
     # this tests StaticFilesMiddleware
diff --git a/Allura/allura/tests/functional/test_subscriber.py b/Allura/allura/tests/functional/test_subscriber.py
index 32333f92d..93b822e60 100644
--- a/Allura/allura/tests/functional/test_subscriber.py
+++ b/Allura/allura/tests/functional/test_subscriber.py
@@ -19,10 +19,8 @@ from allura.tests import TestController
 from allura.tests import decorators as td
 from allura.model.notification import Mailbox
 from allura import model as M
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestSubscriber(TestController):
 
     @td.with_user_project('test-admin')
diff --git a/Allura/allura/tests/functional/test_tool_list.py b/Allura/allura/tests/functional/test_tool_list.py
index 3562463b4..7f43bea5e 100644
--- a/Allura/allura/tests/functional/test_tool_list.py
+++ b/Allura/allura/tests/functional/test_tool_list.py
@@ -17,10 +17,8 @@
 
 from allura.tests import TestController
 from allura.tests import decorators as td
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestToolListController(TestController):
 
     @td.with_wiki
diff --git a/Allura/allura/tests/functional/test_trovecategory.py b/Allura/allura/tests/functional/test_trovecategory.py
index ae9057dd3..5067d586d 100644
--- a/Allura/allura/tests/functional/test_trovecategory.py
+++ b/Allura/allura/tests/functional/test_trovecategory.py
@@ -25,10 +25,8 @@ from allura.lib import helpers as h
 from allura.tests import TestController
 from alluratest.controller import setup_trove_categories
 from allura.tests import decorators as td
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestTroveCategory(TestController):
     @mock.patch('allura.model.project.g.post_event')
     def test_events(self, post_event):
@@ -82,7 +80,6 @@ class TestTroveCategory(TestController):
             check_access(username='root', status=200)
 
 
-@with_nose_compatibility
 class TestTroveCategoryController(TestController):
     def create_some_cats(self):
         root_parent = M.TroveCategory(fullname="Root", trove_cat_id=1, trove_parent_id=0)
diff --git a/Allura/allura/tests/functional/test_user_profile.py b/Allura/allura/tests/functional/test_user_profile.py
index 2a5e8674d..0b93bdd42 100644
--- a/Allura/allura/tests/functional/test_user_profile.py
+++ b/Allura/allura/tests/functional/test_user_profile.py
@@ -22,7 +22,6 @@ from alluratest.controller import TestRestApiBase
 from allura.model import Project, User
 from allura.tests import decorators as td
 from allura.tests import TestController
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 class TestUserProfileSections(TestController):
@@ -65,7 +64,6 @@ class TestUserProfileSections(TestController):
         assert 'Section f' not in r.text
 
 
-@with_nose_compatibility
 class TestUserProfile(TestController):
 
     @td.with_user_project('test-admin')
@@ -278,7 +276,6 @@ class TestUserProfile(TestController):
         assert 'content="noindex, follow"' not in r.text
 
 
-@with_nose_compatibility
 class TestUserProfileHasAccessAPI(TestRestApiBase):
 
     @td.with_user_project('test-admin')
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index 7b6364938..2e77f7486 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -34,7 +34,6 @@ from allura.lib import helpers as h
 from allura.lib import plugin
 from allura.tests import decorators as td
 from alluratest.controller import setup_basic_test, setup_global_objects, setup_functional_test, setup_unit_test
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 class TestAuth:
@@ -423,7 +422,6 @@ class TestAuth:
         assert details[1].ua == 'TestBrowser/57'
 
 
-@with_nose_compatibility
 class TestAuditLog:
 
     @classmethod
diff --git a/Allura/allura/tests/model/test_filesystem.py b/Allura/allura/tests/model/test_filesystem.py
index 5625df27d..70ccdedb4 100644
--- a/Allura/allura/tests/model/test_filesystem.py
+++ b/Allura/allura/tests/model/test_filesystem.py
@@ -27,7 +27,6 @@ from webob import Request, Response
 
 from allura import model as M
 from alluratest.controller import setup_unit_test
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 class File(M.File):
@@ -37,7 +36,6 @@ class File(M.File):
 Mapper.compile_all()
 
 
-@with_nose_compatibility
 class TestFile(TestCase):
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/model/test_notification.py b/Allura/allura/tests/model/test_notification.py
index 774c8dae7..3a2ba9cf8 100644
--- a/Allura/allura/tests/model/test_notification.py
+++ b/Allura/allura/tests/model/test_notification.py
@@ -30,10 +30,8 @@ from allura.model.notification import MailFooter
 from allura.lib import helpers as h
 from allura.tests import decorators as td
 from forgewiki import model as WM
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestNotification(unittest.TestCase):
 
     def setup_method(self, method):
@@ -165,7 +163,6 @@ class TestNotification(unittest.TestCase):
         )
 
 
-@with_nose_compatibility
 class TestPostNotifications(unittest.TestCase):
 
     def setup_method(self, method):
@@ -304,7 +301,6 @@ class TestPostNotifications(unittest.TestCase):
         return M.Notification.post(self.pg, 'metadata')
 
 
-@with_nose_compatibility
 class TestSubscriptionTypes(unittest.TestCase):
 
     def setup_method(self, method):
@@ -478,7 +474,6 @@ class TestSubscriptionTypes(unittest.TestCase):
         assert count == 1
 
 
-@with_nose_compatibility
 class TestSiteNotification(unittest.TestCase):
     def setup_method(self, method):
         self.note = M.SiteNotification(
diff --git a/Allura/allura/tests/model/test_repo.py b/Allura/allura/tests/model/test_repo.py
index b5fffdaa1..9b89a6805 100644
--- a/Allura/allura/tests/model/test_repo.py
+++ b/Allura/allura/tests/model/test_repo.py
@@ -28,10 +28,8 @@ from tg import config
 from alluratest.controller import setup_basic_test, setup_global_objects
 from allura import model as M
 from allura.lib import helpers as h
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestGitLikeTree:
     def test_set_blob(self):
         tree = M.GitLikeTree()
@@ -129,7 +127,6 @@ class RepoTestBase(unittest.TestCase):
         ]
 
 
-@with_nose_compatibility
 class TestLastCommit(unittest.TestCase):
     def setup_method(self, method):
         setup_basic_test()
@@ -402,7 +399,6 @@ class TestLastCommit(unittest.TestCase):
         self.assertEqual(lcd.by_name['file2'], commit3._id)
 
 
-@with_nose_compatibility
 class TestModelCache(unittest.TestCase):
     def setup_method(self, method):
         self.cache = M.repository.ModelCache()
@@ -681,7 +677,6 @@ class TestModelCache(unittest.TestCase):
         session.return_value.expunge.assert_called_once_with(tree1)
 
 
-@with_nose_compatibility
 class TestMergeRequest:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/model/test_timeline.py b/Allura/allura/tests/model/test_timeline.py
index f5af23bb1..ad1b252af 100644
--- a/Allura/allura/tests/model/test_timeline.py
+++ b/Allura/allura/tests/model/test_timeline.py
@@ -18,10 +18,8 @@
 from allura import model as M
 from allura.tests import decorators as td
 from alluratest.controller import setup_basic_test, setup_global_objects
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestActivityObject_Functional:
     # NOTE not for unit tests, this class sets up all the junk
 
diff --git a/Allura/allura/tests/scripts/test_create_sitemap_files.py b/Allura/allura/tests/scripts/test_create_sitemap_files.py
index 23af94dc3..04ec9d975 100644
--- a/Allura/allura/tests/scripts/test_create_sitemap_files.py
+++ b/Allura/allura/tests/scripts/test_create_sitemap_files.py
@@ -26,10 +26,8 @@ from alluratest.controller import setup_basic_test
 from allura import model as M
 from allura.lib import helpers as h
 from allura.scripts.create_sitemap_files import CreateSitemapFiles
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestCreateSitemapFiles:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/scripts/test_delete_projects.py b/Allura/allura/tests/scripts/test_delete_projects.py
index 12d6d69b5..0716cc700 100644
--- a/Allura/allura/tests/scripts/test_delete_projects.py
+++ b/Allura/allura/tests/scripts/test_delete_projects.py
@@ -24,10 +24,8 @@ from allura.tests.decorators import audits, out_audits, with_user_project
 from allura import model as M
 from allura.scripts import delete_projects
 from allura.lib import plugin
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestDeleteProjects(TestController):
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/scripts/test_misc_scripts.py b/Allura/allura/tests/scripts/test_misc_scripts.py
index 174bf9c52..2ec6c2583 100644
--- a/Allura/allura/tests/scripts/test_misc_scripts.py
+++ b/Allura/allura/tests/scripts/test_misc_scripts.py
@@ -21,10 +21,8 @@ from allura.scripts.clear_old_notifications import ClearOldNotifications
 from alluratest.controller import setup_basic_test
 from allura import model as M
 from ming.odm import session
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestClearOldNotifications:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/scripts/test_reindexes.py b/Allura/allura/tests/scripts/test_reindexes.py
index e1f995078..ff6339093 100644
--- a/Allura/allura/tests/scripts/test_reindexes.py
+++ b/Allura/allura/tests/scripts/test_reindexes.py
@@ -21,10 +21,8 @@ from allura.scripts.reindex_users import ReindexUsers
 from allura.tests.decorators import assert_logmsg_and_no_warnings_or_errors
 from alluratest.controller import setup_basic_test
 from allura import model as M
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestReindexProjects:
 
     def setup_method(self, method):
@@ -49,7 +47,6 @@ class TestReindexProjects:
         assert M.MonQTask.query.find({'task_name': 'allura.tasks.index_tasks.add_projects'}).count() == 1
 
 
-@with_nose_compatibility
 class TestReindexUsers:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/templates/jinja_master/test_lib.py b/Allura/allura/tests/templates/jinja_master/test_lib.py
index 84108aaa5..4f294e3c7 100644
--- a/Allura/allura/tests/templates/jinja_master/test_lib.py
+++ b/Allura/allura/tests/templates/jinja_master/test_lib.py
@@ -20,7 +20,6 @@ from mock import Mock
 
 from allura.config.app_cfg import AlluraJinjaRenderer
 from alluratest.controller import setup_basic_test
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 def strip_space(s):
@@ -33,7 +32,6 @@ class TemplateTest:
         self.jinja2_env = AlluraJinjaRenderer.create(config, g)['jinja'].jinja2_env
 
 
-@with_nose_compatibility
 class TestRelatedArtifacts(TemplateTest):
 
     def _render_related_artifacts(self, artifact):
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index 9e13f60cb..fadd8ad09 100644
--- a/Allura/allura/tests/test_commands.py
+++ b/Allura/allura/tests/test_commands.py
@@ -33,7 +33,6 @@ from allura.command import base, script, set_neighborhood_features, \
 from allura import model as M
 from allura.lib.exceptions import InvalidNBFeatureValueError
 from allura.tests import decorators as td
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 test_config = pkg_resources.resource_filename(
@@ -182,7 +181,6 @@ def test_update_neighborhood():
     assert nb.has_home_tool is False
 
 
-@with_nose_compatibility
 class TestEnsureIndexCommand:
 
     def test_run(self):
@@ -269,7 +267,6 @@ class TestEnsureIndexCommand:
         ]
 
 
-@with_nose_compatibility
 class TestTaskCommand:
 
     def teardown_method(self, method):
@@ -336,7 +333,6 @@ class TestTaskCommand:
         assert M.MonQTask.query.find().count() == 0
 
 
-@with_nose_compatibility
 class TestTaskdCleanupCommand:
 
     def setup_method(self, method):
@@ -451,7 +447,6 @@ def test_status_log_retries():
     assert cmd._taskd_status.mock_calls == expected_calls
 
 
-@with_nose_compatibility
 class TestShowModels:
 
     def test_show_models(self):
@@ -463,7 +458,6 @@ class TestShowModels:
          - <FieldProperty content>
         ''' in output.captured
 
-@with_nose_compatibility
 class TestReindexAsTask:
 
     cmd = 'allura.command.show_models.ReindexCommand'
@@ -498,7 +492,6 @@ class TestReindexAsTask:
             M.MonQTask.query.remove()
 
 
-@with_nose_compatibility
 class TestReindexCommand:
 
     @patch('allura.command.show_models.g')
diff --git a/Allura/allura/tests/test_decorators.py b/Allura/allura/tests/test_decorators.py
index 2ef77cd8c..80c3bd46e 100644
--- a/Allura/allura/tests/test_decorators.py
+++ b/Allura/allura/tests/test_decorators.py
@@ -20,12 +20,10 @@ from mock import patch
 import random
 import gc
 
-from alluratest.pytest_helpers import with_nose_compatibility
 from allura.lib.decorators import task, memoize
 from alluratest.controller import setup_basic_test, setup_global_objects
 
 
-@with_nose_compatibility
 class TestTask(TestCase):
 
     def setup_method(self, method):
@@ -63,7 +61,6 @@ class TestTask(TestCase):
         func.post('test', foo=2, delay=1)
 
 
-@with_nose_compatibility
 class TestMemoize:
 
     def test_function(self):
diff --git a/Allura/allura/tests/test_diff.py b/Allura/allura/tests/test_diff.py
index 1485bc1a8..e70b7d6d0 100644
--- a/Allura/allura/tests/test_diff.py
+++ b/Allura/allura/tests/test_diff.py
@@ -18,10 +18,8 @@
 import unittest
 
 from allura.lib.diff import HtmlSideBySideDiff
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestHtmlSideBySideDiff(unittest.TestCase):
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/test_dispatch.py b/Allura/allura/tests/test_dispatch.py
index 983ffac72..5e48ec2fd 100644
--- a/Allura/allura/tests/test_dispatch.py
+++ b/Allura/allura/tests/test_dispatch.py
@@ -16,12 +16,10 @@
 #       under the License.
 
 from allura.tests import TestController
-from alluratest.pytest_helpers import with_nose_compatibility
 
 app = None
 
 
-@with_nose_compatibility
 class TestDispatch(TestController):
 
     validate_skip = True
diff --git a/Allura/allura/tests/test_globals.py b/Allura/allura/tests/test_globals.py
index 29b318027..01a48daf1 100644
--- a/Allura/allura/tests/test_globals.py
+++ b/Allura/allura/tests/test_globals.py
@@ -43,7 +43,6 @@ from allura import model as M
 from allura.lib import helpers as h
 from allura.lib.app_globals import ForgeMarkdown
 from allura.tests import decorators as td
-from alluratest.pytest_helpers import with_nose_compatibility
 
 from forgewiki import model as WM
 from forgeblog import model as BM
@@ -77,7 +76,6 @@ def get_projects_property_in_the_same_order(names, prop):
     return [projects_dict[name] for name in names]
 
 
-@with_nose_compatibility
 class Test():
 
     @classmethod
@@ -787,7 +785,6 @@ class Test():
             assert 'src="/p/test/screenshot/test_file.jpg/thumb"' in r
 
 
-@with_nose_compatibility
 class TestCachedMarkdown(unittest.TestCase):
 
     def setup_method(self, method):
@@ -905,7 +902,6 @@ class TestCachedMarkdown(unittest.TestCase):
         self.assertEqual(required_keys, keys)
 
 
-@with_nose_compatibility
 class TestEmojis(unittest.TestCase):
 
     def test_markdown_emoji_atomic(self):
@@ -941,7 +937,6 @@ class TestEmojis(unittest.TestCase):
         assert 'More emojis \U0001F44D\U0001F42B\U0001F552 wow!' in output
 
 
-@with_nose_compatibility
 class TestUserMentions(unittest.TestCase):
 
     def test_markdown_user_mention_default(self):
@@ -983,7 +978,6 @@ class TestUserMentions(unittest.TestCase):
         assert 'class="user-mention"' in output
 
 
-@with_nose_compatibility
 class TestHandlePaging(unittest.TestCase):
 
     def setup_method(self, method):
@@ -1044,7 +1038,6 @@ class TestHandlePaging(unittest.TestCase):
         self.assertEqual(g.handle_paging(10, 'asdf', 30), (10, 0, 0))
 
 
-@with_nose_compatibility
 class TestIconRender:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 46703b142..571d33fea 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -37,7 +37,6 @@ from allura.lib.search import inject_user
 from allura.lib.security import has_access
 from allura.lib.security import Credentials
 from allura.tests import decorators as td
-from alluratest.pytest_helpers import with_nose_compatibility
 from alluratest.controller import setup_basic_test
 import six
 
@@ -47,7 +46,6 @@ def setup_module():
     setup_basic_test()
 
 
-@with_nose_compatibility
 class TestMakeSafePathPortion(TestCase):
 
     def setup_method(self, method):
@@ -454,7 +452,6 @@ back\\\-slash escaped
         r'tab before \(stuff\)'
 
 
-@with_nose_compatibility
 class TestUrlOpen(TestCase):
 
     @patch('six.moves.urllib.request.urlopen')
@@ -531,7 +528,6 @@ def test_login_overlay():
             raise HTTPUnauthorized()
 
 
-@with_nose_compatibility
 class TestIterEntryPoints(TestCase):
 
     def _make_ep(self, name, cls):
@@ -636,7 +632,6 @@ def test_slugify():
     assert h.slugify('Foo.Bar', True)[0] == 'Foo.Bar'
 
 
-@with_nose_compatibility
 class TestRateLimit(TestCase):
     rate_limits = '{"60": 1, "120": 3, "900": 5, "1800": 7, "3600": 10, "7200": 15, "86400": 20, "604800": 50, "2592000": 200}'
     key_comment = 'allura.rate_limits_per_user'
diff --git a/Allura/allura/tests/test_mail_util.py b/Allura/allura/tests/test_mail_util.py
index acc4c22dd..872117cf5 100644
--- a/Allura/allura/tests/test_mail_util.py
+++ b/Allura/allura/tests/test_mail_util.py
@@ -37,7 +37,6 @@ from allura.lib.mail_util import (
     _parse_message_id,
 )
 from allura.lib.exceptions import AddressException
-from alluratest.pytest_helpers import with_nose_compatibility
 from allura.tests import decorators as td
 
 
@@ -46,7 +45,6 @@ config = ConfigProxy(
     return_path='forgemail.return_path')
 
 
-@with_nose_compatibility
 class TestReactor(unittest.TestCase):
 
     def setup_method(self, method):
@@ -211,7 +209,6 @@ Content-Type: text/html; charset="utf-8"
             assert isinstance(part['payload'], str), type(part['payload'])
 
 
-@with_nose_compatibility
 class TestHeader:
 
     def test_bytestring(self):
@@ -233,7 +230,6 @@ class TestHeader:
                      '=?utf-8?b?ItGC0LXRgdC90Y/RgtGB0Y8i?= <da...@b.com>')
 
 
-@with_nose_compatibility
 class TestIsAutoreply:
 
     def setup_method(self, method):
@@ -280,7 +276,6 @@ class TestIsAutoreply:
         assert is_autoreply(self.msg)
 
 
-@with_nose_compatibility
 class TestIdentifySender:
 
     @mock.patch('allura.model.EmailAddress')
@@ -330,7 +325,6 @@ def test_parse_message_id():
     ]
 
 
-@with_nose_compatibility
 class TestMailServer:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/test_markdown.py b/Allura/allura/tests/test_markdown.py
index 6878143f1..992eec8b0 100644
--- a/Allura/allura/tests/test_markdown.py
+++ b/Allura/allura/tests/test_markdown.py
@@ -19,10 +19,8 @@ import unittest
 import mock
 
 from allura.lib import markdown_extensions as mde
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestTracRef1(unittest.TestCase):
 
     @mock.patch('allura.lib.markdown_extensions.M.Shortlink.lookup')
@@ -49,7 +47,6 @@ class TestTracRef1(unittest.TestCase):
                          '[r123](/p/project/tool/artifact)')
 
 
-@with_nose_compatibility
 class TestTracRef2(unittest.TestCase):
 
     @mock.patch('allura.lib.markdown_extensions.M.Shortlink.lookup')
@@ -79,7 +76,6 @@ class TestTracRef2(unittest.TestCase):
                          '[comment:13:ticket:100](/p/project/tool/artifact/)')
 
 
-@with_nose_compatibility
 class TestTracRef3(unittest.TestCase):
 
     def test_no_app_context(self):
@@ -98,7 +94,6 @@ class TestTracRef3(unittest.TestCase):
                          '[source:file.py#L456](/p/project/tool/HEAD/tree/file.py#l456)')
 
 
-@with_nose_compatibility
 class TestPatternReplacingProcessor(unittest.TestCase):
 
     @mock.patch('allura.lib.markdown_extensions.M.Shortlink.lookup')
@@ -113,7 +108,6 @@ class TestPatternReplacingProcessor(unittest.TestCase):
             '[ticket:100](/p/project/tool/artifact)'])
 
 
-@with_nose_compatibility
 class TestCommitMessageExtension(unittest.TestCase):
 
     @mock.patch('allura.lib.markdown_extensions.TracRef2.get_comment_slug')
diff --git a/Allura/allura/tests/test_middlewares.py b/Allura/allura/tests/test_middlewares.py
index 2435ba8d3..5a2e5c1b2 100644
--- a/Allura/allura/tests/test_middlewares.py
+++ b/Allura/allura/tests/test_middlewares.py
@@ -17,10 +17,8 @@
 
 from mock import MagicMock, patch
 from allura.lib.custom_middleware import CORSMiddleware
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestCORSMiddleware:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/test_multifactor.py b/Allura/allura/tests/test_multifactor.py
index 3ce6fdb31..42b8c6ce9 100644
--- a/Allura/allura/tests/test_multifactor.py
+++ b/Allura/allura/tests/test_multifactor.py
@@ -32,10 +32,8 @@ from allura.lib.multifactor import GoogleAuthenticatorFile, TotpService, Mongodb
 from allura.lib.multifactor import GoogleAuthenticatorPamFilesystemTotpService
 from allura.lib.multifactor import RecoveryCodeService, MongodbRecoveryCodeService
 from allura.lib.multifactor import GoogleAuthenticatorPamFilesystemRecoveryCodeService
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestGoogleAuthenticatorFile:
     sample = textwrap.dedent('''\
         7CL3WL756ISQCU5HRVNAODC44Q
@@ -82,7 +80,6 @@ class GenericTotpService(TotpService):
         pass
 
 
-@with_nose_compatibility
 class TestTotpService:
 
     sample_key = b'\x00K\xda\xbfv\xc2B\xaa\x1a\xbe\xa5\x96b\xb2\xa0Z:\xc9\xcf\x8a'
@@ -127,7 +124,6 @@ class TestTotpService:
         assert srv.get_qr_code(totp, user)
 
 
-@with_nose_compatibility
 class TestAnyTotpServiceImplementation:
 
     __test__ = False
@@ -175,7 +171,6 @@ class TestAnyTotpServiceImplementation:
             srv.verify(totp, '283397', user)
 
 
-@with_nose_compatibility
 class TestMongodbTotpService(TestAnyTotpServiceImplementation):
 
     __test__ = True
@@ -188,7 +183,6 @@ class TestMongodbTotpService(TestAnyTotpServiceImplementation):
         ming.configure(**config)
 
 
-@with_nose_compatibility
 class TestGoogleAuthenticatorPamFilesystemMixin:
 
     def setup_method(self, method):
@@ -200,7 +194,6 @@ class TestGoogleAuthenticatorPamFilesystemMixin:
             shutil.rmtree(self.totp_basedir)
 
 
-@with_nose_compatibility
 class TestGoogleAuthenticatorPamFilesystemTotpService(TestAnyTotpServiceImplementation,
                                                       TestGoogleAuthenticatorPamFilesystemMixin):
 
@@ -214,7 +207,6 @@ class TestGoogleAuthenticatorPamFilesystemTotpService(TestAnyTotpServiceImplemen
         super().test_rate_limiting()
 
 
-@with_nose_compatibility
 class TestRecoveryCodeService:
 
     def test_generate_one_code(self):
@@ -237,7 +229,6 @@ class TestRecoveryCodeService:
         assert len(recovery.saved_codes) == asint(config.get('auth.multifactor.recovery_code.count', 10))
 
 
-@with_nose_compatibility
 class TestAnyRecoveryCodeServiceImplementation:
 
     __test__ = False
@@ -305,7 +296,6 @@ class TestAnyRecoveryCodeServiceImplementation:
             recovery.verify_and_remove_code(user, '22222')
 
 
-@with_nose_compatibility
 class TestMongodbRecoveryCodeService(TestAnyRecoveryCodeServiceImplementation):
 
     __test__ = True
@@ -319,7 +309,6 @@ class TestMongodbRecoveryCodeService(TestAnyRecoveryCodeServiceImplementation):
         ming.configure(**config)
 
 
-@with_nose_compatibility
 class TestGoogleAuthenticatorPamFilesystemRecoveryCodeService(TestAnyRecoveryCodeServiceImplementation,
                                                               TestGoogleAuthenticatorPamFilesystemMixin):
 
diff --git a/Allura/allura/tests/test_plugin.py b/Allura/allura/tests/test_plugin.py
index 49206ba64..c6c78fc77 100644
--- a/Allura/allura/tests/test_plugin.py
+++ b/Allura/allura/tests/test_plugin.py
@@ -37,14 +37,12 @@ from allura.lib.exceptions import ProjectConflict, ProjectShortnameInvalid
 from allura.tests.decorators import audits
 from allura.tests.exclude_from_rewrite_hook import ThemeProviderTestApp
 from alluratest.controller import setup_basic_test, setup_global_objects
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 def setup_module(module):
     setup_basic_test()
 
 
-@with_nose_compatibility
 class TestProjectRegistrationProvider:
 
     def setup_method(self, method):
@@ -82,7 +80,6 @@ class TestProjectRegistrationProvider:
         pytest.raises(ProjectConflict, v, 'thisislegit', neighborhood=nbhd)
 
 
-@with_nose_compatibility
 class TestProjectRegistrationProviderParseProjectFromUrl:
 
     def setup_method(self, method):
@@ -157,7 +154,6 @@ class UserMock:
         return self._projects
 
 
-@with_nose_compatibility
 class TestProjectRegistrationProviderPhoneVerification:
 
     def setup_method(self, method):
@@ -275,7 +271,6 @@ class TestProjectRegistrationProviderPhoneVerification:
             assert 5 == g.phone_service.verify.call_count
 
 
-@with_nose_compatibility
 class TestThemeProvider:
 
     @patch('allura.app.g')
@@ -301,7 +296,6 @@ class TestThemeProvider:
         g.theme_href.assert_called_with('images/testapp_24.png')
 
 
-@with_nose_compatibility
 class TestThemeProvider_notifications:
 
     Provider = ThemeProvider
@@ -623,7 +617,6 @@ class TestThemeProvider_notifications:
         assert get_note[1] == 'testid-2-False'
 
 
-@with_nose_compatibility
 class TestLocalAuthenticationProvider:
 
     def setup_method(self, method):
@@ -738,7 +731,6 @@ class TestLocalAuthenticationProvider:
         assert detail.ua == 'mybrowser'
 
 
-@with_nose_compatibility
 class TestAuthenticationProvider:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/test_scripttask.py b/Allura/allura/tests/test_scripttask.py
index ae284a1e9..9992498a5 100644
--- a/Allura/allura/tests/test_scripttask.py
+++ b/Allura/allura/tests/test_scripttask.py
@@ -19,10 +19,8 @@ import unittest
 import mock
 
 from allura.scripts.scripttask import ScriptTask
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestScriptTask(unittest.TestCase):
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/test_security.py b/Allura/allura/tests/test_security.py
index fea9bc54d..7a27b9302 100644
--- a/Allura/allura/tests/test_security.py
+++ b/Allura/allura/tests/test_security.py
@@ -28,7 +28,6 @@ from forgewiki import model as WM
 from allura.lib.security import HIBPClientError, HIBPClient
 from mock import Mock, patch
 from requests.exceptions import Timeout
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 def _allow(obj, role, perm):
@@ -55,7 +54,6 @@ def test_check_breached_password(r_get):
         HIBPClient.check_breached_password('qwerty')
 
 
-@with_nose_compatibility
 class TestSecurity(TestController):
 
     validate_skip = True
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index 80fffbb8c..3acd939b9 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -47,12 +47,10 @@ from allura.tasks import repo_tasks
 from allura.tasks import export_tasks
 from allura.tasks import admin_tasks
 from allura.tests import decorators as td
-from alluratest.pytest_helpers import with_nose_compatibility
 from allura.tests.exclude_from_rewrite_hook import raise_compound_exception
 from allura.lib.decorators import event_handler, task
 
 
-@with_nose_compatibility
 class TestRepoTasks(unittest.TestCase):
 
     def setup_method(self, method):
@@ -101,7 +99,6 @@ def _task_that_creates_event(event_name,):
     assert not M.MonQTask.query.get(task_name='allura.tasks.event_tasks.event', args=[event_name])
 
 
-@with_nose_compatibility
 class TestEventTasks(unittest.TestCase):
 
     def setup_method(self, method):
@@ -160,7 +157,6 @@ class TestEventTasks(unittest.TestCase):
             assert ('assert %d' % x) in t.result
 
 
-@with_nose_compatibility
 class TestIndexTasks(unittest.TestCase):
 
     def setup_method(self, method):
@@ -246,7 +242,6 @@ class TestIndexTasks(unittest.TestCase):
         solr.delete.assert_called_once_with(q=solr_query)
 
 
-@with_nose_compatibility
 class TestMailTasks(unittest.TestCase):
 
     def setup_method(self, method):
@@ -543,7 +538,6 @@ class TestMailTasks(unittest.TestCase):
             assert hm.call_count == 0
 
 
-@with_nose_compatibility
 class TestUserNotificationTasks(TestController):
     def setup_method(self, method):
         super().setup_method(method)
@@ -575,7 +569,6 @@ class TestUserNotificationTasks(TestController):
         assert 'auth/subscriptions#notifications' in text
 
 
-@with_nose_compatibility
 class TestNotificationTasks(unittest.TestCase):
 
     def setup_method(self, method):
@@ -611,7 +604,6 @@ class _TestArtifact(M.Artifact):
             text=self.text)
 
 
-@with_nose_compatibility
 class TestExportTasks(unittest.TestCase):
 
     def setup_method(self, method):
@@ -666,7 +658,6 @@ class TestExportTasks(unittest.TestCase):
         assert c.project.bulk_export_status() == 'busy'
 
 
-@with_nose_compatibility
 class TestAdminTasks(unittest.TestCase):
 
     def test_install_app_docstring(self):
diff --git a/Allura/allura/tests/test_utils.py b/Allura/allura/tests/test_utils.py
index 5afc2c9b8..d9f5229a9 100644
--- a/Allura/allura/tests/test_utils.py
+++ b/Allura/allura/tests/test_utils.py
@@ -39,11 +39,9 @@ from alluratest.controller import setup_unit_test
 from allura import model as M
 from allura.lib import utils
 from allura.lib import helpers as h
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @patch.dict('allura.lib.utils.tg.config', clear=True, foo='bar', baz='true')
-@with_nose_compatibility
 class TestConfigProxy(unittest.TestCase):
 
     def setup_method(self, method):
@@ -64,7 +62,6 @@ class TestConfigProxy(unittest.TestCase):
         self.assertEqual(self.cp.get_bool("fake"), False)
 
 
-@with_nose_compatibility
 class TestChunkedIterator(unittest.TestCase):
 
     def setup_method(self, method):
@@ -96,7 +93,6 @@ class TestChunkedIterator(unittest.TestCase):
         assert chunks[1][0].username == 'sample-user-3'
 
 
-@with_nose_compatibility
 class TestChunkedList(unittest.TestCase):
 
     def test_chunked_list(self):
@@ -107,7 +103,6 @@ class TestChunkedList(unittest.TestCase):
         self.assertEqual([el for sublist in chunks for el in sublist], l)
 
 
-@with_nose_compatibility
 class TestAntispam(unittest.TestCase):
 
     def setup_method(self, method):
@@ -174,7 +169,6 @@ class TestAntispam(unittest.TestCase):
         return encrypted_form
 
 
-@with_nose_compatibility
 class TestTruthyCallable(unittest.TestCase):
 
     def test_everything(self):
@@ -196,7 +190,6 @@ class TestTruthyCallable(unittest.TestCase):
         assert false_predicate == f
 
 
-@with_nose_compatibility
 class TestCaseInsensitiveDict(unittest.TestCase):
 
     def test_everything(self):
@@ -216,7 +209,6 @@ class TestCaseInsensitiveDict(unittest.TestCase):
         assert d == utils.CaseInsensitiveDict(Foo=1, bar=2)
 
 
-@with_nose_compatibility
 class TestLineAnchorCodeHtmlFormatter(unittest.TestCase):
 
     def test_render(self):
@@ -237,7 +229,6 @@ class TestLineAnchorCodeHtmlFormatter(unittest.TestCase):
             assert '<span class="linenos">1</span>' in hl_code
 
 
-@with_nose_compatibility
 class TestIsTextFile(unittest.TestCase):
 
     def test_is_text_file(self):
@@ -248,7 +239,6 @@ class TestIsTextFile(unittest.TestCase):
         assert not utils.is_text_file(open(bin_file, 'rb').read())
 
 
-@with_nose_compatibility
 class TestCodeStats(unittest.TestCase):
 
     def setup_method(self, method):
@@ -273,7 +263,6 @@ class TestCodeStats(unittest.TestCase):
         assert stats['code_size'] == len(blob.text)
 
 
-@with_nose_compatibility
 class TestHTMLSanitizer(unittest.TestCase):
 
     def walker_from_text(self, text):
diff --git a/Allura/allura/tests/test_validators.py b/Allura/allura/tests/test_validators.py
index a9c011368..fb3dbb008 100644
--- a/Allura/allura/tests/test_validators.py
+++ b/Allura/allura/tests/test_validators.py
@@ -24,7 +24,6 @@ from allura.lib import validators as v
 from allura.lib.decorators import task
 from alluratest.controller import setup_basic_test
 from allura.websetup.bootstrap import create_user
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 def _setup_method():
@@ -36,7 +35,6 @@ def dummy_task(*args, **kw):
     pass
 
 
-@with_nose_compatibility
 class TestJsonConverter(unittest.TestCase):
     val = v.JsonConverter
 
@@ -53,7 +51,6 @@ class TestJsonConverter(unittest.TestCase):
             self.val.to_python('3')
 
 
-@with_nose_compatibility
 class TestJsonFile(unittest.TestCase):
 
     def setup_method(self, method):
@@ -74,7 +71,6 @@ class TestJsonFile(unittest.TestCase):
             self.val.to_python(self.FieldStorage('{'))
 
 
-@with_nose_compatibility
 class TestUserMapFile(unittest.TestCase):
     val = v.UserMapJsonFile()
 
@@ -100,7 +96,6 @@ class TestUserMapFile(unittest.TestCase):
             self.FieldStorage('{"user_old": "user_new"}')))
 
 
-@with_nose_compatibility
 class TestUserValidator(unittest.TestCase):
     val = v.UserValidator
 
@@ -117,7 +112,6 @@ class TestUserValidator(unittest.TestCase):
         self.assertEqual(str(cm.exception), "Invalid username")
 
 
-@with_nose_compatibility
 class TestAnonymousValidator(unittest.TestCase):
     val = v.AnonymousValidator
 
@@ -137,7 +131,6 @@ class TestAnonymousValidator(unittest.TestCase):
         self.assertEqual(str(cm.exception), "Log in to Mark as Private")
 
 
-@with_nose_compatibility
 class TestMountPointValidator(unittest.TestCase):
 
     def setup_method(self, method):
@@ -201,7 +194,6 @@ class TestMountPointValidator(unittest.TestCase):
         self.assertEqual('wiki-0', val.to_python(None))
 
 
-@with_nose_compatibility
 class TestTaskValidator(unittest.TestCase):
     val = v.TaskValidator
 
@@ -235,7 +227,6 @@ class TestTaskValidator(unittest.TestCase):
                          '"allura.tests.test_validators._setup_method" is not a task.')
 
 
-@with_nose_compatibility
 class TestPathValidator(unittest.TestCase):
     val = v.PathValidator(strip=True, if_missing={}, if_empty={})
 
@@ -287,7 +278,6 @@ class TestPathValidator(unittest.TestCase):
         self.assertEqual({}, self.val.to_python(''))
 
 
-@with_nose_compatibility
 class TestUrlValidator(unittest.TestCase):
     val = v.URL
 
@@ -309,7 +299,6 @@ class TestUrlValidator(unittest.TestCase):
         self.assertEqual(str(cm.exception), 'That is not a valid URL')
 
 
-@with_nose_compatibility
 class TestNonHttpUrlValidator(unittest.TestCase):
     val = v.NonHttpUrl
 
@@ -331,7 +320,6 @@ class TestNonHttpUrlValidator(unittest.TestCase):
         self.assertEqual(str(cm.exception), 'You must start your URL with a scheme')
 
 
-@with_nose_compatibility
 class TestIconValidator(unittest.TestCase):
     val = v.IconValidator
 
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index 4494d9a6b..d0785343a 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -43,7 +43,6 @@ from alluratest.controller import (
     TestRestApiBase,
 )
 import six
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 # important to be distinct from 'test' and 'test2' which ForgeGit and
@@ -54,7 +53,6 @@ with_git = td.with_tool(test_project_with_repo, 'git', 'src', 'Git')
 with_git2 = td.with_tool(test_project_with_repo, 'git', 'src2', 'Git2')
 
 
-@with_nose_compatibility
 class TestWebhookBase:
     def setup_method(self, method):
         setup_basic_test()
@@ -86,7 +84,6 @@ class TestWebhookBase:
         return [repo_init]
 
 
-@with_nose_compatibility
 class TestValidators(TestWebhookBase):
     @with_git2
     def test_webhook_validator(self):
@@ -124,7 +121,6 @@ class TestValidators(TestWebhookBase):
         assert v.to_python(str(wh._id)) == wh
 
 
-@with_nose_compatibility
 class TestWebhookController(TestController):
 
     def setup_method(self, method):
@@ -417,7 +413,6 @@ class TestWebhookController(TestController):
         return [text(tds[0]), text(tds[1]), link(tds[2]), delete_btn(tds[3])]
 
 
-@with_nose_compatibility
 class TestSendWebhookHelper(TestWebhookBase):
     def setup_method(self, method):
         super().setup_method(method)
@@ -522,7 +517,6 @@ class TestSendWebhookHelper(TestWebhookBase):
                     requests.post.return_value.headers))
 
 
-@with_nose_compatibility
 class TestRepoPushWebhookSender(TestWebhookBase):
     @patch('allura.webhooks.send_webhook', autospec=True)
     def test_send(self, send_webhook):
@@ -629,7 +623,6 @@ class TestRepoPushWebhookSender(TestWebhookBase):
         assert sender._convert_id('a433fa9:13') == 'r13'
 
 
-@with_nose_compatibility
 class TestModels(TestWebhookBase):
     def test_webhook_url(self):
         assert (self.wh.url() ==
@@ -671,7 +664,6 @@ class TestModels(TestWebhookBase):
         assert self.wh.__json__() == expected
 
 
-@with_nose_compatibility
 class TestWebhookRestController(TestRestApiBase):
     def setup_method(self, method):
         super().setup_method(method)
diff --git a/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py b/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
index 133301f23..f689fa5f7 100644
--- a/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
+++ b/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
@@ -23,10 +23,8 @@ from allura.tests.unit.factories import create_post, create_discussion
 from allura import model
 from allura.controllers.discuss import ModerationController
 from allura.tests.unit import patches
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestWhenModerating(WithDatabase):
     patches = [patches.fake_app_patch,
                patches.fake_user_patch,
@@ -74,7 +72,6 @@ class TestWhenModerating(WithDatabase):
         return model.Post.query.get(slug='mypost', deleted=False)
 
 
-@with_nose_compatibility
 class TestIndexWithNoPosts(WithDatabase):
     patches = [patches.fake_app_patch]
 
@@ -84,7 +81,6 @@ class TestIndexWithNoPosts(WithDatabase):
         assert template_variables['posts'].all() == []
 
 
-@with_nose_compatibility
 class TestIndexWithAPostInTheDiscussion(WithDatabase):
     patches = [patches.fake_app_patch]
 
diff --git a/Allura/allura/tests/unit/phone/test_nexmo.py b/Allura/allura/tests/unit/phone/test_nexmo.py
index 1b6139fa8..f2e99dacd 100644
--- a/Allura/allura/tests/unit/phone/test_nexmo.py
+++ b/Allura/allura/tests/unit/phone/test_nexmo.py
@@ -17,12 +17,10 @@
 
 import json
 from mock import patch
-from alluratest.pytest_helpers import with_nose_compatibility
 
 from allura.lib.phone.nexmo import NexmoPhoneService
 
 
-@with_nose_compatibility
 class TestPhoneService:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/unit/phone/test_phone_service.py b/Allura/allura/tests/unit/phone/test_phone_service.py
index 739594475..faceb374c 100644
--- a/Allura/allura/tests/unit/phone/test_phone_service.py
+++ b/Allura/allura/tests/unit/phone/test_phone_service.py
@@ -16,7 +16,6 @@
 #       under the License.
 
 from allura.lib.phone import PhoneService
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 class MockPhoneService(PhoneService):
@@ -28,7 +27,6 @@ class MockPhoneService(PhoneService):
         return {'status': 'ok'}
 
 
-@with_nose_compatibility
 class TestPhoneService:
 
     def test_verify(self):
diff --git a/Allura/allura/tests/unit/spam/test_akismet.py b/Allura/allura/tests/unit/spam/test_akismet.py
index 384ce1b32..268545dd9 100644
--- a/Allura/allura/tests/unit/spam/test_akismet.py
+++ b/Allura/allura/tests/unit/spam/test_akismet.py
@@ -26,11 +26,9 @@ from datetime import datetime
 from bson import ObjectId
 
 from allura.lib.spam.akismetfilter import AKISMET_AVAILABLE, AkismetSpamFilter
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @unittest.skipIf(not AKISMET_AVAILABLE, "Akismet not available")
-@with_nose_compatibility
 class TestAkismet(unittest.TestCase):
 
     @mock.patch('allura.lib.spam.akismetfilter.akismet')
diff --git a/Allura/allura/tests/unit/spam/test_spam_filter.py b/Allura/allura/tests/unit/spam/test_spam_filter.py
index 93b01b984..ad32d690a 100644
--- a/Allura/allura/tests/unit/spam/test_spam_filter.py
+++ b/Allura/allura/tests/unit/spam/test_spam_filter.py
@@ -25,7 +25,6 @@ from allura import model as M
 from allura.model.artifact import SpamCheckResult
 from alluratest.controller import setup_basic_test
 from forgewiki import model as WM
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 class MockFilter(SpamFilter):
@@ -46,7 +45,6 @@ class MockFilter2(SpamFilter):
         return True
 
 
-@with_nose_compatibility
 class TestSpamFilter(unittest.TestCase):
 
     def test_check(self):
@@ -75,7 +73,6 @@ class TestSpamFilter(unittest.TestCase):
         self.assertTrue(log.exception.called)
 
 
-@with_nose_compatibility
 class TestSpamFilterFunctional:
 
     def setup_method(self, method):
@@ -95,7 +92,6 @@ class TestSpamFilterFunctional:
         assert results[0].user.username == 'test-user'
 
 
-@with_nose_compatibility
 class TestChainedSpamFilter:
 
     def test(self):
diff --git a/Allura/allura/tests/unit/spam/test_stopforumspam.py b/Allura/allura/tests/unit/spam/test_stopforumspam.py
index 296f63a2c..bb827183e 100644
--- a/Allura/allura/tests/unit/spam/test_stopforumspam.py
+++ b/Allura/allura/tests/unit/spam/test_stopforumspam.py
@@ -21,10 +21,8 @@ import mock
 from bson import ObjectId
 
 from allura.lib.spam.stopforumspamfilter import StopForumSpamSpamFilter
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestStopForumSpam:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/unit/test_app.py b/Allura/allura/tests/unit/test_app.py
index 65d000fae..e7d0fbd79 100644
--- a/Allura/allura/tests/unit/test_app.py
+++ b/Allura/allura/tests/unit/test_app.py
@@ -22,10 +22,8 @@ from allura import model
 from allura.tests.unit import WithDatabase
 from allura.tests.unit.patches import fake_app_patch
 from allura.tests.unit.factories import create_project, create_app_config
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestApplication(TestCase):
 
     def test_validate_mount_point(self):
@@ -54,7 +52,6 @@ class TestApplication(TestCase):
         self.assertEqual(f('does_not_exist'), '')
 
 
-@with_nose_compatibility
 class TestInstall(WithDatabase):
     patches = [fake_app_patch]
 
@@ -67,7 +64,6 @@ class TestInstall(WithDatabase):
         return model.Discussion.query.find().count()
 
 
-@with_nose_compatibility
 class TestDefaultDiscussion(WithDatabase):
     patches = [fake_app_patch]
 
@@ -88,7 +84,6 @@ class TestDefaultDiscussion(WithDatabase):
         assert self.discussion.shortname == 'my_mounted_app'
 
 
-@with_nose_compatibility
 class TestAppDefaults(WithDatabase):
     patches = [fake_app_patch]
 
diff --git a/Allura/allura/tests/unit/test_artifact.py b/Allura/allura/tests/unit/test_artifact.py
index 1cbe17f47..895eb74ec 100644
--- a/Allura/allura/tests/unit/test_artifact.py
+++ b/Allura/allura/tests/unit/test_artifact.py
@@ -18,10 +18,8 @@
 import unittest
 
 from allura import model as M
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestArtifact(unittest.TestCase):
 
     def test_translate_query(self):
diff --git a/Allura/allura/tests/unit/test_discuss.py b/Allura/allura/tests/unit/test_discuss.py
index 6078dff49..6f8eba841 100644
--- a/Allura/allura/tests/unit/test_discuss.py
+++ b/Allura/allura/tests/unit/test_discuss.py
@@ -18,10 +18,8 @@
 from allura import model as M
 from allura.tests.unit import WithDatabase
 from allura.tests.unit.patches import fake_app_patch
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestThread(WithDatabase):
     patches = [fake_app_patch]
 
diff --git a/Allura/allura/tests/unit/test_helpers/test_ago.py b/Allura/allura/tests/unit/test_helpers/test_ago.py
index ee4d0925f..8e6406e9a 100644
--- a/Allura/allura/tests/unit/test_helpers/test_ago.py
+++ b/Allura/allura/tests/unit/test_helpers/test_ago.py
@@ -20,10 +20,8 @@ from datetime import datetime
 from mock import patch
 
 from allura.lib import helpers
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestAgo:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/unit/test_helpers/test_set_context.py b/Allura/allura/tests/unit/test_helpers/test_set_context.py
index 68b8898e4..171cf4425 100644
--- a/Allura/allura/tests/unit/test_helpers/test_set_context.py
+++ b/Allura/allura/tests/unit/test_helpers/test_set_context.py
@@ -26,10 +26,8 @@ from allura.tests.unit import patches
 from allura.tests.unit.factories import (create_project,
                                          create_app_config,
                                          create_neighborhood)
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestWhenProjectIsFoundAndAppIsNot(WithDatabase):
 
     def setup_method(self, method):
@@ -44,7 +42,6 @@ class TestWhenProjectIsFoundAndAppIsNot(WithDatabase):
         assert c.app is None, c.app
 
 
-@with_nose_compatibility
 class TestWhenProjectIsFoundInNeighborhood(WithDatabase):
 
     def setup_method(self, method):
@@ -59,7 +56,6 @@ class TestWhenProjectIsFoundInNeighborhood(WithDatabase):
         assert c.app is None
 
 
-@with_nose_compatibility
 class TestWhenAppIsFoundByID(WithDatabase):
     patches = [patches.project_app_loading_patch]
 
@@ -77,7 +73,6 @@ class TestWhenAppIsFoundByID(WithDatabase):
         self.project_app_instance_function.assert_called_with(self.app_config)
 
 
-@with_nose_compatibility
 class TestWhenAppIsFoundByMountPoint(WithDatabase):
     patches = [patches.project_app_loading_patch]
 
@@ -96,7 +91,6 @@ class TestWhenAppIsFoundByMountPoint(WithDatabase):
             'my_mounted_app')
 
 
-@with_nose_compatibility
 class TestWhenProjectIsNotFound(WithDatabase):
 
     def test_that_it_raises_an_exception(self):
@@ -114,7 +108,6 @@ class TestWhenProjectIsNotFound(WithDatabase):
                       neighborhood=None)
 
 
-@with_nose_compatibility
 class TestWhenNeighborhoodIsNotFound(WithDatabase):
 
     def test_that_it_raises_an_exception(self):
diff --git a/Allura/allura/tests/unit/test_ldap_auth_provider.py b/Allura/allura/tests/unit/test_ldap_auth_provider.py
index c66da2b24..a480be211 100644
--- a/Allura/allura/tests/unit/test_ldap_auth_provider.py
+++ b/Allura/allura/tests/unit/test_ldap_auth_provider.py
@@ -31,10 +31,8 @@ from allura.lib import plugin
 from allura.lib import helpers as h
 from allura import model as M
 import six
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestLdapAuthenticationProvider:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/unit/test_mixins.py b/Allura/allura/tests/unit/test_mixins.py
index 775ff0358..ac29a59d5 100644
--- a/Allura/allura/tests/unit/test_mixins.py
+++ b/Allura/allura/tests/unit/test_mixins.py
@@ -17,10 +17,8 @@
 
 from mock import Mock
 from allura.model import VotableArtifact
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestVotableArtifact:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/unit/test_package_path_loader.py b/Allura/allura/tests/unit/test_package_path_loader.py
index 5e3702715..44bb6fbfe 100644
--- a/Allura/allura/tests/unit/test_package_path_loader.py
+++ b/Allura/allura/tests/unit/test_package_path_loader.py
@@ -25,10 +25,8 @@ import pytest
 from tg import config
 
 from allura.lib.package_path_loader import PackagePathLoader
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestPackagePathLoader(TestCase):
 
     @mock.patch('pkg_resources.resource_filename')
diff --git a/Allura/allura/tests/unit/test_post_model.py b/Allura/allura/tests/unit/test_post_model.py
index eca936d1e..7cbcf4682 100644
--- a/Allura/allura/tests/unit/test_post_model.py
+++ b/Allura/allura/tests/unit/test_post_model.py
@@ -22,10 +22,8 @@ from allura import model as M
 from allura.tests.unit import WithDatabase
 from allura.tests.unit import patches
 from allura.tests.unit.factories import create_post
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestPostModel(WithDatabase):
     patches = [patches.fake_app_patch,
                patches.disable_notifications_patch]
diff --git a/Allura/allura/tests/unit/test_project.py b/Allura/allura/tests/unit/test_project.py
index 2fb093643..37af31d18 100644
--- a/Allura/allura/tests/unit/test_project.py
+++ b/Allura/allura/tests/unit/test_project.py
@@ -23,10 +23,8 @@ from tg import config
 from allura import model as M
 from allura.lib import helpers as h
 from allura.app import SitemapEntry
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestProject(unittest.TestCase):
 
     def test_grouped_navbar_entries(self):
diff --git a/Allura/allura/tests/unit/test_repo.py b/Allura/allura/tests/unit/test_repo.py
index f67e69a96..47945e24f 100644
--- a/Allura/allura/tests/unit/test_repo.py
+++ b/Allura/allura/tests/unit/test_repo.py
@@ -29,10 +29,8 @@ from allura.model.repository import zipdir, prefix_paths_union
 from allura.model.repo_refresh import (
     _group_commits,
 )
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestTopoSort(unittest.TestCase):
 
     def test_commit_dates_out_of_order(self):
@@ -85,7 +83,6 @@ def blob(name, id):
     return b
 
 
-@with_nose_compatibility
 class TestTree(unittest.TestCase):
 
     @patch('allura.model.repository.Tree.__getitem__')
@@ -103,7 +100,6 @@ class TestTree(unittest.TestCase):
         getitem().__getitem__().__getitem__.assert_called_with('file.txt')
 
 
-@with_nose_compatibility
 class TestBlob(unittest.TestCase):
 
     def test_pypeline_view(self):
@@ -144,7 +140,6 @@ class TestBlob(unittest.TestCase):
         assert blob.has_html_view == True
 
 
-@with_nose_compatibility
 class TestCommit(unittest.TestCase):
 
     def test_activity_extras(self):
@@ -246,7 +241,6 @@ class TestCommit(unittest.TestCase):
         commit.get_tree.assert_called_with(create=True)
 
 
-@with_nose_compatibility
 class TestZipDir(unittest.TestCase):
 
     @patch('allura.model.repository.Popen')
@@ -290,7 +284,6 @@ class TestZipDir(unittest.TestCase):
         self.assertTrue("STDERR: 2" in emsg)
 
 
-@with_nose_compatibility
 class TestPrefixPathsUnion(unittest.TestCase):
 
     def test_disjoint(self):
@@ -309,7 +302,6 @@ class TestPrefixPathsUnion(unittest.TestCase):
         self.assertEqual(prefix_paths_union(a, b), {'a2'})
 
 
-@with_nose_compatibility
 class TestGroupCommits:
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/unit/test_session.py b/Allura/allura/tests/unit/test_session.py
index d7899302f..e2042c039 100644
--- a/Allura/allura/tests/unit/test_session.py
+++ b/Allura/allura/tests/unit/test_session.py
@@ -28,7 +28,6 @@ from allura.model.session import (
     ArtifactSessionExtension,
     substitute_extensions,
 )
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
 def test_extensions_cm():
@@ -75,7 +74,6 @@ def test_extensions_cm_flush_raises():
     assert session._kwargs['extensions'] == []
 
 
-@with_nose_compatibility
 class TestSessionExtension(TestCase):
 
     def _mock_indexable(self, **kw):
@@ -85,7 +83,6 @@ class TestSessionExtension(TestCase):
         return m
 
 
-@with_nose_compatibility
 class TestIndexerSessionExtension(TestSessionExtension):
 
     def setup_method(self, method):
@@ -124,7 +121,6 @@ class TestIndexerSessionExtension(TestSessionExtension):
         assert self.tasks['add'].post.call_count == 0
 
 
-@with_nose_compatibility
 class TestArtifactSessionExtension(TestSessionExtension):
 
     def setup_method(self, method):
@@ -154,7 +150,6 @@ class TestArtifactSessionExtension(TestSessionExtension):
         assert index_tasks.add_artifacts.post.call_count == 0
 
 
-@with_nose_compatibility
 class TestBatchIndexer(TestCase):
 
     def setup_method(self, method):
diff --git a/Allura/allura/tests/unit/test_sitemapentry.py b/Allura/allura/tests/unit/test_sitemapentry.py
index ea38b10b7..bdd6399d1 100644
--- a/Allura/allura/tests/unit/test_sitemapentry.py
+++ b/Allura/allura/tests/unit/test_sitemapentry.py
@@ -19,10 +19,8 @@ import unittest
 from mock import Mock
 
 from allura.app import SitemapEntry
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestSitemapEntry(unittest.TestCase):
 
     def test_matches_url(self):
diff --git a/Allura/allura/tests/unit/test_solr.py b/Allura/allura/tests/unit/test_solr.py
index 8d15e65ad..c24bfdb35 100644
--- a/Allura/allura/tests/unit/test_solr.py
+++ b/Allura/allura/tests/unit/test_solr.py
@@ -25,10 +25,8 @@ from allura.tests import decorators as td
 from alluratest.controller import setup_basic_test
 from allura.lib.solr import Solr, escape_solr_arg
 from allura.lib.search import search_app, SearchIndexable
-from alluratest.pytest_helpers import with_nose_compatibility
 
 
-@with_nose_compatibility
 class TestSolr(unittest.TestCase):
 
     def setup_method(self, method):
@@ -122,7 +120,6 @@ class TestSolr(unittest.TestCase):
             'username_s:admin1 || username_s:root', fq=fq, ignore_errors=False)
 
 
-@with_nose_compatibility
 class TestSearchIndexable(unittest.TestCase):
 
     def setup_method(self, method):
@@ -147,7 +144,6 @@ class TestSearchIndexable(unittest.TestCase):
         assert self.obj.solarize() == dict(text='<script>a(1)</script>')
 
 
-@with_nose_compatibility
 class TestSearch_app(unittest.TestCase):
 
     def setup_method(self, method):
diff --git a/AlluraTest/alluratest/controller.py b/AlluraTest/alluratest/controller.py
index 207946075..f110c7a57 100644
--- a/AlluraTest/alluratest/controller.py
+++ b/AlluraTest/alluratest/controller.py
@@ -19,7 +19,6 @@
 from __future__ import annotations
 
 import os
-from alluratest.pytest_helpers import with_nose_compatibility
 import six.moves.urllib.request
 import six.moves.urllib.parse
 import six.moves.urllib.error
@@ -163,14 +162,12 @@ def setup_trove_categories():
         create_trove_categories.run([''])
 
 
-@with_nose_compatibility
 class TestController:
 
     application_under_test = 'main'
     validate_skip = False
 
     def setup_method(self, method=None):
-        """Method called by nose before running each test"""
         pkg = self.__module__.split('.')[0]
         self.app = ValidatingTestApp(
             setup_functional_test(app_name=self.application_under_test, current_pkg=pkg))
@@ -182,7 +179,6 @@ class TestController:
             self.smtp_mock.start()
 
     def teardown_method(self, method=None):
-        """Method called by nose after running each test"""
         if asbool(tg.config.get('smtp.mock')):
             self.smtp_mock.stop()
 
@@ -212,7 +208,6 @@ class TestController:
                 return f
 
 
-@with_nose_compatibility
 class TestRestApiBase(TestController):
 
     def setup_method(self, method):
diff --git a/AlluraTest/alluratest/pytest_helpers.py b/AlluraTest/alluratest/pytest_helpers.py
deleted file mode 100644
index 832cfa796..000000000
--- a/AlluraTest/alluratest/pytest_helpers.py
+++ /dev/null
@@ -1,49 +0,0 @@
-
-IS_NOSE = None
-
-
-def is_called_by_nose():
-    global IS_NOSE
-    if IS_NOSE is None:
-        import inspect
-        stack = inspect.stack()
-        IS_NOSE = any(x[0].f_globals['__name__'].startswith('nose.') for x in stack)
-    return IS_NOSE
-
-
-def with_nose_compatibility(test_class):
-
-    if not is_called_by_nose():
-        return test_class
-
-    def setUp_(self):
-        setup_method = hasattr(self, 'setup_method')
-        if setup_method:
-            self.setup_method(None)
-    if hasattr(test_class, 'setup_method'):
-        test_class.setUp = setUp_
-
-    def tearDown_(self):
-        teardown_method = hasattr(self, 'teardown_method')
-        if teardown_method:
-            self.teardown_method(None)
-    if hasattr(test_class, 'teardown_method'):
-        test_class.tearDown = tearDown_
-
-    @classmethod
-    def setUpClass_(cls):
-        setup_class = hasattr(cls, 'setup_class')
-        if setup_class:
-            cls.setup_class()
-    if hasattr(test_class, 'setup_class'):
-        test_class.setUpClass = setUpClass_
-
-    @classmethod
-    def tearDownClass_(cls):
-        teardown_class = hasattr(cls, 'teardown_class')
-        if teardown_class:
-            cls.teardown_class()
-    if hasattr(test_class, 'teardown_class'):
-        test_class.tearDownClass = tearDownClass_
-
-    return test_class
diff --git a/AlluraTest/alluratest/test_syntax.py b/AlluraTest/alluratest/test_syntax.py
index 658d676fc..0d93f4c0d 100644
--- a/AlluraTest/alluratest/test_syntax.py
+++ b/AlluraTest/alluratest/test_syntax.py
@@ -27,7 +27,7 @@ toplevel_dir = os.path.abspath(os.path.dirname(__file__) + "/../..")
 
 def run(cmd):
     proc = Popen(cmd, shell=True, cwd=toplevel_dir, stdout=PIPE, stderr=PIPE)
-    # must capture & reprint stdount, so that nosetests can capture it
+    # must capture & reprint stdount, so that pytest can capture it
     (stdout, stderr) = proc.communicate()
     sys.stdout.write(stdout.decode('utf-8'))
     sys.stderr.write(stderr.decode('utf-8'))


[allura] 04/07: [#8455] updated test docs, removed various old references to nose and replaced with pytest

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dill0wn pushed a commit to branch pytest-finalize
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 954da7fdc479221e94137125724584bb6d5774db
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Tue Nov 1 15:30:08 2022 +0000

    [#8455] updated test docs, removed various old references to nose and replaced with pytest
---
 .travis.yml                                                  |  1 -
 Allura/allura/tests/test_commands.py                         |  1 -
 Allura/allura/tests/test_helpers.py                          |  1 -
 Allura/docs/development/contributing.rst                     |  9 +++------
 Allura/docs/getting_started/installation.rst                 |  5 ++---
 AlluraTest/alluratest/test_syntax.py                         |  2 +-
 CHANGES                                                      | 12 ++++++++++++
 .../forgetracker/tests/command/test_fix_discussion.py        |  1 -
 README.markdown                                              |  2 +-
 rat-excludes.txt                                             |  1 +
 10 files changed, 20 insertions(+), 15 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index e8511f751..14a74ff89 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -27,7 +27,6 @@ jobs:
 install:
   - sudo apt-get install -qq libjpeg8-dev zlib1g-dev
   - pip install --upgrade setuptools pip
-  - pip install nose
   - pip install -r requirements.txt --no-deps --upgrade --upgrade-strategy=only-if-needed
   - npm ci
 script:
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index fadd8ad09..db9ae0296 100644
--- a/Allura/allura/tests/test_commands.py
+++ b/Allura/allura/tests/test_commands.py
@@ -44,7 +44,6 @@ class EmptyClass:
 
 
 def setup_module():
-    """Method called by nose before running each test"""
     setup_basic_test()
     setup_global_objects()
     setup_unit_test()
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 571d33fea..21926356c 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -42,7 +42,6 @@ import six
 
 
 def setup_module():
-    """Method called by nose before running each test"""
     setup_basic_test()
 
 
diff --git a/Allura/docs/development/contributing.rst b/Allura/docs/development/contributing.rst
index bd9fcbf87..29ac70776 100644
--- a/Allura/docs/development/contributing.rst
+++ b/Allura/docs/development/contributing.rst
@@ -206,16 +206,13 @@ as ``pudb`` are also available.
 
 Testing
 -------
-First, install :code:`nose` (not bundled installed by default, since it is LGPL and deprecated)
-:code:`docker-compose run web pip install nose`
-
 To run all the tests, execute ``./run_tests`` in the repo root. To run tests
 for a single package, for example ``forgetracker``::
 
-  cd ForgeTracker && nosetests
+  cd ForgeTracker && pytest
 
-To learn more about the ``nose`` test runner, consult the `documentation
-<http://nose.readthedocs.org/en/latest/>`_.
+To learn more about the ``pytest`` test runner, consult the `documentation
+<https://docs.pytest.org/en/latest/contents.html>`_.
 
 When writing code for Allura, don't forget that you'll need to also create
 tests that cover behaviour that you've added or changed. You may find this
diff --git a/Allura/docs/getting_started/installation.rst b/Allura/docs/getting_started/installation.rst
index a89d2dbe2..c519c42cb 100644
--- a/Allura/docs/getting_started/installation.rst
+++ b/Allura/docs/getting_started/installation.rst
@@ -174,8 +174,7 @@ Update requirements and reinstall apps:
 You may want to restart at least "taskd" container after that in order for it to
 pick up changes.  Run :code:`docker-compose restart taskd`
 
-Running all tests.  First, install :code:`nose` (not bundled installed by default, since it is LGPL and deprecated)
-:code:`docker-compose run web pip install nose` then:
+Run all tests:
 
 .. code-block:: bash
 
@@ -185,7 +184,7 @@ Running subset of tests:
 
 .. code-block:: bash
 
-    docker-compose run web bash -c 'cd ForgeGit && nosetests forgegit.tests.functional.test_controllers:TestFork'
+    docker-compose run web bash -c 'cd ForgeGit && pytest forgegit/tests/functional/test_controllers.py::TestFork'
 
 Connecting to mongo using a container:
 
diff --git a/AlluraTest/alluratest/test_syntax.py b/AlluraTest/alluratest/test_syntax.py
index 0d93f4c0d..bd2ea0701 100644
--- a/AlluraTest/alluratest/test_syntax.py
+++ b/AlluraTest/alluratest/test_syntax.py
@@ -27,7 +27,7 @@ toplevel_dir = os.path.abspath(os.path.dirname(__file__) + "/../..")
 
 def run(cmd):
     proc = Popen(cmd, shell=True, cwd=toplevel_dir, stdout=PIPE, stderr=PIPE)
-    # must capture & reprint stdount, so that pytest can capture it
+    # must capture & reprint stdout, so that pytest can capture it
     (stdout, stderr) = proc.communicate()
     sys.stdout.write(stdout.decode('utf-8'))
     sys.stderr.write(stderr.decode('utf-8'))
diff --git a/CHANGES b/CHANGES
index bc2e43ded..672c16e40 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,15 @@
+Next Version
+
+Upgrade Instructions
+
+  More to come!
+
+General
+ * [#8445] Switched all tests from nose to pytest
+
+
+
+
 Version 1.14.0  (September 2022)
 
 Upgrade Instructions
diff --git a/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py b/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
index 5d51ad120..e31609b77 100644
--- a/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
+++ b/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
@@ -30,7 +30,6 @@ test_config = pkg_resources.resource_filename(
 
 
 def setup_module(self):
-    """Method called by nose before running each test"""
     setup_basic_test()
     setup_global_objects()
 
diff --git a/README.markdown b/README.markdown
index b0bbc4b2e..4c1fdef30 100644
--- a/README.markdown
+++ b/README.markdown
@@ -25,7 +25,7 @@
 
 Allura is an open source implementation of a software "forge", a web site that manages source code repositories, bug reports, discussions, mailing lists, wiki pages, blogs and more for any number of individual projects.
 
-Allura is written in Python and leverages a great many existing Python packages (see requirements.txt and friends).  It comes with tests which we run with [nose](https://nose.readthedocs.org/en/latest/).  It is extensible in several ways, most importantly via the notion of "tools" based on `allura.app.Application`; but also with [themes, authentication, and various other pluggable-APIs](https://forge-allura.apache.org/docs/extending.html).
+Allura is written in Python and leverages a great many existing Python packages (see requirements.txt and friends).  It comes with tests which we run with [pytest](https://docs.pytest.org/en/latest/contents.html).  It is extensible in several ways, most importantly via the notion of "tools" based on `allura.app.Application`; but also with [themes, authentication, and various other pluggable-APIs](https://forge-allura.apache.org/docs/extending.html).
 
 Website: <https://allura.apache.org/>
 
diff --git a/rat-excludes.txt b/rat-excludes.txt
index c33bf1001..7c702e624 100644
--- a/rat-excludes.txt
+++ b/rat-excludes.txt
@@ -7,6 +7,7 @@
 **/.pytest_cache/
 **/MANIFEST.in
 **/nosetests.xml
+**/pytest.junit.xml
 **/setup.cfg
 .eslintignore-es5
 .eslintignore-es6


[allura] 05/07: [#8455] update docker test run to use pytest

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dill0wn pushed a commit to branch pytest-finalize
in repository https://gitbox.apache.org/repos/asf/allura.git

commit be65c00db958de688bc540ac3a2dc02e46e95fe2
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Tue Nov 1 15:30:49 2022 +0000

    [#8455] update docker test run to use pytest
---
 scripts/jenkins-python3.7.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/scripts/jenkins-python3.7.sh b/scripts/jenkins-python3.7.sh
index 3746742a8..d8f5baa67 100755
--- a/scripts/jenkins-python3.7.sh
+++ b/scripts/jenkins-python3.7.sh
@@ -42,7 +42,7 @@ echo "==========================================================================
 echo "Run: cleanup previous runs"
 echo "============================================================================="
 rm -rf ./allura-data
-git clean -f -x  # remove test.log, nosetest.xml etc (don't use -d since it'd remove our venv dir)
+git clean -f -x  # remove test.log, pytest.junit.xml etc (don't use -d since it'd remove our venv dir)
 
 docker-compose down
 
@@ -98,7 +98,7 @@ docker-compose exec -T web bash -c "pyflakes Allura* Forge* scripts | awk -F\: '
 docker-compose exec -T web bash -c "pycodestyle Allura* Forge* scripts > pep8.txt"
 
 # TODO: ALLURA_VALIDATION=all
-docker-compose exec -T -e LANG=en_US.UTF-8 web ./run_tests --with-xunitmp # --with-coverage --cover-erase
+docker-compose exec -T -e LANG=en_US.UTF-8 web ./run_tests --junit-xml=pytest.junit.xml # --with-coverage --cover-erase
 retcode=$?
 
 #find . -name .coverage -maxdepth 2 | while read coveragefile; do pushd `dirname $coveragefile`; coverage xml --include='forge*,allura*'; popd; done;


[allura] 07/07: [#8455] misc fixes to tests from pytest conversion

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dill0wn pushed a commit to branch pytest-finalize
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 92fe315570bf431bd110a87b869ee961ec926d22
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Wed Nov 2 20:44:44 2022 +0000

    [#8455] misc fixes to tests from pytest conversion
---
 Allura/allura/tests/test_globals.py | 28 ++++++++++++++++------------
 1 file changed, 16 insertions(+), 12 deletions(-)

diff --git a/Allura/allura/tests/test_globals.py b/Allura/allura/tests/test_globals.py
index 01a48daf1..9b16328bc 100644
--- a/Allura/allura/tests/test_globals.py
+++ b/Allura/allura/tests/test_globals.py
@@ -48,6 +48,21 @@ from forgewiki import model as WM
 from forgeblog import model as BM
 
 
+def setup():
+    setup_basic_test()
+    setup_unit_test()
+    setup_with_tools()
+
+
+def teardown():
+    setup()
+
+
+@td.with_wiki
+def setup_with_tools():
+    setup_global_objects()
+
+
 def squish_spaces(text):
     # \s is whitespace
     # \xa0 is &nbsp; in unicode form
@@ -78,13 +93,8 @@ def get_projects_property_in_the_same_order(names, prop):
 
 class Test():
 
-    @classmethod
-    def setup_class(cls):
-        setup_basic_test()
-        setup_global_objects()
-
     def setup_method(self, method):
-        setup_global_objects()
+        setup()
         p_nbhd = M.Neighborhood.query.get(name='Projects')
         p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
         self.acl_bak = p_test.acl.copy()
@@ -273,7 +283,6 @@ class Test():
         assert '</a><br/><br/><a href=' not in r, r
         assert '</a></li><li><a href=' in r, r
 
-    @td.with_wiki
     def test_macro_include_no_extra_br(self):
         p_nbhd = M.Neighborhood.query.get(name='Projects')
         p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
@@ -304,7 +313,6 @@ class Test():
     <p></p></div>'''
         assert squish_spaces(html) == squish_spaces(expected_html)
 
-    @td.with_wiki
     @td.with_tool('test', 'Wiki', 'wiki2')
     def test_macro_include_permissions(self):
         p_nbhd = M.Neighborhood.query.get(name='Projects')
@@ -386,7 +394,6 @@ class Test():
             </li>
             </ul>''') in r
 
-    @td.with_wiki
     def test_wiki_artifact_links(self):
         text = g.markdown.convert('See [18:13:49]')
         assert 'See <span>[18:13:49]</span>' in text, text
@@ -436,7 +443,6 @@ class Test():
         assert g.markdown.convert(text) == '<pre>%s</pre>' % text
         assert g.markdown_wiki.convert(text) == '<pre>%s</pre>' % text
 
-    @td.with_wiki
     def test_markdown_basics(self):
         with h.push_context('test', 'wiki', neighborhood='Projects'):
             text = g.markdown.convert('# Foo!\n[Home]')
@@ -629,7 +635,6 @@ class Test():
             assert f'<span>[{charSuperLong}]</span>(Home)' in text, text  # current limitation, not a link
             # assert f'href="/p/test/wiki-len/Home/">{charSuperLong}</a>' in text, text  # ideal output
 
-    @td.with_wiki
     def test_macro_include(self):
         r = g.markdown.convert('[[include ref=Home id=foo]]')
         assert '<div id="foo">' in r, r
@@ -721,7 +726,6 @@ class Test():
             proj_title = f'<h2><a href="{p.url()}">{p.name}</a></h2>'
             assert proj_title in r
 
-    @td.with_wiki
     def test_hideawards_macro(self):
         p_nbhd = M.Neighborhood.query.get(name='Projects')
 


[allura] 06/07: [#8455] remove 'test_suite' and 'tests_require' from setup.py as they are deprecated

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dill0wn pushed a commit to branch pytest-finalize
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 169ddb77d9f911cf54bb591f0b22e302ce4ef873
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Tue Nov 1 16:27:42 2022 +0000

    [#8455] remove 'test_suite' and 'tests_require' from setup.py as they are deprecated
---
 Allura/setup.py          | 2 --
 ForgeDiscussion/setup.py | 2 --
 2 files changed, 4 deletions(-)

diff --git a/Allura/setup.py b/Allura/setup.py
index cb89a44ff..aa5f79113 100644
--- a/Allura/setup.py
+++ b/Allura/setup.py
@@ -50,8 +50,6 @@ setup(
     paster_plugins=['PasteScript', 'TurboGears2', 'Ming'],
     packages=find_packages(exclude=['ez_setup']),
     include_package_data=True,
-    test_suite='nose.collector',
-    tests_require=['WebTest >= 1.2', 'BeautifulSoup', 'nose'],
     package_data={'allura': ['i18n/*/LC_MESSAGES/*.mo',
                              'templates/**.html',
                              'templates/**.py',
diff --git a/ForgeDiscussion/setup.py b/ForgeDiscussion/setup.py
index 165ce4b2f..a8993785d 100644
--- a/ForgeDiscussion/setup.py
+++ b/ForgeDiscussion/setup.py
@@ -38,8 +38,6 @@ setup(name='ForgeDiscussion',
           # -*- Extra requirements: -*-
           'Allura',
       ],
-      test_suite='nose.collector',
-      tests_require=['WebTest', 'BeautifulSoup'],
       entry_points="""
       # -*- Entry points: -*-
       [allura]