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/12 15:06:26 UTC

[allura] branch dw/8455 updated (b44a386e9 -> ba1a294cb)

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

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


 discard b44a386e9 fixup! fixup! all trivial failures resolved for ./Allura, only legit failures remain
 discard 9e34ceb85 fixup! all trivial failures resolved for ./Allura, only legit failures remain
 discard 14dd46807 fixup! All tests in ./Allura collecting, and test_auth completely passing
 discard e54eb4670 fixup! first substantial test file mostly passing under pytest
 discard 727ac44d4 8455 nose2pytest for ./Allura
     add cff9c2913 make "c" a template global too
     new a784125bf 8455 nose2pytest for ./Allura
     new c5ee77989 first substantial test file mostly passing under pytest
     new 8500cccc7 All tests in ./Allura collecting, and test_auth completely passing
     new ba1a294cb all trivial failures resolved for ./Allura, only legit failures remain

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

 * -- * -- B -- O -- O -- O   (b44a386e9)
            \
             N -- N -- N   refs/heads/dw/8455 (ba1a294cb)

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

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

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


Summary of changes:
 Allura/allura/config/app_cfg.py          |  1 +
 Allura/allura/tests/test_globals.py      | 71 ++------------------------------
 Allura/allura/tests/unit/__init__.py     |  2 +-
 Allura/allura/tests/unit/test_app.py     |  9 ----
 Allura/allura/tests/unit/test_discuss.py |  8 ----
 requirements.txt                         | 23 ++++++++++-
 6 files changed, 27 insertions(+), 87 deletions(-)


[allura] 04/04: all trivial failures resolved for ./Allura, only legit failures remain

Posted by di...@apache.org.
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 ba1a294cb2d0edf0da6831c4c56c193d94846ecd
Author: Kenton Taylor <kt...@slashdotmedia.com>
AuthorDate: Tue Aug 23 20:19:46 2022 +0000

    all trivial failures resolved for ./Allura, only legit failures remain
---
 Allura/allura/tests/functional/test_admin.py       |    2 +-
 Allura/allura/tests/functional/test_discuss.py     |    2 +-
 Allura/allura/tests/functional/test_feeds.py       |    4 +-
 Allura/allura/tests/functional/test_nav.py         |    2 +-
 .../tests/functional/test_personal_dashboard.py    |    2 +-
 Allura/allura/tests/functional/test_rest.py        |    2 +-
 Allura/allura/tests/functional/test_root.py        |    4 +-
 Allura/allura/tests/functional/test_site_admin.py  |    4 +-
 .../allura/tests/functional/test_user_profile.py   |    3 -
 Allura/allura/tests/model/test_artifact.py         |    5 +-
 Allura/allura/tests/model/test_auth.py             |    2 +-
 Allura/allura/tests/model/test_discussion.py       |    4 +-
 Allura/allura/tests/model/test_filesystem.py       |    2 +-
 Allura/allura/tests/model/test_monq.py             |    2 +-
 Allura/allura/tests/model/test_neighborhood.py     |    2 +-
 Allura/allura/tests/model/test_notification.py     |   12 +-
 Allura/allura/tests/model/test_oauth.py            |    2 +-
 Allura/allura/tests/model/test_project.py          |    3 +-
 Allura/allura/tests/model/test_repo.py             |    8 +-
 Allura/allura/tests/model/test_timeline.py         |    2 +-
 .../tests/scripts/test_create_sitemap_files.py     |    2 +-
 .../allura/tests/scripts/test_delete_projects.py   |    2 +-
 Allura/allura/tests/scripts/test_misc_scripts.py   |    2 +-
 Allura/allura/tests/scripts/test_reindexes.py      |    4 +-
 .../tests/templates/jinja_master/test_lib.py       |    2 +-
 Allura/allura/tests/test_app.py                    |   12 +-
 Allura/allura/tests/test_commands.py               |   19 +-
 Allura/allura/tests/test_decorators.py             |    4 +
 Allura/allura/tests/test_diff.py                   |    2 +-
 Allura/allura/tests/test_globals.py                | 1452 ++++++++++----------
 Allura/allura/tests/test_helpers.py                |   19 +-
 Allura/allura/tests/test_mail_util.py              |    6 +-
 Allura/allura/tests/test_middlewares.py            |    2 +-
 Allura/allura/tests/test_multifactor.py            |    8 +-
 Allura/allura/tests/test_plugin.py                 |   11 +-
 Allura/allura/tests/test_scripttask.py             |    2 +-
 Allura/allura/tests/test_tasks.py                  |   16 +-
 Allura/allura/tests/test_utils.py                  |    8 +-
 Allura/allura/tests/test_validators.py             |   37 +-
 Allura/allura/tests/test_webhooks.py               |   10 +-
 Allura/allura/tests/unit/__init__.py               |    6 +-
 .../test_discussion_moderation_controller.py       |    4 +-
 Allura/allura/tests/unit/phone/test_nexmo.py       |    2 +-
 Allura/allura/tests/unit/spam/test_spam_filter.py  |    2 +-
 .../allura/tests/unit/spam/test_stopforumspam.py   |    2 +-
 Allura/allura/tests/unit/test_app.py               |    6 +-
 Allura/allura/tests/unit/test_discuss.py           |    2 -
 Allura/allura/tests/unit/test_helpers/test_ago.py  |    2 +-
 .../tests/unit/test_helpers/test_set_context.py    |    8 +-
 .../allura/tests/unit/test_ldap_auth_provider.py   |    2 +-
 Allura/allura/tests/unit/test_mixins.py            |    2 +-
 Allura/allura/tests/unit/test_post_model.py        |    2 +-
 Allura/allura/tests/unit/test_repo.py              |    2 +-
 Allura/allura/tests/unit/test_session.py           |    6 +-
 Allura/allura/tests/unit/test_solr.py              |    8 +-
 .../forgetracker/tests/functional/test_root.py     |    4 +-
 56 files changed, 879 insertions(+), 870 deletions(-)

diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index 8c1caab3a..b5f39a393 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -964,7 +964,7 @@ class TestProjectAdmin(TestController):
 @with_nose_compatibility
 class TestExport(TestController):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         self.setup_with_tools()
 
diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py
index 5164142d3..a2442074a 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -402,7 +402,7 @@ class TestDiscuss(TestDiscussBase):
 @with_nose_compatibility
 class TestAttachment(TestDiscussBase):
 
-    def setup_class(self, method):
+    def setup_method(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 de32fa04c..4baf5f9d0 100644
--- a/Allura/allura/tests/functional/test_feeds.py
+++ b/Allura/allura/tests/functional/test_feeds.py
@@ -26,8 +26,8 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestFeeds(TestController):
 
-    def setup_class(self, method):
-        TestController.setUp(self)
+    def setup_method(self, method):
+        TestController.setup_method(self, method)
         self._setUp()
 
     @td.with_wiki
diff --git a/Allura/allura/tests/functional/test_nav.py b/Allura/allura/tests/functional/test_nav.py
index f865d8f32..840a97969 100644
--- a/Allura/allura/tests/functional/test_nav.py
+++ b/Allura/allura/tests/functional/test_nav.py
@@ -33,7 +33,7 @@ class TestNavigation(TestController):
     - Test of logo.
     """
 
-    def setup_class(self, method):
+    def setup_method(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_personal_dashboard.py b/Allura/allura/tests/functional/test_personal_dashboard.py
index ea3244123..428afb029 100644
--- a/Allura/allura/tests/functional/test_personal_dashboard.py
+++ b/Allura/allura/tests/functional/test_personal_dashboard.py
@@ -89,7 +89,7 @@ class TestTicketsSection(TrackerTestController):
 @with_nose_compatibility
 class TestMergeRequestsSection(TestController):
 
-    def setup_class(self, method):
+    def setup_method(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 a24ff59e4..0be71b396 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -420,7 +420,7 @@ class TestRestHome(TestRestApiBase):
 @with_nose_compatibility
 class TestRestNbhdAddProject(TestRestApiBase):
 
-    def setup_class(self, method):
+    def setup_method(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 47105f8d9..95a1ea15b 100644
--- a/Allura/allura/tests/functional/test_root.py
+++ b/Allura/allura/tests/functional/test_root.py
@@ -47,7 +47,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestRootController(TestController):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         n_adobe = M.Neighborhood.query.get(name='Adobe')
         assert n_adobe
@@ -192,7 +192,7 @@ class TestRootController(TestController):
 
 @with_nose_compatibility
 class TestRootWithSSLPattern(TestController):
-    def setup_class(self, method):
+    def setup_method(self, method):
         with td.patch_middleware_config({'force_ssl.pattern': '^/auth'}):
             super().setup_method(method)
 
diff --git a/Allura/allura/tests/functional/test_site_admin.py b/Allura/allura/tests/functional/test_site_admin.py
index fce6abe29..7ca541362 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -361,7 +361,7 @@ class TestProjectsSearch(TestController):
         'id': 'allura/model/project/Project#53ccf6e8100d2b0741746e9f',
     }])
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         # Create project that matches TEST_HIT id
         _id = ObjectId('53ccf6e8100d2b0741746e9f')
@@ -419,7 +419,7 @@ class TestUsersSearch(TestController):
         'user_registration_date_dt': '2014-09-09T13:17:38Z',
         'username_s': 'darth'}])
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         # Create user that matches TEST_HIT id
         _id = ObjectId('540efdf2100d2b1483155d39')
diff --git a/Allura/allura/tests/functional/test_user_profile.py b/Allura/allura/tests/functional/test_user_profile.py
index 9ec08f518..4a54ffa69 100644
--- a/Allura/allura/tests/functional/test_user_profile.py
+++ b/Allura/allura/tests/functional/test_user_profile.py
@@ -269,9 +269,6 @@ class TestUserProfile(TestController):
         assert 'content="noindex, follow"' not in r.text
 
 
-
-
-
 @with_nose_compatibility
 class TestUserProfileHasAccessAPI(TestRestApiBase):
 
diff --git a/Allura/allura/tests/model/test_artifact.py b/Allura/allura/tests/model/test_artifact.py
index 43ac4828d..8f0100e82 100644
--- a/Allura/allura/tests/model/test_artifact.py
+++ b/Allura/allura/tests/model/test_artifact.py
@@ -54,7 +54,10 @@ class Checkmessage(M.Message):
 Mapper.compile_all()
 
 
-def setup_method(self, method):
+# def setup_method_wrapper(fn):
+#     fn(None)
+
+def setup_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 870fcfa21..2d6aa75f7 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -45,7 +45,7 @@ from alluratest.controller import setup_basic_test, setup_global_objects, setup_
 from allura.tests.pytest_helpers import with_nose_compatibility
 
 
-def setup_method(self, method):
+def setup_method():
     setup_basic_test()
     ThreadLocalORMSession.close_all()
     setup_global_objects()
diff --git a/Allura/allura/tests/model/test_discussion.py b/Allura/allura/tests/model/test_discussion.py
index 86f3467e0..1bfc445b9 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_method(self, method):
+def setup_method():
     controller = TestController()
-    controller.setup_method(method)
+    controller.setup_method(None)
     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 29a32b5d8..df0e5b7d5 100644
--- a/Allura/allura/tests/model/test_filesystem.py
+++ b/Allura/allura/tests/model/test_filesystem.py
@@ -41,7 +41,7 @@ Mapper.compile_all()
 @with_nose_compatibility
 class TestFile(TestCase):
 
-    def setup_class(self, method):
+    def setup_method(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 f2fd7ad29..9dc682149 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_method(self, method):
+def setup_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 28b85dca1..26a773331 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_method(self, method):
+def setup_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 5df6a54df..7e0b8ed3d 100644
--- a/Allura/allura/tests/model/test_notification.py
+++ b/Allura/allura/tests/model/test_notification.py
@@ -37,7 +37,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestNotification(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -169,7 +169,7 @@ class TestNotification(unittest.TestCase):
 @with_nose_compatibility
 class TestPostNotifications(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -308,7 +308,7 @@ class TestPostNotifications(unittest.TestCase):
 @with_nose_compatibility
 class TestSubscriptionTypes(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -346,10 +346,10 @@ class TestSubscriptionTypes(unittest.TestCase):
     def test_message(self):
         self._test_message()
 
-        self.setup_method(method)
+        self.setup_method(None)
         self._test_message()
 
-        self.setup_method(method)
+        self.setup_method(None)
         M.notification.MAILBOX_QUIESCENT = timedelta(minutes=1)
         # will raise "assert msg is not None" since the new message is not 1
         # min old:
@@ -481,7 +481,7 @@ class TestSubscriptionTypes(unittest.TestCase):
 
 @with_nose_compatibility
 class TestSiteNotification(unittest.TestCase):
-    def setup_class(self, method):
+    def setup_method(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 2a8b82ad6..67d12b094 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_method(self, method):
+def setup_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 339168f15..524754bc3 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_method(self, method):
+def setup_method():
     setup_basic_test()
     setup_with_tools()
 
@@ -41,6 +41,7 @@ def setup_with_tools():
     setup_global_objects()
 
 
+@with_setup(setup_method)
 def test_project():
     assert type(c.project.sidebar_menu()) == list
     assert c.project.script_name in c.project.url()
diff --git a/Allura/allura/tests/model/test_repo.py b/Allura/allura/tests/model/test_repo.py
index 60d5487c7..873b411c8 100644
--- a/Allura/allura/tests/model/test_repo.py
+++ b/Allura/allura/tests/model/test_repo.py
@@ -74,7 +74,7 @@ class RepoImplTestBase:
 
 
 class RepoTestBase(unittest.TestCase):
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
 
     @mock.patch('allura.model.repository.Repository.url')
@@ -132,7 +132,7 @@ class RepoTestBase(unittest.TestCase):
 
 @with_nose_compatibility
 class TestLastCommit(unittest.TestCase):
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         setup_global_objects()
         self.repo = mock.Mock(
@@ -405,7 +405,7 @@ class TestLastCommit(unittest.TestCase):
 
 @with_nose_compatibility
 class TestModelCache(unittest.TestCase):
-    def setup_class(self, method):
+    def setup_method(self, method):
         self.cache = M.repository.ModelCache()
 
     def test_normalize_query(self):
@@ -685,7 +685,7 @@ class TestModelCache(unittest.TestCase):
 @with_nose_compatibility
 class TestMergeRequest:
 
-    def setup_class(self, method):
+    def setup_method(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 f1e32434b..3d8bc26e7 100644
--- a/Allura/allura/tests/model/test_timeline.py
+++ b/Allura/allura/tests/model/test_timeline.py
@@ -27,7 +27,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 class TestActivityObject_Functional:
     # NOTE not for unit tests, this class sets up all the junk
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         setup_global_objects()
 
diff --git a/Allura/allura/tests/scripts/test_create_sitemap_files.py b/Allura/allura/tests/scripts/test_create_sitemap_files.py
index 6df1d6f3a..c5835109f 100644
--- a/Allura/allura/tests/scripts/test_create_sitemap_files.py
+++ b/Allura/allura/tests/scripts/test_create_sitemap_files.py
@@ -33,7 +33,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestCreateSitemapFiles:
 
-    def setup_class(self, method):
+    def setup_method(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 29a983f8c..57c219f54 100644
--- a/Allura/allura/tests/scripts/test_delete_projects.py
+++ b/Allura/allura/tests/scripts/test_delete_projects.py
@@ -31,7 +31,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestDeleteProjects(TestController):
 
-    def setup_class(self, method):
+    def setup_method(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 c36a064b6..8ebbba848 100644
--- a/Allura/allura/tests/scripts/test_misc_scripts.py
+++ b/Allura/allura/tests/scripts/test_misc_scripts.py
@@ -28,7 +28,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestClearOldNotifications:
 
-    def setup_class(self, method):
+    def setup_method(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 eaa4eb7bc..53a51f04a 100644
--- a/Allura/allura/tests/scripts/test_reindexes.py
+++ b/Allura/allura/tests/scripts/test_reindexes.py
@@ -28,7 +28,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestReindexProjects:
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
 
     def run_script(self, options):
@@ -53,7 +53,7 @@ class TestReindexProjects:
 @with_nose_compatibility
 class TestReindexUsers:
 
-    def setup_class(self, method):
+    def setup_method(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 87cc5fe87..f3eea9edd 100644
--- a/Allura/allura/tests/templates/jinja_master/test_lib.py
+++ b/Allura/allura/tests/templates/jinja_master/test_lib.py
@@ -30,7 +30,7 @@ def strip_space(s):
 
 
 class TemplateTest:
-    def setup_class(self, method):
+    def setup_method(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 eeeb9ecea..b93170a8e 100644
--- a/Allura/allura/tests/test_app.py
+++ b/Allura/allura/tests/test_app.py
@@ -18,17 +18,18 @@
 from tg import tmpl_context as c
 import mock
 from ming.base import Object
-from alluratest.tools import assert_equal, assert_raises
+from alluratest.tools import assert_raises
 from formencode import validators as fev
 
 from alluratest.controller import setup_unit_test
+from alluratest.tools import with_setup
 from allura import app
 from allura.lib.app_globals import Icon
 from allura.lib import mail_util
 from allura.tests.pytest_helpers import with_nose_compatibility
 
 
-def setup_method(self, method):
+def setup_method():
     setup_unit_test()
     c.user._id = None
     c.project = mock.Mock()
@@ -48,7 +49,6 @@ def setup_method(self, method):
     c.app.url = c.app.config.url()
     c.app.__version__ = '0.0'
 
-
 def test_config_options():
     options = [
         app.ConfigOption('test1', str, 'MyTestValue'),
@@ -77,11 +77,13 @@ def test_config_option_with_validator():
     assert_raises(fev.Invalid, opt.validate, '')
 
 
+@with_setup(setup_method)
 def test_options_on_install_default():
     a = app.Application(c.project, c.app.config)
     assert a.options_on_install() == []
 
 
+@with_setup(setup_method)
 def test_options_on_install():
     opts = [app.ConfigOption('url', str, None),
             app.ConfigOption('private', bool, None)]
@@ -94,7 +96,7 @@ def test_options_on_install():
     a = TestApp(c.project, c.app.config)
     assert a.options_on_install() == opts
 
-
+@with_setup(setup_method)
 def test_main_menu():
     class TestApp(app.Application):
         @property
@@ -110,6 +112,7 @@ def test_main_menu():
     assert main_menu[0].children == []  # default main_menu implementation should drop the children from sitemap()
 
 
+@with_setup(setup_method)
 def test_sitemap():
     sm = app.SitemapEntry('test', '')[
         app.SitemapEntry('a', 'a/'),
@@ -125,6 +128,7 @@ def test_sitemap():
     assert len(sm.children) == 3
 
 
+@with_setup(setup_method)
 @mock.patch('allura.app.Application.PostClass.query.get')
 def test_handle_artifact_unicode(qg):
     """
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index 03b3fb954..84bac7a10 100644
--- a/Allura/allura/tests/test_commands.py
+++ b/Allura/allura/tests/test_commands.py
@@ -19,7 +19,7 @@
 import datetime
 
 import six
-from alluratest.tools import assert_raises, assert_in
+from alluratest.tools import assert_raises, assert_in, with_setup
 from testfixtures import OutputCapture
 
 from datadiff.tools import assert_equal
@@ -30,7 +30,7 @@ from mock import Mock, call, patch
 import pymongo
 import pkg_resources
 
-from alluratest.controller import setup_basic_test, setup_global_objects
+from alluratest.controller import setup_basic_test, setup_global_objects, setup_unit_test
 from allura.command import base, script, set_neighborhood_features, \
     create_neighborhood, show_models, taskd_cleanup, taskd
 from allura import model as M
@@ -47,12 +47,13 @@ class EmptyClass:
     pass
 
 
-def setup_class(self, method):
+def setup_method():
     """Method called by nose before running each test"""
     setup_basic_test()
     setup_global_objects()
+    setup_unit_test()
 
-
+@with_setup(setup_method)
 def test_script():
     cmd = script.ScriptCommand('script')
     cmd.run(
@@ -61,6 +62,7 @@ def test_script():
                   [test_config, pkg_resources.resource_filename('allura', 'tests/tscript_error.py')])
 
 
+@with_setup(setup_method)
 def test_set_neighborhood_max_projects():
     neighborhood = M.Neighborhood.query.find().first()
     n_id = neighborhood._id
@@ -84,6 +86,7 @@ def test_set_neighborhood_max_projects():
                   [test_config, str(n_id), 'max_projects', '2.8'])
 
 
+@with_setup(setup_method)
 def test_set_neighborhood_private():
     neighborhood = M.Neighborhood.query.find().first()
     n_id = neighborhood._id
@@ -109,6 +112,7 @@ def test_set_neighborhood_private():
                   [test_config, str(n_id), 'private_projects', '2.8'])
 
 
+@with_setup(setup_method)
 def test_set_neighborhood_google_analytics():
     neighborhood = M.Neighborhood.query.find().first()
     n_id = neighborhood._id
@@ -134,6 +138,7 @@ def test_set_neighborhood_google_analytics():
                   [test_config, str(n_id), 'google_analytics', '2.8'])
 
 
+@with_setup(setup_method)
 def test_set_neighborhood_css():
     neighborhood = M.Neighborhood.query.find().first()
     n_id = neighborhood._id
@@ -168,6 +173,7 @@ def test_set_neighborhood_css():
                   [test_config, str(n_id), 'css', 'True'])
 
 
+@with_setup(setup_method)
 def test_update_neighborhood():
     cmd = create_neighborhood.UpdateNeighborhoodCommand('update-neighborhood')
     cmd.run([test_config, 'Projects', 'True'])
@@ -341,7 +347,7 @@ class TestTaskCommand:
 @with_nose_compatibility
 class TestTaskdCleanupCommand:
 
-    def setup_class(self, method):
+    def setup_method(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'
@@ -503,6 +509,9 @@ class TestReindexAsTask:
 @with_nose_compatibility
 class TestReindexCommand:
 
+    def setup_method(self, method):
+        setup_method()
+
     @patch('allura.command.show_models.g')
     def test_skip_solr_delete(self, g):
         cmd = show_models.ReindexCommand('reindex')
diff --git a/Allura/allura/tests/test_decorators.py b/Allura/allura/tests/test_decorators.py
index 5bdf5cf70..033e5c41a 100644
--- a/Allura/allura/tests/test_decorators.py
+++ b/Allura/allura/tests/test_decorators.py
@@ -23,11 +23,15 @@ import gc
 from alluratest.tools import assert_equal, assert_not_equal
 from allura.tests.pytest_helpers import with_nose_compatibility
 from allura.lib.decorators import task, memoize
+from alluratest.controller import setup_basic_test, setup_global_objects
 
 
 @with_nose_compatibility
 class TestTask(TestCase):
 
+    def setup_method(self, method):
+        setup_basic_test()
+
     def test_no_params(self):
         @task
         def func():
diff --git a/Allura/allura/tests/test_diff.py b/Allura/allura/tests/test_diff.py
index 505b95c33..d128386b0 100644
--- a/Allura/allura/tests/test_diff.py
+++ b/Allura/allura/tests/test_diff.py
@@ -24,7 +24,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestHtmlSideBySideDiff(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         self.diff = HtmlSideBySideDiff()
 
     def test_render_change(self):
diff --git a/Allura/allura/tests/test_globals.py b/Allura/allura/tests/test_globals.py
index b1f67003b..f75f2d268 100644
--- a/Allura/allura/tests/test_globals.py
+++ b/Allura/allura/tests/test_globals.py
@@ -25,7 +25,6 @@ import six
 from mock import patch, Mock
 
 from bson import ObjectId
-from alluratest.tools import with_setup, assert_equal, assert_in, assert_not_in
 from tg import tmpl_context as c, app_globals as g
 import tg
 from oembed import OEmbedError
@@ -54,756 +53,6 @@ def squish_spaces(text):
     # \xa0 is &nbsp; in unicode form
     return re.sub(r'[\s\xa0]+', ' ', text)
 
-
-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()
-    setup_with_tools()
-
-
-def teadown_method():
-    setup_method(None)
-
-
-@td.with_wiki
-def setup_with_tools():
-    setup_global_objects()
-
-
-@td.with_wiki
-def test_app_globals():
-    with h.push_context('test', 'wiki', neighborhood='Projects'):
-        assert g.app_static(
-            'css/wiki.css') == '/nf/_static_/wiki/css/wiki.css', g.app_static('css/wiki.css')
-
-
-@with_setup(setup_method)
-def test_macro_projects():
-    file_name = 'neo-icon-set-454545-256x350.png'
-    file_path = os.path.join(
-        allura.__path__[0], 'nf', 'allura', 'images', file_name)
-
-    p_nbhd = M.Neighborhood.query.get(name='Projects')
-    p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
-    c.project = p_test
-    icon_file = open(file_path, 'rb')
-    M.ProjectFile.save_image(
-        file_name, icon_file, content_type='image/png',
-        square=True, thumbnail_size=(48, 48),
-        thumbnail_meta=dict(project_id=c.project._id, category='icon'))
-    icon_file.close()
-    p_test2 = M.Project.query.get(
-        shortname='test2', neighborhood_id=p_nbhd._id)
-    c.project = p_test2
-    icon_file = open(file_path, 'rb')
-    M.ProjectFile.save_image(
-        file_name, icon_file, content_type='image/png',
-        square=True, thumbnail_size=(48, 48),
-        thumbnail_meta=dict(project_id=c.project._id, category='icon'))
-    icon_file.close()
-    p_sub1 = M.Project.query.get(
-        shortname='test/sub1', neighborhood_id=p_nbhd._id)
-    c.project = p_sub1
-    icon_file = open(file_path, 'rb')
-    M.ProjectFile.save_image(
-        file_name, icon_file, content_type='image/png',
-        square=True, thumbnail_size=(48, 48),
-        thumbnail_meta=dict(project_id=c.project._id, category='icon'))
-    icon_file.close()
-    p_test.labels = ['test', 'root']
-    p_sub1.labels = ['test', 'sub1']
-    # Make one project private
-    p_test.private = False
-    p_sub1.private = False
-    p_test2.private = True
-
-    ThreadLocalORMSession.flush_all()
-
-    with h.push_config(c,
-                       project=p_nbhd.neighborhood_project,
-                       user=M.User.by_username('test-admin')):
-        r = g.markdown_wiki.convert('[[projects]]')
-        assert 'alt="Test Project Logo"' in r, r
-        assert 'alt="A Subproject Logo"' in r, r
-        r = g.markdown_wiki.convert('[[projects labels=root]]')
-        assert 'alt="Test Project Logo"' in r, r
-        assert 'alt="A Subproject Logo"' not in r, r
-        r = g.markdown_wiki.convert('[[projects labels=sub1]]')
-        assert 'alt="Test Project Logo"' not in r, r
-        assert 'alt="A Subproject Logo"' in r, r
-        r = g.markdown_wiki.convert('[[projects labels=test]]')
-        assert 'alt="Test Project Logo"' in r, r
-        assert 'alt="A Subproject Logo"' in r, r
-        r = g.markdown_wiki.convert('[[projects labels=test,root]]')
-        assert 'alt="Test Project Logo"' in r, r
-        assert 'alt="A Subproject Logo"' not in r, r
-        r = g.markdown_wiki.convert('[[projects labels=test,sub1]]')
-        assert 'alt="Test Project Logo"' not in r, r
-        assert 'alt="A Subproject Logo"' in r, r
-        r = g.markdown_wiki.convert('[[projects labels=root|sub1]]')
-        assert 'alt="Test Project Logo"' in r, r
-        assert 'alt="A Subproject Logo"' in r, r
-        r = g.markdown_wiki.convert('[[projects labels=test,root|root,sub1]]')
-        assert 'alt="Test Project Logo"' in r, r
-        assert 'alt="A Subproject Logo"' not in r, r
-        r = g.markdown_wiki.convert('[[projects labels=test,root|test,sub1]]')
-        assert 'alt="Test Project Logo"' in r, r
-        assert 'alt="A Subproject Logo"' in r, r
-        r = g.markdown_wiki.convert('[[projects show_total=True sort=random]]')
-        assert '<p class="macro_projects_total">3 Projects' in r, r
-        r = g.markdown_wiki.convert(
-            '[[projects show_total=True private=True sort=random]]')
-        assert '<p class="macro_projects_total">1 Projects' in r, r
-        assert 'alt="Test 2 Logo"' in r, r
-        assert 'alt="Test Project Logo"' not in r, r
-        assert 'alt="A Subproject Logo"' not in r, r
-
-        r = g.markdown_wiki.convert('[[projects show_proj_icon=True]]')
-        assert 'alt="Test Project Logo"' in r
-        r = g.markdown_wiki.convert('[[projects show_proj_icon=False]]')
-        assert 'alt="Test Project Logo"' not in r
-
-
-def test_macro_neighborhood_feeds():
-    p_nbhd = M.Neighborhood.query.get(name='Projects')
-    p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
-    with h.push_context('--init--', 'wiki', neighborhood='Projects'):
-        r = g.markdown_wiki.convert('[[neighborhood_feeds tool_name=wiki]]')
-        assert 'Home modified by' in r, r
-        r = re.sub(r'<small>.*? ago</small>', '', r)  # remove "less than 1 second ago" etc
-        orig_len = len(r)
-        # Make project private & verify we don't see its new feed items
-        anon = M.User.anonymous()
-        p_test.acl.insert(0, M.ACE.deny(
-            M.ProjectRole.anonymous(p_test)._id, 'read'))
-        ThreadLocalORMSession.flush_all()
-        pg = WM.Page.query.get(title='Home', app_config_id=c.app.config._id)
-        pg.text = 'Change'
-        with h.push_config(c, user=M.User.by_username('test-admin')):
-            pg.commit()
-        r = g.markdown_wiki.convert('[[neighborhood_feeds tool_name=wiki]]')
-        r = re.sub(r'<small>.*? ago</small>', '', r)  # remove "less than 1 second ago" etc
-        new_len = len(r)
-        assert new_len == orig_len
-        p = BM.BlogPost(title='test me',
-                        neighborhood_id=p_test.neighborhood_id)
-        p.text = 'test content'
-        p.state = 'published'
-        p.make_slug()
-        with h.push_config(c, user=M.User.by_username('test-admin')):
-            p.commit()
-        ThreadLocalORMSession.flush_all()
-        with h.push_config(c, user=anon):
-            r = g.markdown_wiki.convert('[[neighborhood_blog_posts]]')
-        assert 'test content' in r
-
-
-@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)
-    p_test.add_user(M.User.by_username('test-user'), ['Developer'])
-    p_test.add_user(M.User.by_username('test-user-0'), ['Member'])
-    ThreadLocalORMSession.flush_all()
-    r = g.markdown_wiki.convert('[[members limit=2]]').replace('\t', '').replace('\n', '')
-    assert (r ==
-                 '<div class="markdown_content"><h6>Project Members:</h6>'
-                 '<ul class="md-users-list">'
-                 '<li><a href="/u/test-admin/">Test Admin</a> (admin)</li>'
-                 '<li><a href="/u/test-user/">Test User</a></li>'
-                 '<li class="md-users-list-more"><a href="/p/test/_members">All Members</a></li>'
-                 '</ul>'
-                 '</div>')
-
-
-@with_setup(setup_method)
-def test_macro_members_escaping():
-    user = M.User.by_username('test-admin')
-    user.display_name = 'Test Admin <script>'
-    r = g.markdown_wiki.convert('[[members]]')
-    assert (r.replace('\n', '').replace('\t', '') ==
-                 '<div class="markdown_content"><h6>Project Members:</h6>'
-                 '<ul class="md-users-list">'
-                 '<li><a href="/u/test-admin/">Test Admin &lt;script&gt;</a> (admin)</li>'
-                 '</ul></div>')
-
-
-@with_setup(setup_method)
-def test_macro_project_admins():
-    user = M.User.by_username('test-admin')
-    user.display_name = 'Test Ådmin <script>'
-    with h.push_context('test', neighborhood='Projects'):
-        r = g.markdown_wiki.convert('[[project_admins]]')
-    assert (r.replace('\n', '') ==
-                 '<div class="markdown_content"><h6>Project Admins:</h6>'
-                 '<ul class="md-users-list">'
-                 '    <li><a href="/u/test-admin/">Test \xc5dmin &lt;script&gt;</a></li>'
-                 '</ul></div>')
-
-
-@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)
-    p_test.add_user(M.User.by_username('test-user'), ['Admin'])
-    ThreadLocalORMSession.flush_all()
-    with h.push_config(c, project=p_test):
-        r = g.markdown_wiki.convert('[[project_admins]]\n[[download_button]]')
-
-    assert '</a><br/><br/><a href=' not in r, r
-    assert '</a></li><li><a href=' in r, r
-
-
-@td.with_wiki
-def test_macro_include_no_extra_br():
-    p_nbhd = M.Neighborhood.query.get(name='Projects')
-    p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
-    wiki = p_test.app_instance('wiki')
-    with h.push_context(p_test._id, app_config_id=wiki.config._id):
-        p = WM.Page.upsert(title='Include_1')
-        p.text = 'included page 1'
-        p.commit()
-        p = WM.Page.upsert(title='Include_2')
-        p.text = 'included page 2'
-        p.commit()
-        p = WM.Page.upsert(title='Include_3')
-        p.text = 'included page 3'
-        p.commit()
-        ThreadLocalORMSession.flush_all()
-        md = '[[include ref=Include_1]]\n[[include ref=Include_2]]\n[[include ref=Include_3]]'
-        html = g.markdown_wiki.convert(md)
-
-    expected_html = '''<div class="markdown_content"><p></p><div>
-<div class="markdown_content"><p>included page 1</p></div>
-</div>
-<div>
-<div class="markdown_content"><p>included page 2</p></div>
-</div>
-<div>
-<div class="markdown_content"><p>included page 3</p></div>
-</div>
-<p></p></div>'''
-    assert squish_spaces(html) == squish_spaces(expected_html)
-
-
-@with_setup(setup_method, teadown_method)
-@td.with_wiki
-@td.with_tool('test', 'Wiki', 'wiki2')
-def test_macro_include_permissions():
-    p_nbhd = M.Neighborhood.query.get(name='Projects')
-    p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
-    wiki = p_test.app_instance('wiki')
-    wiki2 = p_test.app_instance('wiki2')
-    with h.push_context(p_test._id, app_config_id=wiki.config._id):
-        p = WM.Page.upsert(title='CanRead')
-        p.text = 'Can see this!'
-        p.commit()
-        ThreadLocalORMSession.flush_all()
-
-    with h.push_context(p_test._id, app_config_id=wiki2.config._id):
-        role = M.ProjectRole.by_name('*anonymous')._id
-        read_perm = M.ACE.allow(role, 'read')
-        acl = c.app.config.acl
-        if read_perm in acl:
-            acl.remove(read_perm)
-        p = WM.Page.upsert(title='CanNotRead')
-        p.text = 'Can not see this!'
-        p.commit()
-        ThreadLocalORMSession.flush_all()
-
-    with h.push_context(p_test._id, app_config_id=wiki.config._id):
-        c.user = M.User.anonymous()
-        md = '[[include ref=CanRead]]\n[[include ref=wiki2:CanNotRead]]'
-        html = g.markdown_wiki.convert(md)
-        assert 'Can see this!' in html
-        assert 'Can not see this!' not in html
-        assert "[[include: you don't have a read permission for wiki2:CanNotRead]]" in html
-
-
-@patch('oembed.OEmbedEndpoint.fetch')
-def test_macro_embed(oembed_fetch):
-    oembed_fetch.return_value = {
-        "html": '<iframe width="480" height="270" src="http://www.youtube.com/embed/kOLpSPEA72U?feature=oembed" '
-                'frameborder="0" allowfullscreen></iframe>)',
-        "title": "Nature's 3D Printer: MIND BLOWING Cocoon in Rainforest - Smarter Every Day 94",
-    }
-    r = g.markdown_wiki.convert('[[embed url=http://www.youtube.com/watch?v=kOLpSPEA72U]]')
-    assert ('<p><iframe height="270" '
-              'src="https://www.youtube-nocookie.com/embed/kOLpSPEA72U?feature=oembed" width="480"></iframe></p>' in
-              r.replace('\n', ''))
-
-
-def test_macro_embed_video_gone():
-    # this does a real fetch
-    r = g.markdown_wiki.convert('[[embed url=https://www.youtube.com/watch?v=OWsFqPZ3v-0]]')
-    r = str(r)  # convert away from Markup, to get better assertion diff output
-    # either of these could happen depending on the mood of youtube's oembed API:
-    assert r in [
-        '<div class="markdown_content"><p>Video not available</p></div>',
-        '<div class="markdown_content"><p>Could not embed: https://www.youtube.com/watch?v=OWsFqPZ3v-0</p></div>',
-    ]
-
-
-@patch('oembed.OEmbedEndpoint.fetch')
-def test_macro_embed_video_error(oembed_fetch):
-    oembed_fetch.side_effect = OEmbedError('Invalid mime-type in response...')
-    r = g.markdown_wiki.convert('[[embed url=http://www.youtube.com/watch?v=6YbBmqUnoQM]]')
-    assert (r == '<div class="markdown_content"><p>Could not embed: '
-                    'http://www.youtube.com/watch?v=6YbBmqUnoQM</p></div>')
-
-
-def test_macro_embed_notsupported():
-    r = g.markdown_wiki.convert('[[embed url=http://vimeo.com/46163090]]')
-    assert (
-        r == '<div class="markdown_content"><p>[[embed url=http://vimeo.com/46163090]]</p></div>')
-
-
-def test_markdown_toc():
-    with h.push_context('test', neighborhood='Projects'):
-        r = g.markdown_wiki.convert("""[TOC]
-
-# Header 1
-
-## Header 2""")
-    assert '''<ul>
-<li><a href="#header-1">Header 1</a><ul>
-<li><a href="#header-2">Header 2</a></li>
-</ul>
-</li>
-</ul>''' in r, r
-
-
-@td.with_wiki
-def test_wiki_artifact_links():
-    text = g.markdown.convert('See [18:13:49]')
-    assert 'See <span>[18:13:49]</span>' in text, text
-    with h.push_context('test', 'wiki', neighborhood='Projects'):
-        text = g.markdown.convert('Read [here](Home) about our project')
-        assert '<a class="" href="/p/test/wiki/Home/">here</a>' in text, text
-        text = g.markdown.convert('[Go home](test:wiki:Home)')
-        assert '<a class="" href="/p/test/wiki/Home/">Go home</a>' in text, text
-        text = g.markdown.convert('See [test:wiki:Home]')
-        assert '<a class="alink" href="/p/test/wiki/Home/">[test:wiki:Home]</a>' in text, text
-
-
-def test_markdown_links():
-    with patch.dict(tg.config, {'nofollow_exempt_domains': 'foobar.net'}):
-        text = g.markdown.convert('Read [here](http://foobar.net/) about our project')
-        assert 'class="" href="http://foobar.net/">here</a> about' in text
-
-    text = g.markdown.convert('Read [here](http://foobar.net/) about our project')
-    assert 'class="" href="http://foobar.net/" rel="nofollow">here</a> about' in text
-
-    text = g.markdown.convert('Read [here](/p/foobar/blah) about our project')
-    assert 'class="" href="/p/foobar/blah">here</a> about' in text
-
-    text = g.markdown.convert('Read [here](/p/foobar/blah/) about our project')
-    assert 'class="" href="/p/foobar/blah/">here</a> about' in text
-
-    text = g.markdown.convert('Read <http://foobar.net/> about our project')
-    assert 'href="http://foobar.net/" rel="nofollow">http://foobar.net/</a> about' in text
-
-
-def test_markdown_and_html():
-    with h.push_context('test', neighborhood='Projects'):
-        r = g.markdown_wiki.convert('<div style="float:left">blah</div>')
-    assert '<div style="float: left;">blah</div>' in r, r
-
-
-def test_markdown_within_html():
-    with h.push_context('test', neighborhood='Projects'):
-        r = g.markdown_wiki.convert('<div style="float:left" markdown>**blah**</div>')
-    assert ('<div style="float: left;"><p><strong>blah</strong></p></div>' in
-              r.replace('\n', ''))
-
-
-def test_markdown_with_html_comments():
-    text = g.markdown.convert('test <!-- comment -->')
-    assert '<div class="markdown_content"><p>test </p></div>' == text, text
-
-
-def test_markdown_big_text():
-    '''If text is too big g.markdown.convert should return plain text'''
-    text = 'a' * 40001
-    assert g.markdown.convert(text) == '<pre>%s</pre>' % text
-    assert g.markdown_wiki.convert(text) == '<pre>%s</pre>' % text
-
-
-@td.with_wiki
-def test_markdown_basics():
-    with h.push_context('test', 'wiki', neighborhood='Projects'):
-        text = g.markdown.convert('# Foo!\n[Home]')
-        assert (text ==
-                     '<div class="markdown_content"><h1 id="foo">Foo!</h1>\n'
-                     '<p><a class="alink" href="/p/test/wiki/Home/">[Home]</a></p></div>')
-        text = g.markdown.convert('# Foo!\n[Rooted]')
-        assert (text ==
-                     '<div class="markdown_content"><h1 id="foo">Foo!</h1>\n'
-                     '<p><span>[Rooted]</span></p></div>')
-
-    assert (
-        g.markdown.convert('Multi\nLine') ==
-        '<div class="markdown_content"><p>Multi<br/>\n'
-        'Line</p></div>')
-    assert (
-        g.markdown.convert('Multi\n\nLine') ==
-        '<div class="markdown_content"><p>Multi</p>\n'
-        '<p>Line</p></div>')
-
-    # should not raise an exception:
-    assert (
-        g.markdown.convert("<class 'foo'>") ==
-        '''<div class="markdown_content"><p>&lt;class 'foo'=""&gt;&lt;/class&gt;</p></div>''')
-
-    assert (
-        g.markdown.convert('''# Header
-
-Some text in a regular paragraph
-
-    :::python
-    for i in range(10):
-        print i
-''') ==
-        # no <br
-        '<div class="markdown_content"><h1 id="header">Header</h1>\n'
-        '<p>Some text in a regular paragraph</p>\n'
-        '<div class="codehilite"><pre><span></span><code><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>\n'
-        '    <span class="nb">print</span> <span class="n">i</span>\n'
-        '</code></pre></div>\n'
-        '</div>')
-    assert (
-        g.forge_markdown(email=True).convert('[Home]') ==
-        # uses localhost:
-        '<div class="markdown_content"><p><a class="alink" href="http://localhost/p/test/wiki/Home/">[Home]</a></p></div>')
-    assert (
-        g.markdown.convert('''
-~~~~
-def foo(): pass
-~~~~''') ==
-        '<div class="markdown_content"><div class="codehilite"><pre><span></span><code>def foo(): pass\n'
-        '</code></pre></div>\n'
-        '</div>')
-
-
-def test_markdown_list_without_break():
-    # this is not a valid way to make a list in original Markdown or python-markdown
-    #   https://github.com/Python-Markdown/markdown/issues/874
-    # it is valid in the CommonMark spec https://spec.commonmark.org/0.30/#lists
-    # TODO: try https://github.com/adamb70/mdx-breakless-lists
-    #       or https://gitlab.com/ayblaq/prependnewline
-    assert (
-        g.markdown.convert('''\
-Regular text
-* first item
-* second item''') ==
-        '<div class="markdown_content"><p>Regular text\n'  # no <br>
-        '* first item\n'  # no <br>
-        '* second item</p></div>')
-
-    assert (
-        g.markdown.convert('''\
-Regular text
-- first item
-- second item''') ==
-        '<div class="markdown_content"><p>Regular text<br/>\n'
-        '- first item<br/>\n'
-        '- second item</p></div>')
-
-    assert (
-        g.markdown.convert('''\
-Regular text
-+ first item
-+ second item''') ==
-        '<div class="markdown_content"><p>Regular text<br/>\n'
-        '+ first item<br/>\n'
-        '+ second item</p></div>')
-
-    assert (
-        g.markdown.convert('''\
-Regular text
-1. first item
-2. second item''') ==
-        '<div class="markdown_content"><p>Regular text<br/>\n'
-        '1. first item<br/>\n'
-        '2. second item</p></div>')
-
-
-def test_markdown_autolink():
-    tgt = 'http://everything2.com/?node=nate+oostendorp'
-    s = g.markdown.convert('This is %s' % tgt)
-    assert (
-        s == f'<div class="markdown_content"><p>This is <a href="{tgt}" rel="nofollow">{tgt}</a></p></div>')
-    assert '<a href=' in g.markdown.convert('This is http://domain.net')
-    # beginning of doc
-    assert '<a href=' in g.markdown.convert('http://domain.net abc')
-    # beginning of a line
-    assert ('<br/>\n<a href="http://' in
-              g.markdown.convert('foobar\nhttp://domain.net abc'))
-    # no conversion of these urls:
-    assert ('a blahttp://sdf.com z' in
-              g.markdown.convert('a blahttp://sdf.com z'))
-    assert ('literal <code>http://domain.net</code> literal' in
-              g.markdown.convert('literal `http://domain.net` literal'))
-    assert ('<pre><span></span><code>preformatted http://domain.net\n</code></pre>' in
-              g.markdown.convert('    :::text\n'
-                                 '    preformatted http://domain.net'))
-
-
-def test_markdown_autolink_with_escape():
-    # \_ is unnecessary but valid markdown escaping and should be considered as a regular underscore
-    # (it occurs during html2text conversion during project migrations)
-    r = g.markdown.convert(r'a http://www.phpmyadmin.net/home\_page/security/\#target b')
-    assert 'href="http://www.phpmyadmin.net/home_page/security/#target"' in r, r
-
-
-def test_markdown_invalid_script():
-    r = g.markdown.convert('<script>alert(document.cookies)</script>')
-    assert '<div class="markdown_content">&lt;script&gt;alert(document.cookies)&lt;/script&gt;\n</div>' == r
-
-
-def test_markdown_invalid_onerror():
-    r = g.markdown.convert('<img src=x onerror=alert(document.cookie)>')
-    assert 'onerror' not in r
-
-
-def test_markdown_invalid_tagslash():
-    r = g.markdown.convert('<div/onload><img src=x onerror=alert(document.cookie)>')
-    assert 'onerror' not in r
-
-
-def test_markdown_invalid_script_in_link():
-    r = g.markdown.convert('[xss](http://"><a onmouseover=prompt(document.domain)>xss</a>)')
-    assert ('<div class="markdown_content"><p><a class="" '
-                 '''href='http://"&gt;&lt;a%20onmouseover=prompt(document.domain)&gt;xss&lt;/a&gt;' '''
-                 'rel="nofollow">xss</a></p></div>' == r)
-
-
-def test_markdown_invalid_script_in_link2():
-    r = g.markdown.convert('[xss](http://"><img src=x onerror=alert(document.cookie)>)')
-    assert ('<div class="markdown_content"><p><a class="" '
-                 '''href='http://"&gt;&lt;img%20src=x%20onerror=alert(document.cookie)&gt;' '''
-                 'rel="nofollow">xss</a></p></div>' == r)
-
-
-def test_markdown_extremely_slow():
-    r = g.markdown.convert('''bonjour, voila ce que j'obtient en voulant ajouter un utilisateur a un groupe de sécurite, que ce soit sur un groupe pre-existant, ou sur un groupe crée.
-message d'erreur:
-
-ERROR: Could not complete the Add UserLogin To SecurityGroup [file:/C:/neogia/ofbizNeogia/applications/securityext/script/org/ofbiz/securityext/securitygroup/SecurityGroupServices.xml#addUserLoginToSecurityGroup] process [problem creating the newEntity value: Exception while inserting the following entity: [GenericEntity:UserLoginSecurityGroup][createdStamp,2006-01-23 17:42:39.312(java.sql.Timestamp)][createdTxStamp,2006-01-23 17:42:38.875(java.sql.Timestamp)][fromDate,2006-01-23 17:42:3 [...]
-
-à priori les données du formulaire ne sont pas traitées : VALUES (?, ?, ?, ?, ?, ?, ?, ?) ce qui entraine l'echec du traitement SQL.
-
-
-Si une idée vous vient à l'esprit, merci de me tenir au courant.
-
-cordialement, julien.''')
-    assert True   # finished!
-
-
-@td.with_tool('test', 'Wiki', 'wiki-len')
-def test_markdown_link_length_limits():
-    with h.push_context('test', 'wiki-len', neighborhood='Projects'):
-        # these are always ok, no matter the NOBRACKET length
-        WM.Page.upsert(title='12345678901').commit()
-        text = g.markdown.convert('See [12345678901]')
-        assert 'href="/p/test/wiki-len/12345678901/">[12345678901]</a>' in text, text
-        WM.Page.upsert(title='this is 26 characters long').commit()
-        text = g.markdown.convert('See [this is 26 characters long]')
-        assert 'href="/p/test/wiki-len/this%20is%2026%20characters%20long/">[this is 26 characters long]</a>' in text, text
-
-        # NOBRACKET regex length impacts standard markdown links
-        text = g.markdown.convert('See [short](http://a.de)')
-        assert 'href="http://a.de" rel="nofollow">short</a>' in text, text
-        text = g.markdown.convert('See [this is 26 characters long](http://a.de)')
-        assert 'href="http://a.de" rel="nofollow">this is 26 characters long</a>' in text, text  # {0,12} fails {0,13} ok
-
-        # NOBRACKET regex length impacts our custom artifact links
-        text = g.markdown.convert('See [short](Home)')
-        assert 'href="/p/test/wiki-len/Home/">short</a>' in text, text
-        text = g.markdown.convert('See [123456789](Home)')
-        assert 'href="/p/test/wiki-len/Home/">123456789</a>' in text, text
-        text = g.markdown.convert('See [12345678901](Home)')
-        assert 'href="/p/test/wiki-len/Home/">12345678901</a>' in text, text  # {0,5} fails, {0,6} ok
-        text = g.markdown.convert('See [this is 16 chars](Home)')
-        assert 'href="/p/test/wiki-len/Home/">this is 16 chars</a>' in text, text  # {0,7} fails {0,8} ok
-        text = g.markdown.convert('See [this is 26 characters long](Home)')
-        assert 'href="/p/test/wiki-len/Home/">this is 26 characters long</a>' in text, text  # {0,12} fails {0,13} ok
-
-        # limit, currently
-        charSuperLong = '1234567890'*21
-        text = g.markdown.convert(f'See [{charSuperLong}](Home)')
-        assert f'<span>[{charSuperLong}]</span>(Home)' in text, text  # current limitation, not a link
-        # assert f'href="/p/test/wiki-len/Home/">{charSuperLong}</a>' in text, text  # ideal output
-
-
-@td.with_wiki
-def test_macro_include():
-    r = g.markdown.convert('[[include ref=Home id=foo]]')
-    assert '<div id="foo">' in r, r
-    assert 'href="../foo"' in g.markdown.convert('[My foo](foo)')
-    assert 'href="..' not in g.markdown.convert('[My foo](./foo)')
-
-
-def test_macro_nbhd_feeds():
-    with h.push_context('--init--', 'wiki', neighborhood='Projects'):
-        r = g.markdown_wiki.convert('[[neighborhood_feeds tool_name=wiki]]')
-        assert 'Home modified by ' in r, r
-        assert '&lt;div class="markdown_content"&gt;' not in r
-
-
-def test_sort_alpha():
-    p_nbhd = M.Neighborhood.query.get(name='Projects')
-
-    with h.push_context(p_nbhd.neighborhood_project._id):
-        r = g.markdown_wiki.convert('[[projects sort=alpha]]')
-        project_list = get_project_names(r)
-        assert project_list == sorted(project_list)
-
-
-def test_sort_registered():
-    p_nbhd = M.Neighborhood.query.get(name='Projects')
-
-    with h.push_context(p_nbhd.neighborhood_project._id):
-        r = g.markdown_wiki.convert('[[projects sort=last_registered]]')
-        project_names = get_project_names(r)
-        ids = get_projects_property_in_the_same_order(project_names, '_id')
-        assert ids == sorted(ids, reverse=True)
-
-
-def test_sort_updated():
-    p_nbhd = M.Neighborhood.query.get(name='Projects')
-
-    with h.push_context(p_nbhd.neighborhood_project._id):
-        r = g.markdown_wiki.convert('[[projects sort=last_updated]]')
-        project_names = get_project_names(r)
-        updated_at = get_projects_property_in_the_same_order(
-            project_names, 'last_updated')
-        assert updated_at == sorted(updated_at, reverse=True)
-
-
-@with_setup(setup_functional_test)
-def test_filtering():
-    # set up for test
-    from random import choice
-    setup_trove_categories()
-    random_trove = choice(M.TroveCategory.query.find().all())
-    test_project = M.Project.query.get(shortname='test')
-    test_project_troves = getattr(test_project, 'trove_' + random_trove.type)
-    test_project_troves.append(random_trove._id)
-    ThreadLocalORMSession.flush_all()
-
-    p_nbhd = M.Neighborhood.query.get(name='Projects')
-    with h.push_config(c,
-                       project=p_nbhd.neighborhood_project,
-                       user=M.User.by_username('test-admin')):
-        r = g.markdown_wiki.convert(
-            '[[projects category="%s"]]' % random_trove.fullpath)
-        project_names = get_project_names(r)
-        assert [test_project.name] == project_names
-
-
-def test_projects_macro():
-    two_column_style = 'width: 330px;'
-
-    p_nbhd = M.Neighborhood.query.get(name='Projects')
-    with h.push_config(c,
-                       project=p_nbhd.neighborhood_project,
-                       user=M.User.anonymous()):
-        # test columns
-        r = g.markdown_wiki.convert('[[projects display_mode=list columns=2]]')
-        assert two_column_style in r
-        r = g.markdown_wiki.convert('[[projects display_mode=list columns=3]]')
-        assert two_column_style not in r
-
-
-@td.with_user_project('test-admin')
-@td.with_user_project('test-user-1')
-def test_myprojects_macro():
-    h.set_context('u/%s' % (c.user.username), 'wiki', neighborhood='Users')
-    r = g.markdown_wiki.convert('[[my_projects]]')
-    for p in c.user.my_projects():
-        if p.deleted or p.is_nbhd_project:
-            continue
-        proj_title = f'<h2><a href="{p.url()}">{p.name}</a></h2>'
-        assert proj_title in r
-
-    h.set_context('u/test-user-1', 'wiki', neighborhood='Users')
-    user = M.User.query.get(username='test-user-1')
-    r = g.markdown_wiki.convert('[[my_projects]]')
-    for p in user.my_projects():
-        if p.deleted or p.is_nbhd_project:
-            continue
-        proj_title = f'<h2><a href="{p.url()}">{p.name}</a></h2>'
-        assert proj_title in r
-
-
-@td.with_wiki
-def test_hideawards_macro():
-    p_nbhd = M.Neighborhood.query.get(name='Projects')
-
-    app_config_id = ObjectId()
-    award = M.Award(app_config_id=app_config_id)
-    award.short = 'Award short'
-    award.full = 'Award full'
-    award.created_by_neighborhood_id = p_nbhd._id
-
-    project = M.Project.query.get(
-        neighborhood_id=p_nbhd._id, shortname='test')
-
-    M.AwardGrant(
-        award=award,
-        award_url='http://award.org',
-        comment='Winner!',
-        granted_by_neighborhood=p_nbhd,
-        granted_to_project=project)
-
-    ThreadLocalORMSession.flush_all()
-
-    with h.push_context(p_nbhd.neighborhood_project._id):
-        r = g.markdown_wiki.convert('[[projects]]')
-        assert ('<div class="feature"> <a href="http://award.org" rel="nofollow" title="Winner!">'
-                  'Award short</a> </div>' in
-                  squish_spaces(r))
-
-        r = g.markdown_wiki.convert('[[projects show_awards_banner=False]]')
-        assert 'Award short' not in r
-
-
-@td.with_tool('test', 'Blog', 'blog')
-def test_project_blog_posts_macro():
-    from forgeblog import model as BM
-    with h.push_context('test', 'blog', neighborhood='Projects'):
-        BM.BlogPost.new(
-            title='Test title',
-            text='test post',
-            state='published',
-        )
-        BM.BlogPost.new(
-            title='Test title2',
-            text='test post2',
-            state='published',
-        )
-
-        r = g.markdown_wiki.convert('[[project_blog_posts]]')
-        assert 'Test title</a></h3>' in r
-        assert 'Test title2</a></h3>' in r
-        assert '<div class="markdown_content"><p>test post</p></div>' in r
-        assert '<div class="markdown_content"><p>test post2</p></div>' in r
-        assert 'by <em>Test Admin</em>' in r
-
-
-def test_project_screenshots_macro():
-    with h.push_context('test', neighborhood='Projects'):
-        M.ProjectFile(project_id=c.project._id, category='screenshot', caption='caption', filename='test_file.jpg')
-        ThreadLocalORMSession.flush_all()
-
-        r = g.markdown_wiki.convert('[[project_screenshots]]')
-
-        assert 'href="/p/test/screenshot/test_file.jpg"' in r
-        assert 'src="/p/test/screenshot/test_file.jpg/thumb"' in r
-
-
 def get_project_names(r):
     """
     Extracts a list of project names from a wiki page HTML.
@@ -826,10 +75,705 @@ def get_projects_property_in_the_same_order(names, prop):
     return [projects_dict[name] for name in names]
 
 
+
+@with_nose_compatibility
+class Test():
+
+    def setup_method(self, method):
+        setup_basic_test()
+        setup_unit_test()
+        setup_global_objects()
+
+    @td.with_wiki
+    def test_app_globals(self):
+        with h.push_context('test', 'wiki', neighborhood='Projects'):
+            assert g.app_static(
+                'css/wiki.css') == '/nf/_static_/wiki/css/wiki.css', g.app_static('css/wiki.css')
+
+    def test_macro_projects(self):
+        file_name = 'neo-icon-set-454545-256x350.png'
+        file_path = os.path.join(
+            allura.__path__[0], 'nf', 'allura', 'images', file_name)
+
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+        p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
+        c.project = p_test
+        icon_file = open(file_path, 'rb')
+        M.ProjectFile.save_image(
+            file_name, icon_file, content_type='image/png',
+            square=True, thumbnail_size=(48, 48),
+            thumbnail_meta=dict(project_id=c.project._id, category='icon'))
+        icon_file.close()
+        p_test2 = M.Project.query.get(
+            shortname='test2', neighborhood_id=p_nbhd._id)
+        c.project = p_test2
+        icon_file = open(file_path, 'rb')
+        M.ProjectFile.save_image(
+            file_name, icon_file, content_type='image/png',
+            square=True, thumbnail_size=(48, 48),
+            thumbnail_meta=dict(project_id=c.project._id, category='icon'))
+        icon_file.close()
+        p_sub1 = M.Project.query.get(
+            shortname='test/sub1', neighborhood_id=p_nbhd._id)
+        c.project = p_sub1
+        icon_file = open(file_path, 'rb')
+        M.ProjectFile.save_image(
+            file_name, icon_file, content_type='image/png',
+            square=True, thumbnail_size=(48, 48),
+            thumbnail_meta=dict(project_id=c.project._id, category='icon'))
+        icon_file.close()
+        p_test.labels = ['test', 'root']
+        p_sub1.labels = ['test', 'sub1']
+        # Make one project private
+        p_test.private = False
+        p_sub1.private = False
+        p_test2.private = True
+
+        ThreadLocalORMSession.flush_all()
+
+        with h.push_config(c,
+                        project=p_nbhd.neighborhood_project,
+                        user=M.User.by_username('test-admin')):
+            r = g.markdown_wiki.convert('[[projects]]')
+            assert 'alt="Test Project Logo"' in r, r
+            assert 'alt="A Subproject Logo"' in r, r
+            r = g.markdown_wiki.convert('[[projects labels=root]]')
+            assert 'alt="Test Project Logo"' in r, r
+            assert 'alt="A Subproject Logo"' not in r, r
+            r = g.markdown_wiki.convert('[[projects labels=sub1]]')
+            assert 'alt="Test Project Logo"' not in r, r
+            assert 'alt="A Subproject Logo"' in r, r
+            r = g.markdown_wiki.convert('[[projects labels=test]]')
+            assert 'alt="Test Project Logo"' in r, r
+            assert 'alt="A Subproject Logo"' in r, r
+            r = g.markdown_wiki.convert('[[projects labels=test,root]]')
+            assert 'alt="Test Project Logo"' in r, r
+            assert 'alt="A Subproject Logo"' not in r, r
+            r = g.markdown_wiki.convert('[[projects labels=test,sub1]]')
+            assert 'alt="Test Project Logo"' not in r, r
+            assert 'alt="A Subproject Logo"' in r, r
+            r = g.markdown_wiki.convert('[[projects labels=root|sub1]]')
+            assert 'alt="Test Project Logo"' in r, r
+            assert 'alt="A Subproject Logo"' in r, r
+            r = g.markdown_wiki.convert('[[projects labels=test,root|root,sub1]]')
+            assert 'alt="Test Project Logo"' in r, r
+            assert 'alt="A Subproject Logo"' not in r, r
+            r = g.markdown_wiki.convert('[[projects labels=test,root|test,sub1]]')
+            assert 'alt="Test Project Logo"' in r, r
+            assert 'alt="A Subproject Logo"' in r, r
+            r = g.markdown_wiki.convert('[[projects show_total=True sort=random]]')
+            assert '<p class="macro_projects_total">3 Projects' in r, r
+            r = g.markdown_wiki.convert(
+                '[[projects show_total=True private=True sort=random]]')
+            assert '<p class="macro_projects_total">1 Projects' in r, r
+            assert 'alt="Test 2 Logo"' in r, r
+            assert 'alt="Test Project Logo"' not in r, r
+            assert 'alt="A Subproject Logo"' not in r, r
+
+            r = g.markdown_wiki.convert('[[projects show_proj_icon=True]]')
+            assert 'alt="Test Project Logo"' in r
+            r = g.markdown_wiki.convert('[[projects show_proj_icon=False]]')
+            assert 'alt="Test Project Logo"' not in r
+
+    def test_macro_neighborhood_feeds(self):
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+        p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
+        with h.push_context('--init--', 'wiki', neighborhood='Projects'):
+            r = g.markdown_wiki.convert('[[neighborhood_feeds tool_name=wiki]]')
+            assert 'Home modified by' in r, r
+            r = re.sub(r'<small>.*? ago</small>', '', r)  # remove "less than 1 second ago" etc
+            orig_len = len(r)
+            # Make project private & verify we don't see its new feed items
+            anon = M.User.anonymous()
+            p_test.acl.insert(0, M.ACE.deny(
+                M.ProjectRole.anonymous(p_test)._id, 'read'))
+            ThreadLocalORMSession.flush_all()
+            pg = WM.Page.query.get(title='Home', app_config_id=c.app.config._id)
+            pg.text = 'Change'
+            with h.push_config(c, user=M.User.by_username('test-admin')):
+                pg.commit()
+            r = g.markdown_wiki.convert('[[neighborhood_feeds tool_name=wiki]]')
+            r = re.sub(r'<small>.*? ago</small>', '', r)  # remove "less than 1 second ago" etc
+            new_len = len(r)
+            assert new_len == orig_len
+            p = BM.BlogPost(title='test me',
+                            neighborhood_id=p_test.neighborhood_id)
+            p.text = 'test content'
+            p.state = 'published'
+            p.make_slug()
+            with h.push_config(c, user=M.User.by_username('test-admin')):
+                p.commit()
+            ThreadLocalORMSession.flush_all()
+            with h.push_config(c, user=anon):
+                r = g.markdown_wiki.convert('[[neighborhood_blog_posts]]')
+            assert 'test content' in r
+
+    def test_macro_members(self):
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+        p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
+        p_test.add_user(M.User.by_username('test-user'), ['Developer'])
+        p_test.add_user(M.User.by_username('test-user-0'), ['Member'])
+        ThreadLocalORMSession.flush_all()
+        r = g.markdown_wiki.convert('[[members limit=2]]').replace('\t', '').replace('\n', '')
+        assert (r ==
+                    '<div class="markdown_content"><h6>Project Members:</h6>'
+                    '<ul class="md-users-list">'
+                    '<li><a href="/u/test-admin/">Test Admin</a> (admin)</li>'
+                    '<li><a href="/u/test-user/">Test User</a></li>'
+                    '<li class="md-users-list-more"><a href="/p/test/_members">All Members</a></li>'
+                    '</ul>'
+                    '</div>')
+
+    def test_macro_members_escaping(self):
+        user = M.User.by_username('test-admin')
+        user.display_name = 'Test Admin <script>'
+        r = g.markdown_wiki.convert('[[members]]')
+        assert (r.replace('\n', '').replace('\t', '') ==
+                    '<div class="markdown_content"><h6>Project Members:</h6>'
+                    '<ul class="md-users-list">'
+                    '<li><a href="/u/test-admin/">Test Admin &lt;script&gt;</a> (admin)</li>'
+                    '</ul></div>')
+
+    def test_macro_project_admins(self):
+        user = M.User.by_username('test-admin')
+        user.display_name = 'Test Ådmin <script>'
+        with h.push_context('test', neighborhood='Projects'):
+            r = g.markdown_wiki.convert('[[project_admins]]')
+        assert (r.replace('\n', '') ==
+                    '<div class="markdown_content"><h6>Project Admins:</h6>'
+                    '<ul class="md-users-list">'
+                    '    <li><a href="/u/test-admin/">Test \xc5dmin &lt;script&gt;</a></li>'
+                    '</ul></div>')
+
+    def test_macro_project_admins_one_br(self):
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+        p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
+        p_test.add_user(M.User.by_username('test-user'), ['Admin'])
+        ThreadLocalORMSession.flush_all()
+        with h.push_config(c, project=p_test):
+            r = g.markdown_wiki.convert('[[project_admins]]\n[[download_button]]')
+
+        assert '</a><br/><br/><a href=' not in r, r
+        assert '</a></li><li><a href=' in r, r
+
+    @td.with_wiki
+    def test_macro_include_no_extra_br(self):
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+        p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
+        wiki = p_test.app_instance('wiki')
+        with h.push_context(p_test._id, app_config_id=wiki.config._id):
+            p = WM.Page.upsert(title='Include_1')
+            p.text = 'included page 1'
+            p.commit()
+            p = WM.Page.upsert(title='Include_2')
+            p.text = 'included page 2'
+            p.commit()
+            p = WM.Page.upsert(title='Include_3')
+            p.text = 'included page 3'
+            p.commit()
+            ThreadLocalORMSession.flush_all()
+            md = '[[include ref=Include_1]]\n[[include ref=Include_2]]\n[[include ref=Include_3]]'
+            html = g.markdown_wiki.convert(md)
+
+        expected_html = '''<div class="markdown_content"><p></p><div>
+    <div class="markdown_content"><p>included page 1</p></div>
+    </div>
+    <div>
+    <div class="markdown_content"><p>included page 2</p></div>
+    </div>
+    <div>
+    <div class="markdown_content"><p>included page 3</p></div>
+    </div>
+    <p></p></div>'''
+        assert squish_spaces(html) == squish_spaces(expected_html)
+
+    @td.with_wiki
+    @td.with_tool('test', 'Wiki', 'wiki2')
+    def test_macro_include_permissions(self):
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+        p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
+        wiki = p_test.app_instance('wiki')
+        wiki2 = p_test.app_instance('wiki2')
+        with h.push_context(p_test._id, app_config_id=wiki.config._id):
+            p = WM.Page.upsert(title='CanRead')
+            p.text = 'Can see this!'
+            p.commit()
+            ThreadLocalORMSession.flush_all()
+
+        with h.push_context(p_test._id, app_config_id=wiki2.config._id):
+            role = M.ProjectRole.by_name('*anonymous')._id
+            read_perm = M.ACE.allow(role, 'read')
+            acl = c.app.config.acl
+            if read_perm in acl:
+                acl.remove(read_perm)
+            p = WM.Page.upsert(title='CanNotRead')
+            p.text = 'Can not see this!'
+            p.commit()
+            ThreadLocalORMSession.flush_all()
+
+        with h.push_context(p_test._id, app_config_id=wiki.config._id):
+            c.user = M.User.anonymous()
+            md = '[[include ref=CanRead]]\n[[include ref=wiki2:CanNotRead]]'
+            html = g.markdown_wiki.convert(md)
+            assert 'Can see this!' in html
+            assert 'Can not see this!' not in html
+            assert "[[include: you don't have a read permission for wiki2:CanNotRead]]" in html
+
+    @patch('oembed.OEmbedEndpoint.fetch')
+    def test_macro_embed(self, oembed_fetch):
+        oembed_fetch.return_value = {
+            "html": '<iframe width="480" height="270" src="http://www.youtube.com/embed/kOLpSPEA72U?feature=oembed" '
+                    'frameborder="0" allowfullscreen></iframe>)',
+            "title": "Nature's 3D Printer: MIND BLOWING Cocoon in Rainforest - Smarter Every Day 94",
+        }
+        r = g.markdown_wiki.convert('[[embed url=http://www.youtube.com/watch?v=kOLpSPEA72U]]')
+        assert ('<p><iframe height="270" '
+                'src="https://www.youtube-nocookie.com/embed/kOLpSPEA72U?feature=oembed" width="480"></iframe></p>' in
+                r.replace('\n', ''))
+
+    def test_macro_embed_video_gone(self):
+        # this does a real fetch
+        r = g.markdown_wiki.convert('[[embed url=https://www.youtube.com/watch?v=OWsFqPZ3v-0]]')
+        r = str(r)  # convert away from Markup, to get better assertion diff output
+        # either of these could happen depending on the mood of youtube's oembed API:
+        assert r in [
+            '<div class="markdown_content"><p>Video not available</p></div>',
+            '<div class="markdown_content"><p>Could not embed: https://www.youtube.com/watch?v=OWsFqPZ3v-0</p></div>',
+        ]
+
+    @patch('oembed.OEmbedEndpoint.fetch')
+    def test_macro_embed_video_error(self, oembed_fetch):
+        oembed_fetch.side_effect = OEmbedError('Invalid mime-type in response...')
+        r = g.markdown_wiki.convert('[[embed url=http://www.youtube.com/watch?v=6YbBmqUnoQM]]')
+        assert (r == '<div class="markdown_content"><p>Could not embed: '
+                        'http://www.youtube.com/watch?v=6YbBmqUnoQM</p></div>')
+
+    def test_macro_embed_notsupported(self):
+        r = g.markdown_wiki.convert('[[embed url=http://vimeo.com/46163090]]')
+        assert (
+            r == '<div class="markdown_content"><p>[[embed url=http://vimeo.com/46163090]]</p></div>')
+
+    def test_markdown_toc(self):
+        with h.push_context('test', neighborhood='Projects'):
+            r = g.markdown_wiki.convert("""[TOC]
+
+    # Header 1
+
+    ## Header 2""")
+        assert '''<ul>
+    <li><a href="#header-1">Header 1</a><ul>
+    <li><a href="#header-2">Header 2</a></li>
+    </ul>
+    </li>
+    </ul>''' in r, r
+
+    @td.with_wiki
+    def test_wiki_artifact_links(self):
+        text = g.markdown.convert('See [18:13:49]')
+        assert 'See <span>[18:13:49]</span>' in text, text
+        with h.push_context('test', 'wiki', neighborhood='Projects'):
+            text = g.markdown.convert('Read [here](Home) about our project')
+            assert '<a class="" href="/p/test/wiki/Home/">here</a>' in text, text
+            text = g.markdown.convert('[Go home](test:wiki:Home)')
+            assert '<a class="" href="/p/test/wiki/Home/">Go home</a>' in text, text
+            text = g.markdown.convert('See [test:wiki:Home]')
+            assert '<a class="alink" href="/p/test/wiki/Home/">[test:wiki:Home]</a>' in text, text
+
+    def test_markdown_links(self):
+        with patch.dict(tg.config, {'nofollow_exempt_domains': 'foobar.net'}):
+            text = g.markdown.convert('Read [here](http://foobar.net/) about our project')
+            assert 'class="" href="http://foobar.net/">here</a> about' in text
+
+        text = g.markdown.convert('Read [here](http://foobar.net/) about our project')
+        assert 'class="" href="http://foobar.net/" rel="nofollow">here</a> about' in text
+
+        text = g.markdown.convert('Read [here](/p/foobar/blah) about our project')
+        assert 'class="" href="/p/foobar/blah">here</a> about' in text
+
+        text = g.markdown.convert('Read [here](/p/foobar/blah/) about our project')
+        assert 'class="" href="/p/foobar/blah/">here</a> about' in text
+
+        text = g.markdown.convert('Read <http://foobar.net/> about our project')
+        assert 'href="http://foobar.net/" rel="nofollow">http://foobar.net/</a> about' in text
+
+    def test_markdown_and_html(self):
+        with h.push_context('test', neighborhood='Projects'):
+            r = g.markdown_wiki.convert('<div style="float:left">blah</div>')
+        assert '<div style="float: left;">blah</div>' in r, r
+
+    def test_markdown_within_html(self):
+        with h.push_context('test', neighborhood='Projects'):
+            r = g.markdown_wiki.convert('<div style="float:left" markdown>**blah**</div>')
+        assert ('<div style="float: left;"><p><strong>blah</strong></p></div>' in
+                r.replace('\n', ''))
+
+    def test_markdown_with_html_comments(self):
+        text = g.markdown.convert('test <!-- comment -->')
+        assert '<div class="markdown_content"><p>test </p></div>' == text, text
+
+    def test_markdown_big_text(self):
+        '''If text is too big g.markdown.convert should return plain text'''
+        text = 'a' * 40001
+        assert g.markdown.convert(text) == '<pre>%s</pre>' % text
+        assert g.markdown_wiki.convert(text) == '<pre>%s</pre>' % text
+
+    @td.with_wiki
+    def test_markdown_basics(self):
+        with h.push_context('test', 'wiki', neighborhood='Projects'):
+            text = g.markdown.convert('# Foo!\n[Home]')
+            assert (text ==
+                        '<div class="markdown_content"><h1 id="foo">Foo!</h1>\n'
+                        '<p><a class="alink" href="/p/test/wiki/Home/">[Home]</a></p></div>')
+            text = g.markdown.convert('# Foo!\n[Rooted]')
+            assert (text ==
+                        '<div class="markdown_content"><h1 id="foo">Foo!</h1>\n'
+                        '<p><span>[Rooted]</span></p></div>')
+
+        assert (
+            g.markdown.convert('Multi\nLine') ==
+            '<div class="markdown_content"><p>Multi<br/>\n'
+            'Line</p></div>')
+        assert (
+            g.markdown.convert('Multi\n\nLine') ==
+            '<div class="markdown_content"><p>Multi</p>\n'
+            '<p>Line</p></div>')
+
+        # should not raise an exception:
+        assert (
+            g.markdown.convert("<class 'foo'>") ==
+            '''<div class="markdown_content"><p>&lt;class 'foo'=""&gt;&lt;/class&gt;</p></div>''')
+
+        assert (
+            g.markdown.convert('''# Header
+
+    Some text in a regular paragraph
+
+        :::python
+        for i in range(10):
+            print i
+    ''') ==
+            # no <br
+            '<div class="markdown_content"><h1 id="header">Header</h1>\n'
+            '<p>Some text in a regular paragraph</p>\n'
+            '<div class="codehilite"><pre><span></span><code><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>\n'
+            '    <span class="nb">print</span> <span class="n">i</span>\n'
+            '</code></pre></div>\n'
+            '</div>')
+        assert (
+            g.forge_markdown(email=True).convert('[Home]') ==
+            # uses localhost:
+            '<div class="markdown_content"><p><a class="alink" href="http://localhost/p/test/wiki/Home/">[Home]</a></p></div>')
+        assert (
+            g.markdown.convert('''
+    ~~~~
+    def foo(): pass
+    ~~~~''') ==
+            '<div class="markdown_content"><div class="codehilite"><pre><span></span><code>def foo(): pass\n'
+            '</code></pre></div>\n'
+            '</div>')
+
+    def test_markdown_list_without_break(self):
+        # this is not a valid way to make a list in original Markdown or python-markdown
+        #   https://github.com/Python-Markdown/markdown/issues/874
+        # it is valid in the CommonMark spec https://spec.commonmark.org/0.30/#lists
+        # TODO: try https://github.com/adamb70/mdx-breakless-lists
+        #       or https://gitlab.com/ayblaq/prependnewline
+        assert (
+            g.markdown.convert('''\
+    Regular text
+    * first item
+    * second item''') ==
+            '<div class="markdown_content"><p>Regular text\n'  # no <br>
+            '* first item\n'  # no <br>
+            '* second item</p></div>')
+
+        assert (
+            g.markdown.convert('''\
+    Regular text
+    - first item
+    - second item''') ==
+            '<div class="markdown_content"><p>Regular text<br/>\n'
+            '- first item<br/>\n'
+            '- second item</p></div>')
+
+        assert (
+            g.markdown.convert('''\
+    Regular text
+    + first item
+    + second item''') ==
+            '<div class="markdown_content"><p>Regular text<br/>\n'
+            '+ first item<br/>\n'
+            '+ second item</p></div>')
+
+        assert (
+            g.markdown.convert('''\
+    Regular text
+    1. first item
+    2. second item''') ==
+            '<div class="markdown_content"><p>Regular text<br/>\n'
+            '1. first item<br/>\n'
+            '2. second item</p></div>')
+
+
+    def test_markdown_autolink(self):
+        tgt = 'http://everything2.com/?node=nate+oostendorp'
+        s = g.markdown.convert('This is %s' % tgt)
+        assert (
+            s == f'<div class="markdown_content"><p>This is <a href="{tgt}" rel="nofollow">{tgt}</a></p></div>')
+        assert '<a href=' in g.markdown.convert('This is http://domain.net')
+        # beginning of doc
+        assert '<a href=' in g.markdown.convert('http://domain.net abc')
+        # beginning of a line
+        assert ('<br/>\n<a href="http://' in
+                g.markdown.convert('foobar\nhttp://domain.net abc'))
+        # no conversion of these urls:
+        assert ('a blahttp://sdf.com z' in
+                g.markdown.convert('a blahttp://sdf.com z'))
+        assert ('literal <code>http://domain.net</code> literal' in
+                g.markdown.convert('literal `http://domain.net` literal'))
+        assert ('<pre><span></span><code>preformatted http://domain.net\n</code></pre>' in
+                g.markdown.convert('    :::text\n'
+                                    '    preformatted http://domain.net'))
+
+    def test_markdown_autolink_with_escape(self):
+        # \_ is unnecessary but valid markdown escaping and should be considered as a regular underscore
+        # (it occurs during html2text conversion during project migrations)
+        r = g.markdown.convert(r'a http://www.phpmyadmin.net/home\_page/security/\#target b')
+        assert 'href="http://www.phpmyadmin.net/home_page/security/#target"' in r, r
+
+    def test_markdown_invalid_script(self):
+        r = g.markdown.convert('<script>alert(document.cookies)</script>')
+        assert '<div class="markdown_content">&lt;script&gt;alert(document.cookies)&lt;/script&gt;\n</div>' == r
+
+    def test_markdown_invalid_onerror(self):
+        r = g.markdown.convert('<img src=x onerror=alert(document.cookie)>')
+        assert 'onerror' not in r
+
+    def test_markdown_invalid_tagslash(self):
+        r = g.markdown.convert('<div/onload><img src=x onerror=alert(document.cookie)>')
+        assert 'onerror' not in r
+
+    def test_markdown_invalid_script_in_link(self):
+        r = g.markdown.convert('[xss](http://"><a onmouseover=prompt(document.domain)>xss</a>)')
+        assert ('<div class="markdown_content"><p><a class="" '
+                    '''href='http://"&gt;&lt;a%20onmouseover=prompt(document.domain)&gt;xss&lt;/a&gt;' '''
+                    'rel="nofollow">xss</a></p></div>' == r)
+
+    def test_markdown_invalid_script_in_link2(self):
+        r = g.markdown.convert('[xss](http://"><img src=x onerror=alert(document.cookie)>)')
+        assert ('<div class="markdown_content"><p><a class="" '
+                    '''href='http://"&gt;&lt;img%20src=x%20onerror=alert(document.cookie)&gt;' '''
+                    'rel="nofollow">xss</a></p></div>' == r)
+
+    def test_markdown_extremely_slow(self):
+        r = g.markdown.convert('''bonjour, voila ce que j'obtient en voulant ajouter un utilisateur a un groupe de sécurite, que ce soit sur un groupe pre-existant, ou sur un groupe crée.
+    message d'erreur:
+
+    ERROR: Could not complete the Add UserLogin To SecurityGroup [file:/C:/neogia/ofbizNeogia/applications/securityext/script/org/ofbiz/securityext/securitygroup/SecurityGroupServices.xml#addUserLoginToSecurityGroup] process [problem creating the newEntity value: Exception while inserting the following entity: [GenericEntity:UserLoginSecurityGroup][createdStamp,2006-01-23 17:42:39.312(java.sql.Timestamp)][createdTxStamp,2006-01-23 17:42:38.875(java.sql.Timestamp)][fromDate,2006-01-23 17: [...]
+
+    à priori les données du formulaire ne sont pas traitées : VALUES (?, ?, ?, ?, ?, ?, ?, ?) ce qui entraine l'echec du traitement SQL.
+
+
+    Si une idée vous vient à l'esprit, merci de me tenir au courant.
+
+    cordialement, julien.''')
+        assert True   # finished!
+
+    @td.with_tool('test', 'Wiki', 'wiki-len')
+    def test_markdown_link_length_limits(self):
+        with h.push_context('test', 'wiki-len', neighborhood='Projects'):
+            # these are always ok, no matter the NOBRACKET length
+            WM.Page.upsert(title='12345678901').commit()
+            text = g.markdown.convert('See [12345678901]')
+            assert 'href="/p/test/wiki-len/12345678901/">[12345678901]</a>' in text, text
+            WM.Page.upsert(title='this is 26 characters long').commit()
+            text = g.markdown.convert('See [this is 26 characters long]')
+            assert 'href="/p/test/wiki-len/this%20is%2026%20characters%20long/">[this is 26 characters long]</a>' in text, text
+
+            # NOBRACKET regex length impacts standard markdown links
+            text = g.markdown.convert('See [short](http://a.de)')
+            assert 'href="http://a.de" rel="nofollow">short</a>' in text, text
+            text = g.markdown.convert('See [this is 26 characters long](http://a.de)')
+            assert 'href="http://a.de" rel="nofollow">this is 26 characters long</a>' in text, text  # {0,12} fails {0,13} ok
+
+            # NOBRACKET regex length impacts our custom artifact links
+            text = g.markdown.convert('See [short](Home)')
+            assert 'href="/p/test/wiki-len/Home/">short</a>' in text, text
+            text = g.markdown.convert('See [123456789](Home)')
+            assert 'href="/p/test/wiki-len/Home/">123456789</a>' in text, text
+            text = g.markdown.convert('See [12345678901](Home)')
+            assert 'href="/p/test/wiki-len/Home/">12345678901</a>' in text, text  # {0,5} fails, {0,6} ok
+            text = g.markdown.convert('See [this is 16 chars](Home)')
+            assert 'href="/p/test/wiki-len/Home/">this is 16 chars</a>' in text, text  # {0,7} fails {0,8} ok
+            text = g.markdown.convert('See [this is 26 characters long](Home)')
+            assert 'href="/p/test/wiki-len/Home/">this is 26 characters long</a>' in text, text  # {0,12} fails {0,13} ok
+
+            # limit, currently
+            charSuperLong = '1234567890'*21
+            text = g.markdown.convert(f'See [{charSuperLong}](Home)')
+            assert f'<span>[{charSuperLong}]</span>(Home)' in text, text  # current limitation, not a link
+            # assert f'href="/p/test/wiki-len/Home/">{charSuperLong}</a>' in text, text  # ideal output
+
+    @td.with_wiki
+    def test_macro_include(self):
+        r = g.markdown.convert('[[include ref=Home id=foo]]')
+        assert '<div id="foo">' in r, r
+        assert 'href="../foo"' in g.markdown.convert('[My foo](foo)')
+        assert 'href="..' not in g.markdown.convert('[My foo](./foo)')
+
+    def test_macro_nbhd_feeds(self):
+        with h.push_context('--init--', 'wiki', neighborhood='Projects'):
+            r = g.markdown_wiki.convert('[[neighborhood_feeds tool_name=wiki]]')
+            assert 'Home modified by ' in r, r
+            assert '&lt;div class="markdown_content"&gt;' not in r
+
+    def test_sort_alpha(self):
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+
+        with h.push_context(p_nbhd.neighborhood_project._id):
+            r = g.markdown_wiki.convert('[[projects sort=alpha]]')
+            project_list = get_project_names(r)
+            assert project_list == sorted(project_list)
+
+    def test_sort_registered(self):
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+
+        with h.push_context(p_nbhd.neighborhood_project._id):
+            r = g.markdown_wiki.convert('[[projects sort=last_registered]]')
+            project_names = get_project_names(r)
+            ids = get_projects_property_in_the_same_order(project_names, '_id')
+            assert ids == sorted(ids, reverse=True)
+
+    def test_sort_updated(self):
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+
+        with h.push_context(p_nbhd.neighborhood_project._id):
+            r = g.markdown_wiki.convert('[[projects sort=last_updated]]')
+            project_names = get_project_names(r)
+            updated_at = get_projects_property_in_the_same_order(
+                project_names, 'last_updated')
+            assert updated_at == sorted(updated_at, reverse=True)
+
+    def test_filtering(self):
+        # set up for test
+        from random import choice
+        setup_trove_categories()
+        random_trove = choice(M.TroveCategory.query.find().all())
+        test_project = M.Project.query.get(shortname='test')
+        test_project_troves = getattr(test_project, 'trove_' + random_trove.type)
+        test_project_troves.append(random_trove._id)
+        ThreadLocalORMSession.flush_all()
+
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+        with h.push_config(c,
+                        project=p_nbhd.neighborhood_project,
+                        user=M.User.by_username('test-admin')):
+            r = g.markdown_wiki.convert(
+                '[[projects category="%s"]]' % random_trove.fullpath)
+            project_names = get_project_names(r)
+            assert [test_project.name] == project_names
+
+    def test_projects_macro(self):
+        two_column_style = 'width: 330px;'
+
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+        with h.push_config(c,
+                        project=p_nbhd.neighborhood_project,
+                        user=M.User.anonymous()):
+            # test columns
+            r = g.markdown_wiki.convert('[[projects display_mode=list columns=2]]')
+            assert two_column_style in r
+            r = g.markdown_wiki.convert('[[projects display_mode=list columns=3]]')
+            assert two_column_style not in r
+
+    @td.with_user_project('test-admin')
+    @td.with_user_project('test-user-1')
+    def test_myprojects_macro(self):
+        h.set_context('u/%s' % (c.user.username), 'wiki', neighborhood='Users')
+        r = g.markdown_wiki.convert('[[my_projects]]')
+        for p in c.user.my_projects():
+            if p.deleted or p.is_nbhd_project:
+                continue
+            proj_title = f'<h2><a href="{p.url()}">{p.name}</a></h2>'
+            assert proj_title in r
+
+        h.set_context('u/test-user-1', 'wiki', neighborhood='Users')
+        user = M.User.query.get(username='test-user-1')
+        r = g.markdown_wiki.convert('[[my_projects]]')
+        for p in user.my_projects():
+            if p.deleted or p.is_nbhd_project:
+                continue
+            proj_title = f'<h2><a href="{p.url()}">{p.name}</a></h2>'
+            assert proj_title in r
+
+    @td.with_wiki
+    def test_hideawards_macro(self):
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+
+        app_config_id = ObjectId()
+        award = M.Award(app_config_id=app_config_id)
+        award.short = 'Award short'
+        award.full = 'Award full'
+        award.created_by_neighborhood_id = p_nbhd._id
+
+        project = M.Project.query.get(
+            neighborhood_id=p_nbhd._id, shortname='test')
+
+        M.AwardGrant(
+            award=award,
+            award_url='http://award.org',
+            comment='Winner!',
+            granted_by_neighborhood=p_nbhd,
+            granted_to_project=project)
+
+        ThreadLocalORMSession.flush_all()
+
+        with h.push_context(p_nbhd.neighborhood_project._id):
+            r = g.markdown_wiki.convert('[[projects]]')
+            assert ('<div class="feature"> <a href="http://award.org" rel="nofollow" title="Winner!">'
+                    'Award short</a> </div>' in
+                    squish_spaces(r))
+
+            r = g.markdown_wiki.convert('[[projects show_awards_banner=False]]')
+            assert 'Award short' not in r
+
+    @td.with_tool('test', 'Blog', 'blog')
+    def test_project_blog_posts_macro(self):
+        from forgeblog import model as BM
+        with h.push_context('test', 'blog', neighborhood='Projects'):
+            BM.BlogPost.new(
+                title='Test title',
+                text='test post',
+                state='published',
+            )
+            BM.BlogPost.new(
+                title='Test title2',
+                text='test post2',
+                state='published',
+            )
+
+            r = g.markdown_wiki.convert('[[project_blog_posts]]')
+            assert 'Test title</a></h3>' in r
+            assert 'Test title2</a></h3>' in r
+            assert '<div class="markdown_content"><p>test post</p></div>' in r
+            assert '<div class="markdown_content"><p>test post2</p></div>' in r
+            assert 'by <em>Test Admin</em>' in r
+
+
+    def test_project_screenshots_macro(self):
+        with h.push_context('test', neighborhood='Projects'):
+            M.ProjectFile(project_id=c.project._id, category='screenshot', caption='caption', filename='test_file.jpg')
+            ThreadLocalORMSession.flush_all()
+
+            r = g.markdown_wiki.convert('[[project_screenshots]]')
+
+            assert 'href="/p/test/screenshot/test_file.jpg"' in r
+            assert 'src="/p/test/screenshot/test_file.jpg/thumb"' in r
+
 @with_nose_compatibility
 class TestCachedMarkdown(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         self.md = ForgeMarkdown()
         self.post = M.Post()
         self.post.text = '**bold**'
@@ -1025,7 +969,7 @@ class TestUserMentions(unittest.TestCase):
 @with_nose_compatibility
 class TestHandlePaging(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         prefs = {}
         c.user = Mock()
 
@@ -1086,7 +1030,7 @@ class TestHandlePaging(unittest.TestCase):
 @with_nose_compatibility
 class TestIconRender:
 
-    def setup_class(self, method):
+    def setup_method(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 ea0c50a2b..dbd359e33 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -23,7 +23,7 @@ import time
 import PIL
 from mock import Mock, patch
 from tg import tmpl_context as c
-from alluratest.tools import assert_equals, assert_raises, module_not_available
+from alluratest.tools import assert_equals, assert_raises, module_not_available, with_setup
 from datadiff import tools as dd
 from webob import Request
 from webob.exc import HTTPUnauthorized
@@ -42,7 +42,7 @@ from alluratest.controller import setup_basic_test
 import six
 
 
-def setup_class(self, method):
+def setup_method():
     """Method called by nose before running each test"""
     setup_basic_test()
 
@@ -50,7 +50,7 @@ def setup_class(self, method):
 @with_nose_compatibility
 class TestMakeSafePathPortion(TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         self.f = h.make_safe_path_portion
 
     def test_no_ascii_chars(self):
@@ -118,7 +118,7 @@ def test_really_unicode():
     assert isinstance(s, Markup)
     assert s == '<b>test</b>'
 
-
+@with_setup(setup_method)
 def test_find_project():
     proj, rest = h.find_project('/p/test/foo')
     assert proj.shortname == 'test'
@@ -127,12 +127,14 @@ def test_find_project():
     assert proj is None
 
 
+@with_setup(setup_method)
 def test_make_roles():
     h.set_context('test', 'wiki', neighborhood='Projects')
     pr = M.ProjectRole.anonymous()
     assert next(h.make_roles([pr._id])) == pr
 
 
+@with_setup(setup_method)
 @td.with_wiki
 def test_make_app_admin_only():
     h.set_context('test', 'wiki', neighborhood='Projects')
@@ -165,6 +167,7 @@ def test_make_app_admin_only():
     assert c.app.is_visible_to(admin)
 
 
+@with_setup(setup_method)
 @td.with_wiki
 def test_context_setters():
     h.set_context('test', 'wiki', neighborhood='Projects')
@@ -259,6 +262,7 @@ def test_render_any_markup_empty():
     assert h.render_any_markup('foo', '') == '<p><em>Empty File</em></p>'
 
 
+@with_setup(setup_method)
 def test_render_any_markup_plain():
     assert (
         h.render_any_markup(
@@ -266,6 +270,7 @@ def test_render_any_markup_plain():
         '<pre>&lt;b&gt;blah&lt;/b&gt;\n&lt;script&gt;alert(1)&lt;/script&gt;\nfoo</pre>')
 
 
+@with_setup(setup_method)
 def test_render_any_markup_formatting():
     assert (str(h.render_any_markup('README.md', '### foo\n'
                                           '    <script>alert(1)</script> bar')) ==
@@ -275,6 +280,7 @@ def test_render_any_markup_formatting():
                   '&lt;/script&gt;</span> bar\n</code></pre></div>\n</div>')
 
 
+@with_setup(setup_method)
 def test_render_any_markdown_encoding():
     # send encoded content in, make sure it converts it to actual unicode object which Markdown lib needs
     assert (h.render_any_markup('README.md', 'Müller'.encode()) ==
@@ -306,6 +312,7 @@ def test_log_if_changed():
     assert AuditLogMock.logs[0] == 'updated value'
 
 
+@with_setup(setup_method)
 def test_get_tool_packages():
     assert h.get_tool_packages('tickets') == ['forgetracker']
     assert h.get_tool_packages('Tickets') == ['forgetracker']
@@ -321,6 +328,7 @@ def test_get_first():
     assert h.get_first({'title': ['Value']}, 'title') == 'Value'
 
 
+@with_setup(setup_method)
 @patch('allura.lib.search.c')
 def test_inject_user(context):
     user = Mock(username='user01')
@@ -515,6 +523,7 @@ class TestUrlOpen(TestCase):
         self.assertEqual(urlopen.call_count, 1)
 
 
+@with_setup(setup_method)
 def test_absurl():
     assert h.absurl('/p/test/foobar') == 'http://localhost/p/test/foobar'
 
@@ -525,6 +534,7 @@ def test_daterange():
         [datetime(2013, 1, 1), datetime(2013, 1, 2), datetime(2013, 1, 3)])
 
 
+@with_setup(setup_method)
 @patch.object(h, 'request',
               new=Request.blank('/p/test/foobar', base_url='https://www.mysite.com/p/test/foobar'))
 def test_login_overlay():
@@ -591,6 +601,7 @@ class TestIterEntryPoints(TestCase):
                                 list, h.iter_entry_points('allura'))
 
 
+@with_setup(setup_method)
 def test_get_user_status():
     user = M.User.by_username('test-admin')
     assert h.get_user_status(user) == 'enabled'
diff --git a/Allura/allura/tests/test_mail_util.py b/Allura/allura/tests/test_mail_util.py
index 4a3970c82..f1ad21d09 100644
--- a/Allura/allura/tests/test_mail_util.py
+++ b/Allura/allura/tests/test_mail_util.py
@@ -49,7 +49,7 @@ config = ConfigProxy(
 @with_nose_compatibility
 class TestReactor(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         setup_global_objects()
         ThreadLocalORMSession.flush_all()
@@ -236,7 +236,7 @@ class TestHeader:
 @with_nose_compatibility
 class TestIsAutoreply:
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         self.msg = {'headers': {}}
 
     def test_empty(self):
@@ -333,7 +333,7 @@ def test_parse_message_id():
 @with_nose_compatibility
 class TestMailServer:
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
 
     @mock.patch('allura.command.base.log', autospec=True)
diff --git a/Allura/allura/tests/test_middlewares.py b/Allura/allura/tests/test_middlewares.py
index fa15b7fa3..1f8e78a79 100644
--- a/Allura/allura/tests/test_middlewares.py
+++ b/Allura/allura/tests/test_middlewares.py
@@ -25,7 +25,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestCORSMiddleware:
 
-    def setup_class(self, method):
+    def setup_method(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 de6208c3b..e007b0adf 100644
--- a/Allura/allura/tests/test_multifactor.py
+++ b/Allura/allura/tests/test_multifactor.py
@@ -181,7 +181,7 @@ class TestMongodbTotpService(TestAnyTotpServiceImplementation):
     __test__ = True
     Service = MongodbTotpService
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         config = {
             'ming.main.uri': 'mim://host/allura_test',
         }
@@ -191,7 +191,7 @@ class TestMongodbTotpService(TestAnyTotpServiceImplementation):
 @with_nose_compatibility
 class TestGoogleAuthenticatorPamFilesystemMixin:
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         self.totp_basedir = tempfile.mkdtemp(prefix='totp-test', dir=os.getenv('TMPDIR', '/tmp'))
         config['auth.multifactor.totp.filesystem.basedir'] = self.totp_basedir
 
@@ -312,7 +312,7 @@ class TestMongodbRecoveryCodeService(TestAnyRecoveryCodeServiceImplementation):
 
     Service = MongodbRecoveryCodeService
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         config = {
             'ming.main.uri': 'mim://host/allura_test',
         }
@@ -327,7 +327,7 @@ class TestGoogleAuthenticatorPamFilesystemRecoveryCodeService(TestAnyRecoveryCod
 
     Service = GoogleAuthenticatorPamFilesystemRecoveryCodeService
 
-    def setup_class(self, method):
+    def setup_method(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 c0498de69..22a7ed8bf 100644
--- a/Allura/allura/tests/test_plugin.py
+++ b/Allura/allura/tests/test_plugin.py
@@ -51,7 +51,8 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestProjectRegistrationProvider:
 
-    def setup_class(self, method):
+    def setup_method(self, method):
+        setup_basic_test()
         self.provider = ProjectRegistrationProvider()
 
     @patch('allura.lib.security.has_access')
@@ -89,7 +90,7 @@ class TestProjectRegistrationProvider:
 @with_nose_compatibility
 class TestProjectRegistrationProviderParseProjectFromUrl:
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         ThreadLocalORMSession.close_all()
         setup_global_objects()
@@ -164,7 +165,7 @@ class UserMock:
 @with_nose_compatibility
 class TestProjectRegistrationProviderPhoneVerification:
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         self.p = ProjectRegistrationProvider()
         self.user = UserMock()
         self.nbhd = MagicMock()
@@ -637,7 +638,7 @@ class TestThemeProvider_notifications:
 @with_nose_compatibility
 class TestLocalAuthenticationProvider:
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         ThreadLocalORMSession.close_all()
         setup_global_objects()
@@ -752,7 +753,7 @@ class TestLocalAuthenticationProvider:
 @with_nose_compatibility
 class TestAuthenticationProvider:
 
-    def setup_class(self, method):
+    def setup_method(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 d66bf2684..f8576bbf8 100644
--- a/Allura/allura/tests/test_scripttask.py
+++ b/Allura/allura/tests/test_scripttask.py
@@ -25,7 +25,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestScriptTask(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         class TestScriptTask(ScriptTask):
             _parser = mock.Mock()
 
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index 0547b1954..735b3294b 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -58,6 +58,10 @@ from allura.lib.decorators import event_handler, task
 @with_nose_compatibility
 class TestRepoTasks(unittest.TestCase):
 
+    def setup_method(self, method):
+        setup_basic_test()
+        setup_global_objects()
+
     @mock.patch('allura.tasks.repo_tasks.c.app')
     @mock.patch('allura.tasks.repo_tasks.g.post_event')
     def test_clone_posts_event_on_failure(self, post_event, app):
@@ -103,7 +107,7 @@ def _task_that_creates_event(event_name,):
 @with_nose_compatibility
 class TestEventTasks(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         setup_global_objects()
         self.called_with = []
@@ -162,7 +166,7 @@ class TestEventTasks(unittest.TestCase):
 @with_nose_compatibility
 class TestIndexTasks(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         setup_global_objects()
 
@@ -248,7 +252,7 @@ class TestIndexTasks(unittest.TestCase):
 @with_nose_compatibility
 class TestMailTasks(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         setup_global_objects()
 
@@ -543,7 +547,7 @@ I'm not here'''
 
 @with_nose_compatibility
 class TestUserNotificationTasks(TestController):
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         self.setup_with_tools()
 
@@ -576,7 +580,7 @@ class TestUserNotificationTasks(TestController):
 @with_nose_compatibility
 class TestNotificationTasks(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         setup_global_objects()
 
@@ -623,7 +627,7 @@ class _TestArtifact(M.Artifact):
 @with_nose_compatibility
 class TestExportTasks(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(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 ac408a586..6e7fca1cc 100644
--- a/Allura/allura/tests/test_utils.py
+++ b/Allura/allura/tests/test_utils.py
@@ -51,7 +51,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestConfigProxy(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         self.cp = utils.ConfigProxy(mybaz="baz")
 
     def test_getattr(self):
@@ -72,7 +72,7 @@ class TestConfigProxy(unittest.TestCase):
 @with_nose_compatibility
 class TestChunkedIterator(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_unit_test()
         config = {
             'ming.main.uri': 'mim://host/allura_test',
@@ -115,7 +115,7 @@ class TestChunkedList(unittest.TestCase):
 @with_nose_compatibility
 class TestAntispam(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_unit_test()
         self.a = utils.AntiSpam()
 
@@ -256,7 +256,7 @@ class TestIsTextFile(unittest.TestCase):
 @with_nose_compatibility
 class TestCodeStats(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(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 cc9f2fba0..2e0f5af65 100644
--- a/Allura/allura/tests/test_validators.py
+++ b/Allura/allura/tests/test_validators.py
@@ -27,7 +27,7 @@ from allura.websetup.bootstrap import create_user
 from allura.tests.pytest_helpers import with_nose_compatibility
 
 
-def setup_method(self, method):
+def _setup_method():
     setup_basic_test()
 
 
@@ -40,6 +40,9 @@ def dummy_task(*args, **kw):
 class TestJsonConverter(unittest.TestCase):
     val = v.JsonConverter
 
+    def setup_method(self, method):
+        _setup_method()
+
     def test_valid(self):
         self.assertEqual({}, self.val.to_python('{}'))
 
@@ -52,6 +55,10 @@ class TestJsonConverter(unittest.TestCase):
 
 @with_nose_compatibility
 class TestJsonFile(unittest.TestCase):
+
+    def setup_method(self, method):
+        _setup_method()
+
     val = v.JsonFile
 
     class FieldStorage:
@@ -71,6 +78,9 @@ class TestJsonFile(unittest.TestCase):
 class TestUserMapFile(unittest.TestCase):
     val = v.UserMapJsonFile()
 
+    def setup_method(self, method):
+        _setup_method()
+
     class FieldStorage:
 
         def __init__(self, content):
@@ -94,6 +104,9 @@ class TestUserMapFile(unittest.TestCase):
 class TestUserValidator(unittest.TestCase):
     val = v.UserValidator
 
+    def setup_method(self, method):
+        _setup_method()
+
     def test_valid(self):
         self.assertEqual(M.User.by_username('root'),
                          self.val.to_python('root'))
@@ -108,6 +121,9 @@ class TestUserValidator(unittest.TestCase):
 class TestAnonymousValidator(unittest.TestCase):
     val = v.AnonymousValidator
 
+    def setup_method(self, method):
+        _setup_method()
+
     @patch('allura.lib.validators.c')
     def test_valid(self, c):
         c.user = M.User.by_username('root')
@@ -124,6 +140,9 @@ class TestAnonymousValidator(unittest.TestCase):
 @with_nose_compatibility
 class TestMountPointValidator(unittest.TestCase):
 
+    def setup_method(self, method):
+        _setup_method()
+
     @patch('allura.lib.validators.c')
     def test_valid(self, c):
         App = Mock()
@@ -186,6 +205,9 @@ class TestMountPointValidator(unittest.TestCase):
 class TestTaskValidator(unittest.TestCase):
     val = v.TaskValidator
 
+    def setup_method(self, method):
+        _setup_method()
+
     def test_valid(self):
         self.assertEqual(
             dummy_task, self.val.to_python('allura.tests.test_validators.dummy_task'))
@@ -208,15 +230,18 @@ class TestTaskValidator(unittest.TestCase):
 
     def test_not_a_task(self):
         with self.assertRaises(fe.Invalid) as cm:
-            self.val.to_python('allura.tests.test_validators.setUp')
+            self.val.to_python('allura.tests.test_validators._setup_method')
         self.assertEqual(str(cm.exception),
-                         '"allura.tests.test_validators.setUp" is not a task.')
+                         '"allura.tests.test_validators._setup_method" is not a task.')
 
 
 @with_nose_compatibility
 class TestPathValidator(unittest.TestCase):
     val = v.PathValidator(strip=True, if_missing={}, if_empty={})
 
+    def setup_method(self, method):
+        _setup_method()
+
     def test_valid_project(self):
         project = M.Project.query.get(shortname='test')
         d = self.val.to_python('/p/test')
@@ -266,6 +291,9 @@ class TestPathValidator(unittest.TestCase):
 class TestUrlValidator(unittest.TestCase):
     val = v.URL
 
+    def setup_method(self, method):
+        _setup_method()
+
     def test_valid(self):
         self.assertEqual('http://192.168.0.1', self.val.to_python('192.168.0.1'))
         self.assertEqual('http://url', self.val.to_python('url'))
@@ -285,6 +313,9 @@ class TestUrlValidator(unittest.TestCase):
 class TestNonHttpUrlValidator(unittest.TestCase):
     val = v.NonHttpUrl
 
+    def setup_method(self, method):
+        _setup_method()
+
     def test_valid(self):
         self.assertEqual('svn://192.168.0.1', self.val.to_python('svn://192.168.0.1'))
         self.assertEqual('ssh+git://url', self.val.to_python('ssh+git://url'))
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index df7512d59..5b9d5b1f2 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -62,7 +62,7 @@ with_git2 = td.with_tool(test_project_with_repo, 'git', 'src2', 'Git2')
 
 @with_nose_compatibility
 class TestWebhookBase:
-    def setup_class(self, method):
+    def setup_method(self, method):
         setup_basic_test()
         self.patches = self.monkey_patch()
         for p in self.patches:
@@ -132,7 +132,7 @@ class TestValidators(TestWebhookBase):
 
 @with_nose_compatibility
 class TestWebhookController(TestController):
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         self.patches = self.monkey_patch()
         for p in self.patches:
@@ -424,8 +424,8 @@ class TestWebhookController(TestController):
 
 @with_nose_compatibility
 class TestSendWebhookHelper(TestWebhookBase):
-    def setUp(self, *args, **kw):
-        super().setUp(*args, **kw)
+    def setup_method(self, method):
+        super().setup_method(method)
         self.payload = {'some': ['data', 23]}
         self.h = SendWebhookHelper(self.wh, self.payload)
 
@@ -678,7 +678,7 @@ class TestModels(TestWebhookBase):
 
 @with_nose_compatibility
 class TestWebhookRestController(TestRestApiBase):
-    def setup_class(self, method):
+    def setup_method(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 43b8c8256..4097ac952 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_class(self, method):
+def setup_module(module):
     setup_basic_test()
 
 
 class MockPatchTestCase:
     patches = []
 
-    def setup_class(self, method):
+    def setup_method(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_class(self, method):
+    def setup_method(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 10a0e113d..72abd8ed4 100644
--- a/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
+++ b/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
@@ -35,7 +35,7 @@ class TestWhenModerating(WithDatabase):
                patches.fake_request_patch,
                patches.disable_notifications_patch]
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         post = create_post('mypost')
         discussion_controller = Mock(
@@ -89,7 +89,7 @@ class TestIndexWithNoPosts(WithDatabase):
 class TestIndexWithAPostInTheDiscussion(WithDatabase):
     patches = [patches.fake_app_patch]
 
-    def setup_class(self, method):
+    def setup_method(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 776b4af01..2bcff5cfa 100644
--- a/Allura/allura/tests/unit/phone/test_nexmo.py
+++ b/Allura/allura/tests/unit/phone/test_nexmo.py
@@ -27,7 +27,7 @@ from allura.lib.phone.nexmo import NexmoPhoneService
 @with_nose_compatibility
 class TestPhoneService:
 
-    def setup_class(self, method):
+    def setup_method(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/spam/test_spam_filter.py b/Allura/allura/tests/unit/spam/test_spam_filter.py
index fa71ccecd..99f6ce1a9 100644
--- a/Allura/allura/tests/unit/spam/test_spam_filter.py
+++ b/Allura/allura/tests/unit/spam/test_spam_filter.py
@@ -79,7 +79,7 @@ class TestSpamFilter(unittest.TestCase):
 @with_nose_compatibility
 class TestSpamFilterFunctional:
 
-    def setup_class(self, method):
+    def setup_method(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 fbc6f9e40..2037ddde8 100644
--- a/Allura/allura/tests/unit/spam/test_stopforumspam.py
+++ b/Allura/allura/tests/unit/spam/test_stopforumspam.py
@@ -28,7 +28,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestStopForumSpam:
 
-    def setup_class(self, method):
+    def setup_method(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 f60b95432..2a374f415 100644
--- a/Allura/allura/tests/unit/test_app.py
+++ b/Allura/allura/tests/unit/test_app.py
@@ -17,8 +17,6 @@
 
 from unittest import TestCase
 
-from alluratest.tools import assert_equal
-
 from allura.app import Application
 from allura import model
 from allura.tests.unit import WithDatabase
@@ -73,7 +71,7 @@ class TestInstall(WithDatabase):
 class TestDefaultDiscussion(WithDatabase):
     patches = [fake_app_patch]
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         install_app()
         self.discussion = model.Discussion.query.get(
@@ -94,7 +92,7 @@ class TestDefaultDiscussion(WithDatabase):
 class TestAppDefaults(WithDatabase):
     patches = [fake_app_patch]
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         self.app = install_app()
 
diff --git a/Allura/allura/tests/unit/test_discuss.py b/Allura/allura/tests/unit/test_discuss.py
index 393d08921..9cb225e40 100644
--- a/Allura/allura/tests/unit/test_discuss.py
+++ b/Allura/allura/tests/unit/test_discuss.py
@@ -15,8 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-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
diff --git a/Allura/allura/tests/unit/test_helpers/test_ago.py b/Allura/allura/tests/unit/test_helpers/test_ago.py
index 0c538e752..7fef39014 100644
--- a/Allura/allura/tests/unit/test_helpers/test_ago.py
+++ b/Allura/allura/tests/unit/test_helpers/test_ago.py
@@ -27,7 +27,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestAgo:
 
-    def setup_class(self, method):
+    def setup_method(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 102e48ab8..3468ceed0 100644
--- a/Allura/allura/tests/unit/test_helpers/test_set_context.py
+++ b/Allura/allura/tests/unit/test_helpers/test_set_context.py
@@ -32,7 +32,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestWhenProjectIsFoundAndAppIsNot(WithDatabase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         self.myproject = create_project('myproject')
         set_context('myproject', neighborhood=self.myproject.neighborhood)
@@ -47,7 +47,7 @@ class TestWhenProjectIsFoundAndAppIsNot(WithDatabase):
 @with_nose_compatibility
 class TestWhenProjectIsFoundInNeighborhood(WithDatabase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         self.myproject = create_project('myproject')
         set_context('myproject', neighborhood=self.myproject.neighborhood)
@@ -63,7 +63,7 @@ class TestWhenProjectIsFoundInNeighborhood(WithDatabase):
 class TestWhenAppIsFoundByID(WithDatabase):
     patches = [patches.project_app_loading_patch]
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         self.myproject = create_project('myproject')
         self.app_config = create_app_config(self.myproject, 'my_mounted_app')
@@ -81,7 +81,7 @@ class TestWhenAppIsFoundByID(WithDatabase):
 class TestWhenAppIsFoundByMountPoint(WithDatabase):
     patches = [patches.project_app_loading_patch]
 
-    def setup_class(self, method):
+    def setup_method(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 57c0ea6cb..d94430188 100644
--- a/Allura/allura/tests/unit/test_ldap_auth_provider.py
+++ b/Allura/allura/tests/unit/test_ldap_auth_provider.py
@@ -38,7 +38,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestLdapAuthenticationProvider:
 
-    def setup_class(self, method):
+    def setup_method(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 670a8c929..f4e50aafe 100644
--- a/Allura/allura/tests/unit/test_mixins.py
+++ b/Allura/allura/tests/unit/test_mixins.py
@@ -23,7 +23,7 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestVotableArtifact:
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         self.user1 = Mock()
         self.user1.username = 'test-user'
         self.user2 = Mock()
diff --git a/Allura/allura/tests/unit/test_post_model.py b/Allura/allura/tests/unit/test_post_model.py
index a6d62fff8..e1f7504ed 100644
--- a/Allura/allura/tests/unit/test_post_model.py
+++ b/Allura/allura/tests/unit/test_post_model.py
@@ -30,7 +30,7 @@ class TestPostModel(WithDatabase):
     patches = [patches.fake_app_patch,
                patches.disable_notifications_patch]
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         super().setup_method(method)
         self.post = create_post('mypost')
 
diff --git a/Allura/allura/tests/unit/test_repo.py b/Allura/allura/tests/unit/test_repo.py
index 12bc058c0..df862d547 100644
--- a/Allura/allura/tests/unit/test_repo.py
+++ b/Allura/allura/tests/unit/test_repo.py
@@ -314,7 +314,7 @@ class TestPrefixPathsUnion(unittest.TestCase):
 @with_nose_compatibility
 class TestGroupCommits:
 
-    def setup_class(self, method):
+    def setup_method(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 fbf6e7f0e..cb7f02f7d 100644
--- a/Allura/allura/tests/unit/test_session.py
+++ b/Allura/allura/tests/unit/test_session.py
@@ -88,7 +88,7 @@ class TestSessionExtension(TestCase):
 @with_nose_compatibility
 class TestIndexerSessionExtension(TestSessionExtension):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         session = mock.Mock()
         self.ExtensionClass = IndexerSessionExtension
         self.extension = self.ExtensionClass(session)
@@ -127,7 +127,7 @@ class TestIndexerSessionExtension(TestSessionExtension):
 @with_nose_compatibility
 class TestArtifactSessionExtension(TestSessionExtension):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         session = mock.Mock(disable_index=False)
         self.ExtensionClass = ArtifactSessionExtension
         self.extension = self.ExtensionClass(session)
@@ -157,7 +157,7 @@ class TestArtifactSessionExtension(TestSessionExtension):
 @with_nose_compatibility
 class TestBatchIndexer(TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         session = mock.Mock()
         self.extcls = BatchIndexer
         self.ext = self.extcls(session)
diff --git a/Allura/allura/tests/unit/test_solr.py b/Allura/allura/tests/unit/test_solr.py
index 7bb5a188a..cc73cdb6f 100644
--- a/Allura/allura/tests/unit/test_solr.py
+++ b/Allura/allura/tests/unit/test_solr.py
@@ -32,6 +32,10 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestSolr(unittest.TestCase):
 
+    def setup_method(self, method):
+        # need to create the "test" project so @td.with_wiki works
+        setup_basic_test()
+
     @mock.patch('allura.lib.solr.pysolr')
     def test_init(self, pysolr):
         servers = ['server1', 'server2']
@@ -122,7 +126,7 @@ class TestSolr(unittest.TestCase):
 @with_nose_compatibility
 class TestSearchIndexable(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         self.obj = SearchIndexable()
 
     def test_solarize_empty_index(self):
@@ -147,7 +151,7 @@ class TestSearchIndexable(unittest.TestCase):
 @with_nose_compatibility
 class TestSearch_app(unittest.TestCase):
 
-    def setup_class(self, method):
+    def setup_method(self, method):
         # need to create the "test" project so @td.with_wiki works
         setup_basic_test()
 
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 8faf398de..1630d7d06 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -66,8 +66,8 @@ def find(d, pred):
 
 
 class TrackerTestController(TestController):
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @td.with_tracker


[allura] 02/04: first substantial test file mostly passing under pytest

Posted by di...@apache.org.
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 c5ee779896adc9fbf0aafd9039db1c9808f2cd8a
Author: Kenton Taylor <kt...@slashdotmedia.com>
AuthorDate: Mon Aug 22 21:00:53 2022 +0000

    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 = {}
 


[allura] 01/04: 8455 nose2pytest for ./Allura

Posted by di...@apache.org.
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 a784125bf31e63784fdc45eb3632c34d1f30a98b
Author: Kenton Taylor <kt...@slashdotmedia.com>
AuthorDate: Mon Aug 22 19:11:42 2022 +0000

    8455 nose2pytest for ./Allura
---
 Allura/allura/tests/decorators.py                  |   7 +-
 Allura/allura/tests/functional/test_admin.py       | 174 +++----
 Allura/allura/tests/functional/test_auth.py        | 502 ++++++++++-----------
 Allura/allura/tests/functional/test_discuss.py     |  18 +-
 Allura/allura/tests/functional/test_gravatar.py    |   4 +-
 Allura/allura/tests/functional/test_home.py        |  52 +--
 .../allura/tests/functional/test_neighborhood.py   |  60 +--
 .../tests/functional/test_personal_dashboard.py    |  28 +-
 Allura/allura/tests/functional/test_rest.py        | 158 +++----
 Allura/allura/tests/functional/test_root.py        |  12 +-
 Allura/allura/tests/functional/test_site_admin.py  | 230 +++++-----
 .../allura/tests/functional/test_trovecategory.py  |  44 +-
 .../allura/tests/functional/test_user_profile.py   |  56 +--
 Allura/allura/tests/model/test_artifact.py         |  30 +-
 Allura/allura/tests/model/test_auth.py             | 200 ++++----
 Allura/allura/tests/model/test_discussion.py       |  48 +-
 Allura/allura/tests/model/test_filesystem.py       |  16 +-
 Allura/allura/tests/model/test_notification.py     |  40 +-
 Allura/allura/tests/model/test_oauth.py            |   6 +-
 Allura/allura/tests/model/test_project.py          |  32 +-
 Allura/allura/tests/model/test_repo.py             |  80 ++--
 Allura/allura/tests/model/test_timeline.py         |   2 +-
 .../tests/scripts/test_create_sitemap_files.py     |   6 +-
 .../allura/tests/scripts/test_delete_projects.py   |   2 +-
 Allura/allura/tests/scripts/test_misc_scripts.py   |   6 +-
 Allura/allura/tests/scripts/test_reindexes.py      |   4 +-
 .../tests/templates/jinja_master/test_lib.py       |  10 +-
 Allura/allura/tests/test_app.py                    |  24 +-
 Allura/allura/tests/test_commands.py               |  65 ++-
 Allura/allura/tests/test_decorators.py             |  24 +-
 Allura/allura/tests/test_globals.py                | 203 ++++-----
 Allura/allura/tests/test_helpers.py                | 156 +++----
 Allura/allura/tests/test_mail_util.py              |  68 +--
 Allura/allura/tests/test_middlewares.py            |  30 +-
 Allura/allura/tests/test_multifactor.py            |  36 +-
 Allura/allura/tests/test_patches.py                |   8 +-
 Allura/allura/tests/test_plugin.py                 | 204 ++++-----
 Allura/allura/tests/test_security.py               |  30 +-
 Allura/allura/tests/test_tasks.py                  | 156 +++----
 Allura/allura/tests/test_utils.py                  |  68 +--
 Allura/allura/tests/test_webhooks.py               | 224 ++++-----
 .../test_discussion_moderation_controller.py       |   8 +-
 Allura/allura/tests/unit/phone/test_nexmo.py       |  40 +-
 .../allura/tests/unit/phone/test_phone_service.py  |   8 +-
 Allura/allura/tests/unit/spam/test_spam_filter.py  |  10 +-
 .../allura/tests/unit/spam/test_stopforumspam.py   |   6 +-
 Allura/allura/tests/unit/test_app.py               |   2 +-
 Allura/allura/tests/unit/test_discuss.py           |   8 +-
 Allura/allura/tests/unit/test_helpers/test_ago.py  |   2 +-
 .../allura/tests/unit/test_ldap_auth_provider.py   |  24 +-
 .../allura/tests/unit/test_package_path_loader.py  |  40 +-
 Allura/allura/tests/unit/test_repo.py              |  30 +-
 Allura/allura/tests/unit/test_solr.py              |  26 +-
 requirements.in                                    |   1 +
 requirements.txt                                   |  23 +-
 55 files changed, 1679 insertions(+), 1672 deletions(-)

diff --git a/Allura/allura/tests/decorators.py b/Allura/allura/tests/decorators.py
index 6c561c228..e4ba26c26 100644
--- a/Allura/allura/tests/decorators.py
+++ b/Allura/allura/tests/decorators.py
@@ -226,7 +226,6 @@ def assert_logmsg_and_no_warnings_or_errors(logs, msg):
 def assert_equivalent_urls(url1, url2):
     base1, _, qs1 = url1.partition('?')
     base2, _, qs2 = url2.partition('?')
-    assert_equal(
-        (base1, parse_qs(qs1)),
-        (base2, parse_qs(qs2)),
-    )
+    assert (
+        (base1, parse_qs(qs1)) ==
+        (base2, parse_qs(qs2)))
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index 96138ffea..d8570c668 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -108,7 +108,7 @@ class TestProjectAdmin(TestController):
         # check tool in the nav
         r = self.app.get('/p/test/test-tool/').follow()
         active_link = r.html.findAll('li', {'class': 'selected'})
-        assert_equals(len(active_link), 1)
+        assert len(active_link) == 1
         assert active_link[0].contents[1]['href'] == '/p/test/test-tool/'
         with audits('install tool test-tool2'):
             r = self.app.post('/admin/update_mounts', params={
@@ -160,11 +160,11 @@ class TestProjectAdmin(TestController):
             'task_name': 'allura.tasks.event_tasks.event',
             'args': 'project_menu_updated'
         }).all()
-        assert_equals(len(menu_updated_events), 7)
+        assert len(menu_updated_events) == 7
 
     def test_features(self):
         proj = M.Project.query.get(shortname='test')
-        assert_equals(proj.features, [])
+        assert proj.features == []
         with audits(r"change project features to \[{u}'One', {u}'Two'\]".format(u='')):
             resp = self.app.post('/admin/update', params={
                 'features-0.feature': 'One',
@@ -172,32 +172,32 @@ class TestProjectAdmin(TestController):
                 'features-2.feature': ' Two '})
             if resp.status_int == 200:
                 errors = resp.html.findAll('', attrs={'class': 'fielderror'})
-                assert_equals([], errors)
+                assert [] == errors
                 errors = resp.html.findAll('', attrs={'class': 'error'})
-                assert_equals([], errors)
+                assert [] == errors
                 raise AssertionError('Should be a 301 not 200 response')
 
         r = self.app.get('/admin/overview')
         features = r.html.find('div', {'id': 'features'})
         features = features.findAll('input', {'type': 'text'})
         # two features + extra empty input + stub hidden input for js
-        assert_equals(len(features), 2+1+1)
-        assert_equals(features[0]['value'], 'One')
-        assert_equals(features[1]['value'], 'Two')
+        assert len(features) == 2+1+1
+        assert features[0]['value'] == 'One'
+        assert features[1]['value'] == 'Two'
         proj = M.Project.query.get(shortname='test')
-        assert_equals(proj.features, ['One', 'Two'])
+        assert proj.features == ['One', 'Two']
 
     @td.with_wiki
     def test_block_user_empty_data(self):
         r = self.app.post('/admin/wiki/block_user',
                           params={'username': '', 'perm': '', 'reason': ''})
-        assert_equals(r.json, dict(error='Enter username'))
+        assert r.json == dict(error='Enter username')
 
     @td.with_wiki
     def test_unblock_user_empty_data(self):
         r = self.app.post('/admin/wiki/unblock_user',
                           params={'user_id': '', 'perm': ''})
-        assert_equals(r.json, dict(error='Select user to unblock'))
+        assert r.json == dict(error='Select user to unblock')
 
     @td.with_wiki
     def test_block_user(self):
@@ -207,14 +207,14 @@ class TestProjectAdmin(TestController):
         user = M.User.by_username('test-admin')
         r = self.app.post('/admin/wiki/block_user',
                           params={'username': 'test-admin', 'perm': 'read', 'reason': 'Comment'})
-        assert_equals(
-            r.json, dict(user_id=str(user._id), username='test-admin', reason='Comment'))
+        assert (
+            r.json == dict(user_id=str(user._id), username='test-admin', reason='Comment'))
         user = M.User.by_username('test-admin')
         admin_role = M.ProjectRole.by_user(user)
         project = M.Project.query.get(shortname='test')
         app = project.app_instance('wiki')
         ace = M.ACL.contains(M.ACE.deny(admin_role._id, 'read'), app.acl)
-        assert_equals(ace.reason, 'Comment')
+        assert ace.reason == 'Comment'
         audit_log = M.AuditLog.query.find(
             {'project_id': project._id}).sort('_id', -1).first()
         assert 'blocked user "test-admin"' in audit_log.message
@@ -237,7 +237,7 @@ class TestProjectAdmin(TestController):
         assert M.ACL.contains(ace, app.acl) is not None
         r = self.app.post('/admin/wiki/unblock_user',
                           params={'user_id': str(user._id), 'perm': 'read'})
-        assert_equals(r.json, dict(unblocked=[str(user._id)]))
+        assert r.json == dict(unblocked=[str(user._id)])
         assert M.ACL.contains(ace, app.acl) is None
         r = self.app.get('/admin/wiki/permissions')
         assert '<input type="checkbox" name="user_id"' not in r
@@ -293,7 +293,7 @@ class TestProjectAdmin(TestController):
         assert M.ACL.contains(M.ACE.deny(user_role._id, 'post'), app.acl)
         # ...and all old ACEs also
         for ace in old_acl:
-            assert_in(ace, app.acl)
+            assert ace in app.acl
 
     def test_tool_permissions(self):
         BUILTIN_APPS = ['activity', 'blog', 'discussion', 'git', 'link',
@@ -335,7 +335,7 @@ class TestProjectAdmin(TestController):
             c.project = M.Project.query.get(shortname='test')
             data = c.project.nav_data(admin_options=True)
             menu = [tool['text'] for tool in data['installable_tools']]
-            assert_in('Wiki', menu)
+            assert 'Wiki' in menu
 
             r = self.app.post('/p/test/admin/update_mounts/', params={
                 'new.install': 'install',
@@ -347,7 +347,7 @@ class TestProjectAdmin(TestController):
             c.project = M.Project.query.get(shortname='test')
             data = c.project.nav_data(admin_options=True)
             menu = [tool['text'] for tool in data['installable_tools']]
-            assert_not_in('Wiki', menu)
+            assert 'Wiki' not in menu
 
             r = self.app.post('/p/test/admin/update_mounts/', params={
                 'new.install': 'install',
@@ -361,17 +361,17 @@ class TestProjectAdmin(TestController):
 
     def test_install_tool_form(self):
         r = self.app.get('/admin/install_tool?tool_name=wiki')
-        assert_in('Installing Wiki', r)
+        assert 'Installing Wiki' in r
 
     def test_install_tool_form_options(self):
         opts = ['AllowEmailPosting']
         with mock.patch.object(ForgeWikiApp, 'config_on_install', new=opts):
             r = self.app.get('/admin/install_tool?tool_name=wiki')
-            assert_in('<input id="AllowEmailPosting" name="AllowEmailPosting"', r)
+            assert '<input id="AllowEmailPosting" name="AllowEmailPosting"' in r
 
     def test_install_tool_form_subproject(self):
         r = self.app.get('/admin/install_tool?tool_name=subproject')
-        assert_in('Installing Sub Project', r)
+        assert 'Installing Sub Project' in r
 
     def test_project_icon(self):
         file_name = 'neo-icon-set-454545-256x350.png'
@@ -452,12 +452,12 @@ class TestProjectAdmin(TestController):
                                       neighborhood_id=p_nbhd._id)
         # first uploaded is first by default
         screenshots = project.get_screenshots()
-        assert_equals(screenshots[0].filename, 'admin_24.png')
+        assert screenshots[0].filename == 'admin_24.png'
         # reverse order
         params = {str(ss._id): str(len(screenshots) - 1 - i)
                       for i, ss in enumerate(screenshots)}
         self.app.post('/admin/sort_screenshots', params)
-        assert_equals(project.get_screenshots()[0].filename, 'admin_32.png')
+        assert project.get_screenshots()[0].filename == 'admin_32.png'
 
     def test_project_delete_undelete(self):
         # create a subproject
@@ -586,7 +586,7 @@ class TestProjectAdmin(TestController):
             r = form.submit()
         r = r.follow()
         p = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
-        assert_equals(p.labels, ['asdf'])
+        assert p.labels == ['asdf']
         assert form['labels'].value == 'asdf'
 
     @td.with_wiki
@@ -611,7 +611,7 @@ class TestProjectAdmin(TestController):
 
     def test_project_permissions(self):
         r = self.app.get('/admin/permissions/', status=302)
-        assert_in('/admin/groups', r.location)
+        assert '/admin/groups' in r.location
 
     def test_subproject_permissions(self):
         with audits('create subproject test-subproject'):
@@ -952,7 +952,7 @@ class TestProjectAdmin(TestController):
             foo_page = main_page.click(description='Foo Settings')
             url = foo_page.request.path
             assert url.endswith('/admin/ext/foo'), url
-            assert_equals('here the foo settings go', foo_page.text)
+            assert 'here the foo settings go' == foo_page.text
 
     def test_nbhd_invitations(self):
         r = self.app.get('/admin/invitations')
@@ -975,19 +975,19 @@ class TestExport(TestController):
         exportable_tools = AdminApp.exportable_tools_for(project)
         exportable_mount_points = [
             t.options.mount_point for t in exportable_tools]
-        assert_equals(exportable_mount_points, ['admin', 'wiki', 'wiki2'])
+        assert exportable_mount_points == ['admin', 'wiki', 'wiki2']
 
     def test_access(self):
         r = self.app.get('/admin/export',
                          extra_environ={'username': '*anonymous'}).follow()
-        assert_equals(r.request.url,
+        assert (r.request.url ==
                       'http://localhost/auth/?return_to=%2Fadmin%2Fexport')
         self.app.get('/admin/export',
                      extra_environ={'username': 'test-user'},
                      status=403)
         r = self.app.post('/admin/export',
                           extra_environ={'username': '*anonymous'}).follow()
-        assert_equals(r.request.url, 'http://localhost/auth/')
+        assert r.request.url == 'http://localhost/auth/'
         self.app.post('/admin/export',
                       extra_environ={'username': 'test-user'},
                       status=403)
@@ -995,22 +995,22 @@ class TestExport(TestController):
     def test_ini_option(self):
         tg.config['bulk_export_enabled'] = 'false'
         r = self.app.get('/admin/')
-        assert_not_in('Export', r)
+        assert 'Export' not in r
         r = self.app.get('/admin/export', status=404)
         tg.config['bulk_export_enabled'] = 'true'
         r = self.app.get('/admin/')
-        assert_in('Export', r)
+        assert 'Export' in r
 
     @mock.patch('allura.model.session.project_doc_session')
     def test_export_page_contains_exportable_tools(self, session):
         session.return_value = {'result': [{"total_size": 10000}]}
 
         r = self.app.get('/admin/export')
-        assert_in('Wiki</label> <a href="/p/test/wiki/">/p/test/wiki/</a>', r)
-        assert_in(
-            'Wiki2</label> <a href="/p/test/wiki2/">/p/test/wiki2/</a>', r)
-        assert_not_in(
-            'Search</label> <a href="/p/test/search/">/p/test/search/</a>', r)
+        assert 'Wiki</label> <a href="/p/test/wiki/">/p/test/wiki/</a>' in r
+        assert (
+            'Wiki2</label> <a href="/p/test/wiki2/">/p/test/wiki2/</a>' in r)
+        assert (
+            'Search</label> <a href="/p/test/search/">/p/test/search/</a>' not in r)
 
     def test_export_page_contains_hidden_tools(self):
         with mock.patch('allura.ext.search.search_main.SearchApp.exportable'):
@@ -1018,22 +1018,22 @@ class TestExport(TestController):
             exportable_tools = AdminApp.exportable_tools_for(project)
             exportable_mount_points = [
                 t.options.mount_point for t in exportable_tools]
-            assert_equals(exportable_mount_points,
+            assert (exportable_mount_points ==
                           ['admin', 'search', 'wiki', 'wiki2'])
 
     def test_tools_not_selected(self):
         r = self.app.post('/admin/export')
-        assert_in('error', self.webflash(r))
+        assert 'error' in self.webflash(r)
 
     def test_bad_tool(self):
         r = self.app.post('/admin/export', {'tools': 'search'})
-        assert_in('error', self.webflash(r))
+        assert 'error' in self.webflash(r)
 
     @mock.patch('allura.ext.admin.admin_main.export_tasks')
     @mock.patch.dict(tg.config, {'bulk_export_filename': '{project}.zip'})
     def test_selected_one_tool(self, export_tasks):
         r = self.app.post('/admin/export', {'tools': 'wiki'})
-        assert_in('ok', self.webflash(r))
+        assert 'ok' in self.webflash(r)
         export_tasks.bulk_export.post.assert_called_once_with(
             ['wiki'], 'test.zip', send_email=True, with_attachments=False)
 
@@ -1041,7 +1041,7 @@ class TestExport(TestController):
     @mock.patch.dict(tg.config, {'bulk_export_filename': '{project}.zip'})
     def test_selected_multiple_tools(self, export_tasks):
         r = self.app.post('/admin/export', {'tools': ['wiki', 'wiki2']})
-        assert_in('ok', self.webflash(r))
+        assert 'ok' in self.webflash(r)
         export_tasks.bulk_export.post.assert_called_once_with(
             ['wiki', 'wiki2'], 'test.zip', send_email=True, with_attachments=False)
 
@@ -1051,12 +1051,12 @@ class TestExport(TestController):
         session.return_value = {'result': [{"total_size": 10000}]}
         export_tasks.bulk_export.post(['wiki'])
         r = self.app.get('/admin/export')
-        assert_in('<h2>Busy</h2>', r.text)
+        assert '<h2>Busy</h2>' in r.text
 
     @td.with_user_project('test-user')
     def test_bulk_export_path_for_user_project(self):
         project = M.Project.query.get(shortname='u/test-user')
-        assert_equals(project.bulk_export_path(tg.config['bulk_export_path']),
+        assert (project.bulk_export_path(tg.config['bulk_export_path']) ==
                       '/tmp/bulk_export/u/test-user')
 
     @td.with_user_project('test-user')
@@ -1074,15 +1074,15 @@ class TestExport(TestController):
 
     def test_bulk_export_path_for_nbhd(self):
         project = M.Project.query.get(name='Home Project for Projects')
-        assert_equals(project.bulk_export_path(tg.config['bulk_export_path']), '/tmp/bulk_export/p/p')
+        assert project.bulk_export_path(tg.config['bulk_export_path']) == '/tmp/bulk_export/p/p'
 
     @mock.patch('allura.model.session.project_doc_session')
     def test_export_page_contains_check_all_checkbox(self, session):
         session.return_value = {'result': [{"total_size": 10000}]}
 
         r = self.app.get('/admin/export')
-        assert_in('<input type="checkbox" id="check-all">', r)
-        assert_in('Check All</label>', r)
+        assert '<input type="checkbox" id="check-all">' in r
+        assert 'Check All</label>' in r
 
 
 class TestRestExport(TestRestApiBase):
@@ -1091,11 +1091,11 @@ class TestRestExport(TestRestApiBase):
     def test_export_status(self, MonQTask):
         MonQTask.query.get.return_value = None
         r = self.api_get('/rest/p/test/admin/export_status')
-        assert_equals(r.json, {'status': 'ready'})
+        assert r.json == {'status': 'ready'}
 
         MonQTask.query.get.return_value = 'something'
         r = self.api_get('/rest/p/test/admin/export_status')
-        assert_equals(r.json, {'status': 'busy'})
+        assert r.json == {'status': 'busy'}
 
     @mock.patch('allura.model.project.MonQTask')
     @mock.patch('allura.ext.admin.admin_main.AdminApp.exportable_tools_for')
@@ -1105,7 +1105,7 @@ class TestRestExport(TestRestApiBase):
         exportable_tools.return_value = []
         self.api_post('/rest/p/test/admin/export',
                       tools='tickets, discussion', status=400)
-        assert_equals(bulk_export.post.call_count, 0)
+        assert bulk_export.post.call_count == 0
 
     @mock.patch('allura.model.project.MonQTask')
     @mock.patch('allura.ext.admin.admin_main.AdminApp.exportable_tools_for')
@@ -1117,7 +1117,7 @@ class TestRestExport(TestRestApiBase):
             mock.Mock(options=mock.Mock(mount_point='discussion')),
         ]
         self.api_post('/rest/p/test/admin/export', status=400)
-        assert_equals(bulk_export.post.call_count, 0)
+        assert bulk_export.post.call_count == 0
 
     @mock.patch('allura.model.project.MonQTask')
     @mock.patch('allura.ext.admin.admin_main.AdminApp.exportable_tools_for')
@@ -1130,7 +1130,7 @@ class TestRestExport(TestRestApiBase):
         ]
         self.api_post('/rest/p/test/admin/export',
                       tools='tickets, discussion', status=503)
-        assert_equals(bulk_export.post.call_count, 0)
+        assert bulk_export.post.call_count == 0
 
     @mock.patch('allura.model.project.MonQTask')
     @mock.patch('allura.ext.admin.admin_main.AdminApp.exportable_tools_for')
@@ -1144,10 +1144,10 @@ class TestRestExport(TestRestApiBase):
         ]
         r = self.api_post('/rest/p/test/admin/export',
                           tools='tickets, discussion', status=200)
-        assert_equals(r.json, {
+        assert r.json == {
             'filename': 'test.zip',
             'status': 'in progress',
-        })
+        }
         bulk_export.post.assert_called_once_with(
             ['tickets', 'discussion'], 'test.zip', send_email=False, with_attachments=False)
 
@@ -1163,8 +1163,8 @@ class TestRestInstallTool(TestRestApiBase):
             'tool': 'tickets'
         }
         r = self.api_post('/rest/p/test/admin/install_tool/', **data)
-        assert_equals(r.json['success'], False)
-        assert_equals(r.json['info'], 'All arguments required.')
+        assert r.json['success'] == False
+        assert r.json['info'] == 'All arguments required.'
 
     def test_invalid_tool(self):
         r = self.api_get('/rest/p/test/')
@@ -1177,8 +1177,8 @@ class TestRestInstallTool(TestRestApiBase):
             'mount_label': 'tickets_label1'
         }
         r = self.api_post('/rest/p/test/admin/install_tool/', **data)
-        assert_equals(r.json['success'], False)
-        assert_equals(r.json['info'],
+        assert r.json['success'] == False
+        assert (r.json['info'] ==
                       'Incorrect tool name, or limit is reached.')
 
     def test_bad_mount(self):
@@ -1192,8 +1192,8 @@ class TestRestInstallTool(TestRestApiBase):
             'mount_label': 'tickets_label1'
         }
         r = self.api_post('/rest/p/test/admin/install_tool/', **data)
-        assert_equals(r.json['success'], False)
-        assert_equals(r.json['info'],
+        assert r.json['success'] == False
+        assert (r.json['info'] ==
                       'Mount point "tickets_mount1" is invalid')
 
     def test_install_tool_ok(self):
@@ -1207,17 +1207,17 @@ class TestRestInstallTool(TestRestApiBase):
             'mount_label': 'tickets_label1'
         }
         r = self.api_post('/rest/p/test/admin/install_tool/', **data)
-        assert_equals(r.json['success'], True)
-        assert_equals(r.json['info'],
+        assert r.json['success'] == True
+        assert (r.json['info'] ==
                       'Tool %s with mount_point %s and mount_label %s was created.'
                       % ('tickets', 'ticketsmount1', 'tickets_label1'))
 
         project = M.Project.query.get(shortname='test')
-        assert_equals(project.ordered_mounts()
-                      [-1]['ac'].options.mount_point, 'ticketsmount1')
+        assert (project.ordered_mounts()
+                      [-1]['ac'].options.mount_point == 'ticketsmount1')
         audit_log = M.AuditLog.query.find(
             {'project_id': project._id}).sort('_id', -1).first()
-        assert_equals(audit_log.message, 'install tool ticketsmount1')
+        assert audit_log.message == 'install tool ticketsmount1'
 
     def test_tool_exists(self):
         with mock.patch.object(ForgeWikiApp, 'max_instances') as mi:
@@ -1235,8 +1235,8 @@ class TestRestInstallTool(TestRestApiBase):
             with h.push_config(c, user=M.User.query.get()):
                 project.install_app('wiki', mount_point=data['mount_point'])
             r = self.api_post('/rest/p/test/admin/install_tool/', **data)
-            assert_equals(r.json['success'], False)
-            assert_equals(r.json['info'], 'Mount point already exists.')
+            assert r.json['success'] == False
+            assert r.json['info'] == 'Mount point already exists.'
 
     def test_tool_installation_limit(self):
         with mock.patch.object(ForgeWikiApp, 'max_instances') as mi:
@@ -1251,13 +1251,13 @@ class TestRestInstallTool(TestRestApiBase):
                 'mount_label': 'wiki_label'
             }
             r = self.api_post('/rest/p/test/admin/install_tool/', **data)
-            assert_equals(r.json['success'], True)
+            assert r.json['success'] == True
 
             data['mount_point'] = 'wikimount1'
             data['mount_label'] = 'wiki_label1'
             r = self.api_post('/rest/p/test/admin/install_tool/', **data)
-            assert_equals(r.json['success'], False)
-            assert_equals(r.json['info'],
+            assert r.json['success'] == False
+            assert (r.json['info'] ==
                           'Incorrect tool name, or limit is reached.')
 
     def test_unauthorized(self):
@@ -1274,7 +1274,7 @@ class TestRestInstallTool(TestRestApiBase):
                           extra_environ={'username': '*anonymous'},
                           status=401,
                           params=data)
-        assert_equals(r.status, '401 Unauthorized')
+        assert r.status == '401 Unauthorized'
 
     def test_order(self):
         def get_labels():
@@ -1286,7 +1286,7 @@ class TestRestInstallTool(TestRestApiBase):
                 elif 'sub' in mount:
                     labels.append(mount['sub'].name)
             return labels
-        assert_equals(get_labels(),
+        assert (get_labels() ==
                       ['Admin', 'Search', 'Activity', 'A Subproject'])
 
         data = [
@@ -1316,39 +1316,39 @@ class TestRestInstallTool(TestRestApiBase):
         ]
         for datum in data:
             r = self.api_post('/rest/p/test/admin/install_tool/', **datum)
-            assert_equals(r.json['success'], True)
-            assert_equals(r.json['info'],
+            assert r.json['success'] == True
+            assert (r.json['info'] ==
                           'Tool %s with mount_point %s and mount_label %s was created.'
                           % (datum['tool'], datum['mount_point'], datum['mount_label']))
 
-        assert_equals(
-            get_labels(), ['t1', 'Admin', 'Search', 'Activity', 'A Subproject', 'ta', 'tb', 'tc'])
+        assert (
+            get_labels() == ['t1', 'Admin', 'Search', 'Activity', 'A Subproject', 'ta', 'tb', 'tc'])
 
 
 class TestRestAdminOptions(TestRestApiBase):
     def test_no_mount_point(self):
         r = self.api_get('/rest/p/test/admin/admin_options/', status=400)
-        assert_in('Must provide a mount point', r.text)
+        assert 'Must provide a mount point' in r.text
 
     def test_invalid_mount_point(self):
         r = self.api_get('/rest/p/test/admin/admin_options/?mount_point=asdf', status=400)
-        assert_in('The mount point you provided was invalid', r.text)
+        assert 'The mount point you provided was invalid' in r.text
 
     @td.with_tool('test', 'Git', 'git')
     def test_valid_mount_point(self):
         r = self.api_get('/rest/p/test/admin/admin_options/?mount_point=git', status=200)
-        assert_is_not_none(r.json['options'])
+        assert r.json['options'] is not None
 
 
 class TestRestMountOrder(TestRestApiBase):
     def test_no_kw(self):
         r = self.api_post('/rest/p/test/admin/mount_order/', status=400)
-        assert_in('Expected kw params in the form of "ordinal: mount_point"', r.text)
+        assert 'Expected kw params in the form of "ordinal: mount_point"' in r.text
 
     def test_invalid_kw(self):
         data = {'1': 'git', 'two': 'admin'}
         r = self.api_post('/rest/p/test/admin/mount_order/', status=400, **data)
-        assert_in('Invalid kw: expected "ordinal: mount_point"', r.text)
+        assert 'Invalid kw: expected "ordinal: mount_point"' in r.text
 
     @td.with_wiki
     def test_reorder(self):
@@ -1374,19 +1374,19 @@ class TestRestMountOrder(TestRestApiBase):
 
         # Set initial order to d1
         r = self.api_post('/rest/p/test/admin/mount_order/', **d1)
-        assert_equals(r.json['status'], 'ok')
+        assert r.json['status'] == 'ok'
 
         # Get index of sub1
         a = self.api_get('/p/test/_nav.json').json['menu'].index(tool)
 
         # Set order to d2
         r = self.api_post('/rest/p/test/admin/mount_order/', **d2)
-        assert_equals(r.json['status'], 'ok')
+        assert r.json['status'] == 'ok'
 
         # Get index of sub1 after reordering
         b = self.api_get('/p/test/_nav.json').json['menu'].index(tool)
 
-        assert_greater(b, a)
+        assert b > a
 
 
 class TestRestToolGrouping(TestRestApiBase):
@@ -1394,7 +1394,7 @@ class TestRestToolGrouping(TestRestApiBase):
         for invalid_value in ('100', 'asdf'):
             r = self.api_post('/rest/p/test/admin/configure_tool_grouping/', grouping_threshold=invalid_value,
                               status=400)
-            assert_in('Invalid threshold. Expected a value between 1 and 10', r.text)
+            assert 'Invalid threshold. Expected a value between 1 and 10' in r.text
 
     @td.with_wiki
     @td.with_tool('test', 'Wiki', 'wiki2')
@@ -1405,17 +1405,17 @@ class TestRestToolGrouping(TestRestApiBase):
 
         # The 'wiki' mount_point should not exist at the top level
         result1 = self.app.get('/p/test/_nav.json')
-        assert_not_in('wiki', [tool['mount_point'] for tool in result1.json['menu']])
+        assert 'wiki' not in [tool['mount_point'] for tool in result1.json['menu']]
 
         # Set threshold to 3
         r = self.api_post('/rest/p/test/admin/configure_tool_grouping/', grouping_threshold='3', status=200)
 
         # The wiki mount_point should now be at the top level of the menu
         result2 = self.app.get('/p/test/_nav.json')
-        assert_in('wiki', [tool['mount_point'] for tool in result2.json['menu']])
+        assert 'wiki' in [tool['mount_point'] for tool in result2.json['menu']]
 
 
 class TestInstallableTools(TestRestApiBase):
     def test_installable_tools_response(self):
         r = self.api_get('/rest/p/test/admin/installable_tools', status=200)
-        assert_in('External Link', [tool['tool_label'] for tool in r.json['tools']])
+        assert 'External Link' in [tool['tool_label'] for tool in r.json['tools']]
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index c6b8fae11..9a1537877 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -78,7 +78,7 @@ class TestAuth(TestController):
                 username='test-user', password='foo',
                 _session_id=self.app.cookies['_session_id']),
                 antispam=True).follow()
-            assert_equal(r.headers['Location'], 'http://localhost/dashboard')
+            assert r.headers['Location'] == 'http://localhost/dashboard'
 
         r = self.app.post('/auth/do_login', antispam=True, params=dict(
             username='test-user', password='foo', honey1='robot',  # bad honeypot value
@@ -86,8 +86,8 @@ class TestAuth(TestController):
                           extra_environ={'regular_antispam_err_handling_even_when_tests': 'true'},
                           status=302)
         wf = json.loads(self.webflash(r))
-        assert_equal(wf['status'], 'error')
-        assert_equal(wf['message'], 'Spambot protection engaged')
+        assert wf['status'] == 'error'
+        assert wf['message'] == 'Spambot protection engaged'
 
         with audits('Failed login', user=True):
             r = self.app.post('/auth/do_login', antispam=True, params=dict(
@@ -135,8 +135,8 @@ class TestAuth(TestController):
                                     'regular_antispam_err_handling_even_when_tests': 'true'},
                      status=302)
         wf = json.loads(self.webflash(r))
-        assert_equal(wf['status'], 'error')
-        assert_equal(wf['message'], 'Spambot protection engaged')
+        assert wf['status'] == 'error'
+        assert wf['message'] == 'Spambot protection engaged'
 
     @patch('allura.lib.plugin.AuthenticationProvider.hibp_password_check_enabled', Mock(return_value=True))
     @patch('allura.tasks.mail_tasks.sendsimplemail')
@@ -156,11 +156,11 @@ class TestAuth(TestController):
         r.mustcontain('reset your password via email.<br>\nPlease check your email')
 
         args, kwargs = sendsimplemail.post.call_args
-        assert_equal(sendsimplemail.post.call_count, 1)
-        assert_equal(kwargs['subject'], 'Update your %s password' % config['site_name'])
-        assert_in('/auth/forgotten_password/', kwargs['text'])
+        assert sendsimplemail.post.call_count == 1
+        assert kwargs['subject'] == 'Update your %s password' % config['site_name']
+        assert '/auth/forgotten_password/' in kwargs['text']
 
-        assert_equal([], M.UserLoginDetails.query.find().all())  # no records created
+        assert [] == M.UserLoginDetails.query.find().all()  # no records created
 
     @patch('allura.tasks.mail_tasks.sendsimplemail')
     def test_login_hibp_compromised_password_trusted_client(self, sendsimplemail):
@@ -189,8 +189,8 @@ class TestAuth(TestController):
                 r = f.submit(status=302)
 
             assert r.session.get('pwd-expired')
-            assert_equal(r.session.get('expired-reason'), 'hibp')
-            assert_equal(r.location, 'http://localhost/auth/pwd_expired')
+            assert r.session.get('expired-reason') == 'hibp'
+            assert r.location == 'http://localhost/auth/pwd_expired'
 
             r = r.follow()
             r.mustcontain('must be updated to be more secure')
@@ -241,19 +241,19 @@ class TestAuth(TestController):
 
         logged_in_session = r.session['_id']
         links = r.html.find(*nav_pattern).findAll('a')
-        assert_equal(links[-1].string, "Log Out")
+        assert links[-1].string == "Log Out"
 
         r = self.app.get('/auth/logout').follow().follow()
         logged_out_session = r.session['_id']
         assert logged_in_session is not logged_out_session
         links = r.html.find(*nav_pattern).findAll('a')
-        assert_equal(links[-1].string, 'Log In')
+        assert links[-1].string == 'Log In'
 
     def test_track_login(self):
         user = M.User.by_username('test-user')
-        assert_equal(user.last_access['login_date'], None)
-        assert_equal(user.last_access['login_ip'], None)
-        assert_equal(user.last_access['login_ua'], None)
+        assert user.last_access['login_date'] == None
+        assert user.last_access['login_ip'] == None
+        assert user.last_access['login_ua'] == None
 
         self.app.get('/').follow()  # establish session
         self.app.post('/auth/do_login',
@@ -267,9 +267,9 @@ class TestAuth(TestController):
                       antispam=True,
                       )
         user = M.User.by_username('test-user')
-        assert_not_equal(user.last_access['login_date'], None)
-        assert_equal(user.last_access['login_ip'], '127.0.0.1')
-        assert_equal(user.last_access['login_ua'], 'browser')
+        assert user.last_access['login_date'] != None
+        assert user.last_access['login_ip'] == '127.0.0.1'
+        assert user.last_access['login_ua'] == 'browser'
 
     def test_rememberme(self):
         username = M.User.query.get(username='test-user').username
@@ -281,24 +281,24 @@ class TestAuth(TestController):
             username='test-user', password='foo',
             _session_id=self.app.cookies['_session_id'],
         ), antispam=True)
-        assert_equal(r.session['username'], username)
-        assert_equal(r.session['login_expires'], True)
+        assert r.session['username'] == username
+        assert r.session['login_expires'] == True
 
         for header, contents in r.headerlist:
             if header == 'Set-cookie':
-                assert_not_in('expires', contents)
+                assert 'expires' not in contents
 
         # Login as test-user with remember me checkbox on
         r = self.app.post('/auth/do_login', params=dict(
             username='test-user', password='foo', rememberme='on',
             _session_id=self.app.cookies['_session_id'],
         ), antispam=True)
-        assert_equal(r.session['username'], username)
-        assert_not_equal(r.session['login_expires'], True)
+        assert r.session['username'] == username
+        assert r.session['login_expires'] != True
 
         for header, contents in r.headerlist:
             if header == 'Set-cookie':
-                assert_in('expires', contents)
+                assert 'expires' in contents
 
     @td.with_user_project('test-admin')
     def test_user_can_not_claim_duplicate_emails(self):
@@ -528,25 +528,25 @@ class TestAuth(TestController):
         # logged out, gets redirected to login page
         r = self.app.get('/auth/verify_addr', params=dict(a=email.nonce),
                          extra_environ=dict(username='*anonymous'))
-        assert_in('/auth/?return_to=%2Fauth%2Fverify_addr', r.location)
+        assert '/auth/?return_to=%2Fauth%2Fverify_addr' in r.location
 
         # logged in as someone else
         r = self.app.get('/auth/verify_addr', params=dict(a=email.nonce),
                          extra_environ=dict(username='test-admin'))
-        assert_in('/auth/?return_to=%2Fauth%2Fverify_addr', r.location)
-        assert_equal('You must be logged in to the correct account', json.loads(self.webflash(r))['message'])
-        assert_equal('warning', json.loads(self.webflash(r))['status'])
+        assert '/auth/?return_to=%2Fauth%2Fverify_addr' in r.location
+        assert 'You must be logged in to the correct account' == json.loads(self.webflash(r))['message']
+        assert 'warning' == json.loads(self.webflash(r))['status']
 
         # logged in as correct user
         r = self.app.get('/auth/verify_addr', params=dict(a=email.nonce),
                          extra_environ=dict(username='test-user'))
-        assert_in('confirmed', json.loads(self.webflash(r))['message'])
-        assert_equal('ok', json.loads(self.webflash(r))['status'])
+        assert 'confirmed' in json.loads(self.webflash(r))['message']
+        assert 'ok' == json.loads(self.webflash(r))['status']
 
         # assert 'email added' notification email sent
         args, kwargs = sendsimplemail.post.call_args
-        assert_equal(kwargs['toaddr'], user._id)
-        assert_equal(kwargs['subject'], 'New Email Address Added')
+        assert kwargs['toaddr'] == user._id
+        assert kwargs['subject'] == 'New Email Address Added'
 
     @staticmethod
     def _create_password_reset_hash():
@@ -564,8 +564,8 @@ class TestAuth(TestController):
         session(user).flush(user)
 
         hash_expiry = user.get_tool_data('AuthPasswordReset', 'hash_expiry')
-        assert_equal(hash, 'generated_hash_value')
-        assert_equal(hash_expiry, '04-08-2020')
+        assert hash == 'generated_hash_value'
+        assert hash_expiry == '04-08-2020'
         return user
 
     def test_token_generator(self):
@@ -609,8 +609,8 @@ class TestAuth(TestController):
 
         u = M.User.by_username('test-admin')
         print(u.get_tool_data('AuthPasswordReset', 'hash'))
-        assert_equal(u.get_tool_data('AuthPasswordReset', 'hash'), '')
-        assert_equal(u.get_tool_data('AuthPasswordReset', 'hash_expiry'), '')
+        assert u.get_tool_data('AuthPasswordReset', 'hash') == ''
+        assert u.get_tool_data('AuthPasswordReset', 'hash_expiry') == ''
 
     @td.with_user_project('test-admin')
     def test_change_password(self):
@@ -631,17 +631,17 @@ class TestAuth(TestController):
                           })
 
         # Confirm password was changed.
-        assert_not_equal(old_pass, user.get_pref('password'))
+        assert old_pass != user.get_pref('password')
 
         # Confirm any existing tokens were reset.
-        assert_equal(user.get_tool_data('AuthPasswordReset', 'hash'), '')
-        assert_equal(user.get_tool_data('AuthPasswordReset', 'hash_expiry'), '')
+        assert user.get_tool_data('AuthPasswordReset', 'hash') == ''
+        assert user.get_tool_data('AuthPasswordReset', 'hash_expiry') == ''
 
         # Confirm an email was sent
         tasks = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendsimplemail')).all()
-        assert_equal(len(tasks), 1)
-        assert_equal(tasks[0].kwargs['subject'], 'Password Changed')
-        assert_in('The password for your', tasks[0].kwargs['text'])
+        assert len(tasks) == 1
+        assert tasks[0].kwargs['subject'] == 'Password Changed'
+        assert 'The password for your' in tasks[0].kwargs['text']
 
     @patch('allura.lib.plugin.AuthenticationProvider.hibp_password_check_enabled', Mock(return_value=True))
     @td.with_user_project('test-admin')
@@ -675,7 +675,7 @@ class TestAuth(TestController):
 
         # Confirm password was changed.
         user = M.User.by_username('test-admin')
-        assert_not_equal(old_pass, user.get_pref('password'))
+        assert old_pass != user.get_pref('password')
 
     @patch('allura.tasks.mail_tasks.sendsimplemail')
     @patch('allura.lib.helpers.gen_message_id')
@@ -686,7 +686,7 @@ class TestAuth(TestController):
         # check preconditions of test data
         assert 'test@example.com' not in r
         assert 'test-admin@users.localhost' in r
-        assert_equal(M.User.query.get(username='test-admin').get_pref('email_address'),
+        assert (M.User.query.get(username='test-admin').get_pref('email_address') ==
                      'test-admin@users.localhost')
 
         # add test@example
@@ -704,7 +704,7 @@ class TestAuth(TestController):
         r = self.app.get('/auth/preferences/')
         assert 'test@example.com' in r
         user = M.User.query.get(username='test-admin')
-        assert_equal(user.get_pref('email_address'), 'test-admin@users.localhost')
+        assert user.get_pref('email_address') == 'test-admin@users.localhost'
 
         # remove test-admin@users.localhost
         with td.audits('Email address deleted: test-admin@users.localhost', user=True):
@@ -723,14 +723,14 @@ class TestAuth(TestController):
 
         # assert 'email_removed' notification email sent
         args, kwargs = sendsimplemail.post.call_args
-        assert_equal(kwargs['toaddr'], user._id)
-        assert_equal(kwargs['subject'], 'Email Address Removed')
+        assert kwargs['toaddr'] == user._id
+        assert kwargs['subject'] == 'Email Address Removed'
 
         r = self.app.get('/auth/preferences/')
         assert 'test-admin@users.localhost' not in r
         # preferred address has not changed if email is not verified
         user = M.User.query.get(username='test-admin')
-        assert_equal(user.get_pref('email_address'), None)
+        assert user.get_pref('email_address') == None
 
         with td.audits('Display Name changed Test Admin => Admin', user=True):
             r = self.app.post('/auth/preferences/update',
@@ -754,22 +754,22 @@ class TestAuth(TestController):
         r = self.app.post('/auth/preferences/update_emails',
                           params=new_email_params,
                           extra_environ=dict(username='test-admin'))
-        assert_in('You must provide your current password to claim new email', self.webflash(r))
-        assert_not_in('test@example.com', r.follow())
+        assert 'You must provide your current password to claim new email' in self.webflash(r)
+        assert 'test@example.com' not in r.follow()
         new_email_params['password'] = 'bad pass'
 
         r = self.app.post('/auth/preferences/update_emails',
                           params=new_email_params,
                           extra_environ=dict(username='test-admin'))
-        assert_in('You must provide your current password to claim new email', self.webflash(r))
-        assert_not_in('test@example.com', r.follow())
+        assert 'You must provide your current password to claim new email' in self.webflash(r)
+        assert 'test@example.com' not in r.follow()
         new_email_params['password'] = 'foo'  # valid password
 
         r = self.app.post('/auth/preferences/update_emails',
                           params=new_email_params,
                           extra_environ=dict(username='test-admin'))
-        assert_not_in('You must provide your current password to claim new email', self.webflash(r))
-        assert_in('test@example.com', r.follow())
+        assert 'You must provide your current password to claim new email' not in self.webflash(r)
+        assert 'test@example.com' in r.follow()
 
         # Change primary address
         change_primary_params = {
@@ -780,28 +780,28 @@ class TestAuth(TestController):
         r = self.app.post('/auth/preferences/update_emails',
                           params=change_primary_params,
                           extra_environ=dict(username='test-admin'))
-        assert_in('You must provide your current password to change primary address', self.webflash(r))
-        assert_equal(M.User.by_username('test-admin').get_pref('email_address'), 'test-admin@users.localhost')
+        assert 'You must provide your current password to change primary address' in self.webflash(r)
+        assert M.User.by_username('test-admin').get_pref('email_address') == 'test-admin@users.localhost'
         change_primary_params['password'] = 'bad pass'
 
         r = self.app.post('/auth/preferences/update_emails',
                           params=change_primary_params,
                           extra_environ=dict(username='test-admin'))
-        assert_in('You must provide your current password to change primary address', self.webflash(r))
-        assert_equal(M.User.by_username('test-admin').get_pref('email_address'), 'test-admin@users.localhost')
+        assert 'You must provide your current password to change primary address' in self.webflash(r)
+        assert M.User.by_username('test-admin').get_pref('email_address') == 'test-admin@users.localhost'
         change_primary_params['password'] = 'foo'  # valid password
 
         self.app.get('/auth/preferences/')  # let previous 'flash' message cookie get used up
         r = self.app.post('/auth/preferences/update_emails',
                           params=change_primary_params,
                           extra_environ=dict(username='test-admin'))
-        assert_not_in('You must provide your current password to change primary address', self.webflash(r))
-        assert_equal(M.User.by_username('test-admin').get_pref('email_address'), 'test@example.com')
+        assert 'You must provide your current password to change primary address' not in self.webflash(r)
+        assert M.User.by_username('test-admin').get_pref('email_address') == 'test@example.com'
 
         # assert 'email added' notification email sent using original primary addr
         args, kwargs = sendsimplemail.post.call_args
-        assert_equal(kwargs['toaddr'], 'test-admin@users.localhost')
-        assert_equal(kwargs['subject'], 'Primary Email Address Changed')
+        assert kwargs['toaddr'] == 'test-admin@users.localhost'
+        assert kwargs['subject'] == 'Primary Email Address Changed'
 
         # Remove email
         remove_email_params = {
@@ -815,20 +815,20 @@ class TestAuth(TestController):
         r = self.app.post('/auth/preferences/update_emails',
                           params=remove_email_params,
                           extra_environ=dict(username='test-admin'))
-        assert_in('You must provide your current password to delete an email', self.webflash(r))
-        assert_in('test@example.com', r.follow())
+        assert 'You must provide your current password to delete an email' in self.webflash(r)
+        assert 'test@example.com' in r.follow()
         remove_email_params['password'] = 'bad pass'
         r = self.app.post('/auth/preferences/update_emails',
                           params=remove_email_params,
                           extra_environ=dict(username='test-admin'))
-        assert_in('You must provide your current password to delete an email', self.webflash(r))
-        assert_in('test@example.com', r.follow())
+        assert 'You must provide your current password to delete an email' in self.webflash(r)
+        assert 'test@example.com' in r.follow()
         remove_email_params['password'] = 'foo'  # vallid password
         r = self.app.post('/auth/preferences/update_emails',
                           params=remove_email_params,
                           extra_environ=dict(username='test-admin'))
-        assert_not_in('You must provide your current password to delete an email', self.webflash(r))
-        assert_not_in('test@example.com', r.follow())
+        assert 'You must provide your current password to delete an email' not in self.webflash(r)
+        assert 'test@example.com' not in r.follow()
 
     @td.with_user_project('test-admin')
     def test_prefs_subscriptions(self):
@@ -936,10 +936,10 @@ class TestAuth(TestController):
         r = self.app.post('/auth/save_new',
                           params=dict(username='AAA', pw='123',
                                       _session_id=self.app.cookies['_session_id']))
-        assert_in('Enter a value 6 characters long or more', r)
-        assert_in('Usernames must include only small letters, numbers, '
+        assert 'Enter a value 6 characters long or more' in r
+        assert ('Usernames must include only small letters, numbers, '
                   'and dashes. They must also start with a letter and be '
-                  'at least 3 characters long.', r)
+                  'at least 3 characters long.' in r)
         r = self.app.post(
             '/auth/save_new',
             params=dict(
@@ -983,7 +983,7 @@ class TestAuth(TestController):
                 ))
             user = M.User.query.get(username='aaa')
             assert not user.pending
-            assert_equal(M.Project.query.find({'name': 'u/aaa'}).count(), 1)
+            assert M.Project.query.find({'name': 'u/aaa'}).count() == 1
         with h.push_config(config, **{'auth.require_email_addr': 'true'}):
             self.app.post(
                 '/auth/save_new',
@@ -997,7 +997,7 @@ class TestAuth(TestController):
                 ))
             user = M.User.query.get(username='bbb')
             assert user.pending
-            assert_equal(M.Project.query.find({'name': 'u/bbb'}).count(), 0)
+            assert M.Project.query.find({'name': 'u/bbb'}).count() == 0
 
     def test_verify_email(self):
         with h.push_config(config, **{'auth.require_email_addr': 'true'}):
@@ -1022,7 +1022,7 @@ class TestAuth(TestController):
             assert not user.pending
             assert em.confirmed
             assert user.get_pref('email_address')
-            assert_equal(M.Project.query.find({'name': 'u/aaa'}).count(), 1)
+            assert M.Project.query.find({'name': 'u/aaa'}).count() == 1
 
     def test_create_account_disabled_header_link(self):
         with h.push_config(config, **{'auth.allow_user_registration': 'false'}):
@@ -1083,7 +1083,7 @@ class TestAuth(TestController):
         assert not user.disabled
         r = self.app.get('/p/test/admin/',
                          extra_environ={'username': 'test-admin'})
-        assert_equal(r.status_int, 200, 'Redirect to %s' % r.location)
+        assert r.status_int == 200, 'Redirect to %s' % r.location
         user.disabled = True
         sess.save(user)
         sess.flush()
@@ -1091,8 +1091,8 @@ class TestAuth(TestController):
         assert user.disabled
         r = self.app.get('/p/test/admin/',
                          extra_environ={'username': 'test-admin'})
-        assert_equal(r.status_int, 302)
-        assert_equal(r.location, 'http://localhost/auth/?return_to=%2Fp%2Ftest%2Fadmin%2F')
+        assert r.status_int == 302
+        assert r.location == 'http://localhost/auth/?return_to=%2Fp%2Ftest%2Fadmin%2F'
 
     def test_no_open_return_to(self):
         r = self.app.get('/auth/logout').follow().follow()
@@ -1102,28 +1102,28 @@ class TestAuth(TestController):
             _session_id=self.app.cookies['_session_id']),
             antispam=True
         )
-        assert_equal(r.location, 'http://localhost/foo')
+        assert r.location == 'http://localhost/foo'
 
         r = self.app.get('/auth/logout')
         r = self.app.post('/auth/do_login', antispam=True, params=dict(
             username='test-user', password='foo',
             return_to='http://localhost/foo',
             _session_id=self.app.cookies['_session_id']))
-        assert_equal(r.location, 'http://localhost/foo')
+        assert r.location == 'http://localhost/foo'
 
         r = self.app.get('/auth/logout')
         r = self.app.post('/auth/do_login', antispam=True, params=dict(
             username='test-user', password='foo',
             return_to='http://example.com/foo',
             _session_id=self.app.cookies['_session_id'])).follow()
-        assert_equal(r.location, 'http://localhost/dashboard')
+        assert r.location == 'http://localhost/dashboard'
 
         r = self.app.get('/auth/logout')
         r = self.app.post('/auth/do_login', antispam=True, params=dict(
             username='test-user', password='foo',
             return_to='//example.com/foo',
             _session_id=self.app.cookies['_session_id'])).follow()
-        assert_equal(r.location, 'http://localhost/dashboard')
+        assert r.location == 'http://localhost/dashboard'
 
     def test_no_injected_headers_in_return_to(self):
         r = self.app.get('/auth/logout').follow().follow()
@@ -1134,28 +1134,28 @@ class TestAuth(TestController):
             _session_id=self.app.cookies['_session_id']),
             antispam=True
         )
-        assert_equal(r.location, 'http://localhost/')
-        assert_not_equal(r.content_length, 777)
+        assert r.location == 'http://localhost/'
+        assert r.content_length != 777
 
 
 class TestAuthRest(TestRestApiBase):
 
     def test_tools_list_anon(self):
         resp = self.api_get('/rest/auth/tools/wiki', user='*anonymous')
-        assert_equal(resp.json, {
+        assert resp.json == {
             'tools': []
-        })
+        }
 
     def test_tools_list_invalid_tool(self):
         resp = self.api_get('/rest/auth/tools/af732q9547235')
-        assert_equal(resp.json, {
+        assert resp.json == {
             'tools': []
-        })
+        }
 
     @td.with_tool('test', 'Wiki', mount_point='docs', mount_label='Documentation')
     def test_tools_list_wiki(self):
         resp = self.api_get('/rest/auth/tools/wiki')
-        assert_equal(resp.json, {
+        assert resp.json == {
             'tools': [
                 {
                     'mount_label': 'Wiki',
@@ -1174,7 +1174,7 @@ class TestAuthRest(TestRestApiBase):
                     'api_url': 'http://localhost/rest/p/test/docs/',
                 },
             ]
-        })
+        }
 
 
 class TestPreferences(TestController):
@@ -1257,8 +1257,8 @@ class TestPreferences(TestController):
                                   ))
         user = M.User.query.get(username='test-admin')
         assert len(user.socialnetworks) == 1
-        assert_equal(user.socialnetworks[0].socialnetwork, socialnetwork)
-        assert_equal(user.socialnetworks[0].accounturl, accounturl)
+        assert user.socialnetworks[0].socialnetwork == socialnetwork
+        assert user.socialnetworks[0].accounturl == accounturl
 
         # Add second social network account
         socialnetwork2 = 'Twitter'
@@ -1270,8 +1270,8 @@ class TestPreferences(TestController):
                                   ))
         user = M.User.query.get(username='test-admin')
         assert len(user.socialnetworks) == 2
-        assert_in({'socialnetwork': socialnetwork, 'accounturl': accounturl}, user.socialnetworks)
-        assert_in({'socialnetwork': socialnetwork2, 'accounturl': accounturl2}, user.socialnetworks)
+        assert {'socialnetwork': socialnetwork, 'accounturl': accounturl} in user.socialnetworks
+        assert {'socialnetwork': socialnetwork2, 'accounturl': accounturl2} in user.socialnetworks
 
         # Remove first social network account
         self.app.post('/auth/user_info/contacts/remove_social_network',
@@ -1281,7 +1281,7 @@ class TestPreferences(TestController):
                                   ))
         user = M.User.query.get(username='test-admin')
         assert len(user.socialnetworks) == 1
-        assert_in({'socialnetwork': socialnetwork2, 'accounturl': accounturl2}, user.socialnetworks)
+        assert {'socialnetwork': socialnetwork2, 'accounturl': accounturl2} in user.socialnetworks
 
         # Add empty social network account
         self.app.post('/auth/user_info/contacts/add_social_network',
@@ -1290,7 +1290,7 @@ class TestPreferences(TestController):
                                   ))
         user = M.User.query.get(username='test-admin')
         assert len(user.socialnetworks) == 1
-        assert_in({'socialnetwork': socialnetwork2, 'accounturl': accounturl2}, user.socialnetworks)
+        assert {'socialnetwork': socialnetwork2, 'accounturl': accounturl2} in user.socialnetworks
 
         # Add invalid social network account
         self.app.post('/auth/user_info/contacts/add_social_network',
@@ -1299,7 +1299,7 @@ class TestPreferences(TestController):
                                   ))
         user = M.User.query.get(username='test-admin')
         assert len(user.socialnetworks) == 1
-        assert_in({'socialnetwork': socialnetwork2, 'accounturl': accounturl2}, user.socialnetworks)
+        assert {'socialnetwork': socialnetwork2, 'accounturl': accounturl2} in user.socialnetworks
 
         # Add telephone number
         telnumber = '+3902123456'
@@ -1387,8 +1387,8 @@ class TestPreferences(TestController):
         user = M.User.query.get(username='test-admin')
         timeslot2dict = dict(week_day=weekday2, start_time=starttime2, end_time=endtime2)
         assert len(user.availability) == 2
-        assert_in(timeslot1dict, user.get_availability_timeslots())
-        assert_in(timeslot2dict, user.get_availability_timeslots())
+        assert timeslot1dict in user.get_availability_timeslots()
+        assert timeslot2dict in user.get_availability_timeslots()
 
         # Remove availability timeslot
         r = self.app.post('/auth/user_info/availability/remove_timeslot',
@@ -1444,8 +1444,8 @@ class TestPreferences(TestController):
         user = M.User.query.get(username='test-admin')
         period2dict = dict(start_date=startdate2, end_date=enddate2)
         assert len(user.inactiveperiod) == 2
-        assert_in(period1dict, user.get_inactive_periods())
-        assert_in(period2dict, user.get_inactive_periods())
+        assert period1dict in user.get_inactive_periods()
+        assert period2dict in user.get_inactive_periods()
 
         # Remove first inactivity period
         r = self.app.post(
@@ -1556,7 +1556,7 @@ class TestPreferences(TestController):
         with mock.patch.object(plugin.UserPreferencesProvider, 'get') as upp_get:
             upp_get.return_value = MyPP()
             r = self.app.get('/auth/new_page')
-            assert_equal(r.text, 'new page')
+            assert r.text == 'new page'
             self.app.get('/auth/not_page', status=404)
 
 
@@ -1622,7 +1622,7 @@ class TestPasswordReset(TestController):
             hash = user.get_tool_data('AuthPasswordReset', 'hash')
             assert hash is not None
             args, kwargs = sendmail.post.call_args
-            assert_equal(kwargs['toaddr'], self.test_primary_email)
+            assert kwargs['toaddr'] == self.test_primary_email
 
     @patch('allura.tasks.mail_tasks.sendsimplemail')
     @patch('allura.lib.helpers.gen_message_id')
@@ -1642,7 +1642,7 @@ class TestPasswordReset(TestController):
             hash = user.get_tool_data('AuthPasswordReset', 'hash')
             assert hash is not None
             args, kwargs = sendmail.post.call_args
-            assert_equal(kwargs['toaddr'], email1.email)
+            assert kwargs['toaddr'] == email1.email
 
     @patch('allura.tasks.mail_tasks.sendsimplemail')
     @patch('allura.lib.helpers.gen_message_id')
@@ -1682,9 +1682,9 @@ To update your password on %s, please visit the following URL:
 
         # load reset form and fill it out
         r = self.app.get('/auth/forgotten_password/%s' % hash)
-        assert_in('Enter a new password for: test-admin', r)
-        assert_in('New Password:', r)
-        assert_in('New Password (again):', r)
+        assert 'Enter a new password for: test-admin' in r
+        assert 'New Password:' in r
+        assert 'New Password (again):' in r
         form = r.forms[0]
         form['pw'] = form['pw2'] = new_password = '154321'
         with td.audits(r'Password changed \(through recovery process\)', user=True):
@@ -1693,21 +1693,21 @@ To update your password on %s, please visit the following URL:
 
         # verify 'Password Changed' email sent
         args, kwargs = sendsimplemail.post.call_args
-        assert_equal(kwargs['toaddr'], user._id)
-        assert_equal(kwargs['subject'], 'Password Changed')
+        assert kwargs['toaddr'] == user._id
+        assert kwargs['subject'] == 'Password Changed'
 
         # confirm password changed and works
         user = M.User.query.get(username='test-admin')
-        assert_not_equal(old_pw_hash, user.password)
+        assert old_pw_hash != user.password
         provider = plugin.LocalAuthenticationProvider(None)
-        assert_true(provider._validate_password(user, new_password))
+        assert provider._validate_password(user, new_password)
 
         # confirm reset fields cleared
         user = M.User.query.get(username='test-admin')
         hash = user.get_tool_data('AuthPasswordReset', 'hash')
         hash_expiry = user.get_tool_data('AuthPasswordReset', 'hash_expiry')
-        assert_equal(hash, '')
-        assert_equal(hash_expiry, '')
+        assert hash == ''
+        assert hash_expiry == ''
 
         # confirm can log in now in same session
         r = r.follow()
@@ -1737,16 +1737,16 @@ To update your password on %s, please visit the following URL:
         user.set_tool_data('AuthPasswordReset',
                            hash_expiry=datetime(2000, 10, 10))
         r = self.app.get('/auth/forgotten_password/%s' % hash.encode('utf-8'))
-        assert_in('Unable to process reset, please try again', r.follow().text)
+        assert 'Unable to process reset, please try again' in r.follow().text
         r = self.app.post('/auth/set_new_password/%s' %
                           hash.encode('utf-8'), {'pw': '154321', 'pw2': '154321',
                                                  '_session_id': self.app.cookies['_session_id'],
                                                  })
-        assert_in('Unable to process reset, please try again', r.follow().text)
+        assert 'Unable to process reset, please try again' in r.follow().text
 
     def test_hash_invalid(self):
         r = self.app.get('/auth/forgotten_password/123412341234', status=302)
-        assert_in('Unable to process reset, please try again', r.follow().text)
+        assert 'Unable to process reset, please try again' in r.follow().text
 
     @patch('allura.lib.plugin.AuthenticationProvider')
     def test_provider_disabled(self, AP):
@@ -1797,7 +1797,7 @@ To update your password on %s, please visit the following URL:
         # confirm password changed and works
         user = M.User.query.get(username='test-admin')
         provider = plugin.LocalAuthenticationProvider(None)
-        assert_true(provider._validate_password(user, new_password))
+        assert provider._validate_password(user, new_password)
 
         # confirm can log in now in same session
         r = r.follow()
@@ -1821,7 +1821,7 @@ class TestOAuth(TestController):
                                   }).follow()
         assert 'oautstapp' in r
         # deregister
-        assert_equal(r.forms[0].action, 'deregister')
+        assert r.forms[0].action == 'deregister'
         r.forms[0].submit()
         r = self.app.get('/auth/oauth/')
         assert 'oautstapp' not in r
@@ -1834,24 +1834,24 @@ class TestOAuth(TestController):
                                   '_session_id': self.app.cookies['_session_id'],
                                   }, status=302)
         r = self.app.get('/auth/oauth/')
-        assert_equal(r.forms[1].action, 'generate_access_token')
+        assert r.forms[1].action == 'generate_access_token'
         r = r.forms[1].submit(extra_environ={'username': 'test-user'})  # not the right user
-        assert_in("Invalid app ID", self.webflash(r))                   # gets an error
+        assert "Invalid app ID" in self.webflash(r)                   # gets an error
         r = self.app.get('/auth/oauth/')                                # do it again
         r = r.forms[1].submit()                                         # as correct user
-        assert_equal('', self.webflash(r))
+        assert '' == self.webflash(r)
 
         r = self.app.get('/auth/oauth/')
         assert 'Bearer Token:' in r
-        assert_not_equal(
-            M.OAuthAccessToken.for_user(M.User.by_username('test-admin')), [])
+        assert (
+            M.OAuthAccessToken.for_user(M.User.by_username('test-admin')) != [])
         # revoke
-        assert_equal(r.forms[0].action, 'revoke_access_token')
+        assert r.forms[0].action == 'revoke_access_token'
         r.forms[0].submit()
         r = self.app.get('/auth/oauth/')
-        assert_not_equal(r.forms[0].action, 'revoke_access_token')
-        assert_equal(
-            M.OAuthAccessToken.for_user(M.User.by_username('test-admin')), [])
+        assert r.forms[0].action != 'revoke_access_token'
+        assert (
+            M.OAuthAccessToken.for_user(M.User.by_username('test-admin')) == [])
 
     def test_interactive(self):
         with mock.patch('allura.controllers.rest.oauth.Server') as Server, \
@@ -1881,8 +1881,8 @@ class TestOAuth(TestController):
             }
             r = self.app.get('/rest/oauth/access_token')
             atok = parse_qs(r.text)
-            assert_equal(len(atok['oauth_token']), 1)
-            assert_equal(len(atok['oauth_token_secret']), 1)
+            assert len(atok['oauth_token']) == 1
+            assert len(atok['oauth_token_secret']) == 1
 
         # now use the tokens & secrets to make a full OAuth request:
         oauth_secret = atok['oauth_token_secret'][0]
@@ -1918,18 +1918,18 @@ class TestOAuth(TestController):
         call = Request.from_request.call_args_list[0]
         call[1]['headers'] = dict(call[1]['headers'])
         # then check equality
-        assert_equal(Request.from_request.call_args_list, [
+        assert Request.from_request.call_args_list == [
             mock.call('POST', 'http://localhost/rest/oauth/request_token',
                       headers={'Host': 'localhost:80',
                                'Content-Type': 'application/x-www-form-urlencoded',
                                'Content-Length': '9'},
                       parameters={'key': 'value'},
                       query_string='')
-        ])
+        ]
         Server().verify_request.assert_called_once_with(req, consumer_token.consumer, None)
         request_token = M.OAuthRequestToken.query.get(consumer_token_id=consumer_token._id)
-        assert_is_not_none(request_token)
-        assert_equal(r.text, request_token.to_string())
+        assert request_token is not None
+        assert r.text == request_token.to_string()
 
     @mock.patch('allura.controllers.rest.oauth.Server')
     @mock.patch('allura.controllers.rest.oauth.Request')
@@ -1973,8 +1973,8 @@ class TestOAuth(TestController):
         )
         ThreadLocalORMSession.flush_all()
         r = self.app.post('/rest/oauth/authorize', params={'oauth_token': 'api_key'})
-        assert_in('ctok_desc', r.text)
-        assert_in('api_key', r.text)
+        assert 'ctok_desc' in r.text
+        assert 'api_key' in r.text
 
     def test_authorize_invalid(self):
         self.app.post('/rest/oauth/authorize', params={'oauth_token': 'api_key'}, status=401)
@@ -1995,7 +1995,7 @@ class TestOAuth(TestController):
         ThreadLocalORMSession.flush_all()
         self.app.post('/rest/oauth/do_authorize',
                       params={'no': '1', 'oauth_token': 'api_key'})
-        assert_is_none(M.OAuthRequestToken.query.get(api_key='api_key'))
+        assert M.OAuthRequestToken.query.get(api_key='api_key') is None
 
     def test_do_authorize_oob(self):
         user = M.User.by_username('test-admin')
@@ -2012,7 +2012,7 @@ class TestOAuth(TestController):
         )
         ThreadLocalORMSession.flush_all()
         r = self.app.post('/rest/oauth/do_authorize', params={'yes': '1', 'oauth_token': 'api_key'})
-        assert_is_not_none(r.html.find(text=re.compile('^PIN: ')))
+        assert r.html.find(text=re.compile('^PIN: ')) is not None
 
     def test_do_authorize_cb(self):
         user = M.User.by_username('test-admin')
@@ -2145,8 +2145,8 @@ class TestOAuth(TestController):
         ThreadLocalORMSession.flush_all()
         r = self.app.get('/rest/oauth/access_token')
         atok = parse_qs(r.text)
-        assert_equal(len(atok['oauth_token']), 1)
-        assert_equal(len(atok['oauth_token_secret']), 1)
+        assert len(atok['oauth_token']) == 1
+        assert len(atok['oauth_token_secret']) == 1
 
 
 class TestDisableAccount(TestController):
@@ -2154,8 +2154,8 @@ class TestDisableAccount(TestController):
         r = self.app.get(
             '/auth/disable/',
             extra_environ={'username': '*anonymous'})
-        assert_equal(r.status_int, 302)
-        assert_equal(r.location,
+        assert r.status_int == 302
+        assert (r.location ==
                      'http://localhost/auth/?return_to=%2Fauth%2Fdisable%2F')
 
     def test_lists_user_projects(self):
@@ -2164,8 +2164,8 @@ class TestDisableAccount(TestController):
         for p in user.my_projects_by_role_name('Admin'):
             if p.name == 'u/test-admin':
                 continue
-            assert_in(p.name, r)
-            assert_in(p.url(), r)
+            assert p.name in r
+            assert p.url() in r
 
     def test_has_asks_password(self):
         r = self.app.get('/auth/disable/')
@@ -2176,21 +2176,21 @@ class TestDisableAccount(TestController):
         self.app.get('/').follow()  # establish session
         r = self.app.post('/auth/disable/do_disable', {'password': 'bad',
                                                        '_session_id': self.app.cookies['_session_id'], })
-        assert_in('Invalid password', r)
+        assert 'Invalid password' in r
         user = M.User.by_username('test-admin')
-        assert_equal(user.disabled, False)
+        assert user.disabled == False
 
     def test_disable(self):
         self.app.get('/').follow()  # establish session
         r = self.app.post('/auth/disable/do_disable', {'password': 'foo',
                                                        '_session_id': self.app.cookies['_session_id'], })
-        assert_equal(r.status_int, 302)
-        assert_equal(r.location, 'http://localhost/')
+        assert r.status_int == 302
+        assert r.location == 'http://localhost/'
         flash = json.loads(self.webflash(r))
-        assert_equal(flash['status'], 'ok')
-        assert_equal(flash['message'], 'Your account was successfully disabled!')
+        assert flash['status'] == 'ok'
+        assert flash['message'] == 'Your account was successfully disabled!'
         user = M.User.by_username('test-admin')
-        assert_equal(user.disabled, True)
+        assert user.disabled == True
 
 
 class TestPasswordExpire(TestController):
@@ -2206,14 +2206,14 @@ class TestPasswordExpire(TestController):
 
     def assert_redirects(self, where='/'):
         resp = self.app.get(where, extra_environ={'username': 'test-user'}, status=302)
-        assert_equal(resp.location, 'http://localhost/auth/pwd_expired?' + urlencode({'return_to': where}))
+        assert resp.location == 'http://localhost/auth/pwd_expired?' + urlencode({'return_to': where})
 
     def assert_not_redirects(self, where='/neighborhood'):
         self.app.get(where, extra_environ={'username': 'test-user'}, status=200)
 
     def test_disabled(self):
         r = self.login()
-        assert_false(r.session.get('pwd-expired'))
+        assert not r.session.get('pwd-expired')
         self.assert_not_redirects()
 
     def expired(self, r):
@@ -2230,12 +2230,12 @@ class TestPasswordExpire(TestController):
 
         with h.push_config(config, **{'auth.pwdexpire.days': 180}):
             r = self.login()
-            assert_false(self.expired(r))
+            assert not self.expired(r)
             self.assert_not_redirects()
 
         with h.push_config(config, **{'auth.pwdexpire.days': 90}):
             r = self.login()
-            assert_true(self.expired(r))
+            assert self.expired(r)
             self.assert_redirects()
 
     def test_before(self):
@@ -2245,31 +2245,31 @@ class TestPasswordExpire(TestController):
         before = calendar.timegm(before.timetuple())
         with h.push_config(config, **{'auth.pwdexpire.before': before}):
             r = self.login()
-            assert_false(self.expired(r))
+            assert not self.expired(r)
             self.assert_not_redirects()
 
         before = datetime.utcnow() - timedelta(days=90)
         before = calendar.timegm(before.timetuple())
         with h.push_config(config, **{'auth.pwdexpire.before': before}):
             r = self.login()
-            assert_true(self.expired(r))
+            assert self.expired(r)
             self.assert_redirects()
 
     def test_logout(self):
         self.set_expire_for_user()
         with h.push_config(config, **{'auth.pwdexpire.days': 90}):
             r = self.login()
-            assert_true(self.expired(r))
+            assert self.expired(r)
             self.assert_redirects()
             r = self.app.get('/auth/logout', extra_environ={'username': 'test-user'})
-            assert_false(self.expired(r))
+            assert not self.expired(r)
             self.assert_not_redirects()
 
     def test_change_pwd(self):
         self.set_expire_for_user()
         with h.push_config(config, **{'auth.pwdexpire.days': 90}):
             r = self.login()
-            assert_true(self.expired(r))
+            assert self.expired(r)
             self.assert_redirects()
 
             user = M.User.by_username('test-user')
@@ -2281,28 +2281,28 @@ class TestPasswordExpire(TestController):
             f['pw'] = 'qwerty'
             f['pw2'] = 'qwerty'
             r = f.submit(extra_environ={'username': 'test-user'}, status=302)
-            assert_equal(r.location, 'http://localhost/')
-            assert_false(self.expired(r))
+            assert r.location == 'http://localhost/'
+            assert not self.expired(r)
             user = M.User.by_username('test-user')
-            assert_true(user.last_password_updated > old_update_time)
-            assert_not_equal(user.password, old_password)
+            assert user.last_password_updated > old_update_time
+            assert user.password != old_password
 
             # Can log in with new password and change isn't required anymore
             r = self.login(pwd='qwerty').follow()
-            assert_equal(r.location, 'http://localhost/dashboard')
-            assert_not_in('Invalid login', r)
-            assert_false(self.expired(r))
+            assert r.location == 'http://localhost/dashboard'
+            assert 'Invalid login' not in r
+            assert not self.expired(r)
             self.assert_not_redirects()
 
             # and can't log in with old password
             r = self.login(pwd='foo')
-            assert_in('Invalid login', r)
+            assert 'Invalid login' in r
 
     def test_expired_pwd_change_invalidates_token(self):
         self.set_expire_for_user()
         with h.push_config(config, **{'auth.pwdexpire.days': 90}):
             r = self.login()
-            assert_true(self.expired(r))
+            assert self.expired(r)
             self.assert_redirects()
             user = M.User.by_username('test-user')
             user.set_tool_data('AuthPasswordReset',
@@ -2310,8 +2310,8 @@ class TestPasswordExpire(TestController):
                                hash_expiry="04-08-2020")
             hash = user.get_tool_data('AuthPasswordReset', 'hash')
             hash_expiry = user.get_tool_data('AuthPasswordReset', 'hash_expiry')
-            assert_equal(hash, 'generated_hash_value')
-            assert_equal(hash_expiry, '04-08-2020')
+            assert hash == 'generated_hash_value'
+            assert hash_expiry == '04-08-2020'
             session(user).flush(user)
 
             # Change expired password
@@ -2321,14 +2321,14 @@ class TestPasswordExpire(TestController):
             f['pw'] = 'qwerty'
             f['pw2'] = 'qwerty'
             r = f.submit(extra_environ={'username': 'test-user'}, status=302)
-            assert_equal(r.location, 'http://localhost/')
+            assert r.location == 'http://localhost/'
 
             user = M.User.by_username('test-user')
             hash = user.get_tool_data('AuthPasswordReset', 'hash')
             hash_expiry = user.get_tool_data('AuthPasswordReset', 'hash_expiry')
 
-            assert_equal(hash, '')
-            assert_equal(hash_expiry, '')
+            assert hash == ''
+            assert hash_expiry == ''
 
     def check_validation(self, oldpw, pw, pw2):
         user = M.User.by_username('test-user')
@@ -2340,32 +2340,32 @@ class TestPasswordExpire(TestController):
         f['pw'] = pw
         f['pw2'] = pw2
         r = f.submit(extra_environ={'username': 'test-user'})
-        assert_true(self.expired(r))
+        assert self.expired(r)
         user = M.User.by_username('test-user')
-        assert_equal(user.last_password_updated, old_update_time)
-        assert_equal(user.password, old_password)
+        assert user.last_password_updated == old_update_time
+        assert user.password == old_password
         return r
 
     def test_change_pwd_validation(self):
         self.set_expire_for_user()
         with h.push_config(config, **{'auth.pwdexpire.days': 90}):
             r = self.login()
-            assert_true(self.expired(r))
+            assert self.expired(r)
             self.assert_redirects()
 
             r = self.check_validation('', '', '')
-            assert_in('Please enter a value', r)
+            assert 'Please enter a value' in r
             r = self.check_validation('', 'qwe', 'qwerty')
-            assert_in('Enter a value 6 characters long or more', r)
+            assert 'Enter a value 6 characters long or more' in r
             r = self.check_validation('bad', 'qwerty1', 'qwerty')
-            assert_in('Passwords must match', r)
+            assert 'Passwords must match' in r
             r = self.check_validation('bad', 'qwerty', 'qwerty')
-            assert_in('Incorrect password', self.webflash(r))
-            assert_equal(r.location, 'http://localhost/auth/pwd_expired?return_to=')
+            assert 'Incorrect password' in self.webflash(r)
+            assert r.location == 'http://localhost/auth/pwd_expired?return_to='
 
             with h.push_config(config, **{'auth.min_password_len': 3}):
                 r = self.check_validation('foo', 'foo', 'foo')
-                assert_in('Your old and new password should not be the same', r)
+                assert 'Your old and new password should not be the same' in r
 
     def test_return_to(self):
         return_to = '/p/test/tickets/?milestone=1.0&page=2'
@@ -2373,7 +2373,7 @@ class TestPasswordExpire(TestController):
         with h.push_config(config, **{'auth.pwdexpire.days': 90}):
             r = self.login(query_string='?' + urlencode({'return_to': return_to}))
             # don't go to the return_to yet
-            assert_equal(r.location, 'http://localhost/auth/pwd_expired?' + urlencode({'return_to': return_to}))
+            assert r.location == 'http://localhost/auth/pwd_expired?' + urlencode({'return_to': return_to})
 
             # but if user tries to go directly there anyway, intercept and redirect back
             self.assert_redirects(where=return_to)
@@ -2385,7 +2385,7 @@ class TestPasswordExpire(TestController):
             f['pw2'] = 'qwerty'
             f['return_to'] = return_to
             r = f.submit(extra_environ={'username': 'test-user'}, status=302)
-            assert_equal(r.location, 'http://localhost/p/test/tickets/?milestone=1.0&page=2')
+            assert r.location == 'http://localhost/p/test/tickets/?milestone=1.0&page=2'
 
 
 class TestCSRFProtection(TestController):
@@ -2404,13 +2404,13 @@ class TestCSRFProtection(TestController):
         # regular form submit
         r = self.app.get('/admin/overview')
         r = r.form.submit()
-        assert_equal(r.location, 'http://localhost/admin/overview')
+        assert r.location == 'http://localhost/admin/overview'
 
         # invalid form submit
         r = self.app.get('/admin/overview')
         r.form['_session_id'] = 'bogus'
         r = r.form.submit()
-        assert_equal(r.location, 'http://localhost/auth/')
+        assert r.location == 'http://localhost/auth/'
 
     def test_blocks_invalid_on_login(self):
         r = self.app.get('/auth/')
@@ -2419,7 +2419,7 @@ class TestCSRFProtection(TestController):
 
     def test_token_present_on_first_request(self):
         r = self.app.get('/auth/')
-        assert_true(r.form['_session_id'].value)
+        assert r.form['_session_id'].value
 
 
 class TestTwoFactor(TestController):
@@ -2459,13 +2459,13 @@ class TestTwoFactor(TestController):
     def test_user_disabled(self):
         r = self.app.get('/auth/preferences/')
         info_html = str(r.html.find(attrs={'class': 'preferences multifactor'}))
-        assert_in('disabled', info_html)
+        assert 'disabled' in info_html
 
     def test_user_enabled(self):
         self._init_totp()
         r = self.app.get('/auth/preferences/')
         info_html = str(r.html.find(attrs={'class': 'preferences multifactor'}))
-        assert_in('enabled', info_html)
+        assert 'enabled' in info_html
 
     def test_reconfirm_auth(self):
         from datetime import datetime as real_datetime
@@ -2475,22 +2475,22 @@ class TestTwoFactor(TestController):
             # reconfirm required at first
             datetime.utcnow.return_value = real_datetime(2016, 1, 1, 0, 0, 0)
             r = self.app.get('/auth/preferences/totp_new')
-            assert_in('Password Confirmation', r)
+            assert 'Password Confirmation' in r
 
             # submit form, and its not required
             r.form['password'] = 'foo'
             r = r.form.submit()
-            assert_not_in('Password Confirmation', r)
+            assert 'Password Confirmation' not in r
 
             # still not required
             datetime.utcnow.return_value = real_datetime(2016, 1, 1, 0, 1, 45)
             r = self.app.get('/auth/preferences/totp_new')
-            assert_not_in('Password Confirmation', r)
+            assert 'Password Confirmation' not in r
 
             # required later
             datetime.utcnow.return_value = real_datetime(2016, 1, 1, 0, 2, 3)
             r = self.app.get('/auth/preferences/totp_new')
-            assert_in('Password Confirmation', r)
+            assert 'Password Confirmation' in r
 
     def test_enable_totp(self):
         # create a separate session, for later use in the test
@@ -2500,13 +2500,13 @@ class TestTwoFactor(TestController):
 
         with out_audits(user=True):
             r = self.app.get('/auth/preferences/totp_new')
-            assert_in('Password Confirmation', r)
+            assert 'Password Confirmation' in r
 
         with audits('Visited multifactor new TOTP page', user=True):
             r.form['password'] = 'foo'
             r = r.form.submit()
-            assert_in('Scan this', r)
-            assert_in('Or enter setup key: ', r)
+            assert 'Scan this' in r
+            assert 'Or enter setup key: ' in r
 
         first_key_shown = r.session['totp_new_key']
 
@@ -2514,9 +2514,9 @@ class TestTwoFactor(TestController):
             form = r.forms['totp_set']
             form['code'] = ''
             r = form.submit()
-            assert_in('Invalid', r)
-            assert_in(f'Or enter setup key: {b32encode(first_key_shown).decode()}', r)
-            assert_equal(first_key_shown, r.session['totp_new_key'])  # different keys on each pageload would be bad!
+            assert 'Invalid' in r
+            assert f'Or enter setup key: {b32encode(first_key_shown).decode()}' in r
+            assert first_key_shown == r.session['totp_new_key']  # different keys on each pageload would be bad!
 
         new_totp = TotpService().Totp(r.session['totp_new_key'])
         code = new_totp.generate(time_time())
@@ -2524,20 +2524,19 @@ class TestTwoFactor(TestController):
         form['code'] = code
         with audits('Set up multifactor TOTP', user=True):
             r = form.submit()
-            assert_equal('Two factor authentication has now been set up.', json.loads(self.webflash(r))['message'],
-                         self.webflash(r))
+            assert 'Two factor authentication has now been set up.' == json.loads(self.webflash(r))['message'], self.webflash(r)
 
         tasks = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendsimplemail')).all()
-        assert_equal(len(tasks), 1)
-        assert_equal(tasks[0].kwargs['subject'], 'Two-Factor Authentication Enabled')
-        assert_in('new two-factor authentication', tasks[0].kwargs['text'])
+        assert len(tasks) == 1
+        assert tasks[0].kwargs['subject'] == 'Two-Factor Authentication Enabled'
+        assert 'new two-factor authentication' in tasks[0].kwargs['text']
 
         r = r.follow()
-        assert_in('Recovery Codes', r)
+        assert 'Recovery Codes' in r
 
         # Confirm any pre-existing sessions have to re-authenticate
         r = other_session.app.get('/auth/preferences/')
-        assert_in('/auth/?return_to', r.headers['Location'])
+        assert '/auth/?return_to' in r.headers['Location']
         other_session.tearDown()
 
     def test_reset_totp(self):
@@ -2545,28 +2544,28 @@ class TestTwoFactor(TestController):
 
         # access page
         r = self.app.get('/auth/preferences/totp_new')
-        assert_in('Password Confirmation', r)
+        assert 'Password Confirmation' in r
 
         # reconfirm password to get to it
         r.form['password'] = 'foo'
         r = r.form.submit()
 
         # confirm warning message, and key is not changed yet
-        assert_in('Scan this', r)
-        assert_in('Or enter setup key: ', r)
-        assert_in('this will invalidate your previous', r)
+        assert 'Scan this' in r
+        assert 'Or enter setup key: ' in r
+        assert 'this will invalidate your previous' in r
         current_key = TotpService.get().get_secret_key(M.User.query.get(username='test-admin'))
-        assert_equal(self.sample_key, current_key)
+        assert self.sample_key == current_key
 
         # incorrect submission
         form = r.forms['totp_set']
         form['code'] = ''
         r = form.submit()
-        assert_in('Invalid', r)
+        assert 'Invalid' in r
 
         # still unchanged key
         current_key = TotpService.get().get_secret_key(M.User.query.get(username='test-admin'))
-        assert_equal(self.sample_key, current_key)
+        assert self.sample_key == current_key
 
         # valid submission
         new_key = r.session['totp_new_key']
@@ -2575,13 +2574,12 @@ class TestTwoFactor(TestController):
         form = r.forms['totp_set']
         form['code'] = code
         r = form.submit()
-        assert_equal('Two factor authentication has now been set up.', json.loads(self.webflash(r))['message'],
-                     self.webflash(r))
+        assert 'Two factor authentication has now been set up.' == json.loads(self.webflash(r))['message'], self.webflash(r)
 
         # new key in place
         current_key = TotpService.get().get_secret_key(M.User.query.get(username='test-admin'))
-        assert_equal(new_key, current_key)
-        assert_not_equal(self.sample_key, current_key)
+        assert new_key == current_key
+        assert self.sample_key != current_key
 
     def test_disable(self):
         self._init_totp()
@@ -2594,26 +2592,25 @@ class TestTwoFactor(TestController):
         r = form.submit()
 
         # confirm first, no change
-        assert_in('Password Confirmation', r)
+        assert 'Password Confirmation' in r
         user = M.User.query.get(username='test-admin')
-        assert_equal(user.get_pref('multifactor'), True)
+        assert user.get_pref('multifactor') == True
 
         # confirm submit, everything goes off
         r.form['password'] = 'foo'
         with audits('Disabled multifactor TOTP', user=True):
             r = r.form.submit()
-            assert_equal('Multifactor authentication has now been disabled.', json.loads(self.webflash(r))['message'],
-                         self.webflash(r))
+            assert 'Multifactor authentication has now been disabled.' == json.loads(self.webflash(r))['message'], self.webflash(r)
         user = M.User.query.get(username='test-admin')
-        assert_equal(user.get_pref('multifactor'), False)
-        assert_equal(TotpService().get().get_secret_key(user), None)
-        assert_equal(RecoveryCodeService().get().get_codes(user), [])
+        assert user.get_pref('multifactor') == False
+        assert TotpService().get().get_secret_key(user) == None
+        assert RecoveryCodeService().get().get_codes(user) == []
 
         # email confirmation
         tasks = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendsimplemail')).all()
-        assert_equal(len(tasks), 1)
-        assert_equal(tasks[0].kwargs['subject'], 'Two-Factor Authentication Disabled')
-        assert_in('disabled two-factor authentication', tasks[0].kwargs['text'])
+        assert len(tasks) == 1
+        assert tasks[0].kwargs['subject'] == 'Two-Factor Authentication Disabled'
+        assert 'disabled two-factor authentication' in tasks[0].kwargs['text']
 
     def test_login_totp(self):
         self._init_totp()
@@ -2638,7 +2635,7 @@ class TestTwoFactor(TestController):
         r.form['code'] = 'invalid-code'
         with audits('Multifactor login - invalid code', user=True):
             r = r.form.submit()
-        assert_in('Invalid code', r)
+        assert 'Invalid code' in r
         assert not r.session.get('username')
 
         # use a valid code
@@ -2649,7 +2646,7 @@ class TestTwoFactor(TestController):
             r = r.form.submit()
 
         # confirm login and final page
-        assert_equal(r.session['username'], 'test-admin')
+        assert r.session['username'] == 'test-admin'
         assert r.location.endswith('/p/foo'), r
 
     def test_login_rate_limit(self):
@@ -2671,7 +2668,7 @@ class TestTwoFactor(TestController):
         for i in range(3):
             r.form['code'] = 'invalid-code'
             r = r.form.submit()
-            assert_in('Invalid code', r)
+            assert 'Invalid code' in r
 
         # use a valid code, but it'll hit rate limit
         totp = TotpService().Totp(self.sample_key)
@@ -2680,7 +2677,7 @@ class TestTwoFactor(TestController):
         with audits('Multifactor login - rate limit', user=True):
             r = r.form.submit()
 
-        assert_in('rate limit exceeded', r)
+        assert 'rate limit exceeded' in r
         assert not r.session.get('username')
 
     def test_login_totp_disrupted(self):
@@ -2707,11 +2704,10 @@ class TestTwoFactor(TestController):
         r = r.form.submit()
 
         # sent back to regular login
-        assert_equal('Your multifactor login was disrupted, please start over.',
-                     json.loads(self.webflash(r))['message'],
-                     self.webflash(r))
+        assert ('Your multifactor login was disrupted, please start over.' ==
+                     json.loads(self.webflash(r))['message']), self.webflash(r)
         r = r.follow()
-        assert_in('Password Login', r)
+        assert 'Password Login' in r
 
     def test_login_recovery_code(self):
         self._init_totp()
@@ -2737,7 +2733,7 @@ class TestTwoFactor(TestController):
         # try an invalid code
         r.form['code'] = 'invalid-code'
         r = r.form.submit()
-        assert_in('Invalid code', r)
+        assert 'Invalid code' in r
         assert not r.session.get('username')
 
         # use a valid code
@@ -2750,11 +2746,11 @@ class TestTwoFactor(TestController):
             r = r.form.submit()
 
         # confirm login and final page
-        assert_equal(r.session['username'], 'test-admin')
+        assert r.session['username'] == 'test-admin'
         assert r.location.endswith('/p/foo'), r
 
         # confirm code used up
-        assert_not_in(recovery_code, RecoveryCodeService().get().get_codes(user))
+        assert recovery_code not in RecoveryCodeService().get().get_codes(user)
 
     @patch('allura.lib.plugin.AuthenticationProvider.hibp_password_check_enabled', Mock(return_value=True))
     def test_login_totp_with_hibp(self):
@@ -2787,7 +2783,7 @@ class TestTwoFactor(TestController):
             r = r.form.submit()
 
         # confirm login and final page
-        assert_equal(r.session['username'], 'test-admin')
+        assert r.session['username'] == 'test-admin'
         assert r.location.endswith('/p/foo'), r
 
     def test_view_key(self):
@@ -2795,13 +2791,13 @@ class TestTwoFactor(TestController):
 
         with out_audits(user=True):
             r = self.app.get('/auth/preferences/totp_view')
-            assert_in('Password Confirmation', r)
+            assert 'Password Confirmation' in r
 
         with audits('Viewed multifactor TOTP config page', user=True):
             r.form['password'] = 'foo'
             r = r.form.submit()
-            assert_in('Scan this', r)
-            assert_in(f'Or enter setup key: {self.sample_b32}', r)
+            assert 'Scan this' in r
+            assert f'Or enter setup key: {self.sample_b32}' in r
 
     def test_view_recovery_codes_and_regen(self):
         self._init_totp()
@@ -2809,14 +2805,14 @@ class TestTwoFactor(TestController):
         # reconfirm password
         with out_audits(user=True):
             r = self.app.get('/auth/preferences/multifactor_recovery')
-            assert_in('Password Confirmation', r)
+            assert 'Password Confirmation' in r
 
         # actual visit
         with audits('Viewed multifactor recovery codes', user=True):
             r.form['password'] = 'foo'
             r = r.form.submit()
-            assert_in('Download', r)
-            assert_in('Print', r)
+            assert 'Download' in r
+            assert 'Print' in r
 
         # regenerate codes
         with audits('Regenerated multifactor recovery codes', user=True):
@@ -2824,9 +2820,9 @@ class TestTwoFactor(TestController):
 
         # email confirmation
         tasks = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendsimplemail')).all()
-        assert_equal(len(tasks), 1)
-        assert_equal(tasks[0].kwargs['subject'], 'Two-Factor Recovery Codes Regenerated')
-        assert_in('regenerated', tasks[0].kwargs['text'])
+        assert len(tasks) == 1
+        assert tasks[0].kwargs['subject'] == 'Two-Factor Recovery Codes Regenerated'
+        assert 'regenerated' in tasks[0].kwargs['text']
 
     def test_send_links(self):
         r = self.app.get('/auth/preferences/totp_new')
@@ -2836,7 +2832,7 @@ class TestTwoFactor(TestController):
         r = r.forms['totp_send_link'].submit()
 
         tasks = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendsimplemail')).all()
-        assert_equal(len(tasks), 1)
-        assert_equal(tasks[0].kwargs['subject'], 'Two-Factor Authentication Apps')
-        assert_in('itunes.apple.com', tasks[0].kwargs['text'])
-        assert_in('play.google.com', tasks[0].kwargs['text'])
+        assert len(tasks) == 1
+        assert tasks[0].kwargs['subject'] == 'Two-Factor Authentication Apps'
+        assert 'itunes.apple.com' in tasks[0].kwargs['text']
+        assert 'play.google.com' in tasks[0].kwargs['text']
diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py
index 948a7af93..8dd2934cc 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -55,16 +55,16 @@ class TestDiscuss(TestDiscussBase):
         # remove tool-wide subscription, so it doesn't interfere
         M.Mailbox.query.remove(dict(user_id=user._id, app_config_id=thread.app_config_id))
 
-        assert_false(self._is_subscribed(user, thread))
+        assert not self._is_subscribed(user, thread)
         link = self._thread_link()
         params = {
             'threads-0._id': thread_id,
             'threads-0.subscription': 'on'}
         r = self.app.post('/wiki/_discuss/subscribe', params=params)
-        assert_true(self._is_subscribed(user, thread))
+        assert self._is_subscribed(user, thread)
         params = {'threads-0._id': thread_id}
         r = self.app.post('/wiki/_discuss/subscribe', params=params)
-        assert_false(self._is_subscribed(user, thread))
+        assert not self._is_subscribed(user, thread)
 
     def _make_post(self, text):
         thread_link = self._thread_link()
@@ -90,7 +90,7 @@ class TestDiscuss(TestDiscussBase):
         thread_link = self._thread_link()
         r = self._make_post('This is a post')
         assert 'This is a post' in r, r
-        assert_equal(check_spam.call_args[0][0], 'This is a post')
+        assert check_spam.call_args[0][0] == 'This is a post'
 
         post_link = str(
             r.html.find('div', {'class': 'edit_post_form reply'}).find('form')['action'])
@@ -289,7 +289,7 @@ class TestDiscuss(TestDiscussBase):
         post_link = str(
             r.html.find('div', {'class': 'edit_post_form reply'}).find('form')['action'])
         r = self.app.post(post_link + 'moderate', params=dict(spam='spam'))
-        assert_equal(r.json, {"result": "success"})
+        assert r.json == {"result": "success"}
         post = M.Post.query.find().first()
         post_username = post.author().username
         moderate_link = '/p/test/wiki/_discuss/moderate'
@@ -343,15 +343,15 @@ class TestDiscuss(TestDiscussBase):
         post_link = str(
             r.html.find('div', {'class': 'edit_post_form reply'}).find('form')['action'])
         post = M.Post.query.find().first()
-        assert_equal(post.status, 'pending')
+        assert post.status == 'pending'
         r = self.app.get('/wiki/feed.rss')
-        assert_not_in('Post needs moderation!', r)
+        assert 'Post needs moderation!' not in r
 
         self.app.post(post_link + 'moderate', params=dict(approve='approve'))
         post = M.Post.query.find().first()
-        assert_equal(post.status, 'ok')
+        assert post.status == 'ok'
         r = self.app.get('/wiki/feed.rss')
-        assert_in('Post needs moderation!', r)
+        assert 'Post needs moderation!' in r
 
     def test_post_paging(self):
         thread_link = self._thread_link()
diff --git a/Allura/allura/tests/functional/test_gravatar.py b/Allura/allura/tests/functional/test_gravatar.py
index c0155c0bf..f6738e03e 100644
--- a/Allura/allura/tests/functional/test_gravatar.py
+++ b/Allura/allura/tests/functional/test_gravatar.py
@@ -51,7 +51,7 @@ class TestGravatar(TestController):
         email = 'Wolf@example.com'
         url = urlparse(gravatar.url(email=email, rating='x'))
         query = parse_qs(url.query)
-        assert_equal(query,
+        assert (query ==
                      {'rating': ['x']})
 
     @patch.dict(tg.config, {'default_avatar_image': 'https://example.com/img/icon.png'})
@@ -59,5 +59,5 @@ class TestGravatar(TestController):
         email = 'Wolf@example.com'
         url = urlparse(gravatar.url(email=email))
         query = parse_qs(url.query)
-        assert_equal(query,
+        assert (query ==
                      {'r': ['pg'], 'd': ['https://example.com/img/icon.png']})
diff --git a/Allura/allura/tests/functional/test_home.py b/Allura/allura/tests/functional/test_home.py
index 11af5d674..4096a5add 100644
--- a/Allura/allura/tests/functional/test_home.py
+++ b/Allura/allura/tests/functional/test_home.py
@@ -39,22 +39,22 @@ class TestProjectHome(TestController):
                          str(root.html)), 'Missing Server comment'
         nav_links = root.html.find('div', dict(id='top_nav')).findAll('a')
         nav_links = [nl for nl in nav_links if 'add-tool-toggle' not in nl['class']]
-        assert_equal(len(nav_links), len(response.json['menu']))
+        assert len(nav_links) == len(response.json['menu'])
         for nl, entry in zip(nav_links, response.json['menu']):
             assert nl['href'] == entry['url']
 
     @td.with_wiki
     def test_project_nav_with_admin_options(self):
         r = self.app.get('/p/test/_nav.json?admin_options=1')
-        assert_in({
+        assert {
             "text": "Wiki",
             "href": "/p/test/admin/install_tool?tool_name=wiki",
             "tooltip":
                 "Documentation is key to your project and the wiki tool helps make it easy for anyone to contribute."
-        }, r.json['installable_tools'])
+        } in r.json['installable_tools']
         for m in r.json['menu']:
             if m['mount_point'] == 'sub1':
-                assert_equal(m['admin_options'],
+                assert (m['admin_options'] ==
                              [{'className': None,
                                'text': 'Subproject Admin',
                                'href': '/p/test/sub1/admin',
@@ -64,18 +64,18 @@ class TestProjectHome(TestController):
             raise AssertionError('Did not find sub1 subproject in menu results: {}'.format(r.json['menu']))
         for m in r.json['menu']:
             if m['mount_point'] == 'wiki':
-                assert_in({'className': 'admin_modal',
+                assert {'className': 'admin_modal',
                            'text': 'Set Home',
                            'href': '/p/test/admin/wiki/home',
-                           }, m['admin_options'])
-                assert_in({'className': None,
+                           } in m['admin_options']
+                assert {'className': None,
                            'text': 'Permissions',
                            'href': '/p/test/admin/wiki/permissions',
-                           }, m['admin_options'])
-                assert_in({'className': 'admin_modal',
+                           } in m['admin_options']
+                assert {'className': 'admin_modal',
                            'text': 'Delete Everything',
                            'href': '/p/test/admin/wiki/delete',
-                           }, m['admin_options'])
+                           } in m['admin_options']
                 break
         else:
             raise AssertionError('Did not find wiki in menu results: {}'.format(r.json['menu']))
@@ -92,13 +92,13 @@ class TestProjectHome(TestController):
         menu = response.json['menu']
         wiki_group = menu[-2]
         wikis = wiki_group.pop('children')
-        assert_equal({'url': '/p/test/_list/wiki', 'name': 'Wiki \u25be', 'mount_point': None,
-                      'icon': 'tool-wiki', 'tool_name': 'wiki', 'is_anchored': False}, wiki_group)
-        assert_equal(len(wikis), 2)
-        assert_in({'url': '/p/test/wiki/', 'name': 'Wiki', 'mount_point': 'wiki',
-                   'icon': 'tool-wiki', 'tool_name': 'wiki', 'is_anchored': False}, wikis)
-        assert_in({'url': '/p/test/wiki2/', 'name': 'wiki2', 'mount_point': 'wiki2',
-                   'icon': 'tool-wiki', 'tool_name': 'wiki', 'is_anchored': False}, wikis)
+        assert {'url': '/p/test/_list/wiki', 'name': 'Wiki \u25be', 'mount_point': None,
+                      'icon': 'tool-wiki', 'tool_name': 'wiki', 'is_anchored': False} == wiki_group
+        assert len(wikis) == 2
+        assert {'url': '/p/test/wiki/', 'name': 'Wiki', 'mount_point': 'wiki',
+                   'icon': 'tool-wiki', 'tool_name': 'wiki', 'is_anchored': False} in wikis
+        assert {'url': '/p/test/wiki2/', 'name': 'wiki2', 'mount_point': 'wiki2',
+                   'icon': 'tool-wiki', 'tool_name': 'wiki', 'is_anchored': False} in wikis
 
     def test_sitemap_limit_per_tool(self):
         """Test that sitemap is limited to max of 10 items per tool type."""
@@ -112,7 +112,7 @@ class TestProjectHome(TestController):
         response = self.app.get('/p/test/_nav.json')
         menu = response.json['menu']
         wikis = menu[-2]['children']
-        assert_equal(len(wikis), 10)
+        assert len(wikis) == 10
 
     @td.with_wiki
     def test_project_group_nav_more_than_ten(self):
@@ -126,9 +126,9 @@ class TestProjectHome(TestController):
         response = self.app.get('/p/test/_nav.json')
         menu = response.json['menu']
         wiki_menu = [m for m in menu if m['tool_name'] == 'wiki'][0]
-        assert_equal(len(wiki_menu['children']), 10)
-        assert_in({'url': '/p/test/_list/wiki', 'name': 'More...', 'mount_point': None,
-                   'icon': 'tool-wiki', 'tool_name': 'wiki', 'is_anchored': False}, wiki_menu['children'])
+        assert len(wiki_menu['children']) == 10
+        assert {'url': '/p/test/_list/wiki', 'name': 'More...', 'mount_point': None,
+                   'icon': 'tool-wiki', 'tool_name': 'wiki', 'is_anchored': False} in wiki_menu['children']
 
     @td.with_wiki
     def test_neighborhood_home(self):
@@ -144,7 +144,7 @@ class TestProjectHome(TestController):
 
         r = self.app.get('/u/test-admin/sub1/')
         assert r.location.endswith('admin/'), r.location
-        assert_not_in('Profile', r.follow().text)
+        assert 'Profile' not in r.follow().text
 
     def test_user_icon_missing(self):
         r = self.app.get('/u/test-user/user_icon', status=302)
@@ -162,7 +162,7 @@ class TestProjectHome(TestController):
                 short_description='A Test Project'),
                 upload_files=[upload])
         r = self.app.get('/u/test-admin/user_icon')
-        assert_equal(r.content_type, 'image/png')
+        assert r.content_type == 'image/png'
 
     def test_user_search(self):
         r = self.app.get('/p/test/user_search?term=test', status=200)
@@ -190,7 +190,7 @@ class TestProjectHome(TestController):
             'value': 'test-admin',
             'label': 'Test Admin (test-admin)'
         }]
-        assert_equal(j['options'], expected)
+        assert j['options'] == expected
 
     def test_members(self):
         nbhd = M.Neighborhood.query.get(name='Projects')
@@ -246,7 +246,7 @@ class TestProjectHome(TestController):
         pr.install_app(ep_name='Wiki', mount_point='test-sub', mount_label='Test Sub', ordinal='1')
         r = self.app.get('/p/test/test-mount/test-sub/').follow()
         active_link = r.html.findAll('li', {'class': 'selected'})
-        assert_equal(len(active_link), 1)
+        assert len(active_link) == 1
         assert active_link[0].contents[1]['href'] == '/p/test/test-mount/test-sub/'
         assert 'Welcome to your wiki!' in r
 
@@ -271,6 +271,6 @@ class TestProjectHome(TestController):
         # Check if the tool is accessed and not the subproject.
         r = self.app.get('/p/test/test-mount/').follow()
         active_link = r.html.findAll('li', {'class': 'selected'})
-        assert_equal(len(active_link), 1)
+        assert len(active_link) == 1
         assert active_link[0].contents[1]['href'] == '/p/test/test-mount/'
         assert 'Welcome to your wiki!' in r
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index a60820621..24afb2036 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -174,21 +174,21 @@ class TestNeighborhood(TestController):
                                       anchored_tools='w!iki:Wiki, tickets:Ticket'),
                           extra_environ=dict(username='root'))
         assert 'error' in self.webflash(r)
-        assert_equal(neighborhood.anchored_tools, 'wiki:Wiki, tickets:Ticket')
+        assert neighborhood.anchored_tools == 'wiki:Wiki, tickets:Ticket'
 
         r = self.app.post('/p/_admin/update',
                           params=dict(name='Projects',
                                       anchored_tools='wiki:Wiki,'),
                           extra_environ=dict(username='root'))
         assert 'error' in self.webflash(r)
-        assert_equal(neighborhood.anchored_tools, 'wiki:Wiki, tickets:Ticket')
+        assert neighborhood.anchored_tools == 'wiki:Wiki, tickets:Ticket'
 
         r = self.app.post('/p/_admin/update',
                           params=dict(name='Projects',
                                       anchored_tools='badname,'),
                           extra_environ=dict(username='root'))
         assert 'error' in self.webflash(r)
-        assert_equal(neighborhood.anchored_tools, 'wiki:Wiki, tickets:Ticket')
+        assert neighborhood.anchored_tools == 'wiki:Wiki, tickets:Ticket'
 
         r = self.app.get('/p/test/admin/overview')
         top_nav = r.html.find(id='top_nav')
@@ -769,10 +769,10 @@ class TestNeighborhood(TestController):
         assert "My home text!" in r
         # check tool options
         opts = p.app_config('wiki').options
-        assert_equal(False, opts.show_discussion)
-        assert_equal(False, opts.show_left_bar)
-        assert_equal(False, opts.show_right_bar)
-        assert_equal("http://foo.com/testtemp/", opts.some_url)
+        assert False == opts.show_discussion
+        assert False == opts.show_left_bar
+        assert False == opts.show_right_bar
+        assert "http://foo.com/testtemp/" == opts.some_url
         # check that custom groups/perms/users were setup correctly
         roles = p.named_roles
         for group in test_groups:
@@ -846,15 +846,15 @@ class TestNeighborhood(TestController):
         for name in ('My+Moz', 'Te%st!', 'ab', 'a' * 16):
             r = self.app.get(
                 '/p/check_names?neighborhood=Projects&project_unixname=%s' % name)
-            assert_equal(
-                r.json,
+            assert (
+                r.json ==
                 {'project_unixname': 'Please use 3-15 small letters, numbers, and dashes.'})
         r = self.app.get(
             '/p/check_names?neighborhood=Projects&project_unixname=mymoz')
-        assert_equal(r.json, {})
+        assert r.json == {}
         r = self.app.get(
             '/p/check_names?neighborhood=Projects&project_unixname=test')
-        assert_equal(r.json,
+        assert (r.json ==
                      {'project_unixname': 'This project name is taken.'})
 
     @td.with_tool('test/sub1', 'Wiki', 'wiki')
@@ -914,8 +914,8 @@ class TestNeighborhood(TestController):
                       extra_environ=dict(username='root'))
         r = self.app.get('/adobe/_admin/accolades',
                          extra_environ=dict(username='root'))
-        assert_in('Winner!', r)
-        assert_in('http://award.org', r)
+        assert 'Winner!' in r
+        assert 'http://award.org' in r
         self.app.get('/adobe/_admin/awards/%s/adobe-1' %
                      foo_id, extra_environ=dict(username='root'))
         self.app.post('/adobe/_admin/awards/%s/adobe-1/revoke' % foo_id,
@@ -991,11 +991,11 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
             r = self.app.get('/p/verify_phone', {'number': '1234567890'})
             expected = {'status': 'error',
                         'error': 'Phone service is not configured'}
-            assert_equal(r.json, expected)
+            assert r.json == expected
             rid = r.session.get('phone_verification.request_id')
             hash = r.session.get('phone_verification.number_hash')
-            assert_equal(rid, None)
-            assert_equal(hash, None)
+            assert rid == None
+            assert hash == None
 
     @patch.object(g, 'phone_service', autospec=True)
     def test_verify_phone(self, phone_service):
@@ -1004,11 +1004,11 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
                 'request_id': 'request-id', 'status': 'ok'}
             r = self.app.get('/p/verify_phone', {'number': '1-555-444-3333'})
             phone_service.verify.assert_called_once_with('15554443333')
-            assert_equal(r.json, {'status': 'ok'})
+            assert r.json == {'status': 'ok'}
             rid = r.session.get('phone_verification.request_id')
             hash = r.session.get('phone_verification.number_hash')
-            assert_equal(rid, 'request-id')
-            assert_equal(hash, 'f9ac49faef45d18746ced08d001e23b179107940')
+            assert rid == 'request-id'
+            assert hash == 'f9ac49faef45d18746ced08d001e23b179107940'
 
     @patch.object(g, 'phone_service', autospec=True)
     def test_verify_phone_escapes_error(self, phone_service):
@@ -1022,7 +1022,7 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
             'status': 'error',
             'error': '&lt;script&gt;alert(&#34;hacked&#34;);&lt;/script&gt;',
         }
-        assert_equal(r.json, expected)
+        assert r.json == expected
 
     @patch.object(g, 'phone_service', autospec=True)
     def test_verify_phone_already_used(self, phone_service):
@@ -1032,10 +1032,10 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
             session(u).flush(u)
             phone_service.verify.return_value = {'request_id': 'request-id', 'status': 'ok'}
             r = self.app.get('/p/verify_phone', {'number': '1-555-444-9999'})
-            assert_equal(r.json, {
+            assert r.json == {
                 'status': 'error',
                 'error': 'That phone number has already been used.'
-            })
+            }
 
     def test_check_phone_verification_no_params(self):
         with h.push_config(config, **{'project.verify_phone': 'true'}):
@@ -1053,12 +1053,12 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
             r = self.app.get('/p/verify_phone', {'number': '1234567890'})
 
             r = self.app.get('/p/check_phone_verification', {'pin': '1234'})
-            assert_equal(r.json, {'status': 'error'})
+            assert r.json == {'status': 'error'}
             phone_service.check.assert_called_once_with(req_id, '1234')
 
             user = M.User.by_username('test-admin')
             hash = user.get_tool_data('phone_verification', 'number_hash')
-            assert_equal(hash, None)
+            assert hash == None
 
     @patch.object(g, 'phone_service', autospec=True)
     def test_check_phone_verification_ok(self, phone_service):
@@ -1072,12 +1072,12 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
             r = self.app.get('/p/verify_phone', {'number': '11234567890'})
 
             r = self.app.get('/p/check_phone_verification', {'pin': '1234'})
-            assert_equal(r.json, {'status': 'ok'})
+            assert r.json == {'status': 'ok'}
             phone_service.check.assert_called_once_with(req_id, '1234')
 
             user = M.User.by_username('test-admin')
             hash = user.get_tool_data('phone_verification', 'number_hash')
-            assert_equal(hash, '54c61c96d5d5aea5254c2d4f41508a938e5501b4')
+            assert hash == '54c61c96d5d5aea5254c2d4f41508a938e5501b4'
 
     @patch.object(g, 'phone_service', autospec=True)
     def test_check_phone_verification_escapes_error(self, phone_service):
@@ -1091,7 +1091,7 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
             'status': 'error',
             'error': '&lt;script&gt;alert(&#34;hacked&#34;);&lt;/script&gt;',
         }
-        assert_equal(r.json, expected)
+        assert r.json == expected
 
     def test_register_phone_not_verified(self):
         with h.push_config(config, **{'project.verify_phone': 'true'}):
@@ -1105,11 +1105,11 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
                 extra_environ=dict(username='test-user'),
                 antispam=True)
             overlay = r.html.find('div', {'id': 'phone_verification_overlay'})
-            assert_not_equal(overlay, None)
+            assert overlay != None
             header = overlay.find('h2')
             iframe = overlay.find('iframe')
-            assert_equal(header.getText(), 'Phone Verification Required')
-            assert_equal(iframe.get('src'), '/p/phone_verification_fragment')
+            assert header.getText() == 'Phone Verification Required'
+            assert iframe.get('src') == '/p/phone_verification_fragment'
 
 
 class TestProjectImport(TestController):
diff --git a/Allura/allura/tests/functional/test_personal_dashboard.py b/Allura/allura/tests/functional/test_personal_dashboard.py
index 411704195..ea28f2ff0 100644
--- a/Allura/allura/tests/functional/test_personal_dashboard.py
+++ b/Allura/allura/tests/functional/test_personal_dashboard.py
@@ -36,12 +36,12 @@ class TestPersonalDashboard(TestController):
 
     def test_dashboard(self):
         r = self.app.get('/dashboard')
-        assert_equal('Test Admin / Dashboard', r.html.find('h1', 'project_title').text.strip())
+        assert 'Test Admin / Dashboard' == r.html.find('h1', 'project_title').text.strip()
         sections = {c for s in r.html.findAll(None, 'profile-section') for c in s['class']}
-        assert_in('tickets', sections)
-        assert_in('projects', sections)
-        assert_in('merge_requests', sections)
-        assert_in('activity', sections)
+        assert 'tickets' in sections
+        assert 'projects' in sections
+        assert 'merge_requests' in sections
+        assert 'activity' in sections
 
     def test_dashboard_sections(self):
         def ep(n):
@@ -55,17 +55,17 @@ class TestPersonalDashboard(TestController):
             with mock.patch.dict(tg.config, order):
                 iep.return_value = eps
                 sections = SectionsUtil.load_sections('personal_dashboard')
-                assert_equal(sections, [
+                assert sections == [
                     eps[1].load(),
                     eps[3].load(),
                     eps[2].load(),
-                    eps[0].load()])
+                    eps[0].load()]
                 r = self.app.get('/dashboard')
-                assert_in('Section a', r.text)
-                assert_in('Section b', r.text)
-                assert_in('Section c', r.text)
-                assert_in('Section d', r.text)
-                assert_not_in('Section f', r.text)
+                assert 'Section a' in r.text
+                assert 'Section b' in r.text
+                assert 'Section c' in r.text
+                assert 'Section d' in r.text
+                assert 'Section f' not in r.text
 
 
 class TestTicketsSection(TrackerTestController):
@@ -80,7 +80,7 @@ class TestTicketsSection(TrackerTestController):
     def test_tickets_section(self):
         response = self.app.get('/dashboard')
         ticket_rows = response.html.find('tbody')
-        assert_in('foo', str(ticket_rows))
+        assert 'foo' in str(ticket_rows)
 
 
 class TestMergeRequestsSection(TestController):
@@ -122,4 +122,4 @@ class TestMergeRequestsSection(TestController):
     def test_merge_requests_section(self):
         r = self.app.get('/dashboard')
         merge_req_rows = r.html.find('tbody')
-        assert_in('test request', str(merge_req_rows))
+        assert 'test request' in str(merge_req_rows)
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index 94b009fd5..294c0d60e 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -91,7 +91,7 @@ class TestRestHome(TestRestApiBase):
         request.scheme = 'https'
         request.path = '/rest/p/test/wiki'
         r = self.api_post('/rest/p/test/wiki', access_token='foo')
-        assert_equal(r.status_int, 200)
+        assert r.status_int == 200
 
     @mock.patch('allura.controllers.rest.M.OAuthAccessToken')
     @mock.patch('allura.controllers.rest.request')
@@ -181,13 +181,13 @@ class TestRestHome(TestRestApiBase):
 
     def test_project_data(self):
         r = self.api_get('/rest/p/test/')
-        assert_equal(r.json['shortname'], 'test')
-        assert_equal(r.json['name'], 'Test Project')
-        assert_equal(len(r.json['developers']), 1)
+        assert r.json['shortname'] == 'test'
+        assert r.json['name'] == 'Test Project'
+        assert len(r.json['developers']) == 1
         admin_dev = r.json['developers'][0]
-        assert_equal(admin_dev['username'], 'test-admin')
-        assert_equal(admin_dev['name'], 'Test Admin')
-        assert_equal(admin_dev['url'], 'http://localhost/u/test-admin/')
+        assert admin_dev['username'] == 'test-admin'
+        assert admin_dev['name'] == 'Test Admin'
+        assert admin_dev['url'] == 'http://localhost/u/test-admin/'
 
     @td.with_tool('test', 'Tickets', 'bugs')
     @td.with_tool('test', 'Tickets', 'private-bugs')
@@ -202,18 +202,18 @@ class TestRestHome(TestRestApiBase):
 
         # admin sees both 'Tickets' tools
         r = self.api_get('/rest/p/test/')
-        assert_equal(r.json['shortname'], 'test')
+        assert r.json['shortname'] == 'test'
         tool_mounts = [t['mount_point'] for t in r.json['tools']]
-        assert_in('bugs', tool_mounts)
-        assert_in('private-bugs', tool_mounts)
+        assert 'bugs' in tool_mounts
+        assert 'private-bugs' in tool_mounts
 
         # anonymous sees only non-private tool
         r = self.app.get('/rest/p/test/',
                          extra_environ={'username': '*anonymous'})
-        assert_equal(r.json['shortname'], 'test')
+        assert r.json['shortname'] == 'test'
         tool_mounts = [t['mount_point'] for t in r.json['tools']]
-        assert_in('bugs', tool_mounts)
-        assert_not_in('private-bugs', tool_mounts)
+        assert 'bugs' in tool_mounts
+        assert 'private-bugs' not in tool_mounts
 
     def test_neighborhood_has_access_no_params(self):
         r = self.api_get('/rest/p/has_access', status=404)
@@ -225,13 +225,13 @@ class TestRestHome(TestRestApiBase):
         r = self.api_get(
             '/rest/p/has_access?user=babadook&perm=read',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], False)
+        assert r.status_int == 200
+        assert r.json['result'] == False
         r = self.api_get(
             '/rest/p/has_access?user=test-admin&perm=jump',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], False)
+        assert r.status_int == 200
+        assert r.json['result'] == False
 
     def test_neighborhood_has_access_not_admin(self):
         """
@@ -247,26 +247,26 @@ class TestRestHome(TestRestApiBase):
         r = self.api_get(
             '/rest/p/has_access?user=root&perm=update',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], True)
+        assert r.status_int == 200
+        assert r.json['result'] == True
         r = self.api_get(
             '/rest/p/has_access?user=test-user&perm=update',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], False)
+        assert r.status_int == 200
+        assert r.json['result'] == False
 
     def test_neighborhood(self):
         self.api_get('/rest/p/', status=404)
 
     def test_neighborhood_tools(self):
         r = self.api_get('/rest/p/wiki/Home/')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['title'], 'Home')
+        assert r.status_int == 200
+        assert r.json['title'] == 'Home'
 
         r = self.api_get('/rest/p/admin/installable_tools', status=403)
 
         r = self.api_get('/rest/p/admin/installable_tools', user='root')
-        assert_equal(r.status_int, 200)
+        assert r.status_int == 200
         assert [t for t in r.json['tools'] if t['tool_label'] == 'Wiki'], r.json
 
     def test_project_has_access_no_params(self):
@@ -279,13 +279,13 @@ class TestRestHome(TestRestApiBase):
         r = self.api_get(
             '/rest/p/test/has_access?user=babadook&perm=read',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], False)
+        assert r.status_int == 200
+        assert r.json['result'] == False
         r = self.api_get(
             '/rest/p/test/has_access?user=test-admin&perm=jump',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], False)
+        assert r.status_int == 200
+        assert r.json['result'] == False
 
     def test_project_has_access_not_admin(self):
         """
@@ -301,20 +301,20 @@ class TestRestHome(TestRestApiBase):
         r = self.api_get(
             '/rest/p/test/has_access?user=test-admin&perm=update',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], True)
+        assert r.status_int == 200
+        assert r.json['result'] == True
         r = self.api_get(
             '/rest/p/test/has_access?user=test-user&perm=update',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], False)
+        assert r.status_int == 200
+        assert r.json['result'] == False
 
     def test_subproject_has_access(self):
         r = self.api_get(
             '/rest/p/test/sub1/has_access?user=test-admin&perm=update',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], True)
+        assert r.status_int == 200
+        assert r.json['result'] == True
 
     def test_unicode(self):
         self.app.post(
@@ -357,13 +357,13 @@ class TestRestHome(TestRestApiBase):
         }
         with mock.patch.dict(g.entry_points, eps):
             response = self.app.get('/rest/')
-            assert_equal(response.json, {
+            assert response.json == {
                 'site_stats': {
                     'foo_24hr': 42,
                     'bar_24hr': 84,
                     'qux_24hr': 0,
                 },
-            })
+            }
 
     def test_name_validation(self):
         r = self.api_get('/rest/p/test/')
@@ -442,7 +442,7 @@ class TestRestNbhdAddProject(TestRestApiBase):
                           params=json.dumps(project_data),
                           user='root',
                           status=400)
-        assert_in('Required', r.json['error'])
+        assert 'Required' in r.json['error']
 
     def test_add_project_bad_data(self):
         project_data = {
@@ -455,7 +455,7 @@ class TestRestNbhdAddProject(TestRestApiBase):
                           params=json.dumps(project_data),
                           user='root',
                           status=400)
-        assert_in('is not a valid trove category', r.json['error'])
+        assert 'is not a valid trove category' in r.json['error']
 
         # local icon paths are not allowed through web (have to use icon_url)
         project_data = {
@@ -468,7 +468,7 @@ class TestRestNbhdAddProject(TestRestApiBase):
                           params=json.dumps(project_data),
                           user='root',
                           status=400)
-        assert_in('Unrecognized keys in mapping', r.json['error'])
+        assert 'Unrecognized keys in mapping' in r.json['error']
 
     def test_add_project_name_error(self):
         project_data = {
@@ -478,7 +478,7 @@ class TestRestNbhdAddProject(TestRestApiBase):
                           params=json.dumps(project_data),
                           user='root',
                           status=400)
-        assert_equal('This project name is taken.', r.json['error'])
+        assert 'This project name is taken.' == r.json['error']
 
         project_data = {
             "shortname": "/?xyz",
@@ -487,7 +487,7 @@ class TestRestNbhdAddProject(TestRestApiBase):
                           params=json.dumps(project_data),
                           user='root',
                           status=400)
-        assert_in('Please use', r.json['error'])
+        assert 'Please use' in r.json['error']
 
     def test_add_project_create_error(self):
         project_data = {
@@ -500,7 +500,7 @@ class TestRestNbhdAddProject(TestRestApiBase):
                           params=json.dumps(project_data),
                           user='root',
                           status=400)
-        assert_equal("You can't create private projects in the Projects neighborhood", r.json['error'])
+        assert "You can't create private projects in the Projects neighborhood" == r.json['error']
 
     def test_add_project(self):
         project_data = {
@@ -527,18 +527,18 @@ class TestRestNbhdAddProject(TestRestApiBase):
                           params=json.dumps(project_data),
                           user='root',
                           status=201)
-        assert_equal(r.json, {
+        assert r.json == {
             'status': 'success',
             'html_url': 'http://localhost/p/my-new-proj/',
             'url': 'http://localhost/rest/p/my-new-proj/',
-        })
+        }
         p = M.Project.query.get(shortname='my-new-proj')
-        assert_equal(p.name, 'My New Project')
-        assert_equal(len(p.trove_license), 2)
+        assert p.name == 'My New Project'
+        assert len(p.trove_license) == 2
         assert isinstance(p.trove_license[0], ObjectId), p.trove_license[0]
         # these are sensitive fields only admins should be able to set:
-        assert_equal(p.get_tool_data('allura', 'grouping_threshold'), 5)
-        assert_equal(p.admins()[0].username, 'test-admin')
+        assert p.get_tool_data('allura', 'grouping_threshold') == 5
+        assert p.admins()[0].username == 'test-admin'
 
     def test_add_project_automatic_shortname(self):
         # no shortname given, and name "Test" would conflict with existing "test" project
@@ -550,11 +550,11 @@ class TestRestNbhdAddProject(TestRestApiBase):
                           params=json.dumps(project_data),
                           user='root',
                           status=201)
-        assert_equal(r.json, {
+        assert r.json == {
             'status': 'success',
             'html_url': 'http://localhost/p/test1/',
             'url': 'http://localhost/rest/p/test1/',
-        })
+        }
 
 
 class TestDoap(TestRestApiBase):
@@ -571,22 +571,22 @@ class TestDoap(TestRestApiBase):
         project.short_description = 'A Short Description'
         ThreadLocalODMSession.flush_all()
         r = self.app.get('/rest/p/test?doap')
-        assert_equal(r.content_type, 'application/rdf+xml')
+        assert r.content_type == 'application/rdf+xml'
         p = r.xml.find(self.ns + 'Project')
-        assert_equal(p.attrib[self.rdf + 'about'], 'http://localhost/rest/p/test?doap#')
-        assert_equal(p.find(self.ns + 'name').text, 'test')
-        assert_equal(p.find(self.dc + 'title').text, 'Test Project')
-        assert_equal(p.find(self.ns_sf + 'private').text, '0')
-        assert_equal(p.find(self.ns + 'shortdesc').text, 'A Summary')
-        assert_equal(p.find(self.ns + 'description').text, 'A Short Description')
-        assert_equal(p.find(self.ns + 'created').text, project._id.generation_time.strftime('%Y-%m-%d'))
+        assert p.attrib[self.rdf + 'about'] == 'http://localhost/rest/p/test?doap#'
+        assert p.find(self.ns + 'name').text == 'test'
+        assert p.find(self.dc + 'title').text == 'Test Project'
+        assert p.find(self.ns_sf + 'private').text == '0'
+        assert p.find(self.ns + 'shortdesc').text == 'A Summary'
+        assert p.find(self.ns + 'description').text == 'A Short Description'
+        assert p.find(self.ns + 'created').text == project._id.generation_time.strftime('%Y-%m-%d')
 
         maintainers = p.findall(self.ns + 'maintainer')
-        assert_equal(len(maintainers), 1)
+        assert len(maintainers) == 1
         user = maintainers[0].find(self.foaf + 'Person')
-        assert_equal(user.find(self.foaf + 'name').text, 'Test Admin')
-        assert_equal(user.find(self.foaf + 'nick').text, 'test-admin')
-        assert_equal(list(user.find(self.foaf + 'homepage').items())[0][1],
+        assert user.find(self.foaf + 'name').text == 'Test Admin'
+        assert user.find(self.foaf + 'nick').text == 'test-admin'
+        assert (list(user.find(self.foaf + 'homepage').items())[0][1] ==
                      'http://localhost/u/test-admin/')
 
     @td.with_tool('test', 'Tickets', 'bugs')
@@ -607,8 +607,8 @@ class TestDoap(TestRestApiBase):
         tools = [(t.find(self.ns_sf + 'Feature').find(self.ns + 'name').text,
                   list(t.find(self.ns_sf + 'Feature').find(self.foaf + 'page').items())[0][1])
                  for t in tools]
-        assert_in(('Tickets', 'http://localhost/p/test/bugs/'), tools)
-        assert_in(('Tickets', 'http://localhost/p/test/private-bugs/'), tools)
+        assert ('Tickets', 'http://localhost/p/test/bugs/') in tools
+        assert ('Tickets', 'http://localhost/p/test/private-bugs/') in tools
 
         # anonymous sees only non-private tool
         r = self.app.get('/rest/p/test?doap',
@@ -618,25 +618,25 @@ class TestDoap(TestRestApiBase):
         tools = [(t.find(self.ns_sf + 'Feature').find(self.ns + 'name').text,
                   list(t.find(self.ns_sf + 'Feature').find(self.foaf + 'page').items())[0][1])
                  for t in tools]
-        assert_in(('Tickets', 'http://localhost/p/test/bugs/'), tools)
-        assert_not_in(('Tickets', 'http://localhost/p/test/private-bugs/'), tools)
+        assert ('Tickets', 'http://localhost/p/test/bugs/') in tools
+        assert ('Tickets', 'http://localhost/p/test/private-bugs/') not in tools
 
 
 class TestUserProfile(TestRestApiBase):
     @td.with_user_project('test-admin')
     def test_profile_data(self):
         r = self.app.get('/rest/u/test-admin/profile/')
-        assert_equal(r.content_type, 'application/json')
+        assert r.content_type == 'application/json'
         json = r.json
-        assert_equal(json['username'], 'test-admin')
-        assert_equal(json['name'], 'Test Admin')
-        assert_in('availability', json)
-        assert_in('joined', json)
-        assert_in('localization', json)
-        assert_in('projects', json)
-        assert_in('sex', json)
-        assert_in('skills', json)
-        assert_in('skypeaccount', json)
-        assert_in('socialnetworks', json)
-        assert_in('telnumbers', json)
-        assert_in('webpages', json)
+        assert json['username'] == 'test-admin'
+        assert json['name'] == 'Test Admin'
+        assert 'availability' in json
+        assert 'joined' in json
+        assert 'localization' in json
+        assert 'projects' in json
+        assert 'sex' in json
+        assert 'skills' in json
+        assert 'skypeaccount' in json
+        assert 'socialnetworks' in json
+        assert 'telnumbers' in json
+        assert 'webpages' in json
diff --git a/Allura/allura/tests/functional/test_root.py b/Allura/allura/tests/functional/test_root.py
index 83eadf9c5..47d6d23a0 100644
--- a/Allura/allura/tests/functional/test_root.py
+++ b/Allura/allura/tests/functional/test_root.py
@@ -55,15 +55,15 @@ class TestRootController(TestController):
 
     def test_index(self):
         response = self.app.get('/', extra_environ=dict(username='*anonymous'))
-        assert_equal(response.location, 'http://localhost/neighborhood')
+        assert response.location == 'http://localhost/neighborhood'
 
         response = self.app.get('/')
-        assert_equal(response.location, 'http://localhost/dashboard')
+        assert response.location == 'http://localhost/dashboard'
 
     def test_neighborhood(self):
         response = self.app.get('/neighborhood')
-        assert_equal(response.html.find('h2', {'class': 'dark title'}).find('span').contents[
-                     0].strip(), 'All Neighborhoods')
+        assert response.html.find('h2', {'class': 'dark title'}).find('span').contents[
+                     0].strip() == 'All Neighborhoods'
         nbhds = response.html.findAll('div', {'class': 'nbhd_name'})
         assert nbhds[0].find('a').get('href') == '/adobe/'
         cat_links = response.html.find('div', {'id': 'sidebar'}).findAll('li')
@@ -136,7 +136,7 @@ class TestRootController(TestController):
 
         response = self.app.get('/adobe/')
         projects = response.html.findAll('div', {'class': 'list card proj_icon'})
-        assert_equal(len(projects), 2)
+        assert len(projects) == 2
         cat_links = response.html.find('div', {'id': 'sidebar'}).findAll('ul')[1].findAll('li')
         assert len(cat_links) == 3, cat_links
         assert cat_links[0].find('a').get('href') == '/adobe/browse/clustering'
@@ -178,7 +178,7 @@ class TestRootController(TestController):
             callable_name.return_value = 'foo'
             self.app.get('/p/')
             arg = callable_name.call_args[0][0]
-            assert_equal(arg.__wrapped__,
+            assert (arg.__wrapped__ ==
                          NeighborhoodController.index.__wrapped__)
             set_transaction_name.assert_called_with('foo', priority=2)
 
diff --git a/Allura/allura/tests/functional/test_site_admin.py b/Allura/allura/tests/functional/test_site_admin.py
index 819353de0..a68420a43 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -93,20 +93,20 @@ class TestSiteAdmin(TestController):
         ThreadLocalORMSession.flush_all()
         r = self.app.get('/nf/admin/new_projects', extra_environ=dict(
             username='root'))
-        assert_equal(len(r.html.find('table').findAll('tr')), count - 1)
+        assert len(r.html.find('table').findAll('tr')) == count - 1
 
     def test_new_projects_daterange_filtering(self):
         r = self.app.get('/nf/admin/new_projects', extra_environ=dict(
             username='root'))
         count = len(r.html.find('table').findAll('tr'))
-        assert_equal(count, 7)
+        assert count == 7
 
         filtr = r.forms[0]
         filtr['start-dt'] = '2000/01/01 10:10:10'
         filtr['end-dt'] = '2000/01/01 09:09:09'
         r = filtr.submit()
         count = len(r.html.find('table').findAll('tr'))
-        assert_equal(count, 1)  # only row with headers - no results
+        assert count == 1  # only row with headers - no results
 
     def test_reclone_repo_access(self):
         r = self.app.get('/nf/admin/reclone_repo', extra_environ=dict(
@@ -375,9 +375,9 @@ class TestProjectsSearch(TestController):
         search.site_admin_search.return_value = self.TEST_HIT
         r = self.app.get('/nf/admin/search_projects?q=fake&f=shortname')
         options = [o['value'] for o in r.html.findAll('option')]
-        assert_equal(options, ['shortname', 'name', '__custom__'])
+        assert options == ['shortname', 'name', '__custom__']
         ths = [th.text for th in r.html.findAll('th')]
-        assert_equal(ths, ['Short name', 'Full name', 'Registered', 'Deleted?', 'Details'])
+        assert ths == ['Short name', 'Full name', 'Registered', 'Deleted?', 'Details']
 
     @patch('allura.controllers.site_admin.search')
     def test_additional_fields(self, search):
@@ -386,9 +386,9 @@ class TestProjectsSearch(TestController):
                                       'search.project.additional_display_fields': 'url'}):
             r = self.app.get('/nf/admin/search_projects?q=fake&f=shortname')
         options = [o['value'] for o in r.html.findAll('option')]
-        assert_equal(options, ['shortname', 'name', 'private', 'url', '__custom__'])
+        assert options == ['shortname', 'name', 'private', 'url', '__custom__']
         ths = [th.text for th in r.html.findAll('th')]
-        assert_equal(ths, ['Short name', 'Full name', 'Registered', 'Deleted?', 'url', 'Details'])
+        assert ths == ['Short name', 'Full name', 'Registered', 'Deleted?', 'url', 'Details']
 
 
 class TestUsersSearch(TestController):
@@ -428,10 +428,10 @@ class TestUsersSearch(TestController):
         site_admin_search.return_value = self.TEST_HIT
         r = self.app.get('/nf/admin/search_users?q=fake&f=username')
         options = [o['value'] for o in r.html.findAll('option')]
-        assert_equal(options, ['username', 'display_name', '__custom__'])
+        assert options == ['username', 'display_name', '__custom__']
         ths = [th.text for th in r.html.findAll('th')]
-        assert_equal(ths, ['Username', 'Display name', 'Email', 'Registered',
-                           'Status', 'Details'])
+        assert ths == ['Username', 'Display name', 'Email', 'Registered',
+                           'Status', 'Details']
 
     @patch('allura.controllers.site_admin.search.site_admin_search')
     def test_additional_fields(self, site_admin_search):
@@ -440,10 +440,10 @@ class TestUsersSearch(TestController):
                                       'search.user.additional_display_fields': 'url'}):
             r = self.app.get('/nf/admin/search_users?q=fake&f=username')
         options = [o['value'] for o in r.html.findAll('option')]
-        assert_equal(options, ['username', 'display_name', 'email_addresses', 'url', '__custom__'])
+        assert options == ['username', 'display_name', 'email_addresses', 'url', '__custom__']
         ths = [th.text for th in r.html.findAll('th')]
-        assert_equal(ths, ['Username', 'Display name', 'Email', 'Registered',
-                           'Status', 'url', 'Details'])
+        assert ths == ['Username', 'Display name', 'Email', 'Registered',
+                           'Status', 'url', 'Details']
 
 
 class TestUserDetails(TestController):
@@ -462,22 +462,22 @@ class TestUserDetails(TestController):
                             'session_ip': '7.7.7.7'}
         r = self.app.get('/nf/admin/user/test-admin')
         # general info
-        assert_in('Username: test-admin', r)
-        assert_in('Full name: Test Admin', r)
-        assert_in('Registered: 2014-09-01 09:09:09', r)
+        assert 'Username: test-admin' in r
+        assert 'Full name: Test Admin' in r
+        assert 'Registered: 2014-09-01 09:09:09' in r
         # session info
-        assert_in('Date: 2014-09-02 06:06:06', r)
-        assert_in('IP: 8.8.8.8', r)
-        assert_in('UA: browser of the future 1.0', r)
-        assert_in('Date: 2014-09-12', r)
-        assert_in('IP: 7.7.7.7', r)
-        assert_in('UA: browser of the future 1.1', r)
+        assert 'Date: 2014-09-02 06:06:06' in r
+        assert 'IP: 8.8.8.8' in r
+        assert 'UA: browser of the future 1.0' in r
+        assert 'Date: 2014-09-12' in r
+        assert 'IP: 7.7.7.7' in r
+        assert 'UA: browser of the future 1.1' in r
         # list of projects
         projects = r.html.findAll('fieldset')[-1]
         projects = [e.getText() for e in projects.findAll('li')]
-        assert_in('Test 2\n\u2013\nAdmin\n', projects)
-        assert_in('Test Project\n\u2013\nAdmin\n', projects)
-        assert_in('Adobe project 1\n\u2013\nAdmin\n', projects)
+        assert 'Test 2\n\u2013\nAdmin\n' in projects
+        assert 'Test Project\n\u2013\nAdmin\n' in projects
+        assert 'Adobe project 1\n\u2013\nAdmin\n' in projects
 
     @patch('allura.model.auth.request')
     @patch('allura.lib.helpers.request')
@@ -487,162 +487,162 @@ class TestUserDetails(TestController):
         h.auditlog_user('test activity user 1')
         h.auditlog_user('test activity user 2', user=M.User.by_username('test-user-2'))
         r = self.app.get('/nf/admin/user/test-admin')
-        assert_in('Add comment', r)
-        assert_not_in('test activity', r)
+        assert 'Add comment' in r
+        assert 'test activity' not in r
         r = self.app.get('/nf/admin/user/test-user-1')
-        assert_in('test activity user 1', r)
-        assert_not_in('test activity user 2', r)
+        assert 'test activity user 1' in r
+        assert 'test activity user 2' not in r
         r = self.app.get('/nf/admin/user/test-user-2')
-        assert_not_in('test activity user 1', r)
-        assert_in('test activity user 2', r)
+        assert 'test activity user 1' not in r
+        assert 'test activity user 2' in r
 
     def test_add_audit_trail_entry_access(self):
         self.app.get('/nf/admin/user/add_audit_log_entry', status=404)  # GET is not allowed
         r = self.app.post('/nf/admin/user/add_audit_log_entry',
                           extra_environ={'username': '*anonymous'},
                           status=302)
-        assert_equal(r.location, 'http://localhost/auth/')
+        assert r.location == 'http://localhost/auth/'
 
     def test_add_comment(self):
         r = self.app.get('/nf/admin/user/test-user')
-        assert_not_in('Comment by test-admin: I was hêre!', r)
+        assert 'Comment by test-admin: I was hêre!' not in r
         form = [f for f in r.forms.values() if f.action.endswith('add_audit_trail_entry')][0]
-        assert_equal(form['username'].value, 'test-user')
+        assert form['username'].value == 'test-user'
         form['comment'] = 'I was hêre!'
         r = form.submit()
-        assert_in('Comment added', self.webflash(r))
+        assert 'Comment added' in self.webflash(r)
         r = self.app.get('/nf/admin/user/test-user')
-        assert_in('Comment by test-admin: I was hêre!', r)
+        assert 'Comment by test-admin: I was hêre!' in r
 
     def test_disable_user(self):
         # user was not pending
-        assert_equal(M.User.by_username('test-user-3').disabled, False)
-        assert_equal(M.User.by_username('test-user-3').pending, False)
+        assert M.User.by_username('test-user-3').disabled == False
+        assert M.User.by_username('test-user-3').pending == False
         r = self.app.get('/nf/admin/user/test-user-3')
         form = r.forms[0]
-        assert_equal(form['username'].value, 'test-user-3')
-        assert_equal(form['status'].value, 'enable')
+        assert form['username'].value == 'test-user-3'
+        assert form['status'].value == 'enable'
         form['status'].value = 'disable'
         with td.audits('Account disabled', user=True):
             r = form.submit()
-            assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in('User disabled', self.webflash(r))
-        assert_equal(M.User.by_username('test-user-3').disabled, True)
-        assert_equal(M.User.by_username('test-user-3').pending, False)
+            assert M.AuditLog.query.find().count() == 1
+        assert 'User disabled' in self.webflash(r)
+        assert M.User.by_username('test-user-3').disabled == True
+        assert M.User.by_username('test-user-3').pending == False
 
         # user was pending
         user = M.User.by_username('test-user-3')
         user.disabled = False
         user.pending = True
         ThreadLocalORMSession.flush_all()
-        assert_equal(M.User.by_username('test-user-3').disabled, False)
-        assert_equal(M.User.by_username('test-user-3').pending, True)
+        assert M.User.by_username('test-user-3').disabled == False
+        assert M.User.by_username('test-user-3').pending == True
         r = self.app.get('/nf/admin/user/test-user-3')
         form = r.forms[0]
-        assert_equal(form['username'].value, 'test-user-3')
-        assert_equal(form['status'].value, 'pending')
+        assert form['username'].value == 'test-user-3'
+        assert form['status'].value == 'pending'
         form['status'].value = 'disable'
         with td.audits('Account disabled', user=True):
             r = form.submit()
-            assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in('User disabled', self.webflash(r))
-        assert_equal(M.User.by_username('test-user-3').disabled, True)
-        assert_equal(M.User.by_username('test-user-3').pending, True)
+            assert M.AuditLog.query.find().count() == 1
+        assert 'User disabled' in self.webflash(r)
+        assert M.User.by_username('test-user-3').disabled == True
+        assert M.User.by_username('test-user-3').pending == True
 
     def test_enable_user(self):
         # user was not pending
         user = M.User.by_username('test-user-3')
         user.disabled = True
         ThreadLocalORMSession.flush_all()
-        assert_equal(M.User.by_username('test-user-3').disabled, True)
-        assert_equal(M.User.by_username('test-user-3').pending, False)
+        assert M.User.by_username('test-user-3').disabled == True
+        assert M.User.by_username('test-user-3').pending == False
         r = self.app.get('/nf/admin/user/test-user-3')
         form = r.forms[0]
-        assert_equal(form['username'].value, 'test-user-3')
-        assert_equal(form['status'].value, 'disable')
+        assert form['username'].value == 'test-user-3'
+        assert form['status'].value == 'disable'
         form['status'].value = 'enable'
         with td.audits('Account enabled', user=True):
             r = form.submit()
-            assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in('User enabled', self.webflash(r))
-        assert_equal(M.User.by_username('test-user-3').disabled, False)
-        assert_equal(M.User.by_username('test-user-3').pending, False)
+            assert M.AuditLog.query.find().count() == 1
+        assert 'User enabled' in self.webflash(r)
+        assert M.User.by_username('test-user-3').disabled == False
+        assert M.User.by_username('test-user-3').pending == False
 
         # user was pending
         user = M.User.by_username('test-user-3')
         user.disabled = False
         user.pending = True
         ThreadLocalORMSession.flush_all()
-        assert_equal(M.User.by_username('test-user-3').disabled, False)
-        assert_equal(M.User.by_username('test-user-3').pending, True)
+        assert M.User.by_username('test-user-3').disabled == False
+        assert M.User.by_username('test-user-3').pending == True
         r = self.app.get('/nf/admin/user/test-user-3')
         form = r.forms[0]
-        assert_equal(form['username'].value, 'test-user-3')
-        assert_equal(form['status'].value, 'pending')
+        assert form['username'].value == 'test-user-3'
+        assert form['status'].value == 'pending'
         form['status'].value = 'enable'
         with td.audits('Account activated', user=True):
             r = form.submit()
-            assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in('User enabled', self.webflash(r))
-        assert_equal(M.User.by_username('test-user-3').disabled, False)
-        assert_equal(M.User.by_username('test-user-3').pending, False)
+            assert M.AuditLog.query.find().count() == 1
+        assert 'User enabled' in self.webflash(r)
+        assert M.User.by_username('test-user-3').disabled == False
+        assert M.User.by_username('test-user-3').pending == False
 
         # user was pending and disabled
         user = M.User.by_username('test-user-3')
         user.disabled = True
         user.pending = True
         ThreadLocalORMSession.flush_all()
-        assert_equal(M.User.by_username('test-user-3').disabled, True)
-        assert_equal(M.User.by_username('test-user-3').pending, True)
+        assert M.User.by_username('test-user-3').disabled == True
+        assert M.User.by_username('test-user-3').pending == True
         r = self.app.get('/nf/admin/user/test-user-3')
         form = r.forms[0]
-        assert_equal(form['username'].value, 'test-user-3')
-        assert_equal(form['status'].value, 'disable')
+        assert form['username'].value == 'test-user-3'
+        assert form['status'].value == 'disable'
         form['status'].value = 'enable'
         with td.audits('Account enabled', user=True):
             r = form.submit()
-            assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in('User enabled', self.webflash(r))
-        assert_equal(M.User.by_username('test-user-3').disabled, False)
-        assert_equal(M.User.by_username('test-user-3').pending, False)
+            assert M.AuditLog.query.find().count() == 1
+        assert 'User enabled' in self.webflash(r)
+        assert M.User.by_username('test-user-3').disabled == False
+        assert M.User.by_username('test-user-3').pending == False
 
     def test_set_pending(self):
         # user was disabled
         user = M.User.by_username('test-user-3')
         user.disabled = True
         ThreadLocalORMSession.flush_all()
-        assert_equal(M.User.by_username('test-user-3').disabled, True)
-        assert_equal(M.User.by_username('test-user-3').pending, False)
+        assert M.User.by_username('test-user-3').disabled == True
+        assert M.User.by_username('test-user-3').pending == False
         r = self.app.get('/nf/admin/user/test-user-3')
         form = r.forms[0]
-        assert_equal(form['username'].value, 'test-user-3')
-        assert_equal(form['status'].value, 'disable')
+        assert form['username'].value == 'test-user-3'
+        assert form['status'].value == 'disable'
         form['status'].value = 'pending'
         with td.audits('Account changed to pending', user=True):
             r = form.submit()
-            assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in('Set user status to pending', self.webflash(r))
-        assert_equal(M.User.by_username('test-user-3').disabled, False)
-        assert_equal(M.User.by_username('test-user-3').pending, True)
+            assert M.AuditLog.query.find().count() == 1
+        assert 'Set user status to pending' in self.webflash(r)
+        assert M.User.by_username('test-user-3').disabled == False
+        assert M.User.by_username('test-user-3').pending == True
 
         # user was enabled
         user = M.User.by_username('test-user-3')
         user.pending = False
         user.disabled = False
         ThreadLocalORMSession.flush_all()
-        assert_equal(M.User.by_username('test-user-3').disabled, False)
-        assert_equal(M.User.by_username('test-user-3').pending, False)
+        assert M.User.by_username('test-user-3').disabled == False
+        assert M.User.by_username('test-user-3').pending == False
         r = self.app.get('/nf/admin/user/test-user-3')
         form = r.forms[0]
-        assert_equal(form['username'].value, 'test-user-3')
-        assert_equal(form['status'].value, 'enable')
+        assert form['username'].value == 'test-user-3'
+        assert form['status'].value == 'enable'
         form['status'].value = 'pending'
         with td.audits('Account changed to pending', user=True):
             r = form.submit()
-            assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in('Set user status to pending', self.webflash(r))
-        assert_equal(M.User.by_username('test-user-3').disabled, False)
-        assert_equal(M.User.by_username('test-user-3').pending, True)
+            assert M.AuditLog.query.find().count() == 1
+        assert 'Set user status to pending' in self.webflash(r)
+        assert M.User.by_username('test-user-3').disabled == False
+        assert M.User.by_username('test-user-3').pending == True
 
     def test_emails(self):
         # add test@example.com
@@ -654,11 +654,11 @@ class TestUserDetails(TestController):
                 'primary_addr': 'test@example.com'},
                 extra_environ=dict(username='test-admin'))
         r = self.app.get('/nf/admin/user/test-user')
-        assert_in('test@example.com', r)
+        assert 'test@example.com' in r
         em = M.EmailAddress.get(email='test@example.com')
-        assert_equal(em.confirmed, True)
+        assert em.confirmed == True
         user = M.User.query.get(username='test-user')
-        assert_equal(user.get_pref('email_address'), 'test@example.com')
+        assert user.get_pref('email_address') == 'test@example.com'
 
         # add test2@example.com
         with td.audits('New email address: test2@example.com', user=True):
@@ -669,11 +669,11 @@ class TestUserDetails(TestController):
                 'primary_addr': 'test@example.com'},
                 extra_environ=dict(username='test-admin'))
         r = self.app.get('/nf/admin/user/test-user')
-        assert_in('test2@example.com', r)
+        assert 'test2@example.com' in r
         em = M.EmailAddress.get(email='test2@example.com')
-        assert_equal(em.confirmed, True)
+        assert em.confirmed == True
         user = M.User.query.get(username='test-user')
-        assert_equal(user.get_pref('email_address'), 'test@example.com')
+        assert user.get_pref('email_address') == 'test@example.com'
 
         # change primary: test -> test2
         with td.audits('Primary email changed: test@example.com => test2@example.com', user=True):
@@ -684,7 +684,7 @@ class TestUserDetails(TestController):
                 extra_environ=dict(username='test-admin'))
         r = self.app.get('/nf/admin/user/test-user')
         user = M.User.query.get(username='test-user')
-        assert_equal(user.get_pref('email_address'), 'test2@example.com')
+        assert user.get_pref('email_address') == 'test2@example.com'
 
         # remove test2@example.com
         with td.audits('Email address deleted: test2@example.com', user=True):
@@ -700,14 +700,14 @@ class TestUserDetails(TestController):
         r = self.app.get('/nf/admin/user/test-user')
         user = M.User.query.get(username='test-user')
         # test@example.com set as primary since test2@example.com is deleted
-        assert_equal(user.get_pref('email_address'), 'test-user@allura.local')
+        assert user.get_pref('email_address') == 'test-user@allura.local'
 
     @patch.object(LocalAuthenticationProvider, 'set_password')
     def test_set_random_password(self, set_password):
         with td.audits('Set random password', user=True, actor='test-admin'):
             r = self.app.post('/nf/admin/user/set_random_password', params={'username': 'test-user'})
-        assert_in('Password is set', self.webflash(r))
-        assert_equal(set_password.call_count, 1)
+        assert 'Password is set' in self.webflash(r)
+        assert set_password.call_count == 1
 
     @patch('allura.tasks.mail_tasks.sendsimplemail')
     @patch('allura.lib.helpers.gen_message_id')
@@ -738,7 +738,7 @@ To update your password on %s, please visit the following URL:
             r = self.app.post('/nf/admin/user/make_password_reset_url', params={'username': 'test-user'})
         user = M.User.by_username('test-user')
         hash = user.get_tool_data('AuthPasswordReset', 'hash')
-        assert_in(hash, r.text)
+        assert hash in r.text
 
 
 class TestDeleteProjects(TestController):
@@ -751,14 +751,14 @@ class TestDeleteProjects(TestController):
 
     def test_projects_populated_from_get_params(self):
         r = self.app.get('/nf/admin/delete_projects/')
-        assert_equal(self.delete_form(r)['projects'].value, '')
+        assert self.delete_form(r)['projects'].value == ''
         link = '/nf/admin/delete_projects/?projects=/p/test/++++%23+comment%0A/adobe/adobe-1/%0A/p/test2/'
         link += '&reason=The%0AReason&disable_users=True'
         r = self.app.get(link)
         form = self.delete_form(r)
-        assert_equal(form['projects'].value, '/p/test/    # comment\n/adobe/adobe-1/\n/p/test2/')
-        assert_equal(form['reason'].value, 'The\nReason')
-        assert_equal(form['disable_users'].value, 'on')
+        assert form['projects'].value == '/p/test/    # comment\n/adobe/adobe-1/\n/p/test2/'
+        assert form['reason'].value == 'The\nReason'
+        assert form['disable_users'].value == 'on'
 
     def test_confirm_step_values(self):
         r = self.app.get('/nf/admin/delete_projects/')
@@ -769,16 +769,16 @@ class TestDeleteProjects(TestController):
         r = form.submit()
         confirm_form = self.confirm_form(r)
         for f in ['reason', 'disable_users']:
-            assert_equal(confirm_form[f].value, form[f].value)
-        assert_equal(confirm_form['projects'].value, 'p/test    # OK: /p/test/\ndne/dne    # Neighborhood not found')
+            assert confirm_form[f].value == form[f].value
+        assert confirm_form['projects'].value == 'p/test    # OK: /p/test/\ndne/dne    # Neighborhood not found'
 
         confirm_data = r.html.find('table').findAll('td')
-        assert_equal(len(confirm_data), 4)  # 2 projects == 2 rows (2 columns each)
-        assert_equal(confirm_data[0].getText(), 'p/test')
-        assert_equal(confirm_data[1].find('a').get('href'), '/p/test/')
-        assert_equal(confirm_data[1].getText().strip(), '/p/test/')
-        assert_equal(confirm_data[2].getText().strip(), 'dne/dne')
-        assert_equal(confirm_data[3].getText().strip(), 'Neighborhood not found')
+        assert len(confirm_data) == 4  # 2 projects == 2 rows (2 columns each)
+        assert confirm_data[0].getText() == 'p/test'
+        assert confirm_data[1].find('a').get('href') == '/p/test/'
+        assert confirm_data[1].getText().strip() == '/p/test/'
+        assert confirm_data[2].getText().strip() == 'dne/dne'
+        assert confirm_data[3].getText().strip() == 'Neighborhood not found'
 
     def test_confirm_step_edit_link(self):
         r = self.app.get('/nf/admin/delete_projects/')
diff --git a/Allura/allura/tests/functional/test_trovecategory.py b/Allura/allura/tests/functional/test_trovecategory.py
index fef314e54..c6a903833 100644
--- a/Allura/allura/tests/functional/test_trovecategory.py
+++ b/Allura/allura/tests/functional/test_trovecategory.py
@@ -39,25 +39,25 @@ class TestTroveCategory(TestController):
             r = self.app.post('/categories/create/', params=dict(categoryname='test'))
 
         category_id = post_event.call_args[0][1]
-        assert_true(isinstance(category_id, int))
-        assert_equals(post_event.call_args[0][0], 'trove_category_created')
+        assert isinstance(category_id, int)
+        assert post_event.call_args[0][0] == 'trove_category_created'
         category = M.TroveCategory.query.get(trove_cat_id=category_id)
 
         # Update event
         category.fullname = 'test2'
         session(M.TroveCategory).flush()
         edited_category_id = post_event.call_args[0][1]
-        assert_true(isinstance(edited_category_id, int))
-        assert_equals(edited_category_id, category_id)
-        assert_equals(post_event.call_args[0][0], 'trove_category_updated')
+        assert isinstance(edited_category_id, int)
+        assert edited_category_id == category_id
+        assert post_event.call_args[0][0] == 'trove_category_updated'
 
         # Delete event
         M.TroveCategory.delete(category)
         session(M.TroveCategory).flush()
         deleted_category_id = post_event.call_args[0][1]
-        assert_true(isinstance(deleted_category_id, int))
-        assert_equals(deleted_category_id, category_id)
-        assert_equals(post_event.call_args[0][0], 'trove_category_deleted')
+        assert isinstance(deleted_category_id, int)
+        assert deleted_category_id == category_id
+        assert post_event.call_args[0][0] == 'trove_category_deleted'
 
     def test_enableediting_setting(self):
         def check_access(username=None, status=None):
@@ -122,7 +122,7 @@ class TestTroveCategoryController(TestController):
             </ul>
         </ul>
         """.strip(), 'html.parser')
-        assert_equals(str(expected), str(rendered_tree))
+        assert str(expected) == str(rendered_tree)
 
     @td.with_tool('test2', 'admin_main', 'admin')
     def test_trove_empty_hierarchy(self):
@@ -132,24 +132,24 @@ class TestTroveCategoryController(TestController):
         <ul>
         </ul>
         """.strip(), 'html.parser')
-        assert_equals(str(expected), str(rendered_tree))
+        assert str(expected) == str(rendered_tree)
 
     def test_delete(self):
         self.create_some_cats()
         session(M.TroveCategory).flush()
-        assert_equals(5, M.TroveCategory.query.find().count())
+        assert 5 == M.TroveCategory.query.find().count()
 
         r = self.app.get('/categories/1')
         form = r.forms[0]
         r = form.submit()
-        assert_in("This category contains at least one sub-category, therefore it can't be removed",
+        assert ("This category contains at least one sub-category, therefore it can't be removed" in
                   self.webflash(r))
 
         r = self.app.get('/categories/2')
         form = r.forms[0]
         r = form.submit()
-        assert_in("Category removed", self.webflash(r))
-        assert_equals(4, M.TroveCategory.query.find().count())
+        assert "Category removed" in self.webflash(r)
+        assert 4 == M.TroveCategory.query.find().count()
 
     def test_create_parent(self):
         self.create_some_cats()
@@ -161,9 +161,9 @@ class TestTroveCategoryController(TestController):
         form.submit()
 
         possible = M.TroveCategory.query.find(dict(fullname='New Category')).all()
-        assert_equal(len(possible), 1)
-        assert_equal(possible[0].fullname, 'New Category')
-        assert_equal(possible[0].shortname, 'new-category')
+        assert len(possible) == 1
+        assert possible[0].fullname == 'New Category'
+        assert possible[0].shortname == 'new-category'
 
     def test_create_child(self):
         self.create_some_cats()
@@ -175,10 +175,10 @@ class TestTroveCategoryController(TestController):
         form.submit()
 
         possible =M.TroveCategory.query.find(dict(fullname='New Child')).all()
-        assert_equal(len(possible), 1)
-        assert_equal(possible[0].fullname, 'New Child')
-        assert_equal(possible[0].shortname, 'new-child')
-        assert_equal(possible[0].trove_parent_id, 2)
+        assert len(possible) == 1
+        assert possible[0].fullname == 'New Child'
+        assert possible[0].shortname == 'new-child'
+        assert possible[0].trove_parent_id == 2
 
         # test slugify with periods. the relevant form becomes the third, after a child has been created above.
         r = self.app.get('/categories/2')
@@ -186,7 +186,7 @@ class TestTroveCategoryController(TestController):
         form['categoryname'].value = "New Child.io"
         form.submit()
         possible = M.TroveCategory.query.find(dict(fullname='New Child.io')).all()
-        assert_equal(possible[0].shortname, 'new-child.io')
+        assert possible[0].shortname == 'new-child.io'
 
     def test_create_child_bad_upper(self):
         self.create_some_cats()
diff --git a/Allura/allura/tests/functional/test_user_profile.py b/Allura/allura/tests/functional/test_user_profile.py
index d8f6724c1..c3ec9f7e9 100644
--- a/Allura/allura/tests/functional/test_user_profile.py
+++ b/Allura/allura/tests/functional/test_user_profile.py
@@ -30,18 +30,18 @@ class TestUserProfile(TestController):
     @td.with_user_project('test-admin')
     def test_profile(self):
         r = self.app.get('/u/test-admin/profile/')
-        assert_equal('Test Admin',
+        assert ('Test Admin' ==
                      r.html.find('h1', 'project_title').find('a').text)
         sections = {c for s in r.html.findAll(None, 'profile-section') for c in s['class']}
-        assert_in('personal-data', sections)
-        assert_in('Username:\ntest-admin', r.html.find(None, 'personal-data').getText().replace(' ', ''))
-        assert_in('projects', sections)
-        assert_in('Test Project', r.html.find(None, 'projects').getText())
-        assert_in('Last Updated:', r.html.find(None, 'projects').getText())
-        assert_in('tools', sections)
-        assert_in('Admin', r.html.find(None, 'tools').getText())
-        assert_in('skills', sections)
-        assert_in('No skills entered', r.html.find(None, 'skills').getText())
+        assert 'personal-data' in sections
+        assert 'Username:\ntest-admin' in r.html.find(None, 'personal-data').getText().replace(' ', '')
+        assert 'projects' in sections
+        assert 'Test Project' in r.html.find(None, 'projects').getText()
+        assert 'Last Updated:' in r.html.find(None, 'projects').getText()
+        assert 'tools' in sections
+        assert 'Admin' in r.html.find(None, 'tools').getText()
+        assert 'skills' in sections
+        assert 'No skills entered' in r.html.find(None, 'skills').getText()
 
     @td.with_user_project('test-admin')
     @mock.patch.dict(tg.config, {'use_gravatar': 'true'})
@@ -80,9 +80,9 @@ class TestUserProfile(TestController):
 
         # and accessing it by "-" which was the previous way, will redirect
         response = self.app.get('/u/foo-bar/', status=302)
-        assert_equal(response.location, 'http://localhost/u/foo_bar/')
+        assert response.location == 'http://localhost/u/foo_bar/'
         response = self.app.get('/u/foo-bar/profile/xyz?a=b', status=302)
-        assert_equal(response.location, 'http://localhost/u/foo_bar/profile/xyz?a=b')
+        assert response.location == 'http://localhost/u/foo_bar/profile/xyz?a=b'
 
     def test_differing_profile_proj_shortname_rest_api(self):
         User.upsert('foo_bar')
@@ -246,21 +246,21 @@ class TestUserProfile(TestController):
             with mock.patch.dict(tg.config, order):
                 iep.return_value = eps
                 sections = app.profile_sections
-                assert_equal(sections, [
+                assert sections == [
                     eps[1].load(),
                     eps[3].load(),
                     eps[2].load(),
-                    eps[0].load()])
+                    eps[0].load()]
         r = self.app.get('/u/test-user/profile')
-        assert_in('Section a', r.text)
-        assert_in('Section b', r.text)
-        assert_in('Section c', r.text)
-        assert_in('Section d', r.text)
-        assert_not_in('Section f', r.text)
+        assert 'Section a' in r.text
+        assert 'Section b' in r.text
+        assert 'Section c' in r.text
+        assert 'Section d' in r.text
+        assert 'Section f' not in r.text
 
     def test_no_index_tag_in_empty_profile(self):
         r = self.app.get('/u/test-user/profile/')
-        assert_in('content="noindex, follow"', r.text)
+        assert 'content="noindex, follow"' in r.text
 
     def test_remove_no_index_tag_profile(self):
         r = self.app.get('/u/test-admin/profile/')
@@ -284,13 +284,13 @@ class TestUserProfileHasAccessAPI(TestRestApiBase):
         r = self.api_get(
             '/rest/u/test-admin/profile/has_access?user=babadook&perm=read',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], False)
+        assert r.status_int == 200
+        assert r.json['result'] == False
         r = self.api_get(
             '/rest/u/test-admin/profile/has_access?user=test-user&perm=jump',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], False)
+        assert r.status_int == 200
+        assert r.json['result'] == False
 
     @td.with_user_project('test-admin')
     def test_has_access_not_admin(self):
@@ -308,10 +308,10 @@ class TestUserProfileHasAccessAPI(TestRestApiBase):
         r = self.api_get(
             '/rest/u/test-admin/profile/has_access?user=test-admin&perm=admin',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], True)
+        assert r.status_int == 200
+        assert r.json['result'] == True
         r = self.api_get(
             '/rest/u/test-admin/profile/has_access?user=test-user&perm=admin',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], False)
+        assert r.status_int == 200
+        assert r.json['result'] == False
diff --git a/Allura/allura/tests/model/test_artifact.py b/Allura/allura/tests/model/test_artifact.py
index 530f7c7c0..b851bb8f3 100644
--- a/Allura/allura/tests/model/test_artifact.py
+++ b/Allura/allura/tests/model/test_artifact.py
@@ -203,7 +203,7 @@ def test_last_updated(_datetime):
     _datetime.utcnow.return_value = datetime(2014, 1, 2)
     WM.Page(title='TestPage1')
     ThreadLocalORMSession.flush_all()
-    assert_equal(c.project.last_updated, datetime(2014, 1, 2))
+    assert c.project.last_updated == datetime(2014, 1, 2)
 
 
 @with_setup(setUp, tearDown)
@@ -215,7 +215,7 @@ def test_last_updated_disabled(_datetime):
         M.artifact_orm_session._get().skip_last_updated = True
         WM.Page(title='TestPage1')
         ThreadLocalORMSession.flush_all()
-        assert_equal(c.project.last_updated, datetime(2014, 1, 1))
+        assert c.project.last_updated == datetime(2014, 1, 1)
     finally:
         M.artifact_orm_session._get().skip_last_updated = False
 
@@ -235,12 +235,12 @@ def test_get_discussion_thread_dupe():
     thr4 = M.Thread.new(ref_id=thr1.ref_id)
 
     thread_q = M.Thread.query.find(dict(ref_id=artif.index_id()))
-    assert_equal(thread_q.count(), 4)
+    assert thread_q.count() == 4
 
     thread = artif.get_discussion_thread()[0]  # force cleanup
     threads = thread_q.all()
-    assert_equal(len(threads), 1)
-    assert_equal(len(thread.posts), 6)
+    assert len(threads) == 1
+    assert len(thread.posts) == 6
     assert not any(p.deleted for p in thread.posts)  # normal thread deletion propagates to children, make sure that doesn't happen
 
 
@@ -249,11 +249,11 @@ def test_snapshot_clear_user_data():
                            'display_name': 'John Doe',
                            'logged_ip': '1.2.3.4'})
     s.clear_user_data()
-    assert_equal(s.author, {'username': '',
+    assert s.author == {'username': '',
                             'display_name': '',
                             'logged_ip': None,
                             'id': None,
-                            })
+                            }
 
 
 @with_setup(setUp, tearDown)
@@ -265,7 +265,7 @@ def test_snapshot_from_username():
                            'display_name': 'John Doe',
                            'logged_ip': '1.2.3.4'})
     ThreadLocalORMSession.flush_all()
-    assert_equal(len(M.Snapshot.from_username('johndoe')), 1)
+    assert len(M.Snapshot.from_username('johndoe')) == 1
 
 
 def test_feed_clear_user_data():
@@ -273,17 +273,17 @@ def test_feed_clear_user_data():
                author_link='/u/johndoe/',
                title='Something')
     f.clear_user_data()
-    assert_equal(f.author_name, '')
-    assert_equal(f.author_link, '')
-    assert_equal(f.title, 'Something')
+    assert f.author_name == ''
+    assert f.author_link == ''
+    assert f.title == 'Something'
 
     f = M.Feed(author_name='John Doe',
                author_link='/u/johndoe/',
                title='Home Page modified by John Doe')
     f.clear_user_data()
-    assert_equal(f.author_name, '')
-    assert_equal(f.author_link, '')
-    assert_equal(f.title, 'Home Page modified by <REDACTED>')
+    assert f.author_name == ''
+    assert f.author_link == ''
+    assert f.title == 'Home Page modified by <REDACTED>'
 
 
 @with_setup(setUp, tearDown)
@@ -295,7 +295,7 @@ def test_feed_from_username():
            author_link='/u/johnsmith/',
            title='Something')
     ThreadLocalORMSession.flush_all()
-    assert_equal(len(M.Feed.from_username('johndoe')), 1)
+    assert len(M.Feed.from_username('johndoe')) == 1
 
 
 @with_setup(setUp, tearDown)
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index 90e39884d..6250d650e 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -77,42 +77,42 @@ def test_email_address_lookup_helpers():
     addr = M.EmailAddress.create('TEST@DOMAIN.NET')
     nobody = M.EmailAddress.create('nobody@example.com')
     ThreadLocalORMSession.flush_all()
-    assert_equal(addr.email, 'TEST@domain.net')
+    assert addr.email == 'TEST@domain.net'
 
-    assert_equal(M.EmailAddress.get(email='TEST@DOMAIN.NET'), addr)
-    assert_equal(M.EmailAddress.get(email='TEST@domain.net'), addr)
-    assert_equal(M.EmailAddress.get(email='test@domain.net'), None)
-    assert_equal(M.EmailAddress.get(email=None), None)
-    assert_equal(M.EmailAddress.get(email='nobody@example.com'), nobody)
+    assert M.EmailAddress.get(email='TEST@DOMAIN.NET') == addr
+    assert M.EmailAddress.get(email='TEST@domain.net') == addr
+    assert M.EmailAddress.get(email='test@domain.net') == None
+    assert M.EmailAddress.get(email=None) == None
+    assert M.EmailAddress.get(email='nobody@example.com') == nobody
     # invalid email returns None, but not nobody@example.com as before
-    assert_equal(M.EmailAddress.get(email='invalid'), None)
+    assert M.EmailAddress.get(email='invalid') == None
 
-    assert_equal(M.EmailAddress.find(dict(email='TEST@DOMAIN.NET')).all(), [addr])
-    assert_equal(M.EmailAddress.find(dict(email='TEST@domain.net')).all(), [addr])
-    assert_equal(M.EmailAddress.find(dict(email='test@domain.net')).all(), [])
-    assert_equal(M.EmailAddress.find(dict(email=None)).all(), [])
-    assert_equal(M.EmailAddress.find(dict(email='nobody@example.com')).all(), [nobody])
+    assert M.EmailAddress.find(dict(email='TEST@DOMAIN.NET')).all() == [addr]
+    assert M.EmailAddress.find(dict(email='TEST@domain.net')).all() == [addr]
+    assert M.EmailAddress.find(dict(email='test@domain.net')).all() == []
+    assert M.EmailAddress.find(dict(email=None)).all() == []
+    assert M.EmailAddress.find(dict(email='nobody@example.com')).all() == [nobody]
     # invalid email returns empty query, but not nobody@example.com as before
-    assert_equal(M.EmailAddress.find(dict(email='invalid')).all(), [])
+    assert M.EmailAddress.find(dict(email='invalid')).all() == []
 
 
 @with_setup(setUp)
 def test_email_address_canonical():
-    assert_equal(M.EmailAddress.canonical('nobody@EXAMPLE.COM'),
+    assert (M.EmailAddress.canonical('nobody@EXAMPLE.COM') ==
                  'nobody@example.com')
-    assert_equal(M.EmailAddress.canonical('nobody@example.com'),
+    assert (M.EmailAddress.canonical('nobody@example.com') ==
                  'nobody@example.com')
-    assert_equal(M.EmailAddress.canonical('I Am Nobody <no...@example.com>'),
+    assert (M.EmailAddress.canonical('I Am Nobody <no...@example.com>') ==
                  'nobody@example.com')
-    assert_equal(M.EmailAddress.canonical('  nobody@example.com\t'),
+    assert (M.EmailAddress.canonical('  nobody@example.com\t') ==
                  'nobody@example.com')
-    assert_equal(M.EmailAddress.canonical('I Am@Nobody <no...@example.com> '),
+    assert (M.EmailAddress.canonical('I Am@Nobody <no...@example.com> ') ==
                  'nobody@example.com')
-    assert_equal(M.EmailAddress.canonical(' No@body <no...@example.com> '),
+    assert (M.EmailAddress.canonical(' No@body <no...@example.com> ') ==
                  'no@body@example.com')
-    assert_equal(M.EmailAddress.canonical('no@body@example.com'),
+    assert (M.EmailAddress.canonical('no@body@example.com') ==
                  'no@body@example.com')
-    assert_equal(M.EmailAddress.canonical('invalid'), None)
+    assert M.EmailAddress.canonical('invalid') == None
 
 @with_setup(setUp)
 def test_email_address_send_verification_link():
@@ -124,7 +124,7 @@ def test_email_address_send_verification_link():
     with patch('allura.tasks.mail_tasks.smtp_client._client') as _client:
         M.MonQTask.run_ready()
     return_path, rcpts, body = _client.sendmail.call_args[0]
-    assert_equal(rcpts, ['test_admin@domain.net'])
+    assert rcpts == ['test_admin@domain.net']
 
 
 @with_setup(setUp)
@@ -132,19 +132,19 @@ def test_email_address_send_verification_link():
 def test_user():
     assert c.user.url() .endswith('/u/test-admin/')
     assert c.user.script_name .endswith('/u/test-admin/')
-    assert_equal({p.shortname for p in c.user.my_projects()},
+    assert ({p.shortname for p in c.user.my_projects()} ==
                  {'test', 'test2', 'u/test-admin', 'adobe-1', '--init--'})
     # delete one of the projects and make sure it won't appear in my_projects()
     p = M.Project.query.get(shortname='test2')
     p.deleted = True
     ThreadLocalORMSession.flush_all()
-    assert_equal({p.shortname for p in c.user.my_projects()},
+    assert ({p.shortname for p in c.user.my_projects()} ==
                  {'test', 'u/test-admin', 'adobe-1', '--init--'})
     u = M.User.register(dict(
         username='nosetest-user'))
     ThreadLocalORMSession.flush_all()
     assert u.reg_date
-    assert_equal(u.private_project().shortname, 'u/nosetest-user')
+    assert u.private_project().shortname == 'u/nosetest-user'
     roles = g.credentials.user_roles(
         u._id, project_id=u.private_project().root_project._id)
     assert len(roles) == 3, roles
@@ -208,40 +208,40 @@ def test_user_by_email_address(log):
     # both users are disabled
     u1.disabled, u2.disabled = True, True
     ThreadLocalORMSession.flush_all()
-    assert_equal(M.User.by_email_address('abc123@abc.me'), None)
-    assert_equal(log.warn.call_count, 0)
+    assert M.User.by_email_address('abc123@abc.me') == None
+    assert log.warn.call_count == 0
 
     # only u2 is active
     u1.disabled, u2.disabled = True, False
     ThreadLocalORMSession.flush_all()
-    assert_equal(M.User.by_email_address('abc123@abc.me'), u2)
-    assert_equal(log.warn.call_count, 0)
+    assert M.User.by_email_address('abc123@abc.me') == u2
+    assert log.warn.call_count == 0
 
     # both are active
     u1.disabled, u2.disabled = False, False
     ThreadLocalORMSession.flush_all()
-    assert_in(M.User.by_email_address('abc123@abc.me'), [u1, u2])
-    assert_equal(log.warn.call_count, 1)
+    assert M.User.by_email_address('abc123@abc.me') in [u1, u2]
+    assert log.warn.call_count == 1
 
     # invalid email returns None, but not user which claimed
     # nobody@example.com as before
     nobody = M.EmailAddress(email='nobody@example.com', confirmed=True,
                             claimed_by_user_id=u1._id)
     ThreadLocalORMSession.flush_all()
-    assert_equal(M.User.by_email_address('nobody@example.com'), u1)
-    assert_equal(M.User.by_email_address('invalid'), None)
+    assert M.User.by_email_address('nobody@example.com') == u1
+    assert M.User.by_email_address('invalid') == None
 
 
 def test_user_equality():
-    assert_equal(M.User.by_username('test-user'), M.User.by_username('test-user'))
-    assert_equal(M.User.anonymous(), M.User.anonymous())
-    assert_equal(M.User.by_username('*anonymous'), M.User.anonymous())
+    assert M.User.by_username('test-user') == M.User.by_username('test-user')
+    assert M.User.anonymous() == M.User.anonymous()
+    assert M.User.by_username('*anonymous') == M.User.anonymous()
 
-    assert_not_equal(M.User.by_username('test-user'), M.User.by_username('test-admin'))
-    assert_not_equal(M.User.by_username('test-user'), M.User.anonymous())
-    assert_not_equal(M.User.anonymous(), None)
-    assert_not_equal(M.User.anonymous(), 12345)
-    assert_not_equal(M.User.anonymous(), M.User())
+    assert M.User.by_username('test-user') != M.User.by_username('test-admin')
+    assert M.User.by_username('test-user') != M.User.anonymous()
+    assert M.User.anonymous() != None
+    assert M.User.anonymous() != 12345
+    assert M.User.anonymous() != M.User()
 
 
 def test_user_hash():
@@ -300,9 +300,9 @@ def test_email_address_claimed_by_user():
 @with_setup(setUp)
 @td.with_user_project('test-admin')
 def test_user_projects_by_role():
-    assert_equal({p.shortname for p in c.user.my_projects()},
+    assert ({p.shortname for p in c.user.my_projects()} ==
                  {'test', 'test2', 'u/test-admin', 'adobe-1', '--init--'})
-    assert_equal({p.shortname for p in c.user.my_projects_by_role_name('Admin')},
+    assert ({p.shortname for p in c.user.my_projects_by_role_name('Admin')} ==
                  {'test', 'test2', 'u/test-admin', 'adobe-1', '--init--'})
     # Remove admin access from c.user to test2 project
     project = M.Project.query.get(shortname='test2')
@@ -313,9 +313,9 @@ def test_user_projects_by_role():
     user_role.roles.append(developer_role._id)
     ThreadLocalORMSession.flush_all()
     g.credentials.clear()
-    assert_equal({p.shortname for p in c.user.my_projects()},
+    assert ({p.shortname for p in c.user.my_projects()} ==
                  {'test', 'test2', 'u/test-admin', 'adobe-1', '--init--'})
-    assert_equal({p.shortname for p in c.user.my_projects_by_role_name('Admin')},
+    assert ({p.shortname for p in c.user.my_projects_by_role_name('Admin')} ==
                  {'test', 'u/test-admin', 'adobe-1', '--init--'})
 
 
@@ -333,8 +333,8 @@ def test_user_projects_unnamed():
         project_id=sub1._id)
     ThreadLocalORMSession.flush_all()
     project_names = [p.shortname for p in c.user.my_projects()]
-    assert_not_in('test/sub1', project_names)
-    assert_in('test', project_names)
+    assert 'test/sub1' not in project_names
+    assert 'test' in project_names
 
 
 @patch.object(g, 'user_message_max_messages', 3)
@@ -345,7 +345,7 @@ def test_check_sent_user_message_times():
     time3 = datetime.utcnow() - timedelta(minutes=70)
     user1.sent_user_message_times = [time1, time2, time3]
     assert user1.can_send_user_message()
-    assert_equal(len(user1.sent_user_message_times), 2)
+    assert len(user1.sent_user_message_times) == 2
     user1.sent_user_message_times.append(
         datetime.utcnow() - timedelta(minutes=15))
     assert not user1.can_send_user_message()
@@ -358,40 +358,40 @@ def test_user_track_active():
     setup_functional_test()
     c.user = M.User.by_username('test-admin')
 
-    assert_equal(c.user.last_access['session_date'], None)
-    assert_equal(c.user.last_access['session_ip'], None)
-    assert_equal(c.user.last_access['session_ua'], None)
+    assert c.user.last_access['session_date'] == None
+    assert c.user.last_access['session_ip'] == None
+    assert c.user.last_access['session_ua'] == None
 
     req = Mock(headers={'User-Agent': 'browser'}, remote_addr='addr')
     c.user.track_active(req)
     c.user = M.User.by_username(c.user.username)
-    assert_not_equal(c.user.last_access['session_date'], None)
-    assert_equal(c.user.last_access['session_ip'], 'addr')
-    assert_equal(c.user.last_access['session_ua'], 'browser')
+    assert c.user.last_access['session_date'] != None
+    assert c.user.last_access['session_ip'] == 'addr'
+    assert c.user.last_access['session_ua'] == 'browser'
 
     # ensure that session activity tracked with a whole-day granularity
     prev_date = c.user.last_access['session_date']
     c.user.track_active(req)
     c.user = M.User.by_username(c.user.username)
-    assert_equal(c.user.last_access['session_date'], prev_date)
+    assert c.user.last_access['session_date'] == prev_date
     yesterday = datetime.utcnow() - timedelta(1)
     c.user.last_access['session_date'] = yesterday
     session(c.user).flush(c.user)
     c.user.track_active(req)
     c.user = M.User.by_username(c.user.username)
-    assert_true(c.user.last_access['session_date'] > yesterday)
+    assert c.user.last_access['session_date'] > yesterday
 
     # ...or if IP or User Agent has changed
     req.remote_addr = 'new addr'
     c.user.track_active(req)
     c.user = M.User.by_username(c.user.username)
-    assert_equal(c.user.last_access['session_ip'], 'new addr')
-    assert_equal(c.user.last_access['session_ua'], 'browser')
+    assert c.user.last_access['session_ip'] == 'new addr'
+    assert c.user.last_access['session_ua'] == 'browser'
     req.headers['User-Agent'] = 'new browser'
     c.user.track_active(req)
     c.user = M.User.by_username(c.user.username)
-    assert_equal(c.user.last_access['session_ip'], 'new addr')
-    assert_equal(c.user.last_access['session_ua'], 'new browser')
+    assert c.user.last_access['session_ip'] == 'new addr'
+    assert c.user.last_access['session_ua'] == 'new browser'
 
 
 @with_setup(setUp)
@@ -399,35 +399,35 @@ def test_user_index():
     c.user.email_addresses = ['email1', 'email2']
     c.user.set_pref('email_address', 'email2')
     idx = c.user.index()
-    assert_equal(idx['id'], c.user.index_id())
-    assert_equal(idx['title'], 'User test-admin')
-    assert_equal(idx['type_s'], 'User')
-    assert_equal(idx['username_s'], 'test-admin')
-    assert_equal(idx['email_addresses_t'], 'email1 email2')
-    assert_equal(idx['email_address_s'], 'email2')
-    assert_in('last_password_updated_dt', idx)
-    assert_equal(idx['disabled_b'], False)
-    assert_in('results_per_page_i', idx)
-    assert_in('email_format_s', idx)
-    assert_in('disable_user_messages_b', idx)
-    assert_equal(idx['display_name_t'], 'Test Admin')
-    assert_equal(idx['sex_s'], 'Unknown')
-    assert_in('birthdate_dt', idx)
-    assert_in('localization_s', idx)
-    assert_in('timezone_s', idx)
-    assert_in('socialnetworks_t', idx)
-    assert_in('telnumbers_t', idx)
-    assert_in('skypeaccount_s', idx)
-    assert_in('webpages_t', idx)
-    assert_in('skills_t', idx)
-    assert_in('last_access_login_date_dt', idx)
-    assert_in('last_access_login_ip_s', idx)
-    assert_in('last_access_login_ua_t', idx)
-    assert_in('last_access_session_date_dt', idx)
-    assert_in('last_access_session_ip_s', idx)
-    assert_in('last_access_session_ua_t', idx)
+    assert idx['id'] == c.user.index_id()
+    assert idx['title'] == 'User test-admin'
+    assert idx['type_s'] == 'User'
+    assert idx['username_s'] == 'test-admin'
+    assert idx['email_addresses_t'] == 'email1 email2'
+    assert idx['email_address_s'] == 'email2'
+    assert 'last_password_updated_dt' in idx
+    assert idx['disabled_b'] == False
+    assert 'results_per_page_i' in idx
+    assert 'email_format_s' in idx
+    assert 'disable_user_messages_b' in idx
+    assert idx['display_name_t'] == 'Test Admin'
+    assert idx['sex_s'] == 'Unknown'
+    assert 'birthdate_dt' in idx
+    assert 'localization_s' in idx
+    assert 'timezone_s' in idx
+    assert 'socialnetworks_t' in idx
+    assert 'telnumbers_t' in idx
+    assert 'skypeaccount_s' in idx
+    assert 'webpages_t' in idx
+    assert 'skills_t' in idx
+    assert 'last_access_login_date_dt' in idx
+    assert 'last_access_login_ip_s' in idx
+    assert 'last_access_login_ua_t' in idx
+    assert 'last_access_session_date_dt' in idx
+    assert 'last_access_session_ip_s' in idx
+    assert 'last_access_session_ua_t' in idx
     # provided bby auth provider
-    assert_in('user_registration_date_dt', idx)
+    assert 'user_registration_date_dt' in idx
 
 
 @with_setup(setUp)
@@ -436,9 +436,9 @@ def test_user_index_none_values():
     c.user.set_pref('telnumbers', [None])
     c.user.set_pref('webpages', [None])
     idx = c.user.index()
-    assert_equal(idx['email_addresses_t'], '')
-    assert_equal(idx['telnumbers_t'], '')
-    assert_equal(idx['webpages_t'], '')
+    assert idx['email_addresses_t'] == ''
+    assert idx['telnumbers_t'] == ''
+    assert idx['webpages_t'] == ''
 
 
 @with_setup(setUp)
@@ -461,22 +461,22 @@ def test_user_backfill_login_details():
     c.user.backfill_login_details(auth_provider)
 
     details = M.UserLoginDetails.query.find({'user_id': c.user._id}).sort('ua').all()
-    assert_equal(len(details), 2, details)
-    assert_equal(details[0].ip, '127.0.0.1')
-    assert_equal(details[0].ua, 'TestBrowser/56')
-    assert_equal(details[1].ip, '127.0.0.1')
-    assert_equal(details[1].ua, 'TestBrowser/57')
+    assert len(details) == 2, details
+    assert details[0].ip == '127.0.0.1'
+    assert details[0].ua == 'TestBrowser/56'
+    assert details[1].ip == '127.0.0.1'
+    assert details[1].ua == 'TestBrowser/57'
 
 
 class TestAuditLog:
 
     def test_message_html(self):
         al = h.auditlog_user('our message <script>alert(1)</script>')
-        assert_equal(al.message, textwrap.dedent('''\
+        assert al.message == textwrap.dedent('''\
             IP Address: 127.0.0.1
             User-Agent: None
-            our message <script>alert(1)</script>'''))
-        assert_equal(al.message_html, textwrap.dedent('''\
+            our message <script>alert(1)</script>''')
+        assert al.message_html == textwrap.dedent('''\
             IP Address: 127.0.0.1<br>
             User-Agent: None<br>
-            <strong>our message &lt;script&gt;alert(1)&lt;/script&gt;</strong>'''))
+            <strong>our message &lt;script&gt;alert(1)&lt;/script&gt;</strong>''')
diff --git a/Allura/allura/tests/model/test_discussion.py b/Allura/allura/tests/model/test_discussion.py
index 124c916e6..8ef7d1cb7 100644
--- a/Allura/allura/tests/model/test_discussion.py
+++ b/Allura/allura/tests/model/test_discussion.py
@@ -104,7 +104,7 @@ def test_thread_methods():
     assert t.post_count == 3
     jsn = t.__json__()
     assert '_id' in jsn
-    assert_equals(len(jsn['posts']), 3)
+    assert len(jsn['posts']) == 3
     (p.approve() for p in (p0, p1))
     ThreadLocalORMSession.flush_all()
     assert t.num_replies == 3
@@ -127,10 +127,10 @@ def test_thread_new():
         session(t2).expunge(t2)
         t1_2 = M.Thread.query.get(_id=t1._id)
         t2_2 = M.Thread.query.get(_id=t2._id)
-        assert_equals(t1._id, 'deadbeef')
-        assert_equals(t2._id, 'beefdead')
-        assert_equals(t1_2.subject, 'Test Thread One')
-        assert_equals(t2_2.subject, 'Test Thread Two')
+        assert t1._id == 'deadbeef'
+        assert t2._id == 'beefdead'
+        assert t1_2.subject == 'Test Thread One'
+        assert t2_2.subject == 'Test Thread Two'
 
 
 @with_setup(setUp, tearDown)
@@ -145,7 +145,7 @@ def test_post_methods():
     p.commit()
     assert p.parent is None
     assert p.subject == 'Test Thread'
-    assert_equals(p.attachments, [])
+    assert p.attachments == []
     assert 'wiki/_discuss' in p.url()
     assert p.reply_subject() == 'Re: Test Thread'
     assert p.link_text() == p.subject
@@ -203,9 +203,9 @@ def test_attachment_methods():
     n = M.Notification.query.get(
         subject='[test:wiki] Test comment notification')
     url = h.absurl(f'{p.url()}attachment/{fs.filename}')
-    assert_in(
+    assert (
         '\nAttachments:\n\n'
-        '- [fake.txt]({}) (37 Bytes; text/plain)'.format(url),
+        '- [fake.txt]({}) (37 Bytes; text/plain)'.format(url) in
         n.text)
 
 
@@ -226,7 +226,7 @@ def test_multiple_attachments():
     test_post = t.post('test post')
     test_post.add_multiple_attachments([test_file1, test_file2])
     ThreadLocalORMSession.flush_all()
-    assert_equals(len(test_post.attachments), 2)
+    assert len(test_post.attachments) == 2
     attaches = test_post.attachments
     assert 'test1.txt' in [attaches[0].filename, attaches[1].filename]
     assert 'test2.txt' in [attaches[0].filename, attaches[1].filename]
@@ -244,7 +244,7 @@ def test_add_attachment():
     test_post = t.post('test post')
     test_post.add_attachment(test_file)
     ThreadLocalORMSession.flush_all()
-    assert_equals(len(test_post.attachments), 1)
+    assert len(test_post.attachments) == 1
     attach = test_post.attachments[0]
     assert attach.filename == 'test.txt', attach.filename
     assert attach.content_type == 'text/plain', attach.content_type
@@ -268,10 +268,10 @@ def test_notification_two_attaches():
     n = M.Notification.query.get(
         subject='[test:wiki] Test comment notification')
     base_url = h.absurl(f'{p.url()}attachment/')
-    assert_in(
+    assert (
         '\nAttachments:\n\n'
         '- [fake.txt]({0}fake.txt) (37 Bytes; text/plain)\n'
-        '- [fake2.txt]({0}fake2.txt) (37 Bytes; text/plain)'.format(base_url),
+        '- [fake2.txt]({0}fake2.txt) (37 Bytes; text/plain)'.format(base_url) in
         n.text)
 
 
@@ -289,7 +289,7 @@ def test_discussion_delete():
     ThreadLocalORMSession.flush_all()
     d.delete()
     ThreadLocalORMSession.flush_all()
-    assert_equals(M.ArtifactReference.query.find(dict(_id=rid)).count(), 0)
+    assert M.ArtifactReference.query.find(dict(_id=rid)).count() == 0
 
 
 @with_setup(setUp, tearDown)
@@ -395,7 +395,7 @@ def test_post_url_paginated():
         if page > 0:
             url += '&page=%s' % page
         url += '#' + _p.slug
-        assert_equal(_p.url_paginated(), url)
+        assert _p.url_paginated() == url
 
 
 @with_setup(setUp, tearDown)
@@ -406,7 +406,7 @@ def test_post_url_paginated_with_artifact():
     thread = page.discussion_thread
     comment = thread.post('Comment')
     url = page.url() + '?limit=25#' + comment.slug
-    assert_equals(comment.url_paginated(), url)
+    assert comment.url_paginated() == url
 
 
 @with_setup(setUp, tearDown)
@@ -466,7 +466,7 @@ def test_not_spam_and_has_unmoderated_post_permission(spam_checker):
     t.acl.append(unmoderated_post_permission)
     with h.push_config(c, user=M.User.anonymous()):
         post = t.post('Hey')
-    assert_equal(post.status, 'ok')
+    assert post.status == 'ok'
 
 
 @with_setup(setUp, tearDown)
@@ -481,8 +481,8 @@ def test_not_spam_but_has_no_unmoderated_post_permission(notify_moderators, spam
     t.acl.append(post_permission)
     with h.push_config(c, user=M.User.anonymous()):
         post = t.post('Hey')
-    assert_equal(post.status, 'pending')
-    assert_equal(notify_moderators.call_count, 1)
+    assert post.status == 'pending'
+    assert notify_moderators.call_count == 1
 
 
 @with_setup(setUp, tearDown)
@@ -499,8 +499,8 @@ def test_spam_and_has_unmoderated_post_permission(notify_moderators, spam_checke
     t.acl.append(unmoderated_post_permission)
     with h.push_config(c, user=M.User.anonymous()):
         post = t.post('Hey')
-    assert_equal(post.status, 'pending')
-    assert_equal(notify_moderators.call_count, 1)
+    assert post.status == 'pending'
+    assert notify_moderators.call_count == 1
 
 
 @with_setup(setUp, tearDown)
@@ -510,8 +510,8 @@ def test_thread_subject_not_included_in_text_checked(spam_checker):
     d = M.Discussion(shortname='test', name='test')
     t = M.Thread(discussion_id=d._id, subject='Test Thread')
     t.post('Hello')
-    assert_equal(spam_checker.check.call_count, 1)
-    assert_equal(spam_checker.check.call_args[0][0], 'Hello')
+    assert spam_checker.check.call_count == 1
+    assert spam_checker.check.call_args[0][0] == 'Hello'
 
 
 def test_post_count():
@@ -521,7 +521,7 @@ def test_post_count():
     M.Post(discussion_id=d._id, thread_id=t._id, status='ok')
     M.Post(discussion_id=d._id, thread_id=t._id, status='pending')
     ThreadLocalORMSession.flush_all()
-    assert_equal(t.post_count, 2)
+    assert t.post_count == 2
 
 
 @mock.patch('allura.controllers.discuss.g.spam_checker')
@@ -532,7 +532,7 @@ def test_spam_num_replies(spam_checker):
     ThreadLocalORMSession.flush_all()
     p1 = M.Post(discussion_id=d._id, thread_id=t._id, status='spam')
     p1.spam()
-    assert_equal(t.num_replies, 1)
+    assert t.num_replies == 1
 
 
 def test_deleted_thread_index():
diff --git a/Allura/allura/tests/model/test_filesystem.py b/Allura/allura/tests/model/test_filesystem.py
index 60e78a837..17eb472ae 100644
--- a/Allura/allura/tests/model/test_filesystem.py
+++ b/Allura/allura/tests/model/test_filesystem.py
@@ -133,8 +133,8 @@ class TestFile(TestCase):
             response_body = list(f.serve())
             etag_cache.assert_called_once_with('{}?{}'.format(f.filename,
                                                                f._id.generation_time).encode('utf-8'))
-            assert_equal([b'test1'], response_body)
-            assert_equal(response.content_type, f.content_type)
+            assert [b'test1'] == response_body
+            assert response.content_type == f.content_type
             assert 'Content-Disposition' not in response.headers
 
     def test_serve_embed_false(self):
@@ -146,9 +146,9 @@ class TestFile(TestCase):
             response_body = list(f.serve(embed=False))
             etag_cache.assert_called_once_with('{}?{}'.format(f.filename,
                                                                f._id.generation_time).encode('utf-8'))
-            assert_equal([b'test1'], response_body)
-            assert_equal(response.content_type, f.content_type)
-            assert_equal(response.headers['Content-Disposition'],
+            assert [b'test1'] == response_body
+            assert response.content_type == f.content_type
+            assert (response.headers['Content-Disposition'] ==
                          'attachment;filename="te%20s%E0%AD%AE1.txt"')
 
     def test_image(self):
@@ -199,8 +199,8 @@ class TestFile(TestCase):
         attachment = M.BaseAttachment.save_attachment('user.png', fp,
                                                       save_original=True)
         assert not isinstance(attachment, tuple)   # tuple is for (img, thumb) pairs
-        assert_equal(attachment.length, 500)
-        assert_equal(attachment.filename, 'user.png')
+        assert attachment.length == 500
+        assert attachment.filename == 'user.png'
 
     def test_attachment_name_encoding(self):
         path = os.path.join(os.path.dirname(__file__),
@@ -211,7 +211,7 @@ class TestFile(TestCase):
             'Strukturpr\xfcfung.dvi', fp,
             save_original=True)
         assert not isinstance(attachment, tuple)   # tuple is for (img, thumb) pairs
-        assert_equal(attachment.filename, 'Strukturpr\xfcfung.dvi')
+        assert attachment.filename == 'Strukturpr\xfcfung.dvi'
 
     def _assert_content(self, f, content):
         result = f.rfile().read()
diff --git a/Allura/allura/tests/model/test_notification.py b/Allura/allura/tests/model/test_notification.py
index 5ad0aa6d3..922e1fff2 100644
--- a/Allura/allura/tests/model/test_notification.py
+++ b/Allura/allura/tests/model/test_notification.py
@@ -118,7 +118,7 @@ class TestNotification(unittest.TestCase):
         )
         notification.footer = lambda: ' footer'
         notification.send_direct(c.user._id)
-        assert_equal(sendmail.post.call_count, 0)
+        assert sendmail.post.call_count == 0
 
     @mock.patch('allura.tasks.mail_tasks.sendmail')
     def test_send_direct_wrong_project_context(self, sendmail):
@@ -133,7 +133,7 @@ class TestNotification(unittest.TestCase):
         """
         project1 = c.project
         project2 = M.Project.query.get(shortname='test2')
-        assert_equal(project1.shortname, 'test')
+        assert project1.shortname == 'test'
         c.user = M.User.query.get(username='test-user')
         wiki = project1.app_instance('wiki')
         page = WM.Page.query.get(app_config_id=wiki.config._id)
@@ -188,7 +188,7 @@ class TestPostNotifications(unittest.TestCase):
         ThreadLocalORMSession.flush_all()
         M.MonQTask.list()
         t = M.MonQTask.get()
-        assert_equal(t.args[1], [self.pg.index_id()])
+        assert t.args[1] == [self.pg.index_id()]
 
     def test_post_user_notification(self):
         u = M.User.query.get(username='test-admin')
@@ -220,34 +220,34 @@ class TestPostNotifications(unittest.TestCase):
         self._post_notification()
         ThreadLocalORMSession.flush_all()
 
-        assert_equal(M.Notification.query.get()
-                     ['from_address'], '"Test Admin" <te...@users.localhost>')
-        assert_equal(M.Mailbox.query.find().count(), 2)
+        assert (M.Notification.query.get()
+                     ['from_address'] == '"Test Admin" <te...@users.localhost>')
+        assert M.Mailbox.query.find().count() == 2
 
         # sends the notification out into "mailboxes", and from mailboxes into
         # email tasks
         M.MonQTask.run_ready()
         mboxes = M.Mailbox.query.find().all()
-        assert_equal(len(mboxes), 2)
-        assert_equal(len(mboxes[0].queue), 1)
+        assert len(mboxes) == 2
+        assert len(mboxes[0].queue) == 1
         assert not mboxes[0].queue_empty
-        assert_equal(len(mboxes[1].queue), 1)
+        assert len(mboxes[1].queue) == 1
         assert not mboxes[1].queue_empty
 
         email_tasks = M.MonQTask.query.find({'state': 'ready'}).all()
         # make sure both subscribers will get an email
-        assert_equal(len(email_tasks), 2)
+        assert len(email_tasks) == 2
 
         first_destinations = [e.kwargs['destinations'][0] for e in email_tasks]
-        assert_in(str(c.user._id), first_destinations)
-        assert_in(str(user2._id), first_destinations)
-        assert_equal(email_tasks[0].kwargs['fromaddr'],
+        assert str(c.user._id) in first_destinations
+        assert str(user2._id) in first_destinations
+        assert (email_tasks[0].kwargs['fromaddr'] ==
                      '"Test Admin" <te...@users.localhost>')
-        assert_equal(email_tasks[1].kwargs['fromaddr'],
+        assert (email_tasks[1].kwargs['fromaddr'] ==
                      '"Test Admin" <te...@users.localhost>')
-        assert_equal(email_tasks[0].kwargs['sender'],
+        assert (email_tasks[0].kwargs['sender'] ==
                      'wiki@test.p.in.localhost')
-        assert_equal(email_tasks[1].kwargs['sender'],
+        assert (email_tasks[1].kwargs['sender'] ==
                      'wiki@test.p.in.localhost')
         assert email_tasks[0].kwargs['text'].startswith(
             'Home modified by Test Admin')
@@ -441,14 +441,14 @@ class TestSubscriptionTypes(unittest.TestCase):
         count = M.MonQTask.query.find(dict(
             task_name='allura.tasks.mail_tasks.sendmail',
             state='ready')).count()
-        assert_equal(count, 0)
+        assert count == 0
         user.disabled = False
         ThreadLocalORMSession.flush_all()
         notification.send_direct(user._id)
         count = M.MonQTask.query.find(dict(
             task_name='allura.tasks.mail_tasks.sendmail',
             state='ready')).count()
-        assert_equal(count, 1)
+        assert count == 1
 
     @mock.patch('allura.model.notification.Notification.ref')
     def test_send_digest_disabled_user(self, ref):
@@ -464,7 +464,7 @@ class TestSubscriptionTypes(unittest.TestCase):
         count = M.MonQTask.query.find(dict(
             task_name='allura.tasks.mail_tasks.sendmail',
             state='ready')).count()
-        assert_equal(count, 0)
+        assert count == 0
         user.disabled = False
         ThreadLocalORMSession.flush_all()
         M.Notification.send_digest(
@@ -472,7 +472,7 @@ class TestSubscriptionTypes(unittest.TestCase):
         count = M.MonQTask.query.find(dict(
             task_name='allura.tasks.mail_tasks.sendmail',
             state='ready')).count()
-        assert_equal(count, 1)
+        assert count == 1
 
 
 class TestSiteNotification(unittest.TestCase):
diff --git a/Allura/allura/tests/model/test_oauth.py b/Allura/allura/tests/model/test_oauth.py
index 9f7b7f00c..7179bcd75 100644
--- a/Allura/allura/tests/model/test_oauth.py
+++ b/Allura/allura/tests/model/test_oauth.py
@@ -38,6 +38,6 @@ def test_upsert():
     token1 = M.OAuthConsumerToken.upsert(name, admin)
     token2 = M.OAuthConsumerToken.upsert(name, admin)
     token3 = M.OAuthConsumerToken.upsert(name, user)
-    assert_equal(M.OAuthConsumerToken.query.find().count(), 2)
-    assert_equal(token1._id, token2._id)
-    assert_not_equal(token1._id, token3._id)
+    assert M.OAuthConsumerToken.query.find().count() == 2
+    assert token1._id == token2._id
+    assert token1._id != token3._id
diff --git a/Allura/allura/tests/model/test_project.py b/Allura/allura/tests/model/test_project.py
index 7dc696da7..161df7680 100644
--- a/Allura/allura/tests/model/test_project.py
+++ b/Allura/allura/tests/model/test_project.py
@@ -42,29 +42,29 @@ def setup_with_tools():
 
 
 def test_project():
-    assert_equals(type(c.project.sidebar_menu()), list)
-    assert_in(c.project.script_name, c.project.url())
+    assert type(c.project.sidebar_menu()) == list
+    assert c.project.script_name in c.project.url()
     old_proj = c.project
     h.set_context('test/sub1', neighborhood='Projects')
-    assert_equals(type(c.project.sidebar_menu()), list)
-    assert_equals(type(c.project.sitemap()), list)
-    assert_equals(c.project.sitemap()[1].label, 'Admin')
-    assert_in(old_proj, list(c.project.parent_iter()))
+    assert type(c.project.sidebar_menu()) == list
+    assert type(c.project.sitemap()) == list
+    assert c.project.sitemap()[1].label == 'Admin'
+    assert old_proj in list(c.project.parent_iter())
     h.set_context('test', 'wiki', neighborhood='Projects')
     adobe_nbhd = M.Neighborhood.query.get(name='Adobe')
     p = M.Project.query.get(
         shortname='adobe-1', neighborhood_id=adobe_nbhd._id)
     # assert 'http' in p.url() # We moved adobe into /adobe/, not
     # http://adobe....
-    assert_in(p.script_name, p.url())
-    assert_equals(c.project.shortname, 'test')
-    assert_in('<p>', c.project.description_html)
+    assert p.script_name in p.url()
+    assert c.project.shortname == 'test'
+    assert '<p>' in c.project.description_html
     c.project.uninstall_app('hello-test-mount-point')
     ThreadLocalORMSession.flush_all()
 
     c.project.install_app('Wiki', 'hello-test-mount-point')
     c.project.support_page = 'hello-test-mount-point'
-    assert_equals(c.project.app_config('wiki').tool_name, 'wiki')
+    assert c.project.app_config('wiki').tool_name == 'wiki'
     ThreadLocalORMSession.flush_all()
     with td.raises(ToolError):
         # already installed
@@ -104,10 +104,10 @@ def test_install_app_validates_options():
         for v in [None, '', 'bad@email']:
             with td.raises(ToolError):
                 c.project.install_app('Tickets', 'test-tickets', **{name: v})
-            assert_equals(c.project.app_instance('test-tickets'), None)
+            assert c.project.app_instance('test-tickets') == None
         c.project.install_app('Tickets', 'test-tickets', **{name: 'e@e.com'})
         app = c.project.app_instance('test-tickets')
-        assert_equals(app.config.options[name], 'e@e.com')
+        assert app.config.options[name] == 'e@e.com'
 
 
 def test_project_index():
@@ -142,9 +142,9 @@ def test_subproject():
 def test_anchored_tools():
     c.project.neighborhood.anchored_tools = 'wiki:Wiki, tickets:Ticket'
     c.project.install_app = MagicMock()
-    assert_equals(c.project.sitemap()[0].label, 'Wiki')
-    assert_equals(c.project.install_app.call_args[0][0], 'tickets')
-    assert_equals(c.project.ordered_mounts()[0]['ac'].tool_name, 'wiki')
+    assert c.project.sitemap()[0].label == 'Wiki'
+    assert c.project.install_app.call_args[0][0] == 'tickets'
+    assert c.project.ordered_mounts()[0]['ac'].tool_name == 'wiki'
 
 
 def test_set_ordinal_to_admin_tool():
@@ -152,7 +152,7 @@ def test_set_ordinal_to_admin_tool():
                        user=M.User.by_username('test-admin'),
                        project=M.Project.query.get(shortname='test')):
         sm = c.project.sitemap()
-        assert_equals(sm[-1].tool_name, 'admin')
+        assert sm[-1].tool_name == 'admin'
 
 
 @with_setup(setUp)
diff --git a/Allura/allura/tests/model/test_repo.py b/Allura/allura/tests/model/test_repo.py
index aa85e06b3..2ceccdef2 100644
--- a/Allura/allura/tests/model/test_repo.py
+++ b/Allura/allura/tests/model/test_repo.py
@@ -36,10 +36,10 @@ class TestGitLikeTree:
         tree = M.GitLikeTree()
         tree.set_blob('/dir/dir2/file', 'file-oid')
 
-        assert_equal(tree.blobs, {})
-        assert_equal(tree.get_tree('dir').blobs, {})
-        assert_equal(tree.get_tree('dir').get_tree('dir2')
-                     .blobs, {'file': 'file-oid'})
+        assert tree.blobs == {}
+        assert tree.get_tree('dir').blobs == {}
+        assert (tree.get_tree('dir').get_tree('dir2')
+                     .blobs == {'file': 'file-oid'})
 
     def test_hex(self):
         tree = M.GitLikeTree()
@@ -47,24 +47,24 @@ class TestGitLikeTree:
         hex = tree.hex()
 
         # check the reprs. In case hex (below) fails, this'll be useful
-        assert_equal(repr(tree.get_tree('dir').get_tree('dir2')),
+        assert (repr(tree.get_tree('dir').get_tree('dir2')) ==
                      'b file-oid file')
-        assert_equal(repr(tree),
+        assert (repr(tree) ==
                      't 96af1772ecce1e6044e6925e595d9373ffcd2615 dir')
         # the hex() value shouldn't change, it's an important key
-        assert_equal(hex, '4abba29a43411b9b7cecc1a74f0b27920554350d')
+        assert hex == '4abba29a43411b9b7cecc1a74f0b27920554350d'
 
         # another one should be the same
         tree2 = M.GitLikeTree()
         tree2.set_blob('/dir/dir2/file', 'file-oid')
         hex2 = tree2.hex()
-        assert_equal(hex, hex2)
+        assert hex == hex2
 
     def test_hex_with_unicode(self):
         tree = M.GitLikeTree()
         tree.set_blob('/dir/f•º£', 'file-oid')
         # the hex() value shouldn't change, it's an important key
-        assert_equal(tree.hex(), '51ce65bead2f6452da61d4f6f2e42f8648bf9e4b')
+        assert tree.hex() == '51ce65bead2f6452da61d4f6f2e42f8648bf9e4b'
 
 
 class RepoImplTestBase:
@@ -104,28 +104,28 @@ class RepoTestBase(unittest.TestCase):
         c.app = mock.Mock(**{'config._id': 'deadbeef'})
         repo = M.repository.Repository(tool='git')
         cmd_cats = repo.clone_command_categories(anon=False)
-        assert_equal(cmd_cats, [
+        assert cmd_cats == [
             {'key': 'file', 'name': 'File', 'title': 'Filesystem'}
-        ])
+        ]
 
         cmd_cats = repo.clone_command_categories(anon=True)
-        assert_equal(cmd_cats, [
+        assert cmd_cats == [
             {'key': 'file', 'name': 'File', 'title': 'Filesystem'}
-        ])
+        ]
 
         repo = M.repository.Repository(tool='something-else')  # no "something-else" in config so will use defaults
         cmd_cats = repo.clone_command_categories(anon=False)
-        assert_equal(cmd_cats, [
+        assert cmd_cats == [
             {'key': 'rw', 'name': 'RW', 'title': 'Read/Write'},
             {'key': 'ro', 'name': 'RO', 'title': 'Read Only'},
             {'key': 'https', 'name': 'HTTPS', 'title': 'HTTPS'}
-        ])
+        ]
 
         cmd_cats = repo.clone_command_categories(anon=True)
-        assert_equal(cmd_cats, [
+        assert cmd_cats == [
             {'key': 'ro', 'name': 'RO', 'title': 'Read Only'},
             {'key': 'https_anon', 'name': 'HTTPS', 'title': 'HTTPS'}
-        ])
+        ]
 
 
 class TestLastCommit(unittest.TestCase):
@@ -365,13 +365,13 @@ class TestLastCommit(unittest.TestCase):
         commit2 = self._add_commit('Commit 2', ['file2'])
         commit2.changed_paths = []
         result = self.repo.last_commit_ids(commit2, ['file2'])
-        assert_equal(result, {'file2': commit2._id})
+        assert result == {'file2': commit2._id}
 
     def test_missing_add_record_first_commit(self):
         commit1 = self._add_commit('Commit 1', ['file1'])
         commit1.changed_paths = []
         result = self.repo.last_commit_ids(commit1, ['file1'])
-        assert_equal(result, {'file1': commit1._id})
+        assert result == {'file1': commit1._id}
 
     def test_timeout(self):
         commit1 = self._add_commit('Commit 1', ['file1'])
@@ -704,31 +704,31 @@ class TestMergeRequest:
 
     def test_can_merge_cache_key(self):
         key = self.mr.can_merge_cache_key()
-        assert_equal(key, '12345-09876')
+        assert key == '12345-09876'
 
     def test_get_can_merge_cache(self):
         key = self.mr.can_merge_cache_key()
-        assert_equal(self.mr.get_can_merge_cache(), None)
+        assert self.mr.get_can_merge_cache() == None
         self.mr.can_merge_cache[key] = True
-        assert_equal(self.mr.get_can_merge_cache(), True)
+        assert self.mr.get_can_merge_cache() == True
 
         self.mr.can_merge_cache_key = lambda: '123-123'
         self.mr.can_merge_cache['123-123'] = False
-        assert_equal(self.mr.get_can_merge_cache(), False)
+        assert self.mr.get_can_merge_cache() == False
 
     def test_set_can_merge_cache(self):
         key = self.mr.can_merge_cache_key()
-        assert_equal(self.mr.can_merge_cache, {})
+        assert self.mr.can_merge_cache == {}
         self.mr.set_can_merge_cache(True)
-        assert_equal(self.mr.can_merge_cache, {key: True})
+        assert self.mr.can_merge_cache == {key: True}
 
         self.mr.can_merge_cache_key = lambda: '123-123'
         self.mr.set_can_merge_cache(False)
-        assert_equal(self.mr.can_merge_cache, {key: True, '123-123': False})
+        assert self.mr.can_merge_cache == {key: True, '123-123': False}
 
     def test_can_merge_merged(self):
         self.mr.status = 'merged'
-        assert_equal(self.mr.can_merge(), True)
+        assert self.mr.can_merge() == True
 
     @mock.patch('allura.tasks.repo_tasks.can_merge', autospec=True)
     def test_can_merge_cached(self, can_merge_task):
@@ -738,23 +738,23 @@ class TestMergeRequest:
 
         self.mr.set_can_merge_cache(False)
         self.mr = self._reload_mr_from_db(self.mr)
-        assert_equal(self.mr.can_merge(), False)
+        assert self.mr.can_merge() == False
 
         self.mr.set_can_merge_cache(True)
         self.mr = self._reload_mr_from_db(self.mr)
-        assert_equal(self.mr.can_merge(), True)
-        assert_equal(can_merge_task.post.call_count, 0)
+        assert self.mr.can_merge() == True
+        assert can_merge_task.post.call_count == 0
 
     @mock.patch('allura.tasks.repo_tasks.can_merge', autospec=True)
     def test_can_merge_not_cached(self, can_merge_task):
-        assert_equal(self.mr.can_merge(), None)
+        assert self.mr.can_merge() == None
         can_merge_task.post.assert_called_once_with(self.mr._id)
 
     @mock.patch('allura.tasks.repo_tasks.can_merge', autospec=True)
     def test_can_merge_disabled(self, can_merge_task):
         self.mr.merge_allowed.return_value = False
-        assert_equal(self.mr.can_merge(), None)
-        assert_equal(can_merge_task.post.call_count, 0)
+        assert self.mr.can_merge() == None
+        assert can_merge_task.post.call_count == 0
 
     @mock.patch('allura.tasks.repo_tasks.merge', autospec=True)
     def test_merge(self, merge_task):
@@ -765,21 +765,21 @@ class TestMergeRequest:
         merge_task.reset_mock()
         self.mr.merge_task_status = lambda: 'ready'
         self.mr.merge()
-        assert_equal(merge_task.post.called, False)
+        assert merge_task.post.called == False
 
     def test_merge_task_status(self):
         from allura.tasks import repo_tasks
-        assert_equal(self.mr.merge_task_status(), None)
+        assert self.mr.merge_task_status() == None
         repo_tasks.merge.post(self.mr._id)
-        assert_equal(self.mr.merge_task_status(), 'ready')
+        assert self.mr.merge_task_status() == 'ready'
         M.MonQTask.run_ready()
-        assert_equal(self.mr.merge_task_status(), 'complete')
+        assert self.mr.merge_task_status() == 'complete'
 
     def test_can_merge_task_status(self):
         from allura.tasks import repo_tasks
-        assert_equal(self.mr.can_merge_task_status(), None)
+        assert self.mr.can_merge_task_status() == None
         repo_tasks.can_merge.post(self.mr._id)
-        assert_equal(self.mr.can_merge_task_status(), 'ready')
+        assert self.mr.can_merge_task_status() == 'ready'
         with mock.patch('allura.model.repository.MergeRequest.set_can_merge_cache'):
             M.MonQTask.run_ready()
-        assert_equal(self.mr.can_merge_task_status(), 'complete')
+        assert self.mr.can_merge_task_status() == 'complete'
diff --git a/Allura/allura/tests/model/test_timeline.py b/Allura/allura/tests/model/test_timeline.py
index 067b71991..d60fea319 100644
--- a/Allura/allura/tests/model/test_timeline.py
+++ b/Allura/allura/tests/model/test_timeline.py
@@ -38,5 +38,5 @@ class TestActivityObject_Functional:
         wiki_app = p.app_instance('wiki')
         app_config = wiki_app.config
 
-        assert_equal(bool(app_config.has_activity_access('read', user=M.User.anonymous(), activity=None)),
+        assert (bool(app_config.has_activity_access('read', user=M.User.anonymous(), activity=None)) ==
                      True)
\ No newline at end of file
diff --git a/Allura/allura/tests/scripts/test_create_sitemap_files.py b/Allura/allura/tests/scripts/test_create_sitemap_files.py
index 31fa0bfd1..56e80a383 100644
--- a/Allura/allura/tests/scripts/test_create_sitemap_files.py
+++ b/Allura/allura/tests/scripts/test_create_sitemap_files.py
@@ -50,9 +50,9 @@ class TestCreateSitemapFiles:
             xml_index = ET.parse(os.path.join(tmpdir.path, 'sitemap.xml'))
             ns = {'ns0': 'http://www.sitemaps.org/schemas/sitemap/0.9'}
             locs = [loc.text for loc in xml_index.findall('ns0:sitemap/ns0:loc', ns)]
-            assert_in('http://localhost/allura_sitemap/sitemap-0.xml', locs)
+            assert 'http://localhost/allura_sitemap/sitemap-0.xml' in locs
 
             xml_0 = ET.parse(os.path.join(tmpdir.path, 'sitemap-0.xml'))
             urls = [loc.text for loc in xml_0.findall('ns0:url/ns0:loc', ns)]
-            assert_not_in('http://localhost/p/wiki/', urls)  # blank wiki pages omitted from sitemap
-            assert_in('http://localhost/p/test/sub1/', urls)
+            assert 'http://localhost/p/wiki/' not in urls  # blank wiki pages omitted from sitemap
+            assert 'http://localhost/p/test/sub1/' in urls
diff --git a/Allura/allura/tests/scripts/test_delete_projects.py b/Allura/allura/tests/scripts/test_delete_projects.py
index 6d3f1c3cf..c5af80d49 100644
--- a/Allura/allura/tests/scripts/test_delete_projects.py
+++ b/Allura/allura/tests/scripts/test_delete_projects.py
@@ -91,7 +91,7 @@ class TestDeleteProjects(TestController):
         things = self.things_related_to_project(pid)
         assert len(things) == 0, 'Not all things are deleted: %s' % things
         parent_things_after = self.things_related_to_project(parent_pid)
-        assert_equal(len(parent_things_before), len(parent_things_after))
+        assert len(parent_things_before) == len(parent_things_after)
 
     @patch('allura.lib.plugin.solr_del_project_artifacts', autospec=True)
     def test_solr_index_is_deleted(self, del_solr):
diff --git a/Allura/allura/tests/scripts/test_misc_scripts.py b/Allura/allura/tests/scripts/test_misc_scripts.py
index a20206f56..104ea9f84 100644
--- a/Allura/allura/tests/scripts/test_misc_scripts.py
+++ b/Allura/allura/tests/scripts/test_misc_scripts.py
@@ -38,8 +38,8 @@ class TestClearOldNotifications:
         n = M.Notification(app_config_id=ObjectId(), neighborhood_id=ObjectId(), project_id=ObjectId(),
                            tool_name='blah')
         session(n).flush(n)
-        assert_equal(M.Notification.query.find().count(), 1)
+        assert M.Notification.query.find().count() == 1
         self.run_script(['--back-days', '7'])
-        assert_equal(M.Notification.query.find().count(), 1)
+        assert M.Notification.query.find().count() == 1
         self.run_script(['--back-days', '0'])
-        assert_equal(M.Notification.query.find().count(), 0)
+        assert M.Notification.query.find().count() == 0
diff --git a/Allura/allura/tests/scripts/test_reindexes.py b/Allura/allura/tests/scripts/test_reindexes.py
index c2cdc2bbb..f5f58556c 100644
--- a/Allura/allura/tests/scripts/test_reindexes.py
+++ b/Allura/allura/tests/scripts/test_reindexes.py
@@ -45,7 +45,7 @@ class TestReindexProjects:
             self.run_script(['-n', '/p/', '-p', 'test', '--tasks'])
         assert_logmsg_and_no_warnings_or_errors(logs, 'Reindex project test')
         assert_logmsg_and_no_warnings_or_errors(logs, 'Reindex queued')
-        assert_equal(M.MonQTask.query.find({'task_name': 'allura.tasks.index_tasks.add_projects'}).count(), 1)
+        assert M.MonQTask.query.find({'task_name': 'allura.tasks.index_tasks.add_projects'}).count() == 1
 
 
 class TestReindexUsers:
@@ -71,4 +71,4 @@ class TestReindexUsers:
         assert_logmsg_and_no_warnings_or_errors(logs, 'Reindex user root')
         assert_logmsg_and_no_warnings_or_errors(logs, 'Reindex user test-user-1')
         assert_logmsg_and_no_warnings_or_errors(logs, 'Reindex queued')
-        assert_equal(M.MonQTask.query.find({'task_name': 'allura.tasks.index_tasks.add_users'}).count(), 1)
+        assert M.MonQTask.query.find({'task_name': 'allura.tasks.index_tasks.add_users'}).count() == 1
diff --git a/Allura/allura/tests/templates/jinja_master/test_lib.py b/Allura/allura/tests/templates/jinja_master/test_lib.py
index 9e73a0f54..0acf97104 100644
--- a/Allura/allura/tests/templates/jinja_master/test_lib.py
+++ b/Allura/allura/tests/templates/jinja_master/test_lib.py
@@ -45,7 +45,7 @@ class TestRelatedArtifacts(TemplateTest):
 
     def test_none(self):
         artifact = Mock(related_artifacts=lambda user: [])
-        assert_equal(self._render_related_artifacts(artifact), '')
+        assert self._render_related_artifacts(artifact) == ''
 
     def test_simple(self):
         other = Mock()
@@ -54,12 +54,12 @@ class TestRelatedArtifacts(TemplateTest):
         other.app_config.options.mount_label = 'Foo'
         other.link_text.return_value = 'Bar'
         artifact = Mock(related_artifacts=lambda user: [other])
-        assert_equal(self._render_related_artifacts(artifact), strip_space('''
+        assert self._render_related_artifacts(artifact) == strip_space('''
             <h4>Related</h4>
             <p>
             <a href="/p/test/foo/bar">Test Project: Foo: Bar</a><br>
             </p>
-        '''))
+        ''')
 
     def test_non_artifact(self):
         # e.g. a commit
@@ -73,9 +73,9 @@ class TestRelatedArtifacts(TemplateTest):
                 return '/p/test/code/ci/deadbeef'
 
         artifact = Mock(related_artifacts=lambda user: [CommitThing()])
-        assert_equal(self._render_related_artifacts(artifact), strip_space('''
+        assert self._render_related_artifacts(artifact) == strip_space('''
             <h4>Related</h4>
             <p>
             <a href="/p/test/code/ci/deadbeef">Commit: [deadbeef]</a><br>
             </p>
-        '''))
+        ''')
diff --git a/Allura/allura/tests/test_app.py b/Allura/allura/tests/test_app.py
index ceca74591..5448df2a5 100644
--- a/Allura/allura/tests/test_app.py
+++ b/Allura/allura/tests/test_app.py
@@ -58,27 +58,27 @@ def test_config_options():
 
 def test_config_options_render_attrs():
     opt = app.ConfigOption('test1', str, None, extra_attrs={'type': 'url'})
-    assert_equal(opt.render_attrs(), 'type="url"')
+    assert opt.render_attrs() == 'type="url"'
 
 
 def test_config_option_without_validator():
     opt = app.ConfigOption('test1', str, None)
-    assert_equal(opt.validate(None), None)
-    assert_equal(opt.validate(''), '')
-    assert_equal(opt.validate('val'), 'val')
+    assert opt.validate(None) == None
+    assert opt.validate('') == ''
+    assert opt.validate('val') == 'val'
 
 
 def test_config_option_with_validator():
     v = fev.NotEmpty()
     opt = app.ConfigOption('test1', str, None, validator=v)
-    assert_equal(opt.validate('val'), 'val')
+    assert opt.validate('val') == 'val'
     assert_raises(fev.Invalid, opt.validate, None)
     assert_raises(fev.Invalid, opt.validate, '')
 
 
 def test_options_on_install_default():
     a = app.Application(c.project, c.app.config)
-    assert_equal(a.options_on_install(), [])
+    assert a.options_on_install() == []
 
 
 def test_options_on_install():
@@ -91,7 +91,7 @@ def test_options_on_install():
         config_on_install = ['url', 'private']
 
     a = TestApp(c.project, c.app.config)
-    assert_equal(a.options_on_install(), opts)
+    assert a.options_on_install() == opts
 
 
 def test_main_menu():
@@ -105,8 +105,8 @@ def test_main_menu():
 
     a = TestApp(c.project, c.app.config)
     main_menu = a.main_menu()
-    assert_equal(len(main_menu), 1)
-    assert_equal(main_menu[0].children, [])  # default main_menu implementation should drop the children from sitemap()
+    assert len(main_menu) == 1
+    assert main_menu[0].children == []  # default main_menu implementation should drop the children from sitemap()
 
 
 def test_sitemap():
@@ -138,15 +138,15 @@ def test_handle_artifact_unicode(qg):
 
     msg = dict(payload='foo ƒ†©¥˙¨ˆ'.encode(), message_id=1, headers={})
     a.handle_artifact_message(ticket, msg)
-    assert_equal(post.attach.call_args[0][1].getvalue(), 'foo ƒ†©¥˙¨ˆ'.encode())
+    assert post.attach.call_args[0][1].getvalue() == 'foo ƒ†©¥˙¨ˆ'.encode()
 
     msg = dict(payload=b'foo', message_id=1, headers={})
     a.handle_artifact_message(ticket, msg)
-    assert_equal(post.attach.call_args[0][1].getvalue(), b'foo')
+    assert post.attach.call_args[0][1].getvalue() == b'foo'
 
     msg = dict(payload="\x94my quote\x94".encode(), message_id=1, headers={})
     a.handle_artifact_message(ticket, msg)
-    assert_equal(post.attach.call_args[0][1].getvalue(), '\x94my quote\x94'.encode())
+    assert post.attach.call_args[0][1].getvalue() == '\x94my quote\x94'.encode()
 
     # assert against prod example
     msg_raw = """Message-Id: <15...@webmail.messagingengine.com>
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index edb50b1d3..9f3f3f1bd 100644
--- a/Allura/allura/tests/test_commands.py
+++ b/Allura/allura/tests/test_commands.py
@@ -251,7 +251,7 @@ class TestEnsureIndexCommand:
         cmd = show_models.EnsureIndexCommand('ensure_index')
         cmd._update_indexes(collection, indexes)
 
-        assert_equal(collection.mock_calls, [
+        assert collection.mock_calls == [
             call.index_information(),
             call.ensure_index(
                 [('foo', 1), ('bar', 1), ('temporary_extra_field_for_indexing', 1)]),
@@ -265,7 +265,7 @@ class TestEnsureIndexCommand:
             call.drop_index('_foo_baz_temporary_extra_field_for_indexing'),
             call.ensure_index([('foo', 1), ('baz', 1)], unique=True, sparse=False),
             call.ensure_index([('foo', 1), ('bar', 1)], unique=False, sparse=False, background=True)
-        ])
+        ]
 
 
 class TestTaskCommand:
@@ -275,16 +275,16 @@ class TestTaskCommand:
 
     def test_commit(self):
         exit_code = taskd.TaskCommand('task').run([test_config, 'commit'])
-        assert_equal(M.MonQTask.query.find({'task_name': 'allura.tasks.index_tasks.commit'}).count(), 1)
-        assert_equal(exit_code, 0)
+        assert M.MonQTask.query.find({'task_name': 'allura.tasks.index_tasks.commit'}).count() == 1
+        assert exit_code == 0
 
     def test_list(self):
         exit_code = taskd.TaskCommand('task').run([test_config, 'list'])
-        assert_equal(exit_code, 0)
+        assert exit_code == 0
 
     def test_count(self):
         exit_code = taskd.TaskCommand('task').run([test_config, 'count'])
-        assert_equal(exit_code, 0)
+        assert exit_code == 0
 
     def test_retry(self):
         # self.test_commit()
@@ -293,24 +293,24 @@ class TestTaskCommand:
             '--filter-name-prefix', 'allura.tasks.index_tasks.',
             '--filter-result-regex', 'pysolr',
         ])
-        assert_equal(exit_code, 0)
+        assert exit_code == 0
 
     def test_purge(self):
         # create task
         self.test_commit()
-        assert_equal(M.MonQTask.query.find().count(), 1)
+        assert M.MonQTask.query.find().count() == 1
         M.MonQTask.query.update({'task_name': 'allura.tasks.index_tasks.commit'}, {'$set': {'state': 'complete'}})
         # run purge; verify 0 records
         exit_code = taskd.TaskCommand('task').run([
             test_config, 'purge',
         ])
-        assert_equal(exit_code, 0)
-        assert_equal(M.MonQTask.query.find().count(), 0)
+        assert exit_code == 0
+        assert M.MonQTask.query.find().count() == 0
 
     def test_purge_old_only(self):
         # create task
         self.test_commit()
-        assert_equal(M.MonQTask.query.find().count(), 1)
+        assert M.MonQTask.query.find().count() == 1
 
         # force task to be in complete state
         M.MonQTask.query.update({'task_name': 'allura.tasks.index_tasks.commit'}, {'$set': {'state': 'complete'}})
@@ -318,8 +318,8 @@ class TestTaskCommand:
         exit_code = taskd.TaskCommand('task').run([
             test_config, 'purge', '--filter-queued-days-ago', '180',
         ])
-        assert_equal(exit_code, 0)
-        assert_equal(M.MonQTask.query.find().count(), 1)
+        assert exit_code == 0
+        assert M.MonQTask.query.find().count() == 1
 
         # modify task to be old
         then = datetime.datetime.utcnow() - datetime.timedelta(days=200)
@@ -330,8 +330,8 @@ class TestTaskCommand:
         exit_code = taskd.TaskCommand('task').run([
             test_config, 'purge', '--filter-queued-days-ago', '180',
         ])
-        assert_equal(exit_code, 0)
-        assert_equal(M.MonQTask.query.find().count(), 0)
+        assert exit_code == 0
+        assert M.MonQTask.query.find().count() == 0
 
 
 class TestTaskdCleanupCommand:
@@ -454,10 +454,10 @@ class TestShowModels:
         cmd = show_models.ShowModelsCommand('models')
         with OutputCapture() as output:
             cmd.run([test_config])
-        assert_in('''allura.model.notification.SiteNotification
+        assert '''allura.model.notification.SiteNotification
          - <FieldProperty _id>
          - <FieldProperty content>
-        ''', output.captured)
+        ''' in output.captured
 
 class TestReindexAsTask:
 
@@ -467,12 +467,12 @@ class TestReindexAsTask:
     def test_command_post(self):
         show_models.ReindexCommand.post('-p "project 3"')
         tasks = M.MonQTask.query.find({'task_name': self.task_name}).all()
-        assert_equal(len(tasks), 1)
+        assert len(tasks) == 1
         task = tasks[0]
-        assert_equal(task.args, [self.cmd, '-p "project 3"'])
+        assert task.args == [self.cmd, '-p "project 3"']
 
     def test_command_doc(self):
-        assert_in('Usage:', show_models.ReindexCommand.__doc__)
+        assert 'Usage:' in show_models.ReindexCommand.__doc__
 
     @patch('allura.command.show_models.ReindexCommand')
     def test_run_command(self, command):
@@ -487,7 +487,7 @@ class TestReindexAsTask:
         try:
             with td.raises(Exception) as e:
                 M.MonQTask.run_ready()
-            assert_in('Error parsing args', str(e.exc))
+            assert 'Error parsing args' in str(e.exc)
         finally:
             # cleanup - remove bad MonQTask
             M.MonQTask.query.remove()
@@ -511,7 +511,7 @@ class TestReindexCommand:
         cmd.options, args = cmd.parser.parse_args([
             '-p', 'test', '--solr', '--solr-hosts=http://blah.com/solr/forge'])
         cmd._chunked_add_artifacts(list(range(10)))
-        assert_equal(Solr.call_args[0][0], 'http://blah.com/solr/forge')
+        assert Solr.call_args[0][0] == 'http://blah.com/solr/forge'
 
     @patch('pysolr.Solr')
     def test_solr_hosts_list(self, Solr):
@@ -520,11 +520,10 @@ class TestReindexCommand:
             '-p', 'test', '--solr', '--solr-hosts=http://blah.com/solr/forge,https://other.net/solr/forge'])
         cmd._chunked_add_artifacts(list(range(10)))
         # check constructors of first and second Solr() instantiations
-        assert_equal(
-            {Solr.call_args_list[0][0][0], Solr.call_args_list[1][0][0]},
+        assert (
+            {Solr.call_args_list[0][0][0], Solr.call_args_list[1][0][0]} ==
             {'http://blah.com/solr/forge',
-                 'https://other.net/solr/forge'}
-        )
+                 'https://other.net/solr/forge'})
 
     @patch('allura.command.show_models.utils')
     def test_project_regex(self, utils):
@@ -539,12 +538,12 @@ class TestReindexCommand:
         cmd.options = Mock(tasks=True, max_chunk=10 * 1000, ming_config=None)
         ref_ids = list(range(10 * 1000 * 2 + 20))
         cmd._chunked_add_artifacts(ref_ids)
-        assert_equal(len(add_artifacts.post.call_args_list), 3)
-        assert_equal(
-            len(add_artifacts.post.call_args_list[0][0][0]), 10 * 1000)
-        assert_equal(
-            len(add_artifacts.post.call_args_list[1][0][0]), 10 * 1000)
-        assert_equal(len(add_artifacts.post.call_args_list[2][0][0]), 20)
+        assert len(add_artifacts.post.call_args_list) == 3
+        assert (
+            len(add_artifacts.post.call_args_list[0][0][0]) == 10 * 1000)
+        assert (
+            len(add_artifacts.post.call_args_list[1][0][0]) == 10 * 1000)
+        assert len(add_artifacts.post.call_args_list[2][0][0]) == 20
 
     @patch('allura.command.show_models.add_artifacts')
     def test_post_add_artifacts_too_large(self, add_artifacts):
@@ -571,7 +570,7 @@ class TestReindexCommand:
             call([3], **kw),
             call([4], **kw)
         ]
-        assert_equal(expected, add_artifacts.post.call_args_list)
+        assert expected == add_artifacts.post.call_args_list
 
     @patch('allura.command.show_models.add_artifacts')
     def test_post_add_artifacts_other_error(self, add_artifacts):
diff --git a/Allura/allura/tests/test_decorators.py b/Allura/allura/tests/test_decorators.py
index 0935b7b70..40d68062e 100644
--- a/Allura/allura/tests/test_decorators.py
+++ b/Allura/allura/tests/test_decorators.py
@@ -74,10 +74,10 @@ class TestMemoize:
         const1 = remember_randomy(False)
         rand_kwargs1 = remember_randomy(True, foo='asdf')
         rand_kwargs2 = remember_randomy(True, foo='xyzzy')
-        assert_equal(rand1, rand2)
-        assert_equal(const1, "constant")
-        assert_not_equal(rand1, rand_kwargs1)
-        assert_not_equal(rand_kwargs1, rand_kwargs2)
+        assert rand1 == rand2
+        assert const1 == "constant"
+        assert rand1 != rand_kwargs1
+        assert rand_kwargs1 != rand_kwargs2
 
     def test_methods(self):
 
@@ -103,10 +103,10 @@ class TestMemoize:
         other1 = r.other(True)
         other2 = r.other(True)
 
-        assert_equal(rand1, rand2)
-        assert_equal(const1, "constant")
-        assert_not_equal(rand1, other1)
-        assert_equal(other1, other2)
+        assert rand1 == rand2
+        assert const1 == "constant"
+        assert rand1 != other1
+        assert other1 == other2
 
         r2 = Randomy()
         r2rand1 = r2.randomy(True)
@@ -115,10 +115,10 @@ class TestMemoize:
         r2other1 = r2.other(True)
         r2other2 = r2.other(True)
 
-        assert_not_equal(r2rand1, rand1)
-        assert_equal(r2rand1, r2rand2)
-        assert_not_equal(r2other1, other1)
-        assert_equal(r2other1, r2other2)
+        assert r2rand1 != rand1
+        assert r2rand1 == r2rand2
+        assert r2other1 != other1
+        assert r2other1 == r2other2
 
     def test_methods_garbage_collection(self):
 
diff --git a/Allura/allura/tests/test_globals.py b/Allura/allura/tests/test_globals.py
index 3b1334915..c37901c13 100644
--- a/Allura/allura/tests/test_globals.py
+++ b/Allura/allura/tests/test_globals.py
@@ -206,7 +206,7 @@ def test_macro_members():
     p_test.add_user(M.User.by_username('test-user-0'), ['Member'])
     ThreadLocalORMSession.flush_all()
     r = g.markdown_wiki.convert('[[members limit=2]]').replace('\t', '').replace('\n', '')
-    assert_equal(r,
+    assert (r ==
                  '<div class="markdown_content"><h6>Project Members:</h6>'
                  '<ul class="md-users-list">'
                  '<li><a href="/u/test-admin/">Test Admin</a> (admin)</li>'
@@ -221,7 +221,7 @@ def test_macro_members_escaping():
     user = M.User.by_username('test-admin')
     user.display_name = 'Test Admin <script>'
     r = g.markdown_wiki.convert('[[members]]')
-    assert_equal(r.replace('\n', '').replace('\t', ''),
+    assert (r.replace('\n', '').replace('\t', '') ==
                  '<div class="markdown_content"><h6>Project Members:</h6>'
                  '<ul class="md-users-list">'
                  '<li><a href="/u/test-admin/">Test Admin &lt;script&gt;</a> (admin)</li>'
@@ -234,7 +234,7 @@ def test_macro_project_admins():
     user.display_name = 'Test Ådmin <script>'
     with h.push_context('test', neighborhood='Projects'):
         r = g.markdown_wiki.convert('[[project_admins]]')
-    assert_equal(r.replace('\n', ''),
+    assert (r.replace('\n', '') ==
                  '<div class="markdown_content"><h6>Project Admins:</h6>'
                  '<ul class="md-users-list">'
                  '    <li><a href="/u/test-admin/">Test \xc5dmin &lt;script&gt;</a></li>'
@@ -283,7 +283,7 @@ def test_macro_include_no_extra_br():
 <div class="markdown_content"><p>included page 3</p></div>
 </div>
 <p></p></div>'''
-    assert_equal(squish_spaces(html), squish_spaces(expected_html))
+    assert squish_spaces(html) == squish_spaces(expected_html)
 
 
 @with_setup(setUp, tearDown)
@@ -315,9 +315,9 @@ def test_macro_include_permissions():
         c.user = M.User.anonymous()
         md = '[[include ref=CanRead]]\n[[include ref=wiki2:CanNotRead]]'
         html = g.markdown_wiki.convert(md)
-        assert_in('Can see this!', html)
-        assert_not_in('Can not see this!', html)
-        assert_in("[[include: you don't have a read permission for wiki2:CanNotRead]]", html)
+        assert 'Can see this!' in html
+        assert 'Can not see this!' not in html
+        assert "[[include: you don't have a read permission for wiki2:CanNotRead]]" in html
 
 
 @patch('oembed.OEmbedEndpoint.fetch')
@@ -328,8 +328,8 @@ def test_macro_embed(oembed_fetch):
         "title": "Nature's 3D Printer: MIND BLOWING Cocoon in Rainforest - Smarter Every Day 94",
     }
     r = g.markdown_wiki.convert('[[embed url=http://www.youtube.com/watch?v=kOLpSPEA72U]]')
-    assert_in('<p><iframe height="270" '
-              'src="https://www.youtube-nocookie.com/embed/kOLpSPEA72U?feature=oembed" width="480"></iframe></p>',
+    assert ('<p><iframe height="270" '
+              'src="https://www.youtube-nocookie.com/embed/kOLpSPEA72U?feature=oembed" width="480"></iframe></p>' in
               r.replace('\n', ''))
 
 
@@ -338,24 +338,24 @@ def test_macro_embed_video_gone():
     r = g.markdown_wiki.convert('[[embed url=https://www.youtube.com/watch?v=OWsFqPZ3v-0]]')
     r = str(r)  # convert away from Markup, to get better assertion diff output
     # either of these could happen depending on the mood of youtube's oembed API:
-    assert_in(r, [
+    assert r in [
         '<div class="markdown_content"><p>Video not available</p></div>',
         '<div class="markdown_content"><p>Could not embed: https://www.youtube.com/watch?v=OWsFqPZ3v-0</p></div>',
-    ])
+    ]
 
 
 @patch('oembed.OEmbedEndpoint.fetch')
 def test_macro_embed_video_error(oembed_fetch):
     oembed_fetch.side_effect = OEmbedError('Invalid mime-type in response...')
     r = g.markdown_wiki.convert('[[embed url=http://www.youtube.com/watch?v=6YbBmqUnoQM]]')
-    assert_equal(r, '<div class="markdown_content"><p>Could not embed: '
+    assert (r == '<div class="markdown_content"><p>Could not embed: '
                     'http://www.youtube.com/watch?v=6YbBmqUnoQM</p></div>')
 
 
 def test_macro_embed_notsupported():
     r = g.markdown_wiki.convert('[[embed url=http://vimeo.com/46163090]]')
-    assert_equal(
-        r, '<div class="markdown_content"><p>[[embed url=http://vimeo.com/46163090]]</p></div>')
+    assert (
+        r == '<div class="markdown_content"><p>[[embed url=http://vimeo.com/46163090]]</p></div>')
 
 
 def test_markdown_toc():
@@ -389,19 +389,19 @@ def test_wiki_artifact_links():
 def test_markdown_links():
     with patch.dict(tg.config, {'nofollow_exempt_domains': 'foobar.net'}):
         text = g.markdown.convert('Read [here](http://foobar.net/) about our project')
-        assert_in('class="" href="http://foobar.net/">here</a> about', text)
+        assert 'class="" href="http://foobar.net/">here</a> about' in text
 
     text = g.markdown.convert('Read [here](http://foobar.net/) about our project')
-    assert_in('class="" href="http://foobar.net/" rel="nofollow">here</a> about', text)
+    assert 'class="" href="http://foobar.net/" rel="nofollow">here</a> about' in text
 
     text = g.markdown.convert('Read [here](/p/foobar/blah) about our project')
-    assert_in('class="" href="/p/foobar/blah">here</a> about', text)
+    assert 'class="" href="/p/foobar/blah">here</a> about' in text
 
     text = g.markdown.convert('Read [here](/p/foobar/blah/) about our project')
-    assert_in('class="" href="/p/foobar/blah/">here</a> about', text)
+    assert 'class="" href="/p/foobar/blah/">here</a> about' in text
 
     text = g.markdown.convert('Read <http://foobar.net/> about our project')
-    assert_in('href="http://foobar.net/" rel="nofollow">http://foobar.net/</a> about', text)
+    assert 'href="http://foobar.net/" rel="nofollow">http://foobar.net/</a> about' in text
 
 
 def test_markdown_and_html():
@@ -413,7 +413,7 @@ def test_markdown_and_html():
 def test_markdown_within_html():
     with h.push_context('test', neighborhood='Projects'):
         r = g.markdown_wiki.convert('<div style="float:left" markdown>**blah**</div>')
-    assert_in('<div style="float: left;"><p><strong>blah</strong></p></div>',
+    assert ('<div style="float: left;"><p><strong>blah</strong></p></div>' in
               r.replace('\n', ''))
 
 
@@ -425,37 +425,37 @@ def test_markdown_with_html_comments():
 def test_markdown_big_text():
     '''If text is too big g.markdown.convert should return plain text'''
     text = 'a' * 40001
-    assert_equal(g.markdown.convert(text), '<pre>%s</pre>' % text)
-    assert_equal(g.markdown_wiki.convert(text), '<pre>%s</pre>' % text)
+    assert g.markdown.convert(text) == '<pre>%s</pre>' % text
+    assert g.markdown_wiki.convert(text) == '<pre>%s</pre>' % text
 
 
 @td.with_wiki
 def test_markdown_basics():
     with h.push_context('test', 'wiki', neighborhood='Projects'):
         text = g.markdown.convert('# Foo!\n[Home]')
-        assert_equal(text,
+        assert (text ==
                      '<div class="markdown_content"><h1 id="foo">Foo!</h1>\n'
                      '<p><a class="alink" href="/p/test/wiki/Home/">[Home]</a></p></div>')
         text = g.markdown.convert('# Foo!\n[Rooted]')
-        assert_equal(text,
+        assert (text ==
                      '<div class="markdown_content"><h1 id="foo">Foo!</h1>\n'
                      '<p><span>[Rooted]</span></p></div>')
 
-    assert_equal(
-        g.markdown.convert('Multi\nLine'),
+    assert (
+        g.markdown.convert('Multi\nLine') ==
         '<div class="markdown_content"><p>Multi<br/>\n'
         'Line</p></div>')
-    assert_equal(
-        g.markdown.convert('Multi\n\nLine'),
+    assert (
+        g.markdown.convert('Multi\n\nLine') ==
         '<div class="markdown_content"><p>Multi</p>\n'
         '<p>Line</p></div>')
 
     # should not raise an exception:
-    assert_equal(
-        g.markdown.convert("<class 'foo'>"),
+    assert (
+        g.markdown.convert("<class 'foo'>") ==
         '''<div class="markdown_content"><p>&lt;class 'foo'=""&gt;&lt;/class&gt;</p></div>''')
 
-    assert_equal(
+    assert (
         g.markdown.convert('''# Header
 
 Some text in a regular paragraph
@@ -463,29 +463,26 @@ Some text in a regular paragraph
     :::python
     for i in range(10):
         print i
-'''),
+''') ==
         # no <br
         '<div class="markdown_content"><h1 id="header">Header</h1>\n'
         '<p>Some text in a regular paragraph</p>\n'
         '<div class="codehilite"><pre><span></span><code><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>\n'
         '    <span class="nb">print</span> <span class="n">i</span>\n'
         '</code></pre></div>\n'
-        '</div>'
-    )
-    assert_equal(
-        g.forge_markdown(email=True).convert('[Home]'),
+        '</div>')
+    assert (
+        g.forge_markdown(email=True).convert('[Home]') ==
         # uses localhost:
-        '<div class="markdown_content"><p><a class="alink" href="http://localhost/p/test/wiki/Home/">[Home]</a></p></div>'
-    )
-    assert_equal(
+        '<div class="markdown_content"><p><a class="alink" href="http://localhost/p/test/wiki/Home/">[Home]</a></p></div>')
+    assert (
         g.markdown.convert('''
 ~~~~
 def foo(): pass
-~~~~'''),
+~~~~''') ==
         '<div class="markdown_content"><div class="codehilite"><pre><span></span><code>def foo(): pass\n'
         '</code></pre></div>\n'
-        '</div>'
-    )
+        '</div>')
 
 
 def test_markdown_list_without_break():
@@ -494,64 +491,60 @@ def test_markdown_list_without_break():
     # it is valid in the CommonMark spec https://spec.commonmark.org/0.30/#lists
     # TODO: try https://github.com/adamb70/mdx-breakless-lists
     #       or https://gitlab.com/ayblaq/prependnewline
-    assert_equal(
+    assert (
         g.markdown.convert('''\
 Regular text
 * first item
-* second item'''),
+* second item''') ==
         '<div class="markdown_content"><p>Regular text\n'  # no <br>
         '* first item\n'  # no <br>
-        '* second item</p></div>'
-    )
+        '* second item</p></div>')
 
-    assert_equal(
+    assert (
         g.markdown.convert('''\
 Regular text
 - first item
-- second item'''),
+- second item''') ==
         '<div class="markdown_content"><p>Regular text<br/>\n'
         '- first item<br/>\n'
-        '- second item</p></div>'
-    )
+        '- second item</p></div>')
 
-    assert_equal(
+    assert (
         g.markdown.convert('''\
 Regular text
 + first item
-+ second item'''),
++ second item''') ==
         '<div class="markdown_content"><p>Regular text<br/>\n'
         '+ first item<br/>\n'
-        '+ second item</p></div>'
-    )
+        '+ second item</p></div>')
 
-    assert_equal(
+    assert (
         g.markdown.convert('''\
 Regular text
 1. first item
-2. second item'''),
+2. second item''') ==
         '<div class="markdown_content"><p>Regular text<br/>\n'
         '1. first item<br/>\n'
-        '2. second item</p></div>'
-    )
+        '2. second item</p></div>')
 
 
 def test_markdown_autolink():
     tgt = 'http://everything2.com/?node=nate+oostendorp'
     s = g.markdown.convert('This is %s' % tgt)
-    assert_equal(
-        s, f'<div class="markdown_content"><p>This is <a href="{tgt}" rel="nofollow">{tgt}</a></p></div>')
+    assert (
+        s == f'<div class="markdown_content"><p>This is <a href="{tgt}" rel="nofollow">{tgt}</a></p></div>')
     assert '<a href=' in g.markdown.convert('This is http://domain.net')
     # beginning of doc
-    assert_in('<a href=', g.markdown.convert('http://domain.net abc'))
+    assert '<a href=' in g.markdown.convert('http://domain.net abc')
     # beginning of a line
-    assert_in('<br/>\n<a href="http://',
+    assert ('<br/>\n<a href="http://' in
               g.markdown.convert('foobar\nhttp://domain.net abc'))
     # no conversion of these urls:
-    assert_in('a blahttp://sdf.com z',
+    assert ('a blahttp://sdf.com z' in
               g.markdown.convert('a blahttp://sdf.com z'))
-    assert_in('literal <code>http://domain.net</code> literal',
+    assert ('literal <code>http://domain.net</code> literal' in
               g.markdown.convert('literal `http://domain.net` literal'))
-    assert_in('<pre><span></span><code>preformatted http://domain.net\n</code></pre>',
+    assert ('<pre><span></span><code>preformatted http://domain.net\n</code></pre>' in
               g.markdown.convert('    :::text\n'
                                  '    preformatted http://domain.net'))
 
@@ -565,31 +558,31 @@ def test_markdown_autolink_with_escape():
 
 def test_markdown_invalid_script():
     r = g.markdown.convert('<script>alert(document.cookies)</script>')
-    assert_equal('<div class="markdown_content">&lt;script&gt;alert(document.cookies)&lt;/script&gt;\n</div>', r)
+    assert '<div class="markdown_content">&lt;script&gt;alert(document.cookies)&lt;/script&gt;\n</div>' == r
 
 
 def test_markdown_invalid_onerror():
     r = g.markdown.convert('<img src=x onerror=alert(document.cookie)>')
-    assert_not_in('onerror', r)
+    assert 'onerror' not in r
 
 
 def test_markdown_invalid_tagslash():
     r = g.markdown.convert('<div/onload><img src=x onerror=alert(document.cookie)>')
-    assert_not_in('onerror', r)
+    assert 'onerror' not in r
 
 
 def test_markdown_invalid_script_in_link():
     r = g.markdown.convert('[xss](http://"><a onmouseover=prompt(document.domain)>xss</a>)')
-    assert_equal('<div class="markdown_content"><p><a class="" '
+    assert ('<div class="markdown_content"><p><a class="" '
                  '''href='http://"&gt;&lt;a%20onmouseover=prompt(document.domain)&gt;xss&lt;/a&gt;' '''
-                 'rel="nofollow">xss</a></p></div>', r)
+                 'rel="nofollow">xss</a></p></div>' == r)
 
 
 def test_markdown_invalid_script_in_link2():
     r = g.markdown.convert('[xss](http://"><img src=x onerror=alert(document.cookie)>)')
-    assert_equal('<div class="markdown_content"><p><a class="" '
+    assert ('<div class="markdown_content"><p><a class="" '
                  '''href='http://"&gt;&lt;img%20src=x%20onerror=alert(document.cookie)&gt;' '''
-                 'rel="nofollow">xss</a></p></div>', r)
+                 'rel="nofollow">xss</a></p></div>' == r)
 
 
 def test_markdown_extremely_slow():
@@ -706,7 +699,7 @@ def test_filtering():
         r = g.markdown_wiki.convert(
             '[[projects category="%s"]]' % random_trove.fullpath)
         project_names = get_project_names(r)
-        assert_equal([test_project.name], project_names)
+        assert [test_project.name] == project_names
 
 
 def test_projects_macro():
@@ -732,7 +725,7 @@ def test_myprojects_macro():
         if p.deleted or p.is_nbhd_project:
             continue
         proj_title = f'<h2><a href="{p.url()}">{p.name}</a></h2>'
-        assert_in(proj_title, r)
+        assert proj_title in r
 
     h.set_context('u/test-user-1', 'wiki', neighborhood='Users')
     user = M.User.query.get(username='test-user-1')
@@ -741,7 +734,7 @@ def test_myprojects_macro():
         if p.deleted or p.is_nbhd_project:
             continue
         proj_title = f'<h2><a href="{p.url()}">{p.name}</a></h2>'
-        assert_in(proj_title, r)
+        assert proj_title in r
 
 
 @td.with_wiki
@@ -768,12 +761,12 @@ def test_hideawards_macro():
 
     with h.push_context(p_nbhd.neighborhood_project._id):
         r = g.markdown_wiki.convert('[[projects]]')
-        assert_in('<div class="feature"> <a href="http://award.org" rel="nofollow" title="Winner!">'
-                  'Award short</a> </div>',
+        assert ('<div class="feature"> <a href="http://award.org" rel="nofollow" title="Winner!">'
+                  'Award short</a> </div>' in
                   squish_spaces(r))
 
         r = g.markdown_wiki.convert('[[projects show_awards_banner=False]]')
-        assert_not_in('Award short', r)
+        assert 'Award short' not in r
 
 
 @td.with_tool('test', 'Blog', 'blog')
@@ -792,11 +785,11 @@ def test_project_blog_posts_macro():
         )
 
         r = g.markdown_wiki.convert('[[project_blog_posts]]')
-        assert_in('Test title</a></h3>', r)
-        assert_in('Test title2</a></h3>', r)
-        assert_in('<div class="markdown_content"><p>test post</p></div>', r)
-        assert_in('<div class="markdown_content"><p>test post2</p></div>', r)
-        assert_in('by <em>Test Admin</em>', r)
+        assert 'Test title</a></h3>' in r
+        assert 'Test title2</a></h3>' in r
+        assert '<div class="markdown_content"><p>test post</p></div>' in r
+        assert '<div class="markdown_content"><p>test post2</p></div>' in r
+        assert 'by <em>Test Admin</em>' in r
 
 
 def test_project_screenshots_macro():
@@ -806,8 +799,8 @@ def test_project_screenshots_macro():
 
         r = g.markdown_wiki.convert('[[project_screenshots]]')
 
-        assert_in('href="/p/test/screenshot/test_file.jpg"', r)
-        assert_in('src="/p/test/screenshot/test_file.jpg/thumb"', r)
+        assert 'href="/p/test/screenshot/test_file.jpg"' in r
+        assert 'src="/p/test/screenshot/test_file.jpg/thumb"' in r
 
 
 def get_project_names(r):
@@ -953,35 +946,35 @@ class TestEmojis(unittest.TestCase):
 
     def test_markdown_emoji_atomic(self):
         output = g.markdown.convert(':smile:')
-        assert_in('<p>\U0001F604</p>', output)
+        assert '<p>\U0001F604</p>' in output
         output = g.markdown.convert(':+1:')
-        assert_in('<p>\U0001F44D</p>', output)
+        assert '<p>\U0001F44D</p>' in output
         output = g.markdown.convert(':Bosnia_&_Herzegovina:')
-        assert_in('<p>\U0001F1E7\U0001F1E6</p>', output)
+        assert '<p>\U0001F1E7\U0001F1E6</p>' in output
         output = g.markdown.convert(':Åland_Islands:')  # emoji code with non-ascii character
-        assert_in('<p>\U0001F1E6\U0001F1FD</p>', output)
+        assert '<p>\U0001F1E6\U0001F1FD</p>' in output
 
     def test_markdown_emoji_with_text(self):
         output = g.markdown.convert('Thumbs up emoji :+1: wow!')
-        assert_in('<p>Thumbs up emoji \U0001F44D wow!</p>', output)
+        assert '<p>Thumbs up emoji \U0001F44D wow!</p>' in output
         output = g.markdown.convert('More emojis :+1::camel::three_o’clock: wow!')
-        assert_in('<p>More emojis \U0001F44D\U0001F42B\U0001F552 wow!</p>', output)
+        assert '<p>More emojis \U0001F44D\U0001F42B\U0001F552 wow!</p>' in output
         output = g.markdown.convert(':man_bouncing_ball_medium-light_skin_tone:emoji:+1:')
-        assert_in('<p>\U000026F9\U0001F3FC\U0000200D\U00002642\U0000FE0Femoji\U0001F44D</p>', output)
+        assert '<p>\U000026F9\U0001F3FC\U0000200D\U00002642\U0000FE0Femoji\U0001F44D</p>' in output
 
     def test_markdown_emoji_in_code(self):
         output = g.markdown.convert('This will not become an emoji `:+1:`')
-        assert_in('<p>This will not become an emoji <code>:+1:</code></p>', output)
+        assert '<p>This will not become an emoji <code>:+1:</code></p>' in output
         output = g.markdown.convert('```html\n<p>:camel:</p>\n```')
-        assert_in(':camel:', output)
+        assert ':camel:' in output
         output = g.markdown.convert('~~~\n:camel:\n~~~')
-        assert_in('<pre><span></span><code>:camel:\n</code></pre>', output)
+        assert '<pre><span></span><code>:camel:\n</code></pre>' in output
 
     def test_markdown_commit_with_emojis(self):
         output = g.markdown_commit.convert('Thumbs up emoji :+1: wow!')
-        assert_in('Thumbs up emoji \U0001F44D wow!', output)
+        assert 'Thumbs up emoji \U0001F44D wow!' in output
         output = g.markdown.convert('More emojis :+1::camel::three_o’clock: wow!')
-        assert_in('More emojis \U0001F44D\U0001F42B\U0001F552 wow!', output)
+        assert 'More emojis \U0001F44D\U0001F42B\U0001F552 wow!' in output
 
 
 class TestUserMentions(unittest.TestCase):
@@ -1092,31 +1085,31 @@ class TestIconRender:
 
     def test_default(self):
         html = '<a class="icon" href="#" title="Edit"><i class="fa fa-edit"></i></a>'
-        assert_equal(html, self.i.render())
+        assert html == self.i.render()
 
     def test_show_title(self):
         html = '<a class="icon" href="#" title="Edit"><i class="fa fa-edit"></i>&nbsp;Edit</a>'
-        assert_equal(html, self.i.render(show_title=True))
+        assert html == self.i.render(show_title=True)
 
         html = '<a class="icon" href="#" title="&lt;script&gt;"><i class="fa fa-edit"></i>&nbsp;&lt;script&gt;</a>'
-        assert_equal(html, self.i.render(show_title=True, title="<script>"))
+        assert html == self.i.render(show_title=True, title="<script>")
 
     def test_extra_css(self):
         html = '<a class="icon reply btn" href="#" title="Edit"><i class="fa fa-edit"></i></a>'
-        assert_equal(html, self.i.render(extra_css='reply btn'))
+        assert html == self.i.render(extra_css='reply btn')
 
     def test_no_closing_tag(self):
         html = '<a class="icon" href="#" title="Edit"><i class="fa fa-edit"></i>'
-        assert_equal(html, self.i.render(closing_tag=False))
+        assert html == self.i.render(closing_tag=False)
 
     def test_tag(self):
         html = '<div class="icon" title="Edit"><i class="fa fa-edit"></i></div>'
-        assert_equal(html, self.i.render(tag='div'))
+        assert html == self.i.render(tag='div')
 
     def test_kwargs(self):
         html = '<a class="icon" data-id="123" href="#" title="Edit"><i class="fa fa-edit"></i></a>'
-        assert_equal(html, self.i.render(**{'data-id': '123'}))
+        assert html == self.i.render(**{'data-id': '123'})
 
     def test_escaping(self):
         html = '<a class="icon &#34;" data-url="&gt;" href="#" title="Edit"><i class="fa fa-edit"></i></a>'
-        assert_equal(html, self.i.render(extra_css='"', **{'data-url': '>'}))
+        assert html == self.i.render(extra_css='"', **{'data-url': '>'})
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 650ba023f..8f11145e1 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -80,14 +80,14 @@ def test_escape_json():
     inputdata = {"foo": "bar</script><img src=foobar onerror=alert(1)>"}
     outputsample = '{"foo": "bar\\u003C/script>\\u003Cimg src=foobar onerror=alert(1)>"}'
     outputdata = h.escape_json(inputdata)
-    assert_equals(outputdata, outputsample)
+    assert outputdata == outputsample
 
 
 def test_strip_bad_unicode():
     inputdata = 'Hello\x08World\t\n\rfoo bar\x1E'
     outputsample = 'HelloWorld\t\n\rfoo bar'
     outputdata = h.strip_bad_unicode(inputdata)
-    assert_equals(outputdata, outputsample)
+    assert outputdata == outputsample
 
 
 def test_really_unicode():
@@ -105,22 +105,22 @@ def test_really_unicode():
     s = h._attempt_encodings(b'foo', ['LKDJFLDK'])
     assert isinstance(s, str)
     # unicode stays the same
-    assert_equals(h.really_unicode('¬∂•°‹'), '¬∂•°‹')
+    assert h.really_unicode('¬∂•°‹') == '¬∂•°‹'
     # other types are handled too
-    assert_equals(h.really_unicode(1234), '1234')
-    assert_equals(h.really_unicode(datetime(2020, 1, 1)), '2020-01-01 00:00:00')
-    assert_equals(h.really_unicode(None), '')
+    assert h.really_unicode(1234) == '1234'
+    assert h.really_unicode(datetime(2020, 1, 1)) == '2020-01-01 00:00:00'
+    assert h.really_unicode(None) == ''
     # markup stays markup
     s = h.really_unicode(Markup('<b>test</b>'))
     assert isinstance(s, str)
     assert isinstance(s, Markup)
-    assert_equals(s, '<b>test</b>')
+    assert s == '<b>test</b>'
 
 
 def test_find_project():
     proj, rest = h.find_project('/p/test/foo')
-    assert_equals(proj.shortname, 'test')
-    assert_equals(proj.neighborhood.name, 'Projects')
+    assert proj.shortname == 'test'
+    assert proj.neighborhood.name == 'Projects'
     proj, rest = h.find_project('/p/testable/foo')
     assert proj is None
 
@@ -203,34 +203,34 @@ def test_encode_keys():
 
 
 def test_ago():
-    assert_equals(h.ago(datetime.utcnow() - timedelta(days=2)), '2 days ago')
-    assert_equals(h.ago(datetime.utcnow() + timedelta(days=2)), 'in 2 days')
-    assert_equals(h.ago_ts(time.time() - 60 * 60 * 2), '2 hours ago')
+    assert h.ago(datetime.utcnow() - timedelta(days=2)) == '2 days ago'
+    assert h.ago(datetime.utcnow() + timedelta(days=2)) == 'in 2 days'
+    assert h.ago_ts(time.time() - 60 * 60 * 2) == '2 hours ago'
     d_str = (datetime.utcnow() - timedelta(hours=3)).isoformat()
-    assert_equals(h.ago_string(d_str), '3 hours ago')
-    assert_equals(h.ago_string('bad format'), 'unknown')
-    assert_equals(h.ago_string(None), 'unknown')
+    assert h.ago_string(d_str) == '3 hours ago'
+    assert h.ago_string('bad format') == 'unknown'
+    assert h.ago_string(None) == 'unknown'
 
     monthish = datetime.utcnow() - timedelta(days=32)
     assert 'ago' not in h.ago(monthish)
-    assert_equals(h.ago(monthish, show_date_after=90), '1 month ago')
-    assert_equals(h.ago(monthish, show_date_after=None), '1 month ago')
+    assert h.ago(monthish, show_date_after=90) == '1 month ago'
+    assert h.ago(monthish, show_date_after=None) == '1 month ago'
 
     monthish = datetime.utcnow() + timedelta(days=32)
     assert 'in ' not in h.ago(monthish)
-    assert_equals(h.ago(monthish, show_date_after=90), 'in 1 month')
-    assert_equals(h.ago(monthish, show_date_after=None), 'in 1 month')
+    assert h.ago(monthish, show_date_after=90) == 'in 1 month'
+    assert h.ago(monthish, show_date_after=None) == 'in 1 month'
 
 
 def test_urlquote_unicode():
     # No exceptions please
-    assert_equals('%D0%90', h.urlquote('\u0410'))
-    assert_equals('%D0%90', h.urlquoteplus('\u0410'))
-    assert_equals('%D0%BF%D1%80%D0%B8%D0%B2%D1%96%D1%82.txt', h.urlquote('привіт.txt'))
+    assert '%D0%90' == h.urlquote('\u0410')
+    assert '%D0%90' == h.urlquoteplus('\u0410')
+    assert '%D0%BF%D1%80%D0%B8%D0%B2%D1%96%D1%82.txt' == h.urlquote('привіт.txt')
 
 
 def test_sharded_path():
-    assert_equals(h.sharded_path('foobar'), 'f/fo')
+    assert h.sharded_path('foobar') == 'f/fo'
 
 
 def test_paging_sanitizer():
@@ -254,19 +254,19 @@ def test_paging_sanitizer():
 
 
 def test_render_any_markup_empty():
-    assert_equals(h.render_any_markup('foo', ''), '<p><em>Empty File</em></p>')
+    assert h.render_any_markup('foo', '') == '<p><em>Empty File</em></p>'
 
 
 def test_render_any_markup_plain():
-    assert_equals(
+    assert (
         h.render_any_markup(
-            'readme.txt', '<b>blah</b>\n<script>alert(1)</script>\nfoo'),
+            'readme.txt', '<b>blah</b>\n<script>alert(1)</script>\nfoo') ==
         '<pre>&lt;b&gt;blah&lt;/b&gt;\n&lt;script&gt;alert(1)&lt;/script&gt;\nfoo</pre>')
 
 
 def test_render_any_markup_formatting():
-    assert_equals(str(h.render_any_markup('README.md', '### foo\n'
-                                          '    <script>alert(1)</script> bar')),
+    assert (str(h.render_any_markup('README.md', '### foo\n'
+                                          '    <script>alert(1)</script> bar')) ==
                   '<div class="markdown_content"><h3 id="foo">foo</h3>\n'
                   '<div class="codehilite"><pre><span></span><code><span class="nt">'
                   '&lt;script&gt;</span>alert(1)<span class="nt">'
@@ -275,7 +275,7 @@ def test_render_any_markup_formatting():
 
 def test_render_any_markdown_encoding():
     # send encoded content in, make sure it converts it to actual unicode object which Markdown lib needs
-    assert_equals(h.render_any_markup('README.md', 'Müller'.encode()),
+    assert (h.render_any_markup('README.md', 'Müller'.encode()) ==
                   '<div class="markdown_content"><p>Müller</p></div>')
 
 
@@ -311,29 +311,29 @@ def test_get_tool_packages():
 
 
 def test_get_first():
-    assert_equals(h.get_first({}, 'title'), None)
-    assert_equals(h.get_first({'title': None}, 'title'), None)
-    assert_equals(h.get_first({'title': 'Value'}, 'title'), 'Value')
-    assert_equals(h.get_first({'title': ['Value']}, 'title'), 'Value')
-    assert_equals(h.get_first({'title': []}, 'title'), None)
-    assert_equals(h.get_first({'title': ['Value']}, 'title'), 'Value')
+    assert h.get_first({}, 'title') == None
+    assert h.get_first({'title': None}, 'title') == None
+    assert h.get_first({'title': 'Value'}, 'title') == 'Value'
+    assert h.get_first({'title': ['Value']}, 'title') == 'Value'
+    assert h.get_first({'title': []}, 'title') == None
+    assert h.get_first({'title': ['Value']}, 'title') == 'Value'
 
 
 @patch('allura.lib.search.c')
 def test_inject_user(context):
     user = Mock(username='user01')
-    assert_equals(inject_user(None, user), None)
-    assert_equals(inject_user('', user), '')
-    assert_equals(inject_user('query', user), 'query')
+    assert inject_user(None, user) == None
+    assert inject_user('', user) == ''
+    assert inject_user('query', user) == 'query'
     result = inject_user('reported_by_s:$USER OR assigned_to_s:$USER', user)
-    assert_equals(result, 'reported_by_s:"user01" OR assigned_to_s:"user01"')
+    assert result == 'reported_by_s:"user01" OR assigned_to_s:"user01"'
     context.user = Mock(username='admin1')
     result = inject_user('reported_by_s:$USER OR assigned_to_s:$USER')
-    assert_equals(result, 'reported_by_s:"admin1" OR assigned_to_s:"admin1"')
+    assert result == 'reported_by_s:"admin1" OR assigned_to_s:"admin1"'
     context.user = Mock(username='*anonymous')
     result = inject_user('reported_by_s:$USER OR assigned_to_s:$USER')
-    assert_equals(
-        result, 'reported_by_s:"*anonymous" OR assigned_to_s:"*anonymous"')
+    assert (
+        result == 'reported_by_s:"*anonymous" OR assigned_to_s:"*anonymous"')
 
 
 def test_datetimeformat():
@@ -342,24 +342,24 @@ def test_datetimeformat():
 
 
 def test_nl2br_jinja_filter():
-    assert_equals(h.nl2br_jinja_filter('foo<script>alert(1)</script>\nbar\nbaz'),
+    assert (h.nl2br_jinja_filter('foo<script>alert(1)</script>\nbar\nbaz') ==
                   Markup('foo&lt;script&gt;alert(1)&lt;/script&gt;<br>\nbar<br>\nbaz'))
 
 
 def test_split_select_field_options():
-    assert_equals(h.split_select_field_options('"test message" test2'),
+    assert (h.split_select_field_options('"test message" test2') ==
                   ['test message', 'test2'])
-    assert_equals(h.split_select_field_options('"test message test2'),
+    assert (h.split_select_field_options('"test message test2') ==
                   ['test', 'message', 'test2'])
-    assert_equals(h.split_select_field_options('abc ƒå∂ ººº'),
+    assert (h.split_select_field_options('abc ƒå∂ ººº') ==
                   ['abc', 'ƒå∂', 'ººº'])
 
 
 def test_notifications_disabled():
     project = Mock(notifications_disabled=False)
     with h.notifications_disabled(project):
-        assert_equals(project.notifications_disabled, True)
-    assert_equals(project.notifications_disabled, False)
+        assert project.notifications_disabled == True
+    assert project.notifications_disabled == False
 
 
 @skipIf(module_not_available('html2text'), 'html2text required')
@@ -513,12 +513,12 @@ class TestUrlOpen(TestCase):
 
 
 def test_absurl():
-    assert_equals(h.absurl('/p/test/foobar'), 'http://localhost/p/test/foobar')
+    assert h.absurl('/p/test/foobar') == 'http://localhost/p/test/foobar'
 
 
 def test_daterange():
-    assert_equals(
-        list(h.daterange(datetime(2013, 1, 1), datetime(2013, 1, 4))),
+    assert (
+        list(h.daterange(datetime(2013, 1, 1), datetime(2013, 1, 4))) ==
         [datetime(2013, 1, 1), datetime(2013, 1, 2), datetime(2013, 1, 3)])
 
 
@@ -589,24 +589,24 @@ class TestIterEntryPoints(TestCase):
 
 def test_get_user_status():
     user = M.User.by_username('test-admin')
-    assert_equals(h.get_user_status(user), 'enabled')
+    assert h.get_user_status(user) == 'enabled'
 
     user = Mock(disabled=True, pending=False)
-    assert_equals(h.get_user_status(user), 'disabled')
+    assert h.get_user_status(user) == 'disabled'
 
     user = Mock(disabled=False, pending=True)
-    assert_equals(h.get_user_status(user), 'pending')
+    assert h.get_user_status(user) == 'pending'
 
     user = Mock(disabled=True, pending=True)  # not an expected combination
-    assert_equals(h.get_user_status(user), 'disabled')
+    assert h.get_user_status(user) == 'disabled'
 
 
 def test_convert_bools():
-    assert_equals(h.convert_bools({'foo': 'bar', 'baz': 'false', 'abc': 0, 'def': 1, 'ghi': True}),
+    assert (h.convert_bools({'foo': 'bar', 'baz': 'false', 'abc': 0, 'def': 1, 'ghi': True}) ==
                   {'foo': 'bar', 'baz': False, 'abc': 0, 'def': 1, 'ghi': True})
-    assert_equals(h.convert_bools({'foo': 'true', 'baz': ' TRUE '}),
+    assert (h.convert_bools({'foo': 'true', 'baz': ' TRUE '}) ==
                   {'foo': True, 'baz': True})
-    assert_equals(h.convert_bools({'foo': 'true', 'baz': ' TRUE '}, prefix='ba'),
+    assert (h.convert_bools({'foo': 'true', 'baz': ' TRUE '}, prefix='ba') ==
                   {'foo': 'true', 'baz': True})
 
 
@@ -621,21 +621,21 @@ def test_base64uri_img():
 
 def test_base64uri_text():
     b64txt = h.base64uri('blah blah blah\n123 456\nfoo bar baz', mimetype='text/plain')
-    assert_equals(b64txt, 'data:text/plain;base64,YmxhaCBibGFoIGJsYWgKMTIzIDQ1Ngpmb28gYmFyIGJheg==')
+    assert b64txt == 'data:text/plain;base64,YmxhaCBibGFoIGJsYWgKMTIzIDQ1Ngpmb28gYmFyIGJheg=='
 
     b64txt = h.base64uri('blah blah blah\n123 456\nfoo bar baz', mimetype='text/plain', windows_line_endings=True)
-    assert_equals(b64txt, 'data:text/plain;base64,YmxhaCBibGFoIGJsYWgNCjEyMyA0NTYNCmZvbyBiYXIgYmF6')
+    assert b64txt == 'data:text/plain;base64,YmxhaCBibGFoIGJsYWgNCjEyMyA0NTYNCmZvbyBiYXIgYmF6'
 
 
 def test_slugify():
-    assert_equals(h.slugify('Foo Bar Bat')[0], 'Foo-Bar-Bat')
-    assert_equals(h.slugify('Foo_Bar')[0], 'Foo_Bar')
-    assert_equals(h.slugify('Foo   ')[0], 'Foo')
-    assert_equals(h.slugify('    Foo   ')[0], 'Foo')
-    assert_equals(h.slugify('"    Foo   ')[0], 'Foo')
-    assert_equals(h.slugify('Fôö')[0], 'Foo')
-    assert_equals(h.slugify('Foo.Bar')[0], 'Foo-Bar')
-    assert_equals(h.slugify('Foo.Bar', True)[0], 'Foo.Bar')
+    assert h.slugify('Foo Bar Bat')[0] == 'Foo-Bar-Bat'
+    assert h.slugify('Foo_Bar')[0] == 'Foo_Bar'
+    assert h.slugify('Foo   ')[0] == 'Foo'
+    assert h.slugify('    Foo   ')[0] == 'Foo'
+    assert h.slugify('"    Foo   ')[0] == 'Foo'
+    assert h.slugify('Fôö')[0] == 'Foo'
+    assert h.slugify('Foo.Bar')[0] == 'Foo-Bar'
+    assert h.slugify('Foo.Bar', True)[0] == 'Foo.Bar'
 
 
 class TestRateLimit(TestCase):
@@ -671,26 +671,26 @@ class TestRateLimit(TestCase):
 
 
 def test_hide_private_info():
-    assert_equals(h.hide_private_info(None), None)
-    assert_equals(h.hide_private_info(''), '')
-    assert_equals(h.hide_private_info('foo bar baz@bing.com'), 'foo bar baz@...')
-    assert_equals(h.hide_private_info('some <1...@2.com>\nor asdf+asdf.f@g.f.x'), 'some <1...@...>\nor asdf+asdf.f@...')
+    assert h.hide_private_info(None) == None
+    assert h.hide_private_info('') == ''
+    assert h.hide_private_info('foo bar baz@bing.com') == 'foo bar baz@...'
+    assert h.hide_private_info('some <1...@2.com>\nor asdf+asdf.f@g.f.x') == 'some <1...@...>\nor asdf+asdf.f@...'
     safe_markup_converted = h.hide_private_info(Markup('foo bar baz@bing.com'))
-    assert_equals(type(safe_markup_converted), Markup)
-    assert_equals(safe_markup_converted, Markup('foo bar baz@...'))
+    assert type(safe_markup_converted) == Markup
+    assert safe_markup_converted == Markup('foo bar baz@...')
 
     with h.push_config(h.tg.config, hide_private_info=False):
-        assert_equals(h.hide_private_info('foo bar baz@bing.com'), 'foo bar baz@bing.com')
+        assert h.hide_private_info('foo bar baz@bing.com') == 'foo bar baz@bing.com'
 
 
 def test_emojize():
-    assert_equals(h.emojize(':smile:'), '😄')
+    assert h.emojize(':smile:') == '😄'
 
 
 def test_querystring():
     req = Request.blank('/p/test/foobar?page=1&limit=10&count=100', remote_addr='127.0.0.1',
                         base_url='https://mysite.com/p/test/foobar')
-    assert_equals(h.querystring(req, dict(page=2, limit=5)),
+    assert (h.querystring(req, dict(page=2, limit=5)) ==
                   'https://mysite.com/p/test/foobar/p/test/foobar?page=2&limit=5&count=100')
-    assert_equals(h.querystring(req, dict(page=5, limit=2, count=None)),
+    assert (h.querystring(req, dict(page=5, limit=2, count=None)) ==
                   'https://mysite.com/p/test/foobar/p/test/foobar?page=5&limit=2')
diff --git a/Allura/allura/tests/test_mail_util.py b/Allura/allura/tests/test_mail_util.py
index 1138c44e7..c0b15c264 100644
--- a/Allura/allura/tests/test_mail_util.py
+++ b/Allura/allura/tests/test_mail_util.py
@@ -79,9 +79,9 @@ class TestReactor(unittest.TestCase):
     def test_parse_address_good(self):
         topic, project, app = parse_address(
             'foo@wiki.test.p' + config.common_suffix)
-        assert_equal(topic, 'foo')
-        assert_equal(project.shortname, 'test')
-        assert_true(isinstance(app, Application))
+        assert topic == 'foo'
+        assert project.shortname == 'test'
+        assert isinstance(app, Application)
 
     def test_unicode_simple_message(self):
         charset = 'utf-8'
@@ -96,7 +96,7 @@ class TestReactor(unittest.TestCase):
         s_msg = msg1.as_string()
         msg2 = parse_message(s_msg)
         assert isinstance(msg2['payload'], str)
-        assert_in('всех', msg2['payload'])
+        assert 'всех' in msg2['payload']
 
     def test_more_encodings(self):
         # these are unicode strings to reflect behavior after loading 'route_email' tasks from mongo
@@ -115,7 +115,7 @@ IGZ0cCx0ZWxuZXQscGluZyBjYW4ndCB3b3JrICEKCgpXaHk/
 """
         msg = parse_message(s_msg)
         assert isinstance(msg['payload'], str)
-        assert_in('The Snap7 application', msg['payload'])
+        assert 'The Snap7 application' in msg['payload']
 
         s_msg = """Date: Sat, 25 May 2019 09:32:00 +1000
 From: <fo...@bar.com>
@@ -134,7 +134,7 @@ Content-Transfer-Encoding: 8bit
 """
         msg = parse_message(s_msg)
         assert isinstance(msg['payload'], str)
-        assert_in('• foo', msg['payload'])
+        assert '• foo' in msg['payload']
 
         s_msg = """Date: Sat, 25 May 2019 09:32:00 +1000
 From: <fo...@bar.com>
@@ -147,7 +147,7 @@ programmed or èrogrammed ?
 """
         msg = parse_message(s_msg)
         assert isinstance(msg['payload'], str)
-        assert_in('èrogrammed', msg['payload'])
+        assert 'èrogrammed' in msg['payload']
 
     def test_more_encodings_multipart(self):
         # these are unicode strings to reflect behavior after loading 'route_email' tasks from mongo
@@ -178,8 +178,8 @@ Content-Type: text/html; charset="utf-8"
         msg = parse_message(s_msg)
         assert isinstance(msg['parts'][1]['payload'], str)
         assert isinstance(msg['parts'][2]['payload'], str)
-        assert_in('• foo', msg['parts'][1]['payload'])
-        assert_in('• foo', msg['parts'][2]['payload'])
+        assert '• foo' in msg['parts'][1]['payload']
+        assert '• foo' in msg['parts'][2]['payload']
 
     def test_unicode_complex_message(self):
         charset = 'utf-8'
@@ -214,19 +214,19 @@ class TestHeader:
     @raises(TypeError)
     def test_bytestring(self):
         our_header = Header(b'[asdf2:wiki] Discussion for Home page')
-        assert_equal(our_header.encode(), '[asdf2:wiki] Discussion for Home page')
+        assert our_header.encode() == '[asdf2:wiki] Discussion for Home page'
 
     def test_ascii(self):
         our_header = Header('[asdf2:wiki] Discussion for Home page')
-        assert_equal(our_header.encode(), '[asdf2:wiki] Discussion for Home page')
+        assert our_header.encode() == '[asdf2:wiki] Discussion for Home page'
 
     def test_utf8(self):
         our_header = Header('теснятся')
-        assert_equal(our_header.encode(), '=?utf-8?b?0YLQtdGB0L3Rj9GC0YHRjw==?=')
+        assert our_header.encode() == '=?utf-8?b?0YLQtdGB0L3Rj9GC0YHRjw==?='
 
     def test_name_addr(self):
         our_header = Header('"теснятся"', '<da...@b.com>')
-        assert_equal(our_header.encode(),
+        assert (our_header.encode() ==
                      '=?utf-8?b?ItGC0LXRgdC90Y/RgtGB0Y8i?= <da...@b.com>')
 
 
@@ -236,44 +236,44 @@ class TestIsAutoreply:
         self.msg = {'headers': {}}
 
     def test_empty(self):
-        assert_false(is_autoreply(self.msg))
+        assert not is_autoreply(self.msg)
 
     def test_gmail(self):
         self.msg['headers']['Auto-Submitted'] = 'auto-replied'
         self.msg['headers']['Precedence'] = 'bulk'
         self.msg['headers']['X-Autoreply'] = 'yes'
-        assert_true(is_autoreply(self.msg))
+        assert is_autoreply(self.msg)
 
     def test_qmail(self):
         self.msg['headers']['Delivered-To'] = 'Autoresponder'
-        assert_true(is_autoreply(self.msg))
+        assert is_autoreply(self.msg)
 
     def test_mailtraq(self):
         self.msg['headers']['X-POST-MessageClass'] = '9; Autoresponder'
-        assert_true(is_autoreply(self.msg))
+        assert is_autoreply(self.msg)
 
     def test_firstclass(self):
         self.msg['headers']['X-FC-MachineGenerated'] = 'true'
-        assert_true(is_autoreply(self.msg))
+        assert is_autoreply(self.msg)
 
     def test_domain_technologies_control(self):
         self.msg['headers']['X-AutoReply-From'] = 'something'
         self.msg['headers']['X-Mail-Autoreply'] = 'something'
-        assert_true(is_autoreply(self.msg))
+        assert is_autoreply(self.msg)
 
     def test_communicate_pro(self):
         self.msg['headers']['X-Autogenerated'] = 'Forward'
-        assert_true(is_autoreply(self.msg))
+        assert is_autoreply(self.msg)
 
     def test_boxtrapper_cpanel(self):
         self.msg['headers']['Preference'] = 'auto_reply'
         self.msg['headers']['X-Precedence'] = 'auto_reply'
         self.msg['headers']['X-Autorespond'] = 'auto_reply'
-        assert_true(is_autoreply(self.msg))
+        assert is_autoreply(self.msg)
 
     def test_return_path(self):
         self.msg['headers']['Return-Path'] = '<>'
-        assert_true(is_autoreply(self.msg))
+        assert is_autoreply(self.msg)
 
 
 class TestIdentifySender:
@@ -283,7 +283,7 @@ class TestIdentifySender:
         EA.canonical = lambda e: e
         EA.get.side_effect = [
             mock.Mock(claimed_by_user_id=True, claimed_by_user=lambda:'user')]
-        assert_equal(identify_sender(None, 'arg', None, None), 'user')
+        assert identify_sender(None, 'arg', None, None) == 'user'
         EA.get.assert_called_once_with(email='arg', confirmed=True)
 
     @mock.patch('allura.model.EmailAddress')
@@ -291,9 +291,9 @@ class TestIdentifySender:
         EA.canonical = lambda e: e
         EA.get.side_effect = [
             None, mock.Mock(claimed_by_user_id=True, claimed_by_user=lambda:'user')]
-        assert_equal(
-            identify_sender(None, 'arg', {'From': 'from'}, None), 'user')
-        assert_equal(EA.get.call_args_list,
+        assert (
+            identify_sender(None, 'arg', {'From': 'from'}, None) == 'user')
+        assert (EA.get.call_args_list ==
                      [mock.call(email='arg', confirmed=True), mock.call(email='from')])
 
     @mock.patch('allura.model.User')
@@ -303,8 +303,8 @@ class TestIdentifySender:
         EA.canonical = lambda e: e
         EA.get.side_effect = [
             None, mock.Mock(claimed_by_user_id=True, claimed_by_user=lambda:'user')]
-        assert_equal(identify_sender(None, 'arg', {}, None), anon)
-        assert_equal(EA.get.call_args_list, [mock.call(email='arg', confirmed=True)])
+        assert identify_sender(None, 'arg', {}, None) == anon
+        assert EA.get.call_args_list == [mock.call(email='arg', confirmed=True)]
 
     @mock.patch('allura.model.User')
     @mock.patch('allura.model.EmailAddress')
@@ -312,17 +312,17 @@ class TestIdentifySender:
         anon = User.anonymous()
         EA.canonical = lambda e: e
         EA.get.side_effect = [None, None]
-        assert_equal(
-            identify_sender(None, 'arg', {'From': 'from'}, None), anon)
-        assert_equal(EA.get.call_args_list,
+        assert (
+            identify_sender(None, 'arg', {'From': 'from'}, None) == anon)
+        assert (EA.get.call_args_list ==
                      [mock.call(email='arg', confirmed=True), mock.call(email='from')])
 
 
 def test_parse_message_id():
-    assert_equal(_parse_message_id('<de...@libjpeg-turbo.p.domain.net>, </p...@libjpeg-turbo.p.domain.net>'), [
+    assert _parse_message_id('<de...@libjpeg-turbo.p.domain.net>, </p...@libjpeg-turbo.p.domain.net>') == [
         'de31888f6be2d87dc377d9e713876bb514548625.patches@libjpeg-turbo.p.domain.net',
         'de31888f6be2d87dc377d9e713876bb514548625.patches@libjpeg-turbo.p.domain.net',
-    ])
+    ]
 
 
 class TestMailServer:
@@ -336,5 +336,5 @@ class TestMailServer:
         mailserver = MailServer(listen_port, None)
         mailserver.process_message('127.0.0.1', 'foo@bar.com', ['1234@tickets.test.p.localhost'],
                                    'this is the email body with headers and everything Ο'.encode())
-        assert_equal([], log.exception.call_args_list)
+        assert [] == log.exception.call_args_list
         assert log.info.call_args[0][0].startswith('Msg passed along as task '), log.info.call_args
diff --git a/Allura/allura/tests/test_middlewares.py b/Allura/allura/tests/test_middlewares.py
index a428a10e8..60d4265b6 100644
--- a/Allura/allura/tests/test_middlewares.py
+++ b/Allura/allura/tests/test_middlewares.py
@@ -34,9 +34,9 @@ class TestCORSMiddleware:
 
     def test_init(self):
         cors = CORSMiddleware(self.app, ['get', 'post'], ['Some-Header'])
-        assert_equal(cors.app, self.app)
-        assert_equal(cors.allowed_methods, ['GET', 'POST'])
-        assert_equal(cors.allowed_headers, {'some-header'})
+        assert cors.app == self.app
+        assert cors.allowed_methods == ['GET', 'POST']
+        assert cors.allowed_headers == {'some-header'}
 
     def test_call_not_api_request(self):
         callback = MagicMock()
@@ -56,9 +56,9 @@ class TestCORSMiddleware:
                'HTTP_ORIGIN': 'my.site.com',
                'REQUEST_METHOD': 'GET'}
         self.cors(env, callback)
-        assert_equal(self.app.call_count, 1)
-        assert_equal(self.app.call_args_list[0][0][0], env)
-        assert_not_equal(self.app.call_args_list[0][0][1], callback)
+        assert self.app.call_count == 1
+        assert self.app.call_args_list[0][0][0] == env
+        assert self.app.call_args_list[0][0][1] != callback
 
     @patch('allura.lib.custom_middleware.exc', autospec=True)
     def test_handle_call_preflight_request(self, exc):
@@ -68,7 +68,7 @@ class TestCORSMiddleware:
                'REQUEST_METHOD': 'OPTIONS',
                'HTTP_ACCESS_CONTROL_REQUEST_METHOD': 'POST'}
         self.cors(env, callback)
-        assert_equal(self.app.call_count, 0)
+        assert self.app.call_count == 0
         exc.HTTPOk.assert_called_once_with(headers=[
             ('Access-Control-Allow-Origin', '*'),
             ('Access-Control-Allow-Methods', 'GET, POST, DELETE'),
@@ -78,21 +78,21 @@ class TestCORSMiddleware:
 
     def test_get_response_headers_simple(self):
         # Allow-Origin: * is crucial for security, since that prevents browsers from exposing results fetched withCredentials: true (aka cookies)
-        assert_equal(self.cors.get_response_headers(),
+        assert (self.cors.get_response_headers() ==
                      [('Access-Control-Allow-Origin', '*')])
-        assert_equal(self.cors.get_response_headers(preflight=False),
+        assert (self.cors.get_response_headers(preflight=False) ==
                      [('Access-Control-Allow-Origin', '*')])
 
     def test_get_response_headers_preflight(self):
-        assert_equal(
-            self.cors.get_response_headers(preflight=True),
+        assert (
+            self.cors.get_response_headers(preflight=True) ==
             [('Access-Control-Allow-Origin', '*'),
              ('Access-Control-Allow-Methods', 'GET, POST, DELETE'),
              ('Access-Control-Allow-Headers', 'accept, authorization')])
 
     def test_get_response_headers_preflight_with_cache(self):
         cors = CORSMiddleware(self.app, ['GET', 'PUT'], ['Accept'], 86400)
-        assert_equal(cors.get_response_headers(preflight=True),
+        assert (cors.get_response_headers(preflight=True) ==
                      [('Access-Control-Allow-Origin', '*'),
                       ('Access-Control-Allow-Methods', 'GET, PUT'),
                       ('Access-Control-Allow-Headers', 'accept'),
@@ -101,7 +101,7 @@ class TestCORSMiddleware:
     def test_get_access_control_request_headers(self):
         key = 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS'
         f = self.cors.get_access_control_request_headers
-        assert_equal(f({}), set())
-        assert_equal(f({key: ''}), set())
-        assert_equal(f({key: 'Authorization, Accept'}),
+        assert f({}) == set()
+        assert f({key: ''}) == set()
+        assert (f({key: 'Authorization, Accept'}) ==
                      {'authorization', 'accept'})
diff --git a/Allura/allura/tests/test_multifactor.py b/Allura/allura/tests/test_multifactor.py
index 2c76fc180..cfbef4d5e 100644
--- a/Allura/allura/tests/test_multifactor.py
+++ b/Allura/allura/tests/test_multifactor.py
@@ -54,25 +54,25 @@ class TestGoogleAuthenticatorFile:
 
     def test_parse(self):
         gaf = GoogleAuthenticatorFile.load(self.sample)
-        assert_equal(gaf.key, b'\xf8\x97\xbb/\xfd\xf2%\x01S\xa7\x8dZ\x07\x0c\\\xe4')
-        assert_equal(gaf.options['RATE_LIMIT'], '3 30')
-        assert_equal(gaf.options['DISALLOW_REUSE'], None)
-        assert_equal(gaf.options['TOTP_AUTH'], None)
-        assert_equal(gaf.recovery_codes, [
+        assert gaf.key == b'\xf8\x97\xbb/\xfd\xf2%\x01S\xa7\x8dZ\x07\x0c\\\xe4'
+        assert gaf.options['RATE_LIMIT'] == '3 30'
+        assert gaf.options['DISALLOW_REUSE'] == None
+        assert gaf.options['TOTP_AUTH'] == None
+        assert gaf.recovery_codes == [
             '43504045',
             '16951331',
             '16933944',
             '38009587',
             '49571579',
-        ])
+        ]
 
     def test_dump(self):
         gaf = GoogleAuthenticatorFile.load(self.sample)
-        assert_equal(gaf.dump(), self.sample)
+        assert gaf.dump() == self.sample
 
     def test_dump2(self):
         gaf = GoogleAuthenticatorFile.load(self.sample2)
-        assert_equal(gaf.dump(), self.sample2)
+        assert gaf.dump() == self.sample2
 
 
 class GenericTotpService(TotpService):
@@ -138,21 +138,21 @@ class TestAnyTotpServiceImplementation:
     def test_none(self):
         srv = self.Service()
         user = self.mock_user()
-        assert_equal(None, srv.get_secret_key(user))
+        assert None == srv.get_secret_key(user)
 
     def test_set_get(self):
         srv = self.Service()
         user = self.mock_user()
         srv.set_secret_key(user, self.sample_key)
-        assert_equal(self.sample_key, srv.get_secret_key(user))
+        assert self.sample_key == srv.get_secret_key(user)
 
     def test_delete(self):
         srv = self.Service()
         user = self.mock_user()
         srv.set_secret_key(user, self.sample_key)
-        assert_equal(self.sample_key, srv.get_secret_key(user))
+        assert self.sample_key == srv.get_secret_key(user)
         srv.set_secret_key(user, None)
-        assert_equal(None, srv.get_secret_key(user))
+        assert None == srv.get_secret_key(user)
 
     @patch('allura.lib.multifactor.time')
     def test_rate_limiting(self, time):
@@ -225,8 +225,8 @@ class TestRecoveryCodeService:
 
         recovery.regenerate_codes(user)
 
-        assert_equal(recovery.saved_user, user)
-        assert_equal(len(recovery.saved_codes), asint(config.get('auth.multifactor.recovery_code.count', 10)))
+        assert recovery.saved_user == user
+        assert len(recovery.saved_codes) == asint(config.get('auth.multifactor.recovery_code.count', 10))
 
 
 class TestAnyRecoveryCodeServiceImplementation:
@@ -239,7 +239,7 @@ class TestAnyRecoveryCodeServiceImplementation:
     def test_get_codes_none(self):
         recovery = self.Service()
         user = self.mock_user()
-        assert_equal(recovery.get_codes(user), [])
+        assert recovery.get_codes(user) == []
 
     def test_regen_get_codes(self):
         recovery = self.Service()
@@ -255,7 +255,7 @@ class TestAnyRecoveryCodeServiceImplementation:
             '67890'
         ]
         recovery.replace_codes(user, codes)
-        assert_equal(recovery.get_codes(user), codes)
+        assert recovery.get_codes(user) == codes
 
     def test_verify_fail(self):
         recovery = self.Service()
@@ -274,8 +274,8 @@ class TestAnyRecoveryCodeServiceImplementation:
         ]
         recovery.replace_codes(user, codes)
         result = recovery.verify_and_remove_code(user, '12345')
-        assert_equal(result, True)
-        assert_equal(recovery.get_codes(user), ['67890'])
+        assert result == True
+        assert recovery.get_codes(user) == ['67890']
 
     def test_rate_limiting(self):
         recovery = self.Service()
diff --git a/Allura/allura/tests/test_patches.py b/Allura/allura/tests/test_patches.py
index b86b95f95..0beb9ce96 100644
--- a/Allura/allura/tests/test_patches.py
+++ b/Allura/allura/tests/test_patches.py
@@ -35,7 +35,7 @@ def test_with_trailing_slash():
     patches.apply()
     with assert_raises(webob.exc.HTTPMovedPermanently) as raised:
         tg.decorators.with_trailing_slash(empty_func)()
-    assert_equal(raised.exception.location, 'http://localhost/foo/bar/')
+    assert raised.exception.location == 'http://localhost/foo/bar/'
 
 
 @patch.object(patches, 'request', webob.Request.blank('/foo/bar/?a=b'))
@@ -50,7 +50,7 @@ def test_with_trailing_slash_qs():
     patches.apply()
     with assert_raises(webob.exc.HTTPMovedPermanently) as raised:
         tg.decorators.with_trailing_slash(empty_func)()
-    assert_equal(raised.exception.location, 'http://localhost/foo/bar/?foo=bar&baz=bam')
+    assert raised.exception.location == 'http://localhost/foo/bar/?foo=bar&baz=bam'
 
 
 @patch.object(patches, 'request', webob.Request.blank('/foo/bar/'))
@@ -58,7 +58,7 @@ def test_without_trailing_slash():
     patches.apply()
     with assert_raises(webob.exc.HTTPMovedPermanently) as raised:
         tg.decorators.without_trailing_slash(empty_func)()
-    assert_equal(raised.exception.location, 'http://localhost/foo/bar')
+    assert raised.exception.location == 'http://localhost/foo/bar'
 
 
 @patch.object(patches, 'request', webob.Request.blank('/foo/bar?a=b'))
@@ -73,4 +73,4 @@ def test_without_trailing_slash_qs():
     patches.apply()
     with assert_raises(webob.exc.HTTPMovedPermanently) as raised:
         tg.decorators.without_trailing_slash(empty_func)()
-    assert_equal(raised.exception.location, 'http://localhost/foo/bar?foo=bar&baz=bam')
+    assert raised.exception.location == 'http://localhost/foo/bar?foo=bar&baz=bam'
diff --git a/Allura/allura/tests/test_plugin.py b/Allura/allura/tests/test_plugin.py
index 76133e0b5..4ae9d96fc 100644
--- a/Allura/allura/tests/test_plugin.py
+++ b/Allura/allura/tests/test_plugin.py
@@ -94,48 +94,48 @@ class TestProjectRegistrationProviderParseProjectFromUrl:
         self.parse = self.provider.project_from_url
 
     def test_empty_url(self):
-        assert_equal((None, 'Empty url'), self.parse(None))
-        assert_equal((None, 'Empty url'), self.parse(''))
-        assert_equal((None, 'Empty url'), self.parse('/'))
+        assert (None, 'Empty url') == self.parse(None)
+        assert (None, 'Empty url') == self.parse('')
+        assert (None, 'Empty url') == self.parse('/')
 
     def test_neighborhood_not_found(self):
-        assert_equal((None, 'Neighborhood not found'), self.parse('/nbhd/project'))
+        assert (None, 'Neighborhood not found') == self.parse('/nbhd/project')
 
     def test_project_not_found(self):
-        assert_equal((None, 'Project not found'), self.parse('/p/project'))
-        assert_equal((None, 'Project not found'), self.parse('project'))
+        assert (None, 'Project not found') == self.parse('/p/project')
+        assert (None, 'Project not found') == self.parse('project')
 
     def test_ok_full(self):
         p = M.Project.query.get(shortname='test')
         adobe = M.Project.query.get(shortname='adobe-1')
-        assert_equal((p, None), self.parse('p/test'))
-        assert_equal((p, None), self.parse('/p/test'))
-        assert_equal((p, None), self.parse('/p/test/tickets/1'))
-        assert_equal((p, None), self.parse('http://localhost:8080/p/test/tickets/1'))
-        assert_equal((adobe, None), self.parse('/adobe/adobe-1/'))
+        assert (p, None) == self.parse('p/test')
+        assert (p, None) == self.parse('/p/test')
+        assert (p, None) == self.parse('/p/test/tickets/1')
+        assert (p, None) == self.parse('http://localhost:8080/p/test/tickets/1')
+        assert (adobe, None) == self.parse('/adobe/adobe-1/')
 
     def test_only_shortname_multiple_projects_matched(self):
         adobe_n = M.Neighborhood.query.get(url_prefix='/adobe/')
         M.Project(shortname='test', neighborhood_id=adobe_n._id)
         ThreadLocalORMSession.flush_all()
-        assert_equal((None, 'Too many matches for project: 2'), self.parse('test'))
+        assert (None, 'Too many matches for project: 2') == self.parse('test')
 
     def test_only_shortname_ok(self):
         p = M.Project.query.get(shortname='test')
         adobe = M.Project.query.get(shortname='adobe-1')
-        assert_equal((p, None), self.parse('test'))
-        assert_equal((adobe, None), self.parse('adobe-1'))
+        assert (p, None) == self.parse('test')
+        assert (adobe, None) == self.parse('adobe-1')
 
     def test_subproject(self):
         p = M.Project.query.get(shortname='test/sub1')
-        assert_equal((p, None), self.parse('p/test/sub1'))
-        assert_equal((p, None), self.parse('p/test/sub1/something'))
-        assert_equal((p, None), self.parse('http://localhost:8080/p/test/sub1'))
-        assert_equal((p, None), self.parse('http://localhost:8080/p/test/sub1/something'))
+        assert (p, None) == self.parse('p/test/sub1')
+        assert (p, None) == self.parse('p/test/sub1/something')
+        assert (p, None) == self.parse('http://localhost:8080/p/test/sub1')
+        assert (p, None) == self.parse('http://localhost:8080/p/test/sub1/something')
 
     def test_subproject_not_found(self):
         p = M.Project.query.get(shortname='test')
-        assert_equal((p, None), self.parse('http://localhost:8080/p/test/not-a-sub'))
+        assert (p, None) == self.parse('http://localhost:8080/p/test/not-a-sub')
 
 
 class UserMock:
@@ -168,38 +168,38 @@ class TestProjectRegistrationProviderPhoneVerification:
 
     def test_phone_verified_disabled(self):
         with h.push_config(tg.config, **{'project.verify_phone': 'false'}):
-            assert_true(self.p.phone_verified(self.user, self.nbhd))
+            assert self.p.phone_verified(self.user, self.nbhd)
 
     @patch.object(plugin.security, 'has_access', autospec=True)
     def test_phone_verified_admin(self, has_access):
         has_access.return_value.return_value = True
         with h.push_config(tg.config, **{'project.verify_phone': 'true'}):
-            assert_true(self.p.phone_verified(self.user, self.nbhd))
+            assert self.p.phone_verified(self.user, self.nbhd)
 
     @patch.object(plugin.security, 'has_access', autospec=True)
     def test_phone_verified_project_admin(self, has_access):
         has_access.return_value.return_value = False
         with h.push_config(tg.config, **{'project.verify_phone': 'true'}):
             self.user.set_projects([Mock()])
-            assert_false(self.p.phone_verified(self.user, self.nbhd))
+            assert not self.p.phone_verified(self.user, self.nbhd)
             self.user.set_projects([Mock(neighborhood_id=self.nbhd._id)])
-            assert_true(self.p.phone_verified(self.user, self.nbhd))
+            assert self.p.phone_verified(self.user, self.nbhd)
 
     @patch.object(plugin.security, 'has_access', autospec=True)
     def test_phone_verified(self, has_access):
         has_access.return_value.return_value = False
         with h.push_config(tg.config, **{'project.verify_phone': 'true'}):
-            assert_false(self.p.phone_verified(self.user, self.nbhd))
+            assert not self.p.phone_verified(self.user, self.nbhd)
             self.user.set_tool_data('phone_verification', number_hash='123')
-            assert_true(self.p.phone_verified(self.user, self.nbhd))
+            assert self.p.phone_verified(self.user, self.nbhd)
 
     @patch.object(plugin, 'g')
     def test_verify_phone_disabled(self, g):
         g.phone_service = Mock(spec=phone.PhoneService)
         with h.push_config(tg.config, **{'project.verify_phone': 'false'}):
             result = self.p.verify_phone(self.user, '12345')
-            assert_false(g.phone_service.verify.called)
-            assert_equal(result, {'status': 'ok'})
+            assert not g.phone_service.verify.called
+            assert result == {'status': 'ok'}
 
     @patch.object(plugin, 'g')
     def test_verify_phone(self, g):
@@ -207,7 +207,7 @@ class TestProjectRegistrationProviderPhoneVerification:
         with h.push_config(tg.config, **{'project.verify_phone': 'true'}):
             result = self.p.verify_phone(self.user, '123 45 45')
             g.phone_service.verify.assert_called_once_with('1234545')
-            assert_equal(result, g.phone_service.verify.return_value)
+            assert result == g.phone_service.verify.return_value
 
     @patch.object(plugin, 'g')
     def test_check_phone_verification_disabled(self, g):
@@ -215,8 +215,8 @@ class TestProjectRegistrationProviderPhoneVerification:
         with h.push_config(tg.config, **{'project.verify_phone': 'false'}):
             result = self.p.check_phone_verification(
                 self.user, 'request-id', '1111', 'hash')
-            assert_false(g.phone_service.check.called)
-            assert_equal(result, {'status': 'ok'})
+            assert not g.phone_service.check.called
+            assert result == {'status': 'ok'}
 
     @patch.object(plugin.h, 'auditlog_user', autospec=True)
     @patch.object(plugin, 'g')
@@ -227,9 +227,9 @@ class TestProjectRegistrationProviderPhoneVerification:
                 self.user, 'request-id', '1111', 'hash')
             g.phone_service.check.assert_called_once_with(
                 'request-id', '1111')
-            assert_equal(result, g.phone_service.check.return_value)
-            assert_equal(
-                self.user.get_tool_data('phone_verification', 'number_hash'),
+            assert result == g.phone_service.check.return_value
+            assert (
+                self.user.get_tool_data('phone_verification', 'number_hash') ==
                 None)
             audit.assert_called_once_with(
                 'Phone verification failed. Hash: hash', user=self.user)
@@ -244,8 +244,8 @@ class TestProjectRegistrationProviderPhoneVerification:
                 self.user, 'request-id', '1111', 'hash')
             g.phone_service.check.assert_called_once_with(
                 'request-id', '1111')
-            assert_equal(
-                self.user.get_tool_data('phone_verification', 'number_hash'),
+            assert (
+                self.user.get_tool_data('phone_verification', 'number_hash') ==
                 'hash')
             audit.assert_called_once_with(
                 'Phone verification succeeded. Hash: hash', user=self.user)
@@ -258,8 +258,8 @@ class TestProjectRegistrationProviderPhoneVerification:
         with h.push_config(tg.config, **{'project.verify_phone': 'true', 'phone.attempts_limit': '5'}):
             for i in range(1, 3):
                 result = self.p.verify_phone(user, '123 45 45')
-                assert_equal(result, g.phone_service.verify.return_value)
-            assert_equal(2, g.phone_service.verify.call_count)
+                assert result == g.phone_service.verify.return_value
+            assert 2 == g.phone_service.verify.call_count
 
     @patch.object(plugin, 'g')
     def test_verify_phone_max_limit_reached(self, g):
@@ -270,10 +270,10 @@ class TestProjectRegistrationProviderPhoneVerification:
             for i in range(1, 7):
                 result = self.p.verify_phone(user, '123 45 45')
                 if i > 5:
-                    assert_equal(result, {'status': 'error', 'error': 'Maximum phone verification attempts reached.'})
+                    assert result == {'status': 'error', 'error': 'Maximum phone verification attempts reached.'}
                 else:
-                    assert_equal(result, g.phone_service.verify.return_value)
-            assert_equal(5, g.phone_service.verify.call_count)
+                    assert result == g.phone_service.verify.return_value
+            assert 5 == g.phone_service.verify.call_count
 
 class TestThemeProvider:
 
@@ -285,14 +285,14 @@ class TestThemeProvider:
                 24: 'images/testapp_24.png',
             }
         plugin_g.entry_points = {'tool': {'testapp': TestApp}}
-        assert_equals(ThemeProvider().app_icon_url('testapp', 24),
+        assert (ThemeProvider().app_icon_url('testapp', 24) ==
                       app_g.theme_href.return_value)
         app_g.theme_href.assert_called_with('images/testapp_24.png')
 
     @patch('allura.lib.plugin.g')
     def test_app_icon_str_invalid(self, g):
         g.entry_points = {'tool': {'testapp': Mock()}}
-        assert_equals(ThemeProvider().app_icon_url('invalid', 24),
+        assert (ThemeProvider().app_icon_url('invalid', 24) ==
                       None)
 
     @patch('allura.app.g')
@@ -302,7 +302,7 @@ class TestThemeProvider:
                 24: 'images/testapp_24.png',
             }
         app = TestApp(None, None)
-        assert_equals(ThemeProvider().app_icon_url(app, 24),
+        assert (ThemeProvider().app_icon_url(app, 24) ==
                       g.theme_href.return_value)
         g.theme_href.assert_called_with('images/testapp_24.png')
 
@@ -317,7 +317,7 @@ class TestThemeProvider_notifications:
     @patch('tg.request')
     def test_get_site_notification_no_note(self, request, response, SiteNotification):
         SiteNotification.actives.return_value = []
-        assert_is_none(self.Provider().get_site_notification())
+        assert self.Provider().get_site_notification() is None
         assert not response.set_cookie.called
 
     @patch('allura.lib.plugin.c', MagicMock())
@@ -332,7 +332,7 @@ class TestThemeProvider_notifications:
         note.page_tool_type = None
         SiteNotification.actives.return_value = [note]
         request.cookies = {'site-notification': 'deadbeef-1-true'}
-        assert_is_none(self.Provider().get_site_notification())
+        assert self.Provider().get_site_notification() is None
         assert not response.set_cookie.called
 
     @patch('allura.lib.plugin.c', MagicMock())
@@ -348,7 +348,7 @@ class TestThemeProvider_notifications:
         note.page_tool_type = None
         SiteNotification.actives.return_value = [note]
         request.cookies = {'site-notification': 'deadbeef-3-false'}
-        assert_is_none(self.Provider().get_site_notification())
+        assert self.Provider().get_site_notification() is None
         assert not response.set_cookie.called
 
     @patch('allura.lib.plugin.c', MagicMock())
@@ -366,7 +366,7 @@ class TestThemeProvider_notifications:
         request.cookies = {'site-notification': 'deadbeef-1-false'}
         request.environ['beaker.session'].secure = False
 
-        assert_is(self.Provider().get_site_notification(), note)
+        assert self.Provider().get_site_notification() is note
         response.set_cookie.assert_called_once_with(
             'site-notification', 'deadbeef-2-False', max_age=dt.timedelta(days=365), secure=False)
 
@@ -383,7 +383,7 @@ class TestThemeProvider_notifications:
         note.page_tool_type = None
         SiteNotification.actives.return_value = [note]
         request.cookies = {'site-notification': 'deadbeef-1000-false'}
-        assert_is(self.Provider().get_site_notification(), note)
+        assert self.Provider().get_site_notification() is note
 
     @patch('allura.lib.plugin.c', MagicMock())
     @patch('allura.model.notification.SiteNotification')
@@ -400,7 +400,7 @@ class TestThemeProvider_notifications:
         request.cookies = {'site-notification': '0ddba11-1000-true'}
         request.environ['beaker.session'].secure = False
 
-        assert_is(self.Provider().get_site_notification(), note)
+        assert self.Provider().get_site_notification() is note
         response.set_cookie.assert_called_once_with(
             'site-notification', 'deadbeef-1-False', max_age=dt.timedelta(days=365), secure=False)
 
@@ -418,7 +418,7 @@ class TestThemeProvider_notifications:
         SiteNotification.actives.return_value = [note]
         request.cookies = {}
         request.environ['beaker.session'].secure = False
-        assert_is(self.Provider().get_site_notification(), note)
+        assert self.Provider().get_site_notification() is note
         response.set_cookie.assert_called_once_with(
             'site-notification', 'deadbeef-1-False', max_age=dt.timedelta(days=365), secure=False)
 
@@ -437,7 +437,7 @@ class TestThemeProvider_notifications:
         request.cookies = {'site-notification': 'deadbeef-1000-true-bad'}
         request.environ['beaker.session'].secure = False
 
-        assert_is(self.Provider().get_site_notification(), note)
+        assert self.Provider().get_site_notification() is note
         response.set_cookie.assert_called_once_with(
             'site-notification', 'deadbeef-1-False', max_age=dt.timedelta(days=365), secure=False)
 
@@ -455,21 +455,21 @@ class TestThemeProvider_notifications:
         projects = c.user.my_projects_by_role_name
 
         c.user.is_anonymous.return_value = True
-        assert_is(self.Provider().get_site_notification(), None)
+        assert self.Provider().get_site_notification() is None
 
         c.user.is_anonymous.return_value = False
         projects.return_value = []
-        assert_is(self.Provider().get_site_notification(), None)
+        assert self.Provider().get_site_notification() is None
 
         projects.return_value = [Mock()]
         projects.return_value[0].is_user_project = True
-        assert_is(self.Provider().get_site_notification(), None)
+        assert self.Provider().get_site_notification() is None
 
         projects.return_value[0].is_user_project = False
-        assert_is(self.Provider().get_site_notification(), note)
+        assert self.Provider().get_site_notification() is note
 
         projects.projects.return_value = [Mock(), Mock()]
-        assert_is(self.Provider().get_site_notification(), note)
+        assert self.Provider().get_site_notification() is note
 
     @patch('allura.lib.plugin.c', MagicMock())
     @patch('allura.model.notification.SiteNotification')
@@ -482,7 +482,7 @@ class TestThemeProvider_notifications:
         note.page_tool_type = None
         note.impressions = 10
         SiteNotification.actives.return_value = [note]
-        assert_is(self.Provider().get_site_notification(), note)
+        assert self.Provider().get_site_notification() is note
 
     @patch('allura.lib.plugin.c', MagicMock())
     @patch('re.search')
@@ -498,10 +498,10 @@ class TestThemeProvider_notifications:
         SiteNotification.actives.return_value = [note]
 
         search.return_value = True
-        assert_is(self.Provider().get_site_notification(), note)
+        assert self.Provider().get_site_notification() is note
 
         search.return_value = None
-        assert_is(self.Provider().get_site_notification(), None)
+        assert self.Provider().get_site_notification() is None
 
     @patch('allura.lib.plugin.c')
     @patch('allura.model.notification.SiteNotification')
@@ -516,13 +516,13 @@ class TestThemeProvider_notifications:
         SiteNotification.actives.return_value = [note]
         c.app = Mock()
         c.app.config.tool_name.lower.return_value = 'test1'
-        assert_is(self.Provider().get_site_notification(), note)
+        assert self.Provider().get_site_notification() is note
 
         c.app.config.tool_name.lower.return_value = 'test2'
-        assert_is(self.Provider().get_site_notification(), None)
+        assert self.Provider().get_site_notification() is None
 
         c.app = None
-        assert_is(self.Provider().get_site_notification(), None)
+        assert self.Provider().get_site_notification() is None
 
     @patch('allura.lib.plugin.c')
     @patch('tg.request')
@@ -539,23 +539,23 @@ class TestThemeProvider_notifications:
 
         request.path_qs = 'ttt'
         c.app.config.tool_name.lower.return_value = 'test2'
-        assert_is(self.Provider().get_site_notification(), None)
+        assert self.Provider().get_site_notification() is None
 
         request.path_qs = 'test'
-        assert_is(self.Provider().get_site_notification(), None)
+        assert self.Provider().get_site_notification() is None
 
         request.path_qs = 'ttt'
         c.app.config.tool_name.lower.return_value = 'test1'
-        assert_is(self.Provider().get_site_notification(), None)
+        assert self.Provider().get_site_notification() is None
 
         request.path_qs = 'test'
-        assert_is(self.Provider().get_site_notification(), note)
+        assert self.Provider().get_site_notification() is note
 
         c.app = None
-        assert_is(self.Provider().get_site_notification(), None)
+        assert self.Provider().get_site_notification() is None
 
         request.path_qs = 'ttt'
-        assert_is(self.Provider().get_site_notification(), None)
+        assert self.Provider().get_site_notification() is None
 
     @patch('allura.model.notification.SiteNotification')
     def test_get__site_notification(self, SiteNotification):
@@ -598,8 +598,8 @@ class TestThemeProvider_notifications:
 
         assert isinstance(get_note, tuple)
         assert len(get_note) == 2
-        assert_equal(get_note[0], note2)
-        assert_equal(get_note[1], 'test2-1-False')
+        assert get_note[0] == note2
+        assert get_note[1] == 'test2-1-False'
 
         # and with a cookie set
         get_note = self.Provider()._get_site_notification(
@@ -608,8 +608,8 @@ class TestThemeProvider_notifications:
 
         assert isinstance(get_note, tuple)
         assert len(get_note) == 2
-        assert_equal(get_note[0], note3)
-        assert_equal(get_note[1], 'test2-3-True_test3-1-False')
+        assert get_note[0] == note3
+        assert get_note[1] == 'test2-3-True_test3-1-False'
 
     @patch('allura.model.notification.SiteNotification')
     def test_get_site_notifications_with_api_cookie(self, SiteNotification):
@@ -641,7 +641,7 @@ class TestLocalAuthenticationProvider:
         ep = self.provider._encode_password
         assert ep('test_pass') != ep('test_pass')
         assert ep('test_pass', '0000') == ep('test_pass', '0000')
-        assert_equal(ep('test_pass', '0000'), 'sha2560000j7pRjKKZ5L8G0jScZKja9ECmYF2zBV82Mi+E3wkop30=')
+        assert ep('test_pass', '0000') == 'sha2560000j7pRjKKZ5L8G0jScZKja9ECmYF2zBV82Mi+E3wkop30='
 
     def test_set_password_with_old_password(self):
         user = Mock()
@@ -651,7 +651,7 @@ class TestLocalAuthenticationProvider:
         assert_raises(
             exc.HTTPUnauthorized,
             self.provider.set_password, user, 'old', 'new')
-        assert_equal(self.provider._encode_password.call_count, 0)
+        assert self.provider._encode_password.call_count == 0
 
         self.provider.validate_password = lambda u, p: True
         self.provider.set_password(user, 'old', 'new')
@@ -663,7 +663,7 @@ class TestLocalAuthenticationProvider:
         user.__ming__ = Mock()
         user.last_password_updated = None
         self.provider.set_password(user, None, 'new')
-        assert_equal(user.last_password_updated, dt_mock.utcnow.return_value)
+        assert user.last_password_updated == dt_mock.utcnow.return_value
 
     def test_get_last_password_updated_not_set(self):
         user = Mock()
@@ -673,13 +673,13 @@ class TestLocalAuthenticationProvider:
         upd = self.provider.get_last_password_updated(user)
         gen_time = dt.datetime.utcfromtimestamp(
             calendar.timegm(user._id.generation_time.utctimetuple()))
-        assert_equal(upd, gen_time)
+        assert upd == gen_time
 
     def test_get_last_password_updated(self):
         user = Mock()
         user.last_password_updated = dt.datetime(2014, 6, 4, 13, 13, 13)
         upd = self.provider.get_last_password_updated(user)
-        assert_equal(upd, user.last_password_updated)
+        assert upd == user.last_password_updated
 
     def test_enable_user(self):
         user = Mock(disabled=True, __ming__=Mock(), is_anonymous=lambda: False, _id=ObjectId())
@@ -687,7 +687,7 @@ class TestLocalAuthenticationProvider:
         with audits('Account enabled', user=True, actor='test-admin'):
             self.provider.enable_user(user)
             ThreadLocalORMSession.flush_all()
-        assert_equal(user.disabled, False)
+        assert user.disabled == False
 
     def test_disable_user(self):
         user = Mock(disabled=False, __ming__=Mock(), is_anonymous=lambda: False, _id=ObjectId())
@@ -695,51 +695,51 @@ class TestLocalAuthenticationProvider:
         with audits('Account disabled', user=True, actor='test-admin'):
             self.provider.disable_user(user)
             ThreadLocalORMSession.flush_all()
-        assert_equal(user.disabled, True)
+        assert user.disabled == True
 
     def test_login_details_from_auditlog(self):
         user = M.User(username='asfdasdf')
 
-        assert_equal(self.provider.login_details_from_auditlog(M.AuditLog(message='')),
+        assert (self.provider.login_details_from_auditlog(M.AuditLog(message='')) ==
                      None)
 
         detail = self.provider.login_details_from_auditlog(M.AuditLog(message='IP Address: 1.2.3.4\nFoo', user=user))
-        assert_equal(detail.user_id, user._id)
-        assert_equal(detail.ip, '1.2.3.4')
-        assert_equal(detail.ua, None)
+        assert detail.user_id == user._id
+        assert detail.ip == '1.2.3.4'
+        assert detail.ua == None
 
         detail = self.provider.login_details_from_auditlog(M.AuditLog(message='Foo\nIP Address: 1.2.3.4\nFoo', user=user))
-        assert_equal(detail.ip, '1.2.3.4')
-        assert_equal(detail.ua, None)
+        assert detail.ip == '1.2.3.4'
+        assert detail.ua == None
 
-        assert_equal(self.provider.login_details_from_auditlog(M.AuditLog(
-                        message='blah blah IP Address: 1.2.3.4\nFoo', user=user)),
+        assert (self.provider.login_details_from_auditlog(M.AuditLog(
+                        message='blah blah IP Address: 1.2.3.4\nFoo', user=user)) ==
                      None)
 
         detail = self.provider.login_details_from_auditlog(M.AuditLog(
                         message='User-Agent: Mozilla/Firefox\nFoo', user=user))
-        assert_equal(detail.ip, None)
-        assert_equal(detail.ua, 'Mozilla/Firefox')
+        assert detail.ip == None
+        assert detail.ua == 'Mozilla/Firefox'
 
         detail = self.provider.login_details_from_auditlog(M.AuditLog(
                         message='IP Address: 1.2.3.4\nUser-Agent: Mozilla/Firefox\nFoo', user=user))
-        assert_equal(detail.ip, '1.2.3.4')
-        assert_equal(detail.ua, 'Mozilla/Firefox')
+        assert detail.ip == '1.2.3.4'
+        assert detail.ua == 'Mozilla/Firefox'
 
     def test_get_login_detail(self):
         user = M.User(username='foobarbaz')
         detail = self.provider.get_login_detail(Request.blank('/'), user)
-        assert_equal(detail.user_id, user._id)
-        assert_equal(detail.ip, None)
-        assert_equal(detail.ua, None)
+        assert detail.user_id == user._id
+        assert detail.ip == None
+        assert detail.ua == None
 
         detail = self.provider.get_login_detail(Request.blank('/',
                                                               headers={'User-Agent': 'mybrowser'},
                                                               environ={'REMOTE_ADDR': '3.3.3.3'}),
                                                 user)
-        assert_equal(detail.user_id, user._id)
-        assert_equal(detail.ip, '3.3.3.3')
-        assert_equal(detail.ua, 'mybrowser')
+        assert detail.user_id == user._id
+        assert detail.ip == '3.3.3.3'
+        assert detail.ua == 'mybrowser'
 
 
 class TestAuthenticationProvider:
@@ -752,21 +752,21 @@ class TestAuthenticationProvider:
         self.user = Mock()
 
     def test_is_password_expired_disabled(self):
-        assert_false(self.provider.is_password_expired(self.user))
+        assert not self.provider.is_password_expired(self.user)
 
     def test_is_password_expired_days(self):
         with h.push_config(tg.config, **{'auth.pwdexpire.days': '180'}):
-            assert_false(self.provider.is_password_expired(self.user))
+            assert not self.provider.is_password_expired(self.user)
         with h.push_config(tg.config, **{'auth.pwdexpire.days': '90'}):
-            assert_true(self.provider.is_password_expired(self.user))
+            assert self.provider.is_password_expired(self.user)
 
     def test_is_password_expired_before(self):
         before = dt.datetime.utcnow() - dt.timedelta(days=180)
         before = calendar.timegm(before.timetuple())
         with h.push_config(tg.config, **{'auth.pwdexpire.before': str(before)}):
-            assert_false(self.provider.is_password_expired(self.user))
+            assert not self.provider.is_password_expired(self.user)
 
         before = dt.datetime.utcnow() - dt.timedelta(days=1)
         before = calendar.timegm(before.timetuple())
         with h.push_config(tg.config, **{'auth.pwdexpire.before': str(before)}):
-            assert_true(self.provider.is_password_expired(self.user))
+            assert self.provider.is_password_expired(self.user)
diff --git a/Allura/allura/tests/test_security.py b/Allura/allura/tests/test_security.py
index c94c924ce..54680e331 100644
--- a/Allura/allura/tests/test_security.py
+++ b/Allura/allura/tests/test_security.py
@@ -94,28 +94,28 @@ class TestSecurity(TestController):
         anon_role = M.ProjectRole.by_name('*anonymous')
         test_user = M.User.by_username('test-user')
 
-        assert_equal(all_allowed(wiki, admin_role), {
-            'configure', 'read', 'create', 'edit', 'unmoderated_post', 'post', 'moderate', 'admin', 'delete'})
-        assert_equal(all_allowed(wiki, dev_role), {
-            'read', 'create', 'edit', 'unmoderated_post', 'post', 'moderate', 'delete'})
-        assert_equal(all_allowed(wiki, member_role),
+        assert all_allowed(wiki, admin_role) == {
+            'configure', 'read', 'create', 'edit', 'unmoderated_post', 'post', 'moderate', 'admin', 'delete'}
+        assert all_allowed(wiki, dev_role) == {
+            'read', 'create', 'edit', 'unmoderated_post', 'post', 'moderate', 'delete'}
+        assert (all_allowed(wiki, member_role) ==
                      {'read', 'create', 'edit', 'unmoderated_post', 'post'})
-        assert_equal(all_allowed(wiki, auth_role),
+        assert (all_allowed(wiki, auth_role) ==
                      {'read', 'post', 'unmoderated_post'})
-        assert_equal(all_allowed(wiki, anon_role), {'read'})
-        assert_equal(all_allowed(wiki, test_user),
+        assert all_allowed(wiki, anon_role) == {'read'}
+        assert (all_allowed(wiki, test_user) ==
                      {'read', 'post', 'unmoderated_post'})
 
         _add_to_group(test_user, member_role)
 
-        assert_equal(all_allowed(wiki, test_user),
+        assert (all_allowed(wiki, test_user) ==
                      {'read', 'create', 'edit', 'unmoderated_post', 'post'})
 
         _deny(wiki, auth_role, 'unmoderated_post')
 
-        assert_equal(all_allowed(wiki, member_role),
+        assert (all_allowed(wiki, member_role) ==
                      {'read', 'create', 'edit', 'post'})
-        assert_equal(all_allowed(wiki, test_user),
+        assert (all_allowed(wiki, test_user) ==
                      {'read', 'create', 'edit', 'post'})
 
     @td.with_wiki
@@ -133,12 +133,12 @@ class TestSecurity(TestController):
         assert has_access(page, 'read', anon_role)()
         assert has_access(page, 'post', anon_role)()
         assert has_access(page, 'unmoderated_post', anon_role)()
-        assert_equal(all_allowed(page, anon_role), {'read'})
+        assert all_allowed(page, anon_role) == {'read'}
         # as well as an authenticated user
         assert has_access(page, 'read', test_user)()
         assert has_access(page, 'post', test_user)()
         assert has_access(page, 'unmoderated_post', test_user)()
-        assert_equal(all_allowed(page, test_user),
+        assert (all_allowed(page, test_user) ==
                      {'read', 'post', 'unmoderated_post'})
 
         _deny(page, auth_role, 'read')
@@ -154,7 +154,7 @@ class TestSecurity(TestController):
         assert has_access(wiki, 'read', test_user)()
         assert has_access(wiki, 'post', test_user)()
         assert has_access(wiki, 'unmoderated_post', test_user)()
-        assert_equal(all_allowed(wiki, test_user),
+        assert (all_allowed(wiki, test_user) ==
                      {'read', 'post', 'unmoderated_post'})
 
         _deny(wiki, anon_role, 'read')
@@ -184,7 +184,7 @@ class TestSecurity(TestController):
         page = WM.Page.query.get(app_config_id=wiki.config._id)
         test_user = M.User.by_username('test-user')
 
-        assert_equal(project1.shortname, 'test')
+        assert project1.shortname == 'test'
         assert has_access(page, 'read', test_user)()
         c.project = project2
         assert has_access(page, 'read', test_user)()
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index b1b2215ca..16815e600 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -63,9 +63,9 @@ class TestRepoTasks(unittest.TestCase):
         fake_traceback = 'fake_traceback'
         app.repo.init_as_clone.side_effect = Exception(fake_traceback)
         repo_tasks.clone(None, None, fake_source_url)
-        assert_equal(post_event.call_args[0][0], 'repo_clone_task_failed')
-        assert_equal(post_event.call_args[0][1], fake_source_url)
-        assert_equal(post_event.call_args[0][2], None)
+        assert post_event.call_args[0][0] == 'repo_clone_task_failed'
+        assert post_event.call_args[0][1] == fake_source_url
+        assert post_event.call_args[0][2] == None
         # ignore args[3] which is a traceback string
 
     @mock.patch('allura.tasks.repo_tasks.session', autospec=True)
@@ -76,7 +76,7 @@ class TestRepoTasks(unittest.TestCase):
         MR.query.get.return_value = mr
         repo_tasks.merge(mr._id)
         mr.app.repo.merge.assert_called_once_with(mr)
-        assert_equal(mr.status, 'merged')
+        assert mr.status == 'merged'
         session.assert_called_once_with(mr)
         session.return_value.flush.assert_called_once_with(mr)
 
@@ -146,12 +146,12 @@ class TestEventTasks(unittest.TestCase):
                 mock.patch.dict(tg.config, {'monq.raise_errors': False}):  # match normal non-test behavior
             t()
         # l.check() would be nice, but string is too detailed to check
-        assert_equal(l.records[0].name, 'allura.model.monq_model')
+        assert l.records[0].name == 'allura.model.monq_model'
         msg = l.records[0].getMessage()
-        assert_in("AssertionError('assert 0'", msg)
-        assert_in("AssertionError('assert 5'", msg)
-        assert_in(' on job <MonQTask ', msg)
-        assert_in(' (error) P:10 allura.tests.test_tasks.raise_exc ', msg)
+        assert "AssertionError('assert 0'" in msg
+        assert "AssertionError('assert 5'" in msg
+        assert ' on job <MonQTask ' in msg
+        assert ' (error) P:10 allura.tests.test_tasks.raise_exc ' in msg
         for x in range(10):
             assert ('assert %d' % x) in t.result
 
@@ -208,7 +208,7 @@ class TestIndexTasks(unittest.TestCase):
             M.main_orm_session.clear()
             t3 = _TestArtifact.query.get(_shorthand_id='t3')
             assert len(t3.backrefs) == 5, t3.backrefs
-            assert_equal(find_slinks.call_args_list,
+            assert (find_slinks.call_args_list ==
                          [mock.call(a.index().get('text')) for a in artifacts])
 
     @td.with_wiki
@@ -228,8 +228,8 @@ class TestIndexTasks(unittest.TestCase):
         assert old_shortlinks + 5 == new_shortlinks, 'Shortlinks not created'
         assert solr.add.call_count == 1
         sort_key = operator.itemgetter('id')
-        assert_equal(
-            sorted(solr.add.call_args[0][0], key=sort_key),
+        assert (
+            sorted(solr.add.call_args[0][0], key=sort_key) ==
             sorted((ref.artifact.solarize() for ref in arefs),
                    key=sort_key))
         index_tasks.del_artifacts(ref_ids)
@@ -261,19 +261,19 @@ class TestMailTasks(unittest.TestCase):
                 reply_to=g.noreply,
                 subject='Test subject',
                 message_id=h.gen_message_id())
-            assert_equal(_client.sendmail.call_count, 1)
+            assert _client.sendmail.call_count == 1
             return_path, rcpts, body = _client.sendmail.call_args[0]
             body = body.split('\n')
 
-            assert_equal(rcpts, [c.user.get_pref('email_address')])
-            assert_in('Reply-To: %s' % g.noreply, body)
-            assert_in('From: "Test Admin" <te...@users.localhost>', body)
-            assert_in('Subject: Test subject', body)
+            assert rcpts == [c.user.get_pref('email_address')]
+            assert 'Reply-To: %s' % g.noreply in body
+            assert 'From: "Test Admin" <te...@users.localhost>' in body
+            assert 'Subject: Test subject' in body
             # plain
-            assert_in('This is a test', body)
+            assert 'This is a test' in body
             # html
-            assert_in(
-                '<div class="markdown_content"><p>This is a test</p></div>', body)
+            assert (
+                '<div class="markdown_content"><p>This is a test</p></div>' in body)
 
     def test_send_email_nonascii(self):
         with mock.patch.object(mail_tasks.smtp_client, '_client') as _client:
@@ -284,12 +284,12 @@ class TestMailTasks(unittest.TestCase):
                 reply_to=g.noreply,
                 subject='По оживлённым берегам',
                 message_id=h.gen_message_id())
-            assert_equal(_client.sendmail.call_count, 1)
+            assert _client.sendmail.call_count == 1
             return_path, rcpts, body = _client.sendmail.call_args[0]
             body = body.split('\n')
 
-            assert_equal(rcpts, ['blah@blah.com'])
-            assert_in('Reply-To: %s' % g.noreply, body)
+            assert rcpts == ['blah@blah.com']
+            assert 'Reply-To: %s' % g.noreply in body
 
             # The address portion must not be encoded, only the name portion can be.
             # Also py2 and py3 vary in handling of double-quote separators when the name portion is encoded
@@ -297,11 +297,11 @@ class TestMailTasks(unittest.TestCase):
             quoted_cyrillic_No = '=?utf-8?b?ItCf0L4i?='  # "По"
             assert (f'From: {quoted_cyrillic_No} <fo...@bar.com>' in body or
                     f'From: {unquoted_cyrillic_No} <fo...@bar.com>' in body), body
-            assert_in(
-                'Subject: =?utf-8?b?0J/QviDQvtC20LjQstC70ZHQvdC90YvQvCDQsdC10YDQtdCz0LDQvA==?=', body)
-            assert_in('Content-Type: text/plain; charset="utf-8"', body)
-            assert_in('Content-Transfer-Encoding: base64', body)
-            assert_in(six.ensure_text(b64encode('Громады стройные теснятся'.encode())), body)
+            assert (
+                'Subject: =?utf-8?b?0J/QviDQvtC20LjQstC70ZHQvdC90YvQvCDQsdC10YDQtdCz0LDQvA==?=' in body)
+            assert 'Content-Type: text/plain; charset="utf-8"' in body
+            assert 'Content-Transfer-Encoding: base64' in body
+            assert six.ensure_text(b64encode('Громады стройные теснятся'.encode())) in body
 
     def test_send_email_with_disabled_user(self):
         c.user = M.User.by_username('test-admin')
@@ -317,10 +317,10 @@ class TestMailTasks(unittest.TestCase):
                 reply_to=g.noreply,
                 subject='Test subject',
                 message_id=h.gen_message_id())
-            assert_equal(_client.sendmail.call_count, 1)
+            assert _client.sendmail.call_count == 1
             return_path, rcpts, body = _client.sendmail.call_args[0]
             body = body.split('\n')
-            assert_in('From: %s' % g.noreply, body)
+            assert 'From: %s' % g.noreply in body
 
     def test_send_email_with_disabled_destination_user(self):
         c.user = M.User.by_username('test-admin')
@@ -336,7 +336,7 @@ class TestMailTasks(unittest.TestCase):
                 reply_to=g.noreply,
                 subject='Test subject',
                 message_id=h.gen_message_id())
-            assert_equal(_client.sendmail.call_count, 0)
+            assert _client.sendmail.call_count == 0
 
     def test_sendsimplemail_with_disabled_user(self):
         c.user = M.User.by_username('test-admin')
@@ -348,10 +348,10 @@ class TestMailTasks(unittest.TestCase):
                 reply_to=g.noreply,
                 subject='Test subject',
                 message_id=h.gen_message_id())
-            assert_equal(_client.sendmail.call_count, 1)
+            assert _client.sendmail.call_count == 1
             return_path, rcpts, body = _client.sendmail.call_args[0]
             body = body.split('\n')
-            assert_in('From: "Test Admin" <te...@users.localhost>', body)
+            assert 'From: "Test Admin" <te...@users.localhost>' in body
 
             c.user.disabled = True
             ThreadLocalORMSession.flush_all()
@@ -362,10 +362,10 @@ class TestMailTasks(unittest.TestCase):
                 reply_to=g.noreply,
                 subject='Test subject',
                 message_id=h.gen_message_id())
-            assert_equal(_client.sendmail.call_count, 2)
+            assert _client.sendmail.call_count == 2
             return_path, rcpts, body = _client.sendmail.call_args[0]
             body = body.split('\n')
-            assert_in('From: %s' % g.noreply, body)
+            assert 'From: %s' % g.noreply in body
 
     def test_email_sender_to_headers(self):
         c.user = M.User.by_username('test-admin')
@@ -378,12 +378,12 @@ class TestMailTasks(unittest.TestCase):
                 subject='Test subject',
                 sender='tickets@test.p.domain.net',
                 message_id=h.gen_message_id())
-            assert_equal(_client.sendmail.call_count, 1)
+            assert _client.sendmail.call_count == 1
             return_path, rcpts, body = _client.sendmail.call_args[0]
             body = body.split('\n')
-            assert_in('From: "Test Admin" <te...@users.localhost>', body)
-            assert_in('Sender: tickets@test.p.domain.net', body)
-            assert_in('To: test@mail.com', body)
+            assert 'From: "Test Admin" <te...@users.localhost>' in body
+            assert 'Sender: tickets@test.p.domain.net' in body
+            assert 'To: test@mail.com' in body
 
             _client.reset_mock()
             mail_tasks.sendmail(
@@ -394,12 +394,12 @@ class TestMailTasks(unittest.TestCase):
                 subject='Test subject',
                 sender='tickets@test.p.domain.net',
                 message_id=h.gen_message_id())
-            assert_equal(_client.sendmail.call_count, 1)
+            assert _client.sendmail.call_count == 1
             return_path, rcpts, body = _client.sendmail.call_args[0]
             body = body.split('\n')
-            assert_in('From: "Test Admin" <te...@users.localhost>', body)
-            assert_in('Sender: tickets@test.p.domain.net', body)
-            assert_in('To: 123@tickets.test.p.domain.net', body)
+            assert 'From: "Test Admin" <te...@users.localhost>' in body
+            assert 'Sender: tickets@test.p.domain.net' in body
+            assert 'To: 123@tickets.test.p.domain.net' in body
 
     def test_email_references_header(self):
         c.user = M.User.by_username('test-admin')
@@ -412,11 +412,11 @@ class TestMailTasks(unittest.TestCase):
                 subject='Test subject',
                 references=['a', 'b', 'c'],
                 message_id=h.gen_message_id())
-            assert_equal(_client.sendmail.call_count, 1)
+            assert _client.sendmail.call_count == 1
             return_path, rcpts, body = _client.sendmail.call_args[0]
             body = body.split('\n')
-            assert_in('From: "Test Admin" <te...@users.localhost>', body)
-            assert_in('References: <a> <b> <c>', body)
+            assert 'From: "Test Admin" <te...@users.localhost>' in body
+            assert 'References: <a> <b> <c>' in body
 
             _client.reset_mock()
             mail_tasks.sendmail(
@@ -427,11 +427,11 @@ class TestMailTasks(unittest.TestCase):
                 subject='Test subject',
                 references='ref',
                 message_id=h.gen_message_id())
-            assert_equal(_client.sendmail.call_count, 1)
+            assert _client.sendmail.call_count == 1
             return_path, rcpts, body = _client.sendmail.call_args[0]
             body = body.split('\n')
-            assert_in('From: "Test Admin" <te...@users.localhost>', body)
-            assert_in('References: <ref>', body)
+            assert 'From: "Test Admin" <te...@users.localhost>' in body
+            assert 'References: <ref>' in body
 
     def test_cc(self):
         c.user = M.User.by_username('test-admin')
@@ -444,10 +444,10 @@ class TestMailTasks(unittest.TestCase):
                 subject='Test subject',
                 cc='someone@example.com',
                 message_id=h.gen_message_id())
-            assert_equal(_client.sendmail.call_count, 1)
+            assert _client.sendmail.call_count == 1
             return_path, rcpts, body = _client.sendmail.call_args[0]
-            assert_in('CC: someone@example.com', body)
-            assert_in('someone@example.com', rcpts)
+            assert 'CC: someone@example.com' in body
+            assert 'someone@example.com' in rcpts
 
     def test_fromaddr_objectid_not_str(self):
         c.user = M.User.by_username('test-admin')
@@ -459,9 +459,9 @@ class TestMailTasks(unittest.TestCase):
                 reply_to=g.noreply,
                 subject='Test subject',
                 message_id=h.gen_message_id())
-            assert_equal(_client.sendmail.call_count, 1)
+            assert _client.sendmail.call_count == 1
             return_path, rcpts, body = _client.sendmail.call_args[0]
-            assert_in('From: "Test Admin" <te...@users.localhost>', body)
+            assert 'From: "Test Admin" <te...@users.localhost>' in body
 
     def test_send_email_long_lines_use_quoted_printable(self):
         with mock.patch.object(mail_tasks.smtp_client, '_client') as _client:
@@ -477,14 +477,14 @@ class TestMailTasks(unittest.TestCase):
             body = body.split('\n')
 
             for line in body:
-                assert_less_equal(len(line), MAX_MAIL_LINE_OCTETS)
+                assert len(line) <= MAX_MAIL_LINE_OCTETS
 
             # plain text
-            assert_in('012345678901234567890123456789012345678901234567890123456789012345678901234=', body)
-            assert_in('=D0=93=D1=80=D0=BE=D0=BC=D0=B0=D0=B4=D1=8B =D1=81=D1=82=D1=80=D0=BE =D0=93=', body)
+            assert '012345678901234567890123456789012345678901234567890123456789012345678901234=' in body
+            assert '=D0=93=D1=80=D0=BE=D0=BC=D0=B0=D0=B4=D1=8B =D1=81=D1=82=D1=80=D0=BE =D0=93=' in body
             # html
-            assert_in('<div class=3D"markdown_content"><p>0123456789012345678901234567890123456789=', body)
-            assert_in('<p>=D0=93=D1=80=D0=BE=D0=BC=D0=B0=D0=B4=D1=8B =D1=81=D1=82=D1=80=D0=BE =D0=', body)
+            assert '<div class=3D"markdown_content"><p>0123456789012345678901234567890123456789=' in body
+            assert '<p>=D0=93=D1=80=D0=BE=D0=BC=D0=B0=D0=B4=D1=8B =D1=81=D1=82=D1=80=D0=BE =D0=' in body
 
     @td.with_wiki
     def test_receive_email_ok(self):
@@ -519,7 +519,7 @@ I'm not here'''
                 c.user.email_addresses[0],
                 ['1@bugs.test.p.in.localhost'],
                 message)
-            assert_equal(hm.call_count, 0)
+            assert hm.call_count == 0
 
     @td.with_tool('test', 'Tickets', 'bugs')
     def test_email_posting_disabled(self):
@@ -533,7 +533,7 @@ I'm not here'''
                 c.user.email_addresses[0],
                 ['1@bugs.test.p.in.localhost'],
                 message)
-            assert_equal(hm.call_count, 0)
+            assert hm.call_count == 0
 
 
 class TestUserNotificationTasks(TestController):
@@ -556,15 +556,15 @@ class TestUserNotificationTasks(TestController):
         # check email notification
         tasks = M.MonQTask.query.find(
             dict(task_name='allura.tasks.mail_tasks.sendsimplemail')).all()
-        assert_equal(len(tasks), 1)
-        assert_equal(tasks[0].kwargs['subject'],
+        assert len(tasks) == 1
+        assert (tasks[0].kwargs['subject'] ==
                      '[test:wiki] Your name was mentioned')
-        assert_equal(tasks[0].kwargs['toaddr'], 'test-user-1@allura.local')
-        assert_equal(tasks[0].kwargs['reply_to'], g.noreply)
+        assert tasks[0].kwargs['toaddr'] == 'test-user-1@allura.local'
+        assert tasks[0].kwargs['reply_to'] == g.noreply
         text = tasks[0].kwargs['text']
-        assert_in('Your name was mentioned at [foo]', text)
-        assert_in('by Test Admin', text)
-        assert_in('auth/subscriptions#notifications', text)
+        assert 'Your name was mentioned at [foo]' in text
+        assert 'by Test Admin' in text
+        assert 'auth/subscriptions#notifications' in text
 
 
 class TestNotificationTasks(unittest.TestCase):
@@ -651,25 +651,25 @@ class TestExportTasks(unittest.TestCase):
         # check notification
         tasks = M.MonQTask.query.find(
             dict(task_name='allura.tasks.mail_tasks.sendsimplemail')).all()
-        assert_equal(len(tasks), 1)
-        assert_equal(tasks[0].kwargs['subject'],
+        assert len(tasks) == 1
+        assert (tasks[0].kwargs['subject'] ==
                      'Bulk export for project test completed')
-        assert_equal(tasks[0].kwargs['fromaddr'], '"Allura" <no...@localhost>')
-        assert_equal(tasks[0].kwargs['reply_to'], g.noreply)
+        assert tasks[0].kwargs['fromaddr'] == '"Allura" <no...@localhost>'
+        assert tasks[0].kwargs['reply_to'] == g.noreply
         text = tasks[0].kwargs['text']
-        assert_in('The bulk export for project test is completed.', text)
-        assert_in('The following tools were exported:\n- wiki', text)
-        assert_in('Sample instructions for test', text)
+        assert 'The bulk export for project test is completed.' in text
+        assert 'The following tools were exported:\n- wiki' in text
+        assert 'Sample instructions for test' in text
 
     def test_bulk_export_status(self):
-        assert_equal(c.project.bulk_export_status(), None)
+        assert c.project.bulk_export_status() == None
         export_tasks.bulk_export.post(['wiki'])
-        assert_equal(c.project.bulk_export_status(), 'busy')
+        assert c.project.bulk_export_status() == 'busy'
 
 
 class TestAdminTasks(unittest.TestCase):
 
     def test_install_app_docstring(self):
-        assert_in('ep_name, mount_point=None', admin_tasks.install_app.__doc__)
+        assert 'ep_name, mount_point=None' in admin_tasks.install_app.__doc__
 
 Mapper.compile_all()
diff --git a/Allura/allura/tests/test_utils.py b/Allura/allura/tests/test_utils.py
index 8a1231479..e82c529e3 100644
--- a/Allura/allura/tests/test_utils.py
+++ b/Allura/allura/tests/test_utils.py
@@ -93,9 +93,9 @@ class TestChunkedIterator(unittest.TestCase):
         assert len(chunks) == 2, chunks
         assert len(chunks[0]) == 2, chunks[0]
         assert len(chunks[1]) == 1, chunks[1]
-        assert_equal(chunks[0][0].username, 'sample-user-1')
-        assert_equal(chunks[0][1].username, 'sample-user-2')
-        assert_equal(chunks[1][0].username, 'sample-user-3')
+        assert chunks[0][0].username == 'sample-user-1'
+        assert chunks[0][1].username == 'sample-user-2'
+        assert chunks[1][0].username == 'sample-user-3'
 
 
 class TestChunkedList(unittest.TestCase):
@@ -228,10 +228,10 @@ class TestLineAnchorCodeHtmlFormatter(unittest.TestCase):
         assert '<div id="l1" class="code_block">' in hl_code
         try:
             # older pygments
-            assert_in('<span class="lineno">1 </span>', hl_code)
+            assert '<span class="lineno">1 </span>' in hl_code
         except AssertionError:
             # newer pygments
-            assert_in('<span class="linenos">1</span>', hl_code)
+            assert '<span class="linenos">1</span>' in hl_code
 
 
 class TestIsTextFile(unittest.TestCase):
@@ -285,40 +285,40 @@ class TestHTMLSanitizer(unittest.TestCase):
     def test_html_sanitizer_iframe(self):
         walker = self.walker_from_text('<div><iframe></iframe></div>')
         p = utils.ForgeHTMLSanitizerFilter(walker)
-        assert_equal(self.simple_tag_list(p), ['div', 'div'])
+        assert self.simple_tag_list(p) == ['div', 'div']
 
     def test_html_sanitizer_youtube_iframe(self):
         walker = self.walker_from_text(
             '<div><iframe src="https://www.youtube.com/embed/kOLpSPEA72U?feature=oembed"></iframe></div>')
         p = utils.ForgeHTMLSanitizerFilter(walker)
-        assert_equal(self.simple_tag_list(p), ['div', 'iframe', 'iframe', 'div'])
+        assert self.simple_tag_list(p) == ['div', 'iframe', 'iframe', 'div']
 
         walker = self.walker_from_text(
             '<div><iframe src="https://www.youtube-nocookie.com/embed/kOLpSPEA72U?feature=oembed"></iframe></div>')
         p = utils.ForgeHTMLSanitizerFilter(walker)
-        assert_equal(self.simple_tag_list(p), ['div', 'iframe', 'iframe', 'div'])
+        assert self.simple_tag_list(p) == ['div', 'iframe', 'iframe', 'div']
 
     def test_html_sanitizer_form_elements(self):
         walker = self.walker_from_text('<p>test</p><form method="post" action="http://localhost/foo.php"><input type=file><input type=text><textarea>asdf</textarea></form>')
         p = utils.ForgeHTMLSanitizerFilter(walker)
-        assert_equal(self.simple_tag_list(p), ['p', 'p'])
+        assert self.simple_tag_list(p) == ['p', 'p']
 
     def test_html_sanitizer_checkbox(self):
         walker = self.walker_from_text('<p><input type="checkbox" disabled/><input type="text" disabled/><input type="checkbox" disabled checked/></p>')
         p = utils.ForgeHTMLSanitizerFilter(walker)
-        assert_equal(self.simple_tag_list(p), ['p', 'input', 'input', 'p'])
+        assert self.simple_tag_list(p) == ['p', 'input', 'input', 'p']
 
     def test_html_sanitizer_summary(self):
         walker = self.walker_from_text('<details open="open"><summary>An Summary</summary><ul><li>Bullet Item</li></ul></details>')
         p = utils.ForgeHTMLSanitizerFilter(walker)
-        assert_equal(self.simple_tag_list(p), ['details', 'summary', 'summary', 'ul', 'li', 'li', 'ul', 'details'])
+        assert self.simple_tag_list(p) == ['details', 'summary', 'summary', 'ul', 'li', 'li', 'ul', 'details']
 
 
 def test_ip_address():
     req = Mock()
     req.remote_addr = '1.2.3.4'
     req.headers = {}
-    assert_equal(utils.ip_address(req),
+    assert (utils.ip_address(req) ==
                  '1.2.3.4')
 
 
@@ -327,7 +327,7 @@ def test_ip_address_header():
     req.remote_addr = '1.2.3.4'
     req.headers = {'X_FORWARDED_FOR': '5.6.7.8'}
     with h.push_config(config, **{'ip_address_header': 'X_FORWARDED_FOR'}):
-        assert_equal(utils.ip_address(req),
+        assert (utils.ip_address(req) ==
                      '5.6.7.8')
 
 
@@ -336,22 +336,22 @@ def test_ip_address_header_not_set():
     req.remote_addr = '1.2.3.4'
     req.headers = {}
     with h.push_config(config, **{'ip_address_header': 'X_FORWARDED_FOR'}):
-        assert_equal(utils.ip_address(req),
+        assert (utils.ip_address(req) ==
                      '1.2.3.4')
 
 
 def test_empty_cursor():
     """EmptyCursors conforms to specification of Ming's ODMCursor"""
     cursor = utils.EmptyCursor()
-    assert_equal(cursor.count(), 0)
-    assert_equal(cursor.first(), None)
-    assert_equal(cursor.all(), [])
-    assert_equal(cursor.limit(10), cursor)
-    assert_equal(cursor.skip(10), cursor)
-    assert_equal(cursor.sort('name', 1), cursor)
-    assert_equal(cursor.hint('index'), cursor)
-    assert_equal(cursor.extensions, [])
-    assert_equal(cursor.options(arg1='val1', arg2='val2'), cursor)
+    assert cursor.count() == 0
+    assert cursor.first() == None
+    assert cursor.all() == []
+    assert cursor.limit(10) == cursor
+    assert cursor.skip(10) == cursor
+    assert cursor.sort('name', 1) == cursor
+    assert cursor.hint('index') == cursor
+    assert cursor.extensions == []
+    assert cursor.options(arg1='val1', arg2='val2') == cursor
     assert_raises(ValueError, cursor.one)
     assert_raises(StopIteration, cursor.next)
     assert_raises(StopIteration, cursor._next_impl)
@@ -367,16 +367,16 @@ def test_DateJSONEncoder():
 
 def test_clean_phone_number():
     clean = utils.clean_phone_number
-    assert_equal(clean('123456789'), '123456789')
-    assert_equal(clean('+123 456:789'), '123456789')
-    assert_equal(clean('555-555-5555'), '5555555555')
-    assert_equal(clean('1-555-555-5555'), '15555555555')
+    assert clean('123456789') == '123456789'
+    assert clean('+123 456:789') == '123456789'
+    assert clean('555-555-5555') == '5555555555'
+    assert clean('1-555-555-5555') == '15555555555'
 
 
 def test_phone_number_hash():
     hash = utils.phone_number_hash
-    assert_equal(hash('1234567890'), hash('+123 456:7890'))
-    assert_not_equal(hash('1234567890'), hash('1234567891'))
+    assert hash('1234567890') == hash('+123 456:7890')
+    assert hash('1234567890') != hash('1234567891')
 
 
 def test_skip_mod_date():
@@ -395,8 +395,8 @@ class FakeAttachment:
 
 def unique_attachments():
     ua = utils.unique_attachments
-    assert_equal([], ua(None))
-    assert_equal([], ua([]))
+    assert [] == ua(None)
+    assert [] == ua([])
 
     pic1 = FakeAttachment('pic.png')
     pic2 = FakeAttachment('pic.png')
@@ -405,7 +405,7 @@ def unique_attachments():
     other = FakeAttachment('other')
     attachments = [pic1, file1, pic2, file2, pic2, other]
     expected = [file2, other, pic2]
-    assert_equal(expected, ua(attachments))
+    assert expected == ua(attachments)
 
 
 def test_is_nofollow_url():
@@ -432,8 +432,8 @@ def test_close_ipv4_addrs():
 
 def test_urlencode():
     # dict - a simple one so arbitrary ordering doesn't cause problems on py2
-    assert_equal(utils.urlencode({'a': 'hello'}),
+    assert (utils.urlencode({'a': 'hello'}) ==
                  'a=hello')
     # list of pairs - including unicode and bytes
-    assert_equal(utils.urlencode([('a', 1), ('b', 'ƒ'), ('c', 'ƒ'.encode())]),
+    assert (utils.urlencode([('a', 1), ('b', 'ƒ'), ('c', 'ƒ'.encode())]) ==
                  'a=1&b=%C6%92&c=%C6%92')
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index da3035ff2..84b758013 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -99,10 +99,10 @@ class TestValidators(TestWebhookBase):
         v = WebhookValidator(sender=sender, app=app, not_empty=True)
         with assert_raises(Invalid) as cm:
             v.to_python(None)
-        assert_equal(cm.exception.msg, 'Please enter a value')
+        assert cm.exception.msg == 'Please enter a value'
         with assert_raises(Invalid) as cm:
             v.to_python('invalid id')
-        assert_equal(cm.exception.msg, 'Invalid webhook')
+        assert cm.exception.msg == 'Invalid webhook'
 
         wh = M.Webhook(type='invalid type',
                        app_config_id=invalid_app.config._id,
@@ -112,19 +112,19 @@ class TestValidators(TestWebhookBase):
         # invalid type
         with assert_raises(Invalid) as cm:
             v.to_python(wh._id)
-        assert_equal(cm.exception.msg, 'Invalid webhook')
+        assert cm.exception.msg == 'Invalid webhook'
 
         wh.type = 'repo-push'
         session(wh).flush(wh)
         # invalild app
         with assert_raises(Invalid) as cm:
             v.to_python(wh._id)
-        assert_equal(cm.exception.msg, 'Invalid webhook')
+        assert cm.exception.msg == 'Invalid webhook'
 
         wh.app_config_id = app.config._id
         session(wh).flush(wh)
-        assert_equal(v.to_python(wh._id), wh)
-        assert_equal(v.to_python(str(wh._id)), wh)
+        assert v.to_python(wh._id) == wh
+        assert v.to_python(str(wh._id)) == wh
 
 
 class TestWebhookController(TestController):
@@ -162,8 +162,8 @@ class TestWebhookController(TestController):
         url = url or self.url
         r = self.app.post(url + '/repo-push/create', data)
         wf = json.loads(self.webflash(r))
-        assert_equal(wf['status'], 'ok')
-        assert_equal(wf['message'], 'Created successfully')
+        assert wf['status'] == 'ok'
+        assert wf['message'] == 'Created successfully'
         return r
 
     def find_error(self, r, field, msg, form_type='create'):
@@ -174,7 +174,7 @@ class TestWebhookController(TestController):
             error = form.find('input', attrs={'name': field})
             error = error.findNext('div', attrs={'class': 'error'})
         if error:
-            assert_in(msg, error.getText())
+            assert msg in error.getText()
         else:
             assert False, 'Validation error not found'
 
@@ -186,7 +186,7 @@ class TestWebhookController(TestController):
         r = self.app.get(self.url + '/repo-push/',
                          extra_environ={'username': '*anonymous'},
                          status=302)
-        assert_equal(r.location,
+        assert (r.location ==
                      'http://localhost/auth/'
                      '?return_to=%2Fadobe%2Fadobe-1%2Fadmin%2Fsrc%2Fwebhooks%2Frepo-push%2F')
 
@@ -194,52 +194,52 @@ class TestWebhookController(TestController):
         self.app.get(self.url + '/invalid-hook-type/', status=404)
 
     def test_create(self):
-        assert_equal(M.Webhook.query.find().count(), 0)
+        assert M.Webhook.query.find().count() == 0
         r = self.app.get(self.url)
-        assert_in('<h1>repo-push</h1>', r)
-        assert_not_in('http://httpbin.org/post', r)
+        assert '<h1>repo-push</h1>' in r
+        assert 'http://httpbin.org/post' not in r
         data = {'url': 'http://httpbin.org/post',
                 'secret': ''}
         msg = 'add webhook repo-push {} {}'.format(
             data['url'], self.git.config.url())
         with td.audits(msg):
             r = self.create_webhook(data).follow()
-        assert_in('http://httpbin.org/post', r)
+        assert 'http://httpbin.org/post' in r
 
         hooks = M.Webhook.query.find().all()
-        assert_equal(len(hooks), 1)
-        assert_equal(hooks[0].type, 'repo-push')
-        assert_equal(hooks[0].hook_url, 'http://httpbin.org/post')
-        assert_equal(hooks[0].app_config_id, self.git.config._id)
-        assert_equal(hooks[0].secret, 'super-secret')
+        assert len(hooks) == 1
+        assert hooks[0].type == 'repo-push'
+        assert hooks[0].hook_url == 'http://httpbin.org/post'
+        assert hooks[0].app_config_id == self.git.config._id
+        assert hooks[0].secret == 'super-secret'
 
         # Try to create duplicate
         with td.out_audits(msg):
             r = self.app.post(self.url + '/repo-push/create', data)
         self.find_error(r, '_the_form',
                         '"repo-push" webhook already exists for Git http://httpbin.org/post')
-        assert_equal(M.Webhook.query.find().count(), 1)
+        assert M.Webhook.query.find().count() == 1
 
     def test_create_limit_reached(self):
-        assert_equal(M.Webhook.query.find().count(), 0)
+        assert M.Webhook.query.find().count() == 0
         limit = json.dumps({'git': 1})
         with h.push_config(config, **{'webhook.repo_push.max_hooks': limit}):
             data = {'url': 'http://httpbin.org/post',
                     'secret': ''}
             r = self.create_webhook(data).follow()
-            assert_equal(M.Webhook.query.find().count(), 1)
+            assert M.Webhook.query.find().count() == 1
 
             r = self.app.post(self.url + '/repo-push/create', data)
             wf = json.loads(self.webflash(r))
-            assert_equal(wf['status'], 'error')
-            assert_equal(
-                wf['message'],
+            assert wf['status'] == 'error'
+            assert (
+                wf['message'] ==
                 'You have exceeded the maximum number of webhooks '
                 'you are allowed to create for this project/app')
-            assert_equal(M.Webhook.query.find().count(), 1)
+            assert M.Webhook.query.find().count() == 1
 
     def test_create_validation(self):
-        assert_equal(M.Webhook.query.find().count(), 0)
+        assert M.Webhook.query.find().count() == 0
         r = self.app.post(
             self.url + '/repo-push/create', {}, status=404)
 
@@ -270,13 +270,13 @@ class TestWebhookController(TestController):
                  'secret': 'secret2'}
         self.create_webhook(data1).follow()
         self.create_webhook(data2).follow()
-        assert_equal(M.Webhook.query.find().count(), 2)
+        assert M.Webhook.query.find().count() == 2
         wh1 = M.Webhook.query.get(hook_url=data1['url'])
         r = self.app.get(self.url + '/repo-push/%s' % wh1._id)
         form = r.forms[0]
-        assert_equal(form['url'].value, data1['url'])
-        assert_equal(form['secret'].value, data1['secret'])
-        assert_equal(form['webhook'].value, str(wh1._id))
+        assert form['url'].value == data1['url']
+        assert form['secret'].value == data1['secret']
+        assert form['webhook'].value == str(wh1._id)
         form['url'] = 'http://host.org/hook'
         form['secret'] = 'new secret'
         msg = 'edit webhook repo-push\n{} => {}\n{}'.format(
@@ -284,14 +284,14 @@ class TestWebhookController(TestController):
         with td.audits(msg):
             r = form.submit()
         wf = json.loads(self.webflash(r))
-        assert_equal(wf['status'], 'ok')
-        assert_equal(wf['message'], 'Edited successfully')
-        assert_equal(M.Webhook.query.find().count(), 2)
+        assert wf['status'] == 'ok'
+        assert wf['message'] == 'Edited successfully'
+        assert M.Webhook.query.find().count() == 2
         wh1 = M.Webhook.query.get(_id=wh1._id)
-        assert_equal(wh1.hook_url, 'http://host.org/hook')
-        assert_equal(wh1.app_config_id, self.git.config._id)
-        assert_equal(wh1.secret, 'new secret')
-        assert_equal(wh1.type, 'repo-push')
+        assert wh1.hook_url == 'http://host.org/hook'
+        assert wh1.app_config_id == self.git.config._id
+        assert wh1.secret == 'new secret'
+        assert wh1.type == 'repo-push'
 
         # Duplicates
         r = self.app.get(self.url + '/repo-push/%s' % wh1._id)
@@ -336,15 +336,15 @@ class TestWebhookController(TestController):
         data = {'url': 'http://httpbin.org/post',
                 'secret': 'secret'}
         self.create_webhook(data).follow()
-        assert_equal(M.Webhook.query.find().count(), 1)
+        assert M.Webhook.query.find().count() == 1
         wh = M.Webhook.query.get(hook_url=data['url'])
         data = {'webhook': str(wh._id)}
         msg = 'delete webhook repo-push {} {}'.format(
             wh.hook_url, self.git.config.url())
         with td.audits(msg):
             r = self.app.post(self.url + '/repo-push/delete', data)
-        assert_equal(r.json, {'status': 'ok'})
-        assert_equal(M.Webhook.query.find().count(), 0)
+        assert r.json == {'status': 'ok'}
+        assert M.Webhook.query.find().count() == 0
 
     def test_delete_validation(self):
         invalid = M.Webhook(
@@ -353,14 +353,14 @@ class TestWebhookController(TestController):
             hook_url='http://httpbin.org/post',
             secret='secret')
         session(invalid).flush(invalid)
-        assert_equal(M.Webhook.query.find().count(), 1)
+        assert M.Webhook.query.find().count() == 1
 
         data = {'webhook': ''}
         self.app.post(self.url + '/repo-push/delete', data, status=404)
 
         data = {'webhook': str(invalid._id)}
         self.app.post(self.url + '/repo-push/delete', data, status=404)
-        assert_equal(M.Webhook.query.find().count(), 1)
+        assert M.Webhook.query.find().count() == 1
 
     @with_git2
     def test_list_webhooks(self):
@@ -379,9 +379,9 @@ class TestWebhookController(TestController):
         wh2 = M.Webhook.query.get(hook_url=data2['url'])
 
         r = self.app.get(self.url)
-        assert_in('<h1>repo-push</h1>', r)
+        assert '<h1>repo-push</h1>' in r
         rows = r.html.find('table').findAll('tr')
-        assert_equal(len(rows), 2)
+        assert len(rows) == 2
         rows = sorted((self._format_row(row) for row in rows), key=lambda rows: rows[0]['text'])
         expected_rows = sorted([
             [{'text': wh1.hook_url},
@@ -397,10 +397,10 @@ class TestWebhookController(TestController):
              {'href': self.url + '/repo-push/delete',
               'data-id': str(wh2._id)}],
         ], key=lambda rows: rows[0]['text'])
-        assert_equal(rows, expected_rows)
+        assert rows == expected_rows
         # make sure webhooks for another app is not visible
-        assert_not_in('http://another-app.org/', r)
-        assert_not_in('secret3', r)
+        assert 'http://another-app.org/' not in r
+        assert 'secret3' not in r
 
     def _format_row(self, row):
         def link(td):
@@ -425,14 +425,14 @@ class TestSendWebhookHelper(TestWebhookBase):
         self.h = SendWebhookHelper(self.wh, self.payload)
 
     def test_timeout(self):
-        assert_equal(self.h.timeout, 30)
+        assert self.h.timeout == 30
         with h.push_config(config, **{'webhook.timeout': 10}):
-            assert_equal(self.h.timeout, 10)
+            assert self.h.timeout == 10
 
     def test_retries(self):
-        assert_equal(self.h.retries, [60, 120, 240])
+        assert self.h.retries == [60, 120, 240]
         with h.push_config(config, **{'webhook.retry': '1 2 3 4 5 6'}):
-            assert_equal(self.h.retries, [1, 2, 3, 4, 5, 6])
+            assert self.h.retries == [1, 2, 3, 4, 5, 6]
 
     def test_sign(self):
         json_payload = json.dumps(self.payload)
@@ -441,18 +441,18 @@ class TestSendWebhookHelper(TestWebhookBase):
             json_payload.encode('utf-8'),
             hashlib.sha1)
         signature = 'sha1=' + signature.hexdigest()
-        assert_equal(self.h.sign(json_payload), signature)
+        assert self.h.sign(json_payload) == signature
 
     def test_log_msg(self):
-        assert_equal(
-            self.h.log_msg('OK'),
+        assert (
+            self.h.log_msg('OK') ==
             'OK: repo-push http://httpbin.org/post /adobe/adobe-1/src/')
         response = Mock(
             status_code=500,
             text='that is why',
             headers={'Content-Type': 'application/json'})
-        assert_equal(
-            self.h.log_msg('Error', response=response),
+        assert (
+            self.h.log_msg('Error', response=response) ==
             "Error: repo-push http://httpbin.org/post /adobe/adobe-1/src/ 500 "
             "that is why {'Content-Type': 'application/json'}")
 
@@ -485,15 +485,15 @@ class TestSendWebhookHelper(TestWebhookBase):
     def test_send_error_response_status(self, log, requests, time):
         requests.post.return_value = Mock(status_code=500)
         self.h.send()
-        assert_equal(requests.post.call_count, 4)  # initial call + 3 retries
-        assert_equal(time.sleep.call_args_list,
+        assert requests.post.call_count == 4  # initial call + 3 retries
+        assert (time.sleep.call_args_list ==
                      [call(60), call(120), call(240)])
-        assert_equal(log.info.call_args_list, [
+        assert log.info.call_args_list == [
             call('Retrying webhook in: %s', [60, 120, 240]),
             call('Retrying webhook in %s seconds', 60),
             call('Retrying webhook in %s seconds', 120),
-            call('Retrying webhook in %s seconds', 240)])
-        assert_equal(log.error.call_count, 4)
+            call('Retrying webhook in %s seconds', 240)]
+        assert log.error.call_count == 4
         log.error.assert_called_with(
             'Webhook send error: {} {} {} {} {} {}'.format(
                 self.wh.type, self.wh.hook_url,
@@ -509,10 +509,10 @@ class TestSendWebhookHelper(TestWebhookBase):
         requests.post.return_value = Mock(status_code=500)
         with h.push_config(config, **{'webhook.retry': ''}):
             self.h.send()
-            assert_equal(requests.post.call_count, 1)
-            assert_equal(time.call_count, 0)
+            assert requests.post.call_count == 1
+            assert time.call_count == 0
             log.info.assert_called_once_with('Retrying webhook in: %s', [])
-            assert_equal(log.error.call_count, 1)
+            assert log.error.call_count == 1
             log.error.assert_called_with(
                 'Webhook send error: {} {} {} {} {} {}'.format(
                     self.wh.type, self.wh.hook_url,
@@ -540,10 +540,10 @@ class TestRepoPushWebhookSender(TestWebhookBase):
         self.wh.enforce_limit = Mock(return_value=True)
         with h.push_config(c, app=self.git):
             sender.send([dict(arg1=1, arg2=2), dict(arg1=3, arg2=4)])
-        assert_equal(send_webhook.post.call_count, 2)
-        assert_equal(send_webhook.post.call_args_list,
+        assert send_webhook.post.call_count == 2
+        assert (send_webhook.post.call_args_list ==
                      [call(self.wh._id, 1), call(self.wh._id, 2)])
-        assert_equal(self.wh.enforce_limit.call_count, 1)
+        assert self.wh.enforce_limit.call_count == 1
 
     @patch('allura.webhooks.log', autospec=True)
     @patch('allura.webhooks.send_webhook', autospec=True)
@@ -553,7 +553,7 @@ class TestRepoPushWebhookSender(TestWebhookBase):
         self.wh.enforce_limit = Mock(return_value=False)
         with h.push_config(c, app=self.git):
             sender.send(dict(arg1=1, arg2=2))
-        assert_equal(send_webhook.post.call_count, 0)
+        assert send_webhook.post.call_count == 0
         log.warn.assert_called_once_with(
             'Webhook fires too often: %s. Skipping', self.wh)
 
@@ -564,7 +564,7 @@ class TestRepoPushWebhookSender(TestWebhookBase):
         sender = RepoPushWebhookSender()
         with h.push_config(c, app=self.git):
             sender.send(dict(arg1=1, arg2=2))
-        assert_equal(send_webhook.post.call_count, 0)
+        assert send_webhook.post.call_count == 0
 
     def test_get_payload(self):
         sender = RepoPushWebhookSender()
@@ -584,7 +584,7 @@ class TestRepoPushWebhookSender(TestWebhookBase):
                 'url': 'http://localhost/adobe/adobe-1/src/',
             },
         }
-        assert_equal(result, expected_result)
+        assert result == expected_result
 
     def test_enforce_limit(self):
         def add_webhooks(suffix, n):
@@ -598,64 +598,64 @@ class TestRepoPushWebhookSender(TestWebhookBase):
 
         sender = RepoPushWebhookSender()
         # default
-        assert_equal(sender.enforce_limit(self.git), True)
+        assert sender.enforce_limit(self.git) == True
         add_webhooks('one', 3)
-        assert_equal(sender.enforce_limit(self.git), False)
+        assert sender.enforce_limit(self.git) == False
 
         # config
         limit = json.dumps({'git': 5})
         with h.push_config(config, **{'webhook.repo_push.max_hooks': limit}):
-            assert_equal(sender.enforce_limit(self.git), True)
+            assert sender.enforce_limit(self.git) == True
             add_webhooks('two', 3)
-            assert_equal(sender.enforce_limit(self.git), False)
+            assert sender.enforce_limit(self.git) == False
 
     def test_before(self):
         sender = RepoPushWebhookSender()
         with patch.object(self.git.repo, 'commit', autospec=True) as _ci:
-            assert_equal(sender._before(self.git.repo, ['3', '2', '1']), '')
+            assert sender._before(self.git.repo, ['3', '2', '1']) == ''
             _ci.return_value.parent_ids = ['0']
-            assert_equal(sender._before(self.git.repo, ['3', '2', '1']), '0')
+            assert sender._before(self.git.repo, ['3', '2', '1']) == '0'
 
     def test_after(self):
         sender = RepoPushWebhookSender()
-        assert_equal(sender._after([]), '')
-        assert_equal(sender._after(['3', '2', '1']), '3')
+        assert sender._after([]) == ''
+        assert sender._after(['3', '2', '1']) == '3'
 
     def test_convert_id(self):
         sender = RepoPushWebhookSender()
-        assert_equal(sender._convert_id(''), '')
-        assert_equal(sender._convert_id('a433fa9'), 'a433fa9')
-        assert_equal(sender._convert_id('a433fa9:13'), 'r13')
+        assert sender._convert_id('') == ''
+        assert sender._convert_id('a433fa9') == 'a433fa9'
+        assert sender._convert_id('a433fa9:13') == 'r13'
 
 
 class TestModels(TestWebhookBase):
     def test_webhook_url(self):
-        assert_equal(self.wh.url(),
+        assert (self.wh.url() ==
                      f'/adobe/adobe-1/admin/src/webhooks/repo-push/{self.wh._id}')
 
     def test_webhook_enforce_limit(self):
         self.wh.last_sent = None
-        assert_equal(self.wh.enforce_limit(), True)
+        assert self.wh.enforce_limit() == True
         # default value
         self.wh.last_sent = dt.datetime.utcnow() - dt.timedelta(seconds=31)
-        assert_equal(self.wh.enforce_limit(), True)
+        assert self.wh.enforce_limit() == True
         self.wh.last_sent = dt.datetime.utcnow() - dt.timedelta(seconds=15)
-        assert_equal(self.wh.enforce_limit(), False)
+        assert self.wh.enforce_limit() == False
         # value from config
         with h.push_config(config, **{'webhook.repo_push.limit': 100}):
             self.wh.last_sent = dt.datetime.utcnow() - dt.timedelta(seconds=101)
-            assert_equal(self.wh.enforce_limit(), True)
+            assert self.wh.enforce_limit() == True
             self.wh.last_sent = dt.datetime.utcnow() - dt.timedelta(seconds=35)
-            assert_equal(self.wh.enforce_limit(), False)
+            assert self.wh.enforce_limit() == False
 
     @patch('allura.model.webhook.dt', autospec=True)
     def test_update_limit(self, dt_mock):
         _now = dt.datetime(2015, 2, 2, 13, 39)
         dt_mock.datetime.utcnow.return_value = _now
-        assert_equal(self.wh.last_sent, None)
+        assert self.wh.last_sent == None
         self.wh.update_limit()
         session(self.wh).expunge(self.wh)
-        assert_equal(M.Webhook.query.get(_id=self.wh._id).last_sent, _now)
+        assert M.Webhook.query.get(_id=self.wh._id).last_sent == _now
 
     def test_json(self):
         expected = {
@@ -743,7 +743,7 @@ class TestWebhookRestController(TestRestApiBase):
         dd.assert_equal(r.json, expected)
 
     def test_create_validation(self):
-        assert_equal(M.Webhook.query.find().count(), len(self.webhooks))
+        assert M.Webhook.query.find().count() == len(self.webhooks)
         r = self.api_get(self.url + '/repo-push', status=405)
 
         r = self.api_post(self.url + '/repo-push', status=400)
@@ -751,7 +751,7 @@ class TestWebhookRestController(TestRestApiBase):
             'result': 'error',
             'error': {'url': 'Please enter a value'},
         }
-        assert_equal(r.json, expected)
+        assert r.json == expected
 
         data = {'url': 'qwer', 'secret': 'qwe'}
         r = self.api_post(self.url + '/repo-push', status=400, **data)
@@ -761,11 +761,11 @@ class TestWebhookRestController(TestRestApiBase):
                 'url': 'You must provide a full domain name (like qwer.com)'
             },
         }
-        assert_equal(r.json, expected)
-        assert_equal(M.Webhook.query.find().count(), len(self.webhooks))
+        assert r.json == expected
+        assert M.Webhook.query.find().count() == len(self.webhooks)
 
     def test_create(self):
-        assert_equal(M.Webhook.query.find().count(), len(self.webhooks))
+        assert M.Webhook.query.find().count() == len(self.webhooks)
         data = {'url': 'http://hook.slack.com/abcd'}
         limit = json.dumps({'git': 10})
         with h.push_config(config, **{'webhook.repo_push.max_hooks': limit}):
@@ -774,7 +774,7 @@ class TestWebhookRestController(TestRestApiBase):
             with td.audits(msg):
                 r = self.api_post(self.url + '/repo-push', status=201, **data)
         webhook = M.Webhook.query.get(hook_url=data['url'])
-        assert_equal(webhook.secret, 'super-secret')  # secret generated
+        assert webhook.secret == 'super-secret'  # secret generated
         expected = {
             '_id': str(webhook._id),
             'url': 'http://localhost/rest/adobe/adobe-1/admin'
@@ -784,10 +784,10 @@ class TestWebhookRestController(TestRestApiBase):
             'mod_date': str(webhook.mod_date),
         }
         dd.assert_equal(r.json, expected)
-        assert_equal(M.Webhook.query.find().count(), len(self.webhooks) + 1)
+        assert M.Webhook.query.find().count() == len(self.webhooks) + 1
 
     def test_create_duplicates(self):
-        assert_equal(M.Webhook.query.find().count(), len(self.webhooks))
+        assert M.Webhook.query.find().count() == len(self.webhooks)
         data = {'url': self.webhooks[0].hook_url}
         limit = json.dumps({'git': 10})
         with h.push_config(config, **{'webhook.repo_push.max_hooks': limit}):
@@ -795,11 +795,11 @@ class TestWebhookRestController(TestRestApiBase):
         expected = {'result': 'error',
                     'error': '_the_form: "repo-push" webhook already '
                               'exists for Git http://httpbin.org/post/0'}
-        assert_equal(r.json, expected)
-        assert_equal(M.Webhook.query.find().count(), len(self.webhooks))
+        assert r.json == expected
+        assert M.Webhook.query.find().count() == len(self.webhooks)
 
     def test_create_limit_reached(self):
-        assert_equal(M.Webhook.query.find().count(), len(self.webhooks))
+        assert M.Webhook.query.find().count() == len(self.webhooks)
         data = {'url': 'http://hook.slack.com/abcd'}
         r = self.api_post(self.url + '/repo-push', status=400, **data)
         expected = {
@@ -807,8 +807,8 @@ class TestWebhookRestController(TestRestApiBase):
             'limits': {'max': 3, 'used': 3},
             'error': 'You have exceeded the maximum number of webhooks '
                       'you are allowed to create for this project/app'}
-        assert_equal(r.json, expected)
-        assert_equal(M.Webhook.query.find().count(), len(self.webhooks))
+        assert r.json == expected
+        assert M.Webhook.query.find().count() == len(self.webhooks)
 
     def test_edit_validation(self):
         webhook = self.webhooks[0]
@@ -821,7 +821,7 @@ class TestWebhookRestController(TestRestApiBase):
                 'url': 'You must provide a full domain name (like qwe.com)'
             },
         }
-        assert_equal(r.json, expected)
+        assert r.json == expected
 
     def test_edit(self):
         webhook = self.webhooks[0]
@@ -833,8 +833,8 @@ class TestWebhookRestController(TestRestApiBase):
         with td.audits(msg):
             r = self.api_post(url, status=200, **data)
         webhook = M.Webhook.query.get(_id=webhook._id)
-        assert_equal(webhook.hook_url, data['url'])
-        assert_equal(webhook.secret, 'secret-0')
+        assert webhook.hook_url == data['url']
+        assert webhook.secret == 'secret-0'
         expected = {
             '_id': str(webhook._id),
             'url': 'http://localhost/rest/adobe/adobe-1/admin'
@@ -853,8 +853,8 @@ class TestWebhookRestController(TestRestApiBase):
         with td.audits(msg):
             r = self.api_post(url, status=200, **data)
         webhook = M.Webhook.query.get(_id=webhook._id)
-        assert_equal(webhook.hook_url, 'http://hook.slack.com/abcd')
-        assert_equal(webhook.secret, 'new-secret')
+        assert webhook.hook_url == 'http://hook.slack.com/abcd'
+        assert webhook.secret == 'new-secret'
         expected = {
             '_id': str(webhook._id),
             'url': 'http://localhost/rest/adobe/adobe-1/admin'
@@ -873,14 +873,14 @@ class TestWebhookRestController(TestRestApiBase):
         expected = {'result': 'error',
                     'error': '_the_form: "repo-push" webhook already '
                               'exists for Git http://httpbin.org/post/1'}
-        assert_equal(r.json, expected)
+        assert r.json == expected
 
     def test_delete_validation(self):
         url = f'{self.url}/repo-push/invalid'
         self.api_delete(url, status=404)
 
     def test_delete(self):
-        assert_equal(M.Webhook.query.find().count(), 3)
+        assert M.Webhook.query.find().count() == 3
         webhook = self.webhooks[0]
         url = f'{self.url}/repo-push/{webhook._id}'
         msg = 'delete webhook repo-push {} {}'.format(
@@ -888,8 +888,8 @@ class TestWebhookRestController(TestRestApiBase):
         with td.audits(msg):
             r = self.api_delete(url, status=200)
         dd.assert_equal(r.json, {'result': 'ok'})
-        assert_equal(M.Webhook.query.find().count(), 2)
-        assert_equal(M.Webhook.query.get(_id=webhook._id), None)
+        assert M.Webhook.query.find().count() == 2
+        assert M.Webhook.query.get(_id=webhook._id) == None
 
     def test_permissions(self):
         self.api_get(self.url, user='test-user', status=403)
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 c2c366c4e..6e3dda279 100644
--- a/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
+++ b/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
@@ -50,17 +50,17 @@ class TestWhenModerating(WithDatabase):
             assert mock_post_to_feed.call_count == 1
 
         post = self.get_post()
-        assert_equal(post.status, 'ok')
-        assert_equal(post.thread.last_post_date.strftime("%Y-%m-%d %H:%M:%S"),
+        assert post.status == 'ok'
+        assert (post.thread.last_post_date.strftime("%Y-%m-%d %H:%M:%S") ==
                      mod_date.strftime("%Y-%m-%d %H:%M:%S"))
 
     def test_that_it_can_mark_as_spam(self):
         self.moderate_post(spam=True)
-        assert_equal(self.get_post().status, 'spam')
+        assert self.get_post().status == 'spam'
 
     def test_that_it_can_be_deleted(self):
         self.moderate_post(delete=True)
-        assert_equal(self.get_post(), None)
+        assert self.get_post() == None
 
     def moderate_post(self, **kwargs):
         with patch('allura.controllers.discuss.flash'):
diff --git a/Allura/allura/tests/unit/phone/test_nexmo.py b/Allura/allura/tests/unit/phone/test_nexmo.py
index 32b861579..1a51b4455 100644
--- a/Allura/allura/tests/unit/phone/test_nexmo.py
+++ b/Allura/allura/tests/unit/phone/test_nexmo.py
@@ -38,44 +38,44 @@ class TestPhoneService:
                     'brand': 'Allura',
                     'api_key': 'test-api-key',
                     'api_secret': 'test-api-secret'}
-        assert_equal(expected, res)
+        assert expected == res
 
         self.phone.config['phone.lang'] = 'it-it'
         res = self.phone.add_common_params(params)
         expected['lg'] = 'it-it'
-        assert_equal(expected, res)
+        assert expected == res
 
     def test_error(self):
         res = self.phone.error()
         expected = {'status': 'error',
                     'error': 'Failed sending request to Nexmo'}
-        assert_equal(expected, res)
+        assert expected == res
         # not allowed code
         res = self.phone.error(code='2', msg='text')
-        assert_equal(expected, res)
+        assert expected == res
         # allowed code
         res = self.phone.error(code='15', msg='text')
         expected = {'status': 'error', 'error': 'text'}
-        assert_equal(expected, res)
+        assert expected == res
 
         # invalid format, possibly US
         res = self.phone.error(code='3', msg='Invalid value for parameter: number', number='8005551234')
-        assert_equal(res['status'], 'error')
-        assert_in('Invalid value for parameter: number', res['error'])
-        assert_in('country code', res['error'])
-        assert_in('US', res['error'])
+        assert res['status'] == 'error'
+        assert 'Invalid value for parameter: number' in res['error']
+        assert 'country code' in res['error']
+        assert 'US' in res['error']
 
         # invalid format, not US
         res = self.phone.error(code='3', msg='Invalid value for parameter: number', number='738005551234')
-        assert_equal(res['status'], 'error')
-        assert_in('Invalid value for parameter: number', res['error'])
-        assert_in('country code', res['error'])
-        assert_not_in('US', res['error'])
+        assert res['status'] == 'error'
+        assert 'Invalid value for parameter: number' in res['error']
+        assert 'country code' in res['error']
+        assert 'US' not in res['error']
 
     def test_ok(self):
         res = self.phone.ok(request_id='123', other='smth')
         expected = {'status': 'ok', 'request_id': '123', 'other': 'smth'}
-        assert_equal(expected, res)
+        assert expected == res
 
     @patch('allura.lib.phone.nexmo.requests', autospec=True)
     def test_verify(self, req):
@@ -93,7 +93,7 @@ class TestPhoneService:
 
         resp = self.phone.verify('1234567890')
         expected = {'status': 'ok', 'request_id': 'test-req-id'}
-        assert_equal(expected, resp)
+        assert expected == resp
         req.post.assert_called_once_with(
             'https://api.nexmo.com/verify/json',
             data=data,
@@ -106,7 +106,7 @@ class TestPhoneService:
         }
         resp = self.phone.verify('1234567890')
         expected = {'status': 'error', 'error': 'Something went wrong'}
-        assert_equal(expected, resp)
+        assert expected == resp
         req.post.assert_called_once_with(
             'https://api.nexmo.com/verify/json',
             data=data,
@@ -118,7 +118,7 @@ class TestPhoneService:
         resp = self.phone.verify('1234567890')
         expected = {'status': 'error',
                     'error': 'Failed sending request to Nexmo'}
-        assert_equal(expected, resp)
+        assert expected == resp
 
     @patch('allura.lib.phone.nexmo.requests', autospec=True)
     def test_check(self, req):
@@ -136,7 +136,7 @@ class TestPhoneService:
 
         resp = self.phone.check('test-req-id', '1234')
         expected = {'status': 'ok', 'request_id': 'test-req-id'}
-        assert_equal(expected, resp)
+        assert expected == resp
         req.post.assert_called_once_with(
             'https://api.nexmo.com/verify/check/json',
             data=data,
@@ -149,7 +149,7 @@ class TestPhoneService:
         }
         resp = self.phone.check('test-req-id', '1234')
         expected = {'status': 'error', 'error': 'Something went wrong'}
-        assert_equal(expected, resp)
+        assert expected == resp
         req.post.assert_called_once_with(
             'https://api.nexmo.com/verify/check/json',
             data=data,
@@ -161,4 +161,4 @@ class TestPhoneService:
         resp = self.phone.check('req-id', '1234')
         expected = {'status': 'error',
                     'error': 'Failed sending request to Nexmo'}
-        assert_equal(expected, resp)
+        assert expected == resp
diff --git a/Allura/allura/tests/unit/phone/test_phone_service.py b/Allura/allura/tests/unit/phone/test_phone_service.py
index 20f145b19..62dcbb4ec 100644
--- a/Allura/allura/tests/unit/phone/test_phone_service.py
+++ b/Allura/allura/tests/unit/phone/test_phone_service.py
@@ -36,22 +36,22 @@ class TestPhoneService:
         res = PhoneService({}).verify('1234567890')
         expected = {'status': 'error',
                     'error': 'Phone service is not configured'}
-        assert_equal(res, expected)
+        assert res == expected
 
     def test_check(self):
         res = PhoneService({}).check('test-req-id', '1111')
         expected = {'status': 'error',
                     'error': 'Phone service is not configured'}
-        assert_equal(res, expected)
+        assert res == expected
 
     def test_get_default(self):
         config = {}
         entry_points = None
         phone = PhoneService.get(config, entry_points)
-        assert_true(isinstance(phone, PhoneService))
+        assert isinstance(phone, PhoneService)
 
     def test_get_method(self):
         config = {'phone.method': 'mock'}
         entry_points = {'mock': MockPhoneService}
         phone = PhoneService.get(config, entry_points)
-        assert_true(isinstance(phone, MockPhoneService))
+        assert isinstance(phone, MockPhoneService)
diff --git a/Allura/allura/tests/unit/spam/test_spam_filter.py b/Allura/allura/tests/unit/spam/test_spam_filter.py
index f9a8f59a3..02582135b 100644
--- a/Allura/allura/tests/unit/spam/test_spam_filter.py
+++ b/Allura/allura/tests/unit/spam/test_spam_filter.py
@@ -88,9 +88,9 @@ class TestSpamFilterFunctional:
         ThreadLocalORMSession.flush_all()
 
         results = SpamCheckResult.query.find().all()
-        assert_equal(len(results), 1)
-        assert_equal(results[0].result, True)
-        assert_equal(results[0].user.username, 'test-user')
+        assert len(results) == 1
+        assert results[0].result == True
+        assert results[0].user.username == 'test-user'
 
 
 class TestChainedSpamFilter:
@@ -101,8 +101,8 @@ class TestChainedSpamFilter:
         checker = SpamFilter.get(config, entry_points)
         assert isinstance(checker, ChainedSpamFilter)
         assert len(checker.filters) == 2, checker.filters
-        assert_equal(checker.filters[0].config, {'spam.method': 'mock1', 'spam.settingA': 'bcd'})
-        assert_equal(checker.filters[1].config, {'spam.method': 'mock2', 'spam.settingA': 'bcd'})
+        assert checker.filters[0].config == {'spam.method': 'mock1', 'spam.settingA': 'bcd'}
+        assert checker.filters[1].config == {'spam.method': 'mock2', 'spam.settingA': 'bcd'}
 
         assert checker.check()  # first filter errors out (but ignored by `exceptionless`), and 2nd returns True
 
diff --git a/Allura/allura/tests/unit/spam/test_stopforumspam.py b/Allura/allura/tests/unit/spam/test_stopforumspam.py
index 2ed47ea18..19da2d2b5 100644
--- a/Allura/allura/tests/unit/spam/test_stopforumspam.py
+++ b/Allura/allura/tests/unit/spam/test_stopforumspam.py
@@ -43,10 +43,10 @@ class TestStopForumSpam:
     @mock.patch('allura.lib.spam.stopforumspamfilter.request')
     def test_check(self, request):
         request.remote_addr = '1.2.3.4'
-        assert_equal(True, self.sfs.check(self.content, artifact=self.artifact))
+        assert True == self.sfs.check(self.content, artifact=self.artifact)
 
         request.remote_addr = '1.1.1.1'
-        assert_equal(False, self.sfs.check(self.content, artifact=self.artifact))
+        assert False == self.sfs.check(self.content, artifact=self.artifact)
 
         request.remote_addr = None  # e.g. from background task processing inbound email
-        assert_equal(False, self.sfs.check(self.content, artifact=self.artifact))
+        assert False == self.sfs.check(self.content, artifact=self.artifact)
diff --git a/Allura/allura/tests/unit/test_app.py b/Allura/allura/tests/unit/test_app.py
index 47b5c2bee..d64eaf0c0 100644
--- a/Allura/allura/tests/unit/test_app.py
+++ b/Allura/allura/tests/unit/test_app.py
@@ -108,7 +108,7 @@ class TestAppDefaults(WithDatabase):
 
     def test_email_address(self):
         self.app.url = '/p/project/mount-point/'
-        assert_equal(self.app.email_address, 'mount-point@project.p.in.localhost')
+        assert self.app.email_address == 'mount-point@project.p.in.localhost'
 
 
 def install_app():
diff --git a/Allura/allura/tests/unit/test_discuss.py b/Allura/allura/tests/unit/test_discuss.py
index fc35f1370..a9b4a8acf 100644
--- a/Allura/allura/tests/unit/test_discuss.py
+++ b/Allura/allura/tests/unit/test_discuss.py
@@ -27,13 +27,13 @@ class TestThread(WithDatabase):
 
     def test_should_update_index(self):
         p = M.Thread()
-        assert_false(p.should_update_index({}, {}))
+        assert not p.should_update_index({}, {})
         old = {'num_views': 1}
         new = {'num_views': 2}
-        assert_false(p.should_update_index(old, new))
+        assert not p.should_update_index(old, new)
         old = {'num_views': 1, 'a': 1}
         new = {'num_views': 2, 'a': 1}
-        assert_false(p.should_update_index(old, new))
+        assert not p.should_update_index(old, new)
         old = {'num_views': 1, 'a': 1}
         new = {'num_views': 2, 'a': 2}
-        assert_true(p.should_update_index(old, new))
+        assert p.should_update_index(old, new)
diff --git a/Allura/allura/tests/unit/test_helpers/test_ago.py b/Allura/allura/tests/unit/test_helpers/test_ago.py
index 159d0c72d..2c3513109 100644
--- a/Allura/allura/tests/unit/test_helpers/test_ago.py
+++ b/Allura/allura/tests/unit/test_helpers/test_ago.py
@@ -99,7 +99,7 @@ class TestAgo:
         self.assertTimeSince('in 2 years', 2008, 6, 1, 0, 0, 0, show_date_after=None)
 
     def assertTimeSince(self, time_string, *time_components, **kwargs):
-        assert_equal(time_string, self.time_since(*time_components, **kwargs))
+        assert time_string == self.time_since(*time_components, **kwargs)
 
     def time_since(self, *time_components, **kwargs):
         end_time = datetime(*time_components)
diff --git a/Allura/allura/tests/unit/test_ldap_auth_provider.py b/Allura/allura/tests/unit/test_ldap_auth_provider.py
index e0633f597..021d2016a 100644
--- a/Allura/allura/tests/unit/test_ldap_auth_provider.py
+++ b/Allura/allura/tests/unit/test_ldap_auth_provider.py
@@ -46,10 +46,10 @@ class TestLdapAuthenticationProvider:
         # Note: OSX uses a crypt library with a known issue relating the hashing algorithms.
         if b'$6$rounds=' not in ep('pwd') and platform.system() == 'Darwin':
             raise SkipTest
-        assert_not_equal(ep('test_pass'), ep('test_pass'))
-        assert_equal(ep('test_pass', '0000'), ep('test_pass', '0000'))
+        assert ep('test_pass') != ep('test_pass')
+        assert ep('test_pass', '0000') == ep('test_pass', '0000')
         # Test password format
-        assert_true(ep('pwd').startswith(b'{CRYPT}$6$rounds=6000$'))
+        assert ep('pwd').startswith(b'{CRYPT}$6$rounds=6000$')
 
     @patch('allura.lib.plugin.ldap')
     def test_set_password(self, ldap):
@@ -65,7 +65,7 @@ class TestLdapAuthenticationProvider:
         connection.bind_s.called_once_with(dn, b'old-pass')
         connection.modify_s.assert_called_once_with(
             dn, [(ldap.MOD_REPLACE, 'userPassword', b'new-pass-hash')])
-        assert_equal(connection.unbind_s.call_count, 1)
+        assert connection.unbind_s.call_count == 1
 
     @patch('allura.lib.plugin.ldap')
     def test_login(self, ldap):
@@ -83,7 +83,7 @@ class TestLdapAuthenticationProvider:
         ldap.initialize.assert_called_once_with('ldaps://localhost/')
         connection = ldap.initialize.return_value
         connection.bind_s.called_once_with(dn, 'test-password')
-        assert_equal(connection.unbind_s.call_count, 1)
+        assert connection.unbind_s.call_count == 1
 
     @patch('allura.lib.plugin.ldap')
     def test_login_autoregister(self, ldap):
@@ -103,7 +103,7 @@ class TestLdapAuthenticationProvider:
 
         user = M.User.query.get(username=params['username'])
         assert user
-        assert_equal(user.display_name, 'åℒƒ')
+        assert user.display_name == 'åℒƒ'
 
     @patch('allura.lib.plugin.modlist')
     @patch('allura.lib.plugin.ldap')
@@ -116,11 +116,11 @@ class TestLdapAuthenticationProvider:
         ldap.dn.escape_dn_chars = lambda x: x
         self.provider._encode_password = Mock(return_value=b'new-password-hash')
 
-        assert_equal(M.User.query.get(username=user_doc['username']), None)
+        assert M.User.query.get(username=user_doc['username']) == None
         with h.push_config(config, **{'auth.ldap.autoregister': 'false'}):
             self.provider.register_user(user_doc)
         ThreadLocalORMSession.flush_all()
-        assert_not_equal(M.User.query.get(username=user_doc['username']), None)
+        assert M.User.query.get(username=user_doc['username']) != None
 
         dn = 'uid=%s,ou=people,dc=localdomain' % user_doc['username']
         ldap.initialize.assert_called_once_with('ldaps://localhost/')
@@ -129,7 +129,7 @@ class TestLdapAuthenticationProvider:
             'cn=admin,dc=localdomain',
             'admin-password')
         connection.add_s.assert_called_once_with(dn, modlist.addModlist.return_value)
-        assert_equal(connection.unbind_s.call_count, 1)
+        assert connection.unbind_s.call_count == 1
 
     @patch('allura.lib.plugin.ldap')
     @patch('allura.lib.plugin.datetime', autospec=True)
@@ -138,7 +138,7 @@ class TestLdapAuthenticationProvider:
         user.__ming__ = Mock()
         user.last_password_updated = None
         self.provider.set_password(user, None, 'new')
-        assert_equal(user.last_password_updated, dt_mock.utcnow.return_value)
+        assert user.last_password_updated == dt_mock.utcnow.return_value
 
     def test_get_last_password_updated_not_set(self):
         user = Mock()
@@ -148,10 +148,10 @@ class TestLdapAuthenticationProvider:
         upd = self.provider.get_last_password_updated(user)
         gen_time = datetime.utcfromtimestamp(
             calendar.timegm(user._id.generation_time.utctimetuple()))
-        assert_equal(upd, gen_time)
+        assert upd == gen_time
 
     def test_get_last_password_updated(self):
         user = Mock()
         user.last_password_updated = datetime(2014, 6, 4, 13, 13, 13)
         upd = self.provider.get_last_password_updated(user)
-        assert_equal(upd, user.last_password_updated)
+        assert upd == user.last_password_updated
diff --git a/Allura/allura/tests/unit/test_package_path_loader.py b/Allura/allura/tests/unit/test_package_path_loader.py
index c43118b6e..d00529918 100644
--- a/Allura/allura/tests/unit/test_package_path_loader.py
+++ b/Allura/allura/tests/unit/test_package_path_loader.py
@@ -43,19 +43,19 @@ class TestPackagePathLoader(TestCase):
 
         paths = PackagePathLoader()._load_paths()
 
-        assert_equal(paths, [
+        assert paths == [
             ['site-theme', None],
             ['ep0', 'path:eps.ep0'],
             ['ep1', 'path:eps.ep1'],
             ['ep2', 'path:eps.ep2'],
             ['allura', '/'],
-        ])
-        assert_equal(type(paths[0]), list)
-        assert_equal(resource_filename.call_args_list, [
+        ]
+        assert type(paths[0]) == list
+        assert resource_filename.call_args_list == [
             mock.call('eps.ep0', ''),
             mock.call('eps.ep1', ''),
             mock.call('eps.ep2', ''),
-        ])
+        ]
 
     @mock.patch('pkg_resources.iter_entry_points')
     def test_load_rules(self, iter_entry_points):
@@ -70,8 +70,8 @@ class TestPackagePathLoader(TestCase):
 
         order_rules, replacement_rules = PackagePathLoader()._load_rules()
 
-        assert_equal(order_rules, [('ep0', 'allura'), ('allura', 'ep2')])
-        assert_equal(replacement_rules, {'allura': 'ep1'})
+        assert order_rules == [('ep0', 'allura'), ('allura', 'ep2')]
+        assert replacement_rules == {'allura': 'ep1'}
 
         eps = iter_entry_points.return_value.__iter__.return_value = [
             mock.Mock(ep_name='ep0', rules=[('?', 'allura')]),
@@ -100,11 +100,11 @@ class TestPackagePathLoader(TestCase):
 
         ppl._replace_signposts(paths, rules)
 
-        assert_equal(paths, [
+        assert paths == [
             ['site-theme', '/ep1'],
             ['ep0', '/ep0'],
             ['allura', '/ep2'],
-        ])
+        ]
 
     def test_sort_paths(self):
         paths = [
@@ -125,14 +125,14 @@ class TestPackagePathLoader(TestCase):
 
         PackagePathLoader()._sort_paths(paths, rules)
 
-        assert_equal(paths, [
+        assert paths == [
             ['site-theme', None],
             ['ep2', '/ep2'],
             ['ep3', '/ep3'],
             ['ep1', '/ep1'],
             ['allura', '/'],
             ['ep0', '/ep0'],
-        ])
+        ]
 
     def test_init_paths(self):
         paths = [
@@ -153,7 +153,7 @@ class TestPackagePathLoader(TestCase):
         ppl._sort_paths.assert_called_once_with(paths, 'order_rules')
         ppl._replace_signposts.assert_called_once_with(paths, 'repl_rules')
 
-        assert_equal(output, ['/', '/tail'])
+        assert output == ['/', '/tail']
 
     @mock.patch('jinja2.FileSystemLoader')
     def test_fs_loader(self, FileSystemLoader):
@@ -166,7 +166,7 @@ class TestPackagePathLoader(TestCase):
 
         ppl.init_paths.assert_called_once_with()
         FileSystemLoader.assert_called_once_with(['path1', 'path2'])
-        assert_equal(output1, 'fs_loader')
+        assert output1 == 'fs_loader'
         assert output1 is output2
 
     @mock.patch.dict(config, {'disable_template_overrides': False})
@@ -179,7 +179,7 @@ class TestPackagePathLoader(TestCase):
         # override exists
         output = ppl.get_source('env', 'allura.ext.admin:templates/audit.html')
 
-        assert_equal(output, 'fs_load')
+        assert output == 'fs_load'
         fs_loader().get_source.assert_called_once_with(
             'env', 'override/allura/ext/admin/templates/audit.html')
 
@@ -195,8 +195,8 @@ class TestPackagePathLoader(TestCase):
             rf.assert_called_once_with(
                 'allura.ext.admin', 'templates/audit.html')
 
-        assert_equal(output, 'fs_load')
-        assert_equal(fs_loader().get_source.call_count, 2)
+        assert output == 'fs_load'
+        assert fs_loader().get_source.call_count == 2
         fs_loader().get_source.assert_called_with('env', 'resource')
 
         fs_loader().get_source.reset_mock()
@@ -206,8 +206,8 @@ class TestPackagePathLoader(TestCase):
         # no override, ':' not in template
         output = ppl.get_source('env', 'templates/audit.html')
 
-        assert_equal(output, 'fs_load')
-        assert_equal(fs_loader().get_source.call_count, 2)
+        assert output == 'fs_load'
+        assert fs_loader().get_source.call_count == 2
         fs_loader().get_source.assert_called_with(
             'env', 'templates/audit.html')
 
@@ -220,11 +220,11 @@ class TestPackagePathLoader(TestCase):
         assert_raises(
             jinja2.TemplateError,
             ppl.get_source, 'env', 'allura.ext.admin:templates/audit.html')
-        assert_equal(fs_loader().get_source.call_count, 1)
+        assert fs_loader().get_source.call_count == 1
         fs_loader().get_source.reset_mock()
 
         with mock.patch.dict(config, {'disable_template_overrides': False}):
             assert_raises(
                 jinja2.TemplateError,
                 ppl.get_source, 'env', 'allura.ext.admin:templates/audit.html')
-            assert_equal(fs_loader().get_source.call_count, 2)
+            assert fs_loader().get_source.call_count == 2
diff --git a/Allura/allura/tests/unit/test_repo.py b/Allura/allura/tests/unit/test_repo.py
index 648c330c7..15258ee5f 100644
--- a/Allura/allura/tests/unit/test_repo.py
+++ b/Allura/allura/tests/unit/test_repo.py
@@ -106,40 +106,40 @@ class TestBlob(unittest.TestCase):
 
     def test_pypeline_view(self):
         blob = M.repository.Blob(MagicMock(), 'INSTALL.mdown', 'blob1')
-        assert_equal(blob.has_pypeline_view, True)
+        assert blob.has_pypeline_view == True
 
     def test_has_html_view_text_mime(self):
         blob = M.repository.Blob(MagicMock(), 'INSTALL', 'blob1')
         blob.content_type = 'text/plain'
-        assert_equal(blob.has_html_view, True)
+        assert blob.has_html_view == True
 
     def test_has_html_view_text_ext(self):
         blob = M.repository.Blob(MagicMock(), 'INSTALL.txt', 'blob1')
         blob.content_type = 'foo/bar'
-        assert_equal(blob.has_html_view, True)
+        assert blob.has_html_view == True
 
     def test_has_html_view_text_contents(self):
         blob = M.repository.Blob(MagicMock(), 'INSTALL', 'blob1')
         blob.content_type = 'foo/bar'
         blob.text = b'hello world, this is text here'
-        assert_equal(blob.has_html_view, True)
+        assert blob.has_html_view == True
 
     def test_has_html_view_bin_ext(self):
         blob = M.repository.Blob(MagicMock(), 'INSTALL.zip', 'blob1')
-        assert_equal(blob.has_html_view, False)
+        assert blob.has_html_view == False
 
     def test_has_html_view_bin_content(self):
         blob = M.repository.Blob(MagicMock(), 'myfile', 'blob1')
         blob.content_type = 'whatever'
         blob.text = b'\0\0\0\0'
-        assert_equal(blob.has_html_view, False)
+        assert blob.has_html_view == False
 
     def test_has_html_view__local_setting_override_bin(self):
         blob = M.repository.Blob(MagicMock(), 'myfile.dat', 'blob1')
         blob.content_type = 'whatever'
         blob.text = b'\0\0\0\0'
         blob.repo._additional_viewable_extensions = ['.dat']
-        assert_equal(blob.has_html_view, True)
+        assert blob.has_html_view == True
 
 
 class TestCommit(unittest.TestCase):
@@ -169,19 +169,19 @@ class TestCommit(unittest.TestCase):
         tree = commit.get_tree(create=False)
         assert not commit.repo.compute_tree_new.called
         assert not c.model_cache.get.called
-        assert_equal(tree, None)
+        assert tree == None
 
         commit.tree_id = 'tree'
         tree = commit.get_tree(create=False)
         assert not commit.repo.compute_tree_new.called
         c.model_cache.get.assert_called_with(M.repository.Tree, dict(_id='tree'))
-        assert_equal(tree, None)
+        assert tree == None
 
         _tree = Mock()
         c.model_cache.get.return_value = _tree
         tree = commit.get_tree(create=False)
         _tree.set_context.assert_called_with(commit)
-        assert_equal(tree, _tree)
+        assert tree == _tree
 
     @patch.object(M.repository.Tree.query, 'get')
     def test_get_tree_create(self, tree_get):
@@ -196,7 +196,7 @@ class TestCommit(unittest.TestCase):
         commit.repo.compute_tree_new.assert_called_once_with(commit)
         assert not c.model_cache.get.called
         assert not tree_get.called
-        assert_equal(tree, None)
+        assert tree == None
 
         commit.repo.compute_tree_new.reset_mock()
         commit.repo.compute_tree_new.return_value = 'tree'
@@ -208,7 +208,7 @@ class TestCommit(unittest.TestCase):
         c.model_cache.get.assert_called_once_with(
             M.repository.Tree, dict(_id='tree'))
         _tree.set_context.assert_called_once_with(commit)
-        assert_equal(tree, _tree)
+        assert tree == _tree
 
         commit.repo.compute_tree_new.reset_mock()
         c.model_cache.get.reset_mock()
@@ -219,7 +219,7 @@ class TestCommit(unittest.TestCase):
         c.model_cache.get.assert_called_once_with(
             M.repository.Tree, dict(_id='tree2'))
         _tree.set_context.assert_called_once_with(commit)
-        assert_equal(tree, _tree)
+        assert tree == _tree
 
         commit.repo.compute_tree_new.reset_mock()
         c.model_cache.get.reset_mock()
@@ -229,12 +229,12 @@ class TestCommit(unittest.TestCase):
         c.model_cache.get.assert_called_once_with(
             M.repository.Tree, dict(_id='tree2'))
         commit.repo.compute_tree_new.assert_called_once_with(commit)
-        assert_equal(commit.tree_id, 'tree')
+        assert commit.tree_id == 'tree'
         tree_get.assert_called_once_with(_id='tree')
         c.model_cache.set.assert_called_once_with(
             M.repository.Tree, dict(_id='tree'), _tree)
         _tree.set_context.assert_called_once_with(commit)
-        assert_equal(tree, _tree)
+        assert tree == _tree
 
     def test_tree_create(self):
         commit = M.repository.Commit()
diff --git a/Allura/allura/tests/unit/test_solr.py b/Allura/allura/tests/unit/test_solr.py
index 5ef3492c8..8647aecef 100644
--- a/Allura/allura/tests/unit/test_solr.py
+++ b/Allura/allura/tests/unit/test_solr.py
@@ -36,14 +36,14 @@ class TestSolr(unittest.TestCase):
         solr = Solr(servers, commit=False, commitWithin='10000')
         calls = [mock.call('server1'), mock.call('server2')]
         pysolr.Solr.assert_has_calls(calls)
-        assert_equal(len(solr.push_pool), 2)
+        assert len(solr.push_pool) == 2
 
         pysolr.reset_mock()
         solr = Solr(servers, 'server3', commit=False, commitWithin='10000')
         calls = [mock.call('server1'), mock.call('server2'),
                  mock.call('server3')]
         pysolr.Solr.assert_has_calls(calls)
-        assert_equal(len(solr.push_pool), 2)
+        assert len(solr.push_pool) == 2
 
     @mock.patch('allura.lib.solr.pysolr')
     def test_add(self, pysolr):
@@ -124,21 +124,21 @@ class TestSearchIndexable(unittest.TestCase):
 
     def test_solarize_empty_index(self):
         self.obj.index = lambda: None
-        assert_equal(self.obj.solarize(), None)
+        assert self.obj.solarize() == None
 
     def test_solarize_doc_without_text(self):
         self.obj.index = lambda: dict()
-        assert_equal(self.obj.solarize(), dict(text=''))
+        assert self.obj.solarize() == dict(text='')
 
     def test_solarize_strips_markdown(self):
         self.obj.index = lambda: dict(text='# Header')
-        assert_equal(self.obj.solarize(), dict(text='Header'))
+        assert self.obj.solarize() == dict(text='Header')
 
     def test_solarize_html_in_text(self):
         self.obj.index = lambda: dict(text='<script>a(1)</script>')
-        assert_equal(self.obj.solarize(), dict(text='<script>a(1)</script>'))
+        assert self.obj.solarize() == dict(text='<script>a(1)</script>')
         self.obj.index = lambda: dict(text='&lt;script&gt;a(1)&lt;/script&gt;')
-        assert_equal(self.obj.solarize(), dict(text='<script>a(1)</script>'))
+        assert self.obj.solarize() == dict(text='<script>a(1)</script>')
 
 
 class TestSearch_app(unittest.TestCase):
@@ -156,7 +156,7 @@ class TestSearch_app(unittest.TestCase):
         url_fn.side_effect = ['the-score-url', 'the-date-url']
         with h.push_context('test', 'wiki', neighborhood='Projects'):
             resp = search_app(q='foo bar')
-        assert_equal(resp, dict(
+        assert resp == dict(
             q='foo bar',
             history=None,
             results=[],
@@ -167,7 +167,7 @@ class TestSearch_app(unittest.TestCase):
             sort_score_url='the-score-url',
             sort_date_url='the-date-url',
             sort_field='score',
-        ))
+        )
 
     @td.with_wiki
     @mock.patch('allura.lib.search.g.solr.search')
@@ -196,7 +196,7 @@ class TestSearch_app(unittest.TestCase):
         with h.push_context('test', 'wiki', neighborhood='Projects'):
             resp = search_app(q='foo bar')
 
-        assert_equal(resp, dict(
+        assert resp == dict(
             q='foo bar',
             history=None,
             count=2,
@@ -224,14 +224,14 @@ class TestSearch_app(unittest.TestCase):
                 'text_match': Markup('less scary but still dangerous &amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt; blah <strong>bar</strong> foo foo'),
                 '_artifact': None,
             }]
-        ))
+        )
 
     def test_escape_solr_arg(self):
         text = 'some: weird "text" with symbols'
         escaped_text = escape_solr_arg(text)
-        assert_equal(escaped_text, r'some\: weird \"text\" with symbols')
+        assert escaped_text == r'some\: weird \"text\" with symbols'
 
     def test_escape_solr_arg_with_backslash(self):
         text = 'some: weird "text" with \\ backslash'
         escaped_text = escape_solr_arg(text)
-        assert_equal(escaped_text, r'some\: weird \"text\" with \\ backslash')
+        assert escaped_text == r'some\: weird \"text\" with \\ backslash'
diff --git a/requirements.in b/requirements.in
index 4bfbabcd0..ad966a992 100644
--- a/requirements.in
+++ b/requirements.in
@@ -58,6 +58,7 @@ pyflakes
 #pylint -- disabled due to [#8346]  (also requires diff versions on py2 vs 3, including transitive deps which gets tricky with pip-compile)
 testfixtures
 WebTest
+pytest
 
 # deployment
 gunicorn
diff --git a/requirements.txt b/requirements.txt
index 1b49cf600..b115a9d5b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,6 +6,8 @@
 #
 activitystream==0.4.0
     # via -r requirements.in
+attrs==22.1.0
+    # via pytest
 beaker==1.11.0
     # via -r requirements.in
 beautifulsoup4==4.11.1
@@ -66,9 +68,14 @@ httplib2==0.19.0
 idna==3.3
     # via requests
 importlib-metadata==4.12.0
-    # via markdown
+    # via
+    #   markdown
+    #   pluggy
+    #   pytest
 inflection==0.5.1
     # via profanityfilter
+iniconfig==1.1.1
+    # via pytest
 iso8601==1.0.2
     # via colander
 jinja2==3.1.2
@@ -95,6 +102,8 @@ oauth2 @ https://github.com/joestump/python-oauth2/archive/b94f69b1ad19551354792
     # via -r requirements.in
 oauthlib==3.2.0
     # via requests-oauthlib
+packaging==21.3
+    # via pytest
 paginate==0.5.6
     # via -r requirements.in
 paste==3.5.1
@@ -110,8 +119,12 @@ pastescript==3.2.1
     # via -r requirements.in
 pillow==9.2.0
     # via -r requirements.in
+pluggy==1.0.0
+    # via pytest
 profanityfilter==2.0.6
     # via -r requirements.in
+py==1.11.0
+    # via pytest
 pycparser==2.21
     # via cffi
 pyflakes==2.4.0
@@ -124,11 +137,15 @@ pymongo==3.11.4
     #   activitystream
     #   ming
 pyparsing==2.4.7
-    # via httplib2
+    # via
+    #   httplib2
+    #   packaging
 pypeline[creole,markdown,rst,textile]==0.6.0
     # via -r requirements.in
 pysolr==3.9.0
     # via -r requirements.in
+pytest==7.1.3
+    # via -r requirements.in
 python-dateutil==2.8.2
     # via
     #   -r requirements.in
@@ -188,6 +205,8 @@ timermiddleware==0.6.2
     # via -r requirements.in
 tinycss2==1.1.1
     # via bleach
+tomli==2.0.1
+    # via pytest
 translationstring==1.4
     # via colander
 turbogears2==2.3.12


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

Posted by di...@apache.org.
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 8500cccc75cc19787187a401e0974dd9c52bb671
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 6a8b04cd3..ae0e79f16 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -1568,7 +1568,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'}
@@ -2505,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_method(method)
+        other_session.setup_method(None)
         other_session.app.get('/auth/preferences/')
 
         with out_audits(user=True):
@@ -2547,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_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 1f3e2360a..5076ef049 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: