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/09/09 20:34:16 UTC

[allura] 02/05: fixup! first substantial test file mostly passing under pytest

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

dill0wn pushed a commit to branch dw/8455
in repository https://gitbox.apache.org/repos/asf/allura.git

commit e54eb467079dbc88885148cf4884c4d7343e7921
Author: Kenton Taylor <kt...@slashdotmedia.com>
AuthorDate: Mon Aug 22 21:00:53 2022 +0000

    fixup! first substantial test file mostly passing under pytest
---
 Allura/allura/tests/functional/test_admin.py        | 10 +++++++++-
 Allura/allura/tests/functional/test_auth.py         | 16 +++++++++++++---
 Allura/allura/tests/functional/test_discuss.py      |  5 ++++-
 Allura/allura/tests/functional/test_feeds.py        |  1 +
 Allura/allura/tests/functional/test_gravatar.py     |  1 +
 Allura/allura/tests/functional/test_home.py         |  1 +
 Allura/allura/tests/functional/test_nav.py          |  3 ++-
 Allura/allura/tests/functional/test_neighborhood.py |  3 +++
 Allura/allura/tests/functional/test_newforge.py     |  1 +
 .../tests/functional/test_personal_dashboard.py     |  5 ++++-
 Allura/allura/tests/functional/test_rest.py         |  6 +++++-
 Allura/allura/tests/functional/test_root.py         |  6 ++++--
 Allura/allura/tests/functional/test_search.py       |  1 +
 Allura/allura/tests/functional/test_site_admin.py   | 10 ++++++++--
 Allura/allura/tests/functional/test_static.py       |  1 +
 Allura/allura/tests/functional/test_subscriber.py   |  1 +
 Allura/allura/tests/functional/test_tool_list.py    |  1 +
 .../allura/tests/functional/test_trovecategory.py   |  2 ++
 Allura/allura/tests/functional/test_user_profile.py |  2 ++
 Allura/allura/tests/model/test_artifact.py          |  2 +-
 Allura/allura/tests/model/test_auth.py              |  3 ++-
 Allura/allura/tests/model/test_discussion.py        |  4 ++--
 Allura/allura/tests/model/test_filesystem.py        |  1 +
 Allura/allura/tests/model/test_monq.py              |  2 +-
 Allura/allura/tests/model/test_neighborhood.py      |  2 +-
 Allura/allura/tests/model/test_notification.py      |  8 ++++++--
 Allura/allura/tests/model/test_oauth.py             |  2 +-
 Allura/allura/tests/model/test_project.py           |  2 +-
 Allura/allura/tests/model/test_repo.py              |  4 ++++
 Allura/allura/tests/model/test_timeline.py          |  1 +
 .../tests/scripts/test_create_sitemap_files.py      |  1 +
 Allura/allura/tests/scripts/test_delete_projects.py |  3 ++-
 Allura/allura/tests/scripts/test_misc_scripts.py    |  1 +
 Allura/allura/tests/scripts/test_reindexes.py       |  2 ++
 .../allura/tests/templates/jinja_master/test_lib.py |  1 +
 Allura/allura/tests/test_app.py                     |  3 ++-
 Allura/allura/tests/test_commands.py                | 13 ++++++++++---
 Allura/allura/tests/test_decorators.py              |  4 +++-
 Allura/allura/tests/test_diff.py                    |  2 ++
 Allura/allura/tests/test_dispatch.py                |  1 +
 Allura/allura/tests/test_globals.py                 |  7 ++++++-
 Allura/allura/tests/test_helpers.py                 |  4 ++++
 Allura/allura/tests/test_mail_util.py               |  5 +++++
 Allura/allura/tests/test_markdown.py                |  5 +++++
 Allura/allura/tests/test_middlewares.py             |  1 +
 Allura/allura/tests/test_multifactor.py             | 14 ++++++++++++--
 Allura/allura/tests/test_plugin.py                  | 13 +++++++++++--
 Allura/allura/tests/test_scripttask.py              |  4 +++-
 Allura/allura/tests/test_security.py                |  1 +
 Allura/allura/tests/test_tasks.py                   | 12 ++++++++++--
 Allura/allura/tests/test_utils.py                   | 10 ++++++++++
 Allura/allura/tests/test_validators.py              | 14 +++++++++++++-
 Allura/allura/tests/test_webhooks.py                | 21 ++++++++++++++-------
 Allura/allura/tests/unit/__init__.py                |  4 ++--
 .../test_discussion_moderation_controller.py        |  7 +++++--
 Allura/allura/tests/unit/phone/test_nexmo.py        |  1 +
 .../allura/tests/unit/phone/test_phone_service.py   |  1 +
 Allura/allura/tests/unit/spam/test_akismet.py       |  1 +
 Allura/allura/tests/unit/spam/test_spam_filter.py   |  3 +++
 Allura/allura/tests/unit/spam/test_stopforumspam.py |  1 +
 Allura/allura/tests/unit/test_app.py                |  8 ++++++--
 Allura/allura/tests/unit/test_artifact.py           |  1 +
 Allura/allura/tests/unit/test_discuss.py            |  1 +
 Allura/allura/tests/unit/test_helpers/test_ago.py   |  1 +
 .../tests/unit/test_helpers/test_set_context.py     | 14 ++++++++++----
 Allura/allura/tests/unit/test_ldap_auth_provider.py |  1 +
 Allura/allura/tests/unit/test_mixins.py             |  1 +
 .../allura/tests/unit/test_package_path_loader.py   |  1 +
 Allura/allura/tests/unit/test_post_model.py         |  3 ++-
 Allura/allura/tests/unit/test_project.py            |  1 +
 Allura/allura/tests/unit/test_repo.py               |  7 +++++++
 Allura/allura/tests/unit/test_session.py            |  4 ++++
 Allura/allura/tests/unit/test_sitemapentry.py       |  1 +
 Allura/allura/tests/unit/test_solr.py               |  3 +++
 Allura/docs/development/testing.rst                 |  2 +-
 AlluraTest/alluratest/controller.py                 | 11 +++++++----
 76 files changed, 267 insertions(+), 60 deletions(-)

diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index d8570c668..0f5c3b30e 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -47,6 +47,7 @@ from forgewiki.wiki_main import ForgeWikiApp
 log = logging.getLogger(__name__)
 
 
+@with_nose_compatibility
 class TestProjectAdmin(TestController):
 
     def test_admin_controller(self):
@@ -959,10 +960,11 @@ class TestProjectAdmin(TestController):
         r.mustcontain('Neighborhood Invitation(s) for test')
 
 
+@with_nose_compatibility
 class TestExport(TestController):
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.setup_with_tools()
 
     @td.with_wiki
@@ -1085,6 +1087,7 @@ class TestExport(TestController):
         assert 'Check All</label>' in r
 
 
+@with_nose_compatibility
 class TestRestExport(TestRestApiBase):
 
     @mock.patch('allura.model.project.MonQTask')
@@ -1152,6 +1155,7 @@ 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):
@@ -1325,6 +1329,7 @@ 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)
@@ -1340,6 +1345,7 @@ 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)
@@ -1389,6 +1395,7 @@ class TestRestMountOrder(TestRestApiBase):
         assert b > a
 
 
+@with_nose_compatibility
 class TestRestToolGrouping(TestRestApiBase):
     def test_invalid_grouping_threshold(self):
         for invalid_value in ('100', 'asdf'):
@@ -1415,6 +1422,7 @@ 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 9a1537877..6a8b04cd3 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -45,6 +45,7 @@ import oauth2
 from allura.tests import TestController
 from allura.tests import decorators as td
 from allura.tests.decorators import audits, out_audits
+from allura.tests.pytest_helpers import with_nose_compatibility
 from alluratest.controller import setup_trove_categories, TestRestApiBase
 from allura import model as M
 from allura.lib import plugin
@@ -57,6 +58,7 @@ def unentity(s):
     return s.replace('&quot;', '"').replace('&#34;', '"')
 
 
+@with_nose_compatibility
 class TestAuth(TestController):
     def test_login(self):
         self.app.get('/auth/')
@@ -1138,6 +1140,7 @@ class TestAuth(TestController):
         assert r.content_length != 777
 
 
+@with_nose_compatibility
 class TestAuthRest(TestRestApiBase):
 
     def test_tools_list_anon(self):
@@ -1177,6 +1180,7 @@ class TestAuthRest(TestRestApiBase):
         }
 
 
+@with_nose_compatibility
 class TestPreferences(TestController):
     @td.with_user_project('test-admin')
     def test_personal_data(self):
@@ -1560,11 +1564,12 @@ class TestPreferences(TestController):
             self.app.get('/auth/not_page', status=404)
 
 
+@with_nose_compatibility
 class TestPasswordReset(TestController):
     test_primary_email = 'testprimaryaddr@mail.com'
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         # so test-admin isn't automatically logged in for all requests
         self.app.extra_environ = {'disable_auth_magic': 'True'}
 
@@ -1811,6 +1816,7 @@ 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
@@ -2149,6 +2155,7 @@ class TestOAuth(TestController):
         assert len(atok['oauth_token_secret']) == 1
 
 
+@with_nose_compatibility
 class TestDisableAccount(TestController):
     def test_not_authenticated(self):
         r = self.app.get(
@@ -2193,6 +2200,7 @@ 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'}
@@ -2388,6 +2396,7 @@ 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
@@ -2422,6 +2431,7 @@ 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'
@@ -2495,7 +2505,7 @@ class TestTwoFactor(TestController):
     def test_enable_totp(self):
         # create a separate session, for later use in the test
         other_session = TestController()
-        other_session.setUp()
+        other_session.setup_method(method)
         other_session.app.get('/auth/preferences/')
 
         with out_audits(user=True):
@@ -2537,7 +2547,7 @@ class TestTwoFactor(TestController):
         # Confirm any pre-existing sessions have to re-authenticate
         r = other_session.app.get('/auth/preferences/')
         assert '/auth/?return_to' in r.headers['Location']
-        other_session.tearDown()
+        other_session.teardown_method(method)
 
     def test_reset_totp(self):
         self._init_totp()
diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py
index 8dd2934cc..70ee113f5 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -27,6 +27,7 @@ from allura.lib import helpers as h
 from tg import config
 
 
+@with_nose_compatibility
 class TestDiscussBase(TestController):
 
     def _thread_link(self):
@@ -42,6 +43,7 @@ class TestDiscussBase(TestController):
         return thread_link.split('/')[-2]
 
 
+@with_nose_compatibility
 class TestDiscuss(TestDiscussBase):
 
     def _is_subscribed(self, user, thread):
@@ -396,10 +398,11 @@ class TestDiscuss(TestDiscussBase):
         r = self.app.get(post_link, status=404)
 
 
+@with_nose_compatibility
 class TestAttachment(TestDiscussBase):
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.thread_link = self._thread_link()
         thread = self.app.get(self.thread_link)
         for f in thread.html.findAll('form'):
diff --git a/Allura/allura/tests/functional/test_feeds.py b/Allura/allura/tests/functional/test_feeds.py
index 3cbbc9f57..853a85c91 100644
--- a/Allura/allura/tests/functional/test_feeds.py
+++ b/Allura/allura/tests/functional/test_feeds.py
@@ -22,6 +22,7 @@ from allura.tests import decorators as td
 from allura.lib import helpers as h
 
 
+@with_nose_compatibility
 class TestFeeds(TestController):
 
     def setUp(self):
diff --git a/Allura/allura/tests/functional/test_gravatar.py b/Allura/allura/tests/functional/test_gravatar.py
index f6738e03e..776fe371c 100644
--- a/Allura/allura/tests/functional/test_gravatar.py
+++ b/Allura/allura/tests/functional/test_gravatar.py
@@ -25,6 +25,7 @@ from allura.tests import TestController
 import allura.lib.gravatar as gravatar
 
 
+@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 4096a5add..9bb14588d 100644
--- a/Allura/allura/tests/functional/test_home.py
+++ b/Allura/allura/tests/functional/test_home.py
@@ -29,6 +29,7 @@ from allura.tests import decorators as td
 from allura import model as M
 
 
+@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 347798198..011626197 100644
--- a/Allura/allura/tests/functional/test_nav.py
+++ b/Allura/allura/tests/functional/test_nav.py
@@ -24,6 +24,7 @@ from allura.tests import TestController
 from allura.lib import helpers as h
 
 
+@with_nose_compatibility
 class TestNavigation(TestController):
     """
     Test div-logo and nav-left:
@@ -32,7 +33,7 @@ class TestNavigation(TestController):
     """
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.logo_pattern = ('div', {'class': 'nav-logo'})
         self.global_nav_pattern = ('nav', {'class': 'nav-left'})
         self.nav_data = {
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index 24afb2036..1f3e2360a 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -39,6 +39,7 @@ from allura.lib import utils
 from alluratest.controller import setup_trove_categories
 
 
+@with_nose_compatibility
 class TestNeighborhood(TestController):
 
     def setUp(self):
@@ -977,6 +978,7 @@ 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)
@@ -1112,6 +1114,7 @@ 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 9dfabe614..f9f60a24e 100644
--- a/Allura/allura/tests/functional/test_newforge.py
+++ b/Allura/allura/tests/functional/test_newforge.py
@@ -23,6 +23,7 @@ from allura.tests import decorators as td
 from allura import model as M
 
 
+@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 ea28f2ff0..9dfbe300f 100644
--- a/Allura/allura/tests/functional/test_personal_dashboard.py
+++ b/Allura/allura/tests/functional/test_personal_dashboard.py
@@ -32,6 +32,7 @@ from alluratest.controller import setup_global_objects, setup_unit_test
 from forgetracker.tests.functional.test_root import TrackerTestController
 
 
+@with_nose_compatibility
 class TestPersonalDashboard(TestController):
 
     def test_dashboard(self):
@@ -68,6 +69,7 @@ class TestPersonalDashboard(TestController):
                 assert 'Section f' not in r.text
 
 
+@with_nose_compatibility
 class TestTicketsSection(TrackerTestController):
 
     @td.with_tracker
@@ -83,10 +85,11 @@ class TestTicketsSection(TrackerTestController):
         assert 'foo' in str(ticket_rows)
 
 
+@with_nose_compatibility
 class TestMergeRequestsSection(TestController):
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         setup_unit_test()
         self.setup_with_tools()
         mr= self.merge_request
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index 294c0d60e..894dba98f 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -33,6 +33,7 @@ from allura.lib.exceptions import Invalid
 from allura import model as M
 
 
+@with_nose_compatibility
 class TestRestHome(TestRestApiBase):
 
     def _patch_token(self, OAuthAccessToken):
@@ -415,10 +416,11 @@ class TestRestHome(TestRestApiBase):
         assert r.json == {}
 
 
+@with_nose_compatibility
 class TestRestNbhdAddProject(TestRestApiBase):
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         # create some troves we'll need
         M.TroveCategory(fullname="Root", trove_cat_id=1, trove_parent_id=0)
         M.TroveCategory(fullname="License", trove_cat_id=2, trove_parent_id=1)
@@ -557,6 +559,7 @@ class TestRestNbhdAddProject(TestRestApiBase):
         }
 
 
+@with_nose_compatibility
 class TestDoap(TestRestApiBase):
     validate_skip = True
     ns = '{http://usefulinc.com/ns/doap#}'
@@ -622,6 +625,7 @@ 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 47d6d23a0..2e01bacd6 100644
--- a/Allura/allura/tests/functional/test_root.py
+++ b/Allura/allura/tests/functional/test_root.py
@@ -43,10 +43,11 @@ from allura.lib import helpers as h
 from alluratest.controller import setup_trove_categories
 
 
+@with_nose_compatibility
 class TestRootController(TestController):
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         n_adobe = M.Neighborhood.query.get(name='Adobe')
         assert n_adobe
         u_admin = M.User.query.get(username='test-admin')
@@ -188,10 +189,11 @@ class TestRootController(TestController):
         r.mustcontain("We're sorry but we weren't able to process")
 
 
+@with_nose_compatibility
 class TestRootWithSSLPattern(TestController):
     def setUp(self):
         with td.patch_middleware_config({'force_ssl.pattern': '^/auth'}):
-            super().setUp()
+            super().setup_method(method)
 
     def test_no_weird_ssl_redirect_for_error_document(self):
         # test a 404, same functionality as a 500 from an error
diff --git a/Allura/allura/tests/functional/test_search.py b/Allura/allura/tests/functional/test_search.py
index 3a1a72b44..a45794280 100644
--- a/Allura/allura/tests/functional/test_search.py
+++ b/Allura/allura/tests/functional/test_search.py
@@ -26,6 +26,7 @@ from allura.tests.decorators import with_tool
 from forgewiki.model import Page
 
 
+@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 a68420a43..6bb0c44cd 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -35,6 +35,7 @@ from allura.lib.plugin import LocalAuthenticationProvider
 import six
 
 
+@with_nose_compatibility
 class TestSiteAdmin(TestController):
 
     def test_access(self):
@@ -184,6 +185,7 @@ 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):
@@ -337,6 +339,7 @@ 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=[{
@@ -358,7 +361,7 @@ class TestProjectsSearch(TestController):
     }])
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         # Create project that matches TEST_HIT id
         _id = ObjectId('53ccf6e8100d2b0741746e9f')
         p = M.Project.query.get(_id=_id)
@@ -391,6 +394,7 @@ 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=[{
@@ -415,7 +419,7 @@ class TestUsersSearch(TestController):
         'username_s': 'darth'}])
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         # Create user that matches TEST_HIT id
         _id = ObjectId('540efdf2100d2b1483155d39')
         u = M.User.query.get(_id=_id)
@@ -446,6 +450,7 @@ class TestUsersSearch(TestController):
                            'Status', 'url', 'Details']
 
 
+@with_nose_compatibility
 class TestUserDetails(TestController):
 
     def test_404(self):
@@ -741,6 +746,7 @@ 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 4d530a57a..5b7386741 100644
--- a/Allura/allura/tests/functional/test_static.py
+++ b/Allura/allura/tests/functional/test_static.py
@@ -19,6 +19,7 @@
 from allura.tests import TestController
 
 
+@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 93b822e60..52041fd0a 100644
--- a/Allura/allura/tests/functional/test_subscriber.py
+++ b/Allura/allura/tests/functional/test_subscriber.py
@@ -21,6 +21,7 @@ from allura.model.notification import Mailbox
 from allura import model as M
 
 
+@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 7f43bea5e..61c76f8b6 100644
--- a/Allura/allura/tests/functional/test_tool_list.py
+++ b/Allura/allura/tests/functional/test_tool_list.py
@@ -19,6 +19,7 @@ from allura.tests import TestController
 from allura.tests import decorators as td
 
 
+@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 c6a903833..08ea4d39c 100644
--- a/Allura/allura/tests/functional/test_trovecategory.py
+++ b/Allura/allura/tests/functional/test_trovecategory.py
@@ -28,6 +28,7 @@ from alluratest.controller import setup_trove_categories
 from allura.tests import decorators as td
 
 
+@with_nose_compatibility
 class TestTroveCategory(TestController):
     @mock.patch('allura.model.project.g.post_event')
     def test_events(self, post_event):
@@ -81,6 +82,7 @@ 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 c3ec9f7e9..6c63e1a8e 100644
--- a/Allura/allura/tests/functional/test_user_profile.py
+++ b/Allura/allura/tests/functional/test_user_profile.py
@@ -25,6 +25,7 @@ from allura.tests import decorators as td
 from allura.tests import TestController
 
 
+@with_nose_compatibility
 class TestUserProfile(TestController):
 
     @td.with_user_project('test-admin')
@@ -270,6 +271,7 @@ class TestUserProfile(TestController):
 
 
 
+@with_nose_compatibility
 class TestUserProfileHasAccessAPI(TestRestApiBase):
 
     @td.with_user_project('test-admin')
diff --git a/Allura/allura/tests/model/test_artifact.py b/Allura/allura/tests/model/test_artifact.py
index b851bb8f3..c4389185a 100644
--- a/Allura/allura/tests/model/test_artifact.py
+++ b/Allura/allura/tests/model/test_artifact.py
@@ -54,7 +54,7 @@ class Checkmessage(M.Message):
 Mapper.compile_all()
 
 
-def setUp():
+def setup_method(self, method):
     setup_basic_test()
     setup_unit_test()
     setup_with_tools()
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index 6250d650e..d9818c232 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -44,7 +44,7 @@ from allura.tests import decorators as td
 from alluratest.controller import setup_basic_test, setup_global_objects, setup_functional_test
 
 
-def setUp():
+def setup_method(self, method):
     setup_basic_test()
     ThreadLocalORMSession.close_all()
     setup_global_objects()
@@ -468,6 +468,7 @@ def test_user_backfill_login_details():
     assert details[1].ua == 'TestBrowser/57'
 
 
+@with_nose_compatibility
 class TestAuditLog:
 
     def test_message_html(self):
diff --git a/Allura/allura/tests/model/test_discussion.py b/Allura/allura/tests/model/test_discussion.py
index 8ef7d1cb7..23b787317 100644
--- a/Allura/allura/tests/model/test_discussion.py
+++ b/Allura/allura/tests/model/test_discussion.py
@@ -38,9 +38,9 @@ from allura.tests import TestController
 from alluratest.controller import setup_global_objects
 
 
-def setUp():
+def setup_method(self, method):
     controller = TestController()
-    controller.setUp()
+    controller.setup_method(method)
     controller.app.get('/wiki/Home/')
     setup_global_objects()
     ThreadLocalORMSession.close_all()
diff --git a/Allura/allura/tests/model/test_filesystem.py b/Allura/allura/tests/model/test_filesystem.py
index 17eb472ae..230b0496c 100644
--- a/Allura/allura/tests/model/test_filesystem.py
+++ b/Allura/allura/tests/model/test_filesystem.py
@@ -37,6 +37,7 @@ class File(M.File):
 Mapper.compile_all()
 
 
+@with_nose_compatibility
 class TestFile(TestCase):
 
     def setUp(self):
diff --git a/Allura/allura/tests/model/test_monq.py b/Allura/allura/tests/model/test_monq.py
index 6e3caa846..74ac43e4f 100644
--- a/Allura/allura/tests/model/test_monq.py
+++ b/Allura/allura/tests/model/test_monq.py
@@ -24,7 +24,7 @@ from alluratest.controller import setup_basic_test, setup_global_objects
 from allura import model as M
 
 
-def setUp():
+def setup_method(self, method):
     setup_basic_test()
     ThreadLocalORMSession.close_all()
     setup_global_objects()
diff --git a/Allura/allura/tests/model/test_neighborhood.py b/Allura/allura/tests/model/test_neighborhood.py
index c5a0e5ce4..093dd3192 100644
--- a/Allura/allura/tests/model/test_neighborhood.py
+++ b/Allura/allura/tests/model/test_neighborhood.py
@@ -25,7 +25,7 @@ from allura.tests import decorators as td
 from alluratest.controller import setup_basic_test, setup_global_objects
 
 
-def setUp():
+def setup_method(self, method):
     setup_basic_test()
     setup_with_tools()
 
diff --git a/Allura/allura/tests/model/test_notification.py b/Allura/allura/tests/model/test_notification.py
index 922e1fff2..046939f95 100644
--- a/Allura/allura/tests/model/test_notification.py
+++ b/Allura/allura/tests/model/test_notification.py
@@ -33,6 +33,7 @@ from allura.tests import decorators as td
 from forgewiki import model as WM
 
 
+@with_nose_compatibility
 class TestNotification(unittest.TestCase):
 
     def setUp(self):
@@ -164,6 +165,7 @@ class TestNotification(unittest.TestCase):
         )
 
 
+@with_nose_compatibility
 class TestPostNotifications(unittest.TestCase):
 
     def setUp(self):
@@ -302,6 +304,7 @@ class TestPostNotifications(unittest.TestCase):
         return M.Notification.post(self.pg, 'metadata')
 
 
+@with_nose_compatibility
 class TestSubscriptionTypes(unittest.TestCase):
 
     def setUp(self):
@@ -342,10 +345,10 @@ class TestSubscriptionTypes(unittest.TestCase):
     def test_message(self):
         self._test_message()
 
-        self.setUp()
+        self.setup_method(method)
         self._test_message()
 
-        self.setUp()
+        self.setup_method(method)
         M.notification.MAILBOX_QUIESCENT = timedelta(minutes=1)
         # will raise "assert msg is not None" since the new message is not 1
         # min old:
@@ -475,6 +478,7 @@ class TestSubscriptionTypes(unittest.TestCase):
         assert count == 1
 
 
+@with_nose_compatibility
 class TestSiteNotification(unittest.TestCase):
     def setUp(self):
         self.note = M.SiteNotification(
diff --git a/Allura/allura/tests/model/test_oauth.py b/Allura/allura/tests/model/test_oauth.py
index 7179bcd75..09ddbf138 100644
--- a/Allura/allura/tests/model/test_oauth.py
+++ b/Allura/allura/tests/model/test_oauth.py
@@ -24,7 +24,7 @@ from allura import model as M
 from alluratest.controller import setup_basic_test, setup_global_objects
 
 
-def setUp():
+def setup_method(self, method):
     setup_basic_test()
     ThreadLocalORMSession.close_all()
     setup_global_objects()
diff --git a/Allura/allura/tests/model/test_project.py b/Allura/allura/tests/model/test_project.py
index 161df7680..382449777 100644
--- a/Allura/allura/tests/model/test_project.py
+++ b/Allura/allura/tests/model/test_project.py
@@ -31,7 +31,7 @@ from allura.lib.exceptions import ToolError, Invalid
 from mock import MagicMock, patch
 
 
-def setUp():
+def setup_method(self, method):
     setup_basic_test()
     setup_with_tools()
 
diff --git a/Allura/allura/tests/model/test_repo.py b/Allura/allura/tests/model/test_repo.py
index 2ceccdef2..d5a27931c 100644
--- a/Allura/allura/tests/model/test_repo.py
+++ b/Allura/allura/tests/model/test_repo.py
@@ -31,6 +31,7 @@ from allura import model as M
 from allura.lib import helpers as h
 
 
+@with_nose_compatibility
 class TestGitLikeTree:
     def test_set_blob(self):
         tree = M.GitLikeTree()
@@ -128,6 +129,7 @@ class RepoTestBase(unittest.TestCase):
         ]
 
 
+@with_nose_compatibility
 class TestLastCommit(unittest.TestCase):
     def setUp(self):
         setup_basic_test()
@@ -400,6 +402,7 @@ class TestLastCommit(unittest.TestCase):
         self.assertEqual(lcd.by_name['file2'], commit3._id)
 
 
+@with_nose_compatibility
 class TestModelCache(unittest.TestCase):
     def setUp(self):
         self.cache = M.repository.ModelCache()
@@ -678,6 +681,7 @@ class TestModelCache(unittest.TestCase):
         session.return_value.expunge.assert_called_once_with(tree1)
 
 
+@with_nose_compatibility
 class TestMergeRequest:
 
     def setUp(self):
diff --git a/Allura/allura/tests/model/test_timeline.py b/Allura/allura/tests/model/test_timeline.py
index d60fea319..39d0368d2 100644
--- a/Allura/allura/tests/model/test_timeline.py
+++ b/Allura/allura/tests/model/test_timeline.py
@@ -22,6 +22,7 @@ from allura.tests import decorators as td
 from alluratest.controller import setup_basic_test, setup_global_objects
 
 
+@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 56e80a383..e92572958 100644
--- a/Allura/allura/tests/scripts/test_create_sitemap_files.py
+++ b/Allura/allura/tests/scripts/test_create_sitemap_files.py
@@ -29,6 +29,7 @@ from allura.lib import helpers as h
 from allura.scripts.create_sitemap_files import CreateSitemapFiles
 
 
+@with_nose_compatibility
 class TestCreateSitemapFiles:
 
     def setUp(self):
diff --git a/Allura/allura/tests/scripts/test_delete_projects.py b/Allura/allura/tests/scripts/test_delete_projects.py
index c5af80d49..0a68e8daa 100644
--- a/Allura/allura/tests/scripts/test_delete_projects.py
+++ b/Allura/allura/tests/scripts/test_delete_projects.py
@@ -27,10 +27,11 @@ from allura.scripts import delete_projects
 from allura.lib import plugin
 
 
+@with_nose_compatibility
 class TestDeleteProjects(TestController):
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         n = M.Neighborhood.query.get(name='Projects')
         admin = M.User.by_username('test-admin')
         self.p_shortname = 'test-delete'
diff --git a/Allura/allura/tests/scripts/test_misc_scripts.py b/Allura/allura/tests/scripts/test_misc_scripts.py
index 104ea9f84..c1981bc8d 100644
--- a/Allura/allura/tests/scripts/test_misc_scripts.py
+++ b/Allura/allura/tests/scripts/test_misc_scripts.py
@@ -24,6 +24,7 @@ from allura import model as M
 from ming.odm import session
 
 
+@with_nose_compatibility
 class TestClearOldNotifications:
 
     def setUp(self):
diff --git a/Allura/allura/tests/scripts/test_reindexes.py b/Allura/allura/tests/scripts/test_reindexes.py
index f5f58556c..a05fb8db5 100644
--- a/Allura/allura/tests/scripts/test_reindexes.py
+++ b/Allura/allura/tests/scripts/test_reindexes.py
@@ -24,6 +24,7 @@ from alluratest.controller import setup_basic_test
 from allura import model as M
 
 
+@with_nose_compatibility
 class TestReindexProjects:
 
     def setUp(self):
@@ -48,6 +49,7 @@ class TestReindexProjects:
         assert M.MonQTask.query.find({'task_name': 'allura.tasks.index_tasks.add_projects'}).count() == 1
 
 
+@with_nose_compatibility
 class TestReindexUsers:
 
     def setUp(self):
diff --git a/Allura/allura/tests/templates/jinja_master/test_lib.py b/Allura/allura/tests/templates/jinja_master/test_lib.py
index 0acf97104..57db256d8 100644
--- a/Allura/allura/tests/templates/jinja_master/test_lib.py
+++ b/Allura/allura/tests/templates/jinja_master/test_lib.py
@@ -34,6 +34,7 @@ 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_app.py b/Allura/allura/tests/test_app.py
index 5448df2a5..03b75c31a 100644
--- a/Allura/allura/tests/test_app.py
+++ b/Allura/allura/tests/test_app.py
@@ -25,9 +25,10 @@ from alluratest.controller import setup_unit_test
 from allura import app
 from allura.lib.app_globals import Icon
 from allura.lib import mail_util
+from allura.tests import with_nose_compatibility
 
 
-def setUp():
+def setup_method(self, method):
     setup_unit_test()
     c.user._id = None
     c.project = mock.Mock()
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index 9f3f3f1bd..9df9840e7 100644
--- a/Allura/allura/tests/test_commands.py
+++ b/Allura/allura/tests/test_commands.py
@@ -35,7 +35,8 @@ from allura.command import base, script, set_neighborhood_features, \
     create_neighborhood, show_models, taskd_cleanup, taskd
 from allura import model as M
 from allura.lib.exceptions import InvalidNBFeatureValueError
-from allura.tests import decorators as td
+from allura.tests import decorators as td, with_nose_compatibility
+
 
 test_config = pkg_resources.resource_filename(
     'allura', '../test.ini') + '#main'
@@ -182,6 +183,7 @@ def test_update_neighborhood():
     assert nb.has_home_tool is False
 
 
+@with_nose_compatibility
 class TestEnsureIndexCommand:
 
     def test_run(self):
@@ -268,9 +270,10 @@ class TestEnsureIndexCommand:
         ]
 
 
+@with_nose_compatibility
 class TestTaskCommand:
 
-    def tearDown(self):
+    def teardown_method(self, method):
         M.MonQTask.query.remove({})
 
     def test_commit(self):
@@ -334,6 +337,7 @@ class TestTaskCommand:
         assert M.MonQTask.query.find().count() == 0
 
 
+@with_nose_compatibility
 class TestTaskdCleanupCommand:
 
     def setUp(self):
@@ -351,7 +355,7 @@ class TestTaskdCleanupCommand:
         self.old_complete_suspicious_tasks = self.cmd_class._complete_suspicious_tasks
         self.cmd_class._complete_suspicious_tasks = lambda x: []
 
-    def tearDown(self):
+    def teardown_method(self, method):
         # need to clean up setUp mocking for unit tests below to work properly
         self.cmd_class._check_taskd_status = self.old_check_taskd_status
         self.cmd_class._check_task = self.old_check_task
@@ -448,6 +452,7 @@ def test_status_log_retries():
     assert cmd._taskd_status.mock_calls == expected_calls
 
 
+@with_nose_compatibility
 class TestShowModels:
 
     def test_show_models(self):
@@ -459,6 +464,7 @@ class TestShowModels:
          - <FieldProperty content>
         ''' in output.captured
 
+@with_nose_compatibility
 class TestReindexAsTask:
 
     cmd = 'allura.command.show_models.ReindexCommand'
@@ -493,6 +499,7 @@ 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 40d68062e..6a3b8a46e 100644
--- a/Allura/allura/tests/test_decorators.py
+++ b/Allura/allura/tests/test_decorators.py
@@ -21,10 +21,11 @@ import random
 import gc
 
 from alluratest.tools import assert_equal, assert_not_equal
-
+from allura.tests import with_nose_compatibility
 from allura.lib.decorators import task, memoize
 
 
+@with_nose_compatibility
 class TestTask(TestCase):
 
     def test_no_params(self):
@@ -59,6 +60,7 @@ 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 854b9457e..025ae11e5 100644
--- a/Allura/allura/tests/test_diff.py
+++ b/Allura/allura/tests/test_diff.py
@@ -18,8 +18,10 @@
 import unittest
 
 from allura.lib.diff import HtmlSideBySideDiff
+from allura.tests import with_nose_compatibility
 
 
+@with_nose_compatibility
 class TestHtmlSideBySideDiff(unittest.TestCase):
 
     def setUp(self):
diff --git a/Allura/allura/tests/test_dispatch.py b/Allura/allura/tests/test_dispatch.py
index 5e48ec2fd..074499936 100644
--- a/Allura/allura/tests/test_dispatch.py
+++ b/Allura/allura/tests/test_dispatch.py
@@ -20,6 +20,7 @@ from allura.tests import TestController
 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 c37901c13..ad11499d8 100644
--- a/Allura/allura/tests/test_globals.py
+++ b/Allura/allura/tests/test_globals.py
@@ -54,7 +54,7 @@ def squish_spaces(text):
     return re.sub(r'[\s\xa0]+', ' ', text)
 
 
-def setUp():
+def setup_method(self, method):
     """Method called by nose once before running the package.  Some functions need it run again to reset data"""
     setup_basic_test()
     setup_unit_test()
@@ -825,6 +825,7 @@ def get_projects_property_in_the_same_order(names, prop):
     return [projects_dict[name] for name in names]
 
 
+@with_nose_compatibility
 class TestCachedMarkdown(unittest.TestCase):
 
     def setUp(self):
@@ -942,6 +943,7 @@ class TestCachedMarkdown(unittest.TestCase):
         self.assertEqual(required_keys, keys)
 
 
+@with_nose_compatibility
 class TestEmojis(unittest.TestCase):
 
     def test_markdown_emoji_atomic(self):
@@ -977,6 +979,7 @@ 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):
@@ -1018,6 +1021,7 @@ class TestUserMentions(unittest.TestCase):
         assert 'class="user-mention"' in output
 
 
+@with_nose_compatibility
 class TestHandlePaging(unittest.TestCase):
 
     def setUp(self):
@@ -1078,6 +1082,7 @@ class TestHandlePaging(unittest.TestCase):
         self.assertEqual(g.handle_paging(10, 'asdf', 30), (10, 0, 0))
 
 
+@with_nose_compatibility
 class TestIconRender:
 
     def setUp(self):
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 8f11145e1..8fb0b4df8 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -46,6 +46,7 @@ def setUp(self):
     setup_basic_test()
 
 
+@with_nose_compatibility
 class TestMakeSafePathPortion(TestCase):
 
     def setUp(self):
@@ -458,6 +459,7 @@ back\\\-slash escaped
         r'tab before \(stuff\)')
 
 
+@with_nose_compatibility
 class TestUrlOpen(TestCase):
 
     @patch('six.moves.urllib.request.urlopen')
@@ -534,6 +536,7 @@ def test_login_overlay():
             raise HTTPUnauthorized()
 
 
+@with_nose_compatibility
 class TestIterEntryPoints(TestCase):
 
     def _make_ep(self, name, cls):
@@ -638,6 +641,7 @@ 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 c0b15c264..d9058862d 100644
--- a/Allura/allura/tests/test_mail_util.py
+++ b/Allura/allura/tests/test_mail_util.py
@@ -45,6 +45,7 @@ config = ConfigProxy(
     return_path='forgemail.return_path')
 
 
+@with_nose_compatibility
 class TestReactor(unittest.TestCase):
 
     def setUp(self):
@@ -209,6 +210,7 @@ Content-Type: text/html; charset="utf-8"
             assert isinstance(part['payload'], str), type(part['payload'])
 
 
+@with_nose_compatibility
 class TestHeader:
 
     @raises(TypeError)
@@ -230,6 +232,7 @@ class TestHeader:
                      '=?utf-8?b?ItGC0LXRgdC90Y/RgtGB0Y8i?= <da...@b.com>')
 
 
+@with_nose_compatibility
 class TestIsAutoreply:
 
     def setUp(self):
@@ -276,6 +279,7 @@ class TestIsAutoreply:
         assert is_autoreply(self.msg)
 
 
+@with_nose_compatibility
 class TestIdentifySender:
 
     @mock.patch('allura.model.EmailAddress')
@@ -325,6 +329,7 @@ def test_parse_message_id():
     ]
 
 
+@with_nose_compatibility
 class TestMailServer:
 
     def setUp(self):
diff --git a/Allura/allura/tests/test_markdown.py b/Allura/allura/tests/test_markdown.py
index 992eec8b0..80ae4f6ad 100644
--- a/Allura/allura/tests/test_markdown.py
+++ b/Allura/allura/tests/test_markdown.py
@@ -21,6 +21,7 @@ import mock
 from allura.lib import markdown_extensions as mde
 
 
+@with_nose_compatibility
 class TestTracRef1(unittest.TestCase):
 
     @mock.patch('allura.lib.markdown_extensions.M.Shortlink.lookup')
@@ -47,6 +48,7 @@ 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')
@@ -76,6 +78,7 @@ 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):
@@ -94,6 +97,7 @@ 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')
@@ -108,6 +112,7 @@ 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 60d4265b6..0a11319f5 100644
--- a/Allura/allura/tests/test_middlewares.py
+++ b/Allura/allura/tests/test_middlewares.py
@@ -21,6 +21,7 @@ from alluratest.tools import assert_not_equal
 from allura.lib.custom_middleware import CORSMiddleware
 
 
+@with_nose_compatibility
 class TestCORSMiddleware:
 
     def setUp(self):
diff --git a/Allura/allura/tests/test_multifactor.py b/Allura/allura/tests/test_multifactor.py
index cfbef4d5e..a7a17d161 100644
--- a/Allura/allura/tests/test_multifactor.py
+++ b/Allura/allura/tests/test_multifactor.py
@@ -34,6 +34,7 @@ from allura.lib.multifactor import RecoveryCodeService, MongodbRecoveryCodeServi
 from allura.lib.multifactor import GoogleAuthenticatorPamFilesystemRecoveryCodeService
 
 
+@with_nose_compatibility
 class TestGoogleAuthenticatorFile:
     sample = textwrap.dedent('''\
         7CL3WL756ISQCU5HRVNAODC44Q
@@ -80,6 +81,7 @@ 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'
@@ -124,6 +126,7 @@ class TestTotpService:
         assert srv.get_qr_code(totp, user)
 
 
+@with_nose_compatibility
 class TestAnyTotpServiceImplementation:
 
     __test__ = False
@@ -171,6 +174,7 @@ class TestAnyTotpServiceImplementation:
             srv.verify(totp, '283397', user)
 
 
+@with_nose_compatibility
 class TestMongodbTotpService(TestAnyTotpServiceImplementation):
 
     __test__ = True
@@ -183,17 +187,19 @@ class TestMongodbTotpService(TestAnyTotpServiceImplementation):
         ming.configure(**config)
 
 
+@with_nose_compatibility
 class TestGoogleAuthenticatorPamFilesystemMixin:
 
     def setUp(self):
         self.totp_basedir = tempfile.mkdtemp(prefix='totp-test', dir=os.getenv('TMPDIR', '/tmp'))
         config['auth.multifactor.totp.filesystem.basedir'] = self.totp_basedir
 
-    def tearDown(self):
+    def teardown_method(self, method):
         if os.path.exists(self.totp_basedir):
             shutil.rmtree(self.totp_basedir)
 
 
+@with_nose_compatibility
 class TestGoogleAuthenticatorPamFilesystemTotpService(TestAnyTotpServiceImplementation,
                                                       TestGoogleAuthenticatorPamFilesystemMixin):
 
@@ -207,6 +213,7 @@ class TestGoogleAuthenticatorPamFilesystemTotpService(TestAnyTotpServiceImplemen
         super().test_rate_limiting()
 
 
+@with_nose_compatibility
 class TestRecoveryCodeService:
 
     def test_generate_one_code(self):
@@ -229,6 +236,7 @@ class TestRecoveryCodeService:
         assert len(recovery.saved_codes) == asint(config.get('auth.multifactor.recovery_code.count', 10))
 
 
+@with_nose_compatibility
 class TestAnyRecoveryCodeServiceImplementation:
 
     __test__ = False
@@ -296,6 +304,7 @@ class TestAnyRecoveryCodeServiceImplementation:
             recovery.verify_and_remove_code(user, '22222')
 
 
+@with_nose_compatibility
 class TestMongodbRecoveryCodeService(TestAnyRecoveryCodeServiceImplementation):
 
     __test__ = True
@@ -309,6 +318,7 @@ class TestMongodbRecoveryCodeService(TestAnyRecoveryCodeServiceImplementation):
         ming.configure(**config)
 
 
+@with_nose_compatibility
 class TestGoogleAuthenticatorPamFilesystemRecoveryCodeService(TestAnyRecoveryCodeServiceImplementation,
                                                               TestGoogleAuthenticatorPamFilesystemMixin):
 
@@ -317,7 +327,7 @@ class TestGoogleAuthenticatorPamFilesystemRecoveryCodeService(TestAnyRecoveryCod
     Service = GoogleAuthenticatorPamFilesystemRecoveryCodeService
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
 
         # make a regular .google-authenticator file first, so recovery keys have somewhere to go
         GoogleAuthenticatorPamFilesystemTotpService().set_secret_key(self.mock_user(),
diff --git a/Allura/allura/tests/test_plugin.py b/Allura/allura/tests/test_plugin.py
index 4ae9d96fc..42c90407e 100644
--- a/Allura/allura/tests/test_plugin.py
+++ b/Allura/allura/tests/test_plugin.py
@@ -47,6 +47,7 @@ from allura.tests.decorators import audits
 from alluratest.controller import setup_basic_test, setup_global_objects
 
 
+@with_nose_compatibility
 class TestProjectRegistrationProvider:
 
     def setUp(self):
@@ -84,6 +85,7 @@ class TestProjectRegistrationProvider:
         assert_raises(ProjectConflict, v, 'thisislegit', neighborhood=nbhd)
 
 
+@with_nose_compatibility
 class TestProjectRegistrationProviderParseProjectFromUrl:
 
     def setUp(self):
@@ -158,6 +160,7 @@ class UserMock:
         return self._projects
 
 
+@with_nose_compatibility
 class TestProjectRegistrationProviderPhoneVerification:
 
     def setUp(self):
@@ -275,12 +278,14 @@ class TestProjectRegistrationProviderPhoneVerification:
                     assert result == g.phone_service.verify.return_value
             assert 5 == g.phone_service.verify.call_count
 
+@with_nose_compatibility
 class TestThemeProvider:
 
     @patch('allura.app.g')
     @patch('allura.lib.plugin.g')
     def test_app_icon_str(self, plugin_g, app_g):
-        class TestApp(Application):
+        @with_nose_compatibility
+class TestApp(Application):
             icons = {
                 24: 'images/testapp_24.png',
             }
@@ -297,7 +302,8 @@ class TestThemeProvider:
 
     @patch('allura.app.g')
     def test_app_icon_app(self, g):
-        class TestApp(Application):
+        @with_nose_compatibility
+class TestApp(Application):
             icons = {
                 24: 'images/testapp_24.png',
             }
@@ -307,6 +313,7 @@ class TestThemeProvider:
         g.theme_href.assert_called_with('images/testapp_24.png')
 
 
+@with_nose_compatibility
 class TestThemeProvider_notifications:
 
     Provider = ThemeProvider
@@ -628,6 +635,7 @@ class TestThemeProvider_notifications:
         assert get_note[1] == 'testid-2-False'
 
 
+@with_nose_compatibility
 class TestLocalAuthenticationProvider:
 
     def setUp(self):
@@ -742,6 +750,7 @@ class TestLocalAuthenticationProvider:
         assert detail.ua == 'mybrowser'
 
 
+@with_nose_compatibility
 class TestAuthenticationProvider:
 
     def setUp(self):
diff --git a/Allura/allura/tests/test_scripttask.py b/Allura/allura/tests/test_scripttask.py
index 4a06791a3..7b30ba353 100644
--- a/Allura/allura/tests/test_scripttask.py
+++ b/Allura/allura/tests/test_scripttask.py
@@ -21,10 +21,12 @@ import mock
 from allura.scripts.scripttask import ScriptTask
 
 
+@with_nose_compatibility
 class TestScriptTask(unittest.TestCase):
 
     def setUp(self):
-        class TestScriptTask(ScriptTask):
+        @with_nose_compatibility
+class TestScriptTask(ScriptTask):
             _parser = mock.Mock()
 
             @classmethod
diff --git a/Allura/allura/tests/test_security.py b/Allura/allura/tests/test_security.py
index 54680e331..0384fd9cb 100644
--- a/Allura/allura/tests/test_security.py
+++ b/Allura/allura/tests/test_security.py
@@ -53,6 +53,7 @@ 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 16815e600..038836f19 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -54,6 +54,7 @@ from allura.tests import decorators as td
 from allura.lib.decorators import event_handler, task
 
 
+@with_nose_compatibility
 class TestRepoTasks(unittest.TestCase):
 
     @mock.patch('allura.tasks.repo_tasks.c.app')
@@ -98,6 +99,7 @@ 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(self):
@@ -156,6 +158,7 @@ class TestEventTasks(unittest.TestCase):
             assert ('assert %d' % x) in t.result
 
 
+@with_nose_compatibility
 class TestIndexTasks(unittest.TestCase):
 
     def setUp(self):
@@ -241,6 +244,7 @@ class TestIndexTasks(unittest.TestCase):
         solr.delete.assert_called_once_with(q=solr_query)
 
 
+@with_nose_compatibility
 class TestMailTasks(unittest.TestCase):
 
     def setUp(self):
@@ -536,9 +540,10 @@ I'm not here'''
             assert hm.call_count == 0
 
 
+@with_nose_compatibility
 class TestUserNotificationTasks(TestController):
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.setup_with_tools()
 
     @td.with_wiki
@@ -567,6 +572,7 @@ class TestUserNotificationTasks(TestController):
         assert 'auth/subscriptions#notifications' in text
 
 
+@with_nose_compatibility
 class TestNotificationTasks(unittest.TestCase):
 
     def setUp(self):
@@ -613,6 +619,7 @@ class _TestArtifact(M.Artifact):
             text=self.text)
 
 
+@with_nose_compatibility
 class TestExportTasks(unittest.TestCase):
 
     def setUp(self):
@@ -621,7 +628,7 @@ class TestExportTasks(unittest.TestCase):
         project = M.Project.query.get(shortname='test')
         shutil.rmtree(project.bulk_export_path(tg.config['bulk_export_path']), ignore_errors=True)
 
-    def tearDown(self):
+    def teardown_method(self, method):
         project = M.Project.query.get(shortname='test')
         shutil.rmtree(project.bulk_export_path(tg.config['bulk_export_path']), ignore_errors=True)
 
@@ -667,6 +674,7 @@ 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 e82c529e3..1f2b1eeed 100644
--- a/Allura/allura/tests/test_utils.py
+++ b/Allura/allura/tests/test_utils.py
@@ -47,6 +47,7 @@ from allura.lib import helpers as h
 
 
 @patch.dict('allura.lib.utils.tg.config', clear=True, foo='bar', baz='true')
+@with_nose_compatibility
 class TestConfigProxy(unittest.TestCase):
 
     def setUp(self):
@@ -67,6 +68,7 @@ class TestConfigProxy(unittest.TestCase):
         self.assertEqual(self.cp.get_bool("fake"), False)
 
 
+@with_nose_compatibility
 class TestChunkedIterator(unittest.TestCase):
 
     def setUp(self):
@@ -98,6 +100,7 @@ class TestChunkedIterator(unittest.TestCase):
         assert chunks[1][0].username == 'sample-user-3'
 
 
+@with_nose_compatibility
 class TestChunkedList(unittest.TestCase):
 
     def test_chunked_list(self):
@@ -108,6 +111,7 @@ 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(self):
@@ -174,6 +178,7 @@ class TestAntispam(unittest.TestCase):
         return encrypted_form
 
 
+@with_nose_compatibility
 class TestTruthyCallable(unittest.TestCase):
 
     def test_everything(self):
@@ -195,6 +200,7 @@ class TestTruthyCallable(unittest.TestCase):
         assert false_predicate == f
 
 
+@with_nose_compatibility
 class TestCaseInsensitiveDict(unittest.TestCase):
 
     def test_everything(self):
@@ -214,6 +220,7 @@ class TestCaseInsensitiveDict(unittest.TestCase):
         assert d == utils.CaseInsensitiveDict(Foo=1, bar=2)
 
 
+@with_nose_compatibility
 class TestLineAnchorCodeHtmlFormatter(unittest.TestCase):
 
     def test_render(self):
@@ -234,6 +241,7 @@ 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):
@@ -244,6 +252,7 @@ class TestIsTextFile(unittest.TestCase):
         assert not utils.is_text_file(open(bin_file, 'rb').read())
 
 
+@with_nose_compatibility
 class TestCodeStats(unittest.TestCase):
 
     def setUp(self):
@@ -268,6 +277,7 @@ 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 640ab9c50..cb9d5dd40 100644
--- a/Allura/allura/tests/test_validators.py
+++ b/Allura/allura/tests/test_validators.py
@@ -24,9 +24,10 @@ 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 allura.tests import with_nose_compatibility
 
 
-def setUp():
+def setup_method(self, method):
     setup_basic_test()
 
 
@@ -35,6 +36,7 @@ def dummy_task(*args, **kw):
     pass
 
 
+@with_nose_compatibility
 class TestJsonConverter(unittest.TestCase):
     val = v.JsonConverter
 
@@ -48,6 +50,7 @@ class TestJsonConverter(unittest.TestCase):
             self.val.to_python('3')
 
 
+@with_nose_compatibility
 class TestJsonFile(unittest.TestCase):
     val = v.JsonFile
 
@@ -64,6 +67,7 @@ class TestJsonFile(unittest.TestCase):
             self.val.to_python(self.FieldStorage('{'))
 
 
+@with_nose_compatibility
 class TestUserMapFile(unittest.TestCase):
     val = v.UserMapJsonFile()
 
@@ -86,6 +90,7 @@ class TestUserMapFile(unittest.TestCase):
             self.FieldStorage('{"user_old": "user_new"}')))
 
 
+@with_nose_compatibility
 class TestUserValidator(unittest.TestCase):
     val = v.UserValidator
 
@@ -99,6 +104,7 @@ class TestUserValidator(unittest.TestCase):
         self.assertEqual(str(cm.exception), "Invalid username")
 
 
+@with_nose_compatibility
 class TestAnonymousValidator(unittest.TestCase):
     val = v.AnonymousValidator
 
@@ -115,6 +121,7 @@ class TestAnonymousValidator(unittest.TestCase):
         self.assertEqual(str(cm.exception), "Log in to Mark as Private")
 
 
+@with_nose_compatibility
 class TestMountPointValidator(unittest.TestCase):
 
     @patch('allura.lib.validators.c')
@@ -175,6 +182,7 @@ class TestMountPointValidator(unittest.TestCase):
         self.assertEqual('wiki-0', val.to_python(None))
 
 
+@with_nose_compatibility
 class TestTaskValidator(unittest.TestCase):
     val = v.TaskValidator
 
@@ -205,6 +213,7 @@ class TestTaskValidator(unittest.TestCase):
                          '"allura.tests.test_validators.setUp" is not a task.')
 
 
+@with_nose_compatibility
 class TestPathValidator(unittest.TestCase):
     val = v.PathValidator(strip=True, if_missing={}, if_empty={})
 
@@ -253,6 +262,7 @@ class TestPathValidator(unittest.TestCase):
         self.assertEqual({}, self.val.to_python(''))
 
 
+@with_nose_compatibility
 class TestUrlValidator(unittest.TestCase):
     val = v.URL
 
@@ -271,6 +281,7 @@ 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
 
@@ -289,6 +300,7 @@ 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 84b758013..cfc641674 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -59,6 +59,7 @@ 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(self):
         setup_basic_test()
@@ -75,7 +76,7 @@ class TestWebhookBase:
             secret='secret')
         session(self.wh).flush(self.wh)
 
-    def tearDown(self):
+    def teardown_method(self, method):
         for p in self.patches:
             p.stop()
 
@@ -90,6 +91,7 @@ class TestWebhookBase:
         return [repo_init]
 
 
+@with_nose_compatibility
 class TestValidators(TestWebhookBase):
     @with_git2
     def test_webhook_validator(self):
@@ -127,9 +129,10 @@ class TestValidators(TestWebhookBase):
         assert v.to_python(str(wh._id)) == wh
 
 
+@with_nose_compatibility
 class TestWebhookController(TestController):
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.patches = self.monkey_patch()
         for p in self.patches:
             p.start()
@@ -138,8 +141,8 @@ class TestWebhookController(TestController):
         self.git = self.project.app_instance('src')
         self.url = str(self.git.admin_url + 'webhooks')
 
-    def tearDown(self):
-        super().tearDown()
+    def teardown_method(self, method):
+        super().teardown_method(method)
         for p in self.patches:
             p.stop()
 
@@ -418,6 +421,7 @@ 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(self, *args, **kw):
         super().setUp(*args, **kw)
@@ -522,6 +526,7 @@ 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):
@@ -628,6 +633,7 @@ 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() ==
@@ -669,9 +675,10 @@ class TestModels(TestWebhookBase):
         dd.assert_equal(self.wh.__json__(), expected)
 
 
+@with_nose_compatibility
 class TestWebhookRestController(TestRestApiBase):
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.patches = self.monkey_patch()
         for p in self.patches:
             p.start()
@@ -689,8 +696,8 @@ class TestWebhookRestController(TestRestApiBase):
             session(webhook).flush(webhook)
             self.webhooks.append(webhook)
 
-    def tearDown(self):
-        super().tearDown()
+    def teardown_method(self, method):
+        super().teardown_method(method)
         for p in self.patches:
             p.stop()
 
diff --git a/Allura/allura/tests/unit/__init__.py b/Allura/allura/tests/unit/__init__.py
index b6450991d..34ba63e6e 100644
--- a/Allura/allura/tests/unit/__init__.py
+++ b/Allura/allura/tests/unit/__init__.py
@@ -31,7 +31,7 @@ class MockPatchTestCase:
         for patch_instance in self._patch_instances:
             patch_instance.__enter__()
 
-    def tearDown(self):
+    def teardown_method(self, method):
         for patch_instance in self._patch_instances:
             patch_instance.__exit__(None, None, None)
 
@@ -39,5 +39,5 @@ class MockPatchTestCase:
 class WithDatabase(MockPatchTestCase):
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         clear_all_database_tables()
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 6e3dda279..ec5a6acd0 100644
--- a/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
+++ b/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
@@ -26,6 +26,7 @@ from allura.controllers.discuss import ModerationController
 from allura.tests.unit import patches
 
 
+@with_nose_compatibility
 class TestWhenModerating(WithDatabase):
     patches = [patches.fake_app_patch,
                patches.fake_user_patch,
@@ -34,7 +35,7 @@ class TestWhenModerating(WithDatabase):
                patches.disable_notifications_patch]
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         post = create_post('mypost')
         discussion_controller = Mock(
             discussion=Mock(_id=post.discussion_id),
@@ -73,6 +74,7 @@ class TestWhenModerating(WithDatabase):
         return model.Post.query.get(slug='mypost', deleted=False)
 
 
+@with_nose_compatibility
 class TestIndexWithNoPosts(WithDatabase):
     patches = [patches.fake_app_patch]
 
@@ -82,11 +84,12 @@ class TestIndexWithNoPosts(WithDatabase):
         assert template_variables['posts'].all() == []
 
 
+@with_nose_compatibility
 class TestIndexWithAPostInTheDiscussion(WithDatabase):
     patches = [patches.fake_app_patch]
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.post = create_post('mypost')
         discussion = self.post.discussion
         self.template_variables = show_moderation_index(discussion)
diff --git a/Allura/allura/tests/unit/phone/test_nexmo.py b/Allura/allura/tests/unit/phone/test_nexmo.py
index 1a51b4455..c919f4bc9 100644
--- a/Allura/allura/tests/unit/phone/test_nexmo.py
+++ b/Allura/allura/tests/unit/phone/test_nexmo.py
@@ -23,6 +23,7 @@ from alluratest.tools import assert_in, assert_not_in
 from allura.lib.phone.nexmo import NexmoPhoneService
 
 
+@with_nose_compatibility
 class TestPhoneService:
 
     def setUp(self):
diff --git a/Allura/allura/tests/unit/phone/test_phone_service.py b/Allura/allura/tests/unit/phone/test_phone_service.py
index 62dcbb4ec..2d69d48bc 100644
--- a/Allura/allura/tests/unit/phone/test_phone_service.py
+++ b/Allura/allura/tests/unit/phone/test_phone_service.py
@@ -30,6 +30,7 @@ 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 8dc5a05a7..00885460e 100644
--- a/Allura/allura/tests/unit/spam/test_akismet.py
+++ b/Allura/allura/tests/unit/spam/test_akismet.py
@@ -29,6 +29,7 @@ from allura.lib.spam.akismetfilter import AKISMET_AVAILABLE, AkismetSpamFilter
 
 
 @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 02582135b..bf6930331 100644
--- a/Allura/allura/tests/unit/spam/test_spam_filter.py
+++ b/Allura/allura/tests/unit/spam/test_spam_filter.py
@@ -46,6 +46,7 @@ class MockFilter2(SpamFilter):
         return True
 
 
+@with_nose_compatibility
 class TestSpamFilter(unittest.TestCase):
 
     def test_check(self):
@@ -74,6 +75,7 @@ class TestSpamFilter(unittest.TestCase):
         self.assertTrue(log.exception.called)
 
 
+@with_nose_compatibility
 class TestSpamFilterFunctional:
 
     def setUp(self):
@@ -93,6 +95,7 @@ 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 19da2d2b5..fa7481a1d 100644
--- a/Allura/allura/tests/unit/spam/test_stopforumspam.py
+++ b/Allura/allura/tests/unit/spam/test_stopforumspam.py
@@ -24,6 +24,7 @@ from alluratest.tools import assert_equal
 from allura.lib.spam.stopforumspamfilter import StopForumSpamSpamFilter
 
 
+@with_nose_compatibility
 class TestStopForumSpam:
 
     def setUp(self):
diff --git a/Allura/allura/tests/unit/test_app.py b/Allura/allura/tests/unit/test_app.py
index d64eaf0c0..c3457f19c 100644
--- a/Allura/allura/tests/unit/test_app.py
+++ b/Allura/allura/tests/unit/test_app.py
@@ -26,6 +26,7 @@ from allura.tests.unit.patches import fake_app_patch
 from allura.tests.unit.factories import create_project, create_app_config
 
 
+@with_nose_compatibility
 class TestApplication(TestCase):
 
     def test_validate_mount_point(self):
@@ -54,6 +55,7 @@ class TestApplication(TestCase):
         self.assertEqual(f('does_not_exist'), '')
 
 
+@with_nose_compatibility
 class TestInstall(WithDatabase):
     patches = [fake_app_patch]
 
@@ -66,11 +68,12 @@ class TestInstall(WithDatabase):
         return model.Discussion.query.find().count()
 
 
+@with_nose_compatibility
 class TestDefaultDiscussion(WithDatabase):
     patches = [fake_app_patch]
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         install_app()
         self.discussion = model.Discussion.query.get(
             shortname='my_mounted_app')
@@ -86,11 +89,12 @@ class TestDefaultDiscussion(WithDatabase):
         assert self.discussion.shortname == 'my_mounted_app'
 
 
+@with_nose_compatibility
 class TestAppDefaults(WithDatabase):
     patches = [fake_app_patch]
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.app = install_app()
 
     def test_that_it_has_an_empty_sidebar_menu(self):
diff --git a/Allura/allura/tests/unit/test_artifact.py b/Allura/allura/tests/unit/test_artifact.py
index 895eb74ec..59659bd5b 100644
--- a/Allura/allura/tests/unit/test_artifact.py
+++ b/Allura/allura/tests/unit/test_artifact.py
@@ -20,6 +20,7 @@ import unittest
 from allura import model as M
 
 
+@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 a9b4a8acf..8a5a8b09e 100644
--- a/Allura/allura/tests/unit/test_discuss.py
+++ b/Allura/allura/tests/unit/test_discuss.py
@@ -22,6 +22,7 @@ from allura.tests.unit import WithDatabase
 from allura.tests.unit.patches import fake_app_patch
 
 
+@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 2c3513109..6a26c26c5 100644
--- a/Allura/allura/tests/unit/test_helpers/test_ago.py
+++ b/Allura/allura/tests/unit/test_helpers/test_ago.py
@@ -23,6 +23,7 @@ from alluratest.tools import assert_equal
 from allura.lib import helpers
 
 
+@with_nose_compatibility
 class TestAgo:
 
     def setUp(self):
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 194394aef..281582804 100644
--- a/Allura/allura/tests/unit/test_helpers/test_set_context.py
+++ b/Allura/allura/tests/unit/test_helpers/test_set_context.py
@@ -28,10 +28,11 @@ from allura.tests.unit.factories import (create_project,
                                          create_neighborhood)
 
 
+@with_nose_compatibility
 class TestWhenProjectIsFoundAndAppIsNot(WithDatabase):
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.myproject = create_project('myproject')
         set_context('myproject', neighborhood=self.myproject.neighborhood)
 
@@ -42,10 +43,11 @@ class TestWhenProjectIsFoundAndAppIsNot(WithDatabase):
         assert c.app is None, c.app
 
 
+@with_nose_compatibility
 class TestWhenProjectIsFoundInNeighborhood(WithDatabase):
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.myproject = create_project('myproject')
         set_context('myproject', neighborhood=self.myproject.neighborhood)
 
@@ -56,11 +58,12 @@ class TestWhenProjectIsFoundInNeighborhood(WithDatabase):
         assert c.app is None
 
 
+@with_nose_compatibility
 class TestWhenAppIsFoundByID(WithDatabase):
     patches = [patches.project_app_loading_patch]
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.myproject = create_project('myproject')
         self.app_config = create_app_config(self.myproject, 'my_mounted_app')
         set_context('myproject', app_config_id=self.app_config._id,
@@ -73,11 +76,12 @@ 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]
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.myproject = create_project('myproject')
         self.app_config = create_app_config(self.myproject, 'my_mounted_app')
         set_context('myproject', mount_point='my_mounted_app',
@@ -91,6 +95,7 @@ class TestWhenAppIsFoundByMountPoint(WithDatabase):
             'my_mounted_app')
 
 
+@with_nose_compatibility
 class TestWhenProjectIsNotFound(WithDatabase):
 
     def test_that_it_raises_an_exception(self):
@@ -108,6 +113,7 @@ 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 021d2016a..2d59ebc50 100644
--- a/Allura/allura/tests/unit/test_ldap_auth_provider.py
+++ b/Allura/allura/tests/unit/test_ldap_auth_provider.py
@@ -34,6 +34,7 @@ from allura import model as M
 import six
 
 
+@with_nose_compatibility
 class TestLdapAuthenticationProvider:
 
     def setUp(self):
diff --git a/Allura/allura/tests/unit/test_mixins.py b/Allura/allura/tests/unit/test_mixins.py
index 0e429d63e..f5208c9ab 100644
--- a/Allura/allura/tests/unit/test_mixins.py
+++ b/Allura/allura/tests/unit/test_mixins.py
@@ -19,6 +19,7 @@ from mock import Mock
 from allura.model import VotableArtifact
 
 
+@with_nose_compatibility
 class TestVotableArtifact:
 
     def setUp(self):
diff --git a/Allura/allura/tests/unit/test_package_path_loader.py b/Allura/allura/tests/unit/test_package_path_loader.py
index d00529918..a9946b485 100644
--- a/Allura/allura/tests/unit/test_package_path_loader.py
+++ b/Allura/allura/tests/unit/test_package_path_loader.py
@@ -27,6 +27,7 @@ from tg import config
 from allura.lib.package_path_loader import PackagePathLoader
 
 
+@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 6d6bc4e2a..b6591176e 100644
--- a/Allura/allura/tests/unit/test_post_model.py
+++ b/Allura/allura/tests/unit/test_post_model.py
@@ -24,12 +24,13 @@ from allura.tests.unit import patches
 from allura.tests.unit.factories import create_post
 
 
+@with_nose_compatibility
 class TestPostModel(WithDatabase):
     patches = [patches.fake_app_patch,
                patches.disable_notifications_patch]
 
     def setUp(self):
-        super().setUp()
+        super().setup_method(method)
         self.post = create_post('mypost')
 
     def test_that_it_is_pending_by_default(self):
diff --git a/Allura/allura/tests/unit/test_project.py b/Allura/allura/tests/unit/test_project.py
index 37af31d18..e5ce38aa9 100644
--- a/Allura/allura/tests/unit/test_project.py
+++ b/Allura/allura/tests/unit/test_project.py
@@ -25,6 +25,7 @@ from allura.lib import helpers as h
 from allura.app import SitemapEntry
 
 
+@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 15258ee5f..c8b7e4d74 100644
--- a/Allura/allura/tests/unit/test_repo.py
+++ b/Allura/allura/tests/unit/test_repo.py
@@ -33,6 +33,7 @@ from allura.model.repo_refresh import (
 )
 
 
+@with_nose_compatibility
 class TestTopoSort(unittest.TestCase):
 
     def test_commit_dates_out_of_order(self):
@@ -85,6 +86,7 @@ def blob(name, id):
     return b
 
 
+@with_nose_compatibility
 class TestTree(unittest.TestCase):
 
     @patch('allura.model.repository.Tree.__getitem__')
@@ -102,6 +104,7 @@ class TestTree(unittest.TestCase):
         getitem().__getitem__().__getitem__.assert_called_with('file.txt')
 
 
+@with_nose_compatibility
 class TestBlob(unittest.TestCase):
 
     def test_pypeline_view(self):
@@ -142,6 +145,7 @@ class TestBlob(unittest.TestCase):
         assert blob.has_html_view == True
 
 
+@with_nose_compatibility
 class TestCommit(unittest.TestCase):
 
     def test_activity_extras(self):
@@ -243,6 +247,7 @@ class TestCommit(unittest.TestCase):
         commit.get_tree.assert_called_with(create=True)
 
 
+@with_nose_compatibility
 class TestZipDir(unittest.TestCase):
 
     @patch('allura.model.repository.Popen')
@@ -286,6 +291,7 @@ class TestZipDir(unittest.TestCase):
         self.assertTrue("STDERR: 2" in emsg)
 
 
+@with_nose_compatibility
 class TestPrefixPathsUnion(unittest.TestCase):
 
     def test_disjoint(self):
@@ -304,6 +310,7 @@ class TestPrefixPathsUnion(unittest.TestCase):
         self.assertEqual(prefix_paths_union(a, b), {'a2'})
 
 
+@with_nose_compatibility
 class TestGroupCommits:
 
     def setUp(self):
diff --git a/Allura/allura/tests/unit/test_session.py b/Allura/allura/tests/unit/test_session.py
index f71e1f09f..b69a9f49d 100644
--- a/Allura/allura/tests/unit/test_session.py
+++ b/Allura/allura/tests/unit/test_session.py
@@ -74,6 +74,7 @@ def test_extensions_cm_flush_raises():
     assert session._kwargs['extensions'] == []
 
 
+@with_nose_compatibility
 class TestSessionExtension(TestCase):
 
     def _mock_indexable(self, **kw):
@@ -83,6 +84,7 @@ class TestSessionExtension(TestCase):
         return m
 
 
+@with_nose_compatibility
 class TestIndexerSessionExtension(TestSessionExtension):
 
     def setUp(self):
@@ -121,6 +123,7 @@ class TestIndexerSessionExtension(TestSessionExtension):
         assert self.tasks['add'].post.call_count == 0
 
 
+@with_nose_compatibility
 class TestArtifactSessionExtension(TestSessionExtension):
 
     def setUp(self):
@@ -150,6 +153,7 @@ class TestArtifactSessionExtension(TestSessionExtension):
         assert index_tasks.add_artifacts.post.call_count == 0
 
 
+@with_nose_compatibility
 class TestBatchIndexer(TestCase):
 
     def setUp(self):
diff --git a/Allura/allura/tests/unit/test_sitemapentry.py b/Allura/allura/tests/unit/test_sitemapentry.py
index bdd6399d1..cbe76f874 100644
--- a/Allura/allura/tests/unit/test_sitemapentry.py
+++ b/Allura/allura/tests/unit/test_sitemapentry.py
@@ -21,6 +21,7 @@ from mock import Mock
 from allura.app import SitemapEntry
 
 
+@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 8647aecef..453b52af9 100644
--- a/Allura/allura/tests/unit/test_solr.py
+++ b/Allura/allura/tests/unit/test_solr.py
@@ -28,6 +28,7 @@ from allura.lib.solr import Solr, escape_solr_arg
 from allura.lib.search import search_app, SearchIndexable
 
 
+@with_nose_compatibility
 class TestSolr(unittest.TestCase):
 
     @mock.patch('allura.lib.solr.pysolr')
@@ -117,6 +118,7 @@ class TestSolr(unittest.TestCase):
             'username_s:admin1 || username_s:root', fq=fq, ignore_errors=False)
 
 
+@with_nose_compatibility
 class TestSearchIndexable(unittest.TestCase):
 
     def setUp(self):
@@ -141,6 +143,7 @@ class TestSearchIndexable(unittest.TestCase):
         assert self.obj.solarize() == dict(text='<script>a(1)</script>')
 
 
+@with_nose_compatibility
 class TestSearch_app(unittest.TestCase):
 
     def setUp(self):
diff --git a/Allura/docs/development/testing.rst b/Allura/docs/development/testing.rst
index 0aafb0f30..4fb18b27f 100644
--- a/Allura/docs/development/testing.rst
+++ b/Allura/docs/development/testing.rst
@@ -66,7 +66,7 @@ fully-loaded wsgi app, you can do something like this:
     from allura.lib import helpers a h
     from allura import model as M
 
-    def setUp():
+    def setup_method(self, method):
         # set up globals
         setup_unit_test()
 
diff --git a/AlluraTest/alluratest/controller.py b/AlluraTest/alluratest/controller.py
index 4c7c01bc4..8f3099b39 100644
--- a/AlluraTest/alluratest/controller.py
+++ b/AlluraTest/alluratest/controller.py
@@ -17,6 +17,7 @@
 
 """Unit and functional test suite for allura."""
 import os
+from allura.tests.pytest_helpers import with_nose_compatibility
 import six.moves.urllib.request
 import six.moves.urllib.parse
 import six.moves.urllib.error
@@ -158,12 +159,13 @@ def setup_trove_categories():
         create_trove_categories.run([''])
 
 
+@with_nose_compatibility
 class TestController:
 
     application_under_test = 'main'
     validate_skip = False
 
-    def setUp(self):
+    def setup_method(self, method):
         """Method called by nose before running each test"""
         pkg = self.__module__.split('.')[0]
         self.app = ValidatingTestApp(
@@ -175,7 +177,7 @@ class TestController:
             self.smtp_mock = mock.patch('allura.lib.mail_util.smtplib.SMTP')
             self.smtp_mock.start()
 
-    def tearDown(self):
+    def teardown_method(self, method):
         """Method called by nose after running each test"""
         if asbool(tg.config.get('smtp.mock')):
             self.smtp_mock.stop()
@@ -206,10 +208,11 @@ class TestController:
                 return f
 
 
+@with_nose_compatibility
 class TestRestApiBase(TestController):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self._use_token = None
         self._token_cache = {}