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/23 18:43:28 UTC

[allura] 03/10: All tests in ./Allura collecting, and test_auth completely passing

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

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

commit c39d87e2e50fa6d408b7b65d52fcc5049145b750
Author: Kenton Taylor <kt...@slashdotmedia.com>
AuthorDate: Tue Aug 23 14:06:50 2022 +0000

    All tests in ./Allura collecting, and test_auth completely passing
---
 Allura/allura/tests/functional/test_admin.py       |  3 +-
 Allura/allura/tests/functional/test_auth.py        |  6 +--
 Allura/allura/tests/functional/test_discuss.py     |  3 +-
 Allura/allura/tests/functional/test_feeds.py       |  3 +-
 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/tests/functional/test_neighborhood.py   |  1 +
 Allura/allura/tests/functional/test_newforge.py    |  1 +
 .../tests/functional/test_personal_dashboard.py    |  3 +-
 Allura/allura/tests/functional/test_rest.py        |  3 +-
 Allura/allura/tests/functional/test_root.py        |  5 ++-
 Allura/allura/tests/functional/test_search.py      |  1 +
 Allura/allura/tests/functional/test_site_admin.py  |  5 ++-
 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  |  1 +
 .../allura/tests/functional/test_user_profile.py   |  1 +
 Allura/allura/tests/model/test_artifact.py         | 30 ++++++-------
 Allura/allura/tests/model/test_auth.py             | 39 ++++++++---------
 Allura/allura/tests/model/test_discussion.py       | 42 +++++++++----------
 Allura/allura/tests/model/test_filesystem.py       |  3 +-
 Allura/allura/tests/model/test_monq.py             |  2 +-
 Allura/allura/tests/model/test_neighborhood.py     |  2 +-
 Allura/allura/tests/model/test_notification.py     |  9 ++--
 Allura/allura/tests/model/test_oauth.py            |  2 +-
 Allura/allura/tests/model/test_project.py          |  6 +--
 Allura/allura/tests/model/test_repo.py             |  9 ++--
 Allura/allura/tests/model/test_timeline.py         |  3 +-
 Allura/allura/tests/pytest_helpers.py              | 49 ++++++++++++++++++++++
 .../tests/scripts/test_create_sitemap_files.py     |  3 +-
 .../allura/tests/scripts/test_delete_projects.py   |  3 +-
 Allura/allura/tests/scripts/test_misc_scripts.py   |  3 +-
 Allura/allura/tests/scripts/test_reindexes.py      |  5 ++-
 .../tests/templates/jinja_master/test_lib.py       |  3 +-
 Allura/allura/tests/test_app.py                    |  2 +-
 Allura/allura/tests/test_commands.py               |  7 ++--
 Allura/allura/tests/test_decorators.py             |  2 +-
 Allura/allura/tests/test_diff.py                   |  4 +-
 Allura/allura/tests/test_dispatch.py               |  1 +
 Allura/allura/tests/test_globals.py                | 23 +++++-----
 Allura/allura/tests/test_helpers.py                |  5 ++-
 Allura/allura/tests/test_mail_util.py              |  7 ++--
 Allura/allura/tests/test_markdown.py               |  1 +
 Allura/allura/tests/test_middlewares.py            |  3 +-
 Allura/allura/tests/test_multifactor.py            |  9 ++--
 Allura/allura/tests/test_plugin.py                 | 17 ++++----
 Allura/allura/tests/test_scripttask.py             |  6 +--
 Allura/allura/tests/test_security.py               |  2 +
 Allura/allura/tests/test_tasks.py                  | 13 +++---
 Allura/allura/tests/test_utils.py                  |  9 ++--
 Allura/allura/tests/test_validators.py             |  2 +-
 Allura/allura/tests/test_webhooks.py               |  7 ++--
 Allura/allura/tests/unit/__init__.py               |  6 +--
 .../test_discussion_moderation_controller.py       |  5 ++-
 Allura/allura/tests/unit/phone/test_nexmo.py       |  3 +-
 .../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/tests/unit/spam/test_stopforumspam.py   |  3 +-
 Allura/allura/tests/unit/test_app.py               |  5 ++-
 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  |  3 +-
 .../tests/unit/test_helpers/test_set_context.py    |  9 ++--
 .../allura/tests/unit/test_ldap_auth_provider.py   |  3 +-
 Allura/allura/tests/unit/test_mixins.py            |  3 +-
 .../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              |  3 +-
 Allura/allura/tests/unit/test_session.py           |  7 ++--
 Allura/allura/tests/unit/test_sitemapentry.py      |  1 +
 Allura/allura/tests/unit/test_solr.py              |  5 ++-
 ForgeTracker/forgetracker/tests/test_app.py        |  1 +
 76 files changed, 278 insertions(+), 168 deletions(-)

diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index 0f5c3b30e..8c1caab3a 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -32,6 +32,7 @@ import six
 
 import allura
 from allura.tests import TestController
+from allura.tests.pytest_helpers import with_nose_compatibility
 from allura.tests import decorators as td
 from allura.tests.decorators import audits, out_audits
 from alluratest.controller import TestRestApiBase, setup_trove_categories
@@ -963,7 +964,7 @@ class TestProjectAdmin(TestController):
 @with_nose_compatibility
 class TestExport(TestController):
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.setup_with_tools()
 
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index affb5dcfb..47a5013a0 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -1570,7 +1570,7 @@ class TestPreferences(TestController):
 class TestPasswordReset(TestController):
     test_primary_email = 'testprimaryaddr@mail.com'
 
-    def setUp(self):
+    def setup_method(self, method):
         super().setup_method(method)
         # so test-admin isn't automatically logged in for all requests
         self.app.extra_environ = {'disable_auth_magic': 'True'}
@@ -2503,7 +2503,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_method(method)
+        other_session.setup_method(None)
         other_session.app.get('/auth/preferences/')
 
         with out_audits(user=True):
@@ -2545,7 +2545,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_method(method)
+        other_session.teardown_method(None)
 
     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 70ee113f5..5164142d3 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -25,6 +25,7 @@ from allura.tests import TestController
 from allura import model as M
 from allura.lib import helpers as h
 from tg import config
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
@@ -401,7 +402,7 @@ class TestDiscuss(TestDiscussBase):
 @with_nose_compatibility
 class TestAttachment(TestDiscussBase):
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.thread_link = self._thread_link()
         thread = self.app.get(self.thread_link)
diff --git a/Allura/allura/tests/functional/test_feeds.py b/Allura/allura/tests/functional/test_feeds.py
index 853a85c91..de32fa04c 100644
--- a/Allura/allura/tests/functional/test_feeds.py
+++ b/Allura/allura/tests/functional/test_feeds.py
@@ -20,12 +20,13 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestFeeds(TestController):
 
-    def setUp(self):
+    def setup_class(self, method):
         TestController.setUp(self)
         self._setUp()
 
diff --git a/Allura/allura/tests/functional/test_gravatar.py b/Allura/allura/tests/functional/test_gravatar.py
index 776fe371c..fdec13eb1 100644
--- a/Allura/allura/tests/functional/test_gravatar.py
+++ b/Allura/allura/tests/functional/test_gravatar.py
@@ -23,6 +23,7 @@ from mock import patch
 
 from allura.tests import TestController
 import allura.lib.gravatar as gravatar
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_home.py b/Allura/allura/tests/functional/test_home.py
index 9bb14588d..07a149dbb 100644
--- a/Allura/allura/tests/functional/test_home.py
+++ b/Allura/allura/tests/functional/test_home.py
@@ -27,6 +27,7 @@ import allura
 from allura.tests import TestController
 from allura.tests import decorators as td
 from allura import model as M
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_nav.py b/Allura/allura/tests/functional/test_nav.py
index 011626197..f865d8f32 100644
--- a/Allura/allura/tests/functional/test_nav.py
+++ b/Allura/allura/tests/functional/test_nav.py
@@ -22,6 +22,7 @@ from tg import app_globals as g
 
 from allura.tests import TestController
 from allura.lib import helpers as h
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
@@ -32,7 +33,7 @@ class TestNavigation(TestController):
     - Test of logo.
     """
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.logo_pattern = ('div', {'class': 'nav-logo'})
         self.global_nav_pattern = ('nav', {'class': 'nav-left'})
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index 6cb550b60..a1d1e7d57 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -37,6 +37,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_newforge.py b/Allura/allura/tests/functional/test_newforge.py
index f9f60a24e..1e95a38cb 100644
--- a/Allura/allura/tests/functional/test_newforge.py
+++ b/Allura/allura/tests/functional/test_newforge.py
@@ -21,6 +21,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_personal_dashboard.py b/Allura/allura/tests/functional/test_personal_dashboard.py
index 9dfbe300f..ea3244123 100644
--- a/Allura/allura/tests/functional/test_personal_dashboard.py
+++ b/Allura/allura/tests/functional/test_personal_dashboard.py
@@ -30,6 +30,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
@@ -88,7 +89,7 @@ class TestTicketsSection(TrackerTestController):
 @with_nose_compatibility
 class TestMergeRequestsSection(TestController):
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         setup_unit_test()
         self.setup_with_tools()
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index 894dba98f..a24ff59e4 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -31,6 +31,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
@@ -419,7 +420,7 @@ class TestRestHome(TestRestApiBase):
 @with_nose_compatibility
 class TestRestNbhdAddProject(TestRestApiBase):
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         # create some troves we'll need
         M.TroveCategory(fullname="Root", trove_cat_id=1, trove_parent_id=0)
diff --git a/Allura/allura/tests/functional/test_root.py b/Allura/allura/tests/functional/test_root.py
index 2e01bacd6..47105f8d9 100644
--- a/Allura/allura/tests/functional/test_root.py
+++ b/Allura/allura/tests/functional/test_root.py
@@ -41,12 +41,13 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestRootController(TestController):
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         n_adobe = M.Neighborhood.query.get(name='Adobe')
         assert n_adobe
@@ -191,7 +192,7 @@ class TestRootController(TestController):
 
 @with_nose_compatibility
 class TestRootWithSSLPattern(TestController):
-    def setUp(self):
+    def setup_class(self, method):
         with td.patch_middleware_config({'force_ssl.pattern': '^/auth'}):
             super().setup_method(method)
 
diff --git a/Allura/allura/tests/functional/test_search.py b/Allura/allura/tests/functional/test_search.py
index a45794280..52fba8d7f 100644
--- a/Allura/allura/tests/functional/test_search.py
+++ b/Allura/allura/tests/functional/test_search.py
@@ -24,6 +24,7 @@ from allura.tests import TestController
 from allura.tests.decorators import with_tool
 
 from forgewiki.model import Page
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_site_admin.py b/Allura/allura/tests/functional/test_site_admin.py
index 6bb0c44cd..fce6abe29 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -33,6 +33,7 @@ from allura.lib import helpers as h
 from allura.lib.decorators import task
 from allura.lib.plugin import LocalAuthenticationProvider
 import six
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
@@ -360,7 +361,7 @@ class TestProjectsSearch(TestController):
         'id': 'allura/model/project/Project#53ccf6e8100d2b0741746e9f',
     }])
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         # Create project that matches TEST_HIT id
         _id = ObjectId('53ccf6e8100d2b0741746e9f')
@@ -418,7 +419,7 @@ class TestUsersSearch(TestController):
         'user_registration_date_dt': '2014-09-09T13:17:38Z',
         'username_s': 'darth'}])
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         # Create user that matches TEST_HIT id
         _id = ObjectId('540efdf2100d2b1483155d39')
diff --git a/Allura/allura/tests/functional/test_static.py b/Allura/allura/tests/functional/test_static.py
index 5b7386741..7912e425c 100644
--- a/Allura/allura/tests/functional/test_static.py
+++ b/Allura/allura/tests/functional/test_static.py
@@ -17,6 +17,7 @@
 
 
 from allura.tests import TestController
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_subscriber.py b/Allura/allura/tests/functional/test_subscriber.py
index 52041fd0a..02a273cfc 100644
--- a/Allura/allura/tests/functional/test_subscriber.py
+++ b/Allura/allura/tests/functional/test_subscriber.py
@@ -19,6 +19,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_tool_list.py b/Allura/allura/tests/functional/test_tool_list.py
index 61c76f8b6..8e4bfafb1 100644
--- a/Allura/allura/tests/functional/test_tool_list.py
+++ b/Allura/allura/tests/functional/test_tool_list.py
@@ -17,6 +17,7 @@
 
 from allura.tests import TestController
 from allura.tests import decorators as td
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_trovecategory.py b/Allura/allura/tests/functional/test_trovecategory.py
index 08ea4d39c..8588607aa 100644
--- a/Allura/allura/tests/functional/test_trovecategory.py
+++ b/Allura/allura/tests/functional/test_trovecategory.py
@@ -26,6 +26,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_user_profile.py b/Allura/allura/tests/functional/test_user_profile.py
index 6c63e1a8e..9ec08f518 100644
--- a/Allura/allura/tests/functional/test_user_profile.py
+++ b/Allura/allura/tests/functional/test_user_profile.py
@@ -23,6 +23,7 @@ from alluratest.controller import TestRestApiBase
 from allura.model import Project, User
 from allura.tests import decorators as td
 from allura.tests import TestController
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/model/test_artifact.py b/Allura/allura/tests/model/test_artifact.py
index c4389185a..43ac4828d 100644
--- a/Allura/allura/tests/model/test_artifact.py
+++ b/Allura/allura/tests/model/test_artifact.py
@@ -76,7 +76,7 @@ def tearDown():
     ThreadLocalORMSession.close_all()
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_artifact():
     pg = WM.Page(title='TestPage1')
     assert pg.project == c.project
@@ -111,7 +111,7 @@ def test_artifact_index():
     assert pg.link_text() == pg.shorthand_id()
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_artifactlink():
     pg = WM.Page(title='TestPage2')
     q_shortlink = M.Shortlink.query.find(dict(
@@ -138,27 +138,27 @@ def test_artifactlink():
     assert q_shortlink.count() == 0
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_gen_messageid():
     assert re.match(r'[0-9a-zA-Z]*.wiki@test.p.localhost',
                     h.gen_message_id())
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_gen_messageid_with_id_set():
     oid = ObjectId()
     assert re.match(r'%s.wiki@test.p.localhost' %
                     str(oid), h.gen_message_id(oid))
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_artifact_messageid():
     p = WM.Page(title='T')
     assert re.match(r'%s.wiki@test.p.localhost' %
                     str(p._id), p.message_id())
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_versioning():
     pg = WM.Page(title='TestPage3')
     with patch('allura.model.artifact.request',
@@ -187,7 +187,7 @@ def test_versioning():
     assert pg.history().count() == 3
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_messages_unknown_lookup():
     from bson import ObjectId
     m = Checkmessage()
@@ -196,7 +196,7 @@ def test_messages_unknown_lookup():
     assert m.author() == M.User.anonymous()
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 @patch('allura.model.artifact.datetime')
 def test_last_updated(_datetime):
     c.project.last_updated = datetime(2014, 1, 1)
@@ -206,7 +206,7 @@ def test_last_updated(_datetime):
     assert c.project.last_updated == datetime(2014, 1, 2)
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 @patch('allura.model.artifact.datetime')
 def test_last_updated_disabled(_datetime):
     c.project.last_updated = datetime(2014, 1, 1)
@@ -220,7 +220,7 @@ def test_last_updated_disabled(_datetime):
         M.artifact_orm_session._get().skip_last_updated = False
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_get_discussion_thread_dupe():
     artif = WM.Page(title='TestSomeArtifact')
     thr1 = artif.get_discussion_thread()[0]
@@ -256,7 +256,7 @@ def test_snapshot_clear_user_data():
                             }
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_snapshot_from_username():
     s = M.Snapshot(author={'username': 'johndoe',
                            'display_name': 'John Doe',
@@ -286,7 +286,7 @@ def test_feed_clear_user_data():
     assert f.title == 'Home Page modified by <REDACTED>'
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_feed_from_username():
     M.Feed(author_name='John Doe',
            author_link='/u/johndoe/',
@@ -298,14 +298,14 @@ def test_feed_from_username():
     assert len(M.Feed.from_username('johndoe')) == 1
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_subscribed():
     pg = WM.Page(title='TestPage4a')
     assert pg.subscribed(include_parents=True)  # tool is subscribed to admins by default
     assert not pg.subscribed(include_parents=False)
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_subscribed_no_tool_sub():
     pg = WM.Page(title='TestPage4b')
     M.Mailbox.unsubscribe(user_id=c.user._id,
@@ -316,7 +316,7 @@ def test_subscribed_no_tool_sub():
     assert pg.subscribed(include_parents=False)
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_not_subscribed():
     pg = WM.Page(title='TestPage4c')
     M.Mailbox.unsubscribe(user_id=c.user._id,
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index d9818c232..870fcfa21 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -42,6 +42,7 @@ 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
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 def setup_method(self, method):
@@ -50,7 +51,7 @@ def setup_method(self, method):
     setup_global_objects()
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_email_address():
     addr = M.EmailAddress(email='test_admin@domain.net',
                           claimed_by_user_id=c.user._id)
@@ -72,7 +73,7 @@ def test_email_address():
     assert 'test@domain.net' in c.user.email_addresses
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_email_address_lookup_helpers():
     addr = M.EmailAddress.create('TEST@DOMAIN.NET')
     nobody = M.EmailAddress.create('nobody@example.com')
@@ -96,7 +97,7 @@ def test_email_address_lookup_helpers():
     assert M.EmailAddress.find(dict(email='invalid')).all() == []
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_email_address_canonical():
     assert (M.EmailAddress.canonical('nobody@EXAMPLE.COM') ==
                  'nobody@example.com')
@@ -114,7 +115,7 @@ def test_email_address_canonical():
                  'no@body@example.com')
     assert M.EmailAddress.canonical('invalid') == None
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_email_address_send_verification_link():
     addr = M.EmailAddress(email='test_admin@domain.net',
                           claimed_by_user_id=c.user._id)
@@ -127,7 +128,7 @@ def test_email_address_send_verification_link():
     assert rcpts == ['test_admin@domain.net']
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 @td.with_user_project('test-admin')
 def test_user():
     assert c.user.url() .endswith('/u/test-admin/')
@@ -157,7 +158,7 @@ def test_user():
     assert not provider._validate_password(u, 'foo')
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_user_project_creates_on_demand():
     u = M.User.register(dict(username='foobar123'), make_project=False)
     ThreadLocalORMSession.flush_all()
@@ -166,7 +167,7 @@ def test_user_project_creates_on_demand():
     assert M.Project.query.get(shortname='u/foobar123')
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_user_project_already_deleted_creates_on_demand():
     u = M.User.register(dict(username='foobar123'), make_project=True)
     p = M.Project.query.get(shortname='u/foobar123')
@@ -178,7 +179,7 @@ def test_user_project_already_deleted_creates_on_demand():
     assert M.Project.query.get(shortname='u/foobar123', deleted=False)
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_user_project_does_not_create_on_demand_for_disabled_user():
     u = M.User.register(
         dict(username='foobar123', disabled=True), make_project=False)
@@ -187,7 +188,7 @@ def test_user_project_does_not_create_on_demand_for_disabled_user():
     assert not M.Project.query.get(shortname='u/foobar123')
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_user_project_does_not_create_on_demand_for_anonymous_user():
     u = M.User.anonymous()
     ThreadLocalORMSession.flush_all()
@@ -196,7 +197,7 @@ def test_user_project_does_not_create_on_demand_for_anonymous_user():
     assert not M.Project.query.get(shortname='u/*anonymous')
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 @patch('allura.model.auth.log')
 def test_user_by_email_address(log):
     u1 = M.User.register(dict(username='abc1'), make_project=False)
@@ -254,7 +255,7 @@ def test_user_hash():
     assert M.User.anonymous() not in {0, None}
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_project_role():
     role = M.ProjectRole(project_id=c.project._id, name='test_role')
     M.ProjectRole.by_user(c.user, upsert=True).roles.append(role._id)
@@ -269,7 +270,7 @@ def test_project_role():
         assert pr.user in (c.user, None, M.User.anonymous())
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_default_project_roles():
     roles = {
         pr.name: pr
@@ -288,7 +289,7 @@ def test_default_project_roles():
         project_id=c.project._id)).count() - 1
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_email_address_claimed_by_user():
     addr = M.EmailAddress(email='test_admin@domain.net',
                           claimed_by_user_id=c.user._id)
@@ -297,7 +298,7 @@ def test_email_address_claimed_by_user():
     assert addr.claimed_by_user() is None
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 @td.with_user_project('test-admin')
 def test_user_projects_by_role():
     assert ({p.shortname for p in c.user.my_projects()} ==
@@ -320,7 +321,7 @@ def test_user_projects_by_role():
 
 
 @td.with_user_project('test-admin')
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_user_projects_unnamed():
     """
     Confirm that spurious ProjectRoles associating a user with
@@ -352,7 +353,7 @@ def test_check_sent_user_message_times():
 
 
 @td.with_user_project('test-admin')
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_user_track_active():
     # without this session flushing inside track_active raises Exception
     setup_functional_test()
@@ -394,7 +395,7 @@ def test_user_track_active():
     assert c.user.last_access['session_ua'] == 'new browser'
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_user_index():
     c.user.email_addresses = ['email1', 'email2']
     c.user.set_pref('email_address', 'email2')
@@ -430,7 +431,7 @@ def test_user_index():
     assert 'user_registration_date_dt' in idx
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_user_index_none_values():
     c.user.email_addresses = [None]
     c.user.set_pref('telnumbers', [None])
@@ -441,7 +442,7 @@ def test_user_index_none_values():
     assert idx['webpages_t'] == ''
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_user_backfill_login_details():
     with h.push_config(request, user_agent='TestBrowser/55'):
         # these shouldn't match
diff --git a/Allura/allura/tests/model/test_discussion.py b/Allura/allura/tests/model/test_discussion.py
index 23b787317..86f3467e0 100644
--- a/Allura/allura/tests/model/test_discussion.py
+++ b/Allura/allura/tests/model/test_discussion.py
@@ -53,7 +53,7 @@ def tearDown():
     ThreadLocalORMSession.close_all()
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_discussion_methods():
     d = M.Discussion(shortname='test', name='test')
     assert d.thread_class() == M.Thread
@@ -73,7 +73,7 @@ def test_discussion_methods():
     ThreadLocalORMSession.close_all()
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_thread_methods():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
@@ -115,7 +115,7 @@ def test_thread_methods():
     t.delete()
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_thread_new():
     with mock.patch('allura.model.discuss.h.nonce') as nonce:
         nonce.side_effect = ['deadbeef', 'deadbeef', 'beefdead']
@@ -133,7 +133,7 @@ def test_thread_new():
         assert t2_2.subject == 'Test Thread Two'
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_post_methods():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
@@ -168,7 +168,7 @@ def test_post_methods():
     assert t.num_replies == 1
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_attachment_methods():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
@@ -209,7 +209,7 @@ def test_attachment_methods():
         n.text)
 
 
-@with_setup(setUp, tearDown())
+@with_setup(setup_method, tearDown())
 def test_multiple_attachments():
     test_file1 = FieldStorage()
     test_file1.name = 'file_info'
@@ -232,7 +232,7 @@ def test_multiple_attachments():
     assert 'test2.txt' in [attaches[0].filename, attaches[1].filename]
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_add_attachment():
     test_file = FieldStorage()
     test_file.name = 'file_info'
@@ -275,7 +275,7 @@ def test_notification_two_attaches():
         n.text)
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_discussion_delete():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
@@ -292,7 +292,7 @@ def test_discussion_delete():
     assert M.ArtifactReference.query.find(dict(_id=rid)).count() == 0
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_thread_delete():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
@@ -305,7 +305,7 @@ def test_thread_delete():
     t.delete()
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_post_delete():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
@@ -318,7 +318,7 @@ def test_post_delete():
     p.delete()
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_post_undo():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
@@ -333,7 +333,7 @@ def test_post_undo():
     assert t.num_replies == 3
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_post_permission_check():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
@@ -346,7 +346,7 @@ def test_post_permission_check():
     t.post('This post will pass the check.', ignore_security=True)
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_post_url_paginated():
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread(discussion_id=d._id, subject='Test Thread')
@@ -398,7 +398,7 @@ def test_post_url_paginated():
         assert _p.url_paginated() == url
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_post_url_paginated_with_artifact():
     """Post.url_paginated should return link to attached artifact, if any"""
     from forgewiki.model import Page
@@ -409,7 +409,7 @@ def test_post_url_paginated_with_artifact():
     assert comment.url_paginated() == url
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 def test_post_notify():
     d = M.Discussion(shortname='test', name='test')
     d.monitoring_email = 'darthvader@deathstar.org'
@@ -429,7 +429,7 @@ def test_post_notify():
             assert False, 'send_simple must not be called'
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 @patch('allura.model.discuss.c.project.users_with_role')
 def test_is_spam_for_admin(users):
     users.return_value = [c.user, ]
@@ -440,7 +440,7 @@ def test_is_spam_for_admin(users):
     assert not t.is_spam(post), t.is_spam(post)
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 @patch('allura.model.discuss.c.project.users_with_role')
 def test_is_spam(role):
     d = M.Discussion(shortname='test', name='test')
@@ -453,7 +453,7 @@ def test_is_spam(role):
         assert spam_checker.check.call_count == 1, spam_checker.call_count
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 @mock.patch('allura.controllers.discuss.g.spam_checker')
 def test_not_spam_and_has_unmoderated_post_permission(spam_checker):
     spam_checker.check.return_value = False
@@ -469,7 +469,7 @@ def test_not_spam_and_has_unmoderated_post_permission(spam_checker):
     assert post.status == 'ok'
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 @mock.patch('allura.controllers.discuss.g.spam_checker')
 @mock.patch.object(M.Thread, 'notify_moderators')
 def test_not_spam_but_has_no_unmoderated_post_permission(notify_moderators, spam_checker):
@@ -485,7 +485,7 @@ def test_not_spam_but_has_no_unmoderated_post_permission(notify_moderators, spam
     assert notify_moderators.call_count == 1
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 @mock.patch('allura.controllers.discuss.g.spam_checker')
 @mock.patch.object(M.Thread, 'notify_moderators')
 def test_spam_and_has_unmoderated_post_permission(notify_moderators, spam_checker):
@@ -503,7 +503,7 @@ def test_spam_and_has_unmoderated_post_permission(notify_moderators, spam_checke
     assert notify_moderators.call_count == 1
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method)
 @mock.patch('allura.controllers.discuss.g.spam_checker')
 def test_thread_subject_not_included_in_text_checked(spam_checker):
     spam_checker.check.return_value = False
diff --git a/Allura/allura/tests/model/test_filesystem.py b/Allura/allura/tests/model/test_filesystem.py
index 230b0496c..29a32b5d8 100644
--- a/Allura/allura/tests/model/test_filesystem.py
+++ b/Allura/allura/tests/model/test_filesystem.py
@@ -28,6 +28,7 @@ from webob import Request, Response
 
 from allura import model as M
 from alluratest.controller import setup_unit_test
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 class File(M.File):
@@ -40,7 +41,7 @@ Mapper.compile_all()
 @with_nose_compatibility
 class TestFile(TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         config = {
             'ming.main.uri': 'mim://host/allura',
             'ming.project.uri': 'mim://host/project-data',
diff --git a/Allura/allura/tests/model/test_monq.py b/Allura/allura/tests/model/test_monq.py
index 74ac43e4f..f2fd7ad29 100644
--- a/Allura/allura/tests/model/test_monq.py
+++ b/Allura/allura/tests/model/test_monq.py
@@ -31,7 +31,7 @@ def setup_method(self, method):
     M.MonQTask.query.remove({})
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_basic_task():
     task = M.MonQTask.post(pprint.pformat, ([5, 6],))
     ThreadLocalORMSession.flush_all()
diff --git a/Allura/allura/tests/model/test_neighborhood.py b/Allura/allura/tests/model/test_neighborhood.py
index 093dd3192..28b85dca1 100644
--- a/Allura/allura/tests/model/test_neighborhood.py
+++ b/Allura/allura/tests/model/test_neighborhood.py
@@ -35,7 +35,7 @@ def setup_with_tools():
     setup_global_objects()
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_neighborhood():
     neighborhood = M.Neighborhood.query.get(name='Projects')
     # Check css output depends of neighborhood level
diff --git a/Allura/allura/tests/model/test_notification.py b/Allura/allura/tests/model/test_notification.py
index 046939f95..5df6a54df 100644
--- a/Allura/allura/tests/model/test_notification.py
+++ b/Allura/allura/tests/model/test_notification.py
@@ -31,12 +31,13 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestNotification(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -168,7 +169,7 @@ class TestNotification(unittest.TestCase):
 @with_nose_compatibility
 class TestPostNotifications(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -307,7 +308,7 @@ class TestPostNotifications(unittest.TestCase):
 @with_nose_compatibility
 class TestSubscriptionTypes(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -480,7 +481,7 @@ class TestSubscriptionTypes(unittest.TestCase):
 
 @with_nose_compatibility
 class TestSiteNotification(unittest.TestCase):
-    def setUp(self):
+    def setup_class(self, method):
         self.note = M.SiteNotification(
             active=True,
             impressions=0,
diff --git a/Allura/allura/tests/model/test_oauth.py b/Allura/allura/tests/model/test_oauth.py
index 09ddbf138..2a8b82ad6 100644
--- a/Allura/allura/tests/model/test_oauth.py
+++ b/Allura/allura/tests/model/test_oauth.py
@@ -30,7 +30,7 @@ def setup_method(self, method):
     setup_global_objects()
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_upsert():
     admin = M.User.by_username('test-admin')
     user = M.User.by_username('test-user')
diff --git a/Allura/allura/tests/model/test_project.py b/Allura/allura/tests/model/test_project.py
index 382449777..339168f15 100644
--- a/Allura/allura/tests/model/test_project.py
+++ b/Allura/allura/tests/model/test_project.py
@@ -94,7 +94,7 @@ def test_project():
     c.app.config.breadcrumbs()
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_install_app_validates_options():
     from forgetracker.tracker_main import ForgeTrackerApp
     name = 'TicketMonitoringEmail'
@@ -155,7 +155,7 @@ def test_set_ordinal_to_admin_tool():
         assert sm[-1].tool_name == 'admin'
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_users_and_roles():
     p = M.Project.query.get(shortname='test')
     sub = p.direct_subprojects[0]
@@ -171,7 +171,7 @@ def test_users_and_roles():
     assert p.users_with_role('Admin') == p.admins()
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_project_disabled_users():
     p = M.Project.query.get(shortname='test')
     users = p.users()
diff --git a/Allura/allura/tests/model/test_repo.py b/Allura/allura/tests/model/test_repo.py
index d5a27931c..60d5487c7 100644
--- a/Allura/allura/tests/model/test_repo.py
+++ b/Allura/allura/tests/model/test_repo.py
@@ -29,6 +29,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
@@ -73,7 +74,7 @@ class RepoImplTestBase:
 
 
 class RepoTestBase(unittest.TestCase):
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
 
     @mock.patch('allura.model.repository.Repository.url')
@@ -131,7 +132,7 @@ class RepoTestBase(unittest.TestCase):
 
 @with_nose_compatibility
 class TestLastCommit(unittest.TestCase):
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         setup_global_objects()
         self.repo = mock.Mock(
@@ -404,7 +405,7 @@ class TestLastCommit(unittest.TestCase):
 
 @with_nose_compatibility
 class TestModelCache(unittest.TestCase):
-    def setUp(self):
+    def setup_class(self, method):
         self.cache = M.repository.ModelCache()
 
     def test_normalize_query(self):
@@ -684,7 +685,7 @@ class TestModelCache(unittest.TestCase):
 @with_nose_compatibility
 class TestMergeRequest:
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         setup_global_objects()
         self.mr = M.MergeRequest(
diff --git a/Allura/allura/tests/model/test_timeline.py b/Allura/allura/tests/model/test_timeline.py
index 39d0368d2..f1e32434b 100644
--- a/Allura/allura/tests/model/test_timeline.py
+++ b/Allura/allura/tests/model/test_timeline.py
@@ -20,13 +20,14 @@ from alluratest.tools import assert_equal
 from allura import model as M
 from allura.tests import decorators as td
 from alluratest.controller import setup_basic_test, setup_global_objects
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestActivityObject_Functional:
     # NOTE not for unit tests, this class sets up all the junk
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         setup_global_objects()
 
diff --git a/Allura/allura/tests/pytest_helpers.py b/Allura/allura/tests/pytest_helpers.py
new file mode 100644
index 000000000..832cfa796
--- /dev/null
+++ b/Allura/allura/tests/pytest_helpers.py
@@ -0,0 +1,49 @@
+
+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/Allura/allura/tests/scripts/test_create_sitemap_files.py b/Allura/allura/tests/scripts/test_create_sitemap_files.py
index e92572958..6df1d6f3a 100644
--- a/Allura/allura/tests/scripts/test_create_sitemap_files.py
+++ b/Allura/allura/tests/scripts/test_create_sitemap_files.py
@@ -27,12 +27,13 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestCreateSitemapFiles:
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
 
     def run_script(self, options):
diff --git a/Allura/allura/tests/scripts/test_delete_projects.py b/Allura/allura/tests/scripts/test_delete_projects.py
index 0a68e8daa..29a983f8c 100644
--- a/Allura/allura/tests/scripts/test_delete_projects.py
+++ b/Allura/allura/tests/scripts/test_delete_projects.py
@@ -25,12 +25,13 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestDeleteProjects(TestController):
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         n = M.Neighborhood.query.get(name='Projects')
         admin = M.User.by_username('test-admin')
diff --git a/Allura/allura/tests/scripts/test_misc_scripts.py b/Allura/allura/tests/scripts/test_misc_scripts.py
index c1981bc8d..c36a064b6 100644
--- a/Allura/allura/tests/scripts/test_misc_scripts.py
+++ b/Allura/allura/tests/scripts/test_misc_scripts.py
@@ -22,12 +22,13 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestClearOldNotifications:
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
 
     def run_script(self, options):
diff --git a/Allura/allura/tests/scripts/test_reindexes.py b/Allura/allura/tests/scripts/test_reindexes.py
index a05fb8db5..eaa4eb7bc 100644
--- a/Allura/allura/tests/scripts/test_reindexes.py
+++ b/Allura/allura/tests/scripts/test_reindexes.py
@@ -22,12 +22,13 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestReindexProjects:
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
 
     def run_script(self, options):
@@ -52,7 +53,7 @@ class TestReindexProjects:
 @with_nose_compatibility
 class TestReindexUsers:
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
 
     def run_script(self, options):
diff --git a/Allura/allura/tests/templates/jinja_master/test_lib.py b/Allura/allura/tests/templates/jinja_master/test_lib.py
index 57db256d8..87cc5fe87 100644
--- a/Allura/allura/tests/templates/jinja_master/test_lib.py
+++ b/Allura/allura/tests/templates/jinja_master/test_lib.py
@@ -22,6 +22,7 @@ from alluratest.tools import assert_equal
 import ming
 from allura.config.app_cfg import ForgeConfig, AlluraJinjaRenderer
 from alluratest.controller import setup_basic_test
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 def strip_space(s):
@@ -29,7 +30,7 @@ def strip_space(s):
 
 
 class TemplateTest:
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         self.jinja2_env = AlluraJinjaRenderer.create(config, g)['jinja'].jinja2_env
 
diff --git a/Allura/allura/tests/test_app.py b/Allura/allura/tests/test_app.py
index 03b75c31a..eeeb9ecea 100644
--- a/Allura/allura/tests/test_app.py
+++ b/Allura/allura/tests/test_app.py
@@ -25,7 +25,7 @@ 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
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 def setup_method(self, method):
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index 9df9840e7..03b3fb954 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, with_nose_compatibility
+from allura.tests import decorators as td
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 test_config = pkg_resources.resource_filename(
@@ -46,7 +47,7 @@ class EmptyClass:
     pass
 
 
-def setUp(self):
+def setup_class(self, method):
     """Method called by nose before running each test"""
     setup_basic_test()
     setup_global_objects()
@@ -340,7 +341,7 @@ class TestTaskCommand:
 @with_nose_compatibility
 class TestTaskdCleanupCommand:
 
-    def setUp(self):
+    def setup_class(self, method):
         self.cmd_class = taskd_cleanup.TaskdCleanupCommand
         self.old_check_taskd_status = self.cmd_class._check_taskd_status
         self.cmd_class._check_taskd_status = lambda x, p: 'OK'
diff --git a/Allura/allura/tests/test_decorators.py b/Allura/allura/tests/test_decorators.py
index 6a3b8a46e..5bdf5cf70 100644
--- a/Allura/allura/tests/test_decorators.py
+++ b/Allura/allura/tests/test_decorators.py
@@ -21,7 +21,7 @@ import random
 import gc
 
 from alluratest.tools import assert_equal, assert_not_equal
-from allura.tests import with_nose_compatibility
+from allura.tests.pytest_helpers import with_nose_compatibility
 from allura.lib.decorators import task, memoize
 
 
diff --git a/Allura/allura/tests/test_diff.py b/Allura/allura/tests/test_diff.py
index 025ae11e5..505b95c33 100644
--- a/Allura/allura/tests/test_diff.py
+++ b/Allura/allura/tests/test_diff.py
@@ -18,13 +18,13 @@
 import unittest
 
 from allura.lib.diff import HtmlSideBySideDiff
-from allura.tests import with_nose_compatibility
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestHtmlSideBySideDiff(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         self.diff = HtmlSideBySideDiff()
 
     def test_render_change(self):
diff --git a/Allura/allura/tests/test_dispatch.py b/Allura/allura/tests/test_dispatch.py
index 074499936..265fdd187 100644
--- a/Allura/allura/tests/test_dispatch.py
+++ b/Allura/allura/tests/test_dispatch.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 from allura.tests import TestController
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 app = None
 
diff --git a/Allura/allura/tests/test_globals.py b/Allura/allura/tests/test_globals.py
index ad11499d8..b1f67003b 100644
--- a/Allura/allura/tests/test_globals.py
+++ b/Allura/allura/tests/test_globals.py
@@ -43,6 +43,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 from forgewiki import model as WM
 from forgeblog import model as BM
@@ -61,8 +62,8 @@ def setup_method(self, method):
     setup_with_tools()
 
 
-def tearDown():
-    setUp()
+def teadown_method():
+    setup_method(None)
 
 
 @td.with_wiki
@@ -77,7 +78,7 @@ def test_app_globals():
             'css/wiki.css') == '/nf/_static_/wiki/css/wiki.css', g.app_static('css/wiki.css')
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_macro_projects():
     file_name = 'neo-icon-set-454545-256x350.png'
     file_path = os.path.join(
@@ -198,7 +199,7 @@ def test_macro_neighborhood_feeds():
         assert 'test content' in r
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_macro_members():
     p_nbhd = M.Neighborhood.query.get(name='Projects')
     p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
@@ -216,7 +217,7 @@ def test_macro_members():
                  '</div>')
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_macro_members_escaping():
     user = M.User.by_username('test-admin')
     user.display_name = 'Test Admin <script>'
@@ -228,7 +229,7 @@ def test_macro_members_escaping():
                  '</ul></div>')
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_macro_project_admins():
     user = M.User.by_username('test-admin')
     user.display_name = 'Test Ådmin <script>'
@@ -241,7 +242,7 @@ def test_macro_project_admins():
                  '</ul></div>')
 
 
-@with_setup(setUp)
+@with_setup(setup_method)
 def test_macro_project_admins_one_br():
     p_nbhd = M.Neighborhood.query.get(name='Projects')
     p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
@@ -286,7 +287,7 @@ def test_macro_include_no_extra_br():
     assert squish_spaces(html) == squish_spaces(expected_html)
 
 
-@with_setup(setUp, tearDown)
+@with_setup(setup_method, teadown_method)
 @td.with_wiki
 @td.with_tool('test', 'Wiki', 'wiki2')
 def test_macro_include_permissions():
@@ -828,7 +829,7 @@ def get_projects_property_in_the_same_order(names, prop):
 @with_nose_compatibility
 class TestCachedMarkdown(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         self.md = ForgeMarkdown()
         self.post = M.Post()
         self.post.text = '**bold**'
@@ -1024,7 +1025,7 @@ class TestUserMentions(unittest.TestCase):
 @with_nose_compatibility
 class TestHandlePaging(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         prefs = {}
         c.user = Mock()
 
@@ -1085,7 +1086,7 @@ class TestHandlePaging(unittest.TestCase):
 @with_nose_compatibility
 class TestIconRender:
 
-    def setUp(self):
+    def setup_class(self, method):
         self.i = g.icons['edit']
 
     def test_default(self):
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 8fb0b4df8..ea0c50a2b 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -37,11 +37,12 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 from alluratest.controller import setup_basic_test
 import six
 
 
-def setUp(self):
+def setup_class(self, method):
     """Method called by nose before running each test"""
     setup_basic_test()
 
@@ -49,7 +50,7 @@ def setUp(self):
 @with_nose_compatibility
 class TestMakeSafePathPortion(TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         self.f = h.make_safe_path_portion
 
     def test_no_ascii_chars(self):
diff --git a/Allura/allura/tests/test_mail_util.py b/Allura/allura/tests/test_mail_util.py
index d9058862d..4a3970c82 100644
--- a/Allura/allura/tests/test_mail_util.py
+++ b/Allura/allura/tests/test_mail_util.py
@@ -37,6 +37,7 @@ from allura.lib.mail_util import (
     _parse_message_id,
 )
 from allura.lib.exceptions import AddressException
+from allura.tests.pytest_helpers import with_nose_compatibility
 from allura.tests import decorators as td
 import six
 
@@ -48,7 +49,7 @@ config = ConfigProxy(
 @with_nose_compatibility
 class TestReactor(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         setup_global_objects()
         ThreadLocalORMSession.flush_all()
@@ -235,7 +236,7 @@ class TestHeader:
 @with_nose_compatibility
 class TestIsAutoreply:
 
-    def setUp(self):
+    def setup_class(self, method):
         self.msg = {'headers': {}}
 
     def test_empty(self):
@@ -332,7 +333,7 @@ def test_parse_message_id():
 @with_nose_compatibility
 class TestMailServer:
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
 
     @mock.patch('allura.command.base.log', autospec=True)
diff --git a/Allura/allura/tests/test_markdown.py b/Allura/allura/tests/test_markdown.py
index 80ae4f6ad..5334aa4d6 100644
--- a/Allura/allura/tests/test_markdown.py
+++ b/Allura/allura/tests/test_markdown.py
@@ -19,6 +19,7 @@ import unittest
 import mock
 
 from allura.lib import markdown_extensions as mde
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/test_middlewares.py b/Allura/allura/tests/test_middlewares.py
index 0a11319f5..fa15b7fa3 100644
--- a/Allura/allura/tests/test_middlewares.py
+++ b/Allura/allura/tests/test_middlewares.py
@@ -19,12 +19,13 @@ from mock import MagicMock, patch
 from datadiff.tools import assert_equal
 from alluratest.tools import assert_not_equal
 from allura.lib.custom_middleware import CORSMiddleware
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestCORSMiddleware:
 
-    def setUp(self):
+    def setup_class(self, method):
         self.app = MagicMock()
         self.allowed_methods = ['GET', 'POST', 'DELETE']
         self.allowed_headers = ['Authorization', 'Accept']
diff --git a/Allura/allura/tests/test_multifactor.py b/Allura/allura/tests/test_multifactor.py
index a7a17d161..de6208c3b 100644
--- a/Allura/allura/tests/test_multifactor.py
+++ b/Allura/allura/tests/test_multifactor.py
@@ -32,6 +32,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
@@ -180,7 +181,7 @@ class TestMongodbTotpService(TestAnyTotpServiceImplementation):
     __test__ = True
     Service = MongodbTotpService
 
-    def setUp(self):
+    def setup_class(self, method):
         config = {
             'ming.main.uri': 'mim://host/allura_test',
         }
@@ -190,7 +191,7 @@ class TestMongodbTotpService(TestAnyTotpServiceImplementation):
 @with_nose_compatibility
 class TestGoogleAuthenticatorPamFilesystemMixin:
 
-    def setUp(self):
+    def setup_class(self, method):
         self.totp_basedir = tempfile.mkdtemp(prefix='totp-test', dir=os.getenv('TMPDIR', '/tmp'))
         config['auth.multifactor.totp.filesystem.basedir'] = self.totp_basedir
 
@@ -311,7 +312,7 @@ class TestMongodbRecoveryCodeService(TestAnyRecoveryCodeServiceImplementation):
 
     Service = MongodbRecoveryCodeService
 
-    def setUp(self):
+    def setup_class(self, method):
         config = {
             'ming.main.uri': 'mim://host/allura_test',
         }
@@ -326,7 +327,7 @@ class TestGoogleAuthenticatorPamFilesystemRecoveryCodeService(TestAnyRecoveryCod
 
     Service = GoogleAuthenticatorPamFilesystemRecoveryCodeService
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
 
         # make a regular .google-authenticator file first, so recovery keys have somewhere to go
diff --git a/Allura/allura/tests/test_plugin.py b/Allura/allura/tests/test_plugin.py
index 42c90407e..c0498de69 100644
--- a/Allura/allura/tests/test_plugin.py
+++ b/Allura/allura/tests/test_plugin.py
@@ -45,12 +45,13 @@ from allura.lib.plugin import ThemeProvider
 from allura.lib.exceptions import ProjectConflict, ProjectShortnameInvalid
 from allura.tests.decorators import audits
 from alluratest.controller import setup_basic_test, setup_global_objects
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestProjectRegistrationProvider:
 
-    def setUp(self):
+    def setup_class(self, method):
         self.provider = ProjectRegistrationProvider()
 
     @patch('allura.lib.security.has_access')
@@ -88,7 +89,7 @@ class TestProjectRegistrationProvider:
 @with_nose_compatibility
 class TestProjectRegistrationProviderParseProjectFromUrl:
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         ThreadLocalORMSession.close_all()
         setup_global_objects()
@@ -163,7 +164,7 @@ class UserMock:
 @with_nose_compatibility
 class TestProjectRegistrationProviderPhoneVerification:
 
-    def setUp(self):
+    def setup_class(self, method):
         self.p = ProjectRegistrationProvider()
         self.user = UserMock()
         self.nbhd = MagicMock()
@@ -284,8 +285,7 @@ class TestThemeProvider:
     @patch('allura.app.g')
     @patch('allura.lib.plugin.g')
     def test_app_icon_str(self, plugin_g, app_g):
-        @with_nose_compatibility
-class TestApp(Application):
+        class TestApp(Application):
             icons = {
                 24: 'images/testapp_24.png',
             }
@@ -302,8 +302,7 @@ class TestApp(Application):
 
     @patch('allura.app.g')
     def test_app_icon_app(self, g):
-        @with_nose_compatibility
-class TestApp(Application):
+        class TestApp(Application):
             icons = {
                 24: 'images/testapp_24.png',
             }
@@ -638,7 +637,7 @@ class TestThemeProvider_notifications:
 @with_nose_compatibility
 class TestLocalAuthenticationProvider:
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         ThreadLocalORMSession.close_all()
         setup_global_objects()
@@ -753,7 +752,7 @@ class TestLocalAuthenticationProvider:
 @with_nose_compatibility
 class TestAuthenticationProvider:
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         self.provider = plugin.AuthenticationProvider(Request.blank('/'))
         self.pwd_updated = dt.datetime.utcnow() - dt.timedelta(days=100)
diff --git a/Allura/allura/tests/test_scripttask.py b/Allura/allura/tests/test_scripttask.py
index 7b30ba353..d66bf2684 100644
--- a/Allura/allura/tests/test_scripttask.py
+++ b/Allura/allura/tests/test_scripttask.py
@@ -19,14 +19,14 @@ import unittest
 import mock
 
 from allura.scripts.scripttask import ScriptTask
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestScriptTask(unittest.TestCase):
 
-    def setUp(self):
-        @with_nose_compatibility
-class TestScriptTask(ScriptTask):
+    def setup_class(self, method):
+        class TestScriptTask(ScriptTask):
             _parser = mock.Mock()
 
             @classmethod
diff --git a/Allura/allura/tests/test_security.py b/Allura/allura/tests/test_security.py
index 0384fd9cb..5fe92b964 100644
--- a/Allura/allura/tests/test_security.py
+++ b/Allura/allura/tests/test_security.py
@@ -28,6 +28,8 @@ from forgewiki import model as WM
 from allura.lib.security import HIBPClientError, HIBPClient
 from mock import Mock, patch
 from requests.exceptions import Timeout
+from allura.tests.pytest_helpers import with_nose_compatibility
+
 
 def _allow(obj, role, perm):
     obj.acl.insert(0, M.ACE.allow(role._id, perm))
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index 038836f19..0547b1954 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -51,6 +51,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 from allura.lib.decorators import event_handler, task
 
 
@@ -102,7 +103,7 @@ def _task_that_creates_event(event_name,):
 @with_nose_compatibility
 class TestEventTasks(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         setup_global_objects()
         self.called_with = []
@@ -161,7 +162,7 @@ class TestEventTasks(unittest.TestCase):
 @with_nose_compatibility
 class TestIndexTasks(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         setup_global_objects()
 
@@ -247,7 +248,7 @@ class TestIndexTasks(unittest.TestCase):
 @with_nose_compatibility
 class TestMailTasks(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         setup_global_objects()
 
@@ -542,7 +543,7 @@ I'm not here'''
 
 @with_nose_compatibility
 class TestUserNotificationTasks(TestController):
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.setup_with_tools()
 
@@ -575,7 +576,7 @@ class TestUserNotificationTasks(TestController):
 @with_nose_compatibility
 class TestNotificationTasks(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         setup_global_objects()
 
@@ -622,7 +623,7 @@ class _TestArtifact(M.Artifact):
 @with_nose_compatibility
 class TestExportTasks(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         setup_global_objects()
         project = M.Project.query.get(shortname='test')
diff --git a/Allura/allura/tests/test_utils.py b/Allura/allura/tests/test_utils.py
index 1f2b1eeed..ac408a586 100644
--- a/Allura/allura/tests/test_utils.py
+++ b/Allura/allura/tests/test_utils.py
@@ -44,13 +44,14 @@ 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 allura.tests.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(self):
+    def setup_class(self, method):
         self.cp = utils.ConfigProxy(mybaz="baz")
 
     def test_getattr(self):
@@ -71,7 +72,7 @@ class TestConfigProxy(unittest.TestCase):
 @with_nose_compatibility
 class TestChunkedIterator(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_unit_test()
         config = {
             'ming.main.uri': 'mim://host/allura_test',
@@ -114,7 +115,7 @@ class TestChunkedList(unittest.TestCase):
 @with_nose_compatibility
 class TestAntispam(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_unit_test()
         self.a = utils.AntiSpam()
 
@@ -255,7 +256,7 @@ class TestIsTextFile(unittest.TestCase):
 @with_nose_compatibility
 class TestCodeStats(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_unit_test()
 
     def test_generate_code_stats(self):
diff --git a/Allura/allura/tests/test_validators.py b/Allura/allura/tests/test_validators.py
index cb9d5dd40..cc9f2fba0 100644
--- a/Allura/allura/tests/test_validators.py
+++ b/Allura/allura/tests/test_validators.py
@@ -24,7 +24,7 @@ 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
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 def setup_method(self, method):
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index cfc641674..df7512d59 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -49,6 +49,7 @@ from alluratest.controller import (
     TestRestApiBase,
 )
 import six
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 # important to be distinct from 'test' and 'test2' which ForgeGit and
@@ -61,7 +62,7 @@ with_git2 = td.with_tool(test_project_with_repo, 'git', 'src2', 'Git2')
 
 @with_nose_compatibility
 class TestWebhookBase:
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         self.patches = self.monkey_patch()
         for p in self.patches:
@@ -131,7 +132,7 @@ class TestValidators(TestWebhookBase):
 
 @with_nose_compatibility
 class TestWebhookController(TestController):
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.patches = self.monkey_patch()
         for p in self.patches:
@@ -677,7 +678,7 @@ class TestModels(TestWebhookBase):
 
 @with_nose_compatibility
 class TestWebhookRestController(TestRestApiBase):
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.patches = self.monkey_patch()
         for p in self.patches:
diff --git a/Allura/allura/tests/unit/__init__.py b/Allura/allura/tests/unit/__init__.py
index 34ba63e6e..43b8c8256 100644
--- a/Allura/allura/tests/unit/__init__.py
+++ b/Allura/allura/tests/unit/__init__.py
@@ -19,14 +19,14 @@ from alluratest.controller import setup_basic_test
 from allura.websetup.bootstrap import clear_all_database_tables
 
 
-def setUp(self):
+def setup_class(self, method):
     setup_basic_test()
 
 
 class MockPatchTestCase:
     patches = []
 
-    def setUp(self):
+    def setup_class(self, method):
         self._patch_instances = [patch_fn(self) for patch_fn in self.patches]
         for patch_instance in self._patch_instances:
             patch_instance.__enter__()
@@ -38,6 +38,6 @@ class MockPatchTestCase:
 
 class WithDatabase(MockPatchTestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         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 ec5a6acd0..10a0e113d 100644
--- a/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
+++ b/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
@@ -24,6 +24,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
@@ -34,7 +35,7 @@ class TestWhenModerating(WithDatabase):
                patches.fake_request_patch,
                patches.disable_notifications_patch]
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         post = create_post('mypost')
         discussion_controller = Mock(
@@ -88,7 +89,7 @@ class TestIndexWithNoPosts(WithDatabase):
 class TestIndexWithAPostInTheDiscussion(WithDatabase):
     patches = [patches.fake_app_patch]
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.post = create_post('mypost')
         discussion = self.post.discussion
diff --git a/Allura/allura/tests/unit/phone/test_nexmo.py b/Allura/allura/tests/unit/phone/test_nexmo.py
index c919f4bc9..776b4af01 100644
--- a/Allura/allura/tests/unit/phone/test_nexmo.py
+++ b/Allura/allura/tests/unit/phone/test_nexmo.py
@@ -19,6 +19,7 @@ import json
 from mock import patch
 from datadiff.tools import assert_equal
 from alluratest.tools import assert_in, assert_not_in
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 from allura.lib.phone.nexmo import NexmoPhoneService
 
@@ -26,7 +27,7 @@ from allura.lib.phone.nexmo import NexmoPhoneService
 @with_nose_compatibility
 class TestPhoneService:
 
-    def setUp(self):
+    def setup_class(self, method):
         config = {'phone.api_key': 'test-api-key',
                   'phone.api_secret': 'test-api-secret',
                   'site_name': 'Very loooooooooong site name'}
diff --git a/Allura/allura/tests/unit/phone/test_phone_service.py b/Allura/allura/tests/unit/phone/test_phone_service.py
index 2d69d48bc..fe5b2844e 100644
--- a/Allura/allura/tests/unit/phone/test_phone_service.py
+++ b/Allura/allura/tests/unit/phone/test_phone_service.py
@@ -19,6 +19,7 @@ from alluratest.tools import assert_true
 from datadiff.tools import assert_equal
 
 from allura.lib.phone import PhoneService
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 class MockPhoneService(PhoneService):
diff --git a/Allura/allura/tests/unit/spam/test_akismet.py b/Allura/allura/tests/unit/spam/test_akismet.py
index 00885460e..bee8403cd 100644
--- a/Allura/allura/tests/unit/spam/test_akismet.py
+++ b/Allura/allura/tests/unit/spam/test_akismet.py
@@ -26,6 +26,7 @@ from datetime import datetime
 from bson import ObjectId
 
 from allura.lib.spam.akismetfilter import AKISMET_AVAILABLE, AkismetSpamFilter
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @unittest.skipIf(not AKISMET_AVAILABLE, "Akismet not available")
diff --git a/Allura/allura/tests/unit/spam/test_spam_filter.py b/Allura/allura/tests/unit/spam/test_spam_filter.py
index bf6930331..fa71ccecd 100644
--- a/Allura/allura/tests/unit/spam/test_spam_filter.py
+++ b/Allura/allura/tests/unit/spam/test_spam_filter.py
@@ -26,6 +26,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 class MockFilter(SpamFilter):
@@ -78,7 +79,7 @@ class TestSpamFilter(unittest.TestCase):
 @with_nose_compatibility
 class TestSpamFilterFunctional:
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
 
     def test_record_result(self):
diff --git a/Allura/allura/tests/unit/spam/test_stopforumspam.py b/Allura/allura/tests/unit/spam/test_stopforumspam.py
index fa7481a1d..fbc6f9e40 100644
--- a/Allura/allura/tests/unit/spam/test_stopforumspam.py
+++ b/Allura/allura/tests/unit/spam/test_stopforumspam.py
@@ -22,12 +22,13 @@ from bson import ObjectId
 from alluratest.tools import assert_equal
 
 from allura.lib.spam.stopforumspamfilter import StopForumSpamSpamFilter
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestStopForumSpam:
 
-    def setUp(self):
+    def setup_class(self, method):
         self.content = 'spåm text'
 
         self.artifact = mock.Mock()
diff --git a/Allura/allura/tests/unit/test_app.py b/Allura/allura/tests/unit/test_app.py
index c3457f19c..f60b95432 100644
--- a/Allura/allura/tests/unit/test_app.py
+++ b/Allura/allura/tests/unit/test_app.py
@@ -24,6 +24,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
@@ -72,7 +73,7 @@ class TestInstall(WithDatabase):
 class TestDefaultDiscussion(WithDatabase):
     patches = [fake_app_patch]
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         install_app()
         self.discussion = model.Discussion.query.get(
@@ -93,7 +94,7 @@ class TestDefaultDiscussion(WithDatabase):
 class TestAppDefaults(WithDatabase):
     patches = [fake_app_patch]
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.app = install_app()
 
diff --git a/Allura/allura/tests/unit/test_artifact.py b/Allura/allura/tests/unit/test_artifact.py
index 59659bd5b..801a29fef 100644
--- a/Allura/allura/tests/unit/test_artifact.py
+++ b/Allura/allura/tests/unit/test_artifact.py
@@ -18,6 +18,7 @@
 import unittest
 
 from allura import model as M
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/test_discuss.py b/Allura/allura/tests/unit/test_discuss.py
index 8a5a8b09e..393d08921 100644
--- a/Allura/allura/tests/unit/test_discuss.py
+++ b/Allura/allura/tests/unit/test_discuss.py
@@ -20,6 +20,7 @@ from alluratest.tools import assert_false, assert_true
 from allura import model as M
 from allura.tests.unit import WithDatabase
 from allura.tests.unit.patches import fake_app_patch
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/test_helpers/test_ago.py b/Allura/allura/tests/unit/test_helpers/test_ago.py
index 6a26c26c5..0c538e752 100644
--- a/Allura/allura/tests/unit/test_helpers/test_ago.py
+++ b/Allura/allura/tests/unit/test_helpers/test_ago.py
@@ -21,12 +21,13 @@ from mock import patch
 from alluratest.tools import assert_equal
 
 from allura.lib import helpers
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestAgo:
 
-    def setUp(self):
+    def setup_class(self, method):
         self.start_time = datetime(2010, 1, 1, 0, 0, 0)
 
     def test_that_exact_times_are_phrased_in_seconds(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 281582804..102e48ab8 100644
--- a/Allura/allura/tests/unit/test_helpers/test_set_context.py
+++ b/Allura/allura/tests/unit/test_helpers/test_set_context.py
@@ -26,12 +26,13 @@ from allura.tests.unit import patches
 from allura.tests.unit.factories import (create_project,
                                          create_app_config,
                                          create_neighborhood)
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestWhenProjectIsFoundAndAppIsNot(WithDatabase):
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.myproject = create_project('myproject')
         set_context('myproject', neighborhood=self.myproject.neighborhood)
@@ -46,7 +47,7 @@ class TestWhenProjectIsFoundAndAppIsNot(WithDatabase):
 @with_nose_compatibility
 class TestWhenProjectIsFoundInNeighborhood(WithDatabase):
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.myproject = create_project('myproject')
         set_context('myproject', neighborhood=self.myproject.neighborhood)
@@ -62,7 +63,7 @@ class TestWhenProjectIsFoundInNeighborhood(WithDatabase):
 class TestWhenAppIsFoundByID(WithDatabase):
     patches = [patches.project_app_loading_patch]
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.myproject = create_project('myproject')
         self.app_config = create_app_config(self.myproject, 'my_mounted_app')
@@ -80,7 +81,7 @@ class TestWhenAppIsFoundByID(WithDatabase):
 class TestWhenAppIsFoundByMountPoint(WithDatabase):
     patches = [patches.project_app_loading_patch]
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.myproject = create_project('myproject')
         self.app_config = create_app_config(self.myproject, 'my_mounted_app')
diff --git a/Allura/allura/tests/unit/test_ldap_auth_provider.py b/Allura/allura/tests/unit/test_ldap_auth_provider.py
index 2d59ebc50..57c0ea6cb 100644
--- a/Allura/allura/tests/unit/test_ldap_auth_provider.py
+++ b/Allura/allura/tests/unit/test_ldap_auth_provider.py
@@ -32,12 +32,13 @@ from allura.lib import plugin
 from allura.lib import helpers as h
 from allura import model as M
 import six
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestLdapAuthenticationProvider:
 
-    def setUp(self):
+    def setup_class(self, method):
         setup_basic_test()
         self.provider = plugin.LdapAuthenticationProvider(Request.blank('/'))
 
diff --git a/Allura/allura/tests/unit/test_mixins.py b/Allura/allura/tests/unit/test_mixins.py
index f5208c9ab..670a8c929 100644
--- a/Allura/allura/tests/unit/test_mixins.py
+++ b/Allura/allura/tests/unit/test_mixins.py
@@ -17,12 +17,13 @@
 
 from mock import Mock
 from allura.model import VotableArtifact
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
 class TestVotableArtifact:
 
-    def setUp(self):
+    def setup_class(self, method):
         self.user1 = Mock()
         self.user1.username = 'test-user'
         self.user2 = Mock()
diff --git a/Allura/allura/tests/unit/test_package_path_loader.py b/Allura/allura/tests/unit/test_package_path_loader.py
index a9946b485..5c39771f6 100644
--- a/Allura/allura/tests/unit/test_package_path_loader.py
+++ b/Allura/allura/tests/unit/test_package_path_loader.py
@@ -25,6 +25,7 @@ import mock
 from tg import config
 
 from allura.lib.package_path_loader import PackagePathLoader
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/test_post_model.py b/Allura/allura/tests/unit/test_post_model.py
index b6591176e..a6d62fff8 100644
--- a/Allura/allura/tests/unit/test_post_model.py
+++ b/Allura/allura/tests/unit/test_post_model.py
@@ -22,6 +22,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
@@ -29,7 +30,7 @@ class TestPostModel(WithDatabase):
     patches = [patches.fake_app_patch,
                patches.disable_notifications_patch]
 
-    def setUp(self):
+    def setup_class(self, method):
         super().setup_method(method)
         self.post = create_post('mypost')
 
diff --git a/Allura/allura/tests/unit/test_project.py b/Allura/allura/tests/unit/test_project.py
index e5ce38aa9..3fefa5fe1 100644
--- a/Allura/allura/tests/unit/test_project.py
+++ b/Allura/allura/tests/unit/test_project.py
@@ -23,6 +23,7 @@ from tg import config
 from allura import model as M
 from allura.lib import helpers as h
 from allura.app import SitemapEntry
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/test_repo.py b/Allura/allura/tests/unit/test_repo.py
index c8b7e4d74..12bc058c0 100644
--- a/Allura/allura/tests/unit/test_repo.py
+++ b/Allura/allura/tests/unit/test_repo.py
@@ -31,6 +31,7 @@ from allura.model.repository import zipdir, prefix_paths_union
 from allura.model.repo_refresh import (
     _group_commits,
 )
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
@@ -313,7 +314,7 @@ class TestPrefixPathsUnion(unittest.TestCase):
 @with_nose_compatibility
 class TestGroupCommits:
 
-    def setUp(self):
+    def setup_class(self, method):
         self.repo = Mock()
         self.repo.symbolics_for_commit.return_value = ([], [])
 
diff --git a/Allura/allura/tests/unit/test_session.py b/Allura/allura/tests/unit/test_session.py
index b69a9f49d..fbf6e7f0e 100644
--- a/Allura/allura/tests/unit/test_session.py
+++ b/Allura/allura/tests/unit/test_session.py
@@ -28,6 +28,7 @@ from allura.model.session import (
     ArtifactSessionExtension,
     substitute_extensions,
 )
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 def test_extensions_cm():
@@ -87,7 +88,7 @@ class TestSessionExtension(TestCase):
 @with_nose_compatibility
 class TestIndexerSessionExtension(TestSessionExtension):
 
-    def setUp(self):
+    def setup_class(self, method):
         session = mock.Mock()
         self.ExtensionClass = IndexerSessionExtension
         self.extension = self.ExtensionClass(session)
@@ -126,7 +127,7 @@ class TestIndexerSessionExtension(TestSessionExtension):
 @with_nose_compatibility
 class TestArtifactSessionExtension(TestSessionExtension):
 
-    def setUp(self):
+    def setup_class(self, method):
         session = mock.Mock(disable_index=False)
         self.ExtensionClass = ArtifactSessionExtension
         self.extension = self.ExtensionClass(session)
@@ -156,7 +157,7 @@ class TestArtifactSessionExtension(TestSessionExtension):
 @with_nose_compatibility
 class TestBatchIndexer(TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         session = mock.Mock()
         self.extcls = BatchIndexer
         self.ext = self.extcls(session)
diff --git a/Allura/allura/tests/unit/test_sitemapentry.py b/Allura/allura/tests/unit/test_sitemapentry.py
index cbe76f874..e4e56deec 100644
--- a/Allura/allura/tests/unit/test_sitemapentry.py
+++ b/Allura/allura/tests/unit/test_sitemapentry.py
@@ -19,6 +19,7 @@ import unittest
 from mock import Mock
 
 from allura.app import SitemapEntry
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/test_solr.py b/Allura/allura/tests/unit/test_solr.py
index 453b52af9..7bb5a188a 100644
--- a/Allura/allura/tests/unit/test_solr.py
+++ b/Allura/allura/tests/unit/test_solr.py
@@ -26,6 +26,7 @@ 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 allura.tests.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
@@ -121,7 +122,7 @@ class TestSolr(unittest.TestCase):
 @with_nose_compatibility
 class TestSearchIndexable(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         self.obj = SearchIndexable()
 
     def test_solarize_empty_index(self):
@@ -146,7 +147,7 @@ class TestSearchIndexable(unittest.TestCase):
 @with_nose_compatibility
 class TestSearch_app(unittest.TestCase):
 
-    def setUp(self):
+    def setup_class(self, method):
         # need to create the "test" project so @td.with_wiki works
         setup_basic_test()
 
diff --git a/ForgeTracker/forgetracker/tests/test_app.py b/ForgeTracker/forgetracker/tests/test_app.py
index c9fe6affc..b67bdb969 100644
--- a/ForgeTracker/forgetracker/tests/test_app.py
+++ b/ForgeTracker/forgetracker/tests/test_app.py
@@ -33,6 +33,7 @@ from allura.tests import decorators as td
 from forgetracker import model as TM
 from forgetracker.site_stats import tickets_stats_24hr
 from forgetracker.tests.functional.test_root import TrackerTestController
+from allura.tests.pytest_helpers import with_nose_compatibility
 
 
 class TestApp: