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/30 13:34:03 UTC

[allura] branch dw/8455-part2 updated (8a769b86f -> 50aa084c8)

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

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


 discard 8a769b86f fixup! fixup! fixup! fixup! fixup! fixup! fixup! [#8455] remove @with_setup
 discard 820a0159a fixup! fixup! fixup! fixup! fixup! fixup! [#8455] remove @with_setup
 discard 4c000b4aa fixup! fixup! fixup! fixup! fixup! [#8455] remove @with_setup
 discard 98462a443 fixup! fixup! fixup! fixup! [#8455] remove @with_setup
 discard e26ca2e2b fixup! fixup! fixup! [#8455] remove @with_setup
 discard 7805b2940 fixup! fixup! [#8455] remove @with_setup
 discard 768e1cd0f fixup! [#8455] remove @with_setup
 discard cdc52d5ae [#8455] remove @with_setup
 discard 5f21327c1 fixup! fixup! fixup! fixup! [#8455] converted the remaining modules fully to pytest
 discard dbd639104 fixup! [#8455] added pytest.ini
 discard b80d4c7da fixup! fixup! fixup! [#8455] converted the remaining modules fully to pytest
 discard 5a3c76630 fixup! fixup! [#8455] converted the remaining modules fully to pytest
 discard d0e37d7be [#8455] converted yield test to pytest.mark.parametrize
 discard 7c0e67074 fixup! [#8455] converted the remaining modules fully to pytest
 discard f880aaa52 [#8455] added pytest.ini
 discard 26f8f3051 [#8455] converted the remaining modules fully to pytest
 discard 2ae7e7c8b [#8455] ran nose2pytest on Forge* and AlluraTest modules
 discard df4c736dd [#8455] allura pytest - fix misc test failures that popped up during pytest conversion
 discard 64dea2ba9 all trivial failures resolved for ./Allura, only legit failures remain
 discard c39d87e2e All tests in ./Allura collecting, and test_auth completely passing
 discard a5082aed4 first substantial test file mostly passing under pytest
 discard 1655e93e2 8455 nose2pytest for ./Allura
     add 57e484eb0 [#8467] DefOptScriptTask and other improvements to task submission in web ui
     add c04a6dd1c [#8467] misc type hints
     add 897e23408 [#8469] added missing canonical tags to user profile, discussion, blog, list and tickets
     add ecd1e30db h1 title improvement for wiki sections browse pages and browse labels
     add 108f8e409 noindex,follow for ticket milestone pages
     add f0a4fe461 [#8471] wiki noindex: cleanup, tests, and check for comments now
     new 941177771 8455 nose2pytest for ./Allura
     new 54cc022b7 first substantial test file mostly passing under pytest
     new 4dca14e72 All tests in ./Allura collecting, and test_auth completely passing
     new fae235ff0 all trivial failures resolved for ./Allura, only legit failures remain
     new be8e22a97 [#8455] allura pytest - fix misc test failures that popped up during pytest conversion
     new 2717ae0aa [#8455] ran nose2pytest on Forge* and AlluraTest modules
     new 2de9bcef4 [#8455] converted the remaining modules fully to pytest
     new e9e7bd182 [#8455] added pytest.ini
     new b92975160 [#8455] converted yield test to pytest.mark.parametrize
     new 50aa084c8 [#8455] remove @with_setup

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   (8a769b86f)
            \
             N -- N -- N   refs/heads/dw/8455-part2 (50aa084c8)

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 10 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/controllers/site_admin.py            |  3 +
 .../ext/user_profile/templates/user_index.html     | 13 ++--
 Allura/allura/lib/helpers.py                       |  1 +
 Allura/allura/lib/plugin.py                        |  2 +-
 Allura/allura/model/discuss.py                     |  6 +-
 Allura/allura/model/project.py                     |  2 +-
 Allura/allura/scripts/scripttask.py                | 69 +++++++++++++++++++++-
 Allura/allura/templates/jinja_master/lib.html      | 15 +++--
 Allura/allura/templates/site_admin_task_new.html   |  7 ++-
 Allura/allura/templates/tool_list.html             |  6 +-
 Allura/docs/api/{lib.rst => scripts.rst}           | 10 ++--
 Allura/docs/conf.py                                |  2 +-
 ForgeBlog/forgeblog/templates/blog/post.html       |  5 ++
 .../templates/discussionforums/index.html          |  7 ++-
 .../forgetracker/templates/tracker/milestone.html  |  1 +
 .../forgetracker/templates/tracker/ticket.html     | 10 ++--
 .../forgewiki/templates/wiki/browse_tags.html      |  5 +-
 ForgeWiki/forgewiki/tests/test_app.py              | 23 +++++++-
 ForgeWiki/forgewiki/wiki_main.py                   | 27 ++++++---
 19 files changed, 162 insertions(+), 52 deletions(-)
 copy Allura/docs/api/{lib.rst => scripts.rst} (88%)


[allura] 07/10: [#8455] converted the remaining modules fully to 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-part2
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 2de9bcef4725ef3eb4a647d68d57db363a7a6b74
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Fri Sep 16 18:46:46 2022 +0000

    [#8455] converted the remaining modules fully to pytest
---
 Allura/allura/tests/functional/test_auth.py        | 32 ++++++---------
 Allura/allura/tests/functional/test_discuss.py     |  2 +-
 Allura/allura/tests/model/test_artifact.py         | 16 ++++----
 Allura/allura/tests/model/test_discussion.py       |  4 +-
 Allura/allura/tests/test_app.py                    |  6 +--
 Allura/allura/tests/test_commands.py               | 45 +++++++++-------------
 Allura/allura/tests/test_helpers.py                | 26 ++++---------
 Allura/allura/tests/test_mail_util.py              | 26 ++++++-------
 Allura/allura/tests/test_multifactor.py            | 24 ++++++------
 Allura/allura/tests/test_patches.py                | 21 +++++-----
 Allura/allura/tests/test_plugin.py                 | 22 ++++-------
 Allura/allura/tests/test_security.py               |  4 +-
 Allura/allura/tests/test_utils.py                  | 13 ++-----
 Allura/allura/tests/test_webhooks.py               | 23 +++++------
 Allura/allura/tests/unit/spam/test_akismet.py      |  2 +-
 .../tests/unit/test_helpers/test_set_context.py    |  8 ++--
 .../allura/tests/unit/test_package_path_loader.py  |  8 ++--
 .../forgeactivity/tests/functional/test_rest.py    |  8 ++--
 .../forgeactivity/tests/functional/test_root.py    |  8 ++--
 ForgeBlog/forgeblog/tests/functional/test_rest.py  |  4 +-
 ForgeBlog/forgeblog/tests/test_app.py              |  4 +-
 ForgeBlog/forgeblog/tests/test_commands.py         |  2 +-
 ForgeBlog/forgeblog/tests/test_roles.py            |  2 +-
 ForgeBlog/forgeblog/tests/unit/__init__.py         |  4 +-
 .../forgediscussion/tests/functional/test_forum.py | 16 ++++----
 .../tests/functional/test_forum_admin.py           |  4 +-
 .../tests/functional/test_import.py                |  4 +-
 .../forgediscussion/tests/functional/test_rest.py  |  6 +--
 ForgeDiscussion/forgediscussion/tests/test_app.py  |  2 +-
 .../forgediscussion/tests/test_forum_roles.py      |  2 +-
 .../forgefeedback/tests/functional/test_root.py    |  6 ---
 .../forgefeedback/tests/test_feedback_roles.py     |  2 +-
 ForgeFeedback/forgefeedback/tests/unit/__init__.py |  4 +-
 .../tests/unit/test_root_controller.py             |  4 +-
 .../forgefiles/tests/functional/test_root.py       |  3 --
 ForgeFiles/forgefiles/tests/model/__init__.py      |  4 +-
 ForgeFiles/forgefiles/tests/test_files_roles.py    |  2 +-
 .../forgegit/tests/functional/test_controllers.py  | 32 +++++++--------
 ForgeGit/forgegit/tests/model/test_repository.py   | 12 +++---
 ForgeGit/forgegit/tests/test_git_app.py            |  2 +-
 ForgeGit/forgegit/tests/test_tasks.py              |  6 +--
 ForgeImporters/forgeimporters/github/__init__.py   |  3 ++
 .../forgeimporters/github/tests/test_code.py       |  2 +-
 .../forgeimporters/github/tests/test_oauth.py      |  6 +--
 .../forgeimporters/github/tests/test_utils.py      |  2 +-
 .../forgeimporters/github/tests/test_wiki.py       |  2 +-
 .../forgeimporters/tests/forge/test_discussion.py  | 11 ++----
 .../forgeimporters/tests/forge/test_tracker.py     | 10 ++---
 .../tests/github/functional/test_github.py         |  4 +-
 .../forgeimporters/tests/github/test_extractor.py  |  2 +-
 .../forgeimporters/tests/github/test_tracker.py    |  6 +--
 ForgeImporters/forgeimporters/tests/test_base.py   | 10 ++---
 .../forgeimporters/trac/tests/test_tickets.py      |  6 +--
 ForgeLink/forgelink/tests/functional/test_rest.py  |  8 ++--
 ForgeLink/forgelink/tests/test_app.py              |  2 +-
 .../forgesvn/tests/functional/test_controllers.py  |  8 ++--
 ForgeSVN/forgesvn/tests/model/test_repository.py   | 24 ++++++------
 .../forgesvn/tests/model/test_svnimplementation.py |  2 +-
 ForgeSVN/forgesvn/tests/test_svn_app.py            |  2 +-
 ForgeSVN/forgesvn/tests/test_tasks.py              |  4 +-
 .../tests/functional/{test.py => test_main.py}     |  4 +-
 .../tests/command/test_fix_discussion.py           |  2 +-
 .../forgetracker/tests/functional/test_rest.py     | 16 ++++----
 .../forgetracker/tests/functional/test_root.py     | 25 ++++--------
 ForgeTracker/forgetracker/tests/test_app.py        |  2 +-
 .../forgetracker/tests/test_tracker_roles.py       |  2 +-
 ForgeTracker/forgetracker/tests/unit/__init__.py   |  4 +-
 .../forgetracker/tests/unit/test_globals_model.py  |  4 +-
 .../tests/unit/test_root_controller.py             | 12 +++---
 .../forgetracker/tests/unit/test_ticket_model.py   | 12 ++----
 ForgeUserStats/forgeuserstats/tests/test_model.py  |  2 +-
 ForgeUserStats/forgeuserstats/tests/test_stats.py  |  8 ++--
 ForgeWiki/forgewiki/tests/functional/test_rest.py  |  8 ++--
 ForgeWiki/forgewiki/tests/functional/test_root.py  |  4 +-
 ForgeWiki/forgewiki/tests/test_app.py              |  4 +-
 ForgeWiki/forgewiki/tests/test_wiki_roles.py       |  2 +-
 scripts/perf/call_count.py                         |  4 +-
 77 files changed, 287 insertions(+), 367 deletions(-)

diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index 0acf610e3..06a3daf9d 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -32,16 +32,6 @@ from ming.orm.ormsession import ThreadLocalORMSession, session
 from tg import config, expose
 from mock import patch, Mock
 import mock
-from alluratest.tools import (
-    assert_equal,
-    assert_not_equal,
-    assert_is_none,
-    assert_is_not_none,
-    assert_in,
-    assert_not_in,
-    assert_true,
-    assert_false,
-)
 from tg import tmpl_context as c, app_globals as g
 
 from allura.tests import TestController
@@ -1896,8 +1886,8 @@ class TestOAuth(TestController):
         )
         r = self.app.get(*oauth1_webtest('/rest/oauth/access_token', oauth_params))
         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_token = atok['oauth_token'][0]
@@ -1934,8 +1924,8 @@ class TestOAuth(TestController):
         )
         ThreadLocalORMSession.flush_all()
         r = self.app.post('/rest/oauth/authorize', params={'oauth_token': 'api_key_reqtok_12345'})
-        assert_in('ctok_desc', r.text)
-        assert_in('api_key_reqtok_12345', r.text)
+        assert 'ctok_desc' in r.text
+        assert 'api_key_reqtok_12345' in r.text
 
     def test_authorize_invalid(self):
         resp = self.app.post('/rest/oauth/authorize', params={'oauth_token': 'api_key_reqtok_12345'}, status=400)
@@ -1957,7 +1947,7 @@ class TestOAuth(TestController):
         ThreadLocalORMSession.flush_all()
         self.app.post('/rest/oauth/do_authorize',
                       params={'no': '1', 'oauth_token': 'api_key_reqtok_12345'})
-        assert_is_none(M.OAuthRequestToken.query.get(api_key='api_key_reqtok_12345'))
+        assert M.OAuthRequestToken.query.get(api_key='api_key_reqtok_12345') is None
 
     def test_do_authorize_oob(self):
         user = M.User.by_username('test-admin')
@@ -1974,7 +1964,7 @@ class TestOAuth(TestController):
         )
         ThreadLocalORMSession.flush_all()
         r = self.app.post('/rest/oauth/do_authorize', params={'yes': '1', 'oauth_token': 'api_key_reqtok_12345'})
-        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')
@@ -2018,8 +2008,8 @@ class TestOAuthRequestToken(TestController):
         client_secret='test-client-secret',
     )
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         dummy_oauths()
 
     def test_request_token_valid(self):
@@ -2034,7 +2024,7 @@ class TestOAuthRequestToken(TestController):
         r.mustcontain('oauth_token=')
         r.mustcontain('oauth_token_secret=')
         request_token = M.OAuthRequestToken.query.get(consumer_token_id=consumer_token._id)
-        assert_is_not_none(request_token)
+        assert request_token is not None
 
     def test_request_token_no_consumer_token_matching(self):
         self.app.post(*oauth1_webtest('/rest/oauth/request_token', self.oauth_params), status=401)
@@ -2069,8 +2059,8 @@ class TestOAuthAccessToken(TestController):
         verifier='good_verifier_123456',
     )
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         dummy_oauths()
 
     def test_access_token_no_consumer(self):
diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py
index 8adb1a1af..3c0ed26ef 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -17,7 +17,7 @@
 
 import os
 from mock import patch
-from alluratest.tools import assert_in, assert_not_in, assert_equal, assert_false, assert_true, assert_raises
+import pytest
 from webtest.app import AppError
 from ming.odm import session
 
diff --git a/Allura/allura/tests/model/test_artifact.py b/Allura/allura/tests/model/test_artifact.py
index 8f0100e82..940ae0cac 100644
--- a/Allura/allura/tests/model/test_artifact.py
+++ b/Allura/allura/tests/model/test_artifact.py
@@ -22,8 +22,9 @@ import re
 from datetime import datetime
 
 from tg import tmpl_context as c
-from alluratest.tools import with_setup, assert_raises, assert_equal
+from alluratest.tools import with_setup
 from mock import patch
+import pytest
 from ming.orm.ormsession import ThreadLocalORMSession
 from ming.orm import Mapper
 from bson import ObjectId
@@ -54,15 +55,16 @@ class Checkmessage(M.Message):
 Mapper.compile_all()
 
 
-# def setup_method_wrapper(fn):
-#     fn(None)
-
 def setup_method():
     setup_basic_test()
     setup_unit_test()
     setup_with_tools()
 
 
+def teardown_module():
+    ThreadLocalORMSession.close_all()
+
+
 @td.with_wiki
 def setup_with_tools():
     h.set_context('test', 'wiki', neighborhood='Projects')
@@ -75,10 +77,6 @@ def setup_with_tools():
     Checkmessage.app_config = c.app.config
 
 
-def tearDown():
-    ThreadLocalORMSession.close_all()
-
-
 @with_setup(setup_method)
 def test_artifact():
     pg = WM.Page(title='TestPage1')
@@ -182,7 +180,7 @@ def test_versioning():
     assert ss.shorthand_id() == pg.shorthand_id() + '#2'
     assert ss.title == pg.title
     assert ss.text == pg.text
-    assert_raises(IndexError, pg.get_version, 42)
+    pytest.raises(IndexError, pg.get_version, 42)
     pg.revert(1)
     pg.commit()
     ThreadLocalORMSession.flush_all()
diff --git a/Allura/allura/tests/model/test_discussion.py b/Allura/allura/tests/model/test_discussion.py
index 1bfc445b9..d5abf1a05 100644
--- a/Allura/allura/tests/model/test_discussion.py
+++ b/Allura/allura/tests/model/test_discussion.py
@@ -49,7 +49,7 @@ def setup_method():
     ThreadLocalORMSession.close_all()
 
 
-def tearDown():
+def teardown_module():
     ThreadLocalORMSession.close_all()
 
 
@@ -209,7 +209,7 @@ def test_attachment_methods():
         n.text)
 
 
-@with_setup(setup_method, tearDown())
+@with_setup(setup_method)
 def test_multiple_attachments():
     test_file1 = FieldStorage()
     test_file1.name = 'file_info'
diff --git a/Allura/allura/tests/test_app.py b/Allura/allura/tests/test_app.py
index 024c12fd3..71733a2d5 100644
--- a/Allura/allura/tests/test_app.py
+++ b/Allura/allura/tests/test_app.py
@@ -18,7 +18,7 @@
 from tg import tmpl_context as c
 import mock
 from ming.base import Object
-from alluratest.tools import assert_raises
+import pytest
 from formencode import validators as fev
 
 from alluratest.controller import setup_unit_test
@@ -73,8 +73,8 @@ def test_config_option_with_validator():
     v = fev.NotEmpty()
     opt = app.ConfigOption('test1', str, None, validator=v)
     assert opt.validate('val') == 'val'
-    assert_raises(fev.Invalid, opt.validate, None)
-    assert_raises(fev.Invalid, opt.validate, '')
+    pytest.raises(fev.Invalid, opt.validate, None)
+    pytest.raises(fev.Invalid, opt.validate, '')
 
 
 @with_setup(setup_method)
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index f7440e3a1..18026cc5f 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, with_setup
+from alluratest.tools import with_setup
 from testfixtures import OutputCapture
 
 from datadiff.tools import assert_equal
@@ -27,6 +27,7 @@ from datadiff.tools import assert_equal
 from ming.base import Object
 from ming.orm import ThreadLocalORMSession
 from mock import Mock, call, patch
+import pytest
 import pymongo
 import pkg_resources
 
@@ -47,22 +48,21 @@ class EmptyClass:
     pass
 
 
-def setup_method():
+def setup_module():
     """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(
         [test_config, pkg_resources.resource_filename('allura', 'tests/tscript.py')])
-    assert_raises(ValueError, cmd.run,
+    pytest.raises(ValueError, cmd.run,
                   [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
@@ -80,13 +80,12 @@ def test_set_neighborhood_max_projects():
     assert neighborhood.features['max_projects'] is None
 
     # check validation
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [test_config, str(n_id), 'max_projects', 'string'])
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [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
@@ -104,15 +103,14 @@ def test_set_neighborhood_private():
     assert not neighborhood.features['private_projects']
 
     # check validation
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [test_config, str(n_id), 'private_projects', 'string'])
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [test_config, str(n_id), 'private_projects', '1'])
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [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
@@ -130,15 +128,14 @@ def test_set_neighborhood_google_analytics():
     assert not neighborhood.features['google_analytics']
 
     # check validation
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [test_config, str(n_id), 'google_analytics', 'string'])
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [test_config, str(n_id), 'google_analytics', '1'])
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [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
@@ -161,19 +158,18 @@ def test_set_neighborhood_css():
     assert neighborhood.features['css'] == 'custom'
 
     # check validation
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [test_config, str(n_id), 'css', 'string'])
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [test_config, str(n_id), 'css', '1'])
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [test_config, str(n_id), 'css', '2.8'])
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [test_config, str(n_id), 'css', 'None'])
-    assert_raises(InvalidNBFeatureValueError, cmd.run,
+    pytest.raises(InvalidNBFeatureValueError, cmd.run,
                   [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'])
@@ -363,7 +359,7 @@ class TestTaskdCleanupCommand:
         self.cmd_class._complete_suspicious_tasks = lambda x: []
 
     def teardown_method(self, method):
-        # need to clean up setUp mocking for unit tests below to work properly
+        # need to clean up setup_method 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
         self.cmd_class._busy_tasks = self.old_busy_tasks
@@ -509,9 +505,6 @@ 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_helpers.py b/Allura/allura/tests/test_helpers.py
index 6e03cb8c5..737e35f2e 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -23,12 +23,13 @@ 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, with_setup
+from alluratest.tools import module_not_available, with_setup
 from datadiff import tools as dd
 from webob import Request
 from webob.exc import HTTPUnauthorized
 from ming.orm import ThreadLocalORMSession
 from markupsafe import Markup
+import pytest
 
 from allura import model as M
 from allura.lib import exceptions as exc
@@ -42,7 +43,7 @@ from alluratest.controller import setup_basic_test
 import six
 
 
-def setup_method():
+def setup_module():
     """Method called by nose before running each test"""
     setup_basic_test()
 
@@ -118,7 +119,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,14 +128,12 @@ 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')
@@ -167,7 +166,6 @@ 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')
@@ -262,7 +260,6 @@ 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(
@@ -270,7 +267,6 @@ 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')) ==
@@ -280,7 +276,6 @@ 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()) ==
@@ -312,7 +307,6 @@ 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']
@@ -328,7 +322,6 @@ 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')
@@ -523,7 +516,6 @@ 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'
 
@@ -534,7 +526,6 @@ 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():
@@ -601,7 +592,6 @@ 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'
@@ -665,24 +655,24 @@ class TestRateLimit(TestCase):
 
             start_date = now - timedelta(seconds=30)
             h.rate_limit(self.key_comment, 0, start_date)
-            with assert_raises(exc.RatelimitError):
+            with pytest.raises(exc.RatelimitError):
                 h.rate_limit(self.key_comment, 1, start_date)
 
             start_date = now - timedelta(seconds=61)
             h.rate_limit(self.key_comment, 1, start_date)
             h.rate_limit(self.key_comment, 2, start_date)
-            with assert_raises(exc.RatelimitError):
+            with pytest.raises(exc.RatelimitError):
                 h.rate_limit(self.key_comment, 3, start_date)
 
             start_date = now - timedelta(seconds=86301)
             h.rate_limit(self.key_comment, 19, start_date)
-            with assert_raises(exc.RatelimitError):
+            with pytest.raises(exc.RatelimitError):
                 h.rate_limit(self.key_comment, 20, start_date)
 
             start_date = now - timedelta(seconds=86401)
             h.rate_limit(self.key_comment, 21, start_date)
             h.rate_limit(self.key_comment, 49, start_date)
-            with assert_raises(exc.RatelimitError):
+            with pytest.raises(exc.RatelimitError):
                 h.rate_limit(self.key_comment, 50, start_date)
 
 
diff --git a/Allura/allura/tests/test_mail_util.py b/Allura/allura/tests/test_mail_util.py
index 33e80bfb0..acc4c22dd 100644
--- a/Allura/allura/tests/test_mail_util.py
+++ b/Allura/allura/tests/test_mail_util.py
@@ -20,7 +20,7 @@ from six.moves.email_mime_multipart import MIMEMultipart
 from six.moves.email_mime_text import MIMEText
 
 import mock
-from alluratest.tools import raises, assert_equal, assert_false, assert_true, assert_in
+import pytest
 from ming.orm import ThreadLocalORMSession
 from tg import config as tg_config
 
@@ -39,7 +39,7 @@ from allura.lib.mail_util import (
 from allura.lib.exceptions import AddressException
 from alluratest.pytest_helpers import with_nose_compatibility
 from allura.tests import decorators as td
-import six
+
 
 config = ConfigProxy(
     common_suffix='forgemail.domain',
@@ -55,9 +55,9 @@ class TestReactor(unittest.TestCase):
         ThreadLocalORMSession.flush_all()
         ThreadLocalORMSession.close_all()
 
-    @raises(AddressException)
     def test_parse_address_bad_domain(self):
-        parse_address('foo@bar.com')
+        with pytest.raises(AddressException):
+            parse_address('foo@bar.com')
 
     @td.with_wiki
     @mock.patch.dict(tg_config, {'forgemail.domain.alternates': '.secondary.com .tertiary.com'})
@@ -65,17 +65,17 @@ class TestReactor(unittest.TestCase):
         parse_address('foo@wiki.test.p.secondary.com')
         parse_address('foo@wiki.test.p.tertiary.com')
 
-    @raises(AddressException)
     def test_parse_address_bad_project(self):
-        parse_address('foo@wiki.unicorns.p' + config.common_suffix)
+        with pytest.raises(AddressException):
+            parse_address('foo@wiki.unicorns.p' + config.common_suffix)
 
-    @raises(AddressException)
     def test_parse_address_missing_tool(self):
-        parse_address('foo@test.p' + config.common_suffix)
+        with pytest.raises(AddressException):
+            parse_address('foo@test.p' + config.common_suffix)
 
-    @raises(AddressException)
     def test_parse_address_bad_tool(self):
-        parse_address('foo@hammer.test.p' + config.common_suffix)
+        with pytest.raises(AddressException):
+            parse_address('foo@hammer.test.p' + config.common_suffix)
 
     @td.with_wiki
     def test_parse_address_good(self):
@@ -214,10 +214,10 @@ Content-Type: text/html; charset="utf-8"
 @with_nose_compatibility
 class TestHeader:
 
-    @raises(TypeError)
     def test_bytestring(self):
-        our_header = Header(b'[asdf2:wiki] Discussion for Home page')
-        assert our_header.encode() == '[asdf2:wiki] Discussion for Home page'
+        with pytest.raises(TypeError):
+            our_header = Header(b'[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')
diff --git a/Allura/allura/tests/test_multifactor.py b/Allura/allura/tests/test_multifactor.py
index ba7780f3b..3ce6fdb31 100644
--- a/Allura/allura/tests/test_multifactor.py
+++ b/Allura/allura/tests/test_multifactor.py
@@ -23,7 +23,7 @@ from paste.deploy.converters import asint
 import ming
 from cryptography.hazmat.primitives.twofactor import InvalidToken
 from mock import patch, Mock
-from alluratest.tools import assert_equal, assert_raises
+import pytest
 from tg import config
 
 from allura import model as M
@@ -112,11 +112,11 @@ class TestTotpService:
         srv.verify(totp, '283397', None)
 
         time.return_value = self.sample_time + 60
-        with assert_raises(InvalidToken):
+        with pytest.raises(InvalidToken):
             srv.verify(totp, '283397', None)
 
         time.return_value = self.sample_time - 30
-        with assert_raises(InvalidToken):
+        with pytest.raises(InvalidToken):
             srv.verify(totp, '283397', None)
 
     def test_get_qr_code(self):
@@ -166,12 +166,12 @@ class TestAnyTotpServiceImplementation:
         totp = srv.Totp(key=self.sample_key)
 
         # 4th attempt (good or bad) will trip over the default limit of 3 in 30s
-        with assert_raises(InvalidToken):
+        with pytest.raises(InvalidToken):
             srv.verify(totp, '34dfvdasf', user)
-        with assert_raises(InvalidToken):
+        with pytest.raises(InvalidToken):
             srv.verify(totp, '234asdfsadf', user)
         srv.verify(totp, '283397', user)
-        with assert_raises(MultifactorRateLimitError):
+        with pytest.raises(MultifactorRateLimitError):
             srv.verify(totp, '283397', user)
 
 
@@ -269,9 +269,9 @@ class TestAnyRecoveryCodeServiceImplementation:
     def test_verify_fail(self):
         recovery = self.Service()
         user = self.mock_user()
-        with assert_raises(InvalidRecoveryCode):
+        with pytest.raises(InvalidRecoveryCode):
             recovery.verify_and_remove_code(user, '11111')
-        with assert_raises(InvalidRecoveryCode):
+        with pytest.raises(InvalidRecoveryCode):
             recovery.verify_and_remove_code(user, '')
 
     def test_verify_and_remove_code(self):
@@ -296,12 +296,12 @@ class TestAnyRecoveryCodeServiceImplementation:
         recovery.replace_codes(user, codes)
 
         # 4th attempt (good or bad) will trip over the default limit of 3 in 30s
-        with assert_raises(InvalidRecoveryCode):
+        with pytest.raises(InvalidRecoveryCode):
             recovery.verify_and_remove_code(user, '13485u0233')
-        with assert_raises(InvalidRecoveryCode):
+        with pytest.raises(InvalidRecoveryCode):
             recovery.verify_and_remove_code(user, '34123rdxafs')
         recovery.verify_and_remove_code(user, '11111')
-        with assert_raises(MultifactorRateLimitError):
+        with pytest.raises(MultifactorRateLimitError):
             recovery.verify_and_remove_code(user, '22222')
 
 
@@ -345,5 +345,5 @@ class TestGoogleAuthenticatorPamFilesystemRecoveryCodeService(TestAnyRecoveryCod
         GoogleAuthenticatorPamFilesystemTotpService().set_secret_key(self.mock_user(), None)
 
         # then it errors because no .google-authenticator file
-        with assert_raises(IOError):
+        with pytest.raises(IOError):
             super().test_replace_codes()
diff --git a/Allura/allura/tests/test_patches.py b/Allura/allura/tests/test_patches.py
index 0beb9ce96..b66957c6f 100644
--- a/Allura/allura/tests/test_patches.py
+++ b/Allura/allura/tests/test_patches.py
@@ -17,10 +17,7 @@
 
 import webob
 from mock import patch
-from alluratest.tools import (
-    assert_equal,
-    assert_raises,
-)
+import pytest
 import tg
 
 from allura.lib import patches
@@ -33,9 +30,9 @@ def empty_func():
 @patch.object(patches, 'request', webob.Request.blank('/foo/bar'))
 def test_with_trailing_slash():
     patches.apply()
-    with assert_raises(webob.exc.HTTPMovedPermanently) as raised:
+    with pytest.raises(webob.exc.HTTPMovedPermanently) as raised:
         tg.decorators.with_trailing_slash(empty_func)()
-    assert raised.exception.location == 'http://localhost/foo/bar/'
+    assert raised.value.location == 'http://localhost/foo/bar/'
 
 
 @patch.object(patches, 'request', webob.Request.blank('/foo/bar/?a=b'))
@@ -48,17 +45,17 @@ def test_with_trailing_slash_ok():
 @patch.object(patches, 'request', webob.Request.blank('/foo/bar?foo=bar&baz=bam'))
 def test_with_trailing_slash_qs():
     patches.apply()
-    with assert_raises(webob.exc.HTTPMovedPermanently) as raised:
+    with pytest.raises(webob.exc.HTTPMovedPermanently) as raised:
         tg.decorators.with_trailing_slash(empty_func)()
-    assert raised.exception.location == 'http://localhost/foo/bar/?foo=bar&baz=bam'
+    assert raised.value.location == 'http://localhost/foo/bar/?foo=bar&baz=bam'
 
 
 @patch.object(patches, 'request', webob.Request.blank('/foo/bar/'))
 def test_without_trailing_slash():
     patches.apply()
-    with assert_raises(webob.exc.HTTPMovedPermanently) as raised:
+    with pytest.raises(webob.exc.HTTPMovedPermanently) as raised:
         tg.decorators.without_trailing_slash(empty_func)()
-    assert raised.exception.location == 'http://localhost/foo/bar'
+    assert raised.value.location == 'http://localhost/foo/bar'
 
 
 @patch.object(patches, 'request', webob.Request.blank('/foo/bar?a=b'))
@@ -71,6 +68,6 @@ def test_without_trailing_slash_ok():
 @patch.object(patches, 'request', webob.Request.blank('/foo/bar/?foo=bar&baz=bam'))
 def test_without_trailing_slash_qs():
     patches.apply()
-    with assert_raises(webob.exc.HTTPMovedPermanently) as raised:
+    with pytest.raises(webob.exc.HTTPMovedPermanently) as raised:
         tg.decorators.without_trailing_slash(empty_func)()
-    assert raised.exception.location == 'http://localhost/foo/bar?foo=bar&baz=bam'
+    assert raised.value.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 cfe441693..49206ba64 100644
--- a/Allura/allura/tests/test_plugin.py
+++ b/Allura/allura/tests/test_plugin.py
@@ -23,16 +23,8 @@ from tg import tmpl_context as c
 from webob import Request, exc
 from bson import ObjectId
 from ming.orm.ormsession import ThreadLocalORMSession
-from alluratest.tools import (
-    assert_equals,
-    assert_equal,
-    assert_raises,
-    assert_is_none,
-    assert_is,
-    assert_true,
-    assert_false,
-)
 from mock import Mock, MagicMock, patch
+import pytest
 
 from allura import model as M
 from allura.lib import plugin
@@ -78,16 +70,16 @@ class TestProjectRegistrationProvider:
         v = self.provider.shortname_validator.to_python
 
         v('thisislegit', neighborhood=nbhd)
-        assert_raises(ProjectShortnameInvalid, v,
+        pytest.raises(ProjectShortnameInvalid, v,
                       'not valid', neighborhood=nbhd)
-        assert_raises(ProjectShortnameInvalid, v,
+        pytest.raises(ProjectShortnameInvalid, v,
                       'this-is-valid-but-too-long', neighborhood=nbhd)
-        assert_raises(ProjectShortnameInvalid, v,
+        pytest.raises(ProjectShortnameInvalid, v,
                       'this is invalid and too long', neighborhood=nbhd)
-        assert_raises(ProjectShortnameInvalid, v,
+        pytest.raises(ProjectShortnameInvalid, v,
                       'end-dash-', neighborhood=nbhd)
         Project.query.get.return_value = Mock()
-        assert_raises(ProjectConflict, v, 'thisislegit', neighborhood=nbhd)
+        pytest.raises(ProjectConflict, v, 'thisislegit', neighborhood=nbhd)
 
 
 @with_nose_compatibility
@@ -652,7 +644,7 @@ class TestLocalAuthenticationProvider:
         user.__ming__ = Mock()
         self.provider.validate_password = lambda u, p: False
         self.provider._encode_password = Mock()
-        assert_raises(
+        pytest.raises(
             exc.HTTPUnauthorized,
             self.provider.set_password, user, 'old', 'new')
         assert self.provider._encode_password.call_count == 0
diff --git a/Allura/allura/tests/test_security.py b/Allura/allura/tests/test_security.py
index f6771f3aa..fea9bc54d 100644
--- a/Allura/allura/tests/test_security.py
+++ b/Allura/allura/tests/test_security.py
@@ -16,7 +16,7 @@
 #       under the License.
 
 from tg import tmpl_context as c
-from alluratest.tools import assert_equal, assert_raises
+import pytest
 
 from ming.odm import ThreadLocalODMSession
 from allura.tests import decorators as td
@@ -51,7 +51,7 @@ def _add_to_group(user, role):
 
 @patch('allura.lib.security.requests.get', side_effect=Timeout())
 def test_check_breached_password(r_get):
-    with assert_raises(HIBPClientError):
+    with pytest.raises(HIBPClientError):
         HIBPClient.check_breached_password('qwerty')
 
 
diff --git a/Allura/allura/tests/test_utils.py b/Allura/allura/tests/test_utils.py
index 4c22c1a93..5afc2c9b8 100644
--- a/Allura/allura/tests/test_utils.py
+++ b/Allura/allura/tests/test_utils.py
@@ -27,14 +27,9 @@ from ming.odm import session
 from bson import ObjectId
 from webob import Request
 from mock import Mock, patch
-from alluratest.tools import (
-    assert_equal,
-    assert_not_equal,
-    assert_raises,
-    assert_in,
-)
 from pygments import highlight
 from pygments.lexers import get_lexer_for_filename
+import pytest
 from tg import config
 import html5lib
 import html5lib.treewalkers
@@ -363,9 +358,9 @@ def test_empty_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)
+    pytest.raises(ValueError, cursor.one)
+    pytest.raises(StopIteration, cursor.next)
+    pytest.raises(StopIteration, cursor._next_impl)
 
 
 def test_DateJSONEncoder():
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index 3b11f5e05..b2958d7d2 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -21,12 +21,7 @@ import hashlib
 import datetime as dt
 
 from mock import Mock, MagicMock, patch, call
-from alluratest.tools import (
-    assert_raises,
-    assert_equal,
-    assert_not_in,
-    assert_in,
-)
+import pytest
 from datadiff import tools as dd
 from formencode import Invalid
 from ming.odm import session
@@ -100,12 +95,12 @@ class TestValidators(TestWebhookBase):
         app = self.git
         invalid_app = self.project.app_instance('src2')
         v = WebhookValidator(sender=sender, app=app, not_empty=True)
-        with assert_raises(Invalid) as cm:
+        with pytest.raises(Invalid) as cm:
             v.to_python(None)
-        assert cm.exception.msg == 'Please enter a value'
-        with assert_raises(Invalid) as cm:
+        assert cm.value.msg == 'Please enter a value'
+        with pytest.raises(Invalid) as cm:
             v.to_python('invalid id')
-        assert cm.exception.msg == 'Invalid webhook'
+        assert cm.value.msg == 'Invalid webhook'
 
         wh = M.Webhook(type='invalid type',
                        app_config_id=invalid_app.config._id,
@@ -113,16 +108,16 @@ class TestValidators(TestWebhookBase):
                        secret='secret')
         session(wh).flush(wh)
         # invalid type
-        with assert_raises(Invalid) as cm:
+        with pytest.raises(Invalid) as cm:
             v.to_python(wh._id)
-        assert cm.exception.msg == 'Invalid webhook'
+        assert cm.value.msg == 'Invalid webhook'
 
         wh.type = 'repo-push'
         session(wh).flush(wh)
         # invalild app
-        with assert_raises(Invalid) as cm:
+        with pytest.raises(Invalid) as cm:
             v.to_python(wh._id)
-        assert cm.exception.msg == 'Invalid webhook'
+        assert cm.value.msg == 'Invalid webhook'
 
         wh.app_config_id = app.config._id
         session(wh).flush(wh)
diff --git a/Allura/allura/tests/unit/spam/test_akismet.py b/Allura/allura/tests/unit/spam/test_akismet.py
index 2c6e2af31..384ce1b32 100644
--- a/Allura/allura/tests/unit/spam/test_akismet.py
+++ b/Allura/allura/tests/unit/spam/test_akismet.py
@@ -34,7 +34,7 @@ from alluratest.pytest_helpers import with_nose_compatibility
 class TestAkismet(unittest.TestCase):
 
     @mock.patch('allura.lib.spam.akismetfilter.akismet')
-    def setUp(self, akismet_lib):
+    def setup_method(self, method, akismet_lib):
         self.akismet = AkismetSpamFilter({'spam.key': 'example', 'base_url': 'http://localhost/'})
 
         def side_effect(*args, **kw):
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 9e6999231..68b8898e4 100644
--- a/Allura/allura/tests/unit/test_helpers/test_set_context.py
+++ b/Allura/allura/tests/unit/test_helpers/test_set_context.py
@@ -15,7 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-from alluratest.tools import assert_raises
+import pytest
 from tg import tmpl_context as c
 from bson import ObjectId
 
@@ -101,14 +101,14 @@ class TestWhenProjectIsNotFound(WithDatabase):
 
     def test_that_it_raises_an_exception(self):
         nbhd = create_neighborhood()
-        assert_raises(NoSuchProjectError,
+        pytest.raises(NoSuchProjectError,
                       set_context,
                       'myproject',
                       neighborhood=nbhd)
 
     def test_proper_exception_when_id_lookup(self):
         create_neighborhood()
-        assert_raises(NoSuchProjectError,
+        pytest.raises(NoSuchProjectError,
                       set_context,
                       ObjectId(),
                       neighborhood=None)
@@ -118,7 +118,7 @@ class TestWhenProjectIsNotFound(WithDatabase):
 class TestWhenNeighborhoodIsNotFound(WithDatabase):
 
     def test_that_it_raises_an_exception(self):
-        assert_raises(NoSuchNeighborhoodError,
+        pytest.raises(NoSuchNeighborhoodError,
                       set_context,
                       'myproject',
                       neighborhood='myneighborhood')
diff --git a/Allura/allura/tests/unit/test_package_path_loader.py b/Allura/allura/tests/unit/test_package_path_loader.py
index 5aec5fea2..5e3702715 100644
--- a/Allura/allura/tests/unit/test_package_path_loader.py
+++ b/Allura/allura/tests/unit/test_package_path_loader.py
@@ -20,8 +20,8 @@ from collections import OrderedDict
 from unittest import TestCase
 
 import jinja2
-from alluratest.tools import assert_equal, assert_raises
 import mock
+import pytest
 from tg import config
 
 from allura.lib.package_path_loader import PackagePathLoader
@@ -81,7 +81,7 @@ class TestPackagePathLoader(TestCase):
         for ep in eps:
             ep.name = ep.ep_name
             ep.load.return_value.template_path_rules = ep.rules
-        assert_raises(jinja2.TemplateError, PackagePathLoader()._load_rules)
+        pytest.raises(jinja2.TemplateError, PackagePathLoader()._load_rules)
 
     def test_replace_signposts(self):
         ppl = PackagePathLoader()
@@ -219,14 +219,14 @@ class TestPackagePathLoader(TestCase):
         ppl.init_paths = mock.Mock()
         fs_loader().get_source.side_effect = jinja2.TemplateNotFound('test')
 
-        assert_raises(
+        pytest.raises(
             jinja2.TemplateError,
             ppl.get_source, 'env', 'allura.ext.admin:templates/audit.html')
         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(
+            pytest.raises(
                 jinja2.TemplateError,
                 ppl.get_source, 'env', 'allura.ext.admin:templates/audit.html')
             assert fs_loader().get_source.call_count == 2
diff --git a/ForgeActivity/forgeactivity/tests/functional/test_rest.py b/ForgeActivity/forgeactivity/tests/functional/test_rest.py
index d20166d5f..5ee7b6b0b 100644
--- a/ForgeActivity/forgeactivity/tests/functional/test_rest.py
+++ b/ForgeActivity/forgeactivity/tests/functional/test_rest.py
@@ -23,13 +23,13 @@ from alluratest.controller import TestRestApiBase
 
 class TestActivityHasAccessAPI(TestRestApiBase):
 
-    def setUp(self, *args, **kwargs):
-        super().setUp(*args, **kwargs)
+    def setup_method(self, method, *args, **kwargs):
+        super().setup_method(method, *args, **kwargs)
         self._enabled = config.get('activitystream.enabled', 'false')
         config['activitystream.enabled'] = 'true'
 
-    def tearDown(self, *args, **kwargs):
-        super().tearDown(*args, **kwargs)
+    def teardown_method(self, method, *args, **kwargs):
+        super().teardown_method(method, *args, **kwargs)
         config['activitystream.enabled'] = self._enabled
 
     def test_has_access_no_params(self):
diff --git a/ForgeActivity/forgeactivity/tests/functional/test_root.py b/ForgeActivity/forgeactivity/tests/functional/test_root.py
index cb90360d1..565576e05 100644
--- a/ForgeActivity/forgeactivity/tests/functional/test_root.py
+++ b/ForgeActivity/forgeactivity/tests/functional/test_root.py
@@ -33,13 +33,13 @@ from allura.tests import decorators as td
 
 class TestActivityController(TestController):
 
-    def setUp(self, *args, **kwargs):
-        super().setUp(*args, **kwargs)
+    def setup_method(self, method, *args, **kwargs):
+        super().setup_method(method, *args, **kwargs)
         self._enabled = config.get('activitystream.enabled', 'false')
         config['activitystream.enabled'] = 'true'
 
-    def tearDown(self, *args, **kwargs):
-        super().tearDown(*args, **kwargs)
+    def teardown_method(self, method, *args, **kwargs):
+        super().teardown_method(method, *args, **kwargs)
         config['activitystream.enabled'] = self._enabled
 
     def test_index(self):
diff --git a/ForgeBlog/forgeblog/tests/functional/test_rest.py b/ForgeBlog/forgeblog/tests/functional/test_rest.py
index 9e8a87756..068a5e911 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_rest.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_rest.py
@@ -29,8 +29,8 @@ from forgeblog import model as BM
 
 class TestBlogApi(TestRestApiBase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @td.with_tool('test', 'Blog', 'blog')
diff --git a/ForgeBlog/forgeblog/tests/test_app.py b/ForgeBlog/forgeblog/tests/test_app.py
index bb224a057..c61b0a0e8 100644
--- a/ForgeBlog/forgeblog/tests/test_app.py
+++ b/ForgeBlog/forgeblog/tests/test_app.py
@@ -34,7 +34,7 @@ from forgeblog import model as BM
 
 class TestApp:
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
 
     @td.with_tool('test', 'Blog', 'blog')
@@ -75,7 +75,7 @@ class TestApp:
 
 class TestBulkExport:
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         setup_global_objects()
 
diff --git a/ForgeBlog/forgeblog/tests/test_commands.py b/ForgeBlog/forgeblog/tests/test_commands.py
index b1aa45196..3b82d6801 100644
--- a/ForgeBlog/forgeblog/tests/test_commands.py
+++ b/ForgeBlog/forgeblog/tests/test_commands.py
@@ -34,7 +34,7 @@ test_config = pkg_resources.resource_filename(
     'allura', '../test.ini') + '#main'
 
 
-def setUp():
+def setup_module(module):
     setup_basic_test()
     setup_global_objects()
 
diff --git a/ForgeBlog/forgeblog/tests/test_roles.py b/ForgeBlog/forgeblog/tests/test_roles.py
index 3fe6a64df..18403dce9 100644
--- a/ForgeBlog/forgeblog/tests/test_roles.py
+++ b/ForgeBlog/forgeblog/tests/test_roles.py
@@ -23,7 +23,7 @@ from allura.lib import security
 from allura.lib import helpers as h
 
 
-def setUp():
+def setup_module(module):
     setup_basic_test()
     setup_global_objects()
     h.set_context('test', neighborhood='Projects')
diff --git a/ForgeBlog/forgeblog/tests/unit/__init__.py b/ForgeBlog/forgeblog/tests/unit/__init__.py
index 202091598..648c054e8 100644
--- a/ForgeBlog/forgeblog/tests/unit/__init__.py
+++ b/ForgeBlog/forgeblog/tests/unit/__init__.py
@@ -31,7 +31,7 @@ def setUp():
 
 class BlogTestWithModel:
 
-    def setUp(self):
+    def setup_method(self, method):
         bootstrap.wipe_database()
         project_reg = plugin.ProjectRegistrationProvider.get()
         c.user = bootstrap.create_user('Test User')
@@ -46,5 +46,5 @@ class BlogTestWithModel:
         ThreadLocalORMSession.flush_all()
         h.set_context('test', 'blog', neighborhood='Projects')
 
-    def tearDown(self):
+    def teardown_method(self, method):
         ThreadLocalORMSession.close_all()
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
index a20cb16e9..cfc2f5c84 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 import mock
+import pytest
 import random
 import logging
 from six.moves.email_mime_text import MIMEText
@@ -30,7 +31,6 @@ from ming.odm import ThreadLocalORMSession
 from tg import tmpl_context as c
 from tg import config
 
-from alluratest.tools import assert_equal, assert_in, assert_not_in, assert_true, assert_false, assert_raises
 import feedparser
 
 from allura import model as M
@@ -45,8 +45,8 @@ log = logging.getLogger(__name__)
 
 
 class TestForumEmail(TestController):
-    def setUp(self):
-        TestController.setUp(self)
+    def setup_method(self, method):
+        super().setup_method(method)
         c.user = M.User.by_username('test-admin')
         self.app.get('/discussion/')
         r = self.app.get('/admin/discussion/forums')
@@ -136,8 +136,8 @@ class TestForumMessageHandling(TestController):
     Tests all the "handle_message" related logic, which is what inbound emails run through
     '''
 
-    def setUp(self):
-        TestController.setUp(self)
+    def setup_method(self, method):
+        super().setup_method(method)
         self.app.get('/discussion/')
         r = self.app.get('/admin/discussion/forums')
         form = r.forms['add-forum']
@@ -302,8 +302,8 @@ class TestForumMessageHandling(TestController):
 
 
 class TestForum(TestController):
-    def setUp(self):
-        TestController.setUp(self)
+    def setup_method(self, method):
+        super().setup_method(method)
         self.app.get('/discussion/')
         r = self.app.get('/admin/discussion/forums')
         form = r.forms['add-forum']
@@ -496,7 +496,7 @@ class TestForum(TestController):
             self.test_posting()
 
             # second should fail
-            with assert_raises(Exception):
+            with pytest.raises(Exception):
                 self.test_posting()
 
     def test_notifications_escaping(self):
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py
index 19babaca5..22fea58a3 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py
@@ -29,8 +29,8 @@ log = logging.getLogger(__name__)
 
 class TestForumAdmin(TestController):
 
-    def setUp(self):
-        TestController.setUp(self)
+    def setup_method(self, method):
+        super().setup_method(method)
         self.app.get('/discussion/')
 
     def test_forum_CRUD(self):
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_import.py b/ForgeDiscussion/forgediscussion/tests/functional/test_import.py
index ff7449d1e..1219d2a87 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_import.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_import.py
@@ -31,8 +31,8 @@ from alluratest.controller import TestRestApiBase
 
 class TestImportController(TestRestApiBase):  # TestController):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         here_dir = os.path.dirname(__file__)
         self.app.get('/discussion/')
         self.json_text = open(here_dir + '/data/sf.json').read()
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
index b20d610a2..cebce6467 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
@@ -15,8 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-from alluratest.tools import assert_equal, assert_in
-
 from allura.lib import helpers as h
 from allura.tests import decorators as td
 from allura import model as M
@@ -27,8 +25,8 @@ from ming.orm import ThreadLocalORMSession
 
 class TestDiscussionApiBase(TestRestApiBase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @td.with_discussion
diff --git a/ForgeDiscussion/forgediscussion/tests/test_app.py b/ForgeDiscussion/forgediscussion/tests/test_app.py
index bb0b8d64b..0bf49aeca 100644
--- a/ForgeDiscussion/forgediscussion/tests/test_app.py
+++ b/ForgeDiscussion/forgediscussion/tests/test_app.py
@@ -53,7 +53,7 @@ class TestApp(TestDiscussionApiBase):  # creates some sample data
 
 class TestAppSitemap:
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         setup_unit_test()
         self.user = M.User.query.get(username='root')
diff --git a/ForgeDiscussion/forgediscussion/tests/test_forum_roles.py b/ForgeDiscussion/forgediscussion/tests/test_forum_roles.py
index 4bc0eef66..f05c6d993 100644
--- a/ForgeDiscussion/forgediscussion/tests/test_forum_roles.py
+++ b/ForgeDiscussion/forgediscussion/tests/test_forum_roles.py
@@ -23,7 +23,7 @@ from allura.lib import security
 from allura.tests import decorators as td
 
 
-def setUp():
+def setup_module(module):
     setup_basic_test()
     setup_global_objects()
 
diff --git a/ForgeFeedback/forgefeedback/tests/functional/test_root.py b/ForgeFeedback/forgefeedback/tests/functional/test_root.py
index 37866f797..69243f0d5 100644
--- a/ForgeFeedback/forgefeedback/tests/functional/test_root.py
+++ b/ForgeFeedback/forgefeedback/tests/functional/test_root.py
@@ -17,9 +17,6 @@
 from tg import tmpl_context as c
 from tg import config
 
-from alluratest.tools import assert_equal, assert_in, assert_not_in
-from alluratest.tools import assert_true, assert_false, assert_raises
-
 from allura import model as M
 from alluratest.controller import TestController
 from allura.lib import helpers as h
@@ -30,9 +27,6 @@ from forgefeedback import model as FM
 
 class TestFeedback(TestController):
 
-    def setUp(self):
-        TestController.setUp(self)
-
     def test_feedback(self):
         c.user = M.User.by_username('test-admin')
         self.app.get('/feedback/')
diff --git a/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py b/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py
index 39ba76c70..64b626290 100644
--- a/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py
+++ b/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py
@@ -26,7 +26,7 @@ from allura.tests import decorators as td
 from allura.lib import helpers as h
 
 
-def setUp():
+def setup_module(module):
     setup_basic_test()
     setup_with_tools()
 
diff --git a/ForgeFeedback/forgefeedback/tests/unit/__init__.py b/ForgeFeedback/forgefeedback/tests/unit/__init__.py
index b658fcab8..1bd8052c5 100644
--- a/ForgeFeedback/forgefeedback/tests/unit/__init__.py
+++ b/ForgeFeedback/forgefeedback/tests/unit/__init__.py
@@ -31,7 +31,7 @@ def setUp():
 
 class FeedbackTestWithModel:
 
-    def setUp(self):
+    def setup_method(self, method):
         bootstrap.wipe_database()
         project_reg = plugin.ProjectRegistrationProvider.get()
         c.user = bootstrap.create_user('Test User')
@@ -46,5 +46,5 @@ class FeedbackTestWithModel:
         ThreadLocalORMSession.flush_all()
         h.set_context('test', 'feedback', neighborhood='Projects')
 
-    def tearDown(self):
+    def teardown_method(self, method):
         ThreadLocalORMSession.close_all()
diff --git a/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py b/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py
index d636594a8..f6b2b0ca8 100644
--- a/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py
+++ b/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py
@@ -33,8 +33,8 @@ from forgefeedback import feedback_main
 
 class TestFeedbackApp(FeedbackTestWithModel):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         c.user = User(username='test-user')
         h.set_context('test', 'feedback', neighborhood='Projects')
 
diff --git a/ForgeFiles/forgefiles/tests/functional/test_root.py b/ForgeFiles/forgefiles/tests/functional/test_root.py
index 999fd5367..5c44760f1 100644
--- a/ForgeFiles/forgefiles/tests/functional/test_root.py
+++ b/ForgeFiles/forgefiles/tests/functional/test_root.py
@@ -28,9 +28,6 @@ from testfixtures import TempDirectory
 
 
 class TestFiles(TestController):
-    def setUp(self):
-        TestController.setUp(self)
-
     def test_files(self):
         c.user = M.User.by_username('test-admin')
         r = self.app.get('/files/')
diff --git a/ForgeFiles/forgefiles/tests/model/__init__.py b/ForgeFiles/forgefiles/tests/model/__init__.py
index 9def3d957..72fc7a79b 100644
--- a/ForgeFiles/forgefiles/tests/model/__init__.py
+++ b/ForgeFiles/forgefiles/tests/model/__init__.py
@@ -31,7 +31,7 @@ def setUp():
 
 class FilesTestWithModel:
 
-    def setUp(self):
+    def setup_method(self, method):
         bootstrap.wipe_database()
         project_reg = plugin.ProjectRegistrationProvider.get()
         c.user = bootstrap.create_user('Test User')
@@ -46,6 +46,6 @@ class FilesTestWithModel:
         ThreadLocalORMSession.flush_all()
         h.set_context('test', 'files', neighborhood='Projects')
 
-    def tearDown(self):
+    def teardown_method(self, method):
         ThreadLocalORMSession.close_all()
 
diff --git a/ForgeFiles/forgefiles/tests/test_files_roles.py b/ForgeFiles/forgefiles/tests/test_files_roles.py
index 2675a3e51..6df1ae701 100644
--- a/ForgeFiles/forgefiles/tests/test_files_roles.py
+++ b/ForgeFiles/forgefiles/tests/test_files_roles.py
@@ -25,7 +25,7 @@ from allura.tests import decorators as td
 from allura.lib import helpers as h
 
 
-def setUp():
+def setup_module(module):
     setup_basic_test()
     setup_with_tools()
 
diff --git a/ForgeGit/forgegit/tests/functional/test_controllers.py b/ForgeGit/forgegit/tests/functional/test_controllers.py
index cabeb2b0c..c681d5bbd 100644
--- a/ForgeGit/forgegit/tests/functional/test_controllers.py
+++ b/ForgeGit/forgegit/tests/functional/test_controllers.py
@@ -44,8 +44,8 @@ import six
 
 
 class _TestCase(TestController):
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @with_git
@@ -78,8 +78,8 @@ class _TestCase(TestController):
 
 
 class TestUIController(TestController):
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @with_git
@@ -570,8 +570,8 @@ class TestRestController(_TestCase):
 
 
 class TestHasAccessAPI(TestRestApiBase):
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @with_git
@@ -620,8 +620,8 @@ class TestHasAccessAPI(TestRestApiBase):
 
 
 class TestFork(_TestCase):
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         to_project = M.Project.query.get(
             shortname='test2', neighborhood_id=c.project.neighborhood_id)
         r = self.app.post('/src-git/fork', params=dict(
@@ -969,8 +969,8 @@ class TestFork(_TestCase):
 
 
 class TestDiff(TestController):
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @with_git
@@ -998,8 +998,8 @@ class TestDiff(TestController):
 
 
 class TestGitRename(TestController):
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @with_git
@@ -1066,8 +1066,8 @@ class TestGitRename(TestController):
 
 
 class TestGitBranch(TestController):
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @with_git
@@ -1121,8 +1121,8 @@ class TestGitBranch(TestController):
 
 
 class TestIncludeMacro(_TestCase):
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         setup_global_objects()
 
     def test_parse_repo(self):
diff --git a/ForgeGit/forgegit/tests/model/test_repository.py b/ForgeGit/forgegit/tests/model/test_repository.py
index bb687b91b..7d29d7451 100644
--- a/ForgeGit/forgegit/tests/model/test_repository.py
+++ b/ForgeGit/forgegit/tests/model/test_repository.py
@@ -48,7 +48,7 @@ from forgewiki import model as WM
 
 class TestNewGit(unittest.TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -126,7 +126,7 @@ class TestNewGit(unittest.TestCase):
 
 class TestGitRepo(unittest.TestCase, RepoImplTestBase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -373,7 +373,7 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
         self.assertEqual(new_tree.other_ids, orig_tree.other_ids)
 
     def test_refresh(self):
-        # test results of things that ran during setUp
+        # test results of things that ran during setup_method
         notification = M.Notification.query.find({'subject': '[test:src-git] 5 new commits to Git'}).first()
         assert notification
         domain = '.'.join(reversed(c.app.url[1:-1].split('/'))).replace('_', '-')
@@ -1026,7 +1026,7 @@ class TestGitImplementation(unittest.TestCase):
 
 class TestGitCommit(unittest.TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -1102,7 +1102,7 @@ class TestGitCommit(unittest.TestCase):
 
 class TestGitHtmlView(unittest.TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -1133,7 +1133,7 @@ class TestGitHtmlView(unittest.TestCase):
 
 class TestGitRename(unittest.TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
diff --git a/ForgeGit/forgegit/tests/test_git_app.py b/ForgeGit/forgegit/tests/test_git_app.py
index 823004c40..c4868448a 100644
--- a/ForgeGit/forgegit/tests/test_git_app.py
+++ b/ForgeGit/forgegit/tests/test_git_app.py
@@ -28,7 +28,7 @@ from forgegit.tests import with_git
 
 class TestGitApp(unittest.TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
diff --git a/ForgeGit/forgegit/tests/test_tasks.py b/ForgeGit/forgegit/tests/test_tasks.py
index 7a8921435..26e22c644 100644
--- a/ForgeGit/forgegit/tests/test_tasks.py
+++ b/ForgeGit/forgegit/tests/test_tasks.py
@@ -34,7 +34,7 @@ from forgegit.tests.functional.test_controllers import _TestCase as GitRealDataB
 
 class TestGitTasks(unittest.TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -69,8 +69,8 @@ class TestCoreAlluraTasks(GitRealDataBaseTestCase):
     Not git-specific things we are testing, but the git tool is a useful standard repo type to use for it
     """
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     def test_refreshrepo(self):
diff --git a/ForgeImporters/forgeimporters/github/__init__.py b/ForgeImporters/forgeimporters/github/__init__.py
index 34dab0a3f..968eb2b89 100644
--- a/ForgeImporters/forgeimporters/github/__init__.py
+++ b/ForgeImporters/forgeimporters/github/__init__.py
@@ -250,6 +250,9 @@ class GitHubOAuthMixin:
     @without_trailing_slash
     @expose()
     def oauth_callback(self, **kw):
+        self.handle_oauth_callback()
+
+    def handle_oauth_callback(self):
         client_id = config.get('github_importer.client_id')
         secret = config.get('github_importer.client_secret')
         if not client_id or not secret:
diff --git a/ForgeImporters/forgeimporters/github/tests/test_code.py b/ForgeImporters/forgeimporters/github/tests/test_code.py
index d86d16031..d2ba20a20 100644
--- a/ForgeImporters/forgeimporters/github/tests/test_code.py
+++ b/ForgeImporters/forgeimporters/github/tests/test_code.py
@@ -35,7 +35,7 @@ with_git = with_tool(test_project_with_repo, 'git', 'src', 'git')
 
 class TestGitHubRepoImporter(TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_unit_test()
 
     def _make_project(self, gh_proj_name=None):
diff --git a/ForgeImporters/forgeimporters/github/tests/test_oauth.py b/ForgeImporters/forgeimporters/github/tests/test_oauth.py
index 1e02d83e0..8b60d03df 100644
--- a/ForgeImporters/forgeimporters/github/tests/test_oauth.py
+++ b/ForgeImporters/forgeimporters/github/tests/test_oauth.py
@@ -27,8 +27,8 @@ from forgeimporters.github import GitHubOAuthMixin
 
 class TestGitHubOAuthMixin(TestController, TestCase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         setup_unit_test()
         c.user = Mock()
         self.mix = GitHubOAuthMixin()
@@ -77,6 +77,6 @@ class TestGitHubOAuthMixin(TestController, TestCase):
     def test_oauth_callback_complete(self):
         with patch.object(self.mix, 'oauth_callback_complete') as _mock, \
                 patch('forgeimporters.github.redirect') as tg_redir:
-            self.mix.oauth_callback()
+            self.mix.handle_oauth_callback()
         self.assertEqual(_mock.call_count, 1)
         self.assertEqual(tg_redir.call_count, 1)
diff --git a/ForgeImporters/forgeimporters/github/tests/test_utils.py b/ForgeImporters/forgeimporters/github/tests/test_utils.py
index 5e47bcefe..5f067e8bc 100644
--- a/ForgeImporters/forgeimporters/github/tests/test_utils.py
+++ b/ForgeImporters/forgeimporters/github/tests/test_utils.py
@@ -22,7 +22,7 @@ from forgeimporters.github.utils import GitHubMarkdownConverter
 
 class TestGitHubMarkdownConverter:
 
-    def setUp(self):
+    def setup_method(self, method):
         self.conv = GitHubMarkdownConverter('user', 'project')
 
     def test_convert_sha(self):
diff --git a/ForgeImporters/forgeimporters/github/tests/test_wiki.py b/ForgeImporters/forgeimporters/github/tests/test_wiki.py
index 5afcebb17..20f01002b 100644
--- a/ForgeImporters/forgeimporters/github/tests/test_wiki.py
+++ b/ForgeImporters/forgeimporters/github/tests/test_wiki.py
@@ -74,7 +74,7 @@ class TestGitHubWikiImporter(TestCase):
                 project=p, user=u, url='foo')
             g.post_event.assert_called_once_with('project_updated')
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.blob1 = Mock()
         self.blob1.name = 'Home.md'
diff --git a/ForgeImporters/forgeimporters/tests/forge/test_discussion.py b/ForgeImporters/forgeimporters/tests/forge/test_discussion.py
index 5fd08d631..01788aab1 100644
--- a/ForgeImporters/forgeimporters/tests/forge/test_discussion.py
+++ b/ForgeImporters/forgeimporters/tests/forge/test_discussion.py
@@ -33,14 +33,11 @@ from forgediscussion import utils
 
 class TestDiscussionImporter(TestCase):
 
-    def setUp(self):
-        super().setUp()
-
+    def setup_method(self, method):
         self.patcher_g = mock.patch('forgeimporters.base.g', mock.MagicMock())
         self.patcher_g.start()
 
-    def tearDown(self):
-        super().tearDown()
+    def teardown_method(self, method):
         self.patcher_g.stop()
 
     @mock.patch.object(discussion, 'c')
@@ -1273,8 +1270,8 @@ class TestDiscussionImporter(TestCase):
 
 class TestForgeDiscussionController(TestController, TestCase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
 
     @with_discussion
     def test_index(self):
diff --git a/ForgeImporters/forgeimporters/tests/forge/test_tracker.py b/ForgeImporters/forgeimporters/tests/forge/test_tracker.py
index 9e8277099..f1c5b1aca 100644
--- a/ForgeImporters/forgeimporters/tests/forge/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/forge/test_tracker.py
@@ -33,14 +33,12 @@ from forgeimporters.forge import alluraImporter
 
 class TestTrackerImporter(TestCase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
         # every single test method here creates an importer and ToolImporterMeta uses 'g'
         self.patcher_g = mock.patch('forgeimporters.base.g', mock.MagicMock())
         self.patcher_g.start()
 
-    def tearDown(self):
-        super().tearDown()
+    def teardown_method(self, method):
         self.patcher_g.stop()
 
     @mock.patch.object(tracker, 'File')
@@ -338,9 +336,9 @@ class TestTrackerImporter(TestCase):
 
 class TestForgeTrackerImportController(TestController, TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         """Mount Allura importer on the Tracker admin controller"""
-        super().setUp()
+        super().setup_method(method)
         from forgetracker.tracker_main import TrackerAdminController
         TrackerAdminController._importer = \
                 tracker.ForgeTrackerImportController(tracker.ForgeTrackerImporter())
diff --git a/ForgeImporters/forgeimporters/tests/github/functional/test_github.py b/ForgeImporters/forgeimporters/tests/github/functional/test_github.py
index 78dd617bf..7945bb209 100644
--- a/ForgeImporters/forgeimporters/tests/github/functional/test_github.py
+++ b/ForgeImporters/forgeimporters/tests/github/functional/test_github.py
@@ -47,8 +47,8 @@ class TestGitHubImportController(TestController, TestCase):
 
 class TestGitHubOAuth(TestController):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         tg.config['github_importer.client_id'] = 'client_id'
         tg.config['github_importer.client_secret'] = 'secret'
 
diff --git a/ForgeImporters/forgeimporters/tests/github/test_extractor.py b/ForgeImporters/forgeimporters/tests/github/test_extractor.py
index 0d890c6e4..3edac5eb3 100644
--- a/ForgeImporters/forgeimporters/tests/github/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/github/test_extractor.py
@@ -84,7 +84,7 @@ class TestGitHubProjectExtractor(TestCase):
         response.info = lambda: headers
         return response
 
-    def setUp(self):
+    def setup_method(self, method):
         self.extractor = github.GitHubProjectExtractor('test_project')
         self.extractor.urlopen = self.mocked_urlopen
 
diff --git a/ForgeImporters/forgeimporters/tests/github/test_tracker.py b/ForgeImporters/forgeimporters/tests/github/test_tracker.py
index 2219295cf..e06fcb3e1 100644
--- a/ForgeImporters/forgeimporters/tests/github/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/github/test_tracker.py
@@ -27,14 +27,12 @@ from forgeimporters.github.utils import GitHubMarkdownConverter
 
 class TestTrackerImporter(TestCase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
         # every single test method here creates an importer and ToolImporterMeta uses 'g'
         self.patcher_g = mock.patch('forgeimporters.base.g', mock.MagicMock())
         self.patcher_g.start()
 
-    def tearDown(self):
-        super().tearDown()
+    def teardown_method(self, method):
         self.patcher_g.stop()
 
     @mock.patch.object(tracker, 'g')
diff --git a/ForgeImporters/forgeimporters/tests/test_base.py b/ForgeImporters/forgeimporters/tests/test_base.py
index b5d6dbe74..fe3d18d36 100644
--- a/ForgeImporters/forgeimporters/tests/test_base.py
+++ b/ForgeImporters/forgeimporters/tests/test_base.py
@@ -20,8 +20,8 @@ import errno
 
 from formencode import Invalid
 import mock
+import pytest
 from tg import expose, config
-from alluratest.tools import assert_equal, assert_raises
 from webob.exc import HTTPUnauthorized
 
 from alluratest.controller import TestController, setup_basic_test
@@ -89,7 +89,7 @@ def test_import_tool_failed(g, ToolImporter, format_exc):
     importer.import_tool.side_effect = RuntimeError('my error')
     ToolImporter.return_value = importer
 
-    with assert_raises(RuntimeError):
+    with pytest.raises(RuntimeError):
         base.import_tool('forgeimporters.base.ToolImporter', project_name='project_name')
     g.post_event.assert_called_once_with(
         'import_tool_task_failed',
@@ -248,7 +248,7 @@ class TestToolImporter(TestCase):
 
 class TestToolsValidator(TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         self.tv = base.ToolsValidator('good-source')
 
     @mock.patch.object(base.ToolImporter, 'by_name')
@@ -372,8 +372,8 @@ def test_save_importer_upload(giup, os):
         fp.write.assert_called_once_with('data')
 
     os.makedirs.side_effect = OSError(errno.EACCES, 'foo')
-    assert_raises(OSError, base.save_importer_upload,
-                  'project', 'file', 'data')
+    with pytest.raises(OSError):
+        base.save_importer_upload('project', 'file', 'data')
 
 
 class TestFile:
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
index 07a524efc..1789654d7 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -40,7 +40,7 @@ from forgeimporters.trac.tickets import (
 
 class TestTracTicketImporter(TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_unit_test()
 
     @patch('forgeimporters.trac.tickets.session')
@@ -110,9 +110,9 @@ class TestTracTicketImporter(TestCase):
 
 class TestTracTicketImportController(TestController, TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         """Mount Trac import controller on the Tracker admin controller"""
-        super().setUp()
+        super().setup_method(method)
         from forgetracker.tracker_main import TrackerAdminController
         self.importer = TrackerAdminController._importer = TracTicketImportController(TracTicketImporter())
 
diff --git a/ForgeLink/forgelink/tests/functional/test_rest.py b/ForgeLink/forgelink/tests/functional/test_rest.py
index d420d537a..6b3e026d9 100644
--- a/ForgeLink/forgelink/tests/functional/test_rest.py
+++ b/ForgeLink/forgelink/tests/functional/test_rest.py
@@ -24,8 +24,8 @@ from allura.lib import helpers as h
 
 class TestLinkApi(TestRestApiBase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @td.with_link
@@ -80,8 +80,8 @@ class TestLinkApi(TestRestApiBase):
 
 class TestLinkHasAccess(TestRestApiBase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @td.with_link
diff --git a/ForgeLink/forgelink/tests/test_app.py b/ForgeLink/forgelink/tests/test_app.py
index 28efa0054..d6e2fb4a6 100644
--- a/ForgeLink/forgelink/tests/test_app.py
+++ b/ForgeLink/forgelink/tests/test_app.py
@@ -28,7 +28,7 @@ from alluratest.controller import setup_basic_test
 
 class TestBulkExport:
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
 
     @td.with_link
diff --git a/ForgeSVN/forgesvn/tests/functional/test_controllers.py b/ForgeSVN/forgesvn/tests/functional/test_controllers.py
index 1a4cbe637..27b58e047 100644
--- a/ForgeSVN/forgesvn/tests/functional/test_controllers.py
+++ b/ForgeSVN/forgesvn/tests/functional/test_controllers.py
@@ -38,8 +38,8 @@ from allura.tests.decorators import with_tool
 
 class SVNTestController(TestController):
 
-    def setUp(self):
-        TestController.setUp(self)
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     def _make_app(self, mount_point, name):
@@ -351,8 +351,8 @@ class TestImportController(SVNTestController):
 
 class SVNTestRenames(TestController):
 
-    def setUp(self):
-        TestController.setUp(self)
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @with_svn
diff --git a/ForgeSVN/forgesvn/tests/model/test_repository.py b/ForgeSVN/forgesvn/tests/model/test_repository.py
index 9d5e02856..885aaaff1 100644
--- a/ForgeSVN/forgesvn/tests/model/test_repository.py
+++ b/ForgeSVN/forgesvn/tests/model/test_repository.py
@@ -53,7 +53,7 @@ import six
 
 class TestNewRepo(unittest.TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -107,7 +107,7 @@ class TestNewRepo(unittest.TestCase):
 
 class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -573,7 +573,7 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
 
 class TestSVNRev(unittest.TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -687,7 +687,7 @@ class _Test(unittest.TestCase):
     def _make_log(self, ci):
         session(ci).flush(ci)
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         setup_global_objects()
         ThreadLocalORMSession.flush_all()
@@ -697,8 +697,8 @@ class _Test(unittest.TestCase):
 
 class _TestWithRepo(_Test):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         h.set_context('test', neighborhood='Projects')
         c.project.install_app('svn', 'test1')
         h.set_context('test', 'test1', neighborhood='Projects')
@@ -716,8 +716,8 @@ class _TestWithRepo(_Test):
 
 class _TestWithRepoAndCommit(_TestWithRepo):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.ci, isnew = self._make_commit('foo')
         ThreadLocalORMSession.flush_all()
         # ThreadLocalORMSession.close_all()
@@ -880,8 +880,8 @@ class TestRepoObject(_TestWithRepoAndCommit):
 
 class TestCommit(_TestWithRepo):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.ci, isnew = self._make_commit(
             'foo',
             a=dict(
@@ -1022,7 +1022,7 @@ class TestCommit(_TestWithRepo):
 
 class TestRename(unittest.TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
@@ -1058,7 +1058,7 @@ class TestRename(unittest.TestCase):
 
 class TestDirectRepoAccess:
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
diff --git a/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py b/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py
index 0667d1fdc..0313e822b 100644
--- a/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py
+++ b/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py
@@ -26,7 +26,7 @@ from forgesvn.model.svn import SVNImplementation
 
 class TestSVNImplementation:
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_unit_test()
 
     def test_compute_tree_new(self):
diff --git a/ForgeSVN/forgesvn/tests/test_svn_app.py b/ForgeSVN/forgesvn/tests/test_svn_app.py
index 605ffe59c..d5c8770c1 100644
--- a/ForgeSVN/forgesvn/tests/test_svn_app.py
+++ b/ForgeSVN/forgesvn/tests/test_svn_app.py
@@ -28,7 +28,7 @@ from forgesvn.tests import with_svn
 
 class TestSVNApp(unittest.TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
 
diff --git a/ForgeSVN/forgesvn/tests/test_tasks.py b/ForgeSVN/forgesvn/tests/test_tasks.py
index e95899ecf..b3e388e98 100644
--- a/ForgeSVN/forgesvn/tests/test_tasks.py
+++ b/ForgeSVN/forgesvn/tests/test_tasks.py
@@ -36,14 +36,14 @@ from forgesvn.tests import with_svn
 
 class TestRepoTasks(unittest.TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         self.setup_with_tools()
         if asbool(tg.config.get('smtp.mock')):
             self.smtp_mock = mock.patch('allura.lib.mail_util.smtplib.SMTP')
             self.smtp_mock.start()
 
-    def tearDown(self):
+    def teardown_method(self, method):
         if asbool(tg.config.get('smtp.mock')):
             self.smtp_mock.stop()
 
diff --git a/ForgeShortUrl/forgeshorturl/tests/functional/test.py b/ForgeShortUrl/forgeshorturl/tests/functional/test_main.py
similarity index 98%
rename from ForgeShortUrl/forgeshorturl/tests/functional/test.py
rename to ForgeShortUrl/forgeshorturl/tests/functional/test_main.py
index 547ca8e01..1d3471d06 100644
--- a/ForgeShortUrl/forgeshorturl/tests/functional/test.py
+++ b/ForgeShortUrl/forgeshorturl/tests/functional/test_main.py
@@ -29,8 +29,8 @@ from forgeshorturl.model import ShortUrl
 
 class TestRootController(TestController):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @td.with_url
diff --git a/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py b/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
index 9074e46ff..1f3479d20 100644
--- a/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
+++ b/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
@@ -30,7 +30,7 @@ test_config = pkg_resources.resource_filename(
     'allura', '../test.ini') + '#main'
 
 
-def setUp(self):
+def setup_module(self):
     """Method called by nose before running each test"""
     setup_basic_test()
     setup_global_objects()
diff --git a/ForgeTracker/forgetracker/tests/functional/test_rest.py b/ForgeTracker/forgetracker/tests/functional/test_rest.py
index be72dd250..7db63dfa2 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_rest.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_rest.py
@@ -32,8 +32,8 @@ from forgetracker import model as TM
 
 class TestTrackerApiBase(TestRestApiBase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @td.with_tool('test', 'Tickets', 'bugs',
@@ -101,8 +101,8 @@ class TestRestNewTicket(TestTrackerApiBase):
 
 class TestRestUpdateTicket(TestTrackerApiBase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         ticket_view = self.create_ticket()
         self.ticket_args = ticket_view.json['ticket']
 
@@ -124,8 +124,8 @@ class TestRestUpdateTicket(TestTrackerApiBase):
 
 class TestRestIndex(TestTrackerApiBase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.create_ticket()
 
     def test_ticket_index(self):
@@ -169,8 +169,8 @@ class TestRestIndex(TestTrackerApiBase):
 
 class TestRestDiscussion(TestTrackerApiBase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         ticket_view = self.create_ticket()
         self.ticket_args = ticket_view.json['ticket']
 
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index e0556322e..129198ae6 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -28,15 +28,6 @@ import mock
 import PIL
 from bs4 import BeautifulSoup
 from mock import patch
-from alluratest.tools import (
-    assert_true,
-    assert_false,
-    assert_equal,
-    assert_in,
-    assert_raises,
-    assert_not_in,
-    assert_not_equal,
-)
 from formencode.variabledecode import variable_encode
 from tg import tmpl_context as c
 from tg import app_globals as g
@@ -1415,7 +1406,6 @@ class TestFunctionalController(TrackerTestController):
         canonical = response.html.select_one('link[rel=canonical]')
         assert 'page=' not in canonical
 
-
     def test_search_with_strange_chars(self):
         r = self.app.get('/p/test/bugs/search/?' +
                          urlencode({'q': 'tést'}))
@@ -2753,9 +2743,10 @@ def post_install_hook(app):
 
 
 class TestEmailMonitoring(TrackerTestController):
-    def __init__(self):
-        super().__init__()
-        self.test_email = 'mailinglist@example.com'
+
+    @classmethod
+    def setup_class(cls):
+        cls.test_email = 'mailinglist@example.com'
 
     def _set_options(self, monitoring_type='AllTicketChanges'):
         r = self.app.post('/admin/bugs/set_options', params={
@@ -2887,8 +2878,8 @@ class TestEmailMonitoring(TrackerTestController):
 
 
 class TestCustomUserField(TrackerTestController):
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         params = dict(
             custom_fields=[
                 dict(name='_code_review', label='Code Review', type='user',
@@ -3018,8 +3009,8 @@ class TestShowDefaultFields(TrackerTestController):
 
 
 class TestBulkMove(TrackerTestController):
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.new_ticket(summary='A New Hope')
         self.new_ticket(summary='The Empire Strikes Back')
         self.new_ticket(summary='Return Of The Jedi')
diff --git a/ForgeTracker/forgetracker/tests/test_app.py b/ForgeTracker/forgetracker/tests/test_app.py
index aed0fb60b..a688e8d01 100644
--- a/ForgeTracker/forgetracker/tests/test_app.py
+++ b/ForgeTracker/forgetracker/tests/test_app.py
@@ -38,7 +38,7 @@ from alluratest.pytest_helpers import with_nose_compatibility
 
 class TestApp:
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
 
     @td.with_tracker
diff --git a/ForgeTracker/forgetracker/tests/test_tracker_roles.py b/ForgeTracker/forgetracker/tests/test_tracker_roles.py
index 242646c8f..086e2796f 100644
--- a/ForgeTracker/forgetracker/tests/test_tracker_roles.py
+++ b/ForgeTracker/forgetracker/tests/test_tracker_roles.py
@@ -23,7 +23,7 @@ from allura.lib import security
 from allura.tests import decorators as td
 
 
-def setUp():
+def setup_module(module):
     setup_basic_test()
     setup_with_tools()
 
diff --git a/ForgeTracker/forgetracker/tests/unit/__init__.py b/ForgeTracker/forgetracker/tests/unit/__init__.py
index 1e9a0a9d4..3783fc20f 100644
--- a/ForgeTracker/forgetracker/tests/unit/__init__.py
+++ b/ForgeTracker/forgetracker/tests/unit/__init__.py
@@ -33,7 +33,7 @@ def setUp():
 
 class TrackerTestWithModel:
 
-    def setUp(self):
+    def setup_method(self, method):
         bootstrap.wipe_database()
         project_reg = plugin.ProjectRegistrationProvider.get()
         c.user = bootstrap.create_user('Test User')
@@ -49,5 +49,5 @@ class TrackerTestWithModel:
         h.set_context('test', 'bugs', neighborhood='Projects')
         tg.request_local.context.request = Request.blank('/')
 
-    def tearDown(self):
+    def teardown_method(self, method):
         ThreadLocalORMSession.close_all()
diff --git a/ForgeTracker/forgetracker/tests/unit/test_globals_model.py b/ForgeTracker/forgetracker/tests/unit/test_globals_model.py
index 6c063c8f8..bb22df47a 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_globals_model.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_globals_model.py
@@ -30,8 +30,8 @@ from allura.lib import helpers as h
 
 class TestGlobalsModel(TrackerTestWithModel):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         c.project.install_app('Tickets', 'doc-bugs')
         ThreadLocalORMSession.flush_all()
 
diff --git a/ForgeTracker/forgetracker/tests/unit/test_root_controller.py b/ForgeTracker/forgetracker/tests/unit/test_root_controller.py
index 8051cde32..fd40593bc 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_root_controller.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_root_controller.py
@@ -32,16 +32,16 @@ from forgetracker import tracker_main
 
 class WithUserAndBugsApp(TrackerTestWithModel):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         c.user = User(username='test-user')
         h.set_context('test', 'bugs', neighborhood='Projects')
 
 
 class TestWhenSearchingWithCustomFields(WithUserAndBugsApp):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         with solr_search_returning_colors_are_wrong_ticket():
             self.response = tracker_main.RootController().search(q='friends')
 
@@ -57,8 +57,8 @@ class TestWhenSearchingWithCustomFields(WithUserAndBugsApp):
 
 class TestWhenLoadingFrontPage(WithUserAndBugsApp):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         with mongo_search_returning_colors_are_wrong_ticket():
             self.response = tracker_main.RootController().index()
 
diff --git a/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py b/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py
index 02744756b..940525363 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py
@@ -22,16 +22,10 @@ import six.moves.urllib.request
 import six.moves.urllib.error
 
 import mock
+import pytest
 from ming.orm.ormsession import ThreadLocalORMSession
 from ming.orm import session
 from ming import schema
-from alluratest.tools import (
-    raises,
-    assert_equal,
-    assert_in,
-    assert_true,
-    assert_false,
-)
 from forgetracker.model import Ticket, TicketAttachment
 from forgetracker.tests.unit import TrackerTestWithModel
 from forgetracker.import_support import ResettableStream
@@ -76,9 +70,9 @@ class TestTicketModel(TrackerTestWithModel):
         ticket = Ticket.query.get(summary='my ticket')
         assert ticket.custom_fields == dict(my_field='my value')
 
-    @raises(schema.Invalid)
     def test_ticket_num_required(self):
-        Ticket(summary='my ticket')
+        with pytest.raises(schema.Invalid):
+            Ticket(summary='my ticket')
 
     def test_ticket_num_required2(self):
         t = Ticket(summary='my ticket', ticket_num=12)
diff --git a/ForgeUserStats/forgeuserstats/tests/test_model.py b/ForgeUserStats/forgeuserstats/tests/test_model.py
index 0d7bcba56..1a7720a11 100644
--- a/ForgeUserStats/forgeuserstats/tests/test_model.py
+++ b/ForgeUserStats/forgeuserstats/tests/test_model.py
@@ -38,7 +38,7 @@ with_git = td.with_tool('test', 'Git', 'git-userstats-model', 'Git', type='git')
 
 class TestUserStats(unittest.TestCase):
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         setup_global_objects()
         self.user = User.by_username('test-user-2')
diff --git a/ForgeUserStats/forgeuserstats/tests/test_stats.py b/ForgeUserStats/forgeuserstats/tests/test_stats.py
index b36112658..cf206201b 100644
--- a/ForgeUserStats/forgeuserstats/tests/test_stats.py
+++ b/ForgeUserStats/forgeuserstats/tests/test_stats.py
@@ -31,8 +31,8 @@ from forgetracker import model as TM
 
 class TestStats(TestController):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         p = M.Project.query.get(shortname='test')
         p.add_user(M.User.by_username('test-user'), ['Admin'])
 
@@ -185,8 +185,8 @@ class TestStats(TestController):
 
 class TestGitCommit(TestController, unittest.TestCase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         setup_basic_test()
 
         user = User.by_username('test-admin')
diff --git a/ForgeWiki/forgewiki/tests/functional/test_rest.py b/ForgeWiki/forgewiki/tests/functional/test_rest.py
index 694275746..4eb87d999 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_rest.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_rest.py
@@ -29,8 +29,8 @@ from forgewiki.model import Page
 
 class TestWikiApi(TestRestApiBase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @td.with_wiki
@@ -121,8 +121,8 @@ class TestWikiApi(TestRestApiBase):
 
 class TestWikiHasAccess(TestRestApiBase):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @td.with_wiki
diff --git a/ForgeWiki/forgewiki/tests/functional/test_root.py b/ForgeWiki/forgewiki/tests/functional/test_root.py
index 05e2b181e..927f17dc4 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_root.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_root.py
@@ -37,8 +37,8 @@ from unittest.mock import MagicMock
 
 class TestRootController(TestController):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self.setup_with_tools()
 
     @td.with_wiki
diff --git a/ForgeWiki/forgewiki/tests/test_app.py b/ForgeWiki/forgewiki/tests/test_app.py
index 1696fc192..482288dc5 100644
--- a/ForgeWiki/forgewiki/tests/test_app.py
+++ b/ForgeWiki/forgewiki/tests/test_app.py
@@ -35,7 +35,7 @@ from forgewiki.wiki_main import ForgeWikiApp
 
 class TestBulkExport:
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         setup_global_objects()
         self.setup_with_tools()
@@ -127,7 +127,7 @@ class TestBulkExport:
 
 class TestApp:
 
-    def setUp(self):
+    def setup_method(self, method):
         setup_basic_test()
         setup_global_objects()
         self.setup_with_tools()
diff --git a/ForgeWiki/forgewiki/tests/test_wiki_roles.py b/ForgeWiki/forgewiki/tests/test_wiki_roles.py
index e03880976..f42f76618 100644
--- a/ForgeWiki/forgewiki/tests/test_wiki_roles.py
+++ b/ForgeWiki/forgewiki/tests/test_wiki_roles.py
@@ -25,7 +25,7 @@ from allura.lib import security
 from allura.tests import decorators as td
 
 
-def setUp():
+def setup_module(module):
     setup_basic_test()
     setup_with_tools()
 
diff --git a/scripts/perf/call_count.py b/scripts/perf/call_count.py
index 711a21daa..4c567b7df 100755
--- a/scripts/perf/call_count.py
+++ b/scripts/perf/call_count.py
@@ -70,7 +70,7 @@ def main(args):
                         debug_html=args.debug_html)
     print(json.dumps(counts))
     write_csv(counts, args.id, args.data_file)
-    test.tearDown()
+    test.teardown_method(method)
 
 
 def setup(test):
@@ -79,7 +79,7 @@ def setup(test):
                                   'stats.debug_line_length': 1000,
                                   }), \
             patch('timermiddleware.log.isEnabledFor', return_value=True):  # can't set this via logging configuration since setUp() will load a logging config and then start using it before we have a good place to tweak it
-        test.setUp()
+        test.setup_method(method)
 
     tmw_log = logging.getLogger('timermiddleware')
     tmw_log.disabled = 0  # gets disabled when .ini file is loaded; dumb.


[allura] 06/10: [#8455] ran nose2pytest on Forge* and AlluraTest modules

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-part2
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 2717ae0aa495d1830fe683d5b8389570f8ffc4e4
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Fri Sep 16 13:33:22 2022 +0000

    [#8455] ran nose2pytest on Forge* and AlluraTest modules
---
 Allura/allura/tests/functional/test_admin.py       |   2 +-
 Allura/allura/tests/functional/test_auth.py        |   2 +-
 Allura/allura/tests/functional/test_discuss.py     |   2 +-
 Allura/allura/tests/functional/test_feeds.py       |   2 +-
 Allura/allura/tests/functional/test_gravatar.py    |   2 +-
 Allura/allura/tests/functional/test_home.py        |   2 +-
 Allura/allura/tests/functional/test_nav.py         |   2 +-
 .../allura/tests/functional/test_neighborhood.py   |   2 +-
 Allura/allura/tests/functional/test_newforge.py    |   2 +-
 .../tests/functional/test_personal_dashboard.py    |   2 +-
 Allura/allura/tests/functional/test_rest.py        |   2 +-
 Allura/allura/tests/functional/test_root.py        |   2 +-
 Allura/allura/tests/functional/test_search.py      |   2 +-
 Allura/allura/tests/functional/test_site_admin.py  |   2 +-
 Allura/allura/tests/functional/test_static.py      |   2 +-
 Allura/allura/tests/functional/test_subscriber.py  |   2 +-
 Allura/allura/tests/functional/test_tool_list.py   |   2 +-
 .../allura/tests/functional/test_trovecategory.py  |   2 +-
 .../allura/tests/functional/test_user_profile.py   |   2 +-
 Allura/allura/tests/model/test_auth.py             |   2 +-
 Allura/allura/tests/model/test_filesystem.py       |   2 +-
 Allura/allura/tests/model/test_notification.py     |   2 +-
 Allura/allura/tests/model/test_repo.py             |   2 +-
 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      |   2 +-
 .../tests/templates/jinja_master/test_lib.py       |   2 +-
 Allura/allura/tests/test_app.py                    |   2 +-
 Allura/allura/tests/test_commands.py               |   2 +-
 Allura/allura/tests/test_decorators.py             |   2 +-
 Allura/allura/tests/test_diff.py                   |   2 +-
 Allura/allura/tests/test_dispatch.py               |   2 +-
 Allura/allura/tests/test_globals.py                |   2 +-
 Allura/allura/tests/test_helpers.py                |   2 +-
 Allura/allura/tests/test_mail_util.py              |   2 +-
 Allura/allura/tests/test_markdown.py               |   2 +-
 Allura/allura/tests/test_middlewares.py            |   2 +-
 Allura/allura/tests/test_multifactor.py            |   2 +-
 Allura/allura/tests/test_plugin.py                 |   2 +-
 Allura/allura/tests/test_scripttask.py             |   2 +-
 Allura/allura/tests/test_security.py               |   2 +-
 Allura/allura/tests/test_tasks.py                  |   2 +-
 Allura/allura/tests/test_utils.py                  |   2 +-
 Allura/allura/tests/test_validators.py             |   2 +-
 Allura/allura/tests/test_webhooks.py               |   2 +-
 .../test_discussion_moderation_controller.py       |   2 +-
 Allura/allura/tests/unit/phone/test_nexmo.py       |   2 +-
 .../allura/tests/unit/phone/test_phone_service.py  |   2 +-
 Allura/allura/tests/unit/spam/test_akismet.py      |   2 +-
 Allura/allura/tests/unit/spam/test_spam_filter.py  |   2 +-
 .../allura/tests/unit/spam/test_stopforumspam.py   |   2 +-
 Allura/allura/tests/unit/test_app.py               |   2 +-
 Allura/allura/tests/unit/test_artifact.py          |   2 +-
 Allura/allura/tests/unit/test_discuss.py           |   2 +-
 Allura/allura/tests/unit/test_helpers/test_ago.py  |   2 +-
 .../tests/unit/test_helpers/test_set_context.py    |   2 +-
 .../allura/tests/unit/test_ldap_auth_provider.py   |   2 +-
 Allura/allura/tests/unit/test_mixins.py            |   2 +-
 .../allura/tests/unit/test_package_path_loader.py  |   2 +-
 Allura/allura/tests/unit/test_post_model.py        |   2 +-
 Allura/allura/tests/unit/test_project.py           |   2 +-
 Allura/allura/tests/unit/test_repo.py              |   2 +-
 Allura/allura/tests/unit/test_session.py           |   2 +-
 Allura/allura/tests/unit/test_sitemapentry.py      |   2 +-
 Allura/allura/tests/unit/test_solr.py              |   2 +-
 AlluraTest/alluratest/controller.py                |   2 +-
 .../alluratest}/pytest_helpers.py                  |   0
 .../forgeactivity/tests/functional/test_rest.py    |  18 +-
 .../forgeactivity/tests/functional/test_root.py    |  82 +--
 ForgeBlog/forgeblog/tests/functional/test_feeds.py |  12 +-
 ForgeBlog/forgeblog/tests/functional/test_rest.py  |  92 ++--
 ForgeBlog/forgeblog/tests/functional/test_root.py  |  12 +-
 ForgeBlog/forgeblog/tests/test_app.py              |  26 +-
 ForgeBlog/forgeblog/tests/test_commands.py         |  18 +-
 ForgeBlog/forgeblog/tests/unit/test_blog_post.py   |  20 +-
 ForgeChat/forgechat/tests/functional/test_root.py  |   6 +-
 .../forgediscussion/tests/functional/test_forum.py |  92 ++--
 .../tests/functional/test_import.py                |  20 +-
 .../forgediscussion/tests/functional/test_rest.py  | 176 +++----
 ForgeDiscussion/forgediscussion/tests/test_app.py  |  40 +-
 .../forgefeedback/tests/functional/test_root.py    |  12 +-
 .../forgefeedback/tests/test_feedback_roles.py     |   8 +-
 .../forgefeedback/tests/unit/test_feedback.py      |  10 +-
 .../tests/unit/test_root_controller.py             |   2 +-
 .../forgefiles/tests/functional/test_root.py       |   8 +-
 ForgeFiles/forgefiles/tests/model/test_files.py    |  16 +-
 ForgeFiles/forgefiles/tests/test_files_roles.py    |   8 +-
 ForgeGit/forgegit/tests/functional/test_auth.py    |   4 +-
 .../forgegit/tests/functional/test_controllers.py  | 204 ++++----
 ForgeGit/forgegit/tests/model/test_repository.py   | 148 +++---
 ForgeGit/forgegit/tests/test_git_app.py            |   2 +-
 .../forgeimporters/github/tests/test_utils.py      |  36 +-
 .../forgeimporters/github/tests/test_wiki.py       | 140 +++---
 .../tests/github/functional/test_github.py         |  14 +-
 ForgeImporters/forgeimporters/tests/test_base.py   |  14 +-
 ForgeLink/forgelink/tests/functional/test_rest.py  |  26 +-
 ForgeLink/forgelink/tests/functional/test_root.py  |  30 +-
 ForgeLink/forgelink/tests/test_app.py              |   2 +-
 ForgeSVN/forgesvn/tests/functional/test_auth.py    |  14 +-
 .../forgesvn/tests/functional/test_controllers.py  |  64 +--
 ForgeSVN/forgesvn/tests/model/test_repository.py   | 131 +++--
 .../forgesvn/tests/model/test_svnimplementation.py |  30 +-
 ForgeSVN/forgesvn/tests/test_svn_app.py            |   4 +-
 ForgeSVN/forgesvn/tests/test_tasks.py              |   2 +-
 .../forgeshorturl/tests/functional/test.py         |  12 +-
 .../tests/command/test_fix_discussion.py           |  16 +-
 .../forgetracker/tests/functional/test_rest.py     |  54 +-
 .../forgetracker/tests/functional/test_root.py     | 548 ++++++++++-----------
 ForgeTracker/forgetracker/tests/test_app.py        |  38 +-
 .../forgetracker/tests/unit/test_globals_model.py  |  40 +-
 .../tests/unit/test_milestone_controller.py        |   2 +-
 .../tests/unit/test_root_controller.py             |   2 +-
 .../forgetracker/tests/unit/test_search.py         |   4 +-
 .../forgetracker/tests/unit/test_ticket_model.py   | 112 ++---
 ForgeWiki/forgewiki/tests/functional/test_rest.py  |  50 +-
 ForgeWiki/forgewiki/tests/functional/test_root.py  | 100 ++--
 ForgeWiki/forgewiki/tests/test_app.py              |  28 +-
 ForgeWiki/forgewiki/tests/test_wiki_roles.py       |  18 +-
 120 files changed, 1351 insertions(+), 1352 deletions(-)

diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index b5f39a393..a140f1701 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -32,7 +32,7 @@ import six
 
 import allura
 from allura.tests import TestController
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.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
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index 47a5013a0..0acf610e3 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -47,7 +47,7 @@ from tg import tmpl_context as c, app_globals as g
 from allura.tests import TestController
 from allura.tests import decorators as td
 from allura.tests.decorators import audits, out_audits, assert_logmsg
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.pytest_helpers import with_nose_compatibility
 from alluratest.controller import setup_trove_categories, TestRestApiBase, oauth1_webtest
 from allura import model as M
 from allura.model.oauth import dummy_oauths
diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py
index a2442074a..8adb1a1af 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -25,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_feeds.py b/Allura/allura/tests/functional/test_feeds.py
index 4baf5f9d0..e795f95a9 100644
--- a/Allura/allura/tests/functional/test_feeds.py
+++ b/Allura/allura/tests/functional/test_feeds.py
@@ -20,7 +20,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_gravatar.py b/Allura/allura/tests/functional/test_gravatar.py
index fdec13eb1..2886df357 100644
--- a/Allura/allura/tests/functional/test_gravatar.py
+++ b/Allura/allura/tests/functional/test_gravatar.py
@@ -23,7 +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
+from alluratest.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 07a149dbb..a27febbea 100644
--- a/Allura/allura/tests/functional/test_home.py
+++ b/Allura/allura/tests/functional/test_home.py
@@ -27,7 +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
+from alluratest.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 840a97969..e7113cc77 100644
--- a/Allura/allura/tests/functional/test_nav.py
+++ b/Allura/allura/tests/functional/test_nav.py
@@ -22,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index ac7ee3c9d..b123cb7eb 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -37,7 +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
+from alluratest.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 1e95a38cb..83470c1e4 100644
--- a/Allura/allura/tests/functional/test_newforge.py
+++ b/Allura/allura/tests/functional/test_newforge.py
@@ -21,7 +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
+from alluratest.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 428afb029..c1f7d3cb3 100644
--- a/Allura/allura/tests/functional/test_personal_dashboard.py
+++ b/Allura/allura/tests/functional/test_personal_dashboard.py
@@ -30,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index 0be71b396..c2a8cef8b 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -31,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_root.py b/Allura/allura/tests/functional/test_root.py
index 95a1ea15b..0b2af9c99 100644
--- a/Allura/allura/tests/functional/test_root.py
+++ b/Allura/allura/tests/functional/test_root.py
@@ -41,7 +41,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_search.py b/Allura/allura/tests/functional/test_search.py
index 52fba8d7f..a24bc0497 100644
--- a/Allura/allura/tests/functional/test_search.py
+++ b/Allura/allura/tests/functional/test_search.py
@@ -24,7 +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
+from alluratest.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 7ca541362..ec743bb1f 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -33,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/functional/test_static.py b/Allura/allura/tests/functional/test_static.py
index 7912e425c..38e4ba760 100644
--- a/Allura/allura/tests/functional/test_static.py
+++ b/Allura/allura/tests/functional/test_static.py
@@ -17,7 +17,7 @@
 
 
 from allura.tests import TestController
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.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 02a273cfc..32333f92d 100644
--- a/Allura/allura/tests/functional/test_subscriber.py
+++ b/Allura/allura/tests/functional/test_subscriber.py
@@ -19,7 +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
+from alluratest.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 8e4bfafb1..3562463b4 100644
--- a/Allura/allura/tests/functional/test_tool_list.py
+++ b/Allura/allura/tests/functional/test_tool_list.py
@@ -17,7 +17,7 @@
 
 from allura.tests import TestController
 from allura.tests import decorators as td
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.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 8588607aa..76473d3d7 100644
--- a/Allura/allura/tests/functional/test_trovecategory.py
+++ b/Allura/allura/tests/functional/test_trovecategory.py
@@ -26,7 +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
+from alluratest.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 e4df160e1..353a99fa7 100644
--- a/Allura/allura/tests/functional/test_user_profile.py
+++ b/Allura/allura/tests/functional/test_user_profile.py
@@ -23,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 class TestUserProfileSections(TestController):
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index 7f48c49bc..b4e5e80de 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -42,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 def setup_method():
diff --git a/Allura/allura/tests/model/test_filesystem.py b/Allura/allura/tests/model/test_filesystem.py
index df0e5b7d5..fe88d8aa7 100644
--- a/Allura/allura/tests/model/test_filesystem.py
+++ b/Allura/allura/tests/model/test_filesystem.py
@@ -28,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 class File(M.File):
diff --git a/Allura/allura/tests/model/test_notification.py b/Allura/allura/tests/model/test_notification.py
index 7e0b8ed3d..33d2547fd 100644
--- a/Allura/allura/tests/model/test_notification.py
+++ b/Allura/allura/tests/model/test_notification.py
@@ -31,7 +31,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/model/test_repo.py b/Allura/allura/tests/model/test_repo.py
index 873b411c8..84d26bdf2 100644
--- a/Allura/allura/tests/model/test_repo.py
+++ b/Allura/allura/tests/model/test_repo.py
@@ -29,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/model/test_timeline.py b/Allura/allura/tests/model/test_timeline.py
index 3d8bc26e7..00e7ea069 100644
--- a/Allura/allura/tests/model/test_timeline.py
+++ b/Allura/allura/tests/model/test_timeline.py
@@ -20,7 +20,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/scripts/test_create_sitemap_files.py b/Allura/allura/tests/scripts/test_create_sitemap_files.py
index c5835109f..13c853940 100644
--- a/Allura/allura/tests/scripts/test_create_sitemap_files.py
+++ b/Allura/allura/tests/scripts/test_create_sitemap_files.py
@@ -27,7 +27,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/scripts/test_delete_projects.py b/Allura/allura/tests/scripts/test_delete_projects.py
index 57c219f54..d9414358f 100644
--- a/Allura/allura/tests/scripts/test_delete_projects.py
+++ b/Allura/allura/tests/scripts/test_delete_projects.py
@@ -25,7 +25,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/scripts/test_misc_scripts.py b/Allura/allura/tests/scripts/test_misc_scripts.py
index 8ebbba848..83c8c145e 100644
--- a/Allura/allura/tests/scripts/test_misc_scripts.py
+++ b/Allura/allura/tests/scripts/test_misc_scripts.py
@@ -22,7 +22,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/scripts/test_reindexes.py b/Allura/allura/tests/scripts/test_reindexes.py
index 53a51f04a..c6da7ad62 100644
--- a/Allura/allura/tests/scripts/test_reindexes.py
+++ b/Allura/allura/tests/scripts/test_reindexes.py
@@ -22,7 +22,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/templates/jinja_master/test_lib.py b/Allura/allura/tests/templates/jinja_master/test_lib.py
index f3eea9edd..8c46da06b 100644
--- a/Allura/allura/tests/templates/jinja_master/test_lib.py
+++ b/Allura/allura/tests/templates/jinja_master/test_lib.py
@@ -22,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 def strip_space(s):
diff --git a/Allura/allura/tests/test_app.py b/Allura/allura/tests/test_app.py
index b93170a8e..024c12fd3 100644
--- a/Allura/allura/tests/test_app.py
+++ b/Allura/allura/tests/test_app.py
@@ -26,7 +26,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 def setup_method():
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index 84bac7a10..f7440e3a1 100644
--- a/Allura/allura/tests/test_commands.py
+++ b/Allura/allura/tests/test_commands.py
@@ -36,7 +36,7 @@ from allura.command import base, script, set_neighborhood_features, \
 from allura import model as M
 from allura.lib.exceptions import InvalidNBFeatureValueError
 from allura.tests import decorators as td
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 test_config = pkg_resources.resource_filename(
diff --git a/Allura/allura/tests/test_decorators.py b/Allura/allura/tests/test_decorators.py
index 033e5c41a..71b70d019 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.pytest_helpers import with_nose_compatibility
+from alluratest.pytest_helpers import with_nose_compatibility
 from allura.lib.decorators import task, memoize
 from alluratest.controller import setup_basic_test, setup_global_objects
 
diff --git a/Allura/allura/tests/test_diff.py b/Allura/allura/tests/test_diff.py
index d128386b0..1485bc1a8 100644
--- a/Allura/allura/tests/test_diff.py
+++ b/Allura/allura/tests/test_diff.py
@@ -18,7 +18,7 @@
 import unittest
 
 from allura.lib.diff import HtmlSideBySideDiff
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/test_dispatch.py b/Allura/allura/tests/test_dispatch.py
index 265fdd187..983ffac72 100644
--- a/Allura/allura/tests/test_dispatch.py
+++ b/Allura/allura/tests/test_dispatch.py
@@ -16,7 +16,7 @@
 #       under the License.
 
 from allura.tests import TestController
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.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 389c7a228..29b318027 100644
--- a/Allura/allura/tests/test_globals.py
+++ b/Allura/allura/tests/test_globals.py
@@ -43,7 +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 alluratest.pytest_helpers import with_nose_compatibility
 
 from forgewiki import model as WM
 from forgeblog import model as BM
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index dbd359e33..6e03cb8c5 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -37,7 +37,7 @@ 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.pytest_helpers import with_nose_compatibility
 from alluratest.controller import setup_basic_test
 import six
 
diff --git a/Allura/allura/tests/test_mail_util.py b/Allura/allura/tests/test_mail_util.py
index f1ad21d09..33e80bfb0 100644
--- a/Allura/allura/tests/test_mail_util.py
+++ b/Allura/allura/tests/test_mail_util.py
@@ -37,7 +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 alluratest.pytest_helpers import with_nose_compatibility
 from allura.tests import decorators as td
 import six
 
diff --git a/Allura/allura/tests/test_markdown.py b/Allura/allura/tests/test_markdown.py
index 5334aa4d6..6878143f1 100644
--- a/Allura/allura/tests/test_markdown.py
+++ b/Allura/allura/tests/test_markdown.py
@@ -19,7 +19,7 @@ import unittest
 import mock
 
 from allura.lib import markdown_extensions as mde
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.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 1f8e78a79..b48e27603 100644
--- a/Allura/allura/tests/test_middlewares.py
+++ b/Allura/allura/tests/test_middlewares.py
@@ -19,7 +19,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/test_multifactor.py b/Allura/allura/tests/test_multifactor.py
index e007b0adf..ba7780f3b 100644
--- a/Allura/allura/tests/test_multifactor.py
+++ b/Allura/allura/tests/test_multifactor.py
@@ -32,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/test_plugin.py b/Allura/allura/tests/test_plugin.py
index f11eae8d7..cfe441693 100644
--- a/Allura/allura/tests/test_plugin.py
+++ b/Allura/allura/tests/test_plugin.py
@@ -45,7 +45,7 @@ from allura.lib.exceptions import ProjectConflict, ProjectShortnameInvalid
 from allura.tests.decorators import audits
 from allura.tests.exclude_from_rewrite_hook import ThemeProviderTestApp
 from alluratest.controller import setup_basic_test, setup_global_objects
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 def setup_module(module):
diff --git a/Allura/allura/tests/test_scripttask.py b/Allura/allura/tests/test_scripttask.py
index f8576bbf8..ae284a1e9 100644
--- a/Allura/allura/tests/test_scripttask.py
+++ b/Allura/allura/tests/test_scripttask.py
@@ -19,7 +19,7 @@ import unittest
 import mock
 
 from allura.scripts.scripttask import ScriptTask
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/test_security.py b/Allura/allura/tests/test_security.py
index 5fe92b964..f6771f3aa 100644
--- a/Allura/allura/tests/test_security.py
+++ b/Allura/allura/tests/test_security.py
@@ -28,7 +28,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 def _allow(obj, role, perm):
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index 429effd80..2aeef658d 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -52,7 +52,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 alluratest.pytest_helpers import with_nose_compatibility
 from allura.tests.exclude_from_rewrite_hook import raise_compound_exception
 from allura.lib.decorators import event_handler, task
 
diff --git a/Allura/allura/tests/test_utils.py b/Allura/allura/tests/test_utils.py
index 6e7fca1cc..4c22c1a93 100644
--- a/Allura/allura/tests/test_utils.py
+++ b/Allura/allura/tests/test_utils.py
@@ -44,7 +44,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @patch.dict('allura.lib.utils.tg.config', clear=True, foo='bar', baz='true')
diff --git a/Allura/allura/tests/test_validators.py b/Allura/allura/tests/test_validators.py
index 2e0f5af65..a9c011368 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.pytest_helpers import with_nose_compatibility
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 def _setup_method():
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index 8237847d6..3b11f5e05 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -49,7 +49,7 @@ from alluratest.controller import (
     TestRestApiBase,
 )
 import six
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 # important to be distinct from 'test' and 'test2' which ForgeGit and
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 72abd8ed4..3ad4ed8df 100644
--- a/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
+++ b/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
@@ -24,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/phone/test_nexmo.py b/Allura/allura/tests/unit/phone/test_nexmo.py
index 2bcff5cfa..290273946 100644
--- a/Allura/allura/tests/unit/phone/test_nexmo.py
+++ b/Allura/allura/tests/unit/phone/test_nexmo.py
@@ -19,7 +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 alluratest.pytest_helpers import with_nose_compatibility
 
 from allura.lib.phone.nexmo import NexmoPhoneService
 
diff --git a/Allura/allura/tests/unit/phone/test_phone_service.py b/Allura/allura/tests/unit/phone/test_phone_service.py
index fe5b2844e..e4af19908 100644
--- a/Allura/allura/tests/unit/phone/test_phone_service.py
+++ b/Allura/allura/tests/unit/phone/test_phone_service.py
@@ -19,7 +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
+from alluratest.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 bee8403cd..2c6e2af31 100644
--- a/Allura/allura/tests/unit/spam/test_akismet.py
+++ b/Allura/allura/tests/unit/spam/test_akismet.py
@@ -26,7 +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
+from alluratest.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 99f6ce1a9..f907b41ef 100644
--- a/Allura/allura/tests/unit/spam/test_spam_filter.py
+++ b/Allura/allura/tests/unit/spam/test_spam_filter.py
@@ -26,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 class MockFilter(SpamFilter):
diff --git a/Allura/allura/tests/unit/spam/test_stopforumspam.py b/Allura/allura/tests/unit/spam/test_stopforumspam.py
index 2037ddde8..7a1bd31d7 100644
--- a/Allura/allura/tests/unit/spam/test_stopforumspam.py
+++ b/Allura/allura/tests/unit/spam/test_stopforumspam.py
@@ -22,7 +22,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/test_app.py b/Allura/allura/tests/unit/test_app.py
index 2a374f415..65d000fae 100644
--- a/Allura/allura/tests/unit/test_app.py
+++ b/Allura/allura/tests/unit/test_app.py
@@ -22,7 +22,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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/test_artifact.py b/Allura/allura/tests/unit/test_artifact.py
index 801a29fef..1cbe17f47 100644
--- a/Allura/allura/tests/unit/test_artifact.py
+++ b/Allura/allura/tests/unit/test_artifact.py
@@ -18,7 +18,7 @@
 import unittest
 
 from allura import model as M
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.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 9cb225e40..6078dff49 100644
--- a/Allura/allura/tests/unit/test_discuss.py
+++ b/Allura/allura/tests/unit/test_discuss.py
@@ -18,7 +18,7 @@
 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
+from alluratest.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 7fef39014..a8e4c9044 100644
--- a/Allura/allura/tests/unit/test_helpers/test_ago.py
+++ b/Allura/allura/tests/unit/test_helpers/test_ago.py
@@ -21,7 +21,7 @@ from mock import patch
 from alluratest.tools import assert_equal
 
 from allura.lib import helpers
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
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 3468ceed0..9e6999231 100644
--- a/Allura/allura/tests/unit/test_helpers/test_set_context.py
+++ b/Allura/allura/tests/unit/test_helpers/test_set_context.py
@@ -26,7 +26,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/test_ldap_auth_provider.py b/Allura/allura/tests/unit/test_ldap_auth_provider.py
index d94430188..bc17ce9a5 100644
--- a/Allura/allura/tests/unit/test_ldap_auth_provider.py
+++ b/Allura/allura/tests/unit/test_ldap_auth_provider.py
@@ -32,7 +32,7 @@ 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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/test_mixins.py b/Allura/allura/tests/unit/test_mixins.py
index f4e50aafe..775ff0358 100644
--- a/Allura/allura/tests/unit/test_mixins.py
+++ b/Allura/allura/tests/unit/test_mixins.py
@@ -17,7 +17,7 @@
 
 from mock import Mock
 from allura.model import VotableArtifact
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/test_package_path_loader.py b/Allura/allura/tests/unit/test_package_path_loader.py
index 5c39771f6..5aec5fea2 100644
--- a/Allura/allura/tests/unit/test_package_path_loader.py
+++ b/Allura/allura/tests/unit/test_package_path_loader.py
@@ -25,7 +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
+from alluratest.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 e1f7504ed..eca936d1e 100644
--- a/Allura/allura/tests/unit/test_post_model.py
+++ b/Allura/allura/tests/unit/test_post_model.py
@@ -22,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/test_project.py b/Allura/allura/tests/unit/test_project.py
index 3fefa5fe1..2fb093643 100644
--- a/Allura/allura/tests/unit/test_project.py
+++ b/Allura/allura/tests/unit/test_project.py
@@ -23,7 +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
+from alluratest.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 df862d547..a33d8c644 100644
--- a/Allura/allura/tests/unit/test_repo.py
+++ b/Allura/allura/tests/unit/test_repo.py
@@ -31,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/Allura/allura/tests/unit/test_session.py b/Allura/allura/tests/unit/test_session.py
index cb7f02f7d..d7899302f 100644
--- a/Allura/allura/tests/unit/test_session.py
+++ b/Allura/allura/tests/unit/test_session.py
@@ -28,7 +28,7 @@ from allura.model.session import (
     ArtifactSessionExtension,
     substitute_extensions,
 )
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 def test_extensions_cm():
diff --git a/Allura/allura/tests/unit/test_sitemapentry.py b/Allura/allura/tests/unit/test_sitemapentry.py
index e4e56deec..ea38b10b7 100644
--- a/Allura/allura/tests/unit/test_sitemapentry.py
+++ b/Allura/allura/tests/unit/test_sitemapentry.py
@@ -19,7 +19,7 @@ import unittest
 from mock import Mock
 
 from allura.app import SitemapEntry
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.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 cc73cdb6f..fa23bc135 100644
--- a/Allura/allura/tests/unit/test_solr.py
+++ b/Allura/allura/tests/unit/test_solr.py
@@ -26,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 @with_nose_compatibility
diff --git a/AlluraTest/alluratest/controller.py b/AlluraTest/alluratest/controller.py
index 362c8ee54..f0011a74b 100644
--- a/AlluraTest/alluratest/controller.py
+++ b/AlluraTest/alluratest/controller.py
@@ -19,7 +19,7 @@
 from __future__ import annotations
 
 import os
-from allura.tests.pytest_helpers import with_nose_compatibility
+from alluratest.pytest_helpers import with_nose_compatibility
 import six.moves.urllib.request
 import six.moves.urllib.parse
 import six.moves.urllib.error
diff --git a/Allura/allura/tests/pytest_helpers.py b/AlluraTest/alluratest/pytest_helpers.py
similarity index 100%
rename from Allura/allura/tests/pytest_helpers.py
rename to AlluraTest/alluratest/pytest_helpers.py
diff --git a/ForgeActivity/forgeactivity/tests/functional/test_rest.py b/ForgeActivity/forgeactivity/tests/functional/test_rest.py
index cb11dc9b2..d20166d5f 100644
--- a/ForgeActivity/forgeactivity/tests/functional/test_rest.py
+++ b/ForgeActivity/forgeactivity/tests/functional/test_rest.py
@@ -42,13 +42,13 @@ class TestActivityHasAccessAPI(TestRestApiBase):
         r = self.api_get(
             '/rest/p/test/activity/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/activity/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
 
     def test_has_access_not_admin(self):
         """
@@ -64,15 +64,15 @@ class TestActivityHasAccessAPI(TestRestApiBase):
         r = self.api_get(
             '/rest/p/test/activity/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/p/test/activity/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
 
 
     def test_user_api(self):
         r = self.api_get('/rest/u/test-user/activity')
-        assert_equal(r.status_int, 200)
\ No newline at end of file
+        assert r.status_int == 200
\ No newline at end of file
diff --git a/ForgeActivity/forgeactivity/tests/functional/test_root.py b/ForgeActivity/forgeactivity/tests/functional/test_root.py
index 2948e560b..cb90360d1 100644
--- a/ForgeActivity/forgeactivity/tests/functional/test_root.py
+++ b/ForgeActivity/forgeactivity/tests/functional/test_root.py
@@ -95,9 +95,9 @@ class TestActivityController(TestController):
         })]
         r = self.app.get('/p/test/activity/')
         timeline = r.html.find('ul', 'timeline')
-        assert_equal(1, len(timeline.findAll('li')))
+        assert 1 == len(timeline.findAll('li'))
         activity = timeline.find('li')
-        assert_equal(activity.time['title'], "2013-12-04 21:48:19")
+        assert activity.time['title'] == "2013-12-04 21:48:19"
         h1 = """\
         <h1>
          <img alt="Administrator 1" class="emboss x32 avatar" src="/u/test-admin/user_icon" title="Administrator 1"/>
@@ -114,13 +114,13 @@ class TestActivityController(TestController):
          </a>
         </h1>
         """
-        assert_equal(dedent(h1), activity.h1.prettify())
+        assert dedent(h1) == activity.h1.prettify()
         p = """\
         <p>
          Just wanted to leave a comment on this...
         </p>
         """
-        assert_equal(dedent(p), activity.p.prettify())
+        assert dedent(p) == activity.p.prettify()
 
     @td.with_tool('u/test-user-1', 'activity')
     @td.with_user_project('test-user-1')
@@ -170,11 +170,11 @@ class TestActivityController(TestController):
             create_timeline.side_effect = orig_create_timeline
             M.MonQTask.run_ready()
             # 3 aggregations: 1 actor, 1 follower, 1 project
-            assert_equal(create_timeline.call_count, 3)
+            assert create_timeline.call_count == 3
             create_timeline.reset_mock()
             self.app.get('/u/test-admin/activity/')
             self.app.get('/u/test-user-1/activity/')
-            assert_equal(create_timeline.call_count, 0)
+            assert create_timeline.call_count == 0
 
     @td.with_tool('test', 'activity')
     @patch('forgeactivity.main.g.director')
@@ -213,17 +213,17 @@ class TestActivityController(TestController):
         })]
         r = self.app.get('/p/test/activity/feed.rss')
         timeline = r.xml.find('channel')
-        assert_equal(1, len(timeline.findall('item')))
+        assert 1 == len(timeline.findall('item'))
         activity = timeline.find('item')
-        assert_equal(activity.find('pubDate').text,
+        assert (activity.find('pubDate').text ==
                      'Wed, 04 Dec 2013 21:48:19 -0000')
-        assert_equal(activity.find('title').text,
+        assert (activity.find('title').text ==
                      'Administrator 1 posted a comment on ticket #34')
-        assert_equal(activity.find('description').text,
+        assert (activity.find('description').text ==
                      'Just wanted to leave a comment on this...')
-        assert_equal(activity.find('guid').text,
+        assert (activity.find('guid').text ==
                      'http://localhost/p/test/unicode•º/?limit=25#ed7c')
-        assert_equal(activity.find('link').text,
+        assert (activity.find('link').text ==
                      'http://localhost/p/test/unicode%E2%80%A2%C2%BA/?limit=25#ed7c')
 
     @td.with_tool('test', 'activity')
@@ -244,10 +244,10 @@ class TestActivityController(TestController):
 
         r = self.app.get('/p/test/activity/feed.rss')
         timeline = r.xml.find('channel')
-        assert_equal(1, len(timeline.findall('item')))
+        assert 1 == len(timeline.findall('item'))
         activity = timeline.find('item')
 
-        assert_equal(activity.find('title').text, 'Administrator 1 created ticket #34')
+        assert activity.find('title').text == 'Administrator 1 created ticket #34'
 
     @td.with_tool('u/test-user-1', 'activity')
     @td.with_user_project('test-user-1')
@@ -287,15 +287,15 @@ class TestActivityController(TestController):
         })]
         r = self.app.get('/u/test-user-1/activity/feed.rss')
         timeline = r.xml.find('channel')
-        assert_equal(1, len(timeline.findall('item')))
+        assert 1 == len(timeline.findall('item'))
         activity = timeline.find('item')
-        assert_equal(activity.find('pubDate').text,
+        assert (activity.find('pubDate').text ==
                      'Wed, 04 Dec 2013 21:48:19 -0000')
-        assert_equal(activity.find('title').text,
+        assert (activity.find('title').text ==
                      'Administrator 1 posted a comment on ticket #34')
-        assert_equal(activity.find('description').text,
+        assert (activity.find('description').text ==
                      'Just wanted to leave a comment on this...')
-        assert_equal(activity.find('link').text,
+        assert (activity.find('link').text ==
                      'http://localhost/p/test/tickets/34/?limit=25#ed7c')
 
     @td.with_tool('test', 'activity')
@@ -335,20 +335,20 @@ class TestActivityController(TestController):
         })]
         r = self.app.get('/p/test/activity/feed.atom')
         timeline = r.xml
-        assert_equal(1, len(timeline.findall(
-            '{http://www.w3.org/2005/Atom}entry')))
+        assert 1 == len(timeline.findall(
+            '{http://www.w3.org/2005/Atom}entry'))
         activity = timeline.find('{http://www.w3.org/2005/Atom}entry')
-        assert_equal(
-            activity.find('{http://www.w3.org/2005/Atom}published').text,
+        assert (
+            activity.find('{http://www.w3.org/2005/Atom}published').text ==
             '2013-12-04T21:48:19Z')
-        assert_equal(
-            activity.find('{http://www.w3.org/2005/Atom}title').text,
+        assert (
+            activity.find('{http://www.w3.org/2005/Atom}title').text ==
             'Administrator 1 posted a comment on ticket #34')
-        assert_equal(
-            activity.find('{http://www.w3.org/2005/Atom}summary').text,
+        assert (
+            activity.find('{http://www.w3.org/2005/Atom}summary').text ==
             'Just wanted to leave a comment on this...')
-        assert_equal(
-            activity.find('{http://www.w3.org/2005/Atom}link').get('href'),
+        assert (
+            activity.find('{http://www.w3.org/2005/Atom}link').get('href') ==
             'http://localhost/p/test/tickets/34/?limit=25#ed7c')
 
     @td.with_tool('u/test-user-1', 'activity')
@@ -389,20 +389,20 @@ class TestActivityController(TestController):
         })]
         r = self.app.get('/u/test-user-1/activity/feed.atom')
         timeline = r.xml
-        assert_equal(1, len(timeline.findall(
-            '{http://www.w3.org/2005/Atom}entry')))
+        assert 1 == len(timeline.findall(
+            '{http://www.w3.org/2005/Atom}entry'))
         activity = timeline.find('{http://www.w3.org/2005/Atom}entry')
-        assert_equal(
-            activity.find('{http://www.w3.org/2005/Atom}published').text,
+        assert (
+            activity.find('{http://www.w3.org/2005/Atom}published').text ==
             '2013-12-04T21:48:19Z')
-        assert_equal(
-            activity.find('{http://www.w3.org/2005/Atom}title').text,
+        assert (
+            activity.find('{http://www.w3.org/2005/Atom}title').text ==
             'Administrator 1 posted a comment on ticket #34')
-        assert_equal(
-            activity.find('{http://www.w3.org/2005/Atom}summary').text,
+        assert (
+            activity.find('{http://www.w3.org/2005/Atom}summary').text ==
             'Just wanted to leave a comment on this...')
-        assert_equal(
-            activity.find('{http://www.w3.org/2005/Atom}link').get('href'),
+        assert (
+            activity.find('{http://www.w3.org/2005/Atom}link').get('href') ==
             'http://localhost/p/test/tickets/34/?limit=25#ed7c')
 
     @td.with_tool('u/test-user-1', 'activity')
@@ -459,7 +459,7 @@ class TestActivityController(TestController):
         activity3 = Activity(**dict(activity_data, node_id='User:abc', owner_id='User:abc'))
         ThreadLocalODMSession.flush_all()
         activity_id = str(activity._id)
-        assert_equal(Activity.query.find({'obj.activity_extras.summary': 'Sensitive private info, oops'}).count(), 3)
+        assert Activity.query.find({'obj.activity_extras.summary': 'Sensitive private info, oops'}).count() == 3
 
         self.app.post('/u/test-user-1/activity/delete_item',
                       {'activity_id': activity_id},
@@ -467,4 +467,4 @@ class TestActivityController(TestController):
                       status=200)
         ThreadLocalODMSession.flush_all()
 
-        assert_equal(Activity.query.find({'obj.activity_extras.summary': 'Sensitive private info, oops'}).count(), 0)
+        assert Activity.query.find({'obj.activity_extras.summary': 'Sensitive private info, oops'}).count() == 0
diff --git a/ForgeBlog/forgeblog/tests/functional/test_feeds.py b/ForgeBlog/forgeblog/tests/functional/test_feeds.py
index fde0322f4..530f8bdb4 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_feeds.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_feeds.py
@@ -68,10 +68,10 @@ class TestFeeds(TestController):
     def test_rss_feed_contains_self_link(self):
         r = self.app.get('/blog/feed.rss')
         # atom namespace included
-        assert_in('<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">', r)
+        assert '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">' in r
         # ...and atom:link points to feed url
-        assert_in('<atom:link href="http://localhost/blog/feed.rss" '
-                  'rel="self" type="application/rss+xml"></atom:link>', r)
+        assert ('<atom:link href="http://localhost/blog/feed.rss" '
+                  'rel="self" type="application/rss+xml"></atom:link>' in r)
 
     def test_post_feeds(self):
         self._post()
@@ -82,7 +82,7 @@ class TestFeeds(TestController):
         assert 'Nothing to see' in response
         self._post(title='test', text='*sometext*')
         response = self.app.get('/blog/feed')
-        assert_in('&lt;div class="markdown_content"&gt;&lt;p&gt;&lt;em&gt;sometext&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;',
+        assert ('&lt;div class="markdown_content"&gt;&lt;p&gt;&lt;em&gt;sometext&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;' in
                   response)
 
     def test_related_artifacts(self):
@@ -142,7 +142,7 @@ class TestFeeds(TestController):
         ThreadLocalORMSession.flush_all()
 
         resp = self.app.get(h.urlquote("/blog/" + self._blog_date() + "/my-pôst/feed.rss"))
-        assert_in('boring comment', resp)
+        assert 'boring comment' in resp
 
         resp = self.app.get("/blog/feed.rss")
-        assert_not_in('boring comment', resp)
\ No newline at end of file
+        assert 'boring comment' not in resp
\ No newline at end of file
diff --git a/ForgeBlog/forgeblog/tests/functional/test_rest.py b/ForgeBlog/forgeblog/tests/functional/test_rest.py
index a5219f2c3..9e8a87756 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_rest.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_rest.py
@@ -45,21 +45,21 @@ class TestBlogApi(TestRestApiBase):
             'labels': 'label1, label2'
         }
         r = self.api_post('/rest/p/test/blog/', **data)
-        assert_equal(
-            r.location, 'http://localhost/rest/p/test/blog/%s/%s/test/' %
+        assert (
+            r.location == 'http://localhost/rest/p/test/blog/%s/%s/test/' %
             (date.today().strftime("%Y"), date.today().strftime("%m")))
-        assert_equal(r.status_int, 201)
+        assert r.status_int == 201
         url = '/rest' + BM.BlogPost.query.find().first().url()
         r = self.api_get('/rest/p/test/blog/')
-        assert_equal(r.json['posts'][0]['title'], 'test')
-        assert_in(url, r.json['posts'][0]['url'])
+        assert r.json['posts'][0]['title'] == 'test'
+        assert url in r.json['posts'][0]['url']
 
         r = self.api_get(url)
-        assert_equal(r.json['title'], data['title'])
-        assert_equal(r.json['text'], data['text'])
-        assert_equal(r.json['author'], 'test-admin')
-        assert_equal(r.json['state'], data['state'])
-        assert_equal(r.json['labels'], data['labels'].split(','))
+        assert r.json['title'] == data['title']
+        assert r.json['text'] == data['text']
+        assert r.json['author'] == 'test-admin'
+        assert r.json['state'] == data['state']
+        assert r.json['labels'] == data['labels'].split(',')
 
     def test_update_post(self):
         data = {
@@ -69,7 +69,7 @@ class TestBlogApi(TestRestApiBase):
             'labels': 'label1, label2'
         }
         r = self.api_post('/rest/p/test/blog/', **data)
-        assert_equal(r.status_int, 201)
+        assert r.status_int == 201
         url = '/rest' + BM.BlogPost.query.find().first().url()
         data = {
             'text': 'test text2',
@@ -78,10 +78,10 @@ class TestBlogApi(TestRestApiBase):
         }
         self.api_post(url, **data)
         r = self.api_get(url)
-        assert_equal(r.json['title'], 'test')
-        assert_equal(r.json['text'], data['text'])
-        assert_equal(r.json['state'], data['state'])
-        assert_equal(r.json['labels'], data['labels'].split(','))
+        assert r.json['title'] == 'test'
+        assert r.json['text'] == data['text']
+        assert r.json['state'] == data['state']
+        assert r.json['labels'] == data['labels'].split(',')
 
     def test_delete_post(self):
         data = {
@@ -90,7 +90,7 @@ class TestBlogApi(TestRestApiBase):
             'labels': 'label1, label2'
         }
         r = self.api_post('/rest/p/test/blog/', **data)
-        assert_equal(r.status_int, 201)
+        assert r.status_int == 201
         url = '/rest' + BM.BlogPost.query.find().first().url()
         self.api_post(url, delete='')
         r = self.api_get(url, status=404)
@@ -149,16 +149,16 @@ class TestBlogApi(TestRestApiBase):
                       extra_environ={'username': '*anonymous'},
                       status=200)
         r = self.api_get(url)
-        assert_equal(r.json['title'], 'test2')
-        assert_equal(r.json['text'], 'test text2')
-        assert_equal(r.json['state'], 'published')
+        assert r.json['title'] == 'test2'
+        assert r.json['text'] == 'test text2'
+        assert r.json['state'] == 'published'
 
     def test_permission_draft_post(self):
         self.api_post('/rest/p/test/blog/', title='test',
                       text='test text', state='draft')
         r = self.app.get('/rest/p/test/blog/',
                          extra_environ={'username': '*anonymous'})
-        assert_equal(r.json['posts'], [])
+        assert r.json['posts'] == []
         url = '/rest' + BM.BlogPost.query.find().first().url()
         self.app.post(url,
                       params=dict(title='test2', text='test text2',
@@ -172,19 +172,19 @@ class TestBlogApi(TestRestApiBase):
         acl.append(anon_write)
         r = self.app.get('/rest/p/test/blog/',
                          extra_environ={'username': '*anonymous'})
-        assert_equal(r.json['posts'][0]['title'], 'test')
+        assert r.json['posts'][0]['title'] == 'test'
 
     def test_draft_post(self):
         self.api_post('/rest/p/test/blog/', title='test',
                       text='test text', state='draft')
         r = self.app.get('/rest/p/test/blog/',
                          extra_environ={'username': '*anonymous'})
-        assert_equal(r.json['posts'], [])
+        assert r.json['posts'] == []
         url = '/rest' + BM.BlogPost.query.find().first().url()
         self.api_post(url, state='published')
         r = self.app.get('/rest/p/test/blog/',
                          extra_environ={'username': '*anonymous'})
-        assert_equal(r.json['posts'][0]['title'], 'test')
+        assert r.json['posts'][0]['title'] == 'test'
 
     def test_pagination(self):
         self.api_post('/rest/p/test/blog/', title='test1',
@@ -194,23 +194,23 @@ class TestBlogApi(TestRestApiBase):
         self.api_post('/rest/p/test/blog/', title='test3',
                       text='test text3', state='published')
         r = self.api_get('/rest/p/test/blog/', limit='1', page='0')
-        assert_equal(r.json['posts'][0]['title'], 'test3')
-        assert_equal(len(r.json['posts']), 1)
-        assert_equal(r.json['count'], 3)
-        assert_equal(r.json['limit'], 1)
-        assert_equal(r.json['page'], 0)
+        assert r.json['posts'][0]['title'] == 'test3'
+        assert len(r.json['posts']) == 1
+        assert r.json['count'] == 3
+        assert r.json['limit'] == 1
+        assert r.json['page'] == 0
         r = self.api_get('/rest/p/test/blog/', limit='2', page='0')
-        assert_equal(r.json['posts'][0]['title'], 'test3')
-        assert_equal(r.json['posts'][1]['title'], 'test2')
-        assert_equal(len(r.json['posts']), 2)
-        assert_equal(r.json['count'], 3)
-        assert_equal(r.json['limit'], 2)
-        assert_equal(r.json['page'], 0)
+        assert r.json['posts'][0]['title'] == 'test3'
+        assert r.json['posts'][1]['title'] == 'test2'
+        assert len(r.json['posts']) == 2
+        assert r.json['count'] == 3
+        assert r.json['limit'] == 2
+        assert r.json['page'] == 0
         r = self.api_get('/rest/p/test/blog/', limit='1', page='2')
-        assert_equal(r.json['posts'][0]['title'], 'test1')
-        assert_equal(r.json['count'], 3)
-        assert_equal(r.json['limit'], 1)
-        assert_equal(r.json['page'], 2)
+        assert r.json['posts'][0]['title'] == 'test1'
+        assert r.json['count'] == 3
+        assert r.json['limit'] == 1
+        assert r.json['page'] == 2
 
     def test_has_access_no_params(self):
         self.api_get('/rest/p/test/blog/has_access', status=404)
@@ -222,13 +222,13 @@ class TestBlogApi(TestRestApiBase):
         r = self.api_get(
             '/rest/p/test/blog/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/blog/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
 
     def test_has_access_not_admin(self):
         """
@@ -244,13 +244,13 @@ class TestBlogApi(TestRestApiBase):
         r = self.api_get(
             '/rest/p/test/blog/has_access?user=test-admin&perm=post&access_token=ABCDEF',
             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/blog/has_access?user=*anonymous&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
 
     def test_create_post_limit_by_project(self):
         data = {
diff --git a/ForgeBlog/forgeblog/tests/functional/test_root.py b/ForgeBlog/forgeblog/tests/functional/test_root.py
index 11d5a7467..c0b18e42d 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_root.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_root.py
@@ -257,15 +257,15 @@ class Test(TestController):
         with h.push_config(tg.config, **{'forgeblog.rate_limits': '{"3600": 0}'}):
             r = self._post()
             wf = json.loads(self.webflash(r))
-            assert_equal(wf['status'], 'error')
-            assert_equal(wf['message'], 'Create/edit rate limit exceeded. Please try again later.')
+            assert wf['status'] == 'error'
+            assert wf['message'] == 'Create/edit rate limit exceeded. Please try again later.'
 
     def test_rate_limit_form(self):
         with h.push_config(tg.config, **{'forgeblog.rate_limits': '{"3600": 0}'}):
             r = self.app.get('/blog/new')
             wf = json.loads(self.webflash(r))
-            assert_equal(wf['status'], 'error')
-            assert_equal(wf['message'], 'Create/edit rate limit exceeded. Please try again later.')
+            assert wf['status'] == 'error'
+            assert wf['message'] == 'Create/edit rate limit exceeded. Please try again later.'
 
     def test_admin_external_feed_invalid(self):
         r = self.app.get('/blog/')
@@ -273,7 +273,7 @@ class Test(TestController):
         form = r.forms[0]
         form['new_exfeed'].value = 'asdfasdf'
         r = form.submit()
-        assert_in('Invalid', self.webflash(r))
+        assert 'Invalid' in self.webflash(r)
 
     def test_admin_external_feed_ok(self):
         # sidebar menu doesn't expose link to this, unless "forgeblog.exfeed" config is true, but can use form anyway
@@ -282,7 +282,7 @@ class Test(TestController):
         form = r.forms[0]
         form['new_exfeed'].value = 'https://example.com/feed.rss'
         r = form.submit()
-        assert_in('External feeds updated', self.webflash(r))
+        assert 'External feeds updated' in self.webflash(r)
 
         r = self.app.get('/admin/blog/exfeed')
         r.mustcontain('https://example.com/feed.rss')
diff --git a/ForgeBlog/forgeblog/tests/test_app.py b/ForgeBlog/forgeblog/tests/test_app.py
index 98cc59fda..bb224a057 100644
--- a/ForgeBlog/forgeblog/tests/test_app.py
+++ b/ForgeBlog/forgeblog/tests/test_app.py
@@ -51,26 +51,26 @@ class TestApp:
 
     @td.with_tool('test', 'Blog', 'blog')
     def test_sitemap_xml(self):
-        assert_equal([], c.app.sitemap_xml())
+        assert [] == c.app.sitemap_xml()
         BM.BlogPost.new(
             title='Blog Title',
             state='draft',
             text='This is my first blog Post',
         )
-        assert_equal([], c.app.sitemap_xml())
+        assert [] == c.app.sitemap_xml()
         BM.BlogPost.new(
             title='Blog Title',
             state='published',
             text='This is my first blog Post',
             deleted=True
         )
-        assert_equal([], c.app.sitemap_xml())
+        assert [] == c.app.sitemap_xml()
         BM.BlogPost.new(
             title='Blog Title',
             state='published',
             text='This is my first blog Post',
         )
-        assert_equal(1, len(c.app.sitemap_xml()))
+        assert 1 == len(c.app.sitemap_xml())
 
 
 class TestBulkExport:
@@ -107,14 +107,14 @@ class TestBulkExport:
         blog = json.loads(f.read())
         blog['posts'] = sorted(
             blog['posts'], key=lambda x: x['title'], reverse=True)
-        assert_equal(blog['posts'][0]['title'], 'Test2 title')
-        assert_equal(blog['posts'][0]['text'], 'test2 post')
-        assert_equal(blog['posts'][1]['title'], 'Test title')
-        assert_equal(blog['posts'][1]['text'], 'test post')
-        assert_equal(blog['posts'][1]['labels'],
+        assert blog['posts'][0]['title'] == 'Test2 title'
+        assert blog['posts'][0]['text'] == 'test2 post'
+        assert blog['posts'][1]['title'] == 'Test title'
+        assert blog['posts'][1]['text'] == 'test post'
+        assert (blog['posts'][1]['labels'] ==
                      ['the firstlabel', 'the second label'])
-        assert_equal(blog['posts'][1]['discussion_thread']
-                     ['posts'][0]['text'], 'test comment')
+        assert (blog['posts'][1]['discussion_thread']
+                     ['posts'][0]['text'] == 'test comment')
 
     @td.with_tool('test', 'Blog', 'blog')
     def test_export_with_attachments(self):
@@ -148,6 +148,6 @@ class TestBulkExport:
             post.discussion_thread._id,
             list(post.discussion_thread.post_class().query.find())[0].slug
         )
-        assert_equal(blog['posts'][0]['discussion_thread']['posts'][0]
-                     ['attachments'][0]['path'], file_path)
+        assert (blog['posts'][0]['discussion_thread']['posts'][0]
+                     ['attachments'][0]['path'] == file_path)
         assert os.path.exists(os.path.join(temp_dir, file_path))
\ No newline at end of file
diff --git a/ForgeBlog/forgeblog/tests/test_commands.py b/ForgeBlog/forgeblog/tests/test_commands.py
index 2e86b18ed..b1aa45196 100644
--- a/ForgeBlog/forgeblog/tests/test_commands.py
+++ b/ForgeBlog/forgeblog/tests/test_commands.py
@@ -122,14 +122,14 @@ def test_pull_rss_feeds(parsefeed):
     parsefeed.assert_called_with('http://example.com/news/feed/')
     posts = BM.BlogPost.query.find(
         {'app_config_id': tmp_app._id}).sort('timestamp', 1)
-    assert_equal(posts.count(), 4)
+    assert posts.count() == 4
     posts = posts.all()
-    assert_equal(posts[0].title, 'Test')
-    assert_equal(posts[0].text, 'This is a test [link](http://example.com/)')
-    assert_equal(posts[1].title, 'Default Title 2')
-    assert_equal(posts[1].text, 'Test feed [link](http://example.com/)')
-    assert_equal(posts[2].title, 'Default Title 3')
-    assert_equal(posts[2].text, rendered_html_content)
-    assert_equal(posts[3].title, 'Default Title 4')
-    assert_equal(posts[3].text, rendered_html_content)
+    assert posts[0].title == 'Test'
+    assert posts[0].text == 'This is a test [link](http://example.com/)'
+    assert posts[1].title == 'Default Title 2'
+    assert posts[1].text == 'Test feed [link](http://example.com/)'
+    assert posts[2].title == 'Default Title 3'
+    assert posts[2].text == rendered_html_content
+    assert posts[3].title == 'Default Title 4'
+    assert posts[3].text == rendered_html_content
 
diff --git a/ForgeBlog/forgeblog/tests/unit/test_blog_post.py b/ForgeBlog/forgeblog/tests/unit/test_blog_post.py
index 7d5e20108..56c5e2930 100644
--- a/ForgeBlog/forgeblog/tests/unit/test_blog_post.py
+++ b/ForgeBlog/forgeblog/tests/unit/test_blog_post.py
@@ -33,11 +33,11 @@ class TestBlogPost(BlogTestWithModel):
     def test_new(self):
         post = M.BlogPost.new(
             title='test', text='test message', state='published')
-        assert_equal(post.title, 'test')
-        assert_equal(post.text, 'test message')
-        assert_equal(post.state, 'published')
-        assert_equal(post.activity_extras['summary'], post.title)
-        assert_true('allura_id' in post.activity_extras)
+        assert post.title == 'test'
+        assert post.text == 'test message'
+        assert post.state == 'published'
+        assert post.activity_extras['summary'] == post.title
+        assert 'allura_id' in post.activity_extras
 
 
 class TestFeed(BlogTestWithModel):
@@ -57,7 +57,7 @@ class TestFeed(BlogTestWithModel):
             description=post.text,
             author=post.author(),
             pubdate=post.timestamp)
-        assert_equal(f.pubdate, datetime(2012, 10, 29, 9, 57, 21, 465000))
+        assert f.pubdate == datetime(2012, 10, 29, 9, 57, 21, 465000)
 
 
 class TestHtmlPreview(BlogTestWithModel):
@@ -77,14 +77,14 @@ class TestHtmlPreview(BlogTestWithModel):
                 "esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
                 "occaecat cupidatat non proident, sunt in culpa qui officia "
                 "deserunt mollit anim id est laborum.")
-        assert_equal(self._make_post(text).html_text_preview, wrapped(text))
+        assert self._make_post(text).html_text_preview == wrapped(text)
 
     def test_single_short_paragraph(self):
         text = ("Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
                 "sed do eiusmod tempor incididunt ut labore et dolore magna "
                 "aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
                 "ullamco laboris nisi ut aliquip ex ea commodo consequat.")
-        assert_equal(self._make_post(text).html_text_preview, wrapped(text))
+        assert self._make_post(text).html_text_preview == wrapped(text)
 
     def test_multi_paragraph_short(self):
         text = ("Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
@@ -100,7 +100,7 @@ class TestHtmlPreview(BlogTestWithModel):
                     '<p>Ut enim ad minim veniam, quis nostrud exercitation '
                     'ullamco laboris nisi ut aliquip ex ea commodo '
                     'consequat.</p></div>')
-        assert_equal(self._make_post(text).html_text_preview, expected)
+        assert self._make_post(text).html_text_preview == expected
 
     def test_multi_paragraph_long(self):
         text = ("Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
@@ -134,4 +134,4 @@ class TestHtmlPreview(BlogTestWithModel):
                     'anim id est laborum.... '
                     '<a class="" href="/p/test/blog/%s/%02i/untitled/">'
                     'read more</a></p></div>') % (now.year, now.month)
-        assert_equal(self._make_post(text).html_text_preview, expected)
+        assert self._make_post(text).html_text_preview == expected
diff --git a/ForgeChat/forgechat/tests/functional/test_root.py b/ForgeChat/forgechat/tests/functional/test_root.py
index 96426c140..024e3a460 100644
--- a/ForgeChat/forgechat/tests/functional/test_root.py
+++ b/ForgeChat/forgechat/tests/functional/test_root.py
@@ -39,9 +39,9 @@ class TestRootController(TestController):
         data = {'channel': 'test channel',
                 '_session_id': self.app.cookies['_session_id']}
         ch = CM.ChatChannel.query.get()
-        assert_equal(ch.channel, '')
+        assert ch.channel == ''
         resp = self.app.post('/p/test/admin/chat/configure', data)
         expected = {'status': 'ok', 'message': 'Chat options updated'}
-        assert_equal(json.loads(self.webflash(resp)), expected)
+        assert json.loads(self.webflash(resp)) == expected
         ch = CM.ChatChannel.query.get()
-        assert_equal(ch.channel, 'test channel')
+        assert ch.channel == 'test channel'
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
index bc4a0373a..a20cb16e9 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
@@ -156,8 +156,8 @@ class TestForumMessageHandling(TestController):
         self.user = M.User.query.get(username='root')
 
     def test_has_access(self):
-        assert_false(c.app.has_access(M.User.anonymous(), 'testforum'))
-        assert_true(c.app.has_access(M.User.query.get(username='root'), 'testforum'))
+        assert not c.app.has_access(M.User.anonymous(), 'testforum')
+        assert c.app.has_access(M.User.query.get(username='root'), 'testforum')
 
     def test_post(self):
         self._post('testforum', 'Test Thread', 'Nothing here')
@@ -168,19 +168,19 @@ class TestForumMessageHandling(TestController):
     def test_reply(self):
         self._post('testforum', 'Test Thread', 'Nothing here',
                    message_id='test_reply@domain.net')
-        assert_equal(FM.ForumThread.query.find().count(), 1)
+        assert FM.ForumThread.query.find().count() == 1
         posts = FM.ForumPost.query.find()
-        assert_equal(posts.count(), 1)
-        assert_equal(FM.ForumThread.query.get().num_replies, 1)
-        assert_equal(FM.ForumThread.query.get().first_post_id, 'test_reply@domain.net')
+        assert posts.count() == 1
+        assert FM.ForumThread.query.get().num_replies == 1
+        assert FM.ForumThread.query.get().first_post_id == 'test_reply@domain.net'
 
         post = posts.first()
         self._post('testforum', 'Test Reply', 'Nothing here, either',
                    message_id='test_reply-msg2@domain.net',
                    in_reply_to=['test_reply@domain.net'])
-        assert_equal(FM.ForumThread.query.find().count(), 1)
-        assert_equal(FM.ForumPost.query.find().count(), 2)
-        assert_equal(FM.ForumThread.query.get().first_post_id, 'test_reply@domain.net')
+        assert FM.ForumThread.query.find().count() == 1
+        assert FM.ForumPost.query.find().count() == 2
+        assert FM.ForumThread.query.get().first_post_id == 'test_reply@domain.net'
 
     def test_reply_using_references_headers(self):
         self._post('testforum', 'Test Thread', 'Nothing here',
@@ -193,8 +193,8 @@ class TestForumMessageHandling(TestController):
                    message_id='second-message-id',
                    in_reply_to=['some-other-id@not.helpful.com'],
                    references=refs)
-        assert_equal(FM.ForumThread.query.find().count(), 1)
-        assert_equal(FM.ForumPost.query.find().count(), 2)
+        assert FM.ForumThread.query.find().count() == 1
+        assert FM.ForumPost.query.find().count() == 2
 
         prev_post = FM.ForumPost.query.find().sort('timestamp', pymongo.DESCENDING).first()
         refs = M.Notification._references(thread, prev_post) + ['second-message-id']
@@ -202,8 +202,8 @@ class TestForumMessageHandling(TestController):
                    message_id='third-message-id',
                    # missing in_reply_to altogether
                    references=refs)
-        assert_equal(FM.ForumThread.query.find().count(), 1)
-        assert_equal(FM.ForumPost.query.find().count(), 3)
+        assert FM.ForumThread.query.find().count() == 1
+        assert FM.ForumPost.query.find().count() == 3
 
     def test_attach(self):
         # runs handle_artifact_message() with filename field
@@ -367,7 +367,7 @@ class TestForum(TestController):
         form['add_forum.description'] = '<a href="http://cnn.com">This is CNN</a>'
         form.submit()
         r = self.app.get('/discussion/')
-        assert_equal(len(r.html.findAll('a', rel='nofollow')), 2)
+        assert len(r.html.findAll('a', rel='nofollow')) == 2
 
     def test_forum_search(self):
         self.app.get('/discussion/search')
@@ -459,13 +459,13 @@ class TestForum(TestController):
             params[f.find('input', {'style': 'width: 90%'})['name']] = "this is my post"
             r = self.app.post('/discussion/save_new_topic', params=params)
 
-        assert_equal(5, FM.ForumPost.query.find({'status': 'ok'}).count())
+        assert 5 == FM.ForumPost.query.find({'status': 'ok'}).count()
 
         r = self.app.post('/discussion/testforum/moderate/save_moderation_bulk_user', params={
             'username': 'test-admin',
             'spam': '1'})
-        assert_in('5 posts marked as spam', self.webflash(r))
-        assert_equal(5, FM.ForumPost.query.find({'status': 'spam'}).count())
+        assert '5 posts marked as spam' in self.webflash(r)
+        assert 5 == FM.ForumPost.query.find({'status': 'spam'}).count()
 
     def test_posting(self):
         r = self.app.get('/discussion/create_topic/')
@@ -514,7 +514,7 @@ class TestForum(TestController):
         r = self.app.post('/discussion/save_new_topic', params=params)
         n = M.Notification.query.find(
             dict(subject="[test:discussion] this is <h2> o'clock")).first()
-        assert_in('---\n\n[this is &lt;h2&gt; o&#39;clock]', n.text)
+        assert '---\n\n[this is &lt;h2&gt; o&#39;clock]' in n.text
 
     def _set_anon_allowed(self):
         r = self.app.get('/admin/discussion/permissions')
@@ -557,7 +557,7 @@ class TestForum(TestController):
         assert 'name="approve"' not in r
         assert 'name="spam"' not in r
         assert "Post content" not in r
-        assert_equal(spam_checker.check.call_args[0][0], 'Test Thread\nPost content')
+        assert spam_checker.check.call_args[0][0] == 'Test Thread\nPost content'
 
         # assert unapproved thread replies do not appear
         f = thread.html.find('div', {'class': 'comment-row reply_post_form'}).find('form')
@@ -572,7 +572,7 @@ class TestForum(TestController):
         r = self.app.get(thread.request.url,
                          extra_environ=dict(username='*anonymous'))
         assert 'anon reply to anon post' not in r
-        assert_equal(spam_checker.check.call_args[0][0], 'anon reply to anon post content')
+        assert spam_checker.check.call_args[0][0] == 'anon reply to anon post content'
 
         # assert moderation controls appear for admin
         r = self.app.get(thread.request.url)
@@ -619,8 +619,8 @@ class TestForum(TestController):
         ThreadLocalORMSession.flush_all()
         t = M.Thread()
         p = M.Post(thread=t)
-        assert_in('TestRole', [r.name for r in c.project.named_roles])
-        assert_false(t.is_spam(p))
+        assert 'TestRole' in [r.name for r in c.project.named_roles]
+        assert not t.is_spam(p)
 
     def test_thread(self):
         r = self.app.get('/discussion/create_topic/')
@@ -717,7 +717,7 @@ class TestForum(TestController):
     def check_announcement_table(self, response, topic_name):
         assert response.html.find(text='Announcements')
         rows = self.get_table_rows(response, 'announcements')
-        assert_equal(len(rows), 1)
+        assert len(rows) == 1
         cell = rows[0].findAll('td', {'class': 'topic'})
         assert topic_name in str(cell)
 
@@ -739,7 +739,7 @@ class TestForum(TestController):
             flags='Announcement',
             discussion='testforum'))
         thread2 = FM.ForumThread.query.get(_id=thread_id)
-        assert_equal(thread2.flags, ['Announcement'])
+        assert thread2.flags == ['Announcement']
 
         # Check that announcements are on front discussion page
         r = self.app.get('/discussion/')
@@ -804,7 +804,7 @@ class TestForum(TestController):
         # Check that threads are ordered in reverse creation order
         r = self.app.get('/discussion/testforum/')
         rows = self.get_table_rows(r, 'forum_threads')
-        assert_equal(len(rows), 2)
+        assert len(rows) == 2
         assert 'topic2' in str(rows[0])
         assert 'topic1' in str(rows[1])
 
@@ -813,12 +813,12 @@ class TestForum(TestController):
             flags='Sticky',
             discussion='testforum'))
         thread1 = FM.ForumThread.query.get(_id=tid1)
-        assert_equal(thread1.flags, ['Sticky'])
+        assert thread1.flags == ['Sticky']
 
         # Check that Sticky thread is at the top
         r = self.app.get('/discussion/testforum/')
         rows = self.get_table_rows(r, 'forum_threads')
-        assert_equal(len(rows), 2)
+        assert len(rows) == 2
         assert 'topic1' in rows[0].text
         assert 'topic2' in rows[1].text
 
@@ -827,14 +827,14 @@ class TestForum(TestController):
             flags='',
             discussion='testforum'))
         thread1 = FM.ForumThread.query.get(_id=tid1)
-        assert_equal(thread1.flags, [])
+        assert thread1.flags == []
 
         # Would check that threads are again in reverse creation order,
         # but so far we actually sort by mod_date, and resetting a flag
         # updates it
         r = self.app.get('/discussion/testforum/')
         rows = self.get_table_rows(r, 'forum_threads')
-        assert_equal(len(rows), 2)
+        assert len(rows) == 2
         # assert 'topic2' in str(rows[0])
         # assert 'topic1' in str(rows[1])
 
@@ -868,7 +868,7 @@ class TestForum(TestController):
         # make sure the posts are in the original thread
         posts = thread.html.find('div', {'id': 'comment'}).findAll(
             'div', {'class': 'discussion-post'})
-        assert_equal(len(posts), 2)
+        assert len(posts) == 2
         # move the thread
         r = self.app.post(url + 'moderate', params=dict(
             flags='',
@@ -876,7 +876,7 @@ class TestForum(TestController):
         # make sure all the posts got moved
         posts = r.html.find('div', {'id': 'comment'}).findAll(
             'div', {'class': 'discussion-post'})
-        assert_equal(len(posts), 2)
+        assert len(posts) == 2
 
     def test_rename_thread(self):
         # make the topic
@@ -914,11 +914,11 @@ class TestForum(TestController):
         sidebar = r.html.find('div', {'id': 'sidebar'})
         sidebar_menu = str(sidebar)
         sidebar_links = [i['href'] for i in sidebar.findAll('a')]
-        assert_in("/p/test/discussion/create_topic/", sidebar_links)
-        assert_in("/p/test/discussion/new_forum", sidebar_links)
-        assert_in('<h3 class="">Help</h3>', sidebar_menu)
-        assert_in("/nf/markdown_syntax", sidebar_links)
-        assert_not_in("flag_as_spam", sidebar_links)
+        assert "/p/test/discussion/create_topic/" in sidebar_links
+        assert "/p/test/discussion/new_forum" in sidebar_links
+        assert '<h3 class="">Help</h3>' in sidebar_menu
+        assert "/nf/markdown_syntax" in sidebar_links
+        assert "flag_as_spam" not in sidebar_links
         r = self.app.get('/discussion/create_topic/')
         f = r.html.find('form', {'action': '/p/test/discussion/save_new_topic'})
         params = dict()
@@ -931,18 +931,18 @@ class TestForum(TestController):
         params[f.find('input', {'style': 'width: 90%'})['name']] = 'AAA'
         thread = self.app.post('/discussion/save_new_topic', params=params).follow()
         thread_sidebarmenu = str(thread.html.find('div', {'id': 'sidebar'}))
-        assert_in("flag_as_spam", thread_sidebarmenu)
+        assert "flag_as_spam" in thread_sidebarmenu
 
     def test_sidebar_menu_anon(self):
         r = self.app.get('/discussion/')
         sidebar = r.html.find('div', {'id': 'sidebar'})
         sidebar_menu = str(sidebar)
         sidebar_links = [i['href'] for i in sidebar.findAll('a')]
-        assert_in("/p/test/discussion/create_topic/", sidebar_links)
-        assert_in("/p/test/discussion/new_forum", sidebar_links)
-        assert_in('<h3 class="">Help</h3>', sidebar_menu)
-        assert_in("/nf/markdown_syntax", sidebar_links)
-        assert_not_in("flag_as_spam", sidebar_menu)
+        assert "/p/test/discussion/create_topic/" in sidebar_links
+        assert "/p/test/discussion/new_forum" in sidebar_links
+        assert '<h3 class="">Help</h3>' in sidebar_menu
+        assert "/nf/markdown_syntax" in sidebar_links
+        assert "flag_as_spam" not in sidebar_menu
         r = self.app.get('/discussion/create_topic/')
         f = r.html.find('form', {'action': '/p/test/discussion/save_new_topic'})
         params = dict()
@@ -956,7 +956,7 @@ class TestForum(TestController):
         thread = self.app.post('/discussion/save_new_topic',
                                params=params).follow(extra_environ=dict(username='*anonymous'))
         thread_sidebar_menu = str(thread.html.find('div', {'id': 'sidebar'}))
-        assert_not_in("flag_as_spam", thread_sidebar_menu)
+        assert "flag_as_spam" not in thread_sidebar_menu
 
     def test_feed(self):
         for ext in ['', '.rss', '.atom']:
@@ -1006,7 +1006,7 @@ class TestForum(TestController):
         # View the thread and make sure project last_updated is not updated
         self.app.get(url)
         timestamp_after = M.Project.query.get(shortname='test').last_updated
-        assert_equal(timestamp_before, timestamp_after)
+        assert timestamp_before == timestamp_after
 
 
 class TestForumStats(TestController):
@@ -1042,7 +1042,7 @@ class TestForumStats(TestController):
         ])
         r = self.app.get(
             '/discussion/stats_data?begin=2013-01-01&end=2013-01-06')
-        assert_equal(r.json, {
+        assert r.json == {
             'begin': '2013-01-01 00:00:00',
             'end': '2013-01-06 00:00:00',
             'data': [
@@ -1053,4 +1053,4 @@ class TestForumStats(TestController):
                 [1357344000000, 2],
                 [1357430400000, 0],
             ]
-        })
+        }
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_import.py b/ForgeDiscussion/forgediscussion/tests/functional/test_import.py
index 8a49ae218..ff7449d1e 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_import.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_import.py
@@ -109,15 +109,15 @@ class TestImportController(TestRestApiBase):  # TestController):
         return t.replace('T', ' ').replace('Z', '')
 
     def verify_ticket(self, from_api, org):
-        assert_equal(from_api['status'], org['status'])
-        assert_equal(from_api['description'], org['description'])
-        assert_equal(from_api['summary'], org['summary'])
-        assert_equal(from_api['ticket_num'], org['id'])
-        assert_equal(from_api['created_date'],
+        assert from_api['status'] == org['status']
+        assert from_api['description'] == org['description']
+        assert from_api['summary'] == org['summary']
+        assert from_api['ticket_num'] == org['id']
+        assert (from_api['created_date'] ==
                      self.time_normalize(org['date']))
-        assert_equal(from_api['mod_date'],
+        assert (from_api['mod_date'] ==
                      self.time_normalize(org['date_updated']))
-        assert_equal(from_api['custom_fields']
-                     ['_resolution'], org['resolution'])
-        assert_equal(from_api['custom_fields']['_cc'], org['cc'])
-        assert_equal(from_api['custom_fields']['_private'], org['private'])
+        assert (from_api['custom_fields']
+                     ['_resolution'] == org['resolution'])
+        assert from_api['custom_fields']['_cc'] == org['cc']
+        assert from_api['custom_fields']['_private'] == org['private']
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
index 5f42de636..b20d610a2 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
@@ -66,55 +66,55 @@ class TestRootRestController(TestDiscussionApiBase):
     def test_forum_list(self):
         forums = self.api_get('/rest/p/test/discussion/')
         forums = forums.json['forums']
-        assert_equal(len(forums), 2)
+        assert len(forums) == 2
         forums = sorted(forums, key=lambda x: x['name'])
-        assert_equal(forums[0]['name'], 'General Discussion')
-        assert_equal(
-            forums[0]['description'], 'Forum about anything you want to talk about.')
-        assert_equal(forums[0]['num_topics'], 2)
-        assert_equal(
-            forums[0]['url'], 'http://localhost/rest/p/test/discussion/general/')
-        assert_equal(forums[0]['last_post']['subject'], 'Hi guys')
-        assert_equal(forums[0]['last_post']['author'], 'test-admin')
-        assert_equal(forums[0]['last_post']['text'], 'Hi boys and girls')
-        assert_equal(forums[1]['name'], 'Say Héllo')
-        assert_equal(forums[1]['description'], 'Say héllo here')
-        assert_equal(forums[1]['num_topics'], 0)
-        assert_equal(
-            forums[1]['url'], 'http://localhost/rest/p/test/discussion/h%C3%A9llo/')
-        assert_equal(forums[1]['last_post'], None)
+        assert forums[0]['name'] == 'General Discussion'
+        assert (
+            forums[0]['description'] == 'Forum about anything you want to talk about.')
+        assert forums[0]['num_topics'] == 2
+        assert (
+            forums[0]['url'] == 'http://localhost/rest/p/test/discussion/general/')
+        assert forums[0]['last_post']['subject'] == 'Hi guys'
+        assert forums[0]['last_post']['author'] == 'test-admin'
+        assert forums[0]['last_post']['text'] == 'Hi boys and girls'
+        assert forums[1]['name'] == 'Say Héllo'
+        assert forums[1]['description'] == 'Say héllo here'
+        assert forums[1]['num_topics'] == 0
+        assert (
+            forums[1]['url'] == 'http://localhost/rest/p/test/discussion/h%C3%A9llo/')
+        assert forums[1]['last_post'] == None
 
     def test_forum(self):
         forum = self.api_get('/rest/p/test/discussion/general/')
         forum = forum.json['forum']
-        assert_equal(forum['name'], 'General Discussion')
-        assert_equal(
-            forum['description'], 'Forum about anything you want to talk about.')
+        assert forum['name'] == 'General Discussion'
+        assert (
+            forum['description'] == 'Forum about anything you want to talk about.')
         topics = forum['topics']
-        assert_equal(len(topics), 2)
-        assert_equal(topics[0]['subject'], 'Hi guys')
-        assert_equal(topics[0]['num_views'], 0)
-        assert_equal(topics[0]['num_replies'], 1)
-        assert_equal(topics[0]['last_post']['author'], 'test-admin')
-        assert_equal(topics[0]['last_post']['text'], 'Hi boys and girls')
+        assert len(topics) == 2
+        assert topics[0]['subject'] == 'Hi guys'
+        assert topics[0]['num_views'] == 0
+        assert topics[0]['num_replies'] == 1
+        assert topics[0]['last_post']['author'] == 'test-admin'
+        assert topics[0]['last_post']['text'] == 'Hi boys and girls'
         t = ForumThread.query.find({'subject': 'Hi guys'}).first()
         url = 'http://localhost/rest/p/test/discussion/general/thread/%s/' % t._id
-        assert_equal(topics[0]['url'], url)
-        assert_equal(topics[1]['subject'], 'Let\'s talk')
-        assert_equal(topics[1]['num_views'], 0)
-        assert_equal(topics[1]['num_replies'], 1)
-        assert_equal(topics[1]['last_post']['author'], 'test-admin')
-        assert_equal(topics[1]['last_post']['text'], '1st post')
+        assert topics[0]['url'] == url
+        assert topics[1]['subject'] == 'Let\'s talk'
+        assert topics[1]['num_views'] == 0
+        assert topics[1]['num_replies'] == 1
+        assert topics[1]['last_post']['author'] == 'test-admin'
+        assert topics[1]['last_post']['text'] == '1st post'
         t = ForumThread.query.find({'subject': 'Let\'s talk'}).first()
         url = 'http://localhost/rest/p/test/discussion/general/thread/%s/' % t._id
-        assert_equal(topics[1]['url'], url)
+        assert topics[1]['url'] == url
 
     def test_forum_show_ok_topics(self):
         forum = self.api_get('/rest/p/test/discussion/general/')
         forum = forum.json['forum']
-        assert_equal(forum['name'], 'General Discussion')
+        assert forum['name'] == 'General Discussion'
         topics = forum['topics']
-        assert_equal(len(topics), 2)
+        assert len(topics) == 2
         self.create_topic('general', 'Hi again', 'It should not be shown')
         t = ForumThread.query.find({'subject': 'Hi again'}).first()
         first_post = t.first_post
@@ -122,57 +122,57 @@ class TestRootRestController(TestDiscussionApiBase):
         first_post.commit()
         forum = self.api_get('/rest/p/test/discussion/general/')
         forum = forum.json['forum']
-        assert_equal(forum['name'], 'General Discussion')
+        assert forum['name'] == 'General Discussion'
         topics = forum['topics']
-        assert_equal(len(topics), 2)
+        assert len(topics) == 2
 
     def test_topic(self):
         forum = self.api_get('/rest/p/test/discussion/general/')
         forum = forum.json['forum']
-        assert_equal(forum['name'], 'General Discussion')
-        assert_equal(
-            forum['description'], 'Forum about anything you want to talk about.')
+        assert forum['name'] == 'General Discussion'
+        assert (
+            forum['description'] == 'Forum about anything you want to talk about.')
         topics = forum['topics']
         topic = self.api_get(topics[0]['url'][len('http://localhost'):])
         topic = topic.json['topic']
-        assert_equal(len(topic['posts']), 1)
-        assert_equal(topic['subject'], 'Hi guys')
-        assert_equal(topic['posts'][0]['text'], 'Hi boys and girls')
-        assert_equal(topic['posts'][0]['subject'], 'Hi guys')
-        assert_in('timestamp', topic['posts'][0])
-        assert_in('last_edited', topic['posts'][0])
+        assert len(topic['posts']) == 1
+        assert topic['subject'] == 'Hi guys'
+        assert topic['posts'][0]['text'] == 'Hi boys and girls'
+        assert topic['posts'][0]['subject'] == 'Hi guys'
+        assert 'timestamp' in topic['posts'][0]
+        assert 'last_edited' in topic['posts'][0]
 
     def test_forum_list_pagination(self):
         resp = self.app.get('/rest/p/test/discussion/?limit=1')
         forums = resp.json['forums']
-        assert_equal(len(forums), 1)
-        assert_equal(forums[0]['name'], 'General Discussion')
-        assert_equal(resp.json['count'], 2)
-        assert_equal(resp.json['page'], 0)
-        assert_equal(resp.json['limit'], 1)
+        assert len(forums) == 1
+        assert forums[0]['name'] == 'General Discussion'
+        assert resp.json['count'] == 2
+        assert resp.json['page'] == 0
+        assert resp.json['limit'] == 1
         resp = self.app.get('/rest/p/test/discussion/?limit=1&page=1')
         forums = resp.json['forums']
-        assert_equal(len(forums), 1)
-        assert_equal(forums[0]['name'], 'Say Héllo')
-        assert_equal(resp.json['count'], 2)
-        assert_equal(resp.json['page'], 1)
-        assert_equal(resp.json['limit'], 1)
+        assert len(forums) == 1
+        assert forums[0]['name'] == 'Say Héllo'
+        assert resp.json['count'] == 2
+        assert resp.json['page'] == 1
+        assert resp.json['limit'] == 1
 
     def test_forum_pagination(self):
         resp = self.app.get('/rest/p/test/discussion/general/?limit=1')
         topics = resp.json['forum']['topics']
-        assert_equal(len(topics), 1)
-        assert_equal(topics[0]['subject'], 'Hi guys')
-        assert_equal(resp.json['count'], 2)
-        assert_equal(resp.json['page'], 0)
-        assert_equal(resp.json['limit'], 1)
+        assert len(topics) == 1
+        assert topics[0]['subject'] == 'Hi guys'
+        assert resp.json['count'] == 2
+        assert resp.json['page'] == 0
+        assert resp.json['limit'] == 1
         resp = self.app.get('/rest/p/test/discussion/general/?limit=1&page=1')
         topics = resp.json['forum']['topics']
-        assert_equal(len(topics), 1)
-        assert_equal(topics[0]['subject'], 'Let\'s talk')
-        assert_equal(resp.json['count'], 2)
-        assert_equal(resp.json['page'], 1)
-        assert_equal(resp.json['limit'], 1)
+        assert len(topics) == 1
+        assert topics[0]['subject'] == 'Let\'s talk'
+        assert resp.json['count'] == 2
+        assert resp.json['page'] == 1
+        assert resp.json['limit'] == 1
 
     def test_topic_pagination(self):
         thread = ForumThread.query.find({'subject': 'Hi guys'}).first()
@@ -180,25 +180,25 @@ class TestRootRestController(TestDiscussionApiBase):
         url = '/rest/p/test/discussion/general/thread/%s/' % thread._id
         resp = self.app.get(url + '?limit=1')
         posts = resp.json['topic']['posts']
-        assert_equal(len(posts), 1)
-        assert_equal(posts[0]['text'], 'Hi boys and girls')
-        assert_equal(resp.json['count'], 2)
-        assert_equal(resp.json['page'], 0)
-        assert_equal(resp.json['limit'], 1)
+        assert len(posts) == 1
+        assert posts[0]['text'] == 'Hi boys and girls'
+        assert resp.json['count'] == 2
+        assert resp.json['page'] == 0
+        assert resp.json['limit'] == 1
         resp = self.app.get(url + '?limit=1&page=1')
         posts = resp.json['topic']['posts']
-        assert_equal(len(posts), 1)
-        assert_equal(posts[0]['text'], 'I am second post')
-        assert_equal(resp.json['count'], 2)
-        assert_equal(resp.json['page'], 1)
-        assert_equal(resp.json['limit'], 1)
+        assert len(posts) == 1
+        assert posts[0]['text'] == 'I am second post'
+        assert resp.json['count'] == 2
+        assert resp.json['page'] == 1
+        assert resp.json['limit'] == 1
 
     def test_topic_show_ok_only(self):
         thread = ForumThread.query.find({'subject': 'Hi guys'}).first()
         url = '/rest/p/test/discussion/general/thread/%s/' % thread._id
         resp = self.app.get(url)
         posts = resp.json['topic']['posts']
-        assert_equal(len(posts), 1)
+        assert len(posts) == 1
         thread.post('Hello', 'I am not ok post')
         last_post = thread.last_post
         last_post.status = 'pending'
@@ -206,7 +206,7 @@ class TestRootRestController(TestDiscussionApiBase):
         ThreadLocalORMSession.flush_all()
         resp = self.app.get(url)
         posts = resp.json['topic']['posts']
-        assert_equal(len(posts), 1)
+        assert len(posts) == 1
 
     def test_security(self):
         p = M.Project.query.get(shortname='test')
@@ -240,11 +240,11 @@ class TestRootRestController(TestDiscussionApiBase):
             form['forum-1.members_only'] = True
         form.submit()
         r = self.api_get('/rest/p/test/discussion/')
-        assert_equal(len(r.json['forums']), 2)
+        assert len(r.json['forums']) == 2
         r = self.app.get('/rest/p/test/discussion/',
                          extra_environ={'username': '*anonymous'})
-        assert_equal(len(r.json['forums']), 1)
-        assert_equal(r.json['forums'][0]['shortname'], 'general')
+        assert len(r.json['forums']) == 1
+        assert r.json['forums'][0]['shortname'] == 'general'
 
     def test_has_access_no_params(self):
         self.api_get('/rest/p/test/discussion/has_access', status=404)
@@ -256,13 +256,13 @@ class TestRootRestController(TestDiscussionApiBase):
         r = self.api_get(
             '/rest/p/test/discussion/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/discussion/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
 
     def test_has_access_not_admin(self):
         """
@@ -278,10 +278,10 @@ class TestRootRestController(TestDiscussionApiBase):
         r = self.api_get(
             '/rest/p/test/discussion/has_access?user=test-admin&perm=post',
             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/discussion/has_access?user=*anonymous&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/ForgeDiscussion/forgediscussion/tests/test_app.py b/ForgeDiscussion/forgediscussion/tests/test_app.py
index b93666e48..bb0b8d64b 100644
--- a/ForgeDiscussion/forgediscussion/tests/test_app.py
+++ b/ForgeDiscussion/forgediscussion/tests/test_app.py
@@ -48,7 +48,7 @@ class TestApp(TestDiscussionApiBase):  # creates some sample data
     @td.with_discussion
     def test_tickets_stats_24hr(self):
         # invoked normally via entry point
-        assert_equal(2, posts_24hr())
+        assert 2 == posts_24hr()
 
 
 class TestAppSitemap:
@@ -60,27 +60,27 @@ class TestAppSitemap:
 
     @td.with_discussion
     def test_sitemap_xml(self):
-        assert_equal([], c.app.sitemap_xml())
+        assert [] == c.app.sitemap_xml()
         forum = utils.create_forum(c.app, dict(
             shortname='test-forum',
             name="Test Forum",
             description="Test Forum Description",
         ))
         ThreadLocalORMSession.flush_all()
-        assert_equal([], c.app.sitemap_xml())
+        assert [] == c.app.sitemap_xml()
         thread = ForumThread(
             subject='test-thread',
         )
         thread.set_forum(forum)
         ThreadLocalORMSession.flush_all()
 
-        assert_equal([], c.app.sitemap_xml())
+        assert [] == c.app.sitemap_xml()
         thread.post(
             subject='test-post',
             text='this is a test post.',
         )
         ThreadLocalORMSession.flush_all()
-        assert_equal(1, len(c.app.sitemap_xml()))
+        assert 1 == len(c.app.sitemap_xml())
 
 
 class TestBulkExport(TestDiscussionApiBase):
@@ -98,22 +98,22 @@ class TestBulkExport(TestDiscussionApiBase):
         discussion = json.loads(f.read())
         forums = sorted(discussion['forums'], key=lambda x: x['name'])
 
-        assert_equal(forums[0]['shortname'], 'general')
-        assert_equal(
-            forums[0]['description'], 'Forum about anything you want to talk about.')
-        assert_equal(forums[0]['name'], 'General Discussion')
+        assert forums[0]['shortname'] == 'general'
+        assert (
+            forums[0]['description'] == 'Forum about anything you want to talk about.')
+        assert forums[0]['name'] == 'General Discussion'
         forums[0]['threads'] = sorted(forums[0]['threads'],
                                       key=lambda x: x['posts'][0]['subject'])
-        assert_equal(
-            forums[0]['threads'][0]['posts'][0]['text'], 'Hi boys and girls')
-        assert_equal(
-            forums[0]['threads'][0]['posts'][0]['subject'], 'Hi guys')
-        assert_equal(forums[0]['threads'][1]['posts'][0]['text'], '1st post')
-        assert_equal(
-            forums[0]['threads'][1]['posts'][0]['subject'], "Let's talk")
-        assert_equal(forums[1]['shortname'], 'héllo')
-        assert_equal(forums[1]['description'], 'Say héllo here')
-        assert_equal(forums[1]['name'], 'Say Héllo')
+        assert (
+            forums[0]['threads'][0]['posts'][0]['text'] == 'Hi boys and girls')
+        assert (
+            forums[0]['threads'][0]['posts'][0]['subject'] == 'Hi guys')
+        assert forums[0]['threads'][1]['posts'][0]['text'] == '1st post'
+        assert (
+            forums[0]['threads'][1]['posts'][0]['subject'] == "Let's talk")
+        assert forums[1]['shortname'] == 'héllo'
+        assert forums[1]['description'] == 'Say héllo here'
+        assert forums[1]['name'] == 'Say Héllo'
 
     def test_export_with_attachments(self):
         project = M.Project.query.get(shortname='test')
@@ -142,5 +142,5 @@ class TestBulkExport(TestDiscussionApiBase):
             post.slug,
             'test_file'
         )
-        assert_equal(threads[0]['posts'][0]['attachments'][0]['path'], file_path)
+        assert threads[0]['posts'][0]['attachments'][0]['path'] == file_path
         os.path.exists(file_path)
diff --git a/ForgeFeedback/forgefeedback/tests/functional/test_root.py b/ForgeFeedback/forgefeedback/tests/functional/test_root.py
index 371d491b6..37866f797 100644
--- a/ForgeFeedback/forgefeedback/tests/functional/test_root.py
+++ b/ForgeFeedback/forgefeedback/tests/functional/test_root.py
@@ -38,31 +38,31 @@ class TestFeedback(TestController):
         self.app.get('/feedback/')
         r = self.app.get('/p/test/feedback')
         assert 'test' in r
-        assert_in('<a href="/p/test/feedback/new_feedback">Feedback</a>', r)
+        assert '<a href="/p/test/feedback/new_feedback">Feedback</a>' in r
 
     def test_new_feedback(self):
         c.user = M.User.by_username('test-admin')
         self.app.get('/feedback/')
         r = self.app.get('/p/test/feedback/new_feedback/')
-        assert_in('Provide your feedback for <b> Test Project</b>', r)
-        assert_in('Enter your feedback here', r)
+        assert 'Provide your feedback for <b> Test Project</b>' in r
+        assert 'Enter your feedback here' in r
 
     def test_create_feedback(self):
         resp = post_feedback(self)
         resp = resp.follow()
-        assert_in('Good tool', resp)
+        assert 'Good tool' in resp
 
     def test_edit_feedback(self):
         post_feedback(self)
         data = {'rating': '2', 'description': 'Not useful'}
         resp = self.app.post('/p/test/feedback/edit_user_review', data)
         resp = resp.follow()
-        assert_in('Not useful', resp)
+        assert 'Not useful' in resp
 
     def test_delete_feedback(self):
         post_feedback(self)
         resp = self.app.post('/p/test/feedback/delete_feedback')
-        assert_in('Success', resp)
+        assert 'Success' in resp
 
 
 def post_feedback(self):
diff --git a/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py b/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py
index 19d335dd9..39ba76c70 100644
--- a/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py
+++ b/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py
@@ -48,7 +48,7 @@ def test_role_assignments():
     def check_access(perm):
         pred = security.has_access(c.app, perm)
         return pred(user=admin), pred(user=user), pred(user=anon)
-    assert_equal(check_access('read'), (True, True, True))
-    assert_equal(check_access('create'), (True, True, False))
-    assert_equal(check_access('update'), (True, False, False))
-    assert_equal(check_access('delete'), (True, False, False))
+    assert check_access('read') == (True, True, True)
+    assert check_access('create') == (True, True, False)
+    assert check_access('update') == (True, False, False)
+    assert check_access('delete') == (True, False, False)
diff --git a/ForgeFeedback/forgefeedback/tests/unit/test_feedback.py b/ForgeFeedback/forgefeedback/tests/unit/test_feedback.py
index b4996b310..a5863d3d4 100644
--- a/ForgeFeedback/forgefeedback/tests/unit/test_feedback.py
+++ b/ForgeFeedback/forgefeedback/tests/unit/test_feedback.py
@@ -29,14 +29,14 @@ class TestFeedback(FeedbackTestWithModel):
         feedback = M.Feedback()
         feedback.rating = '4'
         feedback.description = 'Very good tool'
-        assert_equal(feedback.rating, '4')
-        assert_equal(feedback.description, 'Very good tool')
-        assert_equal(feedback.activity_extras['summary'], feedback.description)
-        assert_true('allura_id' in feedback.activity_extras)
+        assert feedback.rating == '4'
+        assert feedback.description == 'Very good tool'
+        assert feedback.activity_extras['summary'] == feedback.description
+        assert 'allura_id' in feedback.activity_extras
 
     def test_index(self):
         feedback = M.Feedback()
         feedback.rating = '4'
         feedback.description = 'Good tool'
         result = feedback.index()
-        assert_equal(result["text"], 'Good tool')
+        assert result["text"] == 'Good tool'
diff --git a/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py b/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py
index a4ac76124..d636594a8 100644
--- a/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py
+++ b/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py
@@ -56,7 +56,7 @@ class TestFeedbackApp(FeedbackTestWithModel):
     def test_getRating(self):
         create_feedbacks()
         rating = feedback_main.RootController().getRating()
-        assert_equal(rating, 3.5)
+        assert rating == 3.5
 
     def test_edit_feedback(self):
         create_feedbacks()
diff --git a/ForgeFiles/forgefiles/tests/functional/test_root.py b/ForgeFiles/forgefiles/tests/functional/test_root.py
index da6b6754a..999fd5367 100644
--- a/ForgeFiles/forgefiles/tests/functional/test_root.py
+++ b/ForgeFiles/forgefiles/tests/functional/test_root.py
@@ -77,7 +77,7 @@ class TestFiles(TestController):
         data1 = {'folder_id': str(folder_object._id), 'remarks': 'Publishing new Version'}
         self.app.post('/p/test/files/publish_folder', data1)
         resp = self.app.get('/files/')
-        assert_equals(folder_object.published, True)
+        assert folder_object.published == True
 
     def test_link_file(self):
         file_object = upload_file(self)
@@ -85,7 +85,7 @@ class TestFiles(TestController):
         data1 = {'file_id': str(db_file_object._id)}
         self.app.post('/p/test/files/link_file', data1)
         resp = self.app.get('/files/')
-        assert_true(str(db_file_object.linked_to_download) in resp)
+        assert str(db_file_object.linked_to_download) in resp
 
     def test_disable_folder(self):
         create_folder(self)
@@ -93,7 +93,7 @@ class TestFiles(TestController):
         data1 = {'folder_id': str(folder_object._id), 'status': 'True'}
         self.app.post('/p/test/files/disable_folder', data1)
         resp = self.app.get('/files/')
-        assert_true(str(folder_object.disabled) in resp)
+        assert str(folder_object.disabled) in resp
 
     def test_disable_file(self):
         file_object = upload_file(self)
@@ -101,7 +101,7 @@ class TestFiles(TestController):
         data1 = {'file_id': str(db_file_object._id), 'status': 'True'}
         self.app.post('/p/test/files/disable_file', data1)
         resp = self.app.get('/files/')
-        assert_true(str(db_file_object.disabled) in resp)
+        assert str(db_file_object.disabled) in resp
 
     def test_delete_folder(self):
         create_folder(self)
diff --git a/ForgeFiles/forgefiles/tests/model/test_files.py b/ForgeFiles/forgefiles/tests/model/test_files.py
index a9a6b5eb3..38929e68e 100644
--- a/ForgeFiles/forgefiles/tests/model/test_files.py
+++ b/ForgeFiles/forgefiles/tests/model/test_files.py
@@ -34,10 +34,10 @@ class TestUpload(FilesTestWithModel):
         upload_folder.published = True
         upload_folder.remark = 'Publishing new Version'
         upload_folder.disabled = False
-        assert_equal(upload_folder.folder_name, 'testFolder')
-        assert_true(upload_folder.published)
-        assert_equal(upload_folder.remark, 'Publishing new Version')
-        assert_false(upload_folder.disabled)
+        assert upload_folder.folder_name == 'testFolder'
+        assert upload_folder.published
+        assert upload_folder.remark == 'Publishing new Version'
+        assert not upload_folder.disabled
 
     def test_upload_file(self):
         '''Creates an object of the UploadFiles Collection and tests its fields'''
@@ -48,7 +48,7 @@ class TestUpload(FilesTestWithModel):
         upload_file.file_url = 'TestFolder/testFile'
         upload_file.linked_to_download = True
         upload_file.published = False
-        assert_equal(upload_file.filename, 'testFile')
-        assert_equal(upload_file.filetype, 'project_file')
-        assert_true(upload_file.linked_to_download)
-        assert_false(upload_file.published)
+        assert upload_file.filename == 'testFile'
+        assert upload_file.filetype == 'project_file'
+        assert upload_file.linked_to_download
+        assert not upload_file.published
diff --git a/ForgeFiles/forgefiles/tests/test_files_roles.py b/ForgeFiles/forgefiles/tests/test_files_roles.py
index 203f331ce..2675a3e51 100644
--- a/ForgeFiles/forgefiles/tests/test_files_roles.py
+++ b/ForgeFiles/forgefiles/tests/test_files_roles.py
@@ -47,7 +47,7 @@ def test_role_assignments():
     def check_access(perm):
         pred = security.has_access(c.app, perm)
         return pred(user=admin), pred(user=user), pred(user=anon)
-    assert_equal(check_access('read'), (True, True, True))
-    assert_equal(check_access('create'), (True, False, False))
-    assert_equal(check_access('update'), (True, False, False))
-    assert_equal(check_access('delete'), (True, False, False))
+    assert check_access('read') == (True, True, True)
+    assert check_access('create') == (True, False, False)
+    assert check_access('update') == (True, False, False)
+    assert check_access('delete') == (True, False, False)
diff --git a/ForgeGit/forgegit/tests/functional/test_auth.py b/ForgeGit/forgegit/tests/functional/test_auth.py
index c5bb1cdab..d0bacfa6d 100644
--- a/ForgeGit/forgegit/tests/functional/test_auth.py
+++ b/ForgeGit/forgegit/tests/functional/test_auth.py
@@ -86,6 +86,6 @@ class TestGitUserPermissions(TestController):
     def test_list_repos(self):
         r = self.app.get('/auth/repo_permissions',
                          params=dict(username='test-admin'), status=200)
-        assert_equal(json.loads(r.text), {"allow_write": [
+        assert json.loads(r.text) == {"allow_write": [
             '/git/test/src-git',
-        ]})
+        ]}
diff --git a/ForgeGit/forgegit/tests/functional/test_controllers.py b/ForgeGit/forgegit/tests/functional/test_controllers.py
index 0a966d04e..cabeb2b0c 100644
--- a/ForgeGit/forgegit/tests/functional/test_controllers.py
+++ b/ForgeGit/forgegit/tests/functional/test_controllers.py
@@ -109,7 +109,7 @@ class TestUIController(TestController):
                         'added', 'eee.txt',
                         'added', 'ggg.txt']
         for i, item in enumerate(sortedCommits):
-            assert_equal(actualCommit[i], ''.join(item.findAll(text=True)).strip())
+            assert actualCommit[i] == ''.join(item.findAll(text=True)).strip()
 
 
 class TestRootController(_TestCase):
@@ -156,8 +156,8 @@ class TestRootController(_TestCase):
     def test_commit_browser_data(self):
         resp = self.app.get('/src-git/commit_browser_data')
         data = json.loads(resp.text)
-        assert_equal(
-            data['built_tree']['df30427c488aeab84b2352bdf88a3b19223f9d7a'],
+        assert (
+            data['built_tree']['df30427c488aeab84b2352bdf88a3b19223f9d7a'] ==
             {'url': '/p/test/src-git/ci/df30427c488aeab84b2352bdf88a3b19223f9d7a/',
              'oid': 'df30427c488aeab84b2352bdf88a3b19223f9d7a',
              'short_id': '[df3042]',
@@ -175,7 +175,7 @@ class TestRootController(_TestCase):
         assert '<div class="markdown_content"><p>Change README</div>' in resp, resp
         assert 'tree/README?format=raw">Download</a>' not in resp
         assert 'Tree' in resp.html.findAll('td')[2].text, resp.html.findAll('td')[2].text
-        assert_in('by Rick Copeland', squish_spaces(resp.html.findAll('td')[0].text))
+        assert 'by Rick Copeland' in squish_spaces(resp.html.findAll('td')[0].text)
         resp = self.app.get('/src-git/ci/1e146e67985dcd71c74de79613719bef7bddca4a/log/?path=/README')
         assert 'View' in resp.html.findAll('td')[2].text
         assert 'Change README' in resp
@@ -230,29 +230,29 @@ class TestRootController(_TestCase):
             r = self.app.get('/src-git/feed%s' % ext)
             channel = r.xml.find('channel')
             title = channel.find('title').text
-            assert_equal(title, 'test Git changes')
+            assert title == 'test Git changes'
             description = channel.find('description').text
-            assert_equal(description,
+            assert (description ==
                          'Recent changes to Git repository in test project')
             link = channel.find('link').text
-            assert_equal(link, 'http://localhost/p/test/src-git/')
+            assert link == 'http://localhost/p/test/src-git/'
             earliest_commit = channel.findall('item')[-1]
-            assert_equal(earliest_commit.find('title').text, 'Initial commit')
+            assert earliest_commit.find('title').text == 'Initial commit'
             link = 'http://localhost/p/test/src-git/ci/9a7df788cf800241e3bb5a849c8870f2f8259d98/'
-            assert_equal(earliest_commit.find('link').text, link)
-            assert_equal(earliest_commit.find('guid').text, link)
+            assert earliest_commit.find('link').text == link
+            assert earliest_commit.find('guid').text == link
 
         # .atom has slightly different structure
         prefix = '{http://www.w3.org/2005/Atom}'
         r = self.app.get('/src-git/feed.atom')
         title = r.xml.find(prefix + 'title').text
-        assert_equal(title, 'test Git changes')
+        assert title == 'test Git changes'
         link = r.xml.find(prefix + 'link').attrib['href']
-        assert_equal(link, 'http://localhost/p/test/src-git/')
+        assert link == 'http://localhost/p/test/src-git/'
         earliest_commit = r.xml.findall(prefix + 'entry')[-1]
-        assert_equal(earliest_commit.find(prefix + 'title').text, 'Initial commit')
+        assert earliest_commit.find(prefix + 'title').text == 'Initial commit'
         link = 'http://localhost/p/test/src-git/ci/9a7df788cf800241e3bb5a849c8870f2f8259d98/'
-        assert_equal(earliest_commit.find(prefix + 'link').attrib['href'], link)
+        assert earliest_commit.find(prefix + 'link').attrib['href'] == link
 
     def test_tree(self):
         ci = self._get_ci()
@@ -288,26 +288,26 @@ class TestRootController(_TestCase):
         ci = self._get_ci(repo='/p/test/weird-chars/')
         url = ci + 'tree/' + h.urlquote('привіт.txt') + '?format=raw'
         resp = self.app.get(url)
-        assert_in('Привіт!\nWhich means Hello!', resp.text)
-        assert_equal(six.ensure_text(resp.headers.get('Content-Disposition')),
+        assert 'Привіт!\nWhich means Hello!' in resp.text
+        assert (six.ensure_text(resp.headers.get('Content-Disposition')) ==
                      'attachment;filename="%D0%BF%D1%80%D0%B8%D0%B2%D1%96%D1%82.txt"')
 
         url = ci + 'tree/' + h.urlquote('with space.txt') + '?format=raw'
         resp = self.app.get(url)
-        assert_in('with space', resp.text)
-        assert_equal(six.ensure_text(resp.headers.get('Content-Disposition')),
+        assert 'with space' in resp.text
+        assert (six.ensure_text(resp.headers.get('Content-Disposition')) ==
                      'attachment;filename="with%20space.txt"')
 
         url = ci + 'tree/' + h.urlquote('with%2Furlquote-literal.txt') + '?format=raw'
         resp = self.app.get(url)
-        assert_in('%2F means /', resp.body.decode('utf-8'))
-        assert_equal(resp.headers.get('Content-Disposition'),
+        assert '%2F means /' in resp.body.decode('utf-8')
+        assert (resp.headers.get('Content-Disposition') ==
                      'attachment;filename="with%252Furlquote-literal.txt"')
 
         url = ci + 'tree/' + h.urlquote('with"&:specials.txt') + '?format=raw'
         resp = self.app.get(url)
-        assert_in('"&: encodes as %22%26%3A', resp.body.decode('utf-8'))
-        assert_equal(resp.headers.get('Content-Disposition'),
+        assert '"&: encodes as %22%26%3A' in resp.body.decode('utf-8')
+        assert (resp.headers.get('Content-Disposition') ==
                      'attachment;filename="with%22%26%3Aspecials.txt"')
 
     def test_file_too_large(self):
@@ -336,17 +336,17 @@ class TestRootController(_TestCase):
         ci = self._get_ci(repo='/p/test/weird-chars/')
         resp = self.app.get(h.urlquote(ci + 'tree/привіт.txt') + '?diff=407950e8fba4dbc108ffbce0128ed1085c52cfd7')
         diffhtml = str(resp.html.select_one('.diffbrowser'))
-        assert_in(textwrap.dedent('''\
+        assert (textwrap.dedent('''\
                     <span class="gd">--- a/привіт.txt</span><span class="w"></span>
                     <span class="gi">+++ b/привіт.txt</span><span class="w"></span>
                     <span class="gu">@@ -1 +1,2 @@</span><span class="w"></span>
                     <span class="w"> </span>Привіт!<span class="w"></span>
-                    <span class="gi">+Which means Hello!</span><span class="w"></span>'''),
+                    <span class="gi">+Which means Hello!</span><span class="w"></span>''') in
                   diffhtml)
 
         resp = self.app.get(h.urlquote(ci + 'tree/привіт.txt') + '?diff=407950e8fba4dbc108ffbce0128ed1085c52cfd7&diformat=sidebyside')
         diffhtml = str(resp.html.select_one('.diffbrowser'))
-        assert_in(textwrap.dedent('''\
+        assert (textwrap.dedent('''\
                     <thead>
                     <th class="lineno"></th>
                     <th>a/привіт.txt</th>
@@ -367,7 +367,7 @@ class TestRootController(_TestCase):
                     </pre></td>
                     <td class="lineno">2</td>
                     <td class="diff-add"><pre>Which means Hello!
-                    </pre></td>'''),
+                    </pre></td>''') in
                   diffhtml)
 
     def test_diff_view_mode(self):
@@ -434,31 +434,31 @@ class TestRootController(_TestCase):
         r = self.app.get(ci + 'tree/',
                          extra_environ={'username': str(user.username)})
         opts = self.subscription_options(r)
-        assert_equal(opts['subscribed'], False)
+        assert opts['subscribed'] == False
 
         # subscribe
         r = self.app.post(str(ci + 'tree/subscribe'),
                           {'subscribe': 'True'},
                           extra_environ={'username': str(user.username)})
-        assert_equal(r.json, {'status': 'ok', 'subscribed': True})
+        assert r.json == {'status': 'ok', 'subscribed': True}
         # user is subscribed
         assert M.Mailbox.subscribed(user_id=user._id)
         r = self.app.get(ci + 'tree/',
                          extra_environ={'username': str(user.username)})
         opts = self.subscription_options(r)
-        assert_equal(opts['subscribed'], True)
+        assert opts['subscribed'] == True
 
         # unsubscribe
         r = self.app.post(str(ci + 'tree/subscribe'),
                           {'unsubscribe': 'True'},
                           extra_environ={'username': str(user.username)})
-        assert_equal(r.json, {'status': 'ok', 'subscribed': False})
+        assert r.json == {'status': 'ok', 'subscribed': False}
         # user is not subscribed
         assert not M.Mailbox.subscribed(user_id=user._id)
         r = self.app.get(ci + 'tree/',
                          extra_environ={'username': str(user.username)})
         opts = self.subscription_options(r)
-        assert_equal(opts['subscribed'], False)
+        assert opts['subscribed'] == False
 
     def test_timezone(self):
         ci = self._get_ci()
@@ -501,15 +501,15 @@ class TestRootController(_TestCase):
         self.setup_testgit_index_repo()
         r = self.app.get('/p/test/testgit-index/ci/master/tree/index/')
         form = r.html.find('form', 'tarball')
-        assert_equal(form.get('action'), '/p/test/testgit-index/ci/master/tarball')
-        assert_equal(form.input.get('value'), '/index')
+        assert form.get('action') == '/p/test/testgit-index/ci/master/tarball'
+        assert form.input.get('value') == '/index'
 
     def test_default_branch(self):
-        assert_equal(c.app.default_branch_name, 'master')
+        assert c.app.default_branch_name == 'master'
         c.app.repo.set_default_branch('zz')
-        assert_equal(c.app.default_branch_name, 'zz')
+        assert c.app.default_branch_name == 'zz'
         c.app.repo.set_default_branch('master')
-        assert_equal(c.app.default_branch_name, 'master')
+        assert c.app.default_branch_name == 'master'
 
     def test_set_default_branch(self):
         r = self.app.get('/p/test/admin/src-git/set_default_branch_name')
@@ -521,41 +521,41 @@ class TestRootController(_TestCase):
         r = self.app.get('/p/test/src-git/').follow().follow()
         assert '<span class="scm-branch-label">zz</span>' in r
         # 'bad' is a file name which in zz, but not in master
-        assert_in('bad</a>', r)
+        assert 'bad</a>' in r
 
         self.app.post('/p/test/admin/src-git/set_default_branch_name',
                       params={'branch_name': 'master'})
         r = self.app.get('/p/test/src-git/').follow().follow()
-        assert_not_in('bad</a>', r)
-        assert_in('README</a>', r)
+        assert 'bad</a>' not in r
+        assert 'README</a>' in r
 
     def test_set_checkout_url(self):
         r = self.app.get('/p/test/admin/src-git/checkout_url')
         r.form['external_checkout_url'].value = 'http://foo.bar/baz'
         r.form['merge_disabled'].checked = True
         r = r.form.submit()
-        assert_equal(json.loads(self.webflash(r))['message'],
+        assert (json.loads(self.webflash(r))['message'] ==
                      "External checkout URL successfully changed. One-click merge disabled.")
         # for some reason c.app.config.options has old values still
         app_config = M.AppConfig.query.get(_id=c.app.config._id)
-        assert_equal(app_config.options['external_checkout_url'], 'http://foo.bar/baz')
-        assert_equal(app_config.options['merge_disabled'], True)
+        assert app_config.options['external_checkout_url'] == 'http://foo.bar/baz'
+        assert app_config.options['merge_disabled'] == True
 
     def test_refresh(self):
         r = self.app.get('/p/test/src-git/refresh')
-        assert_in('refresh queued', r)
-        assert_equal(1, M.MonQTask.query.find(dict(task_name='allura.tasks.repo_tasks.refresh')).count())
+        assert 'refresh queued' in r
+        assert 1 == M.MonQTask.query.find(dict(task_name='allura.tasks.repo_tasks.refresh')).count()
 
         r = self.app.get('/p/test/src-git/refresh', extra_environ={'HTTP_REFERER': '/p/test/src-git/'},
                          status=302)
-        assert_in('is being refreshed', self.webflash(r))
-        assert_equal(2, M.MonQTask.query.find(dict(task_name='allura.tasks.repo_tasks.refresh')).count())
+        assert 'is being refreshed' in self.webflash(r)
+        assert 2 == M.MonQTask.query.find(dict(task_name='allura.tasks.repo_tasks.refresh')).count()
 
 
 class TestRestController(_TestCase):
     def test_index(self):
         resp = self.app.get('/rest/p/test/src-git/', status=200)
-        assert_equal(resp.json, {
+        assert resp.json == {
             'api_url': 'http://localhost/rest/p/test/src-git/',
             'url': 'http://localhost/p/test/src-git/',
             'mount_label': 'Git',
@@ -563,7 +563,7 @@ class TestRestController(_TestCase):
             'name': 'git',
             'clone_url_file': '/srv/git/p/test/testgit',  # should be "src-git" but test data is weird?
             'commit_count': 5,
-        })
+        }
 
     def test_commits(self):
         self.app.get('/rest/p/test/src-git/commits', status=200)
@@ -588,13 +588,13 @@ class TestHasAccessAPI(TestRestApiBase):
         r = self.api_get(
             '/rest/p/test/src-git/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/src-git/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
 
     def test_has_access_not_admin(self):
         """
@@ -610,13 +610,13 @@ class TestHasAccessAPI(TestRestApiBase):
         r = self.api_get(
             '/rest/p/test/src-git/has_access?user=test-admin&perm=create',
             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/src-git/has_access?user=test-user&perm=create',
             user='root')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['result'], False)
+        assert r.status_int == 200
+        assert r.json['result'] == False
 
 
 class TestFork(_TestCase):
@@ -723,20 +723,20 @@ class TestFork(_TestCase):
 
     def test_merge_request_detail_view(self):
         r, mr_num = self._request_merge()
-        assert_in('wants to merge', r)
+        assert 'wants to merge' in r
 
         merge_instructions = r.html.findAll('textarea')[0].getText()
-        assert_in('git checkout master', merge_instructions)
-        assert_in('git fetch /srv/git/p/test2/code master', merge_instructions)
+        assert 'git checkout master' in merge_instructions
+        assert 'git fetch /srv/git/p/test2/code master' in merge_instructions
         c_id = self.forked_repo.get_heads()[0]['object_id']
-        assert_in(f'git merge {c_id}', merge_instructions)
+        assert f'git merge {c_id}' in merge_instructions
         assert_regexp_matches(str(r), r'[0-9]+ seconds? ago')
 
         merge_form = r.html.find('div', {'class': 'merge-help-text merge-ok'})
         assert merge_form
-        assert_in('Merge request has no conflicts. You can merge automatically.', merge_form.getText())
+        assert 'Merge request has no conflicts. You can merge automatically.' in merge_form.getText()
 
-        assert_not_in('Improve documentation', r)  # no details yet
+        assert 'Improve documentation' not in r  # no details yet
 
         # a task is busy/ready to compute
         r = self.app.get('/p/test/src-git/merge-requests/1/commits_html', status=202)  # 202 used for "busy"
@@ -746,16 +746,16 @@ class TestFork(_TestCase):
         ThreadLocalORMSession.close_all()  # close ming connections so that new data gets loaded later
 
         def assert_commit_details(r):
-            assert_in('Improve documentation', r.text)
+            assert 'Improve documentation' in r.text
             revs = r.html.findAll('tr', attrs={'class': 'rev'})
-            assert_equal(len(revs), 1)
+            assert len(revs) == 1
             rev_links = revs[0].findAll('a', attrs={'class': 'rev'})
             browse_links = revs[0].findAll('a', attrs={'class': 'browse'})
-            assert_equal(rev_links[0].get('href'), '/p/test2/code/ci/%s/' % c_id)
-            assert_equal(rev_links[0].getText(), '[%s]' % c_id[:6])
-            assert_equal(browse_links[0].get('href'),
+            assert rev_links[0].get('href') == '/p/test2/code/ci/%s/' % c_id
+            assert rev_links[0].getText() == '[%s]' % c_id[:6]
+            assert (browse_links[0].get('href') ==
                          '/p/test2/code/ci/%s/tree' % c_id)
-            assert_equal(browse_links[0].getText().strip(), 'Tree')
+            assert browse_links[0].getText().strip() == 'Tree'
 
         r = self.app.get('/p/test/src-git/merge-requests/1/commits_html', status=200)
         assert_commit_details(r)
@@ -764,16 +764,16 @@ class TestFork(_TestCase):
         assert_commit_details(r)
 
         r = self.app.get('/p/test/src-git/merge-requests/1/can_merge_task_status')
-        assert_equal(r.json, {'status': 'ready'})
+        assert r.json == {'status': 'ready'}
         r = self.app.get('/p/test/src-git/merge-requests/1/can_merge_result')
-        assert_equal(r.json, {'can_merge': None})
+        assert r.json == {'can_merge': None}
         r = self.app.get('/p/test/src-git/merge-requests/1/merge_task_status')
-        assert_equal(r.json, {'status': None})
+        assert r.json == {'status': None}
 
     def test_merge_request_detail_noslash(self):
         self._request_merge()
         r = self.app.get('/p/test/src-git/merge-requests/1', status=301)
-        assert_equal(r.location, 'http://localhost/p/test/src-git/merge-requests/1/')
+        assert r.location == 'http://localhost/p/test/src-git/merge-requests/1/'
 
     def test_merge_request_with_deleted_repo(self):
         self._request_merge()
@@ -811,19 +811,19 @@ class TestFork(_TestCase):
     def test_merge_request_default_branches(self):
         _select_val = lambda r, n: r.html.find('select', {'name': n}).find(selected=True).string
         r = self.app.get('/p/test2/code/request_merge')
-        assert_equal(_select_val(r, 'source_branch'), 'master')
-        assert_equal(_select_val(r, 'target_branch'), 'master')
+        assert _select_val(r, 'source_branch') == 'master'
+        assert _select_val(r, 'target_branch') == 'master'
         r = self.app.get('/p/test2/code/ci/zz/tree/').click('Request Merge')
-        assert_equal(_select_val(r, 'source_branch'), 'zz')
-        assert_equal(_select_val(r, 'target_branch'), 'master')
+        assert _select_val(r, 'source_branch') == 'zz'
+        assert _select_val(r, 'target_branch') == 'master'
         GM.Repository.query.get(_id=c.app.repo._id).default_branch_name = 'zz'
         ThreadLocalORMSession.flush_all()
         r = self.app.get('/p/test2/code/request_merge')
-        assert_equal(_select_val(r, 'source_branch'), 'master')
-        assert_equal(_select_val(r, 'target_branch'), 'zz')
+        assert _select_val(r, 'source_branch') == 'master'
+        assert _select_val(r, 'target_branch') == 'zz'
         r = self.app.get('/p/test2/code/ci/zz/tree/').click('Request Merge')
-        assert_equal(_select_val(r, 'source_branch'), 'zz')
-        assert_equal(_select_val(r, 'target_branch'), 'zz')
+        assert _select_val(r, 'source_branch') == 'zz'
+        assert _select_val(r, 'target_branch') == 'zz'
 
     def test_merge_request_with_branch(self):
         def get_mr_page(r):
@@ -955,7 +955,7 @@ class TestFork(_TestCase):
         mr_commits.side_effect = Exception
         r = self.app.get('/p/test/src-git/merge-requests/%s/' % mr_num)
         # errors don't show up on the page directly any more, so just assert that the bg task is there
-        assert_in('commits-loading', r)
+        assert 'commits-loading' in r
         self.app.get('/p/test/src-git/merge-requests/%s/commits_html' % mr_num, status=202)  # 202 used for "busy"
 
     def test_merge_request_validation_error(self):
@@ -1042,9 +1042,9 @@ class TestGitRename(TestController):
 
         # the diff portion of the output
         resp_no_ws = re.sub(r'\s+', '', str(resp))
-        assert_in('<a href="/p/test/src-git/ci/fbb0644603bb6ecee3ebb62efe8c86efc9b84ee6/tree/f.txt" rel="nofollow">f.txt</a>'
+        assert ('<a href="/p/test/src-git/ci/fbb0644603bb6ecee3ebb62efe8c86efc9b84ee6/tree/f.txt" rel="nofollow">f.txt</a>'
                   'to<a href="/p/test/src-git/ci/b120505a61225e6c14bee3e5b5862db81628c35c/tree/f2.txt" rel="nofollow">f2.txt</a>'
-                  .replace(' ', ''), resp_no_ws)
+                  .replace(' ', '') in resp_no_ws)
         assert '<span class="empty-diff">File was renamed.</span>' in resp
 
     def test_directory_changed_type(self):
@@ -1052,11 +1052,11 @@ class TestGitRename(TestController):
         resp = self.app.get('/src-git/ci/7b1c9ef214eb0ef8c06bada0966dd941f442beec/')
 
         resp_no_ws = re.sub(r'\s+', '', str(resp))
-        assert_in('<a href="/p/test/src-git/ci/7b1c9ef214eb0ef8c06bada0966dd941f442beec/tree/b_dir" rel="nofollow">b_dir</a>'
+        assert ('<a href="/p/test/src-git/ci/7b1c9ef214eb0ef8c06bada0966dd941f442beec/tree/b_dir" rel="nofollow">b_dir</a>'
                   '</h6>'
                   '<div id="diff-3" class="inline-diff-body">'
                   '<span class="empty-diff">Symlink.</span>'
-                  .replace(' ', ''), resp_no_ws)
+                  .replace(' ', '') in resp_no_ws)
 
     def test_symlink_in_tree(self):
         # change a_dir to a file; b_dir to a symlink
@@ -1085,7 +1085,7 @@ class TestGitBranch(TestController):
     def test_exotic_default_branch(self):
         r = self.app.get('/src-git/').follow().follow()
         assert 'README</a>' in r
-        assert_equal(c.app.repo.get_default_branch('master'), 'test')
+        assert c.app.repo.get_default_branch('master') == 'test'
 
     def test_branch_with_slashes(self):
         branches_page = self.app.get('/src-git/ref/master/branches/')
@@ -1126,19 +1126,19 @@ class TestIncludeMacro(_TestCase):
         setup_global_objects()
 
     def test_parse_repo(self):
-        assert_equal(macro.parse_repo('app'), None)
-        assert_equal(macro.parse_repo('proj:app'), None)
-        assert_equal(macro.parse_repo('nbhd:test:src-git'), None)
-        assert_equal(macro.parse_repo('a:b:c:d:e:f'), None)
-        assert_not_equal(macro.parse_repo('src-git'), None)
-        assert_not_equal(macro.parse_repo('test:src-git'), None)
-        assert_not_equal(macro.parse_repo('p:test:src-git'), None)
+        assert macro.parse_repo('app') == None
+        assert macro.parse_repo('proj:app') == None
+        assert macro.parse_repo('nbhd:test:src-git') == None
+        assert macro.parse_repo('a:b:c:d:e:f') == None
+        assert macro.parse_repo('src-git') != None
+        assert macro.parse_repo('test:src-git') != None
+        assert macro.parse_repo('p:test:src-git') != None
 
     def test_include_file_no_repo(self):
         expected = '[[include repo %s (not found)]]'
-        assert_equal(macro.include_file(None), expected % None)
-        assert_equal(macro.include_file('a:b'), expected % 'a:b')
-        assert_equal(macro.include_file('repo'), expected % 'repo')
+        assert macro.include_file(None) == expected % None
+        assert macro.include_file('a:b') == expected % 'a:b'
+        assert macro.include_file('repo') == expected % 'repo'
 
     def test_include_file_permissions(self):
         h.set_context('test', 'src-git', neighborhood='Projects')
@@ -1149,13 +1149,13 @@ class TestIncludeMacro(_TestCase):
             acl.remove(read_perm)
         c.user = M.User.anonymous()
         expected = "[[include: you don't have a read permission for repo src-git]]"
-        assert_equal(macro.include_file('src-git'), expected)
+        assert macro.include_file('src-git') == expected
 
     def test_include_file_cant_find_file(self):
         expected = "[[include can't find file %s in revision %s]]"
-        assert_equal(macro.include_file('src-git', 'a.txt'),
+        assert (macro.include_file('src-git', 'a.txt') ==
                      expected % ('a.txt', '1e146e67985dcd71c74de79613719bef7bddca4a'))
-        assert_equal(macro.include_file('src-git', 'a.txt', '6a45885ae7347f1cac5103b0050cc1be6a1496c8'),
+        assert (macro.include_file('src-git', 'a.txt', '6a45885ae7347f1cac5103b0050cc1be6a1496c8') ==
                      expected % ('a.txt', '6a45885ae7347f1cac5103b0050cc1be6a1496c8'))
 
     @patch('allura.model.repo.Blob.has_pypeline_view', new_callable=PropertyMock)
@@ -1164,9 +1164,9 @@ class TestIncludeMacro(_TestCase):
         has_html_view.return_value = False
         has_pypeline_view.return_value = False
         expected = "[[include can't display file README in revision 1e146e67985dcd71c74de79613719bef7bddca4a]]"
-        assert_equal(macro.include_file('src-git', 'README'), expected)
+        assert macro.include_file('src-git', 'README') == expected
 
     def test_include_file_display(self):
         result = macro.include_file('src-git', 'README')
-        assert_in('This is readme', result)
-        assert_in('Another Line', result)
+        assert 'This is readme' in result
+        assert 'Another Line' in result
diff --git a/ForgeGit/forgegit/tests/model/test_repository.py b/ForgeGit/forgegit/tests/model/test_repository.py
index 9fb886289..bb687b91b 100644
--- a/ForgeGit/forgegit/tests/model/test_repository.py
+++ b/ForgeGit/forgegit/tests/model/test_repository.py
@@ -85,8 +85,8 @@ class TestNewGit(unittest.TestCase):
         c.lcid_cache = {}
         self.rev.tree.ls()
         # print self.rev.tree.readme()
-        assert_equal(self.rev.tree.readme(), (
-            'README', 'This is readme\nAnother Line\n'))
+        assert self.rev.tree.readme() == (
+            'README', 'This is readme\nAnother Line\n')
         assert self.rev.tree.path() == '/'
         assert self.rev.tree.url() == (
             '/p/test/src-git/ci/'
@@ -109,17 +109,17 @@ class TestNewGit(unittest.TestCase):
                 '/p/test/src-git/ci/'
                 '1e146e67985dcd71c74de79613719bef7bddca4a/')
 
-        assert_equal(self.rev.authored_user, None)
-        assert_equal(self.rev.committed_user, None)
+        assert self.rev.authored_user == None
+        assert self.rev.committed_user == None
         user = M.User.upsert('rick')
         email = user.claim_address('rcopeland@geek.net')
         email.confirmed = True
         session(email).flush(email)
         rev = self.repo.commit(self.rev._id)  # to update cached values of LazyProperty
-        assert_equal(rev.authored_user, user)
-        assert_equal(rev.committed_user, user)
-        assert_equal(
-            sorted(rev.webhook_info.keys()),
+        assert rev.authored_user == user
+        assert rev.committed_user == user
+        assert (
+            sorted(rev.webhook_info.keys()) ==
             sorted(['id', 'url', 'timestamp', 'message', 'author',
                     'committer', 'added', 'removed', 'renamed', 'modified', 'copied']))
 
@@ -257,15 +257,15 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
 
     def test_log_id_only(self):
         entries = list(self.repo.log(id_only=True))
-        assert_equal(entries, [
+        assert entries == [
             '1e146e67985dcd71c74de79613719bef7bddca4a',
             'df30427c488aeab84b2352bdf88a3b19223f9d7a',
             '6a45885ae7347f1cac5103b0050cc1be6a1496c8',
-            '9a7df788cf800241e3bb5a849c8870f2f8259d98'])
+            '9a7df788cf800241e3bb5a849c8870f2f8259d98']
 
     def test_log(self):
         entries = list(self.repo.log(id_only=False))
-        assert_equal(entries, [
+        assert entries == [
             {'authored': {'date': datetime.datetime(2010, 10, 7, 18, 44, 11),
                           'email': 'rcopeland@geek.net',
                           'name': 'Rick Copeland'},
@@ -314,15 +314,15 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
              'refs': [],
              'size': None,
              'rename_details': {}},
-        ])
+        ]
 
     def test_log_unicode(self):
         entries = list(self.repo.log(path='völundr', id_only=False))
-        assert_equal(entries, [])
+        assert entries == []
 
     def test_log_file(self):
         entries = list(self.repo.log(path='README', id_only=False))
-        assert_equal(entries, [
+        assert entries == [
             {'authored': {'date': datetime.datetime(2010, 10, 7, 18, 44, 11),
                           'email': 'rcopeland@geek.net',
                           'name': 'Rick Copeland'},
@@ -347,7 +347,7 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
              'refs': [],
              'size': 15,
              'rename_details': {}},
-        ])
+        ]
 
     def test_commit(self):
         entry = self.repo.commit('HEAD')
@@ -379,13 +379,13 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
         domain = '.'.join(reversed(c.app.url[1:-1].split('/'))).replace('_', '-')
         common_suffix = tg.config['forgemail.domain']
         email = f'noreply@{domain}{common_suffix}'
-        assert_in(email, notification['reply_to_address'])
+        assert email in notification['reply_to_address']
 
         commit1_loc = notification.text.find('Initial commit')
         assert commit1_loc != -1
         commit2_loc = notification.text.find('Remove file')
         assert commit2_loc != -1
-        assert_less(commit1_loc, commit2_loc)
+        assert commit1_loc < commit2_loc
 
     def test_notification_email(self):
         send_notifications(
@@ -394,7 +394,7 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
 
         n = M.Notification.query.find({'subject': '[test:src-git] New commit [1e146e] by Rick Copeland'}).first()
         assert n
-        assert_in('Change README', n.text)
+        assert 'Change README' in n.text
 
     def test_notification_email_multiple_commits(self):
         send_notifications(self.repo, ['df30427c488aeab84b2352bdf88a3b19223f9d7a',
@@ -458,7 +458,7 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
             .get_payload(decode=True).decode('utf-8')
 
         # no extra HTML in commit messages
-        assert_in('''-----
+        assert '''-----
 
 Add foo.txt.  Commit ref [616d24f8dd4e95cadd8e93df5061f09855d1a066] *bold* <b>bold</b>
 
@@ -468,20 +468,20 @@ Add foo.txt.  Commit ref [616d24f8dd4e95cadd8e93df5061f09855d1a066] *bold* <b>bo
 
 http://example.com/
 
-By Dave Brondsema''', text_body)
+By Dave Brondsema''' in text_body
         # these bracketed links could look like HTML tags, ensure they don't get removed
-        assert_in('further messages, please visit <http://localhost/auth/subscriptions/>', text_body)
+        assert 'further messages, please visit <http://localhost/auth/subscriptions/>' in text_body
 
         # limited markdown handling of commit messages (see `markdown_commit`)
         # and HTML escaped
-        assert_in('''<hr/>
+        assert '''<hr/>
 <div class="markdown_content"><p>Add foo.txt.  Commit ref <a class="alink" href="http://localhost/p/test/weird-chars/ci/616d24f8dd4e95cadd8e93df5061f09855d1a066/">[616d24f8dd4e95cadd8e93df5061f09855d1a066]</a> *bold* &lt;b&gt;bold&lt;/b&gt;</p>
 <p>* one<br/>
 * two<br/>
 * three</p>
 <p>http://example.com/</p></div>
 
-<p>By Dave Brondsema''', html_body)
+<p>By Dave Brondsema''' in html_body
 
     def test_commit_artifact_references(self):
         self._setup_weird_chars_repo()
@@ -500,9 +500,9 @@ By Dave Brondsema''', text_body)
         if os.path.isfile(os.path.join(tmpdir, "git/t/te/test/testgit.git/test-src-git-HEAD.zip")):
             os.remove(
                 os.path.join(tmpdir, "git/t/te/test/testgit.git/test-src-git-HEAD.zip"))
-        assert_equal(self.repo.tarball_path,
+        assert (self.repo.tarball_path ==
                      os.path.join(tmpdir, 'git/t/te/test/testgit.git'))
-        assert_equal(self.repo.tarball_url('HEAD'),
+        assert (self.repo.tarball_url('HEAD') ==
                      'file:///git/t/te/test/testgit.git/test-src-git-HEAD.zip')
         self.repo.tarball('HEAD')
         assert os.path.isfile(
@@ -556,20 +556,20 @@ By Dave Brondsema''', text_body)
             os.removedirs(
                 os.path.join(tmpdir, "git/t/te/test/testgit.git/test-src-git-HEAD/"))
         self.repo.tarball('HEAD')
-        assert_equal(self.repo.get_tarball_status('HEAD'), 'complete')
+        assert self.repo.get_tarball_status('HEAD') == 'complete'
 
         os.remove(
             os.path.join(tmpdir, "git/t/te/test/testgit.git/test-src-git-HEAD.zip"))
-        assert_equal(self.repo.get_tarball_status('HEAD'), None)
+        assert self.repo.get_tarball_status('HEAD') == None
 
     def test_tarball_status_task(self):
-        assert_equal(self.repo.get_tarball_status('HEAD'), None)
+        assert self.repo.get_tarball_status('HEAD') == None
 
         # create tarball task in MonQTask and check get_tarball_status
         tarball.post('HEAD', '')
 
         # task created
-        assert_equal(self.repo.get_tarball_status('HEAD'), 'ready')
+        assert self.repo.get_tarball_status('HEAD') == 'ready'
 
         task = M.MonQTask.query.get(**{
             'task_name': 'allura.tasks.repo_tasks.tarball',
@@ -580,12 +580,12 @@ By Dave Brondsema''', text_body)
         # task is running
         task.state = 'busy'
         task.query.session.flush_all()
-        assert_equal(self.repo.get_tarball_status('HEAD'), 'busy')
+        assert self.repo.get_tarball_status('HEAD') == 'busy'
 
         # when state is complete, but file don't exists, then status is None
         task.state = 'complete'
         task.query.session.flush_all()
-        assert_equal(self.repo.get_tarball_status('HEAD'), None)
+        assert self.repo.get_tarball_status('HEAD') == None
 
     def test_is_empty(self):
         assert not self.repo.is_empty()
@@ -604,13 +604,13 @@ By Dave Brondsema''', text_body)
 
     def test_default_branch_set(self):
         self.repo.default_branch_name = 'zz'
-        assert_equal(self.repo.get_default_branch('master'), 'zz')
+        assert self.repo.get_default_branch('master') == 'zz'
 
     def test_default_branch_non_standard_unset(self):
         with mock.patch.object(self.repo, 'get_branches') as gb,\
              mock.patch.object(self.repo, 'set_default_branch') as set_db:
             gb.return_value = [Object(name='foo')]
-            assert_equal(self.repo.get_default_branch('master'), 'foo')
+            assert self.repo.get_default_branch('master') == 'foo'
             set_db.assert_called_once_with('foo')
 
     def test_default_branch_non_standard_invalid(self):
@@ -618,7 +618,7 @@ By Dave Brondsema''', text_body)
              mock.patch.object(self.repo, 'set_default_branch') as set_db:
             self.repo.default_branch_name = 'zz'
             gb.return_value = [Object(name='foo')]
-            assert_equal(self.repo.get_default_branch('master'), 'foo')
+            assert self.repo.get_default_branch('master') == 'foo'
             set_db.assert_called_once_with('foo')
 
     def test_default_branch_invalid(self):
@@ -626,28 +626,28 @@ By Dave Brondsema''', text_body)
              mock.patch.object(self.repo, 'set_default_branch') as set_db:
             self.repo.default_branch_name = 'zz'
             gb.return_value = [Object(name='foo'), Object(name='master')]
-            assert_equal(self.repo.get_default_branch('master'), 'master')
+            assert self.repo.get_default_branch('master') == 'master'
             set_db.assert_called_once_with('master')
 
     def test_default_branch_no_clobber(self):
         with mock.patch.object(self.repo, 'get_branches') as gb:
             gb.return_value = []
             self.repo.default_branch_name = 'zz'
-            assert_equal(self.repo.get_default_branch('master'), 'zz')
+            assert self.repo.get_default_branch('master') == 'zz'
 
     def test_default_branch_clobber_none(self):
         with mock.patch.object(self.repo, 'get_branches') as gb:
             gb.return_value = []
             self.repo.default_branch_name = None
-            assert_equal(self.repo.get_default_branch('master'), 'master')
+            assert self.repo.get_default_branch('master') == 'master'
 
     def test_clone_url(self):
-        assert_equal(
-            self.repo.clone_url('file', 'nobody'),
+        assert (
+            self.repo.clone_url('file', 'nobody') ==
             '/srv/git/p/test/testgit')
         with h.push_config(self.repo.app.config.options, external_checkout_url='https://$username@foo.com/'):
-            assert_equal(
-                self.repo.clone_url('https', 'user'),
+            assert (
+                self.repo.clone_url('https', 'user') ==
                 'https://user@foo.com/')
 
     def test_webhook_payload(self):
@@ -707,7 +707,7 @@ By Dave Brondsema''', text_body)
                 'url': 'http://localhost/p/test/src-git/',
             },
         }
-        assert_equals(payload, expected_payload)
+        assert payload == expected_payload
 
     def test_can_merge(self):
         mr = mock.Mock(downstream_repo=Object(full_fs_path='downstream-url'),
@@ -717,7 +717,7 @@ By Dave Brondsema''', text_body)
         git = mock.Mock()
         git.merge_tree.return_value = 'clean merge'
         self.repo._impl._git.git = git
-        assert_equal(self.repo.can_merge(mr), True)
+        assert self.repo.can_merge(mr) == True
         git.fetch.assert_called_once_with('downstream-url', 'source-branch')
         git.merge_base.assert_called_once_with('cid', 'target-branch')
         git.merge_tree.assert_called_once_with(
@@ -725,7 +725,7 @@ By Dave Brondsema''', text_body)
             'target-branch',
             'cid')
         git.merge_tree.return_value = '+<<<<<<<'
-        assert_equal(self.repo.can_merge(mr), False)
+        assert self.repo.can_merge(mr) == False
 
     @mock.patch('forgegit.model.git_repo.tempfile', autospec=True)
     @mock.patch('forgegit.model.git_repo.git', autospec=True)
@@ -748,13 +748,13 @@ By Dave Brondsema''', text_body)
             bare=False,
             shared=True)
         tmp_repo = GitImplementation.return_value._git
-        assert_equal(
-            tmp_repo.git.fetch.call_args_list,
+        assert (
+            tmp_repo.git.fetch.call_args_list ==
             [mock.call('origin', 'target-branch'),
              mock.call('downstream-url', 'source-branch')])
         tmp_repo.git.checkout.assert_called_once_with('target-branch')
-        assert_equal(
-            tmp_repo.git.config.call_args_list,
+        assert (
+            tmp_repo.git.config.call_args_list ==
             [mock.call('user.name', b'Test Admin'),
              mock.call('user.email', 'allura@localhost')])
         msg = 'Merge downstream-repo-url branch source-branch into target-branch'
@@ -802,7 +802,7 @@ By Dave Brondsema''', text_body)
             'copied': [],
             'total': 2,
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
 
         diffs = repo.paged_diffs('f3de6a0e7601cdde326054a1cc708afdc1dbe70b')
         expected = {
@@ -813,7 +813,7 @@ By Dave Brondsema''', text_body)
             'changed': ['привіт.txt'],
             'total': 1,
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
 
         # initial commit is special, but must work too
         diffs = repo.paged_diffs('afaa6d93eb5661fb04f8e10e9ba1039b7441a6c7')
@@ -825,7 +825,7 @@ By Dave Brondsema''', text_body)
             'renamed': [],
             'total': 1,
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
 
         # pagination
         diffs = repo.paged_diffs('407950e8fba4dbc108ffbce0128ed1085c52cfd7', start=0, end=1)
@@ -837,7 +837,7 @@ By Dave Brondsema''', text_body)
             'changed': [],
             'total': 2,  # there are two total changes but result is limited to first
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
         diffs = repo.paged_diffs('407950e8fba4dbc108ffbce0128ed1085c52cfd7', start=1, end=2)
         expected = {
             'added': ['привіт.txt'],
@@ -847,7 +847,7 @@ By Dave Brondsema''', text_body)
             'changed': [],
             'total': 2,  # there are two total changes but result is limited to second
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
         diffs = repo.paged_diffs('346c52c1dddc729e2c2711f809336401f0ff925e')  # Test copy
         expected = {
             'added': ['README.copy'],
@@ -857,7 +857,7 @@ By Dave Brondsema''', text_body)
             'changed': ['README'],
             'total': 2,
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
         diffs = repo.paged_diffs('3cb2bbcd7997f89060a14fe8b1a363f01883087f')  # Test rename
         expected = {
             'added': ['README'],
@@ -867,7 +867,7 @@ By Dave Brondsema''', text_body)
             'changed': [],
             'total': 2,
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
         diffs = repo.paged_diffs('616d24f8dd4e95cadd8e93df5061f09855d1a066')  # Test type change
         expected = {
             'added': [],
@@ -877,7 +877,7 @@ By Dave Brondsema''', text_body)
             'changed': ['README.copy'],
             'total': 1,
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
 
     @mock.patch.dict('allura.lib.app_globals.config',  {'scm.commit.git.detect_copies': 'true'})
     @td.with_tool('test', 'Git', 'src-weird', 'Git', type='git')
@@ -905,7 +905,7 @@ By Dave Brondsema''', text_body)
             'changed': ['README'],
             'total': 2,
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
         diffs = repo.paged_diffs('3cb2bbcd7997f89060a14fe8b1a363f01883087f')  # Test rename
         expected = {
             'added': [],
@@ -915,11 +915,11 @@ By Dave Brondsema''', text_body)
             'changed': [],
             'total': 1,
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
 
     def test_merge_base(self):
         res = self.repo._impl.merge_base(self.merge_request)
-        assert_equal(res, '1e146e67985dcd71c74de79613719bef7bddca4a')
+        assert res == '1e146e67985dcd71c74de79613719bef7bddca4a'
 
     def test_merge_request_commits(self):
         res = self.repo.merge_request_commits(self.merge_request)
@@ -938,7 +938,7 @@ By Dave Brondsema''', text_body)
              'refs': ['zz'],
              'rename_details': {},
              'size': None}]
-        assert_equals(res, expected)
+        assert res == expected
 
     def test_merge_request_commits_tmp_dir(self):
         """
@@ -950,19 +950,19 @@ By Dave Brondsema''', text_body)
         opt = {'scm.merge_list.git.use_tmp_dir': True}
         with h.push_config(tg.config, **opt):
             res_with_tmp = self.repo.merge_request_commits(mr)
-        assert_equals(res_without_tmp, res_with_tmp)
+        assert res_without_tmp == res_with_tmp
 
     def test_cached_branches(self):
         with mock.patch.dict('allura.lib.app_globals.config', {'repo_refs_cache_threshold': '0'}):
             rev = GM.Repository.query.get(_id=self.repo['_id'])
             branches = rev._impl._get_refs('branches')
-            assert_equal(rev.cached_branches, branches)
+            assert rev.cached_branches == branches
 
     def test_cached_tags(self):
         with mock.patch.dict('allura.lib.app_globals.config', {'repo_refs_cache_threshold': '0'}):
             rev = GM.Repository.query.get(_id=self.repo['_id'])
             tags = rev._impl._get_refs('tags')
-            assert_equal(rev.cached_tags, tags)
+            assert rev.cached_tags == tags
 
 
 class TestGitImplementation(unittest.TestCase):
@@ -1069,35 +1069,35 @@ class TestGitCommit(unittest.TestCase):
     def test_log(self):
         # path only
         commits = list(self.repo.log(id_only=True))
-        assert_equal(commits, [
+        assert commits == [
             "1e146e67985dcd71c74de79613719bef7bddca4a",
             "df30427c488aeab84b2352bdf88a3b19223f9d7a",
             "6a45885ae7347f1cac5103b0050cc1be6a1496c8",
             "9a7df788cf800241e3bb5a849c8870f2f8259d98",
-        ])
+        ]
         commits = list(self.repo.log(self.repo.head, 'README', id_only=True))
-        assert_equal(commits, [
+        assert commits == [
             "1e146e67985dcd71c74de79613719bef7bddca4a",
             "df30427c488aeab84b2352bdf88a3b19223f9d7a",
-        ])
+        ]
         commits = list(
             self.repo.log("df30427c488aeab84b2352bdf88a3b19223f9d7a", 'README', id_only=True))
-        assert_equal(commits, [
+        assert commits == [
             "df30427c488aeab84b2352bdf88a3b19223f9d7a",
-        ])
+        ]
         commits = list(self.repo.log(self.repo.head, '/a/b/c/', id_only=True))
-        assert_equal(commits, [
+        assert commits == [
             "6a45885ae7347f1cac5103b0050cc1be6a1496c8",
             "9a7df788cf800241e3bb5a849c8870f2f8259d98",
-        ])
+        ]
         commits = list(
             self.repo.log("9a7df788cf800241e3bb5a849c8870f2f8259d98", '/a/b/c/', id_only=True))
-        assert_equal(commits, [
+        assert commits == [
             "9a7df788cf800241e3bb5a849c8870f2f8259d98",
-        ])
+        ]
         commits = list(
             self.repo.log(self.repo.head, '/does/not/exist/', id_only=True))
-        assert_equal(commits, [])
+        assert commits == []
 
 
 class TestGitHtmlView(unittest.TestCase):
diff --git a/ForgeGit/forgegit/tests/test_git_app.py b/ForgeGit/forgegit/tests/test_git_app.py
index 80372039f..823004c40 100644
--- a/ForgeGit/forgegit/tests/test_git_app.py
+++ b/ForgeGit/forgegit/tests/test_git_app.py
@@ -40,7 +40,7 @@ class TestGitApp(unittest.TestCase):
         ThreadLocalORMSession.close_all()
 
     def test_admin_menu(self):
-        assert_equals(len(c.app.admin_menu()), 7)
+        assert len(c.app.admin_menu()) == 7
 
     def test_uninstall(self):
         from allura import model as M
diff --git a/ForgeImporters/forgeimporters/github/tests/test_utils.py b/ForgeImporters/forgeimporters/github/tests/test_utils.py
index 1ea4c761b..5e47bcefe 100644
--- a/ForgeImporters/forgeimporters/github/tests/test_utils.py
+++ b/ForgeImporters/forgeimporters/github/tests/test_utils.py
@@ -28,67 +28,67 @@ class TestGitHubMarkdownConverter:
     def test_convert_sha(self):
         text = '16c999e8c71134401a78d4d46435517b2271d6ac'
         result = self.conv.convert(text)
-        assert_equal(result, '[16c999]')
+        assert result == '[16c999]'
 
         text = 'some context  16c999e8c71134401a78d4d46435517b2271d6ac '
         result = self.conv.convert(text)
-        assert_equal(result, 'some context  [16c999] ')
+        assert result == 'some context  [16c999] '
 
     def test_convert_user_sha(self):
         text = 'user@16c999e8c71134401a78d4d46435517b2271d6ac'
         result = self.conv.convert(text)
-        assert_equal(result, '[16c999]')
+        assert result == '[16c999]'
 
         # Not an owner of current project
         text = 'another-user@16c999e8c71134401a78d4d46435517b2271d6ac'
         result = self.conv.convert(text)
-        assert_equal(result, text)
+        assert result == text
 
     def test_convert_user_repo_sha(self):
         text = 'user/project@16c999e8c71134401a78d4d46435517b2271d6ac'
         result = self.conv.convert(text)
-        assert_equal(result, '[16c999]')
+        assert result == '[16c999]'
 
         # Not a current project
         text = 'user/p@16c999e8c71134401a78d4d46435517b2271d6ac'
         result = self.conv.convert(text)
-        assert_equal(result, '[user/p@16c999]'
+        assert (result == '[user/p@16c999]'
                              '(https://github.com/user/p/commit/16c999e8c71134401a78d4d46435517b2271d6ac)')
 
     def test_convert_ticket(self):
         text = 'Ticket #1'
         result = self.conv.convert(text)
-        assert_equal(result, 'Ticket [#1]')
-        assert_equal(self.conv.convert('#1'), '[#1]')
+        assert result == 'Ticket [#1]'
+        assert self.conv.convert('#1') == '[#1]'
 
     def test_convert_user_ticket(self):
         text = 'user#1'
         result = self.conv.convert(text)
-        assert_equal(result, '[#1]')
+        assert result == '[#1]'
 
         # Not an owner of current project
         text = 'another-user#1'
         result = self.conv.convert(text)
-        assert_equal(result, 'another-user#1')
+        assert result == 'another-user#1'
 
     def test_convert_user_repo_ticket(self):
         text = 'user/project#1'
         result = self.conv.convert(text)
-        assert_equal(result, '[#1]')
+        assert result == '[#1]'
 
         # Not a current project
         text = 'user/p#1'
         result = self.conv.convert(text)
-        assert_equal(result, '[user/p#1](https://github.com/user/p/issues/1)')
+        assert result == '[user/p#1](https://github.com/user/p/issues/1)'
 
     def test_convert_strikethrough(self):
         text = '~~mistake~~'
-        assert_equal(self.conv.convert(text), '<s>mistake</s>')
+        assert self.conv.convert(text) == '<s>mistake</s>'
 
     def test_inline_code_block(self):
         text = 'This `~~some text~~` converts to this ~~strike out~~.'
         result = 'This `~~some text~~` converts to this <s>strike out</s>.'
-        assert_equal(self.conv.convert(text).strip(), result)
+        assert self.conv.convert(text).strip() == result
 
     def test_convert_code_blocks(self):
         text = '''```python
@@ -111,7 +111,7 @@ Two code blocks here!
         console.log(i);
     }'''
 
-        assert_equal(self.conv.convert(text).strip(), result)
+        assert self.conv.convert(text).strip() == result
 
     def test_code_blocks_without_newline_before(self):
         text = '''
@@ -126,9 +126,9 @@ There are some code snippet:
 
     print 'Hello'
 Pretty cool, ha?'''
-        assert_equal(self.conv.convert(text).strip(), result.strip())
+        assert self.conv.convert(text).strip() == result.strip()
         text = text.replace('```', '~~~')
-        assert_equal(self.conv.convert(text).strip(), result.strip())
+        assert self.conv.convert(text).strip() == result.strip()
 
         text = '''
 There are some code snippet:
@@ -143,4 +143,4 @@ There are some code snippet:
     :::python
     print 'Hello'
 Pretty cool, ha?'''
-        assert_equal(self.conv.convert(text).strip(), result.strip())
+        assert self.conv.convert(text).strip() == result.strip()
diff --git a/ForgeImporters/forgeimporters/github/tests/test_wiki.py b/ForgeImporters/forgeimporters/github/tests/test_wiki.py
index d99117d94..5afcebb17 100644
--- a/ForgeImporters/forgeimporters/github/tests/test_wiki.py
+++ b/ForgeImporters/forgeimporters/github/tests/test_wiki.py
@@ -118,7 +118,7 @@ class TestGitHubWikiImporter(TestCase):
             'project_name': 'me/project',
             'source_id': 'Page',
         }
-        assert_equal(page.import_id, import_id)
+        assert page.import_id == import_id
 
     @patch('forgeimporters.github.wiki.WM.Page.upsert')
     @patch('forgeimporters.github.wiki.h.render_any_markup')
@@ -132,11 +132,11 @@ class TestGitHubWikiImporter(TestCase):
         importer.app.url = '/p/test/wiki/'
         importer.rewrite_links = Mock(return_value='')
         importer._without_history(self.commit2)
-        assert_equal(upsert.call_args_list, [call('Home2'), call('Home3')])
+        assert upsert.call_args_list == [call('Home2'), call('Home3')]
 
-        assert_equal(render.call_args_list, [
+        assert render.call_args_list == [
             call('Home2.creole', '**test message**'),
-            call('Home3.rest', 'test message')])
+            call('Home3.rest', 'test message')]
 
     @patch('forgeimporters.github.wiki.git.Repo')
     @patch('forgeimporters.github.wiki.mkdtemp')
@@ -154,16 +154,16 @@ class TestGitHubWikiImporter(TestCase):
         repo = clone.return_value
         repo.iter_commits.return_value = [self.commit1, self.commit2]
         GitHubWikiImporter().import_pages('wiki_url', history=True)
-        assert_equal(with_history.call_count, 2)
-        assert_equal(without_history.call_count, 0)
+        assert with_history.call_count == 2
+        assert without_history.call_count == 0
 
     @patch('forgeimporters.github.wiki.GitHubWikiImporter._with_history')
     @patch('forgeimporters.github.wiki.GitHubWikiImporter._without_history')
     def test_get_commits_without_history(self, without_history, with_history):
         with patch('forgeimporters.github.wiki.git.Repo._clone'):
             GitHubWikiImporter().import_pages('wiki_url')
-            assert_equal(with_history.call_count, 0)
-            assert_equal(without_history.call_count, 1)
+            assert with_history.call_count == 0
+            assert without_history.call_count == 1
 
     @patch('forgeimporters.github.wiki.WM.Page.upsert')
     @patch('forgeimporters.github.wiki.h.render_any_markup')
@@ -178,8 +178,8 @@ class TestGitHubWikiImporter(TestCase):
         importer.app.url = '/p/test/wiki/'
         importer.rewrite_links = Mock(return_value='')
         importer._with_history(self.commit2)
-        assert_equal(upsert.call_args_list, [call('Home')])
-        assert_equal(render.call_args_list,
+        assert upsert.call_args_list == [call('Home')]
+        assert (render.call_args_list ==
                      [call('Home.rst', '# test message')])
 
     @skipIf(module_not_available('html2text'), 'html2text required')
@@ -198,8 +198,8 @@ class TestGitHubWikiImporter(TestCase):
         importer.rewrite_links = Mock(return_value='')
         importer.convert_gollum_tags = Mock(return_value='# test message')
         importer._with_history(self.commit2)
-        assert_equal(upsert.call_args_list, [call('Home')])
-        assert_equal(md2mkm.call_args_list, [call('# test message')])
+        assert upsert.call_args_list == [call('Home')]
+        assert md2mkm.call_args_list == [call('# test message')]
 
     def test_set_available_pages(self):
         importer = GitHubWikiImporter()
@@ -210,70 +210,70 @@ class TestGitHubWikiImporter(TestCase):
         blobs[2].name = 'code & fun.textile'
         commit.tree.traverse.return_value = blobs
         importer._set_available_pages(commit)
-        assert_equal(importer.available_pages, ['Home 42', 'code & fun'])
+        assert importer.available_pages == ['Home 42', 'code & fun']
 
     def test_gollum_page_links_case_insensitive(self):
         i = GitHubWikiImporter()
         i.available_pages = ['Home 42', 'code & fun']
-        assert_equal(i.convert_gollum_tags('[[Code & Fun]]'), '[code & fun]')
-        assert_equal(i.convert_gollum_tags('[[home-42]]'), '[Home 42]')
-        assert_equal(i.convert_gollum_tags('[[Unknown]]'), '[Unknown]')
+        assert i.convert_gollum_tags('[[Code & Fun]]') == '[code & fun]'
+        assert i.convert_gollum_tags('[[home-42]]') == '[Home 42]'
+        assert i.convert_gollum_tags('[[Unknown]]') == '[Unknown]'
 
     def test_convert_page_name(self):
         f = GitHubWikiImporter()._convert_page_name
-        assert_equal(f('Page Name'), 'Page Name')
-        assert_equal(f('Page-Name'), 'Page Name')
-        assert_equal(f('Page / Name'), 'Page   Name')
+        assert f('Page Name') == 'Page Name'
+        assert f('Page-Name') == 'Page Name'
+        assert f('Page / Name') == 'Page   Name'
 
     def test_convert_gollum_page_links(self):
         f = GitHubWikiImporter().convert_gollum_tags
-        assert_equal(f('[[Page]]'), '[Page]')
-        assert_equal(f('[[Page Title|Page]]'), '[Page Title](Page)')
-        assert_equal(f('[[Pagê Nâme]]'), '[Pagê Nâme]')
+        assert f('[[Page]]') == '[Page]'
+        assert f('[[Page Title|Page]]') == '[Page Title](Page)'
+        assert f('[[Pagê Nâme]]') == '[Pagê Nâme]'
         # Github always converts spaces and slashes in links to hyphens,
         # to lookup page in the filesystem. During import we're converting
         # all hyphens in page name to spaces, but still supporting both link
         # formats.
-        assert_equal(f('[[Page With Spaces]]'), '[Page With Spaces]')
-        assert_equal(f('[[Page-With-Spaces]]'), '[Page With Spaces]')
-        assert_equal(f('[[Page / 1]]'), '[Page   1]')
-        assert_equal(f('[[Title|Page With Spaces]]'),
+        assert f('[[Page With Spaces]]') == '[Page With Spaces]'
+        assert f('[[Page-With-Spaces]]') == '[Page With Spaces]'
+        assert f('[[Page / 1]]') == '[Page   1]'
+        assert (f('[[Title|Page With Spaces]]') ==
                      '[Title](Page With Spaces)')
-        assert_equal(f('[[Title|Page-With-Spaces]]'),
+        assert (f('[[Title|Page-With-Spaces]]') ==
                      '[Title](Page With Spaces)')
-        assert_equal(f('[[go here|Page / 1]]'), '[go here](Page   1)')
+        assert f('[[go here|Page / 1]]') == '[go here](Page   1)'
 
     def test_convert_gollum_page_links_escaped(self):
         f = GitHubWikiImporter().convert_gollum_tags
-        assert_equal(f("'[[Page]]"), '[[Page]]')
-        assert_equal(f("'[[Page Title|Page]]"), '[[Page Title|Page]]')
-        assert_equal(f("'[[Page With Spaces]]"), '[[Page With Spaces]]')
-        assert_equal(f("'[[Page-With-Spaces]]"), '[[Page-With-Spaces]]')
-        assert_equal(f("'[[Page / 1]]"), '[[Page / 1]]')
-        assert_equal(f("'[[Title|Page With Spaces]]"),
+        assert f("'[[Page]]") == '[[Page]]'
+        assert f("'[[Page Title|Page]]") == '[[Page Title|Page]]'
+        assert f("'[[Page With Spaces]]") == '[[Page With Spaces]]'
+        assert f("'[[Page-With-Spaces]]") == '[[Page-With-Spaces]]'
+        assert f("'[[Page / 1]]") == '[[Page / 1]]'
+        assert (f("'[[Title|Page With Spaces]]") ==
                      '[[Title|Page With Spaces]]')
-        assert_equal(f("'[[Title|Page-With-Spaces]]"),
+        assert (f("'[[Title|Page-With-Spaces]]") ==
                      '[[Title|Page-With-Spaces]]')
-        assert_equal(f("'[[go here|Page / 1]]"), '[[go here|Page / 1]]')
+        assert f("'[[go here|Page / 1]]") == '[[go here|Page / 1]]'
 
     def test_convert_gollum_external_links(self):
         f = GitHubWikiImporter().convert_gollum_tags
-        assert_equal(f('[[http://domain.net]]'), '<http://domain.net>')
-        assert_equal(f('[[https://domain.net]]'), '<https://domain.net>')
-        assert_equal(f('[[Site|http://domain.net]]'),
+        assert f('[[http://domain.net]]') == '<http://domain.net>'
+        assert f('[[https://domain.net]]') == '<https://domain.net>'
+        assert (f('[[Site|http://domain.net]]') ==
                      '[Site](http://domain.net)')
 
     def test_convert_gollum_external_links_escaped(self):
         f = GitHubWikiImporter().convert_gollum_tags
-        assert_equal(f("'[[http://domain.net]]"), '[[http://domain.net]]')
-        assert_equal(f("'[[https://domain.net]]"), '[[https://domain.net]]')
-        assert_equal(f("'[[Site|http://domain.net]]"),
+        assert f("'[[http://domain.net]]") == '[[http://domain.net]]'
+        assert f("'[[https://domain.net]]") == '[[https://domain.net]]'
+        assert (f("'[[Site|http://domain.net]]") ==
                      '[[Site|http://domain.net]]')
 
     def test_convert_gollum_toc(self):
         f = GitHubWikiImporter().convert_gollum_tags
-        assert_equal(f('[[_TOC_]]'), '[TOC]')
-        assert_equal(f("'[[_TOC_]]"), '[[_TOC_]]')
+        assert f('[[_TOC_]]') == '[TOC]'
+        assert f("'[[_TOC_]]") == '[[_TOC_]]'
 
     def test_convert_gollum_tags(self):
         f = GitHubWikiImporter().convert_gollum_tags
@@ -293,7 +293,7 @@ Our website is <http://domain.net>.
 
 [[Escaped Tag]]'''
 
-        assert_equal(f(source), result)
+        assert f(source) == result
 
     @skipIf(module_not_available('html2text'), 'html2text required')
     def test_convert_markup(self):
@@ -337,9 +337,9 @@ ticket [#1]
 [#1] header
 
 sha [aaaaaa]'''
-        assert_equal(f(source, 'test.md').strip(), result)
+        assert f(source, 'test.md').strip() == result
 
-        assert_equal(f('h1. Hello', 't.textile').strip(), '# Hello')
+        assert f('h1. Hello', 't.textile').strip() == '# Hello'
 
     @without_module('html2text')
     def test_convert_markup_without_html2text(self):
@@ -367,35 +367,35 @@ Our website is [[http://domain.net]].
 <p>[External link to the wiki page](https://github.com/a/b/wiki/Page)</p>
 <p>[External link](https://github.com/a/b/issues/1)</p>'''
 
-        assert_equal(f(source, 'test.textile').strip(), result)
+        assert f(source, 'test.textile').strip() == result
 
     def test_rewrite_links(self):
         f = GitHubWikiImporter().rewrite_links
         prefix = 'https://github/a/b/wiki'
         new = '/p/test/wiki/'
-        assert_equal(
+        assert (
             f('<a href="https://github/a/b/wiki/Test Page">Test Page</a>',
-              prefix, new),
+              prefix, new) ==
             '<a href="/p/test/wiki/Test Page">Test Page</a>')
-        assert_equal(
+        assert (
             f('<a href="https://github/a/b/wiki/Test-Page">Test-Page</a>',
-              prefix, new),
+              prefix, new) ==
             '<a href="/p/test/wiki/Test Page">Test Page</a>')
-        assert_equal(
+        assert (
             f('<a href="https://github/a/b/issues/1" class="1"></a>',
-              prefix, new),
+              prefix, new) ==
             '<a class="1" href="https://github/a/b/issues/1"></a>')
-        assert_equal(
+        assert (
             f('<a href="https://github/a/b/wiki/Test Page">https://github/a/b/wiki/Test Page</a>',
-              prefix, new),
+              prefix, new) ==
             '<a href="/p/test/wiki/Test Page">/p/test/wiki/Test Page</a>')
-        assert_equal(
+        assert (
             f('<a href="https://github/a/b/wiki/Test Page">Test blah blah</a>',
-              prefix, new),
+              prefix, new) ==
             '<a href="/p/test/wiki/Test Page">Test blah blah</a>')
-        assert_equal(
+        assert (
             f('<a href="https://github/a/b/wiki/Test Page">Test <b>Page</b></a>',
-              prefix, new),
+              prefix, new) ==
             '<a href="/p/test/wiki/Test Page">Test <b>Page</b></a>')
 
     @skipIf(module_not_available('html2text'), 'html2text required')
@@ -424,7 +424,7 @@ Our website is [[http://domain.net]].
 
 '''
 
-        assert_equal(f(source, 'test.mediawiki'), result)
+        assert f(source, 'test.mediawiki') == result
 
     @skipIf(module_not_available('html2text'), 'html2text required')
     def test_convert_textile_no_leading_tabs(self):
@@ -448,7 +448,7 @@ Some text 1.
 ## Header 2
 
 See [Page]'''
-        assert_equal(f(source, 'test.textile').strip(), result)
+        assert f(source, 'test.textile').strip() == result
 
     @skipIf(module_not_available('html2text'), 'html2text required')
     def test_convert_markup_with_amp_in_links(self):
@@ -460,7 +460,7 @@ See [Page]'''
         source = '[[Ticks & Leeches]]'
         result = '[Ticks & Leeches]'
         # markdown should be untouched
-        assert_equal(f(source, 'test.rst').strip(), result)
+        assert f(source, 'test.rst').strip() == result
 
     @skipIf(module_not_available('html2text'), 'html2text required')
     def test_convert_markup_textile(self):
@@ -485,12 +485,12 @@ See [Page]'''
 
 '''
 
-        assert_equal(f(source, 'test.textile'), result)
+        assert f(source, 'test.textile') == result
 
         # textile-style links converts normal
         source = '*"Textile":Troubleshooting*'
         result = '**[Textile](Troubleshooting)**\n\n'
-        assert_equal(f(source, 'test2.textile'), result)
+        assert f(source, 'test2.textile') == result
 
         # links with formatting converts normal in textile now
         source = '''*[[this checklist|Troubleshooting]]*
@@ -506,7 +506,7 @@ some text and **[Tips n\u2019 Tricks]**
 **[link](http://otherlink.com)**
 
 '''
-        assert_equal(f(source, 'test3.textile'), result)
+        assert f(source, 'test3.textile') == result
 
     @skipIf(module_not_available('html2text'), 'html2text required')
     def test_convert_textile_special_tag(self):
@@ -516,7 +516,7 @@ some text and **[Tips n\u2019 Tricks]**
         importer.app.url = '/p/test/wiki/'
         f = importer.convert_markup
         source = '*[[this checklist|Troubleshooting]]*'
-        assert_equal(f(source, 't.textile').strip(),
+        assert (f(source, 't.textile').strip() ==
                      '**[this checklist](Troubleshooting)**')
 
     @without_module('html2text')
@@ -528,7 +528,7 @@ some text and **[Tips n\u2019 Tricks]**
         f = importer.convert_markup
         source = '*[[this checklist|Troubleshooting]]*'
         result = '<p><strong>[[this checklist|Troubleshooting]]</strong></p>'
-        assert_equal(f(source, 't.textile').strip(), result)
+        assert f(source, 't.textile').strip() == result
 
     @patch('forgeimporters.github.wiki.mkdtemp', autospec=True)
     @patch('forgeimporters.github.wiki.rmtree', autospec=True)
@@ -536,7 +536,7 @@ some text and **[Tips n\u2019 Tricks]**
     def test_has_wiki_repo(self, repo, rmtree, mkdtemp):
         mkdtemp.return_value = 'fake path'
         i = GitHubWikiImporter()
-        assert_equal(i.has_wiki_repo('fake url'), True)
+        assert i.has_wiki_repo('fake url') == True
         repo.clone_from.assert_called_once_with(
             'fake url', to_path='fake path', bare=True)
         rmtree.assert_called_once_with('fake path')
@@ -544,7 +544,7 @@ some text and **[Tips n\u2019 Tricks]**
         def raise_error(*args, **kw):
             raise git.GitCommandError('bam', 'bam', 'bam')
         repo.clone_from.side_effect = raise_error
-        assert_equal(i.has_wiki_repo('fake url'), False)
+        assert i.has_wiki_repo('fake url') == False
 
 
 class TestGitHubWikiImportController(TestController, TestCase):
diff --git a/ForgeImporters/forgeimporters/tests/github/functional/test_github.py b/ForgeImporters/forgeimporters/tests/github/functional/test_github.py
index 57cb9b228..78dd617bf 100644
--- a/ForgeImporters/forgeimporters/tests/github/functional/test_github.py
+++ b/ForgeImporters/forgeimporters/tests/github/functional/test_github.py
@@ -62,16 +62,16 @@ class TestGitHubOAuth(TestController):
         oauth.return_value = oauth_instance
 
         user = M.User.by_username('test-admin')
-        assert_equal(user.get_tool_data('GitHubProjectImport', 'token'), None)
+        assert user.get_tool_data('GitHubProjectImport', 'token') == None
         r = self.app.get('/p/import_project/github/')
-        assert_equal(r.status_int, 302)
-        assert_equal(r.location, redirect)
+        assert r.status_int == 302
+        assert r.location == redirect
         session.__setitem__.assert_has_calls([
             call('github.oauth.state', 'state'),
             call('github.oauth.redirect',
                  'http://localhost/p/import_project/github/')
         ])
-        assert_equal(session.save.call_count, 1)
+        assert session.save.call_count == 1
 
         r = self.app.get(redirect)
         session.get.assert_has_calls([
@@ -79,14 +79,14 @@ class TestGitHubOAuth(TestController):
             call('github.oauth.redirect', '/')
         ])
         user = M.User.by_username('test-admin')
-        assert_equal(user.get_tool_data('GitHubProjectImport', 'token'), 'abc')
+        assert user.get_tool_data('GitHubProjectImport', 'token') == 'abc'
 
         with patch('forgeimporters.github.requests.post') as valid_access_token_post:
             valid_access_token_post.return_value = Mock(status_code=200)
             r = self.app.get('/p/import_project/github/')
 
         # token in user data, so oauth isn't triggered
-        assert_equal(r.status_int, 200)
+        assert r.status_int == 200
 
         valid_access_token_post.assert_called_once_with('https://api.github.com/applications/client_id/token',
                                                         auth=requests.auth.HTTPBasicAuth('client_id', 'secret'),
@@ -96,5 +96,5 @@ class TestGitHubOAuth(TestController):
 
     def test_project_import_login_required(self):
         r = self.app.get('/p/import_project/github/', extra_environ=dict(username='*anonymous'))
-        assert_equal(None, r.location)
+        assert None == r.location
         r.mustcontain('Login Required')
diff --git a/ForgeImporters/forgeimporters/tests/test_base.py b/ForgeImporters/forgeimporters/tests/test_base.py
index 44595cb86..b5d6dbe74 100644
--- a/ForgeImporters/forgeimporters/tests/test_base.py
+++ b/ForgeImporters/forgeimporters/tests/test_base.py
@@ -317,7 +317,7 @@ class TestProjectToolsImportController(TestController):
             import1_page = import_main_page.click('Import', href=r'importer1$')
         url = import1_page.request.path
         assert url.endswith('/admin/ext/import/importer1'), url
-        assert_equal(import1_page.text, 'test importer 1 controller webpage')
+        assert import1_page.text == 'test importer 1 controller webpage'
 
     @mock.patch.object(base.h, 'iter_entry_points')
     def test_hidden(self, iep):
@@ -344,16 +344,16 @@ def test_get_importer_upload_path():
         neighborhood=mock.Mock(url_prefix='p/'),
     )
     with h.push_config(config, importer_upload_path='path/{nbhd}/{project}'):
-        assert_equal(base.get_importer_upload_path(project), 'path/p/prefix')
+        assert base.get_importer_upload_path(project) == 'path/p/prefix'
         project.is_nbhd_project = True
-        assert_equal(base.get_importer_upload_path(project), 'path/p/n_url')
+        assert base.get_importer_upload_path(project) == 'path/p/n_url'
         project.is_nbhd_project = False
         project.is_user_project = True
-        assert_equal(base.get_importer_upload_path(project),
+        assert (base.get_importer_upload_path(project) ==
                      'path/p/shortname')
         project.is_user_project = False
         project.is_root = True
-        assert_equal(base.get_importer_upload_path(project),
+        assert (base.get_importer_upload_path(project) ==
                      'path/p/prefix/shortname')
 
 
@@ -385,7 +385,7 @@ class TestFile:
             'data': 'data',
         }
         f = base.File('http://example.com/barbaz.jpg')
-        assert_equal(f.type, 'image/jpeg')
+        assert f.type == 'image/jpeg'
 
         f = base.File('http://example.com/barbaz')
-        assert_equal(f.type, 'image/png')
+        assert f.type == 'image/png'
diff --git a/ForgeLink/forgelink/tests/functional/test_rest.py b/ForgeLink/forgelink/tests/functional/test_rest.py
index 4ec59b71e..d420d537a 100644
--- a/ForgeLink/forgelink/tests/functional/test_rest.py
+++ b/ForgeLink/forgelink/tests/functional/test_rest.py
@@ -34,20 +34,20 @@ class TestLinkApi(TestRestApiBase):
 
     def test_rest_link(self):
         r = self.api_get('/rest/p/test/link')
-        assert_equal(r.json['url'], None)
+        assert r.json['url'] == None
 
         r = self.api_post('/rest/p/test/link',
                           url='http://google.com')
-        assert_equal(r.json['url'], 'http://google.com')
+        assert r.json['url'] == 'http://google.com'
 
         self.api_post('/rest/p/test/link',
                       url='http://yahoo.com')
         r = self.api_get('/rest/p/test/link')
-        assert_equal(r.json['url'], 'http://yahoo.com')
+        assert r.json['url'] == 'http://yahoo.com'
 
         self.api_post('/rest/p/test/link')
         r = self.api_get('/rest/p/test/link')
-        assert_equal(r.json['url'], 'http://yahoo.com')
+        assert r.json['url'] == 'http://yahoo.com'
 
     def test_rest_link_get_permissions(self):
         self.app.get('/rest/p/test/link',
@@ -75,7 +75,7 @@ class TestLinkApi(TestRestApiBase):
                       extra_environ={'username': '*anonymous'},
                       status=200)
         r = self.api_get('/rest/p/test/link')
-        assert_equal(r.json['url'], 'http://yahoo.com')
+        assert r.json['url'] == 'http://yahoo.com'
 
 
 class TestLinkHasAccess(TestRestApiBase):
@@ -98,13 +98,13 @@ class TestLinkHasAccess(TestRestApiBase):
         r = self.api_get(
             '/rest/p/test/link/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/link/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
 
     def test_has_access_not_admin(self):
         """
@@ -120,10 +120,10 @@ class TestLinkHasAccess(TestRestApiBase):
         r = self.api_get(
             '/rest/p/test/link/has_access?user=test-admin&perm=configure',
             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/link/has_access?user=test-user&perm=configure',
             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/ForgeLink/forgelink/tests/functional/test_root.py b/ForgeLink/forgelink/tests/functional/test_root.py
index 31b570d96..595c2aa46 100644
--- a/ForgeLink/forgelink/tests/functional/test_root.py
+++ b/ForgeLink/forgelink/tests/functional/test_root.py
@@ -37,7 +37,7 @@ class TestRootController(TestController):
         response.form['url'] = 'http://www.google.com/'
         response.form.submit()
         redir = self.app.get('/link/index', status=302)
-        assert_equal(redir.location, 'http://www.google.com/')
+        assert redir.location == 'http://www.google.com/'
 
     @td.with_link
     def test_root_with_url(self):
@@ -45,7 +45,7 @@ class TestRootController(TestController):
         response.form['url'] = 'http://www.google.com/'
         response.form.submit()
         redir = self.app.get('/link', status=302)
-        assert_equal(redir.location, 'http://www.google.com/')
+        assert redir.location == 'http://www.google.com/'
 
     @td.with_link
     def test_root_suffix_with_url_slash(self):
@@ -53,7 +53,7 @@ class TestRootController(TestController):
         response.form['url'] = 'http://www.google.com/'
         response.form.submit()
         redir = self.app.get('/link/service', status=302)
-        assert_equal(redir.location, 'http://www.google.com/service')
+        assert redir.location == 'http://www.google.com/service'
 
     @td.with_link
     def test_root_suffix_with_url_value(self):
@@ -61,7 +61,7 @@ class TestRootController(TestController):
         response.form['url'] = 'http://www.google.de/search?q='
         response.form.submit()
         redir = self.app.get(h.urlquote('/link/helpåß'), status=302)
-        assert_equal(redir.location, 'http://www.google.de/search?q=help%C3%A5%C3%9F')
+        assert redir.location == 'http://www.google.de/search?q=help%C3%A5%C3%9F'
 
 
 class TestConfigOptions(TestController):
@@ -72,7 +72,7 @@ class TestConfigOptions(TestController):
 
     def assert_url(self, mount_point, val):
         app = self.project.app_instance(mount_point)
-        assert_equal(app.config.options['url'], val)
+        assert app.config.options['url'] == val
 
     def test_sets_url_on_install(self):
         r = self.app.post('/p/test/admin/update_mounts', params={
@@ -93,17 +93,17 @@ class TestConfigOptions(TestController):
             'new.mount_label': 'Google',
             'url': 'invalid url'})
         flash = json.loads(self.webflash(r))
-        assert_equal(flash['status'], 'error')
-        assert_equal(flash['message'], 'ToolError: url: That is not a valid URL')
+        assert flash['status'] == 'error'
+        assert flash['message'] == 'ToolError: url: That is not a valid URL'
         app = self.project.app_instance('link-google')
-        assert_equal(app, None)
+        assert app == None
 
     @td.with_link
     def test_sets_url_on_config(self):
         self.assert_url('link', None)
         params = {'url': 'https://allura.apache.org'}
         r = self.app.post('/p/test/admin/link/configure', params=params)
-        assert_equal(self.webflash(r), '')
+        assert self.webflash(r) == ''
         self.assert_url('link', 'https://allura.apache.org')
 
     @td.with_link
@@ -112,28 +112,28 @@ class TestConfigOptions(TestController):
         params = {'url': 'invalid link'}
         r = self.app.post('/p/test/admin/link/configure', params=params)
         flash = json.loads(self.webflash(r))
-        assert_equal(flash['status'], 'error')
-        assert_equal(flash['message'], 'url: That is not a valid URL')
+        assert flash['status'] == 'error'
+        assert flash['message'] == 'url: That is not a valid URL'
         self.assert_url('link', None)
 
     @td.with_link
     def test_menu_url(self):
         resp = self.app.get('/p/test/admin/')
-        assert_in('/p/test/link/', str(resp.html.find(id='top_nav')))
+        assert '/p/test/link/' in str(resp.html.find(id='top_nav'))
 
         response = self.app.get('/admin/link/options')
         response.form['url'] = 'http://foo.bar/baz'
         response.form.submit()
 
         resp = self.app.get('/p/test/admin/')
-        assert_in('http://foo.bar/baz', str(resp.html.find(id='top_nav')))
+        assert 'http://foo.bar/baz' in str(resp.html.find(id='top_nav'))
 
     def _check_configurable(self, admin_nav_data):
         for menu_item in admin_nav_data['menu']:
             if menu_item['tool_name'] == 'link':
-                assert_in({'className': 'admin_modal',
+                assert ({'className': 'admin_modal',
                            'text': 'Options',
-                           'href': '/p/test/admin/link/options'},
+                           'href': '/p/test/admin/link/options'} in
                           menu_item['admin_options'])
                 break
         else:
diff --git a/ForgeLink/forgelink/tests/test_app.py b/ForgeLink/forgelink/tests/test_app.py
index 6ff4d5b1b..28efa0054 100644
--- a/ForgeLink/forgelink/tests/test_app.py
+++ b/ForgeLink/forgelink/tests/test_app.py
@@ -43,4 +43,4 @@ class TestBulkExport:
         f = tempfile.TemporaryFile('w+')
         link.bulk_export(f)
         f.seek(0)
-        assert_equal(json.loads(f.read())['url'], 'http://domain.net')
+        assert json.loads(f.read())['url'] == 'http://domain.net'
diff --git a/ForgeSVN/forgesvn/tests/functional/test_auth.py b/ForgeSVN/forgesvn/tests/functional/test_auth.py
index 992613e91..6ae98a74f 100644
--- a/ForgeSVN/forgesvn/tests/functional/test_auth.py
+++ b/ForgeSVN/forgesvn/tests/functional/test_auth.py
@@ -27,19 +27,19 @@ class TestSVNAuth(TestController):
     @with_svn
     def test_refresh_repo(self):
         r = self.app.get('/auth/refresh_repo')
-        assert_equal(r.text, 'No repo specified')
+        assert r.text == 'No repo specified'
 
         r = self.app.get('/auth/refresh_repo/p/gbalksdfh')
-        assert_equal(r.text, 'No project at /p/gbalksdfh')
+        assert r.text == 'No project at /p/gbalksdfh'
 
         r = self.app.get('/auth/refresh_repo/p/test')
-        assert_equal(r.text, '/p/test does not include a repo mount point')
+        assert r.text == '/p/test does not include a repo mount point'
 
         r = self.app.get('/auth/refresh_repo/p/test/blah/')
-        assert_equal(r.text, 'Cannot find repo at /p/test/blah')
+        assert r.text == 'Cannot find repo at /p/test/blah'
 
         r = self.app.get('/auth/refresh_repo/p/test/src/')
-        assert_equal(r.text,
+        assert (r.text ==
                      '<Repository /tmp/svn/p/test/src> refresh queued.\n')
 
 
@@ -52,6 +52,6 @@ class TestSVNUserPermissions(TestController):
     def test_list_repos(self):
         r = self.app.get('/auth/repo_permissions',
                          params=dict(username='test-admin'), status=200)
-        assert_equal(json.loads(r.text), {"allow_write": [
+        assert json.loads(r.text) == {"allow_write": [
             '/svn/test/src',
-        ]})
+        ]}
diff --git a/ForgeSVN/forgesvn/tests/functional/test_controllers.py b/ForgeSVN/forgesvn/tests/functional/test_controllers.py
index 1644b20cc..1a4cbe637 100644
--- a/ForgeSVN/forgesvn/tests/functional/test_controllers.py
+++ b/ForgeSVN/forgesvn/tests/functional/test_controllers.py
@@ -101,42 +101,42 @@ class TestRootController(SVNTestController):
     def test_commit_browser_data(self):
         resp = self.app.get('/src/commit_browser_data')
         data = json.loads(resp.text)
-        assert_equal(data['max_row'], 6)
-        assert_equal(data['next_column'], 1)
+        assert data['max_row'] == 6
+        assert data['next_column'] == 1
         for val in data['built_tree'].values():
             if val['url'] == '/p/test/src/1/':
-                assert_equal(val['short_id'], '[r1]')
-                assert_equal(val['column'], 0)
-                assert_equal(val['row'], 6)
-                assert_equal(val['message'], 'Create readme')
+                assert val['short_id'] == '[r1]'
+                assert val['column'] == 0
+                assert val['row'] == 6
+                assert val['message'] == 'Create readme'
 
     def test_feed(self):
         for ext in ['', '.rss']:
             r = self.app.get('/src/feed%s' % ext)
             channel = r.xml.find('channel')
             title = channel.find('title').text
-            assert_equal(title, 'test SVN changes')
+            assert title == 'test SVN changes'
             description = channel.find('description').text
-            assert_equal(description,
+            assert (description ==
                          'Recent changes to SVN repository in test project')
             link = channel.find('link').text
-            assert_equal(link, 'http://localhost/p/test/src/')
+            assert link == 'http://localhost/p/test/src/'
             earliest_commit = channel.findall('item')[-1]
-            assert_equal(earliest_commit.find('title').text, 'Create readme')
+            assert earliest_commit.find('title').text == 'Create readme'
             link = 'http://localhost/p/test/src/1/'
-            assert_equal(earliest_commit.find('link').text, link)
-            assert_equal(earliest_commit.find('guid').text, link)
+            assert earliest_commit.find('link').text == link
+            assert earliest_commit.find('guid').text == link
         # .atom has slightly different structure
         prefix = '{http://www.w3.org/2005/Atom}'
         r = self.app.get('/src/feed.atom')
         title = r.xml.find(prefix + 'title').text
-        assert_equal(title, 'test SVN changes')
+        assert title == 'test SVN changes'
         link = r.xml.find(prefix + 'link').attrib['href']
-        assert_equal(link, 'http://localhost/p/test/src/')
+        assert link == 'http://localhost/p/test/src/'
         earliest_commit = r.xml.findall(prefix + 'entry')[-1]
-        assert_equal(earliest_commit.find(prefix + 'title').text, 'Create readme')
+        assert earliest_commit.find(prefix + 'title').text == 'Create readme'
         link = 'http://localhost/p/test/src/1/'
-        assert_equal(earliest_commit.find(prefix + 'link').attrib['href'], link)
+        assert earliest_commit.find(prefix + 'link').attrib['href'] == link
 
     def test_commit(self):
         resp = self.app.get('/src/3/tree/')
@@ -146,7 +146,7 @@ class TestRootController(SVNTestController):
         resp = self.app.get('/src/6/')
         file_url = resp.html.find("a", string="/ЗРЯЧИЙ_ТА_ПОБАЧИТЬ")['href']
         resp = self.app.get(file_url)
-        assert_in('This is readme',  # same content as the README file actually
+        assert ('This is readme' in  # same content as the README file actually
                   resp.html.select_one('.codebrowser').text)
 
         resp = self.app.get('/src/7/')
@@ -154,22 +154,22 @@ class TestRootController(SVNTestController):
             '\n\t'.join(str(t) for t in resp.html.select('.inline-diff a'))))
         file_url = resp.html.find("a", string="/with%2Furlquote-literal.txt")['href']
         file_resp = self.app.get(file_url)
-        assert_in('%2F means /',
+        assert ('%2F means /' in
                   file_resp.html.select_one('.codebrowser').text)
 
         file_url = resp.html.find("a", string='/with-percent%.txt')['href']
         file_resp = self.app.get(file_url)
-        assert_in('%%%',
+        assert ('%%%' in
                   file_resp.html.select_one('.codebrowser').text)
 
         file_url = resp.html.find("a", string="/with space.txt")['href']
         file_resp = self.app.get(file_url)
-        assert_in('spaces',
+        assert ('spaces' in
                   file_resp.html.select_one('.codebrowser').text)
 
         file_url = resp.html.find("a", string='/with"&:specials.txt')['href']
         file_resp = self.app.get(file_url)
-        assert_in('"&: encodes as %22%26%3A',
+        assert ('"&: encodes as %22%26%3A' in
                   file_resp.html.select_one('.codebrowser').text)
 
     def test_tree(self):
@@ -262,39 +262,39 @@ class TestRootController(SVNTestController):
         shutil.rmtree(c.app.repo.tarball_path, ignore_errors=True)
         r = self.app.get('/p/test/svn-tags/19/tree/')
         form = r.html.find('form', 'tarball')
-        assert_equal(form.button.text, '\xa0Download Snapshot')
-        assert_equal(form.get('action'), '/p/test/svn-tags/19/tarball')
+        assert form.button.text == '\xa0Download Snapshot'
+        assert form.get('action') == '/p/test/svn-tags/19/tarball'
 
         r = self.app.get('/p/test/svn-tags/19/tree/tags/tag-1.0/')
         form = r.html.find('form', 'tarball')
-        assert_equal(form.button.text, '\xa0Download Snapshot')
-        assert_equal(form.get('action'), '/p/test/svn-tags/19/tarball')
-        assert_equal(form.find('input', attrs=dict(name='path')).get('value'), '/tags/tag-1.0')
+        assert form.button.text == '\xa0Download Snapshot'
+        assert form.get('action') == '/p/test/svn-tags/19/tarball'
+        assert form.find('input', attrs=dict(name='path')).get('value') == '/tags/tag-1.0'
 
         r = self.app.get('/p/test/svn-tags/19/tarball_status?path=/tags/tag-1.0')
-        assert_equal(r.json['status'], None)
+        assert r.json['status'] == None
         r = self.app.post('/p/test/svn-tags/19/tarball',
                           dict(path='/tags/tag-1.0')).follow()
         assert 'Generating snapshot...' in r
         M.MonQTask.run_ready()
         r = self.app.get('/p/test/svn-tags/19/tarball_status?path=/tags/tag-1.0')
-        assert_equal(r.json['status'], 'complete')
+        assert r.json['status'] == 'complete'
 
         r = self.app.get('/p/test/svn-tags/19/tarball_status?path=/trunk')
-        assert_equal(r.json['status'], None)
+        assert r.json['status'] == None
         r = self.app.post('/p/test/svn-tags/19/tarball',
                           dict(path='/trunk/')).follow()
         assert 'Generating snapshot...' in r
         M.MonQTask.run_ready()
         r = self.app.get('/p/test/svn-tags/19/tarball_status?path=/trunk')
-        assert_equal(r.json['status'], 'complete')
+        assert r.json['status'] == 'complete'
 
         r = self.app.get('/p/test/svn-tags/19/tarball_status?path=/branches/aaa/')
-        assert_equal(r.json['status'], None)
+        assert r.json['status'] == None
 
         # this is is the same as trunk snapshot, so it's ready already
         r = self.app.get('/p/test/svn-tags/19/tarball_status')
-        assert_equal(r.json['status'], 'complete')
+        assert r.json['status'] == 'complete'
 
 
 class TestImportController(SVNTestController):
diff --git a/ForgeSVN/forgesvn/tests/model/test_repository.py b/ForgeSVN/forgesvn/tests/model/test_repository.py
index 8451b4341..9d5e02856 100644
--- a/ForgeSVN/forgesvn/tests/model/test_repository.py
+++ b/ForgeSVN/forgesvn/tests/model/test_repository.py
@@ -82,25 +82,25 @@ class TestNewRepo(unittest.TestCase):
         assert self.rev.index_id().startswith('allura/model/repo/Commit#')
         self.rev.author_url
         self.rev.committer_url
-        assert_equal(self.rev.tree._id, self.rev.tree_id)
-        assert_equal(self.rev.shorthand_id(), f'[r{latest_rev}]')
-        assert_equal(self.rev.symbolic_ids, ([], []))
-        assert_equal(self.rev.url(), f'/p/test/src/{latest_rev}/')
+        assert self.rev.tree._id == self.rev.tree_id
+        assert self.rev.shorthand_id() == f'[r{latest_rev}]'
+        assert self.rev.symbolic_ids == ([], [])
+        assert self.rev.url() == f'/p/test/src/{latest_rev}/'
         all_cis = list(self.repo.log(self.rev._id, limit=25))
-        assert_equal(len(all_cis), latest_rev)
+        assert len(all_cis) == latest_rev
         self.rev.tree.ls()
-        assert_equal(self.rev.tree.readme(), ('README', 'This is readme\nAnother Line\n'))
-        assert_equal(self.rev.tree.path(), '/')
-        assert_equal(self.rev.tree.url(), f'/p/test/src/{latest_rev}/tree/')
+        assert self.rev.tree.readme() == ('README', 'This is readme\nAnother Line\n')
+        assert self.rev.tree.path() == '/'
+        assert self.rev.tree.url() == f'/p/test/src/{latest_rev}/tree/'
         self.rev.tree.by_name['README']
         assert self.rev.tree.is_blob('README') is True
-        assert_equal(self.rev.tree['a']['b']['c'].ls(), [])
+        assert self.rev.tree['a']['b']['c'].ls() == []
         self.assertRaises(KeyError, lambda: self.rev.tree['a']['b']['d'])
 
-        assert_equal(self.rev.authored_user, None)
-        assert_equal(self.rev.committed_user, None)
-        assert_equal(
-            sorted(self.rev.webhook_info.keys()),
+        assert self.rev.authored_user == None
+        assert self.rev.committed_user == None
+        assert (
+            sorted(self.rev.webhook_info.keys()) ==
             sorted(['id', 'url', 'timestamp', 'message', 'author',
                     'committer', 'added', 'removed', 'renamed', 'modified', 'copied']))
 
@@ -235,11 +235,11 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
 
     def test_log_id_only(self):
         entries = list(self.repo.log(id_only=True, limit=25))
-        assert_equal(entries, [7, 6, 5, 4, 3, 2, 1])
+        assert entries == [7, 6, 5, 4, 3, 2, 1]
 
     def test_log(self):
         entries = list(self.repo.log(id_only=False, limit=25))
-        assert_equal(entries[len(entries)-6:],  # only 6, so this test doesn't have to change when commits added
+        assert (entries[len(entries)-6:] ==  # only 6, so this test doesn't have to change when commits added
                      [
             {'parents': [5],
              'refs': [],
@@ -327,7 +327,7 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
 
     def test_log_file(self):
         entries = list(self.repo.log(path='/README', id_only=False, limit=25))
-        assert_equal(entries, [
+        assert entries == [
             {'authored': {'date': datetime(2010, 10, 8, 15, 32, 48, 272296),
                           'email': '',
                           'name': 'rick446'},
@@ -352,7 +352,7 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
              'refs': [],
              'size': 15,
              'rename_details': {}},
-        ])
+        ]
 
     def test_is_file(self):
         assert self.repo.is_file('/README')
@@ -407,9 +407,9 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
 
     def test_diff_copy(self):
         entry = self.repo.commit(next(self.repo.log(5, id_only=True, limit=1)))
-        assert_equals(dict(entry.diffs), dict(
+        assert dict(entry.diffs) == dict(
                 copied=[{'new': '/b', 'old': '/a', 'ratio': 1}],  renamed=[],
-                changed=[], removed=[], added=[], total=1))
+                changed=[], removed=[], added=[], total=1)
 
     def test_commit(self):
         entry = self.repo.commit(1)
@@ -432,16 +432,16 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
     @skipUnless(os.path.exists(tg.config.get('scm.repos.tarball.zip_binary', '/usr/bin/zip')), 'zip binary is missing')
     def test_tarball(self):
         tmpdir = tg.config['scm.repos.tarball.root']
-        assert_equal(self.repo.tarball_path,
+        assert (self.repo.tarball_path ==
                      os.path.join(tmpdir, 'svn/t/te/test/testsvn'))
-        assert_equal(self.repo.tarball_url('1'),
+        assert (self.repo.tarball_url('1') ==
                      'file:///svn/t/te/test/testsvn/test-src-r1.zip')
         self.repo.tarball('1')
         assert os.path.isfile(
             os.path.join(tmpdir, "svn/t/te/test/testsvn/test-src-r1.zip"))
         tarball_zip = ZipFile(
             os.path.join(tmpdir, 'svn/t/te/test/testsvn/test-src-r1.zip'), 'r')
-        assert_equal(tarball_zip.namelist(),
+        assert (tarball_zip.namelist() ==
                      ['test-src-r1/', 'test-src-r1/README'])
         shutil.rmtree(self.repo.tarball_path.encode('utf-8'),
                       ignore_errors=True)
@@ -461,7 +461,7 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
         tag_content = sorted(['test-svn-tags-r19-tags-tag-1.0/',
                               'test-svn-tags-r19-tags-tag-1.0/svn-commit.tmp',
                               'test-svn-tags-r19-tags-tag-1.0/README'])
-        assert_equal(sorted(snapshot.namelist()), tag_content)
+        assert sorted(snapshot.namelist()) == tag_content
         os.remove(fn)
 
         # a directory (of tags)
@@ -473,7 +473,7 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
                                'test-svn-tags-r19-tags/tag-1.0/',
                                'test-svn-tags-r19-tags/tag-1.0/svn-commit.tmp',
                                'test-svn-tags-r19-tags/tag-1.0/README'])
-        assert_equal(sorted(snapshot.namelist()), tags_content)
+        assert sorted(snapshot.namelist()) == tags_content
         os.remove(fn)
 
         # no path, but there are trunk in the repo
@@ -487,7 +487,7 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
                                 'test-svn-tags-r19-trunk/bbb.txt',
                                 'test-svn-tags-r19-trunk/ccc.txt',
                                 'test-svn-tags-r19-trunk/README'])
-        assert_equal(sorted(snapshot.namelist()), trunk_content)
+        assert sorted(snapshot.namelist()) == trunk_content
         os.remove(fn)
 
         # no path, and no trunk dir
@@ -497,7 +497,7 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
         self.repo.tarball('1')
         assert os.path.isfile(fn), fn
         snapshot = ZipFile(fn, 'r')
-        assert_equal(snapshot.namelist(), ['test-src-r1/', 'test-src-r1/README'])
+        assert snapshot.namelist() == ['test-src-r1/', 'test-src-r1/README']
         shutil.rmtree(os.path.join(tmpdir, 'svn/t/te/test/testsvn/'),
                       ignore_errors=True)
         shutil.rmtree(tarball_path, ignore_errors=True)
@@ -568,7 +568,7 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
                 'url': 'http://localhost/p/test/src/',
             },
         }
-        assert_equals(payload, expected_payload)
+        assert payload == expected_payload
 
 
 class TestSVNRev(unittest.TestCase):
@@ -614,17 +614,17 @@ class TestSVNRev(unittest.TestCase):
     def test_log(self):
         # path only
         commits = list(self.repo.log(self.repo.head, id_only=True, limit=25))
-        assert_equal(commits, [7, 6, 5, 4, 3, 2, 1])
+        assert commits == [7, 6, 5, 4, 3, 2, 1]
         commits = list(self.repo.log(self.repo.head, 'README', id_only=True, limit=25))
-        assert_equal(commits, [3, 1])
+        assert commits == [3, 1]
         commits = list(self.repo.log(1, 'README', id_only=True, limit=25))
-        assert_equal(commits, [1])
+        assert commits == [1]
         commits = list(self.repo.log(self.repo.head, 'a/b/c/', id_only=True, limit=25))
-        assert_equal(commits, [4, 2])
+        assert commits == [4, 2]
         commits = list(self.repo.log(3, 'a/b/c/', id_only=True, limit=25))
-        assert_equal(commits, [2])
-        assert_equal(
-            list(self.repo.log(self.repo.head, 'does/not/exist', id_only=True, limit=25)), [])
+        assert commits == [2]
+        assert (
+            list(self.repo.log(self.repo.head, 'does/not/exist', id_only=True, limit=25)) == [])
 
     def test_notification_email(self):
         setup_global_objects()
@@ -644,8 +644,8 @@ class TestSVNRev(unittest.TestCase):
         n = M.Notification.query.find({'subject': '[test:src] New commit [r1] by rick446'}).first()
 
         assert n
-        assert_in('By rick446', n.text)
-        assert_in('Create readme', n.text)
+        assert 'By rick446' in n.text
+        assert 'Create readme' in n.text
 
 
 class _Test(unittest.TestCase):
@@ -770,12 +770,12 @@ class TestRepo(_TestWithRepo):
         assert i['name_s'] == 'test1', i
 
     def test_scm_host_url(self):
-        assert_equal(self.repo.clone_url('rw', 'nobody'),
+        assert (self.repo.clone_url('rw', 'nobody') ==
                      'svn+ssh://nobody@localhost:8022/scm-repo/p/test/test1/')
-        assert_equal(self.repo.clone_url('https', 'nobody'),
+        assert (self.repo.clone_url('https', 'nobody') ==
                      'https://nobody@localhost:8022/scm-repo/p/test/test1/')
         with h.push_config(self.repo.app.config.options, external_checkout_url='https://$username@foo.com/'):
-            assert_equal(self.repo.clone_url('https', 'user'),
+            assert (self.repo.clone_url('https', 'user') ==
                          'https://user@foo.com/')
 
     def test_guess_type(self):
@@ -821,8 +821,8 @@ class TestRepo(_TestWithRepo):
         notifications = M.Notification.query.find().all()
         for n in notifications:
             if '100 new commits' in n.subject:
-                assert_in('By Test Committer on 10/08/2010 15:32', n.text)
-                assert_in('http://localhost/ci/foo99/', n.text)
+                assert 'By Test Committer on 10/08/2010 15:32' in n.text
+                assert 'http://localhost/ci/foo99/' in n.text
                 break
         else:
             assert False, 'Did not find notification'
@@ -945,7 +945,7 @@ class TestCommit(_TestWithRepo):
             'removed': [],
             'total': 5,
         }
-        assert_equal(self.ci.diffs.added,
+        assert (self.ci.diffs.added ==
                      ['a', 'a/a', 'a/a/a', 'a/a/b', 'a/b'])
         assert (self.ci.diffs.copied
                 == self.ci.diffs.changed
@@ -969,8 +969,8 @@ class TestCommit(_TestWithRepo):
             'removed': ['a', 'a/a', 'a/a/a', 'a/a/b', 'a/b'],
             'total': 10,
         }
-        assert_equal(ci.diffs.added, ['b', 'b/a', 'b/a/a', 'b/a/b', 'b/b'])
-        assert_equal(ci.diffs.removed, ['a', 'a/a', 'a/a/a', 'a/a/b', 'a/b'])
+        assert ci.diffs.added == ['b', 'b/a', 'b/a/a', 'b/a/b', 'b/b']
+        assert ci.diffs.removed == ['a', 'a/a', 'a/a/a', 'a/a/b', 'a/b']
         assert (ci.diffs.copied
                 == ci.diffs.changed
                 == [])
@@ -1004,17 +1004,17 @@ class TestCommit(_TestWithRepo):
             'renamed': [],
             'total': 2
         }
-        assert_equal(ci.diffs.added, ['b/a/z', 'b/c'])
-        assert_equal(ci.diffs.changed, [])
-        assert_equal(ci.diffs.removed, ['/b/a/b', 'b/b'])
+        assert ci.diffs.added == ['b/a/z', 'b/c']
+        assert ci.diffs.changed == []
+        assert ci.diffs.removed == ['/b/a/b', 'b/b']
         # see mock for open_blob
-        assert_equal(len(ci.diffs.copied), 2)
-        assert_equal(ci.diffs.copied[1]['old'], 'b/a/b')
-        assert_equal(ci.diffs.copied[1]['new'], 'b/c')
-        assert_equal(ci.diffs.copied[1]['ratio'], 1)
-        assert_equal(ci.diffs.copied[1]['diff'], '')
-        assert_equal(ci.diffs.copied[0]['old'], 'b/b')
-        assert_equal(ci.diffs.copied[0]['new'], 'b/a/z')
+        assert len(ci.diffs.copied) == 2
+        assert ci.diffs.copied[1]['old'] == 'b/a/b'
+        assert ci.diffs.copied[1]['new'] == 'b/c'
+        assert ci.diffs.copied[1]['ratio'] == 1
+        assert ci.diffs.copied[1]['diff'] == ''
+        assert ci.diffs.copied[0]['old'] == 'b/b'
+        assert ci.diffs.copied[0]['new'] == 'b/a/z'
 
     def test_context(self):
         self.ci.context()
@@ -1042,19 +1042,18 @@ class TestRename(unittest.TestCase):
 
     def test_log_file_with_rename(self):
         entry = list(self.repo.log(path='/dir/b.txt', id_only=False, limit=1))[0]
-        assert_equal(entry['id'], 3)
-        assert_equal(entry['rename_details']['path'], '/dir/a.txt')
-        assert_equal(
-            entry['rename_details']['commit_url'],
-            self.repo.url_for_commit(2)  # previous revision
-        )
+        assert entry['id'] == 3
+        assert entry['rename_details']['path'] == '/dir/a.txt'
+        assert (
+            entry['rename_details']['commit_url'] ==
+            self.repo.url_for_commit(2))
 
     def test_check_changed_path(self):
         changed_path = {'copyfrom_path': '/test/path', 'path': '/test/path2'}
         result = self.repo._impl._check_changed_path(
             changed_path, '/test/path2/file.txt')
-        assert_equal({'path': '/test/path2/file.txt',
-                     'copyfrom_path': '/test/path/file.txt'}, result)
+        assert {'path': '/test/path2/file.txt',
+                     'copyfrom_path': '/test/path/file.txt'} == result
 
 
 class TestDirectRepoAccess:
@@ -1088,7 +1087,7 @@ class TestDirectRepoAccess:
             'renamed': [],
             'total': 1,
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
 
         _id = self.repo._impl._oid(2)
         diffs = self.repo.commit(_id).diffs
@@ -1100,7 +1099,7 @@ class TestDirectRepoAccess:
             'copied': [],
             'total': 4,
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
 
         _id = self.repo._impl._oid(3)
         diffs = self.repo.commit(_id).diffs
@@ -1112,7 +1111,7 @@ class TestDirectRepoAccess:
             'copied': [],
             'total': 1,
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
 
         _id = self.repo._impl._oid(4)
         diffs = self.repo.commit(_id).diffs
@@ -1124,4 +1123,4 @@ class TestDirectRepoAccess:
             'copied': [],
             'total': 1,
         }
-        assert_equals(diffs, expected)
+        assert diffs == expected
diff --git a/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py b/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py
index 769ce1a82..0667d1fdc 100644
--- a/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py
+++ b/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py
@@ -51,8 +51,8 @@ class TestSVNImplementation:
 
         tree_id = impl.compute_tree_new(commit, path)
 
-        assert_equal(impl._svn.info2.call_args[0]
-                     [0], 'file://' + g.tmpdir + '/code/trunk/foo')
+        assert (impl._svn.info2.call_args[0]
+                     [0] == 'file://' + g.tmpdir + '/code/trunk/foo')
         assert lcd_partial.called
 
     def test_last_commit_ids(self):
@@ -73,9 +73,9 @@ class TestSVNImplementation:
         commit._id = '5057636b9c1040636b81e4b1:6'
         entries = impl.last_commit_ids(commit, [path])
 
-        assert_equal(entries, {path.strip('/'): '5057636b9c1040636b81e4b1:1'})
-        assert_equal(impl._svn.info2.call_args[0]
-                     [0], 'file://' + g.tmpdir + '/code/trunk')
+        assert entries == {path.strip('/'): '5057636b9c1040636b81e4b1:1'}
+        assert (impl._svn.info2.call_args[0]
+                     [0] == 'file://' + g.tmpdir + '/code/trunk')
 
     @patch('forgesvn.model.svn.svn_path_exists')
     def test__tarball_path_clean(self, path_exists):
@@ -85,16 +85,16 @@ class TestSVNImplementation:
         impl = SVNImplementation(repo)
         path_exists.return_value = False
         # edge cases
-        assert_equal(impl._tarball_path_clean(None), '')
-        assert_equal(impl._tarball_path_clean(''), '')
+        assert impl._tarball_path_clean(None) == ''
+        assert impl._tarball_path_clean('') == ''
         # common
-        assert_equal(impl._tarball_path_clean('/some/path/'), 'some/path')
-        assert_equal(impl._tarball_path_clean('some/path'), 'some/path')
-        assert_equal(impl._tarball_path_clean('/some/path/tags/1.0/some/dir'), 'some/path/tags/1.0/some/dir')
+        assert impl._tarball_path_clean('/some/path/') == 'some/path'
+        assert impl._tarball_path_clean('some/path') == 'some/path'
+        assert impl._tarball_path_clean('/some/path/tags/1.0/some/dir') == 'some/path/tags/1.0/some/dir'
         # with fallback to trunk
         path_exists.return_value = True
-        assert_equal(impl._tarball_path_clean(None), 'trunk')
-        assert_equal(impl._tarball_path_clean(''), 'trunk')
+        assert impl._tarball_path_clean(None) == 'trunk'
+        assert impl._tarball_path_clean('') == 'trunk'
 
     @patch('forgesvn.model.svn.svn_path_exists')
     def test_update_checkout_url(self, svn_path_exists):
@@ -104,14 +104,14 @@ class TestSVNImplementation:
         svn_path_exists.side_effect = lambda path: False
         opts['checkout_url'] = 'invalid'
         impl.update_checkout_url()
-        assert_equal(opts['checkout_url'], '')
+        assert opts['checkout_url'] == ''
 
         svn_path_exists.side_effect = lambda path: path.endswith('trunk')
         opts['checkout_url'] = 'invalid'
         impl.update_checkout_url()
-        assert_equal(opts['checkout_url'], 'trunk')
+        assert opts['checkout_url'] == 'trunk'
 
         svn_path_exists.side_effect = lambda path: path.endswith('trunk')
         opts['checkout_url'] = ''
         impl.update_checkout_url()
-        assert_equal(opts['checkout_url'], 'trunk')
+        assert opts['checkout_url'] == 'trunk'
diff --git a/ForgeSVN/forgesvn/tests/test_svn_app.py b/ForgeSVN/forgesvn/tests/test_svn_app.py
index b62a5cf5b..605ffe59c 100644
--- a/ForgeSVN/forgesvn/tests/test_svn_app.py
+++ b/ForgeSVN/forgesvn/tests/test_svn_app.py
@@ -40,8 +40,8 @@ class TestSVNApp(unittest.TestCase):
         ThreadLocalORMSession.close_all()
 
     def test_admin_menu(self):
-        assert_equals(len(c.app.admin_menu()), 7)
-        assert_equals(c.app.admin_menu()[0].label, 'Checkout URL')
+        assert len(c.app.admin_menu()) == 7
+        assert c.app.admin_menu()[0].label == 'Checkout URL'
 
     def test_uninstall(self):
         from allura import model as M
diff --git a/ForgeSVN/forgesvn/tests/test_tasks.py b/ForgeSVN/forgesvn/tests/test_tasks.py
index 3fa2c6d7c..e95899ecf 100644
--- a/ForgeSVN/forgesvn/tests/test_tasks.py
+++ b/ForgeSVN/forgesvn/tests/test_tasks.py
@@ -57,7 +57,7 @@ class TestRepoTasks(unittest.TestCase):
             repo_tasks.init()
             M.main_orm_session.flush()
             assert f.called_with()
-            assert_equal(ns, M.Notification.query.find().count())
+            assert ns == M.Notification.query.find().count()
 
     def test_clone(self):
         ns = M.Notification.query.find().count()
diff --git a/ForgeShortUrl/forgeshorturl/tests/functional/test.py b/ForgeShortUrl/forgeshorturl/tests/functional/test.py
index 704dcc78f..547ca8e01 100644
--- a/ForgeShortUrl/forgeshorturl/tests/functional/test.py
+++ b/ForgeShortUrl/forgeshorturl/tests/functional/test.py
@@ -43,7 +43,7 @@ class TestRootController(TestController):
         response.form['full_url'] = 'http://www.google.com/'
         response.form.submit()
         redir = self.app.get('/url/test', status=302)
-        assert_equal(redir.location, 'http://www.google.com/')
+        assert redir.location == 'http://www.google.com/'
 
     def test_shorturl_http_head(self):
         response = self.app.get('/admin/url/add')
@@ -51,7 +51,7 @@ class TestRootController(TestController):
         response.form['full_url'] = 'http://www.google.com/'
         response.form.submit()
         r = self.app.head('/url/test', status=302)
-        assert_equal(r.location, 'http://www.google.com/')
+        assert r.location == 'http://www.google.com/'
 
     def test_shorturl_update(self):
         response = self.app.get('/admin/url/add')
@@ -59,7 +59,7 @@ class TestRootController(TestController):
         response.form['full_url'] = 'http://www.google.com/'
         response.form.submit()
         redir = self.app.get('/url/g', status=302)
-        assert_equal(redir.location, 'http://www.google.com/')
+        assert redir.location == 'http://www.google.com/'
 
         response = self.app.get('/url/')
         form = response.forms['short-url-form']
@@ -69,7 +69,7 @@ class TestRootController(TestController):
         form.action = '/admin/url/add/'
         form.submit()
         redir = self.app.get('/url/g', status=302)
-        assert_equal(redir.location, 'http://www.yahoo.com/')
+        assert redir.location == 'http://www.yahoo.com/'
 
     def test_shorturl_not_found(self):
         self.app.post('/admin/url/add',
@@ -148,7 +148,7 @@ class TestRootController(TestController):
 
             url = ShortUrl.build_short_url(app, 's')
 
-            assert_equal(url, 'b:n:p:m:s')
+            assert url == 'b:n:p:m:s'
 
     def test_short_url(self):
         response = self.app.get('/admin/url/add')
@@ -162,4 +162,4 @@ class TestRootController(TestController):
                 'short_url.url_pattern': '{base_url}:{nbhd}:{project}:{mount_point}:{short_name}',
                 'base_url': 'b',
         }):
-            assert_equal(surl.short_url(), 'b:p:test:url:test')
+            assert surl.short_url() == 'b:p:test:url:test'
diff --git a/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py b/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
index 7c82519a1..9074e46ff 100644
--- a/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
+++ b/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
@@ -70,18 +70,18 @@ def test_fix_discussion():
     tracker = M.AppConfig.query.find({'options.mount_point': 'bugs'}).first()
     t1 = TM.Ticket.query.get(ticket_num=1)
     t2 = TM.Ticket.query.get(ticket_num=2)
-    assert_not_equal(
-        t1.discussion_thread.discussion.app_config_id, tracker._id)
-    assert_not_equal(t2.discussion_thread.discussion_id, tracker.discussion_id)
+    assert (
+        t1.discussion_thread.discussion.app_config_id != tracker._id)
+    assert t2.discussion_thread.discussion_id != tracker.discussion_id
 
     cmd = fix_discussion.FixDiscussion('fix-discussion')
     cmd.run([test_config, 'test'])
 
     t1 = TM.Ticket.query.get(ticket_num=1)
     t2 = TM.Ticket.query.get(ticket_num=2)
-    assert_equal(t1.discussion_thread.discussion.app_config_id, tracker._id)
-    assert_equal(t2.discussion_thread.discussion_id, tracker.discussion_id)
+    assert t1.discussion_thread.discussion.app_config_id == tracker._id
+    assert t2.discussion_thread.discussion_id == tracker.discussion_id
     for p in t2.discussion_thread.posts:
-        assert_equal(p.app_config_id, tracker._id)
-        assert_equal(p.app_id, tracker._id)
-        assert_equal(p.discussion_id, tracker.discussion_id)
+        assert p.app_config_id == tracker._id
+        assert p.app_id == tracker._id
+        assert p.discussion_id == tracker.discussion_id
diff --git a/ForgeTracker/forgetracker/tests/functional/test_rest.py b/ForgeTracker/forgetracker/tests/functional/test_rest.py
index 0d1f4acc3..be72dd250 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_rest.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_rest.py
@@ -90,13 +90,13 @@ class TestRestNewTicket(TestTrackerApiBase):
             summary = 'Second ticket'
             self.create_ticket(summary=summary)
             t = TM.Ticket.query.get(summary=summary)
-            assert_not_equal(t, None)
+            assert t != None
         # Set rate limit to 1 in first hour of project
         with h.push_config(config, **{'forgetracker.rate_limits': '{"3600": 1}'}):
             summary = 'Third ticket'
             self.create_ticket(summary=summary, status=429)
             t = TM.Ticket.query.get(summary=summary)
-            assert_equal(t, None)
+            assert t == None
 
 
 class TestRestUpdateTicket(TestTrackerApiBase):
@@ -152,7 +152,7 @@ class TestRestIndex(TestTrackerApiBase):
         # make sure it didn't get removed from the db too
         ticket_config = M.AppConfig.query.get(
             project_id=c.project._id, tool_name='tickets')
-        assert_equal(ticket_config.options.get('TicketMonitoringEmail'),
+        assert (ticket_config.options.get('TicketMonitoringEmail') ==
                      'test@localhost')
 
     @td.with_tool('test', 'Tickets', 'dummy')
@@ -164,7 +164,7 @@ class TestRestIndex(TestTrackerApiBase):
             params={'tracker': str(dummy_tracker.config._id)}).follow()
 
         ticket = self.api_get('/rest/p/test/bugs/1/')
-        assert_equal(ticket.request.path, '/rest/p/test/dummy/1/')
+        assert ticket.request.path == '/rest/p/test/dummy/1/'
 
 
 class TestRestDiscussion(TestTrackerApiBase):
@@ -206,11 +206,11 @@ class TestRestSearch(TestTrackerApiBase):
     def test_no_criteria(self, paged_search):
         paged_search.return_value = dict(tickets=[self.ticket])
         r = self.api_get('/rest/p/test/bugs/search')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['tickets'][0]['summary'], 'our test ticket')
-        assert_equal(r.json['tickets'][0]['ticket_num'], 5)
-        assert_equal(r.json['tickets'][0]['status'], 'open')
-        assert_equal(r.json['tickets'][0]['labels'], ['tiny', 'minor'])
+        assert r.status_int == 200
+        assert r.json['tickets'][0]['summary'] == 'our test ticket'
+        assert r.json['tickets'][0]['ticket_num'] == 5
+        assert r.json['tickets'][0]['status'] == 'open'
+        assert r.json['tickets'][0]['labels'] == ['tiny', 'minor']
         assert 'description' not in r.json
         assert 'discussion_thread' not in r.json
 
@@ -227,16 +227,16 @@ class TestRestSearch(TestTrackerApiBase):
         )
         r = self.api_get('/rest/p/test/bugs/search',
                          q=q, sort='status', limit='2')
-        assert_equal(r.status_int, 200)
-        assert_equal(r.json['limit'], 2)
-        assert_equal(r.json['q'], q)
-        assert_equal(r.json['sort'], 'status')
-        assert_equal(r.json['count'], 1)
-        assert_equal(r.json['page'], 0)
-        assert_equal(r.json['tickets'][0]['summary'], 'our test ticket')
-        assert_equal(r.json['tickets'][0]['ticket_num'], 5)
-        assert_equal(r.json['tickets'][0]['status'], 'open')
-        assert_equal(r.json['tickets'][0]['labels'], ['tiny', 'minor'])
+        assert r.status_int == 200
+        assert r.json['limit'] == 2
+        assert r.json['q'] == q
+        assert r.json['sort'] == 'status'
+        assert r.json['count'] == 1
+        assert r.json['page'] == 0
+        assert r.json['tickets'][0]['summary'] == 'our test ticket'
+        assert r.json['tickets'][0]['ticket_num'] == 5
+        assert r.json['tickets'][0]['status'] == 'open'
+        assert r.json['tickets'][0]['labels'] == ['tiny', 'minor']
         assert 'description' not in r.json
         assert 'discussion_thread' not in r.json
 
@@ -253,13 +253,13 @@ class TestRestHasAccess(TestTrackerApiBase):
         r = self.api_get(
             '/rest/p/test/bugs/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/bugs/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
 
     def test_has_access_not_admin(self):
         """
@@ -275,10 +275,10 @@ class TestRestHasAccess(TestTrackerApiBase):
         r = self.api_get(
             '/rest/p/test/bugs/has_access?user=test-admin&perm=delete',
             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/bugs/has_access?user=test-user&perm=delete',
             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/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 02ff29132..e0556322e 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -303,9 +303,9 @@ class TestFunctionalController(TrackerTestController):
     def test_new_ticket(self):
         summary = 'test new ticket'
         ticket_view = self.new_ticket(summary=summary).follow()
-        assert_true(summary in ticket_view)
+        assert summary in ticket_view
         opts = self.subscription_options(ticket_view)
-        assert_equal(opts['subscribed'], False)
+        assert opts['subscribed'] == False
 
     def test_ticket_get_markdown(self):
         self.new_ticket(summary='my ticket', description='my description')
@@ -350,17 +350,17 @@ class TestFunctionalController(TrackerTestController):
                 {'name': '1.0', 'count': 2},
                 {'name': '2.0', 'count': 0}
             ]}
-        assert_equal(r.text, json.dumps(counts))
+        assert r.text == json.dumps(counts)
         # Private tickets shouldn't be included in counts if user doesn't
         # have read access to private tickets.
         r = self.app.get('/bugs/milestone_counts',
                          extra_environ=dict(username='*anonymous'))
         counts['milestone_counts'][0]['count'] = 1
-        assert_equal(r.text, json.dumps(counts))
+        assert r.text == json.dumps(counts)
 
         self.app.post('/bugs/1/delete')
         r = self.app.get('/bugs/milestone_counts')
-        assert_equal(r.text, json.dumps(counts))
+        assert r.text == json.dumps(counts)
 
     def test_bin_counts(self):
         self.new_ticket(summary='test new')
@@ -368,9 +368,9 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.run_ready()
 
         r = self.app.get('/bugs/bin_counts')
-        assert_equal(r.json, {"bin_counts": [{"count": 2, "label": "Changes"},
+        assert r.json == {"bin_counts": [{"count": 2, "label": "Changes"},
                                              {"count": 0, "label": "Closed Tickets"},
-                                             {"count": 2, "label": "Open Tickets"}]})
+                                             {"count": 2, "label": "Open Tickets"}]}
 
         """
         forgetracker.model.ticket.Globals.bin_count doesn't do a permission check like corresponding milestone_count
@@ -409,13 +409,13 @@ class TestFunctionalController(TrackerTestController):
         response = self.app.get('/bugs/new/?summary=very buggy&description=descr&labels=label1,label2&private=true'
                                 '&assigned_to=test-user&_milestone=2.0&status=pending')
         form = self._find_new_ticket_form(response)
-        assert_equal(form['ticket_form.summary'].value, 'very buggy')
-        assert_equal(form['ticket_form.description'].value, 'descr')
-        assert_equal(form['ticket_form.labels'].value, 'label1,label2')
-        assert_equal(form['ticket_form.assigned_to'].value, 'test-user')
-        assert_equal(form['ticket_form._milestone'].value, '2.0')
-        assert_equal(form['ticket_form.status'].value, 'pending')
-        assert_equal(form['ticket_form.private'].checked, True)
+        assert form['ticket_form.summary'].value == 'very buggy'
+        assert form['ticket_form.description'].value == 'descr'
+        assert form['ticket_form.labels'].value == 'label1,label2'
+        assert form['ticket_form.assigned_to'].value == 'test-user'
+        assert form['ticket_form._milestone'].value == '2.0'
+        assert form['ticket_form.status'].value == 'pending'
+        assert form['ticket_form.private'].checked == True
 
     def test_mass_edit(self):
         self.new_ticket(summary='First Ticket').follow()
@@ -481,9 +481,9 @@ class TestFunctionalController(TrackerTestController):
         ticket1 = tm.Ticket.query.get(summary='Ticket1')
         ticket2 = tm.Ticket.query.get(summary='Ticket2')
         ticket3 = tm.Ticket.query.get(summary='Ticket3')
-        assert_equal(ticket1.labels, ['tag2', 'tag3'])
-        assert_equal(ticket2.labels, ['tag1', 'tag2', 'tag3'])
-        assert_equal(ticket3.labels, ['tag1', 'tag2', 'tag3'])
+        assert ticket1.labels == ['tag2', 'tag3']
+        assert ticket2.labels == ['tag1', 'tag2', 'tag3']
+        assert ticket3.labels == ['tag1', 'tag2', 'tag3']
         r = self.app.get('/p/test/bugs/3/')
         assert '<li><strong>Labels</strong>: tag1, tag2 --&gt; tag1, tag2, tag3</li>' in r
 
@@ -524,8 +524,8 @@ class TestFunctionalController(TrackerTestController):
             'summary': 'First Custom'}).first()
         ticket2 = tm.Ticket.query.find({
             'summary': 'Second Custom'}).first()
-        assert_equal(ticket1.custom_fields._major, False)
-        assert_equal(ticket2.custom_fields._major, False)
+        assert ticket1.custom_fields._major == False
+        assert ticket2.custom_fields._major == False
 
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
@@ -542,8 +542,8 @@ class TestFunctionalController(TrackerTestController):
         assert '<li><strong>Major</strong>: False --&gt; True</li>' in r
         ticket1 = tm.Ticket.query.find({'summary': 'First Custom'}).first()
         ticket2 = tm.Ticket.query.find({'summary': 'Second Custom'}).first()
-        assert_equal(ticket1.custom_fields._major, True)
-        assert_equal(ticket2.custom_fields._major, True)
+        assert ticket1.custom_fields._major == True
+        assert ticket2.custom_fields._major == True
 
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
@@ -554,7 +554,7 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.run_ready()
         ticket2 = tm.Ticket.query.find({
             'summary': 'Second Custom'}).first()
-        assert_equal(ticket2.custom_fields._major, False)
+        assert ticket2.custom_fields._major == False
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
@@ -566,8 +566,8 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.run_ready()
         ticket1 = tm.Ticket.query.find({'summary': 'First Custom'}).first()
         ticket2 = tm.Ticket.query.find({'summary': 'Second Custom'}).first()
-        assert_equal(ticket1.custom_fields._major, True)
-        assert_equal(ticket2.custom_fields._major, False)
+        assert ticket1.custom_fields._major == True
+        assert ticket2.custom_fields._major == False
 
     def test_mass_edit_select_options_split(self):
         params = dict(
@@ -585,12 +585,12 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.get('/p/test/bugs/edit/')
         opts = r.html.find('select', attrs={'name': '_type'})
         opts = opts.findAll('option')
-        assert_equal(opts[0].get('value'), '')
-        assert_equal(opts[0].getText(), 'no change')
-        assert_equal(opts[1].get('value'), 'Bug')
-        assert_equal(opts[1].getText(), 'Bug')
-        assert_equal(opts[2].get('value'), 'Feature Request')
-        assert_equal(opts[2].getText(), 'Feature Request')
+        assert opts[0].get('value') == ''
+        assert opts[0].getText() == 'no change'
+        assert opts[1].get('value') == 'Bug'
+        assert opts[1].getText() == 'Bug'
+        assert opts[2].get('value') == 'Feature Request'
+        assert opts[2].getText() == 'Feature Request'
 
     def test_mass_edit_private_field(self):
         kw = {'private': True}
@@ -613,8 +613,8 @@ class TestFunctionalController(TrackerTestController):
         assert '<li><strong>Private</strong>: No --&gt; Yes</li>' not in r
         ticket1 = tm.Ticket.query.find({'summary': 'First'}).first()
         ticket2 = tm.Ticket.query.find({'summary': 'Second'}).first()
-        assert_equal(ticket1.private, False)
-        assert_equal(ticket2.private, False)
+        assert ticket1.private == False
+        assert ticket2.private == False
 
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
@@ -630,8 +630,8 @@ class TestFunctionalController(TrackerTestController):
         assert '<li><strong>Private</strong>: No --&gt; Yes</li>' in r
         ticket1 = tm.Ticket.query.find({'summary': 'First'}).first()
         ticket2 = tm.Ticket.query.find({'summary': 'Second'}).first()
-        assert_equal(ticket1.private, True)
-        assert_equal(ticket2.private, True)
+        assert ticket1.private == True
+        assert ticket2.private == True
 
         ticket2.private = False
         self.app.post('/p/test/bugs/update_tickets', {
@@ -644,15 +644,15 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.run_ready()
         ticket1 = tm.Ticket.query.find({'summary': 'First'}).first()
         ticket2 = tm.Ticket.query.find({'summary': 'Second'}).first()
-        assert_equal(ticket1.private, True)
-        assert_equal(ticket2.private, False)
+        assert ticket1.private == True
+        assert ticket2.private == False
 
     def test_private_ticket(self):
         ticket_view = self.new_ticket(summary='Public Ticket').follow()
-        assert_in('<label class="simple">Private:</label> No', squish_spaces(ticket_view.text))
+        assert '<label class="simple">Private:</label> No' in squish_spaces(ticket_view.text)
         ticket_view = self.new_ticket(summary='Private Ticket',
                                       private=True).follow()
-        assert_in('<label class="simple">Private:</label> Yes', squish_spaces(ticket_view.text))
+        assert '<label class="simple">Private:</label> Yes' in squish_spaces(ticket_view.text)
         M.MonQTask.run_ready()
         # Creator sees private ticket on list page...
         index_response = self.app.get('/p/test/bugs/')
@@ -699,12 +699,12 @@ class TestFunctionalController(TrackerTestController):
             'ticket_form.private': 'on',
         })
         response = self.app.get('/bugs/1/')
-        assert_true('<li><strong>private</strong>: No --&gt; Yes</li>' in response)
+        assert '<li><strong>private</strong>: No --&gt; Yes</li>' in response
 
     def test_discussion_disabled_ticket(self):
         response = self.new_ticket(summary='test discussion disabled ticket').follow()
         # New tickets will not show discussion disabled
-        assert_not_in('<span class="closed">Discussion Disabled</span>', response)
+        assert '<span class="closed">Discussion Disabled</span>' not in response
 
         ticket_params = {
             'ticket_form.summary': 'test discussion disabled ticket',
@@ -719,28 +719,28 @@ class TestFunctionalController(TrackerTestController):
 
         # Disable Discussion
         response = self.app.post('/bugs/1/update_ticket_from_widget', ticket_params).follow()
-        assert_in('<li><strong>discussion</strong>: enabled --&gt; disabled</li>', response)
-        assert_in('<span class="closed">Discussion Disabled</span>', response)
-        assert_in('edit_post_form reply', response)  # Make sure admin can still comment
+        assert '<li><strong>discussion</strong>: enabled --&gt; disabled</li>' in response
+        assert '<span class="closed">Discussion Disabled</span>' in response
+        assert 'edit_post_form reply' in response  # Make sure admin can still comment
 
         # Unauthorized user cannot comment or even see form fields
         env = dict(username='*anonymous')
         r = self.app.get('/p/test/bugs/1', extra_environ=env)
-        assert_not_in('edit_post_form reply', r)
+        assert 'edit_post_form reply' not in r
 
         # Test re-enabling discussions
         ticket_params['ticket_form.discussion_disabled'] = 'off'
         response = self.app.post('/bugs/1/update_ticket_from_widget', ticket_params).follow()
-        assert_in('<li><strong>discussion</strong>: disabled --&gt; enabled</li>', response)
-        assert_not_in('<span class="closed">Discussion Disabled</span>', response)
+        assert '<li><strong>discussion</strong>: disabled --&gt; enabled</li>' in response
+        assert '<span class="closed">Discussion Disabled</span>' not in response
 
         # Test solr search
         M.MonQTask.run_ready()
         ThreadLocalORMSession.flush_all()
         # At this point, there is one ticket and it has discussion_disabled set to False
         r = self.app.get('/bugs/search/?q=discussion_disabled_b:False')
-        assert_in('1 results', r)
-        assert_in('test discussion disabled ticket', r)
+        assert '1 results' in r
+        assert 'test discussion disabled ticket' in r
 
         # Set discussion_disabled to True and search again
         ticket_params['ticket_form.discussion_disabled'] = 'on'
@@ -748,12 +748,12 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.run_ready()
         ThreadLocalORMSession.flush_all()
         r = self.app.get('/bugs/search/?q=discussion_disabled_b:True')
-        assert_in('1 results', r)
-        assert_in('test discussion disabled ticket', r)
+        assert '1 results' in r
+        assert 'test discussion disabled ticket' in r
 
         # Make sure there are no other tickets or false positives for good measure.
         r = self.app.get('/bugs/search/?q=discussion_disabled_b:False')
-        assert_in('0 results', r)
+        assert '0 results' in r
 
     @td.with_tool('test', 'Tickets', 'doc-bugs')
     def test_two_trackers(self):
@@ -763,13 +763,13 @@ class TestFunctionalController(TrackerTestController):
         ThreadLocalORMSession.flush_all()
         M.MonQTask.run_ready()
         ThreadLocalORMSession.flush_all()
-        assert_true(summary in ticket_view)
+        assert summary in ticket_view
         index_view = self.app.get('/doc-bugs/')
-        assert_true(summary in index_view)
-        assert_true(sidebar_contains(index_view, '<span>1.0</span>'))
+        assert summary in index_view
+        assert sidebar_contains(index_view, '<span>1.0</span>')
         index_view = self.app.get('/bugs/')
-        assert_true(sidebar_contains(index_view, '<span>1.0</span>'))
-        assert_false(summary in index_view)
+        assert sidebar_contains(index_view, '<span>1.0</span>')
+        assert not summary in index_view
 
     def test_render_ticket(self):
         summary = 'test render ticket'
@@ -790,7 +790,7 @@ class TestFunctionalController(TrackerTestController):
         # Make sure the 'Create Ticket' button is disabled for user without 'create' perm
         r = self.app.get('/bugs/', extra_environ=dict(username='*anonymous'))
         create_button = r.html.find('a', attrs={'href': '/p/test/bugs/new/'})
-        assert_equal(create_button['class'], ['icon', 'sidebar-disabled'])
+        assert create_button['class'] == ['icon', 'sidebar-disabled']
 
     @patch.dict('allura.lib.app_globals.config', markdown_cache_threshold='0')
     def test_cached_convert(self):
@@ -811,11 +811,11 @@ class TestFunctionalController(TrackerTestController):
         # We want to make sure the 'last_updated' field isn't updated by the cache creation
         r = self.app.get('/bugs/1').follow()
         last_updated = r.html.find("span", {"id": "updated_id"}).text.strip()
-        assert_equal(last_updated, '2010-01-01')
+        assert last_updated == '2010-01-01'
 
         # Make sure the cache has been saved.
         t = tm.Ticket.query.find({'_id': ticket._id}).first()
-        assert_in('<h1 id="test-markdown-cached_convert">Test markdown cached_convert</h1>', t.description_cache.html)
+        assert '<h1 id="test-markdown-cached_convert">Test markdown cached_convert</h1>' in t.description_cache.html
 
     def test_ticket_diffs(self):
         self.new_ticket(summary='difftest', description='1\n2\n3\n')
@@ -834,8 +834,8 @@ class TestFunctionalController(TrackerTestController):
             'comment': 'user comment',
         })
         t = tm.Ticket.query.get(ticket_num=1)
-        assert_true(t.discussion_thread.first_post.is_meta)
-        assert_false(t.discussion_thread.last_post.is_meta)
+        assert t.discussion_thread.first_post.is_meta
+        assert not t.discussion_thread.last_post.is_meta
 
     def test_ticket_label_unlabel(self):
         summary = 'test labeling and unlabeling a ticket'
@@ -850,9 +850,9 @@ class TestFunctionalController(TrackerTestController):
             'comment': ''
         })
         response = self.app.get('/bugs/1/')
-        assert_true('yellow' in response)
-        assert_true('greén' in response)
-        assert_true('<li><strong>labels</strong>:  --&gt; yellow, greén</li>' in response)
+        assert 'yellow' in response
+        assert 'greén' in response
+        assert '<li><strong>labels</strong>:  --&gt; yellow, greén</li>' in response
         self.app.post('/bugs/1/update_ticket', {
             'summary': 'zzz',
             'description': 'bbb',
@@ -863,8 +863,8 @@ class TestFunctionalController(TrackerTestController):
             'comment': ''
         })
         response = self.app.get('/bugs/1/')
-        assert_true('yellow' in response)
-        assert_true('<li><strong>labels</strong>: yellow, greén --&gt; yellow</li>' in response)
+        assert 'yellow' in response
+        assert '<li><strong>labels</strong>: yellow, greén --&gt; yellow</li>' in response
         self.app.post('/bugs/1/update_ticket', {
             'summary': 'zzz',
             'description': 'bbb',
@@ -875,7 +875,7 @@ class TestFunctionalController(TrackerTestController):
             'comment': ''
         })
         response = self.app.get('/bugs/1/')
-        assert_true('<li><strong>labels</strong>: yellow --&gt; </li>' in response)
+        assert '<li><strong>labels</strong>: yellow --&gt; </li>' in response
 
     def test_new_attachment(self):
         file_name = 'test_root.py'
@@ -885,12 +885,12 @@ class TestFunctionalController(TrackerTestController):
         ticket_editor = self.app.post('/bugs/1/update_ticket', {
             'summary': 'zzz'
         }, upload_files=[upload]).follow()
-        assert_true(file_name in ticket_editor)
+        assert file_name in ticket_editor
         assert '<span>py</span>' not in ticket_editor
         ticket_page = self.app.get('/bugs/1/')
         diff = ticket_page.html.findAll('div', attrs={'class': 'codehilite'})
         added = diff[-1].findAll('span', attrs={'class': 'gi'})[-1]
-        assert_in('+test_root.py', added.getText())
+        assert '+test_root.py' in added.getText()
 
     def test_delete_attachment(self):
         file_name = 'test_root.py'
@@ -904,7 +904,7 @@ class TestFunctionalController(TrackerTestController):
         req = self.app.get('/bugs/1/')
         form = self._find_update_ticket_form(req)
         file_link = BeautifulSoup(form.text).findAll('a')[2]
-        assert_equal(file_link.string, file_name)
+        assert file_link.string == file_name
         self.app.post(str(file_link['href']), {
             'delete': 'True'
         })
@@ -912,7 +912,7 @@ class TestFunctionalController(TrackerTestController):
         assert '/p/test/bugs/1/attachment/test_root.py' not in ticket_page
         diff = ticket_page.html.findAll('div', attrs={'class': 'codehilite'})
         removed = diff[-1].findAll('span', attrs={'class': 'gd'})[-1]
-        assert_in('-test_root.py', removed.getText())
+        assert '-test_root.py' in removed.getText()
 
     def test_delete_attachment_from_comments(self):
         ticket_view = self.new_ticket(summary='test ticket').follow()
@@ -947,7 +947,7 @@ class TestFunctionalController(TrackerTestController):
         }, upload_files=[upload]).follow()
         form = self._find_update_ticket_form(ticket_editor)
         download = self.app.get(str(BeautifulSoup(form.text).findAll('a')[2]['href']))
-        assert_equal(download.body, file_data)
+        assert download.body == file_data
 
     def test_two_attachments(self):
         file_name1 = 'test_root1.py'
@@ -1043,12 +1043,12 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.run_ready()
         ThreadLocalORMSession.flush_all()
         r = self.app.get('/p/test/bugs/3/')
-        assert_in('Tickets: #1', r)
-        assert_not_in('Tickets: <s>#1</s>', r)
-        assert_in('Tickets: <s>#2</s>', r)
+        assert 'Tickets: #1' in r
+        assert 'Tickets: <s>#1</s>' not in r
+        assert 'Tickets: <s>#2</s>' in r
 
-        assert_in('<a class="alink" href="/p/test/bugs/1/">[#1]</a>', r.text)
-        assert_in('<a class="alink strikethrough" href="/p/test/bugs/2/">[#2]</a>', r.text)
+        assert '<a class="alink" href="/p/test/bugs/1/">[#1]</a>' in r.text
+        assert '<a class="alink strikethrough" href="/p/test/bugs/2/">[#2]</a>' in r.text
 
     def test_ticket_view_editable(self):
         summary = 'test ticket view page can be edited'
@@ -1299,22 +1299,22 @@ class TestFunctionalController(TrackerTestController):
         form['ticket_form.custom_fields._category'] = 'bugs'
         error_form = form.submit()
         form = self._find_new_ticket_form(error_form)
-        assert_equal(form['ticket_form.custom_fields._priority'].value, 'urgent')
-        assert_equal(form['ticket_form.custom_fields._category'].value, 'bugs')
+        assert form['ticket_form.custom_fields._priority'].value == 'urgent'
+        assert form['ticket_form.custom_fields._category'].value == 'bugs'
         # Test edit ticket form
         self.new_ticket(summary='Test ticket')
         response = self.app.get('/bugs/1/')
         form = self._find_update_ticket_form(response)
-        assert_equal(
-            form['ticket_form.custom_fields._priority'].value, 'normal')
-        assert_equal(form['ticket_form.custom_fields._category'].value, '')
+        assert (
+            form['ticket_form.custom_fields._priority'].value == 'normal')
+        assert form['ticket_form.custom_fields._category'].value == ''
         form['ticket_form.summary'] = ''
         form['ticket_form.custom_fields._priority'] = 'urgent'
         form['ticket_form.custom_fields._category'] = 'bugs'
         error_form = form.submit()
         form = self._find_update_ticket_form(error_form)
-        assert_equal(form['ticket_form.custom_fields._priority'].value, 'urgent')
-        assert_equal(form['ticket_form.custom_fields._category'].value, 'bugs')
+        assert form['ticket_form.custom_fields._priority'].value == 'urgent'
+        assert form['ticket_form.custom_fields._category'].value == 'bugs'
 
     def test_new_ticket_validation(self):
         summary = 'ticket summary'
@@ -1384,9 +1384,9 @@ class TestFunctionalController(TrackerTestController):
         response.mustcontain('results of 3')
         response.mustcontain('test second ticket')
         next_page_link = response.html.select('.page_list a')[0]
-        assert_equal(next_page_link.text, '2')
+        assert next_page_link.text == '2'
         # keep 'q' and zero-based page nums:
-        assert_equal(next_page_link['href'], '/p/test/bugs/search/?q=test&limit=2&page=1')
+        assert next_page_link['href'] == '/p/test/bugs/search/?q=test&limit=2&page=1'
 
         # 'filter' is special kwarg, don't let it cause problems
         r = self.app.get('/p/test/bugs/search/?q=test&filter=blah')
@@ -1550,8 +1550,8 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.post(f['action'], params=params,
                           headers={'Referer': b'/bugs/1/'})
         r = self.app.get('/bugs/1/', dict(page='1'))
-        assert_true(post_content in r)
-        assert_true(len(r.html.findAll(attrs={'class': 'discussion-post'})) == 1)
+        assert post_content in r
+        assert len(r.html.findAll(attrs={'class': 'discussion-post'})) == 1
 
         new_summary = 'old ticket'
         for f in ticket_view.html.findAll('form'):
@@ -1566,8 +1566,8 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.post(f['action'], params=params,
                           headers={'Referer': b'/bugs/1/'})
         r = self.app.get('/bugs/1/', dict(page='1'))
-        assert_true(summary + ' --&gt; ' + new_summary in r)
-        assert_true(len(r.html.findAll(attrs={'class': 'discussion-post meta_post'})) == 1)
+        assert summary + ' --&gt; ' + new_summary in r
+        assert len(r.html.findAll(attrs={'class': 'discussion-post meta_post'})) == 1
 
     def test_discussion_paging(self):
         summary = 'test discussion paging'
@@ -1585,17 +1585,17 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.post(f['action'], params=params,
                           headers={'Referer': b'/bugs/1/'})
         r = self.app.get('/bugs/1/', dict(page='-1'))
-        assert_true(summary in r)
+        assert summary in r
         r = self.app.get('/bugs/1/', dict(page='1'))
-        assert_true(post_content in r)
+        assert post_content in r
         # no pager if just one page
-        assert_false('Page 1 of 1' in r)
+        assert not 'Page 1 of 1' in r
         # add some more posts and check for pager
         for i in range(2):
             r = self.app.post(f['action'], params=params,
                               headers={'Referer': b'/bugs/1/'})
         r = self.app.get('/bugs/1/', dict(page='1', limit='2'))
-        assert_true('Page 2 of 2' in r)
+        assert 'Page 2 of 2' in r
 
     def test_discussion_feed(self):
         summary = 'test discussion paging'
@@ -1643,16 +1643,16 @@ class TestFunctionalController(TrackerTestController):
         ThreadLocalORMSession.flush_all()
         response = self.app.get('/p/test/bugs/?sort=summary+asc')
         ticket_rows = response.html.find('table', {'class': 'ticket-list'}).find('tbody')
-        assert_in('test first ticket', ticket_rows.text)
-        assert_in('test second ticket', ticket_rows.text)
+        assert 'test first ticket' in ticket_rows.text
+        assert 'test second ticket' in ticket_rows.text
         edit_link = response.html.find('a', {'title': 'Bulk Edit'})
         expected_link = "/p/test/bugs/edit/?q=%21status%3Aclosed+%26%26+%21status%3Awont-fix"\
                         "&sort=snippet_s+asc&limit=25&filter=&page=0"
         assert_equivalent_urls(expected_link, edit_link['href'])
         response = self.app.get(edit_link['href'])
         ticket_rows = response.html.find('tbody', {'class': 'ticket-list'})
-        assert_in('test first ticket', ticket_rows.text)
-        assert_in('test second ticket', ticket_rows.text)
+        assert 'test first ticket' in ticket_rows.text
+        assert 'test second ticket' in ticket_rows.text
 
     def test_bulk_edit_milestone(self):
         self.new_ticket(summary='test first ticket',
@@ -1666,17 +1666,17 @@ class TestFunctionalController(TrackerTestController):
         ThreadLocalORMSession.flush_all()
         response = self.app.get('/p/test/bugs/milestone/1.0/?sort=ticket_num+asc')
         ticket_rows = response.html.find('table', {'class': 'ticket-list'}).find('tbody')
-        assert_in('test first ticket', ticket_rows.text)
-        assert_in('test second ticket', ticket_rows.text)
-        assert_in('test third ticket', ticket_rows.text)
+        assert 'test first ticket' in ticket_rows.text
+        assert 'test second ticket' in ticket_rows.text
+        assert 'test third ticket' in ticket_rows.text
         edit_link = response.html.find('a', {'title': 'Bulk Edit'})
         expected_link = "/p/test/bugs/edit/?q=_milestone%3A1.0&sort=ticket_num_i+asc&limit=25&filter=&page=0"
         assert_equivalent_urls(expected_link, edit_link['href'])
         response = self.app.get(edit_link['href'])
         ticket_rows = response.html.find('tbody', {'class': 'ticket-list'})
-        assert_in('test first ticket', ticket_rows.text)
-        assert_in('test second ticket', ticket_rows.text)
-        assert_in('test third ticket', ticket_rows.text)
+        assert 'test first ticket' in ticket_rows.text
+        assert 'test second ticket' in ticket_rows.text
+        assert 'test third ticket' in ticket_rows.text
 
     def test_bulk_edit_search(self):
         self.new_ticket(summary='test first ticket', status='open')
@@ -1687,17 +1687,17 @@ class TestFunctionalController(TrackerTestController):
         ThreadLocalORMSession.flush_all()
         response = self.app.get('/p/test/bugs/search/?q=status%3Aopen')
         ticket_rows = response.html.find('table', {'class': 'ticket-list'}).find('tbody')
-        assert_in('test first ticket', ticket_rows.text)
-        assert_in('test second ticket', ticket_rows.text)
-        assert_false('test third ticket' in ticket_rows.text)
+        assert 'test first ticket' in ticket_rows.text
+        assert 'test second ticket' in ticket_rows.text
+        assert not 'test third ticket' in ticket_rows.text
         edit_link = response.html.find('a', {'title': 'Bulk Edit'})
         expected_link = "/p/test/bugs/edit/?q=status%3Aopen&limit=25&filter=%7B%7D&page=0"
         assert_equivalent_urls(expected_link, edit_link['href'])
         response = self.app.get(edit_link['href'])
         ticket_rows = response.html.find('tbody', {'class': 'ticket-list'})
-        assert_in('test first ticket', ticket_rows.text)
-        assert_in('test second ticket', ticket_rows.text)
-        assert_false('test third ticket' in ticket_rows.text)
+        assert 'test first ticket' in ticket_rows.text
+        assert 'test second ticket' in ticket_rows.text
+        assert not 'test third ticket' in ticket_rows.text
 
     def test_bulk_edit_after_filtering(self):
         self.new_ticket(summary='test first ticket', status='open')
@@ -1713,7 +1713,7 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.post('/bugs/save_ticket', {
             'ticket_form.summary': 'new ticket with attachment'
         }, upload_files=[upload]).follow()
-        assert_in(file_name, r)
+        assert file_name in r
         ThreadLocalORMSession.flush_all()
         M.MonQTask.run_ready()
         ThreadLocalORMSession.flush_all()
@@ -1724,7 +1724,7 @@ class TestFunctionalController(TrackerTestController):
             '**Attachments:**\n\n'
             '- [tést_root.py]'
             '(http://localhost/p/test/bugs/1/attachment/t%C3%A9st_root.py)')
-        assert_in(expected_text, email.kwargs['text'])
+        assert expected_text in email.kwargs['text']
 
     def test_ticket_notification_contains_milestones(self):
         params = dict(
@@ -1755,8 +1755,8 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.run_ready()
         ThreadLocalORMSession.flush_all()
         email = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).first()
-        assert_in('**Releases:** 1.0-beta', email.kwargs.text)
-        assert_in('**Milestone:** 2.0', email.kwargs.text)
+        assert '**Releases:** 1.0-beta' in email.kwargs.text
+        assert '**Milestone:** 2.0' in email.kwargs.text
 
     def test_bulk_edit_notifications(self):
         self.new_ticket(summary='test first ticket',
@@ -1788,26 +1788,26 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.run_ready()
 
         emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
-        assert_equal(len(emails), 3)
+        assert len(emails) == 3
         for email in emails:
-            assert_equal(email.kwargs.subject, '[test:bugs] Mass edit changes by Test Admin')
+            assert email.kwargs.subject == '[test:bugs] Mass edit changes by Test Admin'
         first_user_email = M.MonQTask.query.find({
             'task_name': 'allura.tasks.mail_tasks.sendmail',
             'kwargs.destinations': str(first_user._id)
         }).all()
-        assert_equal(len(first_user_email), 1)
+        assert len(first_user_email) == 1
         first_user_email = first_user_email[0]
         second_user_email = M.MonQTask.query.find({
             'task_name': 'allura.tasks.mail_tasks.sendmail',
             'kwargs.destinations': str(second_user._id)
         }).all()
-        assert_equal(len(second_user_email), 1)
+        assert len(second_user_email) == 1
         second_user_email = second_user_email[0]
         admin_email = M.MonQTask.query.find({
             'task_name': 'allura.tasks.mail_tasks.sendmail',
             'kwargs.destinations': str(admin._id)
         }).all()
-        assert_equal(len(admin_email), 1)
+        assert len(admin_email) == 1
         admin_email = admin_email[0]
 
         # Expected data
@@ -1835,13 +1835,13 @@ class TestFunctionalController(TrackerTestController):
 - **Milestone**: 1.0 --> 2.0
 '''
         email = '\n'.join([email_header, first_ticket_changes, ''])
-        assert_equal(email, first_user_email.kwargs.text)
+        assert email == first_user_email.kwargs.text
         email = '\n'.join([email_header, second_ticket_changes, ''])
-        assert_equal(email, second_user_email.kwargs.text)
-        assert_in(email_header, admin_email.kwargs.text)
-        assert_in(first_ticket_changes, admin_email.kwargs.text)
-        assert_in(second_ticket_changes, admin_email.kwargs.text)
-        assert_in(third_ticket_changes, admin_email.kwargs.text)
+        assert email == second_user_email.kwargs.text
+        assert email_header in admin_email.kwargs.text
+        assert first_ticket_changes in admin_email.kwargs.text
+        assert second_ticket_changes in admin_email.kwargs.text
+        assert third_ticket_changes in admin_email.kwargs.text
 
     def test_bulk_edit_notifications_monitoring_email(self):
         self.app.post('/admin/bugs/set_options', params={
@@ -1862,9 +1862,9 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.run_ready()
         emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
         # one for admin and one for monitoring email
-        assert_equal(len(emails), 2)
+        assert len(emails) == 2
         for email in emails:
-            assert_equal(email.kwargs.subject, '[test:bugs] Mass edit changes by Test Admin')
+            assert email.kwargs.subject == '[test:bugs] Mass edit changes by Test Admin'
         admin = M.User.by_username('test-admin')
         admin_email = M.MonQTask.query.find({
             'task_name': 'allura.tasks.mail_tasks.sendmail',
@@ -1874,11 +1874,11 @@ class TestFunctionalController(TrackerTestController):
             'task_name': 'allura.tasks.mail_tasks.sendmail',
             'kwargs.destinations': 'monitoring@email.com'
         }).all()
-        assert_equal(len(admin_email), 1)
-        assert_equal(len(monitoring_email), 1)
+        assert len(admin_email) == 1
+        assert len(monitoring_email) == 1
         admin_email_text = admin_email[0].kwargs.text
         monitoring_email_text = monitoring_email[0].kwargs.text
-        assert_equal(admin_email_text, monitoring_email_text)
+        assert admin_email_text == monitoring_email_text
 
     def test_bulk_edit_notifications_monitoring_email_public_only(self):
         """Test that private tickets are not included in bulk edit
@@ -1902,9 +1902,9 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.run_ready()
         emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
         # one for admin and one for monitoring email
-        assert_equal(len(emails), 2)
+        assert len(emails) == 2
         for email in emails:
-            assert_equal(email.kwargs.subject, '[test:bugs] Mass edit changes by Test Admin')
+            assert email.kwargs.subject == '[test:bugs] Mass edit changes by Test Admin'
         admin = M.User.by_username('test-admin')
         admin_email = M.MonQTask.query.find({
             'task_name': 'allura.tasks.mail_tasks.sendmail',
@@ -1914,12 +1914,12 @@ class TestFunctionalController(TrackerTestController):
             'task_name': 'allura.tasks.mail_tasks.sendmail',
             'kwargs.destinations': 'monitoring@email.com'
         }).all()
-        assert_equal(len(admin_email), 1)
-        assert_equal(len(monitoring_email), 1)
+        assert len(admin_email) == 1
+        assert len(monitoring_email) == 1
         admin_email_text = admin_email[0].kwargs.text
         monitoring_email_text = monitoring_email[0].kwargs.text
-        assert_in('second ticket', admin_email_text)
-        assert_not_in('second ticket', monitoring_email_text)
+        assert 'second ticket' in admin_email_text
+        assert 'second ticket' not in monitoring_email_text
 
     def test_bulk_edit_monitoring_email_all_private_edits(self):
         """Test that no monitoring email is sent if the "public only"
@@ -1942,9 +1942,9 @@ class TestFunctionalController(TrackerTestController):
             'status': 'accepted'})
         M.MonQTask.run_ready()
         emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
-        assert_equal(len(emails), 1)  # only admin email sent
+        assert len(emails) == 1  # only admin email sent
         for email in emails:
-            assert_equal(email.kwargs.subject, '[test:bugs] Mass edit changes by Test Admin')
+            assert email.kwargs.subject == '[test:bugs] Mass edit changes by Test Admin'
         admin = M.User.by_username('test-admin')
         admin_email = M.MonQTask.query.find({
             'task_name': 'allura.tasks.mail_tasks.sendmail',
@@ -1954,8 +1954,8 @@ class TestFunctionalController(TrackerTestController):
             'task_name': 'allura.tasks.mail_tasks.sendmail',
             'kwargs.destinations': 'monitoring@email.com'
         }).all()
-        assert_equal(len(admin_email), 1)
-        assert_equal(len(monitoring_email), 0)
+        assert len(admin_email) == 1
+        assert len(monitoring_email) == 0
 
     def test_filtered_by_subscription(self):
         self.new_ticket(summary='test first ticket', status='open')
@@ -1991,50 +1991,50 @@ class TestFunctionalController(TrackerTestController):
         }
         filtered_changes = c.app.globals.filtered_by_subscription(changes)
         filtered_users = [uid for uid, data in filtered_changes.items()]
-        assert_equal(sorted(filtered_users),
+        assert (sorted(filtered_users) ==
                      sorted(u._id for u in users[:-1] + [admin]))
         ticket_ids = [t._id for t in tickets]
-        assert_equal(filtered_changes[users[0]._id], set(ticket_ids[0:1]))
-        assert_equal(filtered_changes[users[1]._id], set(ticket_ids[:-1]))
-        assert_equal(filtered_changes[admin._id], set(ticket_ids[:-1]))
+        assert filtered_changes[users[0]._id] == set(ticket_ids[0:1])
+        assert filtered_changes[users[1]._id] == set(ticket_ids[:-1])
+        assert filtered_changes[admin._id] == set(ticket_ids[:-1])
 
     def test_vote(self):
         r = self.new_ticket(summary='test vote').follow()
-        assert_true(r.html.find('div', {'id': 'vote'}))
+        assert r.html.find('div', {'id': 'vote'})
 
         # test vote form not visible to anon user
         r = self.app.get('/bugs/1/', extra_environ=dict(username='*anonymous'))
-        assert_false(r.html.find('div', {'id': 'vote'}))
+        assert not r.html.find('div', {'id': 'vote'})
 
         r = self.app.get('/bugs/1/')
         votes_up = r.html.find('span', {'class': 'votes-up'})
         votes_down = r.html.find('span', {'class': 'votes-down'})
-        assert_in('0', str(votes_up))
-        assert_in('0', str(votes_down))
+        assert '0' in str(votes_up)
+        assert '0' in str(votes_down)
 
         # invalid vote
         r = self.app.post('/bugs/1/vote', dict(vote='invalid'))
         expected_resp = json.dumps(dict(status='error', votes_up=0, votes_down=0, votes_percent=0))
-        assert_equal(r.response.text, expected_resp)
+        assert r.response.text == expected_resp
 
         # vote up
         r = self.app.post('/bugs/1/vote', dict(vote='u'))
         expected_resp = json.dumps(dict(status='ok', votes_up=1, votes_down=0, votes_percent=100))
-        assert_equal(r.response.text, expected_resp)
+        assert r.response.text == expected_resp
 
         # vote down by another user
         r = self.app.post('/bugs/1/vote', dict(vote='d'),
                           extra_environ=dict(username='test-user-0'))
 
         expected_resp = json.dumps(dict(status='ok', votes_up=1, votes_down=1, votes_percent=50))
-        assert_equal(r.response.text, expected_resp)
+        assert r.response.text == expected_resp
 
         # make sure that on the page we see the same result
         r = self.app.get('/bugs/1/')
         votes_up = r.html.find('span', {'class': 'votes-up'})
         votes_down = r.html.find('span', {'class': 'votes-down'})
-        assert_in('1', str(votes_up))
-        assert_in('1', str(votes_down))
+        assert '1' in str(votes_up)
+        assert '1' in str(votes_down)
 
         r = self.app.get('/bugs/')
         assert "Votes" in r
@@ -2075,7 +2075,7 @@ class TestFunctionalController(TrackerTestController):
         ticket_url = r.headers['Location']
         r = self.app.get(ticket_url, extra_environ=dict(username='*anonymous'))
         a = r.html.find('a', {'class': 'icon edit_ticket'})
-        assert_equal(a.text, '\xa0Edit')
+        assert a.text == '\xa0Edit'
 
     def test_ticket_creator_cant_edit_private_ticket_without_update_perm(self):
         p = M.Project.query.get(shortname='test')
@@ -2184,18 +2184,18 @@ class TestFunctionalController(TrackerTestController):
         tracker = p.app_instance('bugs2')
         r = self.app.post('/p/test/bugs/1/move/',
                           params={'tracker': str(tracker.config._id)}).follow()
-        assert_equal(r.request.path, '/p/test2/bugs2/1/')
+        assert r.request.path == '/p/test2/bugs2/1/'
         summary = r.html.findAll('h2', {'class': 'dark title'})[0].find('span').contents[0].strip()
-        assert_equal(summary, '#1 test')
+        assert summary == '#1 test'
         ac_id = tracker.config._id
         ticket = tm.Ticket.query.find({
             'app_config_id': ac_id,
             'ticket_num': 1}).first()
         assert ticket is not None, "Can't find moved ticket"
-        assert_equal(ticket.discussion_thread.app_config_id, ac_id)
-        assert_equal(ticket.discussion_thread.discussion.app_config_id, ac_id)
+        assert ticket.discussion_thread.app_config_id == ac_id
+        assert ticket.discussion_thread.discussion.app_config_id == ac_id
         post = ticket.discussion_thread.last_post
-        assert_equal(post.text, 'Ticket moved from /p/test/bugs/1/')
+        assert post.text == 'Ticket moved from /p/test/bugs/1/'
 
     @td.with_tool('test2', 'Tickets', 'bugs2')
     def test_move_ticket_feed(self):
@@ -2211,7 +2211,7 @@ class TestFunctionalController(TrackerTestController):
         post = ticket.discussion_thread.last_post
         ticket_link = '/p/test2/bugs2/1/?limit=25#' + post.slug
         msg = 'Ticket moved from /p/test/bugs/1/'
-        assert_equal(post.text, msg)
+        assert post.text == msg
         # auto comment content and link to it should be in a ticket's feed
         r = self.app.get('/p/test2/bugs2/1/feed')
         assert msg in r, r
@@ -2236,9 +2236,9 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.post(f['action'], params=params,
                           headers={'Referer': b'/p/test2/bugs2/1/'})
         r = self.app.get('/p/test2/bugs2/1/', dict(page='1'))
-        assert_true(post_content in r)
+        assert post_content in r
         comments_cnt = len(r.html.findAll(attrs={'class': 'discussion-post'}))
-        assert_equal(comments_cnt, 2)  # moved auto comment + new comment
+        assert comments_cnt == 2  # moved auto comment + new comment
         post = ticket.discussion_thread.last_post
         # content and link to the ticket should be in a tracker's feed
         ticket_link = '/p/test2/bugs2/1/?limit=25#' + post.slug
@@ -2284,11 +2284,11 @@ class TestFunctionalController(TrackerTestController):
         dummy_tracker = p.app_instance('dummy')
         r = self.app.post('/p/test/bugs/1/move',
                           params={'tracker': str(dummy_tracker.config._id)}).follow()
-        assert_equal(r.request.path, '/p/test/dummy/1/')
+        assert r.request.path == '/p/test/dummy/1/'
 
         # test that old url redirects to moved ticket
         self.app.get('/p/test/bugs/1/', status=301).follow()
-        assert_equal(r.request.path, '/p/test/dummy/1/')
+        assert r.request.path == '/p/test/dummy/1/'
 
     @td.with_tool('test', 'Tickets', 'dummy')
     def test_move_ticket_and_delete_tool(self):
@@ -2304,7 +2304,7 @@ class TestFunctionalController(TrackerTestController):
         dummy_tracker = p.app_instance('dummy')
         r = self.app.post('/p/test/bugs/1/move',
                           params={'tracker': str(dummy_tracker.config._id)}).follow()
-        assert_equal(r.request.path, '/p/test/dummy/1/')
+        assert r.request.path == '/p/test/dummy/1/'
 
         # delete 'dummy' tracker
         p.uninstall_app('dummy')
@@ -2329,7 +2329,7 @@ class TestFunctionalController(TrackerTestController):
         dummy_tracker = p.app_instance('dummy')
         r = self.app.post('/p/test/bugs/1/move',
                           params={'tracker': str(dummy_tracker.config._id)}).follow()
-        assert_equal(r.request.path, '/p/test/dummy/1/')
+        assert r.request.path == '/p/test/dummy/1/'
 
         # comment ticket 2
         M.Notification.query.remove()
@@ -2346,8 +2346,8 @@ class TestFunctionalController(TrackerTestController):
         # notification for ticket 2 should reference [test:bugs], not
         # [test:dummy]
         n = M.Notification.query.find().all()[0]
-        assert_in('[test:bugs]', n.subject)
-        assert_in('[test:bugs]', n.reply_to_address)
+        assert '[test:bugs]' in n.subject
+        assert '[test:bugs]' in n.reply_to_address
 
     @td.with_tool('test2', 'Tickets', 'features')
     def test_move_ticket_subscriptions(self):
@@ -2380,7 +2380,7 @@ class TestFunctionalController(TrackerTestController):
         # move ticket to new project & tool: test/bugs/2 => test2/features/1
         r = self.app.post('/p/test/bugs/2/move',
                           params={'tracker': str(features.config._id)}).follow()
-        assert_equal(r.request.path, '/p/test2/features/1/')
+        assert r.request.path == '/p/test2/features/1/'
 
         # test-user should be subscribed to it
         assert M.Mailbox.query.get(user_id=user._id,
@@ -2425,16 +2425,16 @@ class TestFunctionalController(TrackerTestController):
         attach_comments = r.html.findAll('div', attrs={'class': 'attachment_item'})
         ta = str(attach_tickets)  # ticket's attachments
         ca = str(attach_comments)  # comment's attachments
-        assert_in('<a href="/p/test2/bugs2/1/attachment/neo-icon-set-454545-256x350.png"', ta)
-        assert_in('<img alt="Thumbnail" src="/p/test2/bugs2/1/attachment/neo-icon-set-454545-256x350.png/thumb"', ta)
+        assert '<a href="/p/test2/bugs2/1/attachment/neo-icon-set-454545-256x350.png"' in ta
+        assert '<img alt="Thumbnail" src="/p/test2/bugs2/1/attachment/neo-icon-set-454545-256x350.png/thumb"' in ta
         p = M.Post.query.find().sort('timestamp', 1).first()
-        assert_in(
+        assert (
             '<a href="/p/test2/bugs2/_discuss/thread/%s/%s/attachment/test.txt"' %
-            (p.thread_id, p.slug), ca)
+            (p.thread_id, p.slug) in ca)
         for attach in M.BaseAttachment.query.find():
-            assert_equal(attach.app_config_id, bugs2.config._id)
+            assert attach.app_config_id == bugs2.config._id
             if attach.attachment_type == 'DiscussionAttachment':
-                assert_equal(attach.discussion_id, bugs2.config.discussion_id)
+                assert attach.discussion_id == bugs2.config.discussion_id
 
     @td.with_tool('test', 'Tickets', 'dummy')
     def test_move_ticket_comments(self):
@@ -2450,14 +2450,14 @@ class TestFunctionalController(TrackerTestController):
         form.fields[field_name][0].value = 'I am comment'
         form.submit()
         r = self.app.get('/p/test/bugs/1/')
-        assert_in('I am comment', r)
+        assert 'I am comment' in r
 
         p = M.Project.query.get(shortname='test')
         dummy_tracker = p.app_instance('dummy')
         r = self.app.post('/p/test/bugs/1/move',
                           params={'tracker': str(dummy_tracker.config._id)}).follow()
-        assert_equal(r.request.path, '/p/test/dummy/1/')
-        assert_in('I am comment', r)
+        assert r.request.path == '/p/test/dummy/1/'
+        assert 'I am comment' in r
 
     def test_tags(self):
         p = M.Project.query.get(shortname='test')
@@ -2468,9 +2468,9 @@ class TestFunctionalController(TrackerTestController):
         # Testing only empty 'term', because mim doesn't support aggregation
         # calls
         r = self.app.get('/p/test/bugs/tags')
-        assert_equal(json.loads(r.text), [])
+        assert json.loads(r.text) == []
         r = self.app.get('/p/test/bugs/tags?term=')
-        assert_equal(json.loads(r.text), [])
+        assert json.loads(r.text) == []
 
     def test_rest_tickets(self):
         ticket_view = self.new_ticket(summary='test').follow()
@@ -2493,12 +2493,12 @@ class TestFunctionalController(TrackerTestController):
         discussion_url = r.html.findAll('form')[-1]['action'][:-4]
         r = self.app.get('/rest/p/test/bugs/1/')
         r = json.loads(r.text)
-        assert_equal(r['ticket']['discussion_thread_url'],
+        assert (r['ticket']['discussion_thread_url'] ==
                      'http://localhost/rest%s' % discussion_url)
         slug = r['ticket']['discussion_thread']['posts'][0]['slug']
-        assert_equal(r['ticket']['discussion_thread']['posts'][0]['attachments'][0]['url'],
+        assert (r['ticket']['discussion_thread']['posts'][0]['attachments'][0]['url'] ==
                      f'http://localhost{discussion_url}{slug}/attachment/test.txt')
-        assert_equal(r['ticket']['discussion_thread']['posts'][0]['attachments'][0]['bytes'],
+        assert (r['ticket']['discussion_thread']['posts'][0]['attachments'][0]['bytes'] ==
                      11)
 
         file_name = 'test_root.py'
@@ -2509,7 +2509,7 @@ class TestFunctionalController(TrackerTestController):
         }, upload_files=[upload]).follow()
         r = self.app.get('/rest/p/test/bugs/1/')
         r = json.loads(r.text)
-        assert_equal(r['ticket']['attachments'][0]['url'],
+        assert (r['ticket']['attachments'][0]['url'] ==
                      'http://localhost/p/test/bugs/1/attachment/test_root.py')
 
     def test_html_escaping(self):
@@ -2521,7 +2521,7 @@ class TestFunctionalController(TrackerTestController):
             ThreadLocalORMSession.flush_all()
             email = M.MonQTask.query.find(
                 dict(task_name='allura.tasks.mail_tasks.sendmail')).first()
-            assert_equal(email.kwargs.subject,
+            assert (email.kwargs.subject ==
                          '[test:bugs] #1 test <h2> ticket')
             text = email.kwargs.text
             assert '** [bugs:#1] test &lt;h2&gt; ticket**' in text
@@ -2532,17 +2532,17 @@ class TestFunctionalController(TrackerTestController):
                 reply_to=g.noreply,
                 subject=email.kwargs.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')
             # check subject
             assert 'Subject: [test:bugs] #1 test <h2> ticket' in body
             # check html, need tags escaped
-            assert_in('<p><strong> <a class="alink" href="http://localhost/p/test/bugs/1/">[bugs:#1]</a>'
-                      ' test &lt;h2&gt; ticket</strong></p>',
+            assert ('<p><strong> <a class="alink" href="http://localhost/p/test/bugs/1/">[bugs:#1]</a>'
+                      ' test &lt;h2&gt; ticket</strong></p>' in
                       body)
             # check plaintext (ok to have "html" tags)
-            assert_in('** [bugs:#1] test <h2> ticket**', body)
+            assert '** [bugs:#1] test <h2> ticket**' in body
 
     @patch('forgetracker.search.query_filter_choices', autospec=True)
     def test_multiselect(self, query_filter_choices):
@@ -2558,16 +2558,16 @@ class TestFunctionalController(TrackerTestController):
         # Set rate limit to unlimit
         with h.push_config(config, **{'forgetracker.rate_limits': '{}'}):
             r = self.app.get('/bugs/new/')
-            assert_equal(r.status_int, 200)
+            assert r.status_int == 200
         # Set rate limit to 1 in first hour of project
         with h.push_config(config, **{'forgetracker.rate_limits': '{"3600": 1}'}):
             r = self.app.get('/bugs/new/')
-            assert_equal(r.status_int, 302)
-            assert_equal(r.location, 'http://localhost/bugs/')
+            assert r.status_int == 302
+            assert r.location == 'http://localhost/bugs/'
             wf = json.loads(self.webflash(r))
-            assert_equal(wf['status'], 'error')
-            assert_equal(
-                wf['message'],
+            assert wf['status'] == 'error'
+            assert (
+                wf['message'] ==
                 'Ticket creation rate limit exceeded. Please try again later.')
 
     def test_rate_limit_save_ticket(self):
@@ -2576,24 +2576,24 @@ class TestFunctionalController(TrackerTestController):
             summary = 'Ticket w/o limit'
             post_data = {'ticket_form.summary': summary}
             r = self.app.post('/bugs/save_ticket', post_data).follow()
-            assert_in(summary, r)
+            assert summary in r
             t = tm.Ticket.query.get(summary=summary)
-            assert_not_equal(t, None)
+            assert t != None
         # Set rate limit to 1 in first hour of project
         with h.push_config(config, **{'forgetracker.rate_limits': '{"3600": 1}'}):
             summary = 'Ticket with limit'
             post_data = {'ticket_form.summary': summary}
             r = self.app.post('/bugs/save_ticket', post_data)
-            assert_equal(r.status_int, 302)
-            assert_equal(r.location, 'http://localhost/bugs/')
+            assert r.status_int == 302
+            assert r.location == 'http://localhost/bugs/'
             wf = json.loads(self.webflash(r))
-            assert_equal(wf['status'], 'error')
-            assert_equal(
-                wf['message'],
+            assert wf['status'] == 'error'
+            assert (
+                wf['message'] ==
                 'Ticket creation rate limit exceeded. Please try again later.')
-            assert_not_in(summary, r.follow())
+            assert summary not in r.follow()
             t = tm.Ticket.query.get(summary=summary)
-            assert_equal(t, None)
+            assert t == None
 
     def test_user_missing(self):
         # add test-user to project so it can be assigned the ticket
@@ -2904,7 +2904,7 @@ class TestCustomUserField(TrackerTestController):
         kw = {'custom_fields._code_review': ''}
         ticket_view = self.new_ticket(summary='test custom fields', **kw).follow()
         # summary header shows 'nobody'
-        assert_equal(squish_spaces(ticket_view.html.findAll('label', 'simple', text='Code Review:')[0].parent.text),
+        assert (squish_spaces(ticket_view.html.findAll('label', 'simple', text='Code Review:')[0].parent.text) ==
                      ' Code Review: nobody ')
         # form input is blank
         select = ticket_view.html.find('select',
@@ -2919,7 +2919,7 @@ class TestCustomUserField(TrackerTestController):
         kw = {'custom_fields._code_review': 'test-admin'}
         ticket_view = self.new_ticket(summary='test custom fields', **kw).follow()
         # summary header shows 'Test Admin'
-        assert_equal(squish_spaces(ticket_view.html.findAll('label', 'simple', text='Code Review:')[0].parent.text),
+        assert (squish_spaces(ticket_view.html.findAll('label', 'simple', text='Code Review:')[0].parent.text) ==
                      ' Code Review: Test Admin ')
         # form input is blank
         select = ticket_view.html.find('select',
@@ -2928,7 +2928,7 @@ class TestCustomUserField(TrackerTestController):
         for option in select.findChildren():
             if option.has_attr('selected'):
                 selected = option
-        assert_equal(selected['value'], 'test-admin')
+        assert selected['value'] == 'test-admin'
 
     def test_change_user_field(self):
         kw = {'custom_fields._code_review': ''}
@@ -2942,8 +2942,8 @@ class TestCustomUserField(TrackerTestController):
         kw = {'custom_fields._code_review': 'test-admin'}
         self.new_ticket(summary='test custom fields', **kw)
         r = self.app.get('/bugs/')
-        assert_equal(r.html.find('table', 'ticket-list').findAll('th')[7].text.strip()[:11], 'Code Review')
-        assert_equal(r.html.find('table', 'ticket-list').tbody.tr.findAll('td')[7].text, 'Test Admin')
+        assert r.html.find('table', 'ticket-list').findAll('th')[7].text.strip()[:11] == 'Code Review'
+        assert r.html.find('table', 'ticket-list').tbody.tr.findAll('td')[7].text == 'Test Admin'
 
 
 class TestHelpTextOptions(TrackerTestController):
@@ -3041,9 +3041,9 @@ class TestBulkMove(TrackerTestController):
         r = self.app.get('/bugs/move/?q=The')
         tickets_table = r.html.find('tbody', attrs={'class': 'ticket-list'})
         tickets = tickets_table.findAll('tr')
-        assert_equal(len(tickets), 2)
-        assert_in('The Empire Strikes Back', tickets_table.text)
-        assert_in('Return Of The Jedi', tickets_table.text)
+        assert len(tickets) == 2
+        assert 'The Empire Strikes Back' in tickets_table.text
+        assert 'Return Of The Jedi' in tickets_table.text
 
     @td.with_tool('test', 'Tickets', 'bugs2')
     @td.with_tool('test2', 'Tickets', 'bugs')
@@ -3053,7 +3053,7 @@ class TestBulkMove(TrackerTestController):
         trackers = r.html.find('select', {'name': 'tracker'}).findAll('option')
         trackers = {t.text for t in trackers}
         expected = {'test/bugs', 'test/bugs2', 'test2/bugs', 'test2/bugs2'}
-        assert_equal(trackers, expected)
+        assert trackers == expected
         move_btn = r.html.find('input', attrs={'type': 'submit', 'value': 'Move'})
         assert move_btn is not None
 
@@ -3078,16 +3078,16 @@ class TestBulkMove(TrackerTestController):
         original_ac_id = original_tracker.config._id
         moved_tickets = tm.Ticket.query.find({'app_config_id': ac_id}).all()
         original_tickets = tm.Ticket.query.find({'app_config_id': original_ac_id}).all()
-        assert_equal(len(moved_tickets), 2)
-        assert_equal(len(original_tickets), 1)
+        assert len(moved_tickets) == 2
+        assert len(original_tickets) == 1
         for ticket in moved_tickets:
-            assert_equal(ticket.discussion_thread.app_config_id, ac_id)
-            assert_equal(ticket.discussion_thread.discussion.app_config_id, ac_id)
+            assert ticket.discussion_thread.app_config_id == ac_id
+            assert ticket.discussion_thread.discussion.app_config_id == ac_id
             post = ticket.discussion_thread.last_post
-            assert_in('Ticket moved from /p/test/bugs/', post.text)
+            assert 'Ticket moved from /p/test/bugs/' in post.text
         for t in original_tickets:
-            assert_equal(t.discussion_thread.app_config_id, original_ac_id)
-            assert_equal(t.discussion_thread.discussion.app_config_id, original_ac_id)
+            assert t.discussion_thread.app_config_id == original_ac_id
+            assert t.discussion_thread.discussion.app_config_id == original_ac_id
             assert t.discussion_thread.last_post is None
 
     @td.with_tool('test2', 'Tickets', 'bugs2')
@@ -3112,46 +3112,46 @@ class TestBulkMove(TrackerTestController):
         })
         M.MonQTask.run_ready()
         emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
-        assert_equal(len(emails), 3)
+        assert len(emails) == 3
         for email in emails:
-            assert_equal(email.kwargs.subject,
+            assert (email.kwargs.subject ==
                          '[test:bugs] Mass ticket moving by Test Admin')
         first_user_email = M.MonQTask.query.find({
             'task_name': 'allura.tasks.mail_tasks.sendmail',
             'kwargs.destinations': str(first_user._id)
         }).all()
-        assert_equal(len(first_user_email), 1)
+        assert len(first_user_email) == 1
         first_user_email = first_user_email[0]
         second_user_email = M.MonQTask.query.find({
             'task_name': 'allura.tasks.mail_tasks.sendmail',
             'kwargs.destinations': str(second_user._id)
         }).all()
-        assert_equal(len(second_user_email), 1)
+        assert len(second_user_email) == 1
         second_user_email = second_user_email[0]
         admin_email = M.MonQTask.query.find({
             'task_name': 'allura.tasks.mail_tasks.sendmail',
             'kwargs.destinations': str(admin._id)
         }).all()
-        assert_equal(len(admin_email), 1)
+        assert len(admin_email) == 1
         admin_email = admin_email[0]
 
         email_header = 'Tickets were moved from [test:bugs] to [test2:bugs2]\n'
         first_ticket_changes = 'A New Hope'
         second_ticket_changes = 'The Empire Strikes Back'
         third_ticket_changes = 'Return Of The Jedi'
-        assert_in(email_header, first_user_email.kwargs.text)
-        assert_in(first_ticket_changes, first_user_email.kwargs.text)
-        assert_in(email_header, second_user_email.kwargs.text)
-        assert_in(second_ticket_changes, second_user_email.kwargs.text)
-        assert_in(email_header, admin_email.kwargs.text)
-        assert_in(first_ticket_changes, admin_email.kwargs.text)
-        assert_in(second_ticket_changes, admin_email.kwargs.text)
-        assert_in(third_ticket_changes, admin_email.kwargs.text)
+        assert email_header in first_user_email.kwargs.text
+        assert first_ticket_changes in first_user_email.kwargs.text
+        assert email_header in second_user_email.kwargs.text
+        assert second_ticket_changes in second_user_email.kwargs.text
+        assert email_header in admin_email.kwargs.text
+        assert first_ticket_changes in admin_email.kwargs.text
+        assert second_ticket_changes in admin_email.kwargs.text
+        assert third_ticket_changes in admin_email.kwargs.text
         # After tickets moved, user should see a flash
         mbox = M.Mailbox.query.get(user_id=admin._id, is_flash=True)
         notification_id = mbox.queue[-1]
         notification = M.Notification.query.get(_id=notification_id)
-        assert_equal(notification.text,
+        assert (notification.text ==
                      'Tickets moved from test/bugs to test2/bugs2')
 
     @td.with_tool('test2', 'Tickets', 'bugs2')
@@ -3175,9 +3175,9 @@ class TestBulkMove(TrackerTestController):
         })
         M.MonQTask.run_ready()
         emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
-        assert_equal(len(emails), 2)
+        assert len(emails) == 2
         for email in emails:
-            assert_equal(email.kwargs.subject,
+            assert (email.kwargs.subject ==
                          '[test:bugs] Mass ticket moving by Test Admin')
         admin_email = M.MonQTask.query.find({
             'task_name': 'allura.tasks.mail_tasks.sendmail',
@@ -3187,21 +3187,21 @@ class TestBulkMove(TrackerTestController):
             'task_name': 'allura.tasks.mail_tasks.sendmail',
             'kwargs.destinations': 'monitoring@email.com'
         }).all()
-        assert_equal(len(admin_email), 1)
-        assert_equal(len(monitoring_email), 1)
+        assert len(admin_email) == 1
+        assert len(monitoring_email) == 1
         admin_email_text = admin_email[0].kwargs.text
-        assert_in('test:bugs:#1 --> test2:bugs2:#1 A New Hope',
+        assert ('test:bugs:#1 --> test2:bugs2:#1 A New Hope' in
                   admin_email_text)
-        assert_in('test:bugs:#2 --> test2:bugs2:#2 The Empire Strikes Back',
+        assert ('test:bugs:#2 --> test2:bugs2:#2 The Empire Strikes Back' in
                   admin_email_text)
-        assert_in('test:bugs:#3 --> test2:bugs2:#3 Return Of The Jedi',
+        assert ('test:bugs:#3 --> test2:bugs2:#3 Return Of The Jedi' in
                   admin_email_text)
         monitoring_email_text = monitoring_email[0].kwargs.text
-        assert_in('test:bugs:#1 --> test2:bugs2:#1 A New Hope',
+        assert ('test:bugs:#1 --> test2:bugs2:#1 A New Hope' in
                   monitoring_email_text)
-        assert_in('test:bugs:#2 --> test2:bugs2:#2 The Empire Strikes Back',
+        assert ('test:bugs:#2 --> test2:bugs2:#2 The Empire Strikes Back' in
                   monitoring_email_text)
-        assert_in('test:bugs:#3 --> test2:bugs2:#3 Return Of The Jedi',
+        assert ('test:bugs:#3 --> test2:bugs2:#3 Return Of The Jedi' in
                   monitoring_email_text)
 
     @td.with_tool('test2', 'Tickets', 'bugs2')
@@ -3232,9 +3232,9 @@ class TestBulkMove(TrackerTestController):
         M.MonQTask.run_ready()
         emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
         # one for admin and one for monitoring email
-        assert_equal(len(emails), 2)
+        assert len(emails) == 2
         for email in emails:
-            assert_equal(email.kwargs.subject,
+            assert (email.kwargs.subject ==
                          '[test:bugs] Mass ticket moving by Test Admin')
         admin = M.User.by_username('test-admin')
         admin_email = M.MonQTask.query.find({
@@ -3245,12 +3245,12 @@ class TestBulkMove(TrackerTestController):
             'task_name': 'allura.tasks.mail_tasks.sendmail',
             'kwargs.destinations': 'monitoring@email.com'
         }).all()
-        assert_equal(len(admin_email), 1)
-        assert_equal(len(monitoring_email), 1)
+        assert len(admin_email) == 1
+        assert len(monitoring_email) == 1
         admin_email_text = admin_email[0].kwargs.text
         monitoring_email_text = monitoring_email[0].kwargs.text
-        assert_in('second ticket', admin_email_text)
-        assert_not_in('second ticket', monitoring_email_text)
+        assert 'second ticket' in admin_email_text
+        assert 'second ticket' not in monitoring_email_text
 
     @td.with_tool('test2', 'Tickets', 'bugs2')
     def test_monitoring_email_all_private_moved(self):
@@ -3279,9 +3279,9 @@ class TestBulkMove(TrackerTestController):
         })
         M.MonQTask.run_ready()
         emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
-        assert_equal(len(emails), 1)  # only admin email sent
+        assert len(emails) == 1  # only admin email sent
         for email in emails:
-            assert_equal(email.kwargs.subject,
+            assert (email.kwargs.subject ==
                          '[test:bugs] Mass ticket moving by Test Admin')
         admin = M.User.by_username('test-admin')
         admin_email = M.MonQTask.query.find({
@@ -3292,8 +3292,8 @@ class TestBulkMove(TrackerTestController):
             'task_name': 'allura.tasks.mail_tasks.sendmail',
             'kwargs.destinations': 'monitoring@email.com'
         }).all()
-        assert_equal(len(admin_email), 1)
-        assert_equal(len(monitoring_email), 0)
+        assert len(admin_email) == 1
+        assert len(monitoring_email) == 0
 
 
 def sidebar_contains(response, text):
@@ -3304,7 +3304,7 @@ def sidebar_contains(response, text):
 class TestStats(TrackerTestController):
     def test_stats(self):
         r = self.app.get('/bugs/stats/', status=200)
-        assert_in('# tickets: 0', r.text)
+        assert '# tickets: 0' in r.text
 
 
 class TestNotificationEmailGrouping(TrackerTestController):
@@ -3315,9 +3315,9 @@ class TestNotificationEmailGrouping(TrackerTestController):
         ThreadLocalORMSession.flush_all()
         email = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).first()
         ticket = tm.Ticket.query.get(ticket_num=1)
-        assert_equal(email.kwargs.message_id, ticket.message_id())
-        assert_equal(email.kwargs.in_reply_to, None)
-        assert_equal(email.kwargs.references, [])
+        assert email.kwargs.message_id == ticket.message_id()
+        assert email.kwargs.in_reply_to == None
+        assert email.kwargs.references == []
 
     def test_comments(self):
         ticket_view = self.new_ticket(summary='Test Ticket').follow()
@@ -3336,9 +3336,9 @@ class TestNotificationEmailGrouping(TrackerTestController):
         ticket = tm.Ticket.query.get(ticket_num=1)
         top_level_comment = ticket.discussion_thread.posts[0]
         top_level_comment_msg_id = ticket.url() + top_level_comment._id
-        assert_equal(email.kwargs.message_id, top_level_comment_msg_id)
-        assert_equal(email.kwargs.in_reply_to, ticket.message_id())
-        assert_equal(email.kwargs.references, [ticket.message_id()])
+        assert email.kwargs.message_id == top_level_comment_msg_id
+        assert email.kwargs.in_reply_to == ticket.message_id()
+        assert email.kwargs.references == [ticket.message_id()]
 
         ThreadLocalORMSession.flush_all()
         M.MonQTask.query.remove()
@@ -3356,9 +3356,9 @@ class TestNotificationEmailGrouping(TrackerTestController):
         email = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).first()
         ticket = tm.Ticket.query.get(ticket_num=1)
         reply = [post for post in ticket.discussion_thread.posts if post.text == reply_text][0]
-        assert_equal(email.kwargs.message_id, ticket.url() + reply._id)
-        assert_equal(email.kwargs.in_reply_to, top_level_comment_msg_id)
-        assert_equal(email.kwargs.references,
+        assert email.kwargs.message_id == ticket.url() + reply._id
+        assert email.kwargs.in_reply_to == top_level_comment_msg_id
+        assert (email.kwargs.references ==
                      [ticket.message_id(), top_level_comment_msg_id])
 
 
@@ -3370,10 +3370,10 @@ def test_status_passthru():
                           open_status_names='foo bar', closed_status_names='qux baz')
     ThreadLocalORMSession.flush_all()
     app = c.project.app_instance('tsp')
-    assert_equal(app.globals.set_of_open_status_names, {'foo', 'bar'})
-    assert_equal(app.globals.set_of_closed_status_names, {'qux', 'baz'})
-    assert_not_in('open_status_names', app.config.options)
-    assert_not_in('closed_status_names', app.config.options)
+    assert app.globals.set_of_open_status_names == {'foo', 'bar'}
+    assert app.globals.set_of_closed_status_names == {'qux', 'baz'}
+    assert 'open_status_names' not in app.config.options
+    assert 'closed_status_names' not in app.config.options
 
 
 class TestArtifactLinks(TrackerTestController):
@@ -3393,15 +3393,15 @@ class TestArtifactLinks(TrackerTestController):
         self.new_ticket('/features/', summary='Ticket 1 in features', _milestone='1.0').follow()
         ticket_bugs = tm.Ticket.query.get(summary='Ticket 1 in bugs')
         ticket_features = tm.Ticket.query.get(summary='Ticket 1 in features')
-        assert_equal(ticket_bugs.ticket_num, 1)
-        assert_equal(ticket_bugs.app.config._id, bugs.config._id)
-        assert_equal(ticket_features.ticket_num, 1)
-        assert_equal(ticket_features.app.config._id, features.config._id)
+        assert ticket_bugs.ticket_num == 1
+        assert ticket_bugs.app.config._id == bugs.config._id
+        assert ticket_features.ticket_num == 1
+        assert ticket_features.app.config._id == features.config._id
 
         c.app = bugs
         link = '<div class="markdown_content"><p><a class="alink" href="/p/test/bugs/1/">[#1]</a></p></div>'
-        assert_equal(g.markdown.convert('[#1]'), link)
+        assert g.markdown.convert('[#1]') == link
 
         c.app = features
         link = '<div class="markdown_content"><p><a class="alink" href="/p/test/features/1/">[#1]</a></p></div>'
-        assert_equal(g.markdown.convert('[#1]'), link)
+        assert g.markdown.convert('[#1]') == link
diff --git a/ForgeTracker/forgetracker/tests/test_app.py b/ForgeTracker/forgetracker/tests/test_app.py
index b67bdb969..aed0fb60b 100644
--- a/ForgeTracker/forgetracker/tests/test_app.py
+++ b/ForgeTracker/forgetracker/tests/test_app.py
@@ -33,7 +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
+from alluratest.pytest_helpers import with_nose_compatibility
 
 
 class TestApp:
@@ -54,7 +54,7 @@ class TestApp:
         c.app.handle_message('1', msg)
         # message gets added as a post on the ticket
         post = M.Post.query.get(_id=message_id)
-        assert_equal(post["text"], message)
+        assert post["text"] == message
 
     @td.with_tracker
     def test_inbound_email_no_match(self):
@@ -66,7 +66,7 @@ class TestApp:
         c.app.handle_message('6789', msg)
         # no new message
         post = M.Post.query.get(_id=message_id)
-        assert_equal(post, None)
+        assert post == None
 
     @td.with_tracker
     def test_uninstall(self):
@@ -83,22 +83,22 @@ class TestApp:
         # invoked normally via entry point
         TM.Ticket.new()
         TM.Ticket.new()
-        assert_equal(2, tickets_stats_24hr())
+        assert 2 == tickets_stats_24hr()
 
     @td.with_tracker
     def test_sitemap_xml(self):
-        assert_equal([], c.app.sitemap_xml())
+        assert [] == c.app.sitemap_xml()
         TM.Ticket.new()
-        assert_equal(1, len(c.app.sitemap_xml()))
+        assert 1 == len(c.app.sitemap_xml())
 
     @td.with_tracker
     def test_sitemap_xml_ignored(self):
         TM.Ticket.new(form_fields=dict(deleted=True))
-        assert_equal([], c.app.sitemap_xml())
+        assert [] == c.app.sitemap_xml()
         # still add to sitemap even if only tickets are closed
         TM.Ticket.new(form_fields=dict(
             status=c.app.globals.closed_status_names[0]))
-        assert_equal(1, len(c.app.sitemap_xml()))
+        assert 1 == len(c.app.sitemap_xml())
 
 
 class TestBulkExport(TrackerTestController):
@@ -132,26 +132,26 @@ class TestBulkExport(TrackerTestController):
 
         tickets = sorted(tracker['tickets'],
                          key=operator.itemgetter('summary'))
-        assert_equal(len(tickets), 2)
+        assert len(tickets) == 2
         ticket_foo = tickets[1]
-        assert_equal(ticket_foo['summary'], 'foo')
-        assert_equal(ticket_foo['custom_fields']['_milestone'], '1.0')
+        assert ticket_foo['summary'] == 'foo'
+        assert ticket_foo['custom_fields']['_milestone'] == '1.0'
         posts_foo = ticket_foo['discussion_thread']['posts']
-        assert_equal(len(posts_foo), 1)
-        assert_equal(posts_foo[0]['text'], 'silly comment')
+        assert len(posts_foo) == 1
+        assert posts_foo[0]['text'] == 'silly comment'
 
         tracker_config = tracker['tracker_config']
-        assert_true('options' in list(tracker_config.keys()))
-        assert_equal(tracker_config['options']['mount_point'], 'bugs')
+        assert 'options' in list(tracker_config.keys())
+        assert tracker_config['options']['mount_point'] == 'bugs'
 
         milestones = sorted(tracker['milestones'],
                             key=operator.itemgetter('name'))
-        assert_equal(milestones[0]['name'], '1.0')
-        assert_equal(milestones[1]['name'], '2.0')
+        assert milestones[0]['name'] == '1.0'
+        assert milestones[1]['name'] == '2.0'
 
         saved_bins_summaries = [bin['summary']
                                 for bin in tracker['saved_bins']]
-        assert_true('Closed Tickets' in saved_bins_summaries)
+        assert 'Closed Tickets' in saved_bins_summaries
 
     def test_export_with_attachments(self):
 
@@ -169,5 +169,5 @@ class TestBulkExport(TrackerTestController):
             self.post.slug,
             'test_file'
         )
-        assert_equal(tickets[1]['discussion_thread']['posts'][0]['attachments'][0]['path'], file_path)
+        assert tickets[1]['discussion_thread']['posts'][0]['attachments'][0]['path'] == file_path
         os.path.exists(file_path)
diff --git a/ForgeTracker/forgetracker/tests/unit/test_globals_model.py b/ForgeTracker/forgetracker/tests/unit/test_globals_model.py
index 439e8a5ac..6c063c8f8 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_globals_model.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_globals_model.py
@@ -43,14 +43,14 @@ class TestGlobalsModel(TrackerTestWithModel):
 
     def test_next_ticket_number_increments(self):
         gl = Globals()
-        assert_equal(gl.next_ticket_num(), 1)
-        assert_equal(gl.next_ticket_num(), 2)
+        assert gl.next_ticket_num() == 1
+        assert gl.next_ticket_num() == 2
 
     def test_ticket_numbers_are_independent(self):
         with h.push_context('test', 'doc-bugs', neighborhood='Projects'):
-            assert_equal(c.app.globals.next_ticket_num(), 1)
+            assert c.app.globals.next_ticket_num() == 1
         with h.push_context('test', 'bugs', neighborhood='Projects'):
-            assert_equal(c.app.globals.next_ticket_num(), 1)
+            assert c.app.globals.next_ticket_num() == 1
 
     @mock.patch('forgetracker.model.ticket.datetime')
     def test_bin_count(self, mock_dt):
@@ -65,14 +65,14 @@ class TestGlobalsModel(TrackerTestWithModel):
         gbl.invalidate_bin_counts.reset_mock()
         gbl._bin_counts_expire = now + timedelta(minutes=5)
         bin = gbl.bin_count('bar')
-        assert_equal(bin['hits'], 2)
+        assert bin['hits'] == 2
         assert not gbl.invalidate_bin_counts.called
 
         # expired, returns value for missing bin
         gbl.invalidate_bin_counts.reset_mock()
         gbl._bin_counts_expire = now - timedelta(minutes=5)
         bin = gbl.bin_count('qux')
-        assert_equal(bin['hits'], 0)
+        assert bin['hits'] == 0
         assert gbl.invalidate_bin_counts.called
 
         # config set to no expiration
@@ -86,7 +86,7 @@ class TestGlobalsModel(TrackerTestWithModel):
         gbl.invalidate_bin_counts.reset_mock()
         gbl._bin_counts_expire = None
         bin = gbl.bin_count('qux')
-        assert_equal(bin['hits'], 0)
+        assert bin['hits'] == 0
         assert gbl.invalidate_bin_counts.called
 
     @mock.patch('forgetracker.tasks.update_bin_counts')
@@ -105,14 +105,14 @@ class TestGlobalsModel(TrackerTestWithModel):
         gbl._bin_counts_invalidated = now - timedelta(minutes=6)
         gbl.invalidate_bin_counts()
         assert mock_task.post.called
-        assert_equal(gbl._bin_counts_invalidated, now)
+        assert gbl._bin_counts_invalidated == now
 
         # never invalidated
         mock_task.reset_mock()
         gbl._bin_counts_invalidated = None
         gbl.invalidate_bin_counts()
         assert mock_task.post.called
-        assert_equal(gbl._bin_counts_invalidated, now)
+        assert gbl._bin_counts_invalidated == now
 
     @mock.patch('forgetracker.model.ticket.Bin')
     @mock.patch('forgetracker.model.ticket.search_artifact')
@@ -126,24 +126,24 @@ class TestGlobalsModel(TrackerTestWithModel):
             mock.Mock(summary='foo', terms='bar')]
         mock_search().hits = 5
 
-        assert_equal(gbl._bin_counts_data, [])  # sanity pre-check
+        assert gbl._bin_counts_data == []  # sanity pre-check
         gbl.update_bin_counts()
         assert mock_bin.query.find.called
         mock_search.assert_called_with(
             forgetracker.model.Ticket, 'bar', rows=0, short_timeout=False, fq=['-deleted_b:true'])
-        assert_equal(gbl._bin_counts_data, [{'summary': 'foo', 'hits': 5}])
-        assert_equal(gbl._bin_counts_expire, now + timedelta(minutes=60))
-        assert_equal(gbl._bin_counts_invalidated, None)
+        assert gbl._bin_counts_data == [{'summary': 'foo', 'hits': 5}]
+        assert gbl._bin_counts_expire == now + timedelta(minutes=60)
+        assert gbl._bin_counts_invalidated == None
 
     def test_append_new_labels(self):
         gbl = Globals()
-        assert_equal(gbl.append_new_labels([], ['tag1']), ['tag1'])
-        assert_equal(
-            gbl.append_new_labels(['tag1', 'tag2'], ['tag2']), ['tag1', 'tag2'])
-        assert_equal(gbl.append_new_labels(
-            ['tag1', 'tag2'], ['tag3']), ['tag1', 'tag2', 'tag3'])
-        assert_equal(gbl.append_new_labels(
-            ['tag1', 'tag2', 'tag3'], ['tag2']), ['tag1', 'tag2', 'tag3'])
+        assert gbl.append_new_labels([], ['tag1']) == ['tag1']
+        assert (
+            gbl.append_new_labels(['tag1', 'tag2'], ['tag2']) == ['tag1', 'tag2'])
+        assert gbl.append_new_labels(
+            ['tag1', 'tag2'], ['tag3']) == ['tag1', 'tag2', 'tag3']
+        assert gbl.append_new_labels(
+            ['tag1', 'tag2', 'tag3'], ['tag2']) == ['tag1', 'tag2', 'tag3']
 
 
 class TestCustomFields(TrackerTestWithModel):
diff --git a/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py b/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
index f22f13eee..3bc0edd50 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
@@ -41,4 +41,4 @@ def test_unicode_lookup():
         mc = MilestoneController(root, field, milestone_urlparam)
 
     assert mc.milestone  # check that it is found
-    assert_equal(mc.milestone.name, 'Перспектива')
+    assert mc.milestone.name == 'Перспектива'
diff --git a/ForgeTracker/forgetracker/tests/unit/test_root_controller.py b/ForgeTracker/forgetracker/tests/unit/test_root_controller.py
index ac65449a1..8051cde32 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_root_controller.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_root_controller.py
@@ -49,7 +49,7 @@ class TestWhenSearchingWithCustomFields(WithUserAndBugsApp):
         expected = [dict(sortable_name='_iteration_number_s',
                          name='_iteration_number',
                          label='Iteration Number')]
-        assert_equal(self.response['sortable_custom_fields'], expected)
+        assert self.response['sortable_custom_fields'] == expected
 
     def test_that_tickets_are_listed(self):
         assert self.response['tickets'][0].summary == 'colors are wrong'
diff --git a/ForgeTracker/forgetracker/tests/unit/test_search.py b/ForgeTracker/forgetracker/tests/unit/test_search.py
index 9f34a03ad..c9bb4bdbf 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_search.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_search.py
@@ -35,7 +35,7 @@ def hit_mock():
 
 def test_get_facets():
     hit,expected = hit_mock()
-    assert_equal(get_facets(hit), expected)
+    assert get_facets(hit) == expected
 
 
 @mock.patch('forgetracker.search.search')
@@ -56,4 +56,4 @@ def test_query_filter_choices(c, search):
               'facet.sort': 'index',
               'facet.mincount': 1}
     search.assert_called_once_with(None, **params)
-    assert_equal(result, expected)
+    assert result == expected
diff --git a/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py b/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py
index c958094db..02744756b 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py
@@ -91,27 +91,27 @@ class TestTicketModel(TrackerTestWithModel):
 
     def test_activity_extras(self):
         t = Ticket(summary='my ticket', ticket_num=12)
-        assert_in('allura_id', t.activity_extras)
-        assert_equal(t.activity_extras['summary'], t.summary)
+        assert 'allura_id' in t.activity_extras
+        assert t.activity_extras['summary'] == t.summary
 
     def test_has_activity_access(self):
         t = Ticket(summary='ticket', ticket_num=666)
-        assert_true(t.has_activity_access('read', c.user, 'activity'))
+        assert t.has_activity_access('read', c.user, 'activity')
         t.deleted = True
-        assert_false(t.has_activity_access('read', c.user, 'activity'))
+        assert not t.has_activity_access('read', c.user, 'activity')
 
     def test_comment_has_activity_access(self):
         t = Ticket(summary='ticket', ticket_num=666, deleted=True)
         p = t.discussion_thread.add_post(text='test post')
-        assert_equal(p.status, 'ok')
-        assert_true(p.has_activity_access('read', c.user, 'activity'))
+        assert p.status == 'ok'
+        assert p.has_activity_access('read', c.user, 'activity')
         p.status = 'spam'
-        assert_false(p.has_activity_access('read', c.user, 'activity'))
+        assert not p.has_activity_access('read', c.user, 'activity')
         p.status = 'pending'
-        assert_false(p.has_activity_access('read', c.user, 'activity'))
+        assert not p.has_activity_access('read', c.user, 'activity')
         p.status = 'ok'
         p.deleted = True
-        assert_false(p.has_activity_access('read', c.user, 'activity'))
+        assert not p.has_activity_access('read', c.user, 'activity')
 
     def test_private_ticket(self):
         from allura.model import ProjectRole
@@ -138,7 +138,7 @@ class TestTicketModel(TrackerTestWithModel):
         cred = Credentials.get().clear()
 
         t.private = True
-        assert_equal(t.acl, [
+        assert t.acl == [
             ACE.allow(role_developer, 'create'),
             ACE.allow(role_developer, 'delete'),
             ACE.allow(role_developer, 'moderate'),
@@ -151,7 +151,7 @@ class TestTicketModel(TrackerTestWithModel):
             ACE.allow(role_creator, 'post'),
             ACE.allow(role_creator, 'read'),
             ACE.allow(role_creator, 'unmoderated_post'),
-            DENY_ALL])
+            DENY_ALL]
         assert has_access(t, 'read', user=admin)()
         assert has_access(t, 'create', user=admin)()
         assert has_access(t, 'update', user=admin)()
@@ -192,15 +192,15 @@ class TestTicketModel(TrackerTestWithModel):
             summary='test ticket',
             description='test description',
             created_date=datetime(2012, 10, 29, 9, 57, 21, 465000))
-        assert_equal(t.created_date, datetime(2012, 10, 29, 9, 57, 21, 465000))
+        assert t.created_date == datetime(2012, 10, 29, 9, 57, 21, 465000)
         f = Feed.post(
             t,
             title=t.summary,
             description=t.description,
             pubdate=t.created_date)
-        assert_equal(f.pubdate, datetime(2012, 10, 29, 9, 57, 21, 465000))
-        assert_equal(f.title, 'test ticket')
-        assert_equal(f.description,
+        assert f.pubdate == datetime(2012, 10, 29, 9, 57, 21, 465000)
+        assert f.title == 'test ticket'
+        assert (f.description ==
                      '<div class="markdown_content"><p>test description</p></div>')
 
     @td.with_tool('test', 'Tickets', 'bugs', username='test-user')
@@ -215,33 +215,33 @@ class TestTicketModel(TrackerTestWithModel):
             ticket.assigned_to_id = User.by_username('test-user')._id
             ticket.discussion_thread.add_post(text='test comment')
 
-        assert_equal(
-            Ticket.query.find({'app_config_id': app1.config._id}).count(), 1)
-        assert_equal(
-            Ticket.query.find({'app_config_id': app2.config._id}).count(), 0)
-        assert_equal(
-            Post.query.find(dict(thread_id=ticket.discussion_thread._id)).count(), 1)
+        assert (
+            Ticket.query.find({'app_config_id': app1.config._id}).count() == 1)
+        assert (
+            Ticket.query.find({'app_config_id': app2.config._id}).count() == 0)
+        assert (
+            Post.query.find(dict(thread_id=ticket.discussion_thread._id)).count() == 1)
 
         t = ticket.move(app2.config)
-        assert_equal(
-            Ticket.query.find({'app_config_id': app1.config._id}).count(), 0)
-        assert_equal(
-            Ticket.query.find({'app_config_id': app2.config._id}).count(), 1)
-        assert_equal(t.summary, 'test ticket')
-        assert_equal(t.description, 'test description')
-        assert_equal(t.assigned_to.username, 'test-user')
-        assert_equal(t.url(), '/p/test/bugs2/1/')
+        assert (
+            Ticket.query.find({'app_config_id': app1.config._id}).count() == 0)
+        assert (
+            Ticket.query.find({'app_config_id': app2.config._id}).count() == 1)
+        assert t.summary == 'test ticket'
+        assert t.description == 'test description'
+        assert t.assigned_to.username == 'test-user'
+        assert t.url() == '/p/test/bugs2/1/'
 
         post = Post.query.find(dict(thread_id=ticket.discussion_thread._id,
                                     text={'$ne': 'test comment'})).first()
         assert post is not None, 'No comment about ticket moving'
         message = 'Ticket moved from /p/test/bugs/1/'
-        assert_equal(post.text, message)
+        assert post.text == message
 
         post = Post.query.find(dict(text='test comment')).first()
-        assert_equal(post.thread.discussion_id, app2.config.discussion_id)
-        assert_equal(post.thread.app_config_id, app2.config._id)
-        assert_equal(post.app_config_id, app2.config._id)
+        assert post.thread.discussion_id == app2.config.discussion_id
+        assert post.thread.app_config_id == app2.config._id
+        assert post.app_config_id == app2.config._id
 
     @td.with_tool('test', 'Tickets', 'bugs', username='test-user')
     @td.with_tool('test', 'Tickets', 'bugs2', username='test-user')
@@ -263,16 +263,16 @@ class TestTicketModel(TrackerTestWithModel):
             ticket.custom_fields['_test2'] = 'test val 2'
 
         t = ticket.move(app2.config)
-        assert_equal(t.summary, 'test ticket')
-        assert_equal(t.description, 'test description')
-        assert_equal(t.custom_fields['_test'], 'test val')
+        assert t.summary == 'test ticket'
+        assert t.description == 'test description'
+        assert t.custom_fields['_test'] == 'test val'
         post = Post.query.find(
             dict(thread_id=ticket.discussion_thread._id)).first()
         assert post is not None, 'No comment about ticket moving'
         message = 'Ticket moved from /p/test/bugs/1/'
         message += '\n\nCan\'t be converted:\n'
         message += '\n- **_test2**: test val 2'
-        assert_equal(post.text, message)
+        assert post.text == message
 
     @td.with_tool('test', 'Tickets', 'bugs', username='test-user')
     @td.with_tool('test', 'Tickets', 'bugs2', username='test-user')
@@ -300,9 +300,9 @@ class TestTicketModel(TrackerTestWithModel):
             ticket.assigned_to_id = User.by_username('test-user-0')._id
 
         t = ticket.move(app2.config)
-        assert_equal(t.assigned_to_id, None)
-        assert_equal(t.custom_fields['_user_field'], 'test-user')
-        assert_equal(t.custom_fields['_user_field_2'], '')
+        assert t.assigned_to_id == None
+        assert t.custom_fields['_user_field'] == 'test-user'
+        assert t.custom_fields['_user_field_2'] == ''
         post = Post.query.find(
             dict(thread_id=ticket.discussion_thread._id)).first()
         assert post is not None, 'No comment about ticket moving'
@@ -310,7 +310,7 @@ class TestTicketModel(TrackerTestWithModel):
         message += '\n\nCan\'t be converted:\n'
         message += '\n- **_user_field_2**: test-user-0 (user not in project)'
         message += '\n- **assigned_to**: test-user-0 (user not in project)'
-        assert_equal(post.text, message)
+        assert post.text == message
 
     @td.with_tool('test', 'Tickets', 'bugs', username='test-user')
     def test_attach_with_resettable_stream(self):
@@ -318,7 +318,7 @@ class TestTicketModel(TrackerTestWithModel):
             ticket = Ticket.new()
             ticket.summary = 'test ticket'
             ticket.description = 'test description'
-        assert_equal(len(ticket.attachments), 0)
+        assert len(ticket.attachments) == 0
         f = six.moves.urllib.request.urlopen('file://%s' % __file__)
         TicketAttachment.save_attachment(
             'test_ticket_model.py', ResettableStream(f),
@@ -327,15 +327,15 @@ class TestTicketModel(TrackerTestWithModel):
         # need to refetch since attachments are cached
         session(ticket).expunge(ticket)
         ticket = Ticket.query.get(_id=ticket._id)
-        assert_equal(len(ticket.attachments), 1)
-        assert_equal(ticket.attachments[0].filename, 'test_ticket_model.py')
+        assert len(ticket.attachments) == 1
+        assert ticket.attachments[0].filename == 'test_ticket_model.py'
 
     def test_json_parents(self):
         ticket = Ticket.new()
         json_keys = list(ticket.__json__().keys())
-        assert_in('related_artifacts', json_keys)  # from Artifact
-        assert_in('votes_up', json_keys)  # VotableArtifact
-        assert_in('ticket_num', json_keys)  # Ticket
+        assert 'related_artifacts' in json_keys  # from Artifact
+        assert 'votes_up' in json_keys  # VotableArtifact
+        assert 'ticket_num' in json_keys  # Ticket
         assert ticket.__json__()['assigned_to'] is None
 
     @mock.patch('forgetracker.model.ticket.tsearch')
@@ -349,20 +349,20 @@ class TestTicketModel(TrackerTestWithModel):
         filter = None
         Ticket.paged_query_or_search(app_cfg, user, mongo_query, solr_query, filter, **kw)
         query.assert_called_once_with(app_cfg, user, mongo_query, sort=None, limit=None, page=0, **kw)
-        assert_equal(tsearch.query_filter_choices.call_count, 1)
-        assert_equal(tsearch.query_filter_choices.call_args[0][0], 'solr query')
-        assert_equal(search.call_count, 0)
+        assert tsearch.query_filter_choices.call_count == 1
+        assert tsearch.query_filter_choices.call_args[0][0] == 'solr query'
+        assert search.call_count == 0
         query.reset_mock(), search.reset_mock(), tsearch.reset_mock()
 
         filter = {'status': 'unread'}
         Ticket.paged_query_or_search(app_cfg, user, mongo_query, solr_query, filter, **kw)
         search.assert_called_once_with(app_cfg, user, solr_query, filter=filter, sort=None, limit=None, page=0, **kw)
-        assert_equal(query.call_count, 0)
-        assert_equal(tsearch.query_filter_choices.call_count, 0)
+        assert query.call_count == 0
+        assert tsearch.query_filter_choices.call_count == 0
 
     def test_index(self):
         idx = Ticket(ticket_num=2, summary="ticket2", labels=["mylabel", "other"]).index()
-        assert_equal(idx['summary_t'], 'ticket2')
-        assert_equal(idx['labels_t'], 'mylabel other')
-        assert_equal(idx['reported_by_s'], 'test-user')
-        assert_equal(idx['assigned_to_s'], None)  # must exist at least
+        assert idx['summary_t'] == 'ticket2'
+        assert idx['labels_t'] == 'mylabel other'
+        assert idx['reported_by_s'] == 'test-user'
+        assert idx['assigned_to_s'] == None  # must exist at least
diff --git a/ForgeWiki/forgewiki/tests/functional/test_rest.py b/ForgeWiki/forgewiki/tests/functional/test_rest.py
index e2f2ecaa2..694275746 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_rest.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_rest.py
@@ -39,7 +39,7 @@ class TestWikiApi(TestRestApiBase):
 
     def test_get_root(self):
         r = self.app.get('/rest/p/test/wiki/')
-        assert_equal(r.json, {'pages': ['Home']})
+        assert r.json == {'pages': ['Home']}
 
     def test_get_page(self):
         r = self.app.get('/p/test/wiki/Home/')
@@ -49,17 +49,17 @@ class TestWikiApi(TestRestApiBase):
                       upload_files=[('file_info', 'test_root.py', content)])
         r = self.app.get('/rest/p/test/wiki/Home/')
         r = json.loads(r.text)
-        assert_equal(r['attachments'][0]['url'],
+        assert (r['attachments'][0]['url'] ==
                      'http://localhost/p/test/wiki/Home/attachment/test_root.py')
-        assert_equal(r['discussion_thread_url'], 'http://localhost/rest%s' %
+        assert (r['discussion_thread_url'] == 'http://localhost/rest%s' %
                      discussion_url)
-        assert_equal(r['discussion_thread']['_id'],
+        assert (r['discussion_thread']['_id'] ==
                      discussion_url.split('/')[-2])
         self.app.post('/wiki/Home/attach',
                       upload_files=[('file_info', '__init__.py', content), ])
         r = self.app.get('/rest/p/test/wiki/Home/')
         r = json.loads(r.text)
-        assert_equal(len(r['attachments']), 2)
+        assert len(r['attachments']) == 2
 
     def test_page_does_not_exist(self):
         r = self.api_get('/rest/p/test/wiki/fake/', status=404)
@@ -70,10 +70,10 @@ class TestWikiApi(TestRestApiBase):
             'labels': 'head hunting,dark side'
         }
         r = self.api_post('/rest/p/test/wiki/Home/', **data)
-        assert_equal(r.status_int, 200)
+        assert r.status_int == 200
         r = self.api_get('/rest/p/test/wiki/Home/')
-        assert_equal(r.json['text'], data['text'])
-        assert_equal(r.json['labels'], data['labels'].split(','))
+        assert r.json['text'] == data['text']
+        assert r.json['labels'] == data['labels'].split(',')
 
     def test_create_page(self):
         data = {
@@ -81,10 +81,10 @@ class TestWikiApi(TestRestApiBase):
             'labels': 'head hunting,dark side'
         }
         r = self.api_post(h.urlquote('/rest/p/test/wiki/tést/'), **data)
-        assert_equal(r.status_int, 200)
+        assert r.status_int == 200
         r = self.api_get(h.urlquote('/rest/p/test/wiki/tést/'))
-        assert_equal(r.json['text'], data['text'])
-        assert_equal(r.json['labels'], data['labels'].split(','))
+        assert r.json['text'] == data['text']
+        assert r.json['labels'] == data['labels'].split(',')
 
     def test_create_page_limit(self):
         data = {
@@ -95,12 +95,12 @@ class TestWikiApi(TestRestApiBase):
         with h.push_config(tg.config, **{'forgewiki.rate_limits': '{}'}):
             r = self.api_post('/rest/p/test/wiki/page1/', status=200, **data)
             p = Page.query.get(title='page1')
-            assert_not_equal(p, None)
+            assert p != None
         # Set rate limit to 1 in first hour of project
         with h.push_config(tg.config, **{'forgewiki.rate_limits': '{"3600": 1}'}):
             r = self.api_post('/rest/p/test/wiki/page2/', status=429, **data)
             p = Page.query.get(title='page2')
-            assert_equal(p, None)
+            assert p == None
 
     # http://blog.watchfire.com/wfblog/2011/10/json-based-xss-exploitation.html
     def test_json_encoding_security(self):
@@ -108,15 +108,15 @@ class TestWikiApi(TestRestApiBase):
                       text='foo <img src=x onerror=alert(1)> bar')
         r = self.api_get('/rest/p/test/wiki/foo.html')
         # raw text is not an HTML tag
-        assert_in(r'foo \u003Cimg src=x onerror=alert(1)> bar', r.text)
+        assert r'foo \u003Cimg src=x onerror=alert(1)> bar' in r.text
         # and json still is parsed into correct content
-        assert_equal(r.json['text'], 'foo <img src=x onerror=alert(1)> bar')
+        assert r.json['text'] == 'foo <img src=x onerror=alert(1)> bar'
 
     def test_json_encoding_directly(self):
         # used in @expose('json'), monkey-patched in our patches.py
-        assert_equal(tg.jsonify.encode('<'), r'"\u003C"')
+        assert tg.jsonify.encode('<') == r'"\u003C"'
         # make sure these are unchanged
-        assert_equal(json.dumps('<'), '"<"')
+        assert json.dumps('<') == '"<"'
 
 
 class TestWikiHasAccess(TestRestApiBase):
@@ -139,13 +139,13 @@ class TestWikiHasAccess(TestRestApiBase):
         r = self.api_get(
             '/rest/p/test/wiki/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/wiki/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
 
     def test_has_access_not_admin(self):
         """
@@ -161,10 +161,10 @@ class TestWikiHasAccess(TestRestApiBase):
         r = self.api_get(
             '/rest/p/test/wiki/has_access?user=test-admin&perm=create',
             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/wiki/has_access?user=test-user&perm=create',
             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/ForgeWiki/forgewiki/tests/functional/test_root.py b/ForgeWiki/forgewiki/tests/functional/test_root.py
index 0c4ea7f0f..05e2b181e 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_root.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_root.py
@@ -80,7 +80,7 @@ class TestRootController(TestController):
 
     def test_root_new_page(self):
         response = self.app.get('/wiki/new_page?title=' + h.urlquote('tést'))
-        assert_equal(response.location, 'http://localhost/wiki/t%C3%A9st/')
+        assert response.location == 'http://localhost/wiki/t%C3%A9st/'
 
     def test_root_new_search(self):
         self.app.get(h.urlquote('/wiki/tést/'))
@@ -95,10 +95,10 @@ class TestRootController(TestController):
     @patch('allura.lib.search.search')
     def test_search(self, search):
         r = self.app.get('/wiki/search/?q=test')
-        assert_in(
-            '<a href="/wiki/search/?q=test&amp;sort=score+asc" class="strong">relevance</a>', r)
-        assert_in(
-            '<a href="/wiki/search/?q=test&amp;sort=mod_date_dt+desc" class="">date</a>', r)
+        assert (
+            '<a href="/wiki/search/?q=test&amp;sort=score+asc" class="strong">relevance</a>' in r)
+        assert (
+            '<a href="/wiki/search/?q=test&amp;sort=mod_date_dt+desc" class="">date</a>' in r)
 
         p = M.Project.query.get(shortname='test')
         r = self.app.get('/wiki/search/?q=test&sort=score+asc')
@@ -147,7 +147,7 @@ class TestRootController(TestController):
         assert btn is not None, "Can't find a help button"
         div = r.html.find('div', attrs={'id': 'lightbox_search_help_modal'})
         assert div is not None, "Can't find help text"
-        assert_in('To search for an exact phrase', div.text)
+        assert 'To search for an exact phrase' in div.text
 
     def test_nonexistent_page_edit(self):
         resp = self.app.get(h.urlquote('/wiki/tést/'))
@@ -188,7 +188,7 @@ class TestRootController(TestController):
                 'text': 'sometext',
                 'labels': 'test label',
                 }).follow()
-        assert_in('<a href="/p/test/wiki/search/?q=labels_t:%22test label%22&parser=standard">test label (1)</a>',
+        assert ('<a href="/p/test/wiki/search/?q=labels_t:%22test label%22&parser=standard">test label (1)</a>' in
                   response)
 
     def test_title_slashes(self):
@@ -347,14 +347,14 @@ class TestRootController(TestController):
                                             <script>alert(1)</script>""")
         self.app.post('/wiki/testdiff/update', params=d)
         response = self.app.get('/wiki/testdiff/diff?v1=1&v2=2')
-        assert_in('# Now fix <del> permissons. </del> <ins> permissions. </ins> '
-                  'Wrong permissions may cause <ins> a </ins> massive slowdown!',
+        assert ('# Now fix <del> permissons. </del> <ins> permissions. </ins> '
+                  'Wrong permissions may cause <ins> a </ins> massive slowdown!' in
                   response)
-        assert_not_in('<script>alert', response)
-        assert_in('&lt;script&gt;alert', response)
+        assert '<script>alert' not in response
+        assert '&lt;script&gt;alert' in response
         response = self.app.get('/wiki/testdiff/diff?v1=2&v2=1')
-        assert_in('# Now fix <del> permissions. </del> <ins> permissons. </ins> '
-                  'Wrong permissions may cause <del> a </del> massive slowdown!',
+        assert ('# Now fix <del> permissions. </del> <ins> permissons. </ins> '
+                  'Wrong permissions may cause <del> a </del> massive slowdown!' in
                   response)
 
     def test_page_raw(self):
@@ -405,8 +405,8 @@ class TestRootController(TestController):
                 'text': 'sometext',
                 'labels': '',
                 })
-        assert_equal(spam_checker.check.call_args[0][0], 'tést\nsometext')
-        assert_equal(response.location, 'http://localhost/wiki/t%C3%A9st/')
+        assert spam_checker.check.call_args[0][0] == 'tést\nsometext'
+        assert response.location == 'http://localhost/wiki/t%C3%A9st/'
 
     def test_page_get_markdown(self):
         self.app.post(
@@ -450,7 +450,7 @@ class TestRootController(TestController):
                 'text': 'sometext',
                 'labels': 'yellow,green',
                 })
-        assert_equal(response.location, 'http://localhost/wiki/t%C3%A9st/')
+        assert response.location == 'http://localhost/wiki/t%C3%A9st/'
         response = self.app.post(
             h.urlquote('/wiki/tést/update'),
             params={
@@ -458,7 +458,7 @@ class TestRootController(TestController):
                 'text': 'sometext',
                 'labels': 'yellow',
                 })
-        assert_equal(response.location, 'http://localhost/wiki/t%C3%A9st/')
+        assert response.location == 'http://localhost/wiki/t%C3%A9st/'
 
     def test_page_label_count(self):
         labels = "label"
@@ -539,7 +539,7 @@ class TestRootController(TestController):
         self.app.post(h.urlquote('/wiki/tést/attach'), upload_files=[upload])
         page_editor = self.app.get(h.urlquote('/wiki/tést/edit'))
         download = page_editor.click(description=file_name)
-        assert_true(download.body == file_data)
+        assert download.body == file_data
 
     def test_new_image_attachment_content(self):
         self.app.post('/wiki/TEST/update', params={
@@ -679,7 +679,7 @@ class TestRootController(TestController):
             'labels': '',
             })
         homepage_admin = self.app.get('/admin/wiki/home', validate_chunk=True)
-        assert_equal(homepage_admin.form['new_home'].value, 'Home')
+        assert homepage_admin.form['new_home'].value == 'Home'
         homepage_admin.form['new_home'].value = 'our_néw_home'
         homepage_admin.form.submit()
         root_path = self.app.get('/wiki/', status=301)
@@ -783,7 +783,7 @@ class TestRootController(TestController):
         # first request caches html, second serves from cache
         r = self.app.get('/wiki/cache/')
         r = self.app.get('/wiki/cache/')
-        assert_true(html in r)
+        assert html in r
 
     def test_page_delete(self):
         self.app.post('/wiki/aaa/update', params={
@@ -814,7 +814,7 @@ class TestRootController(TestController):
         # undelete it
         undelete_url = deletedpath + 'undelete'
         response = self.app.post(undelete_url)
-        assert_equal(response.json, {'location': './edit'})
+        assert response.json == {'location': './edit'}
         response = self.app.get(deletedpath + 'edit')
         assert 'Edit bbb' in response
 
@@ -877,20 +877,20 @@ class TestRootController(TestController):
         # Set rate limit to unlimit
         with h.push_config(config, **{'forgewiki.rate_limits': '{}'}):
             r = self.app.get('/p/test/wiki/new-page-title/')
-            assert_equal(r.status_int, 302)
-            assert_equal(
-                r.location,
+            assert r.status_int == 302
+            assert (
+                r.location ==
                 'http://localhost/p/test/wiki/new-page-title/edit')
-            assert_equal(self.webflash(r), '')
+            assert self.webflash(r) == ''
         # Set rate limit to 1 in first hour of project
         with h.push_config(config, **{'forgewiki.rate_limits': '{"3600": 1}'}):
             r = self.app.get('/p/test/wiki/new-page-title/')
-            assert_equal(r.status_int, 302)
-            assert_equal(r.location, 'http://localhost/p/test/wiki/')
+            assert r.status_int == 302
+            assert r.location == 'http://localhost/p/test/wiki/'
             wf = json.loads(self.webflash(r))
-            assert_equal(wf['status'], 'error')
-            assert_equal(
-                wf['message'],
+            assert wf['status'] == 'error'
+            assert (
+                wf['message'] ==
                 'Page create/edit rate limit exceeded. Please try again later.')
 
     def test_rate_limit_update(self):
@@ -899,23 +899,23 @@ class TestRootController(TestController):
             r = self.app.post(
                 '/p/test/wiki/page1/update',
                 dict(text='Some text', title='page1')).follow()
-            assert_in('Some text', r)
+            assert 'Some text' in r
             p = model.Page.query.get(title='page1')
-            assert_not_equal(p, None)
+            assert p != None
         # Set rate limit to 1 in first hour of project
         with h.push_config(config, **{'forgewiki.rate_limits': '{"3600": 1}'}):
             r = self.app.post(
                 '/p/test/wiki/page2/update',
                 dict(text='Some text', title='page2'))
-            assert_equal(r.status_int, 302)
-            assert_equal(r.location, 'http://localhost/p/test/wiki/')
+            assert r.status_int == 302
+            assert r.location == 'http://localhost/p/test/wiki/'
             wf = json.loads(self.webflash(r))
-            assert_equal(wf['status'], 'error')
-            assert_equal(
-                wf['message'],
+            assert wf['status'] == 'error'
+            assert (
+                wf['message'] ==
                 'Page create/edit rate limit exceeded. Please try again later.')
             p = model.Page.query.get(title='page2')
-            assert_equal(p, None)
+            assert p == None
 
     def test_rate_limit_by_user(self):
         # also test that multiple edits to a page counts as one page towards the limit
@@ -926,45 +926,45 @@ class TestRootController(TestController):
         with h.push_config(config, **{'forgewiki.rate_limits_per_user': '{"3600": 5}'}):
             r = self.app.post('/p/test/wiki/page123/update',  # page 4 (remember, 3 other projects' wiki pages)
                               dict(text='Starting a new page, ok', title='page123'))
-            assert_equal(self.webflash(r), '')
+            assert self.webflash(r) == ''
             r = self.app.post('/p/test/wiki/page123/update',
                               dict(text='Editing some', title='page123'))
-            assert_equal(self.webflash(r), '')
+            assert self.webflash(r) == ''
             r = self.app.post('/p/test/wiki/page123/update',
                               dict(text='Still editing', title='page123'))
-            assert_equal(self.webflash(r), '')
+            assert self.webflash(r) == ''
             r = self.app.post('/p/test/wiki/pageABC/update',  # page 5
                               dict(text='Another new page', title='pageABC'))
-            assert_equal(self.webflash(r), '')
+            assert self.webflash(r) == ''
             r = self.app.post('/p/test/wiki/pageZZZZZ/update',  # page 6
                               dict(text='This new page hits the limit', title='pageZZZZZ'))
             wf = json.loads(self.webflash(r))
-            assert_equal(wf['status'], 'error')
-            assert_equal(wf['message'], 'Page create/edit rate limit exceeded. Please try again later.')
+            assert wf['status'] == 'error'
+            assert wf['message'] == 'Page create/edit rate limit exceeded. Please try again later.'
 
     def test_sidebar_admin_menu(self):
         r = self.app.get('/p/test/wiki/Home/')
         menu = r.html.find('div', {'id': 'sidebar-admin-menu'})
-        assert_equal(menu['class'], ['hidden'])  # (not expanded)
+        assert menu['class'] == ['hidden']  # (not expanded)
         menu = [li.find('span').getText() for li in menu.findAll('li')]
-        assert_equal(
-            menu,
+        assert (
+            menu ==
             ['Set Home', 'Permissions', 'Options', 'Rename', 'Delete Everything'])
 
     def test_sidebar_admin_menu_is_expanded(self):
         r = self.app.get('/p/test/admin/wiki/permissions')
         menu = r.html.find('div', {'id': 'sidebar-admin-menu'})
-        assert_not_in('hidden', menu.get('class', []))  # expanded
+        assert 'hidden' not in menu.get('class', [])  # expanded
 
     def test_sidebar_admin_menu_invisible_to_not_admin(self):
         def assert_invisible_for(username):
             env = {'username': str(username)}
             r = self.app.get('/p/test/wiki/Home/', extra_environ=env)
             menu = r.html.find('div', {'id': 'sidebar-admin-menu'})
-            assert_equal(menu, None)
+            assert menu == None
         assert_invisible_for('*anonymous')
         assert_invisible_for('test-user')
 
     def test_no_index_tag_on_empty_wiki(self):
         r = self.app.get('/u/test-user/wiki/Home/')
-        assert_in('content="noindex, follow"', r.text)
+        assert 'content="noindex, follow"' in r.text
diff --git a/ForgeWiki/forgewiki/tests/test_app.py b/ForgeWiki/forgewiki/tests/test_app.py
index 580423939..1696fc192 100644
--- a/ForgeWiki/forgewiki/tests/test_app.py
+++ b/ForgeWiki/forgewiki/tests/test_app.py
@@ -72,22 +72,22 @@ class TestBulkExport:
         f.seek(0)
         wiki = json.loads(f.read())
         pages = sorted(wiki['pages'], key=operator.itemgetter('title'))
-        assert_equal(len(pages), 3)
-        assert_equal(pages[0]['title'], 'A New Hope')
-        assert_equal(pages[0]['text'], 'Star Wars Episode IV: A New Hope')
-        assert_equal(pages[0]['mod_date'], '2013-07-05 00:00:00')
-        assert_equal(pages[0]['labels'], ['star wars', 'movies'])
-        assert_equal(len(pages[0]['discussion_thread']['posts']), 2)
-
-        assert_equal(pages[1]['title'], 'Return of the Jedi')
-        assert_equal(pages[1]['text'],
+        assert len(pages) == 3
+        assert pages[0]['title'] == 'A New Hope'
+        assert pages[0]['text'] == 'Star Wars Episode IV: A New Hope'
+        assert pages[0]['mod_date'] == '2013-07-05 00:00:00'
+        assert pages[0]['labels'] == ['star wars', 'movies']
+        assert len(pages[0]['discussion_thread']['posts']) == 2
+
+        assert pages[1]['title'] == 'Return of the Jedi'
+        assert (pages[1]['text'] ==
                      'Star Wars Episode VI: Return of the Jedi')
-        assert_equal(len(pages[1]['discussion_thread']['posts']), 0)
+        assert len(pages[1]['discussion_thread']['posts']) == 0
 
-        assert_equal(pages[2]['title'], 'The Empire Strikes Back')
-        assert_equal(pages[2]['text'],
+        assert pages[2]['title'] == 'The Empire Strikes Back'
+        assert (pages[2]['text'] ==
                      'Star Wars Episode V: The Empire Strikes Back')
-        assert_equal(len(pages[2]['discussion_thread']['posts']), 0)
+        assert len(pages[2]['discussion_thread']['posts']) == 0
 
     def add_page_with_attachmetns(self):
         self.page = WM.Page.upsert('ZTest_title')
@@ -160,7 +160,7 @@ class TestApp:
         msg = dict(payload=message, message_id=message_id, headers={'Subject': 'test'})
         self.wiki.handle_message('A_New_Hope', msg)
         post = M.Post.query.get(_id=message_id)
-        assert_equal(post["text"], message)
+        assert post["text"] == message
 
     def test_uninstall(self):
         assert WM.Page.query.get(title='A New Hope')
diff --git a/ForgeWiki/forgewiki/tests/test_wiki_roles.py b/ForgeWiki/forgewiki/tests/test_wiki_roles.py
index 605c4cedc..e03880976 100644
--- a/ForgeWiki/forgewiki/tests/test_wiki_roles.py
+++ b/ForgeWiki/forgewiki/tests/test_wiki_roles.py
@@ -44,12 +44,12 @@ def test_role_assignments():
     def check_access(perm):
         pred = security.has_access(c.app, perm)
         return pred(user=admin), pred(user=user), pred(user=anon)
-    assert_equal(check_access('configure'), (True, False, False))
-    assert_equal(check_access('read'), (True, True, True))
-    assert_equal(check_access('create'), (True, False, False))
-    assert_equal(check_access('edit'), (True, False, False))
-    assert_equal(check_access('delete'), (True, False, False))
-    assert_equal(check_access('unmoderated_post'), (True, True, False))
-    assert_equal(check_access('post'), (True, True, False))
-    assert_equal(check_access('moderate'), (True, False, False))
-    assert_equal(check_access('admin'), (True, False, False))
+    assert check_access('configure') == (True, False, False)
+    assert check_access('read') == (True, True, True)
+    assert check_access('create') == (True, False, False)
+    assert check_access('edit') == (True, False, False)
+    assert check_access('delete') == (True, False, False)
+    assert check_access('unmoderated_post') == (True, True, False)
+    assert check_access('post') == (True, True, False)
+    assert check_access('moderate') == (True, False, False)
+    assert check_access('admin') == (True, False, False)


[allura] 10/10: [#8455] remove @with_setup

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-part2
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 50aa084c873c6f74d5b159f19f5369f12af7d63f
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Thu Sep 29 14:04:06 2022 +0000

    [#8455] remove @with_setup
---
 Allura/allura/tests/model/test_artifact.py     | 493 ++++++-------
 Allura/allura/tests/model/test_auth.py         | 823 ++++++++++-----------
 Allura/allura/tests/model/test_discussion.py   | 966 ++++++++++++-------------
 Allura/allura/tests/model/test_monq.py         |   4 +-
 Allura/allura/tests/model/test_neighborhood.py | 107 ++-
 Allura/allura/tests/model/test_oauth.py        |  32 +-
 Allura/allura/tests/model/test_project.py      | 325 ++++-----
 Allura/allura/tests/test_app.py                | 295 ++++----
 8 files changed, 1449 insertions(+), 1596 deletions(-)

diff --git a/Allura/allura/tests/model/test_artifact.py b/Allura/allura/tests/model/test_artifact.py
index 940ae0cac..35402dd0c 100644
--- a/Allura/allura/tests/model/test_artifact.py
+++ b/Allura/allura/tests/model/test_artifact.py
@@ -22,7 +22,6 @@ import re
 from datetime import datetime
 
 from tg import tmpl_context as c
-from alluratest.tools import with_setup
 from mock import patch
 import pytest
 from ming.orm.ormsession import ThreadLocalORMSession
@@ -55,273 +54,239 @@ class Checkmessage(M.Message):
 Mapper.compile_all()
 
 
-def setup_method():
-    setup_basic_test()
-    setup_unit_test()
-    setup_with_tools()
-
-
-def teardown_module():
-    ThreadLocalORMSession.close_all()
-
-
-@td.with_wiki
-def setup_with_tools():
-    h.set_context('test', 'wiki', neighborhood='Projects')
-    Checkmessage.query.remove({})
-    WM.Page.query.remove({})
-    WM.PageHistory.query.remove({})
-    M.Shortlink.query.remove({})
-    c.user = M.User.query.get(username='test-admin')
-    Checkmessage.project = c.project
-    Checkmessage.app_config = c.app.config
-
-
-@with_setup(setup_method)
-def test_artifact():
-    pg = WM.Page(title='TestPage1')
-    assert pg.project == c.project
-    assert pg.project_id == c.project._id
-    assert pg.app.config == c.app.config
-    assert pg.app_config == c.app.config
-    u = M.User.query.get(username='test-user')
-    pr = M.ProjectRole.by_user(u, upsert=True)
-    ThreadLocalORMSession.flush_all()
-    REGISTRY.register(allura.credentials, allura.lib.security.Credentials())
-    assert not security.has_access(pg, 'delete')(user=u)
-    pg.acl.append(M.ACE.allow(pr._id, 'delete'))
-    ThreadLocalORMSession.flush_all()
-    assert security.has_access(pg, 'delete')(user=u)
-    pg.acl.pop()
-    ThreadLocalORMSession.flush_all()
-    assert not security.has_access(pg, 'delete')(user=u)
-
-
-def test_artifact_index():
-    pg = WM.Page(title='TestPage1')
-    idx = pg.index()
-    assert 'title' in idx
-    assert 'url_s' in idx
-    assert 'project_id_s' in idx
-    assert 'mount_point_s' in idx
-    assert 'type_s' in idx
-    assert 'id' in idx
-    assert idx['id'] == pg.index_id()
-    assert 'text' in idx
-    assert 'TestPage' in pg.shorthand_id()
-    assert pg.link_text() == pg.shorthand_id()
-
-
-@with_setup(setup_method)
-def test_artifactlink():
-    pg = WM.Page(title='TestPage2')
-    q_shortlink = M.Shortlink.query.find(dict(
-        project_id=c.project._id,
-        app_config_id=c.app.config._id,
-        link=pg.shorthand_id()))
-    assert q_shortlink.count() == 0
-
-    ThreadLocalORMSession.flush_all()
-    M.MonQTask.run_ready()
-    ThreadLocalORMSession.flush_all()
-    assert q_shortlink.count() == 1
-
-    assert M.Shortlink.lookup('[TestPage2]')
-    assert M.Shortlink.lookup('[wiki:TestPage2]')
-    assert M.Shortlink.lookup('[test:wiki:TestPage2]')
-    assert not M.Shortlink.lookup('[test:wiki:TestPage2:foo]')
-    assert not M.Shortlink.lookup('[Wiki:TestPage2]')
-    assert not M.Shortlink.lookup('[TestPage2_no_such_page]')
-
-    pg.delete()
-    c.project.uninstall_app('wiki')
-    assert not M.Shortlink.lookup('[wiki:TestPage2]')
-    assert q_shortlink.count() == 0
-
-
-@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_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_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_method)
-def test_versioning():
-    pg = WM.Page(title='TestPage3')
-    with patch('allura.model.artifact.request',
-               Request.blank('/', remote_addr='1.1.1.1')):
+class TestArtifact:
+
+    def setup_method(self):
+        setup_basic_test()
+        setup_unit_test()
+        self.setup_with_tools()
+
+    def teardown_class(cls):
+        ThreadLocalORMSession.close_all()
+
+    @td.with_wiki
+    def setup_with_tools(self):
+        h.set_context('test', 'wiki', neighborhood='Projects')
+        Checkmessage.query.remove({})
+        WM.Page.query.remove({})
+        WM.PageHistory.query.remove({})
+        M.Shortlink.query.remove({})
+        c.user = M.User.query.get(username='test-admin')
+        Checkmessage.project = c.project
+        Checkmessage.app_config = c.app.config
+
+    def test_artifact(self):
+        pg = WM.Page(title='TestPage1')
+        assert pg.project == c.project
+        assert pg.project_id == c.project._id
+        assert pg.app.config == c.app.config
+        assert pg.app_config == c.app.config
+        u = M.User.query.get(username='test-user')
+        pr = M.ProjectRole.by_user(u, upsert=True)
+        ThreadLocalORMSession.flush_all()
+        REGISTRY.register(allura.credentials, allura.lib.security.Credentials())
+        assert not security.has_access(pg, 'delete')(user=u)
+        pg.acl.append(M.ACE.allow(pr._id, 'delete'))
+        ThreadLocalORMSession.flush_all()
+        assert security.has_access(pg, 'delete')(user=u)
+        pg.acl.pop()
+        ThreadLocalORMSession.flush_all()
+        assert not security.has_access(pg, 'delete')(user=u)
+
+    def test_artifact_index(self):
+        pg = WM.Page(title='TestPage1')
+        idx = pg.index()
+        assert 'title' in idx
+        assert 'url_s' in idx
+        assert 'project_id_s' in idx
+        assert 'mount_point_s' in idx
+        assert 'type_s' in idx
+        assert 'id' in idx
+        assert idx['id'] == pg.index_id()
+        assert 'text' in idx
+        assert 'TestPage' in pg.shorthand_id()
+        assert pg.link_text() == pg.shorthand_id()
+
+    def test_artifactlink(self):
+        pg = WM.Page(title='TestPage2')
+        q_shortlink = M.Shortlink.query.find(dict(
+            project_id=c.project._id,
+            app_config_id=c.app.config._id,
+            link=pg.shorthand_id()))
+        assert q_shortlink.count() == 0
+
+        ThreadLocalORMSession.flush_all()
+        M.MonQTask.run_ready()
+        ThreadLocalORMSession.flush_all()
+        assert q_shortlink.count() == 1
+
+        assert M.Shortlink.lookup('[TestPage2]')
+        assert M.Shortlink.lookup('[wiki:TestPage2]')
+        assert M.Shortlink.lookup('[test:wiki:TestPage2]')
+        assert not M.Shortlink.lookup('[test:wiki:TestPage2:foo]')
+        assert not M.Shortlink.lookup('[Wiki:TestPage2]')
+        assert not M.Shortlink.lookup('[TestPage2_no_such_page]')
+
+        pg.delete()
+        c.project.uninstall_app('wiki')
+        assert not M.Shortlink.lookup('[wiki:TestPage2]')
+        assert q_shortlink.count() == 0
+
+    def test_gen_messageid(self):
+        assert re.match(r'[0-9a-zA-Z]*.wiki@test.p.localhost',
+                        h.gen_message_id())
+
+    def test_gen_messageid_with_id_set(self):
+        oid = ObjectId()
+        assert re.match(r'%s.wiki@test.p.localhost' %
+                        str(oid), h.gen_message_id(oid))
+
+    def test_artifact_messageid(self):
+        p = WM.Page(title='T')
+        assert re.match(r'%s.wiki@test.p.localhost' %
+                        str(p._id), p.message_id())
+
+    def test_versioning(self):
+        pg = WM.Page(title='TestPage3')
+        with patch('allura.model.artifact.request', Request.blank('/', remote_addr='1.1.1.1')):
+            pg.commit()
+        ThreadLocalORMSession.flush_all()
+        pg.text = 'Here is some text'
         pg.commit()
-    ThreadLocalORMSession.flush_all()
-    pg.text = 'Here is some text'
-    pg.commit()
-    ThreadLocalORMSession.flush_all()
-    ss = pg.get_version(1)
-    assert ss.author.logged_ip == '1.1.1.1'
-    assert ss.index()['is_history_b']
-    assert ss.shorthand_id() == pg.shorthand_id() + '#1'
-    assert ss.title == pg.title
-    assert ss.text != pg.text
-    ss = pg.get_version(-1)
-    assert ss.index()['is_history_b']
-    assert ss.shorthand_id() == pg.shorthand_id() + '#2'
-    assert ss.title == pg.title
-    assert ss.text == pg.text
-    pytest.raises(IndexError, pg.get_version, 42)
-    pg.revert(1)
-    pg.commit()
-    ThreadLocalORMSession.flush_all()
-    assert ss.text != pg.text
-    assert pg.history().count() == 3
-
-
-@with_setup(setup_method)
-def test_messages_unknown_lookup():
-    from bson import ObjectId
-    m = Checkmessage()
-    m.author_id = ObjectId()  # something new
-    assert isinstance(m.author(), M.User), type(m.author())
-    assert m.author() == M.User.anonymous()
-
-
-@with_setup(setup_method)
-@patch('allura.model.artifact.datetime')
-def test_last_updated(_datetime):
-    c.project.last_updated = datetime(2014, 1, 1)
-    _datetime.utcnow.return_value = datetime(2014, 1, 2)
-    WM.Page(title='TestPage1')
-    ThreadLocalORMSession.flush_all()
-    assert c.project.last_updated == datetime(2014, 1, 2)
-
-
-@with_setup(setup_method)
-@patch('allura.model.artifact.datetime')
-def test_last_updated_disabled(_datetime):
-    c.project.last_updated = datetime(2014, 1, 1)
-    _datetime.utcnow.return_value = datetime(2014, 1, 2)
-    try:
-        M.artifact_orm_session._get().skip_last_updated = True
+        ThreadLocalORMSession.flush_all()
+        ss = pg.get_version(1)
+        assert ss.author.logged_ip == '1.1.1.1'
+        assert ss.index()['is_history_b']
+        assert ss.shorthand_id() == pg.shorthand_id() + '#1'
+        assert ss.title == pg.title
+        assert ss.text != pg.text
+        ss = pg.get_version(-1)
+        assert ss.index()['is_history_b']
+        assert ss.shorthand_id() == pg.shorthand_id() + '#2'
+        assert ss.title == pg.title
+        assert ss.text == pg.text
+        pytest.raises(IndexError, pg.get_version, 42)
+        pg.revert(1)
+        pg.commit()
+        ThreadLocalORMSession.flush_all()
+        assert ss.text != pg.text
+        assert pg.history().count() == 3
+
+    def test_messages_unknown_lookup(self):
+        from bson import ObjectId
+        m = Checkmessage()
+        m.author_id = ObjectId()  # something new
+        assert isinstance(m.author(), M.User), type(m.author())
+        assert m.author() == M.User.anonymous()
+
+    @patch('allura.model.artifact.datetime')
+    def test_last_updated(self, _datetime):
+        c.project.last_updated = datetime(2014, 1, 1)
+        _datetime.utcnow.return_value = datetime(2014, 1, 2)
         WM.Page(title='TestPage1')
         ThreadLocalORMSession.flush_all()
-        assert c.project.last_updated == datetime(2014, 1, 1)
-    finally:
-        M.artifact_orm_session._get().skip_last_updated = False
-
-
-@with_setup(setup_method)
-def test_get_discussion_thread_dupe():
-    artif = WM.Page(title='TestSomeArtifact')
-    thr1 = artif.get_discussion_thread()[0]
-    thr1.post('thr1-post1')
-    thr1.post('thr1-post2')
-    thr2 = M.Thread.new(ref_id=thr1.ref_id)
-    thr2.post('thr2-post1')
-    thr2.post('thr2-post2')
-    thr2.post('thr2-post3')
-    thr3 = M.Thread.new(ref_id=thr1.ref_id)
-    thr3.post('thr3-post1')
-    thr4 = M.Thread.new(ref_id=thr1.ref_id)
-
-    thread_q = M.Thread.query.find(dict(ref_id=artif.index_id()))
-    assert thread_q.count() == 4
-
-    thread = artif.get_discussion_thread()[0]  # force cleanup
-    threads = thread_q.all()
-    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
-
-
-def test_snapshot_clear_user_data():
-    s = M.Snapshot(author={'username': 'johndoe',
-                           'display_name': 'John Doe',
-                           'logged_ip': '1.2.3.4'})
-    s.clear_user_data()
-    assert s.author == {'username': '',
+        assert c.project.last_updated == datetime(2014, 1, 2)
+
+    @patch('allura.model.artifact.datetime')
+    def test_last_updated_disabled(self, _datetime):
+        c.project.last_updated = datetime(2014, 1, 1)
+        _datetime.utcnow.return_value = datetime(2014, 1, 2)
+        try:
+            M.artifact_orm_session._get().skip_last_updated = True
+            WM.Page(title='TestPage1')
+            ThreadLocalORMSession.flush_all()
+            assert c.project.last_updated == datetime(2014, 1, 1)
+        finally:
+            M.artifact_orm_session._get().skip_last_updated = False
+
+    def test_get_discussion_thread_dupe(self):
+        artif = WM.Page(title='TestSomeArtifact')
+        thr1 = artif.get_discussion_thread()[0]
+        thr1.post('thr1-post1')
+        thr1.post('thr1-post2')
+        thr2 = M.Thread.new(ref_id=thr1.ref_id)
+        thr2.post('thr2-post1')
+        thr2.post('thr2-post2')
+        thr2.post('thr2-post3')
+        thr3 = M.Thread.new(ref_id=thr1.ref_id)
+        thr3.post('thr3-post1')
+        thr4 = M.Thread.new(ref_id=thr1.ref_id)
+
+        thread_q = M.Thread.query.find(dict(ref_id=artif.index_id()))
+        assert thread_q.count() == 4
+
+        thread = artif.get_discussion_thread()[0]  # force cleanup
+        threads = thread_q.all()
+        assert len(threads) == 1
+        assert len(thread.posts) == 6
+        # normal thread deletion propagates to children, make sure that doesn't happen
+        assert not any(p.deleted for p in thread.posts)
+
+    def test_snapshot_clear_user_data(self):
+        s = M.Snapshot(author={'username': 'johndoe',
+                               'display_name': 'John Doe',
+                               'logged_ip': '1.2.3.4'})
+        s.clear_user_data()
+        assert s.author == {'username': '',
                             'display_name': '',
                             'logged_ip': None,
-                            'id': None,
-                            }
-
-
-@with_setup(setup_method)
-def test_snapshot_from_username():
-    s = M.Snapshot(author={'username': 'johndoe',
-                           'display_name': 'John Doe',
-                           'logged_ip': '1.2.3.4'})
-    s = M.Snapshot(author={'username': 'johnsmith',
-                           'display_name': 'John Doe',
-                           'logged_ip': '1.2.3.4'})
-    ThreadLocalORMSession.flush_all()
-    assert len(M.Snapshot.from_username('johndoe')) == 1
-
-
-def test_feed_clear_user_data():
-    f = M.Feed(author_name='John Doe',
+                            'id': None}
+
+    def test_snapshot_from_username(self):
+        s = M.Snapshot(author={'username': 'johndoe',
+                               'display_name': 'John Doe',
+                               'logged_ip': '1.2.3.4'})
+        s = M.Snapshot(author={'username': 'johnsmith',
+                               'display_name': 'John Doe',
+                               'logged_ip': '1.2.3.4'})
+        ThreadLocalORMSession.flush_all()
+        assert len(M.Snapshot.from_username('johndoe')) == 1
+
+    def test_feed_clear_user_data(self):
+        f = M.Feed(author_name='John Doe',
+                   author_link='/u/johndoe/',
+                   title='Something')
+        f.clear_user_data()
+        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 f.author_name == ''
+        assert f.author_link == ''
+        assert f.title == 'Home Page modified by <REDACTED>'
+
+    def test_feed_from_username(self):
+        M.Feed(author_name='John Doe',
                author_link='/u/johndoe/',
                title='Something')
-    f.clear_user_data()
-    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 f.author_name == ''
-    assert f.author_link == ''
-    assert f.title == 'Home Page modified by <REDACTED>'
-
-
-@with_setup(setup_method)
-def test_feed_from_username():
-    M.Feed(author_name='John Doe',
-           author_link='/u/johndoe/',
-           title='Something')
-    M.Feed(author_name='John Smith',
-           author_link='/u/johnsmith/',
-           title='Something')
-    ThreadLocalORMSession.flush_all()
-    assert len(M.Feed.from_username('johndoe')) == 1
-
-
-@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_method)
-def test_subscribed_no_tool_sub():
-    pg = WM.Page(title='TestPage4b')
-    M.Mailbox.unsubscribe(user_id=c.user._id,
-                          project_id=c.project._id,
-                          app_config_id=c.app.config._id)
-    pg.subscribe()
-    assert pg.subscribed(include_parents=True)
-    assert pg.subscribed(include_parents=False)
-
-
-@with_setup(setup_method)
-def test_not_subscribed():
-    pg = WM.Page(title='TestPage4c')
-    M.Mailbox.unsubscribe(user_id=c.user._id,
-                          project_id=c.project._id,
-                          app_config_id=c.app.config._id)
-    assert not pg.subscribed(include_parents=True)
-    assert not pg.subscribed(include_parents=False)
+        M.Feed(author_name='John Smith',
+               author_link='/u/johnsmith/',
+               title='Something')
+        ThreadLocalORMSession.flush_all()
+        assert len(M.Feed.from_username('johndoe')) == 1
+
+    def test_subscribed(self):
+        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)
+
+    def test_subscribed_no_tool_sub(self):
+        pg = WM.Page(title='TestPage4b')
+        M.Mailbox.unsubscribe(user_id=c.user._id,
+                              project_id=c.project._id,
+                              app_config_id=c.app.config._id)
+        pg.subscribe()
+        assert pg.subscribed(include_parents=True)
+        assert pg.subscribed(include_parents=False)
+
+    def test_not_subscribed(self):
+        pg = WM.Page(title='TestPage4c')
+        M.Mailbox.unsubscribe(user_id=c.user._id,
+                              project_id=c.project._id,
+                              app_config_id=c.app.config._id)
+        assert not pg.subscribed(include_parents=True)
+        assert not pg.subscribed(include_parents=False)
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index b4e5e80de..7b6364938 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -22,15 +22,7 @@ Model tests for auth
 import textwrap
 from datetime import datetime, timedelta
 
-from alluratest.tools import (
-    with_setup,
-    assert_equal,
-    assert_not_equal,
-    assert_true,
-    assert_not_in,
-    assert_in,
-)
-from tg import tmpl_context as c, app_globals as g, request
+from tg import tmpl_context as c, app_globals as g, request as r
 from webob import Request
 from mock import patch, Mock
 
@@ -41,437 +33,404 @@ from allura import model as M
 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 alluratest.controller import setup_basic_test, setup_global_objects, setup_functional_test, setup_unit_test
 from alluratest.pytest_helpers import with_nose_compatibility
 
 
-def setup_method():
-    setup_basic_test()
-    ThreadLocalORMSession.close_all()
-    setup_global_objects()
-
-
-@with_setup(setup_method)
-def test_email_address():
-    addr = M.EmailAddress(email='test_admin@domain.net',
-                          claimed_by_user_id=c.user._id)
-    ThreadLocalORMSession.flush_all()
-    assert addr.claimed_by_user() == c.user
-    addr2 = M.EmailAddress.create('test@domain.net')
-    addr3 = M.EmailAddress.create('test_admin@domain.net')
-
-    # Duplicate emails are allowed, until the email is confirmed
-    assert addr3 is not addr
-
-    assert addr2 is not addr
-    assert addr2
-    addr4 = M.EmailAddress.create('test@DOMAIN.NET')
-    assert addr4 is not addr2
-
-    assert addr is c.user.address_object('test_admin@domain.net')
-    c.user.claim_address('test@DOMAIN.NET')
-    assert 'test@domain.net' in c.user.email_addresses
-
-
-@with_setup(setup_method)
-def test_email_address_lookup_helpers():
-    addr = M.EmailAddress.create('TEST@DOMAIN.NET')
-    nobody = M.EmailAddress.create('nobody@example.com')
-    ThreadLocalORMSession.flush_all()
-    assert addr.email == 'TEST@domain.net'
-
-    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 M.EmailAddress.get(email='invalid') == None
-
-    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 M.EmailAddress.find(dict(email='invalid')).all() == []
-
-
-@with_setup(setup_method)
-def test_email_address_canonical():
-    assert (M.EmailAddress.canonical('nobody@EXAMPLE.COM') ==
-                 'nobody@example.com')
-    assert (M.EmailAddress.canonical('nobody@example.com') ==
-                 'nobody@example.com')
-    assert (M.EmailAddress.canonical('I Am Nobody <no...@example.com>') ==
-                 'nobody@example.com')
-    assert (M.EmailAddress.canonical('  nobody@example.com\t') ==
-                 'nobody@example.com')
-    assert (M.EmailAddress.canonical('I Am@Nobody <no...@example.com> ') ==
-                 'nobody@example.com')
-    assert (M.EmailAddress.canonical(' No@body <no...@example.com> ') ==
-                 'no@body@example.com')
-    assert (M.EmailAddress.canonical('no@body@example.com') ==
-                 'no@body@example.com')
-    assert M.EmailAddress.canonical('invalid') == None
-
-@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)
-
-    addr.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 rcpts == ['test_admin@domain.net']
-
-
-@with_setup(setup_method)
-@td.with_user_project('test-admin')
-def test_user():
-    assert c.user.url() .endswith('/u/test-admin/')
-    assert c.user.script_name .endswith('/u/test-admin/')
-    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 ({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 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
-    u.set_password('foo')
-    provider = plugin.LocalAuthenticationProvider(Request.blank('/'))
-    assert provider._validate_password(u, 'foo')
-    assert not provider._validate_password(u, 'foobar')
-    u.set_password('foobar')
-    assert provider._validate_password(u, 'foobar')
-    assert not provider._validate_password(u, 'foo')
-
-
-@with_setup(setup_method)
-def test_user_project_creates_on_demand():
-    u = M.User.register(dict(username='foobar123'), make_project=False)
-    ThreadLocalORMSession.flush_all()
-    assert not M.Project.query.get(shortname='u/foobar123')
-    assert u.private_project()
-    assert M.Project.query.get(shortname='u/foobar123')
-
-
-@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')
-    p.deleted = True
-    ThreadLocalORMSession.flush_all()
-    assert not M.Project.query.get(shortname='u/foobar123', deleted=False)
-    assert u.private_project()
-    ThreadLocalORMSession.flush_all()
-    assert M.Project.query.get(shortname='u/foobar123', deleted=False)
-
-
-@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)
-    ThreadLocalORMSession.flush_all()
-    assert not u.private_project()
-    assert not M.Project.query.get(shortname='u/foobar123')
-
-
-@with_setup(setup_method)
-def test_user_project_does_not_create_on_demand_for_anonymous_user():
-    u = M.User.anonymous()
-    ThreadLocalORMSession.flush_all()
-    assert not u.private_project()
-    assert not M.Project.query.get(shortname='u/anonymous')
-    assert not M.Project.query.get(shortname='u/*anonymous')
-
-
-@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)
-    u2 = M.User.register(dict(username='abc2'), make_project=False)
-    addr1 = M.EmailAddress(email='abc123@abc.me', confirmed=True,
-                           claimed_by_user_id=u1._id)
-    addr2 = M.EmailAddress(email='abc123@abc.me', confirmed=True,
-                           claimed_by_user_id=u2._id)
-    # both users are disabled
-    u1.disabled, u2.disabled = True, True
-    ThreadLocalORMSession.flush_all()
-    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 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 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 M.User.by_email_address('nobody@example.com') == u1
-    assert M.User.by_email_address('invalid') == None
-
-
-def test_user_equality():
-    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 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():
-    assert M.User.by_username('test-user') in {M.User.by_username('test-user')}
-    assert M.User.anonymous() in {M.User.anonymous()}
-    assert M.User.by_username('*anonymous') in {M.User.anonymous()}
-
-    assert M.User.by_username('test-user') not in {M.User.by_username('test-admin')}
-    assert M.User.anonymous() not in {M.User.by_username('test-admin')}
-    assert M.User.anonymous() not in {0, None}
-
-
-@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)
-    ThreadLocalORMSession.flush_all()
-    roles = g.credentials.user_roles(
-        c.user._id, project_id=c.project.root_project._id)
-    roles_ids = [r['_id'] for r in roles]
-    roles = M.ProjectRole.query.find({'_id': {'$in': roles_ids}})
-    for pr in roles:
-        assert pr.display()
-        pr.special
-        assert pr.user in (c.user, None, M.User.anonymous())
-
-
-@with_setup(setup_method)
-def test_default_project_roles():
-    roles = {
-        pr.name: pr
-        for pr in M.ProjectRole.query.find(dict(
-            project_id=c.project._id)).all()
-        if pr.name}
-    assert 'Admin' in list(roles.keys()), list(roles.keys())
-    assert 'Developer' in list(roles.keys()), list(roles.keys())
-    assert 'Member' in list(roles.keys()), list(roles.keys())
-    assert roles['Developer']._id in roles['Admin'].roles
-    assert roles['Member']._id in roles['Developer'].roles
-
-    # There're 1 user assigned to project, represented by
-    # relational (vs named) ProjectRole's
-    assert len(roles) == M.ProjectRole.query.find(dict(
-        project_id=c.project._id)).count() - 1
-
-
-@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)
-    c.user.disabled = True
-    ThreadLocalORMSession.flush_all()
-    assert addr.claimed_by_user() is None
-
-
-@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()} ==
-                 {'test', 'test2', 'u/test-admin', 'adobe-1', '--init--'})
-    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')
-    admin_role = M.ProjectRole.by_name('Admin', project)
-    developer_role = M.ProjectRole.by_name('Developer', project)
-    user_role = M.ProjectRole.by_user(c.user, project=project, upsert=True)
-    user_role.roles.remove(admin_role._id)
-    user_role.roles.append(developer_role._id)
-    ThreadLocalORMSession.flush_all()
-    g.credentials.clear()
-    assert ({p.shortname for p in c.user.my_projects()} ==
-                 {'test', 'test2', 'u/test-admin', 'adobe-1', '--init--'})
-    assert ({p.shortname for p in c.user.my_projects_by_role_name('Admin')} ==
-                 {'test', 'u/test-admin', 'adobe-1', '--init--'})
-
-
-@td.with_user_project('test-admin')
-@with_setup(setup_method)
-def test_user_projects_unnamed():
-    """
-    Confirm that spurious ProjectRoles associating a user with
-    a project to which they do not belong to any named group
-    don't cause the user to count as a member of the project.
-    """
-    sub1 = M.Project.query.get(shortname='test/sub1')
-    M.ProjectRole(
-        user_id=c.user._id,
-        project_id=sub1._id)
-    ThreadLocalORMSession.flush_all()
-    project_names = [p.shortname for p in c.user.my_projects()]
-    assert 'test/sub1' not in project_names
-    assert 'test' in project_names
-
-
-@patch.object(g, 'user_message_max_messages', 3)
-def test_check_sent_user_message_times():
-    user1 = M.User.register(dict(username='test-user'), make_project=False)
-    time1 = datetime.utcnow() - timedelta(minutes=30)
-    time2 = datetime.utcnow() - timedelta(minutes=45)
-    time3 = datetime.utcnow() - timedelta(minutes=70)
-    user1.sent_user_message_times = [time1, time2, time3]
-    assert user1.can_send_user_message()
-    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()
-
-
-@with_setup(setup_method)
-@td.with_user_project('test-admin')
-def test_user_track_active():
-    # without this session flushing inside track_active raises Exception
-    setup_functional_test()
-    c.user = M.User.by_username('test-admin')
-
-    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 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 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 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 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 c.user.last_access['session_ip'] == 'new addr'
-    assert c.user.last_access['session_ua'] == 'new browser'
-
-
-@with_setup(setup_method)
-def test_user_index():
-    c.user.email_addresses = ['email1', 'email2']
-    c.user.set_pref('email_address', 'email2')
-    idx = c.user.index()
-    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 'user_registration_date_dt' in idx
-
-
-@with_setup(setup_method)
-def test_user_index_none_values():
-    c.user.email_addresses = [None]
-    c.user.set_pref('telnumbers', [None])
-    c.user.set_pref('webpages', [None])
-    idx = c.user.index()
-    assert idx['email_addresses_t'] == ''
-    assert idx['telnumbers_t'] == ''
-    assert idx['webpages_t'] == ''
-
-
-@with_setup(setup_method)
-def test_user_backfill_login_details():
-    with h.push_config(request, user_agent='TestBrowser/55'):
-        # these shouldn't match
-        h.auditlog_user('something happened')
-        h.auditlog_user('blah blah Password changed')
-    with h.push_config(request, user_agent='TestBrowser/56'):
-        # these should all match, but only one entry created for this ip/ua
-        h.auditlog_user('Account activated')
-        h.auditlog_user('Successful login')
-        h.auditlog_user('Password changed')
-    with h.push_config(request, user_agent='TestBrowser/57'):
-        # this should match too
-        h.auditlog_user('Set up multifactor TOTP')
-    ThreadLocalORMSession.flush_all()
-
-    auth_provider = plugin.AuthenticationProvider.get(None)
-    c.user.backfill_login_details(auth_provider)
-
-    details = M.UserLoginDetails.query.find({'user_id': c.user._id}).sort('ua').all()
-    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 TestAuth:
+
+    def setup_method(self):
+        setup_basic_test()
+        setup_global_objects()
+
+    def test_email_address(self):
+        addr = M.EmailAddress(email='test_admin@domain.net',
+                              claimed_by_user_id=c.user._id)
+        ThreadLocalORMSession.flush_all()
+        assert addr.claimed_by_user() == c.user
+        addr2 = M.EmailAddress.create('test@domain.net')
+        addr3 = M.EmailAddress.create('test_admin@domain.net')
+        ThreadLocalORMSession.flush_all()
+
+        # Duplicate emails are allowed, until the email is confirmed
+        assert addr3 is not addr
+
+        assert addr2 is not addr
+        assert addr2
+        addr4 = M.EmailAddress.create('test@DOMAIN.NET')
+        assert addr4 is not addr2
+
+        assert addr is c.user.address_object('test_admin@domain.net')
+        c.user.claim_address('test@DOMAIN.NET')
+        assert 'test@domain.net' in c.user.email_addresses
+
+    def selftest_email_address_lookup_helpers():
+        addr = M.EmailAddress.create('TEST@DOMAIN.NET')
+        nobody = M.EmailAddress.create('nobody@example.com')
+        ThreadLocalORMSession.flush_all()
+        assert addr.email == 'TEST@domain.net'
+
+        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') is None
+        assert M.EmailAddress.get(email=None) is None
+        assert M.EmailAddress.get(email='nobody@example.com') == nobody
+        # invalid email returns None, but not nobody@example.com as before
+        assert M.EmailAddress.get(email='invalid') is None
+
+        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 M.EmailAddress.find(dict(email='invalid')).all() == []
+
+    def test_email_address_canonical(self):
+        assert M.EmailAddress.canonical('nobody@EXAMPLE.COM') == \
+               'nobody@example.com'
+        assert M.EmailAddress.canonical('nobody@example.com') == \
+               'nobody@example.com'
+        assert M.EmailAddress.canonical('I Am Nobody <no...@example.com>') == \
+               'nobody@example.com'
+        assert M.EmailAddress.canonical('  nobody@example.com\t') == \
+               'nobody@example.com'
+        assert M.EmailAddress.canonical('I Am@Nobody <no...@example.com> ') == \
+               'nobody@example.com'
+        assert M.EmailAddress.canonical(' No@body <no...@example.com> ') == \
+               'no@body@example.com'
+        assert M.EmailAddress.canonical('no@body@example.com') == \
+               'no@body@example.com'
+        assert M.EmailAddress.canonical('invalid') is None
+
+    def test_email_address_send_verification_link(self):
+        addr = M.EmailAddress(email='test_admin@domain.net',
+                              claimed_by_user_id=c.user._id)
+
+        addr.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 rcpts == ['test_admin@domain.net']
+
+    @td.with_user_project('test-admin')
+    def test_user(self):
+        assert c.user.url() .endswith('/u/test-admin/')
+        assert c.user.script_name .endswith('/u/test-admin/')
+        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 ({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 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
+        u.set_password('foo')
+        provider = plugin.LocalAuthenticationProvider(Request.blank('/'))
+        assert provider._validate_password(u, 'foo')
+        assert not provider._validate_password(u, 'foobar')
+        u.set_password('foobar')
+        assert provider._validate_password(u, 'foobar')
+        assert not provider._validate_password(u, 'foo')
+
+    def test_user_project_creates_on_demand(self):
+        u = M.User.register(dict(username='foobar123'), make_project=False)
+        ThreadLocalORMSession.flush_all()
+        assert not M.Project.query.get(shortname='u/foobar123')
+        assert u.private_project()
+        assert M.Project.query.get(shortname='u/foobar123')
+
+    def test_user_project_already_deleted_creates_on_demand(self):
+        u = M.User.register(dict(username='foobar123'), make_project=True)
+        p = M.Project.query.get(shortname='u/foobar123')
+        p.deleted = True
+        ThreadLocalORMSession.flush_all()
+        assert not M.Project.query.get(shortname='u/foobar123', deleted=False)
+        assert u.private_project()
+        ThreadLocalORMSession.flush_all()
+        assert M.Project.query.get(shortname='u/foobar123', deleted=False)
+
+    def test_user_project_does_not_create_on_demand_for_disabled_user(self):
+        u = M.User.register(
+            dict(username='foobar123', disabled=True), make_project=False)
+        ThreadLocalORMSession.flush_all()
+        assert not u.private_project()
+        assert not M.Project.query.get(shortname='u/foobar123')
+
+    def test_user_project_does_not_create_on_demand_for_anonymous_user(self):
+        u = M.User.anonymous()
+        ThreadLocalORMSession.flush_all()
+        assert not u.private_project()
+        assert not M.Project.query.get(shortname='u/anonymous')
+        assert not M.Project.query.get(shortname='u/*anonymous')
+
+    @patch('allura.model.auth.log')
+    def test_user_by_email_address(self, log):
+        u1 = M.User.register(dict(username='abc1'), make_project=False)
+        u2 = M.User.register(dict(username='abc2'), make_project=False)
+        addr1 = M.EmailAddress(email='abc123@abc.me', confirmed=True,
+                               claimed_by_user_id=u1._id)
+        addr2 = M.EmailAddress(email='abc123@abc.me', confirmed=True,
+                               claimed_by_user_id=u2._id)
+        # both users are disabled
+        u1.disabled, u2.disabled = True, True
+        ThreadLocalORMSession.flush_all()
+        assert M.User.by_email_address('abc123@abc.me') is None
+        assert log.warn.call_count == 0
+
+        # only u2 is active
+        u1.disabled, u2.disabled = True, False
+        ThreadLocalORMSession.flush_all()
+        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 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 M.User.by_email_address('nobody@example.com') == u1
+        assert M.User.by_email_address('invalid') is None
+
+    def test_user_equality(self):
+        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 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() is not None
+        assert M.User.anonymous() != 12345
+        assert M.User.anonymous() != M.User()
+
+    def test_user_hash(self):
+        assert M.User.by_username('test-user') in {M.User.by_username('test-user')}
+        assert M.User.anonymous() in {M.User.anonymous()}
+        assert M.User.by_username('*anonymous') in {M.User.anonymous()}
+
+        assert M.User.by_username('test-user') not in {M.User.by_username('test-admin')}
+        assert M.User.anonymous() not in {M.User.by_username('test-admin')}
+        assert M.User.anonymous() not in {0, None}
+
+    def test_project_role(self):
+        role = M.ProjectRole(project_id=c.project._id, name='test_role')
+        M.ProjectRole.by_user(c.user, upsert=True).roles.append(role._id)
+        ThreadLocalORMSession.flush_all()
+        roles = g.credentials.user_roles(
+            c.user._id, project_id=c.project.root_project._id)
+        roles_ids = [r['_id'] for r in roles]
+        roles = M.ProjectRole.query.find({'_id': {'$in': roles_ids}})
+        for pr in roles:
+            assert pr.display()
+            pr.special
+            assert pr.user in (c.user, None, M.User.anonymous())
+
+    def test_default_project_roles(self):
+        roles = {
+            pr.name: pr
+            for pr in M.ProjectRole.query.find(dict(
+                project_id=c.project._id)).all()
+            if pr.name}
+        assert 'Admin' in list(roles.keys()), list(roles.keys())
+        assert 'Developer' in list(roles.keys()), list(roles.keys())
+        assert 'Member' in list(roles.keys()), list(roles.keys())
+        assert roles['Developer']._id in roles['Admin'].roles
+        assert roles['Member']._id in roles['Developer'].roles
+
+        # There're 1 user assigned to project, represented by
+        # relational (vs named) ProjectRole's
+        assert len(roles) == M.ProjectRole.query.find(dict(
+            project_id=c.project._id)).count() - 1
+
+    def test_email_address_claimed_by_user(self):
+        addr = M.EmailAddress(email='test_admin@domain.net',
+                              claimed_by_user_id=c.user._id)
+        c.user.disabled = True
+        ThreadLocalORMSession.flush_all()
+        assert addr.claimed_by_user() is None
+
+    @td.with_user_project('test-admin')
+    def test_user_projects_by_role(self):
+        assert ({p.shortname for p in c.user.my_projects()} ==
+                {'test', 'test2', 'u/test-admin', 'adobe-1', '--init--'})
+        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')
+        admin_role = M.ProjectRole.by_name('Admin', project)
+        developer_role = M.ProjectRole.by_name('Developer', project)
+        user_role = M.ProjectRole.by_user(c.user, project=project, upsert=True)
+        user_role.roles.remove(admin_role._id)
+        user_role.roles.append(developer_role._id)
+        ThreadLocalORMSession.flush_all()
+        g.credentials.clear()
+        assert ({p.shortname for p in c.user.my_projects()} ==
+                {'test', 'test2', 'u/test-admin', 'adobe-1', '--init--'})
+        assert ({p.shortname for p in c.user.my_projects_by_role_name('Admin')} ==
+                {'test', 'u/test-admin', 'adobe-1', '--init--'})
+
+    @td.with_user_project('test-admin')
+    def test_user_projects_unnamed(self):
+        """
+        Confirm that spurious ProjectRoles associating a user with
+        a project to which they do not belong to any named group
+        don't cause the user to count as a member of the project.
+        """
+        sub1 = M.Project.query.get(shortname='test/sub1')
+        M.ProjectRole(
+            user_id=c.user._id,
+            project_id=sub1._id)
+        ThreadLocalORMSession.flush_all()
+        project_names = [p.shortname for p in c.user.my_projects()]
+        assert 'test/sub1' not in project_names
+        assert 'test' in project_names
+
+    @patch.object(g, 'user_message_max_messages', 3)
+    def test_check_sent_user_message_times(self):
+        user1 = M.User.register(dict(username='test-user'), make_project=False)
+        time1 = datetime.utcnow() - timedelta(minutes=30)
+        time2 = datetime.utcnow() - timedelta(minutes=45)
+        time3 = datetime.utcnow() - timedelta(minutes=70)
+        user1.sent_user_message_times = [time1, time2, time3]
+        assert user1.can_send_user_message()
+        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()
+
+    @td.with_user_project('test-admin')
+    def test_user_track_active(self):
+        # without this session flushing inside track_active raises Exception
+        setup_functional_test()
+        c.user = M.User.by_username('test-admin')
+
+        assert c.user.last_access['session_date'] is None
+        assert c.user.last_access['session_ip'] is None
+        assert c.user.last_access['session_ua'] is 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 c.user.last_access['session_date'] is not 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 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 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 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 c.user.last_access['session_ip'] == 'new addr'
+        assert c.user.last_access['session_ua'] == 'new browser'
+
+    def test_user_index(self):
+        c.user.email_addresses = ['email1', 'email2']
+        c.user.set_pref('email_address', 'email2')
+        idx = c.user.index()
+        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'] is 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 'user_registration_date_dt' in idx
+
+    def test_user_index_none_values(self):
+        c.user.email_addresses = [None]
+        c.user.set_pref('telnumbers', [None])
+        c.user.set_pref('webpages', [None])
+        idx = c.user.index()
+        assert idx['email_addresses_t'] == ''
+        assert idx['telnumbers_t'] == ''
+        assert idx['webpages_t'] == ''
+
+    def test_user_backfill_login_details(self):
+        with h.push_config(r, user_agent='TestBrowser/55'):
+            # these shouldn't match
+            h.auditlog_user('something happened')
+            h.auditlog_user('blah blah Password changed')
+        with h.push_config(r, user_agent='TestBrowser/56'):
+            # these should all match, but only one entry created for this ip/ua
+            h.auditlog_user('Account activated')
+            h.auditlog_user('Successful login')
+            h.auditlog_user('Password changed')
+        with h.push_config(r, user_agent='TestBrowser/57'):
+            # this should match too
+            h.auditlog_user('Set up multifactor TOTP')
+        ThreadLocalORMSession.flush_all()
+
+        auth_provider = plugin.AuthenticationProvider.get(None)
+        c.user.backfill_login_details(auth_provider)
+
+        details = M.UserLoginDetails.query.find({'user_id': c.user._id}).sort('ua').all()
+        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'
 
 
 @with_nose_compatibility
 class TestAuditLog:
 
+    @classmethod
+    def setup_class(cls):
+        setup_basic_test()
+        setup_global_objects()
+
     def test_message_html(self):
         al = h.auditlog_user('our message <script>alert(1)</script>')
         assert al.message == textwrap.dedent('''\
diff --git a/Allura/allura/tests/model/test_discussion.py b/Allura/allura/tests/model/test_discussion.py
index d5abf1a05..3c158cd3f 100644
--- a/Allura/allura/tests/model/test_discussion.py
+++ b/Allura/allura/tests/model/test_discussion.py
@@ -24,10 +24,8 @@ from datetime import datetime, timedelta
 from cgi import FieldStorage
 
 from tg import tmpl_context as c
-from alluratest.tools import assert_equals, with_setup
 import mock
 from mock import patch
-from alluratest.tools import assert_equal, assert_in
 
 from ming.orm import session, ThreadLocalORMSession
 from webob import exc
@@ -38,513 +36,469 @@ from allura.tests import TestController
 from alluratest.controller import setup_global_objects
 
 
-def setup_method():
-    controller = TestController()
-    controller.setup_method(None)
-    controller.app.get('/wiki/Home/')
-    setup_global_objects()
-    ThreadLocalORMSession.close_all()
-    h.set_context('test', 'wiki', neighborhood='Projects')
-    ThreadLocalORMSession.flush_all()
-    ThreadLocalORMSession.close_all()
-
-
-def teardown_module():
-    ThreadLocalORMSession.close_all()
-
-
-@with_setup(setup_method)
-def test_discussion_methods():
-    d = M.Discussion(shortname='test', name='test')
-    assert d.thread_class() == M.Thread
-    assert d.post_class() == M.Post
-    assert d.attachment_class() == M.DiscussionAttachment
-    ThreadLocalORMSession.flush_all()
-    d.update_stats()
-    ThreadLocalORMSession.flush_all()
-    assert d.last_post is None
-    assert d.url().endswith('wiki/_discuss/')
-    assert d.index()['name_s'] == 'test'
-    assert d.find_posts().count() == 0
-    jsn = d.__json__()
-    assert jsn['name'] == d.name
-    d.delete()
-    ThreadLocalORMSession.flush_all()
-    ThreadLocalORMSession.close_all()
-
-
-@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')
-    assert t.discussion_class() == M.Discussion
-    assert t.post_class() == M.Post
-    assert t.attachment_class() == M.DiscussionAttachment
-    p0 = t.post('This is a post')
-    p1 = t.post('This is another post')
-    time.sleep(0.25)
-    t.post('This is a reply', parent_id=p0._id)
-    ThreadLocalORMSession.flush_all()
-    ThreadLocalORMSession.close_all()
-    d = M.Discussion.query.get(shortname='test')
-    t = d.threads[0]
-    assert d.last_post is not None
-    assert t.last_post is not None
-    t.create_post_threads(t.posts)
-    posts0 = t.find_posts(page=0, limit=10, style='threaded')
-    posts1 = t.find_posts(page=0, limit=10, style='timestamp')
-    assert posts0 != posts1
-    ts = p0.timestamp.replace(
-        microsecond=int(p0.timestamp.microsecond // 1000) * 1000)
-    posts2 = t.find_posts(page=0, limit=10, style='threaded', timestamp=ts)
-    assert len(posts2) > 0
-
-    assert 'wiki/_discuss/' in t.url()
-    assert t.index()['views_i'] == 0
-    assert t.post_count == 3
-    jsn = t.__json__()
-    assert '_id' in jsn
-    assert len(jsn['posts']) == 3
-    (p.approve() for p in (p0, p1))
-    ThreadLocalORMSession.flush_all()
-    assert t.num_replies == 3
-    t.spam()
-    assert t.num_replies == 0
-    ThreadLocalORMSession.flush_all()
-    assert len(t.find_posts()) == 0
-    t.delete()
-
-
-@with_setup(setup_method)
-def test_thread_new():
-    with mock.patch('allura.model.discuss.h.nonce') as nonce:
-        nonce.side_effect = ['deadbeef', 'deadbeef', 'beefdead']
+class TestDiscussion:
+
+    def setup_method(self):
+        controller = TestController()
+        controller.setup_method(None)
+        controller.app.get('/wiki/Home/')
+        setup_global_objects()
+        ThreadLocalORMSession.close_all()
+        h.set_context('test', 'wiki', neighborhood='Projects')
+        ThreadLocalORMSession.flush_all()
+        ThreadLocalORMSession.close_all()
+
+    @classmethod
+    def teardown_class(cls):
+        ThreadLocalORMSession.close_all()
+
+    def test_discussion_methods(self):
         d = M.Discussion(shortname='test', name='test')
-        t1 = M.Thread.new(discussion_id=d._id, subject='Test Thread One')
-        t2 = M.Thread.new(discussion_id=d._id, subject='Test Thread Two')
+        assert d.thread_class() == M.Thread
+        assert d.post_class() == M.Post
+        assert d.attachment_class() == M.DiscussionAttachment
         ThreadLocalORMSession.flush_all()
-        session(t1).expunge(t1)
-        session(t2).expunge(t2)
-        t1_2 = M.Thread.query.get(_id=t1._id)
-        t2_2 = M.Thread.query.get(_id=t2._id)
-        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_method)
-def test_post_methods():
-    d = M.Discussion(shortname='test', name='test')
-    t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
-    p = t.post('This is a post')
-    p2 = t.post('This is another post')
-    assert p.discussion_class() == M.Discussion
-    assert p.thread_class() == M.Thread
-    assert p.attachment_class() == M.DiscussionAttachment
-    p.commit()
-    assert p.parent is None
-    assert p.subject == 'Test Thread'
-    assert p.attachments == []
-    assert 'wiki/_discuss' in p.url()
-    assert p.reply_subject() == 'Re: Test Thread'
-    assert p.link_text() == p.subject
-
-    ss = p.history().first()
-    assert 'version' in h.get_first(ss.index(), 'title')
-    assert '#' in ss.shorthand_id()
-
-    jsn = p.__json__()
-    assert jsn["thread_id"] == t._id
-
-    (p.approve() for p in (p, p2))
-    ThreadLocalORMSession.flush_all()
-    assert t.num_replies == 2
-    p.spam()
-    assert t.num_replies == 1
-    p.undo('ok')
-    assert t.num_replies == 2
-    p.delete()
-    assert t.num_replies == 1
-
-
-@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')
-    p = t.post('This is a post')
-    p_att = p.attach('foo.text', BytesIO(b'Hello, world!'),
-                     discussion_id=d._id,
-                     thread_id=t._id,
-                     post_id=p._id)
-    t_att = p.attach('foo2.text', BytesIO(b'Hello, thread!'),
-                     discussion_id=d._id,
-                     thread_id=t._id)
-    d_att = p.attach('foo3.text', BytesIO(b'Hello, discussion!'),
-                     discussion_id=d._id)
-
-    ThreadLocalORMSession.flush_all()
-    assert p_att.post == p
-    assert p_att.thread == t
-    assert p_att.discussion == d
-    for att in (p_att, t_att, d_att):
-        assert 'wiki/_discuss' in att.url()
-        assert 'attachment/' in att.url()
-
-    # Test notification in mail
-    t = M.Thread.new(discussion_id=d._id, subject='Test comment notification')
-    fs = FieldStorage()
-    fs.name = 'file_info'
-    fs.filename = 'fake.txt'
-    fs.type = 'text/plain'
-    fs.file = BytesIO(b'this is the content of the fake file\n')
-    p = t.post(text='test message', forum=None, subject='', file_info=fs)
-    ThreadLocalORMSession.flush_all()
-    n = M.Notification.query.get(
-        subject='[test:wiki] Test comment notification')
-    url = h.absurl(f'{p.url()}attachment/{fs.filename}')
-    assert (
-        '\nAttachments:\n\n'
-        '- [fake.txt]({}) (37 Bytes; text/plain)'.format(url) in
-        n.text)
-
-
-@with_setup(setup_method)
-def test_multiple_attachments():
-    test_file1 = FieldStorage()
-    test_file1.name = 'file_info'
-    test_file1.filename = 'test1.txt'
-    test_file1.type = 'text/plain'
-    test_file1.file = BytesIO(b'test file1\n')
-    test_file2 = FieldStorage()
-    test_file2.name = 'file_info'
-    test_file2.filename = 'test2.txt'
-    test_file2.type = 'text/plain'
-    test_file2.file = BytesIO(b'test file2\n')
-    d = M.Discussion(shortname='test', name='test')
-    t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
-    test_post = t.post('test post')
-    test_post.add_multiple_attachments([test_file1, test_file2])
-    ThreadLocalORMSession.flush_all()
-    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]
-
-
-@with_setup(setup_method)
-def test_add_attachment():
-    test_file = FieldStorage()
-    test_file.name = 'file_info'
-    test_file.filename = 'test.txt'
-    test_file.type = 'text/plain'
-    test_file.file = BytesIO(b'test file\n')
-    d = M.Discussion(shortname='test', name='test')
-    t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
-    test_post = t.post('test post')
-    test_post.add_attachment(test_file)
-    ThreadLocalORMSession.flush_all()
-    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
-
-
-def test_notification_two_attaches():
-    d = M.Discussion(shortname='test', name='test')
-    t = M.Thread.new(discussion_id=d._id, subject='Test comment notification')
-    fs1 = FieldStorage()
-    fs1.name = 'file_info'
-    fs1.filename = 'fake.txt'
-    fs1.type = 'text/plain'
-    fs1.file = BytesIO(b'this is the content of the fake file\n')
-    fs2 = FieldStorage()
-    fs2.name = 'file_info'
-    fs2.filename = 'fake2.txt'
-    fs2.type = 'text/plain'
-    fs2.file = BytesIO(b'this is the content of the fake file\n')
-    p = t.post(text='test message', forum=None, subject='', file_info=[fs1, fs2])
-    ThreadLocalORMSession.flush_all()
-    n = M.Notification.query.get(
-        subject='[test:wiki] Test comment notification')
-    base_url = h.absurl(f'{p.url()}attachment/')
-    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) in
-        n.text)
-
-
-@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')
-    p = t.post('This is a post')
-    p.attach('foo.text', BytesIO(b''),
-             discussion_id=d._id,
-             thread_id=t._id,
-             post_id=p._id)
-    M.ArtifactReference.from_artifact(d)
-    rid = d.index_id()
-    ThreadLocalORMSession.flush_all()
-    d.delete()
-    ThreadLocalORMSession.flush_all()
-    assert M.ArtifactReference.query.find(dict(_id=rid)).count() == 0
-
-
-@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')
-    p = t.post('This is a post')
-    p.attach('foo.text', BytesIO(b''),
-             discussion_id=d._id,
-             thread_id=t._id,
-             post_id=p._id)
-    ThreadLocalORMSession.flush_all()
-    t.delete()
-
-
-@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')
-    p = t.post('This is a post')
-    p.attach('foo.text', BytesIO(b''),
-             discussion_id=d._id,
-             thread_id=t._id,
-             post_id=p._id)
-    ThreadLocalORMSession.flush_all()
-    p.delete()
-
-
-@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')
-    p = t.post('This is a post')
-    t.post('This is a post2')
-    t.post('This is a post3')
-    ThreadLocalORMSession.flush_all()
-    assert t.num_replies == 3
-    p.spam()
-    assert t.num_replies == 2
-    p.undo('ok')
-    assert t.num_replies == 3
-
-
-@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')
-    c.user = M.User.anonymous()
-    try:
-        t.post('This post will fail the check.')
-        assert False, "Expected an anonymous post to fail."
-    except exc.HTTPUnauthorized:
-        pass
-    t.post('This post will pass the check.', ignore_security=True)
-
-
-@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')
-    p = []  # posts in display order
-    ts = datetime.utcnow() - timedelta(days=1)
-    for i in range(5):
-        ts += timedelta(minutes=1)
-        p.append(t.post('This is a post #%s' % i, timestamp=ts))
-
-    ts += timedelta(minutes=1)
-    p.insert(1, t.post(
-        'This is reply #0 to post #0', parent_id=p[0]._id, timestamp=ts))
-
-    ts += timedelta(minutes=1)
-    p.insert(2, t.post(
-        'This is reply #1 to post #0', parent_id=p[0]._id, timestamp=ts))
-
-    ts += timedelta(minutes=1)
-    p.insert(4, t.post(
-        'This is reply #0 to post #1', parent_id=p[3]._id, timestamp=ts))
-
-    ts += timedelta(minutes=1)
-    p.insert(6, t.post(
-        'This is reply #0 to post #2', parent_id=p[5]._id, timestamp=ts))
-
-    ts += timedelta(minutes=1)
-    p.insert(7, t.post(
-        'This is reply #1 to post #2', parent_id=p[5]._id, timestamp=ts))
-
-    ts += timedelta(minutes=1)
-    p.insert(8, t.post(
-        'This is reply #0 to reply #1 to post #2',
-        parent_id=p[7]._id, timestamp=ts))
-
-    # with default paging limit
-    for _p in p:
-        url = t.url() + '?limit=25#' + _p.slug
-        assert _p.url_paginated() == url, _p.url_paginated()
-
-    # with user paging limit
-    limit = 3
-    c.user.set_pref('results_per_page', limit)
-    for i, _p in enumerate(p):
-        page = i // limit
-        url = t.url() + '?limit=%s' % limit
-        if page > 0:
-            url += '&page=%s' % page
-        url += '#' + _p.slug
-        assert _p.url_paginated() == url
-
-
-@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
-    page = Page.upsert(title='Test Page')
-    thread = page.discussion_thread
-    comment = thread.post('Comment')
-    url = page.url() + '?limit=25#' + comment.slug
-    assert comment.url_paginated() == url
-
-
-@with_setup(setup_method)
-def test_post_notify():
-    d = M.Discussion(shortname='test', name='test')
-    d.monitoring_email = 'darthvader@deathstar.org'
-    t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
-    with patch('allura.model.notification.Notification.send_simple') as send:
-        t.post('This is a post')
-        send.assert_called_with(d.monitoring_email)
+        d.update_stats()
+        ThreadLocalORMSession.flush_all()
+        assert d.last_post is None
+        assert d.url().endswith('wiki/_discuss/')
+        assert d.index()['name_s'] == 'test'
+        assert d.find_posts().count() == 0
+        jsn = d.__json__()
+        assert jsn['name'] == d.name
+        d.delete()
+        ThreadLocalORMSession.flush_all()
+        ThreadLocalORMSession.close_all()
+
+    def test_thread_methods(self):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
+        assert t.discussion_class() == M.Discussion
+        assert t.post_class() == M.Post
+        assert t.attachment_class() == M.DiscussionAttachment
+        p0 = t.post('This is a post')
+        p1 = t.post('This is another post')
+        time.sleep(0.25)
+        t.post('This is a reply', parent_id=p0._id)
+        ThreadLocalORMSession.flush_all()
+        ThreadLocalORMSession.close_all()
+        d = M.Discussion.query.get(shortname='test')
+        t = d.threads[0]
+        assert d.last_post is not None
+        assert t.last_post is not None
+        t.create_post_threads(t.posts)
+        posts0 = t.find_posts(page=0, limit=10, style='threaded')
+        posts1 = t.find_posts(page=0, limit=10, style='timestamp')
+        assert posts0 != posts1
+        ts = p0.timestamp.replace(
+            microsecond=int(p0.timestamp.microsecond // 1000) * 1000)
+        posts2 = t.find_posts(page=0, limit=10, style='threaded', timestamp=ts)
+        assert len(posts2) > 0
+
+        assert 'wiki/_discuss/' in t.url()
+        assert t.index()['views_i'] == 0
+        assert t.post_count == 3
+        jsn = t.__json__()
+        assert '_id' in jsn
+        assert len(jsn['posts']) == 3
+        (p.approve() for p in (p0, p1))
+        ThreadLocalORMSession.flush_all()
+        assert t.num_replies == 3
+        t.spam()
+        assert t.num_replies == 0
+        ThreadLocalORMSession.flush_all()
+        assert len(t.find_posts()) == 0
+        t.delete()
+
+    def test_thread_new(self):
+        with mock.patch('allura.model.discuss.h.nonce') as nonce:
+            nonce.side_effect = ['deadbeef', 'deadbeef', 'beefdead']
+            d = M.Discussion(shortname='test', name='test')
+            t1 = M.Thread.new(discussion_id=d._id, subject='Test Thread One')
+            t2 = M.Thread.new(discussion_id=d._id, subject='Test Thread Two')
+            ThreadLocalORMSession.flush_all()
+            session(t1).expunge(t1)
+            session(t2).expunge(t2)
+            t1_2 = M.Thread.query.get(_id=t1._id)
+            t2_2 = M.Thread.query.get(_id=t2._id)
+            assert t1._id == 'deadbeef'
+            assert t2._id == 'beefdead'
+            assert t1_2.subject == 'Test Thread One'
+            assert t2_2.subject == 'Test Thread Two'
+
+    def test_post_methods(self):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
+        p = t.post('This is a post')
+        p2 = t.post('This is another post')
+        assert p.discussion_class() == M.Discussion
+        assert p.thread_class() == M.Thread
+        assert p.attachment_class() == M.DiscussionAttachment
+        p.commit()
+        assert p.parent is None
+        assert p.subject == 'Test Thread'
+        assert p.attachments == []
+        assert 'wiki/_discuss' in p.url()
+        assert p.reply_subject() == 'Re: Test Thread'
+        assert p.link_text() == p.subject
+
+        ss = p.history().first()
+        assert 'version' in h.get_first(ss.index(), 'title')
+        assert '#' in ss.shorthand_id()
+
+        jsn = p.__json__()
+        assert jsn["thread_id"] == t._id
+
+        (p.approve() for p in (p, p2))
+        ThreadLocalORMSession.flush_all()
+        assert t.num_replies == 2
+        p.spam()
+        assert t.num_replies == 1
+        p.undo('ok')
+        assert t.num_replies == 2
+        p.delete()
+        assert t.num_replies == 1
+
+    def test_attachment_methods(self):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
+        p = t.post('This is a post')
+        p_att = p.attach('foo.text', BytesIO(b'Hello, world!'),
+                         discussion_id=d._id,
+                         thread_id=t._id,
+                         post_id=p._id)
+        t_att = p.attach('foo2.text', BytesIO(b'Hello, thread!'),
+                         discussion_id=d._id,
+                         thread_id=t._id)
+        d_att = p.attach('foo3.text', BytesIO(b'Hello, discussion!'),
+                         discussion_id=d._id)
+
+        ThreadLocalORMSession.flush_all()
+        assert p_att.post == p
+        assert p_att.thread == t
+        assert p_att.discussion == d
+        for att in (p_att, t_att, d_att):
+            assert 'wiki/_discuss' in att.url()
+            assert 'attachment/' in att.url()
+
+        # Test notification in mail
+        t = M.Thread.new(discussion_id=d._id, subject='Test comment notification')
+        fs = FieldStorage()
+        fs.name = 'file_info'
+        fs.filename = 'fake.txt'
+        fs.type = 'text/plain'
+        fs.file = BytesIO(b'this is the content of the fake file\n')
+        p = t.post(text='test message', forum=None, subject='', file_info=fs)
+        ThreadLocalORMSession.flush_all()
+        n = M.Notification.query.get(
+            subject='[test:wiki] Test comment notification')
+        url = h.absurl(f'{p.url()}attachment/{fs.filename}')
+        assert (
+            '\nAttachments:\n\n'
+            '- [fake.txt]({}) (37 Bytes; text/plain)'.format(url) in
+            n.text)
+
+    def test_multiple_attachments(self):
+        test_file1 = FieldStorage()
+        test_file1.name = 'file_info'
+        test_file1.filename = 'test1.txt'
+        test_file1.type = 'text/plain'
+        test_file1.file = BytesIO(b'test file1\n')
+        test_file2 = FieldStorage()
+        test_file2.name = 'file_info'
+        test_file2.filename = 'test2.txt'
+        test_file2.type = 'text/plain'
+        test_file2.file = BytesIO(b'test file2\n')
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
+        test_post = t.post('test post')
+        test_post.add_multiple_attachments([test_file1, test_file2])
+        ThreadLocalORMSession.flush_all()
+        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]
+
+    def test_add_attachment(self):
+        test_file = FieldStorage()
+        test_file.name = 'file_info'
+        test_file.filename = 'test.txt'
+        test_file.type = 'text/plain'
+        test_file.file = BytesIO(b'test file\n')
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
+        test_post = t.post('test post')
+        test_post.add_attachment(test_file)
+        ThreadLocalORMSession.flush_all()
+        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
+
+    def test_notification_two_attaches(self):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread.new(discussion_id=d._id, subject='Test comment notification')
+        fs1 = FieldStorage()
+        fs1.name = 'file_info'
+        fs1.filename = 'fake.txt'
+        fs1.type = 'text/plain'
+        fs1.file = BytesIO(b'this is the content of the fake file\n')
+        fs2 = FieldStorage()
+        fs2.name = 'file_info'
+        fs2.filename = 'fake2.txt'
+        fs2.type = 'text/plain'
+        fs2.file = BytesIO(b'this is the content of the fake file\n')
+        p = t.post(text='test message', forum=None, subject='', file_info=[fs1, fs2])
+        ThreadLocalORMSession.flush_all()
+        n = M.Notification.query.get(
+            subject='[test:wiki] Test comment notification')
+        base_url = h.absurl(f'{p.url()}attachment/')
+        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) in
+            n.text)
+
+    def test_discussion_delete(self):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
+        p = t.post('This is a post')
+        p.attach('foo.text', BytesIO(b''),
+                 discussion_id=d._id,
+                 thread_id=t._id,
+                 post_id=p._id)
+        M.ArtifactReference.from_artifact(d)
+        rid = d.index_id()
+        ThreadLocalORMSession.flush_all()
+        d.delete()
+        ThreadLocalORMSession.flush_all()
+        assert M.ArtifactReference.query.find(dict(_id=rid)).count() == 0
+
+    def test_thread_delete(self):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
+        p = t.post('This is a post')
+        p.attach('foo.text', BytesIO(b''),
+                 discussion_id=d._id,
+                 thread_id=t._id,
+                 post_id=p._id)
+        ThreadLocalORMSession.flush_all()
+        t.delete()
+
+    def test_post_delete(self):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
+        p = t.post('This is a post')
+        p.attach('foo.text', BytesIO(b''),
+                 discussion_id=d._id,
+                 thread_id=t._id,
+                 post_id=p._id)
+        ThreadLocalORMSession.flush_all()
+        p.delete()
+
+    def test_post_undo(self):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
+        p = t.post('This is a post')
+        t.post('This is a post2')
+        t.post('This is a post3')
+        ThreadLocalORMSession.flush_all()
+        assert t.num_replies == 3
+        p.spam()
+        assert t.num_replies == 2
+        p.undo('ok')
+        assert t.num_replies == 3
 
-    c.app.config.project.notifications_disabled = True
-    with patch('allura.model.notification.Notification.send_simple') as send:
-        t.post('Another post')
+    def test_post_permission_check(self):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
+        c.user = M.User.anonymous()
         try:
+            t.post('This post will fail the check.')
+            assert False, "Expected an anonymous post to fail."
+        except exc.HTTPUnauthorized:
+            pass
+        t.post('This post will pass the check.', ignore_security=True)
+
+    def test_post_url_paginated(self):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread(discussion_id=d._id, subject='Test Thread')
+        p = []  # posts in display order
+        ts = datetime.utcnow() - timedelta(days=1)
+        for i in range(5):
+            ts += timedelta(minutes=1)
+            p.append(t.post('This is a post #%s' % i, timestamp=ts))
+
+        ts += timedelta(minutes=1)
+        p.insert(1, t.post(
+            'This is reply #0 to post #0', parent_id=p[0]._id, timestamp=ts))
+
+        ts += timedelta(minutes=1)
+        p.insert(2, t.post(
+            'This is reply #1 to post #0', parent_id=p[0]._id, timestamp=ts))
+
+        ts += timedelta(minutes=1)
+        p.insert(4, t.post(
+            'This is reply #0 to post #1', parent_id=p[3]._id, timestamp=ts))
+
+        ts += timedelta(minutes=1)
+        p.insert(6, t.post(
+            'This is reply #0 to post #2', parent_id=p[5]._id, timestamp=ts))
+
+        ts += timedelta(minutes=1)
+        p.insert(7, t.post(
+            'This is reply #1 to post #2', parent_id=p[5]._id, timestamp=ts))
+
+        ts += timedelta(minutes=1)
+        p.insert(8, t.post(
+            'This is reply #0 to reply #1 to post #2',
+            parent_id=p[7]._id, timestamp=ts))
+
+        # with default paging limit
+        for _p in p:
+            url = t.url() + '?limit=25#' + _p.slug
+            assert _p.url_paginated() == url, _p.url_paginated()
+
+        # with user paging limit
+        limit = 3
+        c.user.set_pref('results_per_page', limit)
+        for i, _p in enumerate(p):
+            page = i // limit
+            url = t.url() + '?limit=%s' % limit
+            if page > 0:
+                url += '&page=%s' % page
+            url += '#' + _p.slug
+            assert _p.url_paginated() == url
+
+    def test_post_url_paginated_with_artifact(self):
+        """Post.url_paginated should return link to attached artifact, if any"""
+        from forgewiki.model import Page
+        page = Page.upsert(title='Test Page')
+        thread = page.discussion_thread
+        comment = thread.post('Comment')
+        url = page.url() + '?limit=25#' + comment.slug
+        assert comment.url_paginated() == url
+
+    def test_post_notify(self):
+        d = M.Discussion(shortname='test', name='test')
+        d.monitoring_email = 'darthvader@deathstar.org'
+        t = M.Thread.new(discussion_id=d._id, subject='Test Thread')
+        with patch('allura.model.notification.Notification.send_simple') as send:
+            t.post('This is a post')
             send.assert_called_with(d.monitoring_email)
-        except AssertionError:
-            pass  # method not called as expected
-        else:
-            assert False, 'send_simple must not be called'
-
-
-@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, ]
-    d = M.Discussion(shortname='test', name='test')
-    t = M.Thread(discussion_id=d._id, subject='Test Thread')
-    t.post('This is a post')
-    post = M.Post.query.get(text='This is a post')
-    assert not t.is_spam(post), t.is_spam(post)
-
-
-@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')
-    t = M.Thread(discussion_id=d._id, subject='Test Thread')
-    role.return_value = []
-    with mock.patch('allura.controllers.discuss.g.spam_checker') as spam_checker:
+
+        c.app.config.project.notifications_disabled = True
+        with patch('allura.model.notification.Notification.send_simple') as send:
+            t.post('Another post')
+            try:
+                send.assert_called_with(d.monitoring_email)
+            except AssertionError:
+                pass  # method not called as expected
+            else:
+                assert False, 'send_simple must not be called'
+
+    @patch('allura.model.discuss.c.project.users_with_role')
+    def test_is_spam_for_admin(self, users):
+        users.return_value = [c.user, ]
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread(discussion_id=d._id, subject='Test Thread')
+        t.post('This is a post')
+        post = M.Post.query.get(text='This is a post')
+        assert not t.is_spam(post), t.is_spam(post)
+
+    @patch('allura.model.discuss.c.project.users_with_role')
+    def test_is_spam(self, role):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread(discussion_id=d._id, subject='Test Thread')
+        role.return_value = []
+        with mock.patch('allura.controllers.discuss.g.spam_checker') as spam_checker:
+            spam_checker.check.return_value = True
+            post = mock.Mock()
+            assert t.is_spam(post), t.is_spam(post)
+            assert spam_checker.check.call_count == 1, spam_checker.call_count
+
+    @mock.patch('allura.controllers.discuss.g.spam_checker')
+    def test_not_spam_and_has_unmoderated_post_permission(self, spam_checker):
+        spam_checker.check.return_value = False
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread(discussion_id=d._id, subject='Test Thread')
+        role = M.ProjectRole.by_name('*anonymous')._id
+        post_permission = M.ACE.allow(role, 'post')
+        unmoderated_post_permission = M.ACE.allow(role, 'unmoderated_post')
+        t.acl.append(post_permission)
+        t.acl.append(unmoderated_post_permission)
+        with h.push_config(c, user=M.User.anonymous()):
+            post = t.post('Hey')
+        assert post.status == 'ok'
+
+    @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(self, notify_moderators, spam_checker):
+        spam_checker.check.return_value = False
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread(discussion_id=d._id, subject='Test Thread')
+        role = M.ProjectRole.by_name('*anonymous')._id
+        post_permission = M.ACE.allow(role, 'post')
+        t.acl.append(post_permission)
+        with h.push_config(c, user=M.User.anonymous()):
+            post = t.post('Hey')
+        assert post.status == 'pending'
+        assert notify_moderators.call_count == 1
+
+    @mock.patch('allura.controllers.discuss.g.spam_checker')
+    @mock.patch.object(M.Thread, 'notify_moderators')
+    def test_spam_and_has_unmoderated_post_permission(self, notify_moderators, spam_checker):
         spam_checker.check.return_value = True
-        post = mock.Mock()
-        assert t.is_spam(post), t.is_spam(post)
-        assert spam_checker.check.call_count == 1, spam_checker.call_count
-
-
-@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
-    d = M.Discussion(shortname='test', name='test')
-    t = M.Thread(discussion_id=d._id, subject='Test Thread')
-    role = M.ProjectRole.by_name('*anonymous')._id
-    post_permission = M.ACE.allow(role, 'post')
-    unmoderated_post_permission = M.ACE.allow(role, 'unmoderated_post')
-    t.acl.append(post_permission)
-    t.acl.append(unmoderated_post_permission)
-    with h.push_config(c, user=M.User.anonymous()):
-        post = t.post('Hey')
-    assert post.status == 'ok'
-
-
-@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):
-    spam_checker.check.return_value = False
-    d = M.Discussion(shortname='test', name='test')
-    t = M.Thread(discussion_id=d._id, subject='Test Thread')
-    role = M.ProjectRole.by_name('*anonymous')._id
-    post_permission = M.ACE.allow(role, 'post')
-    t.acl.append(post_permission)
-    with h.push_config(c, user=M.User.anonymous()):
-        post = t.post('Hey')
-    assert post.status == 'pending'
-    assert notify_moderators.call_count == 1
-
-
-@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):
-    spam_checker.check.return_value = True
-    d = M.Discussion(shortname='test', name='test')
-    t = M.Thread(discussion_id=d._id, subject='Test Thread')
-    role = M.ProjectRole.by_name('*anonymous')._id
-    post_permission = M.ACE.allow(role, 'post')
-    unmoderated_post_permission = M.ACE.allow(role, 'unmoderated_post')
-    t.acl.append(post_permission)
-    t.acl.append(unmoderated_post_permission)
-    with h.push_config(c, user=M.User.anonymous()):
-        post = t.post('Hey')
-    assert post.status == 'pending'
-    assert notify_moderators.call_count == 1
-
-
-@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
-    d = M.Discussion(shortname='test', name='test')
-    t = M.Thread(discussion_id=d._id, subject='Test Thread')
-    t.post('Hello')
-    assert spam_checker.check.call_count == 1
-    assert spam_checker.check.call_args[0][0] == 'Hello'
-
-
-def test_post_count():
-    d = M.Discussion(shortname='test', name='test')
-    t = M.Thread(discussion_id=d._id, subject='Test Thread')
-    M.Post(discussion_id=d._id, thread_id=t._id, status='spam')
-    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 t.post_count == 2
-
-
-@mock.patch('allura.controllers.discuss.g.spam_checker')
-def test_spam_num_replies(spam_checker):
-    d = M.Discussion(shortname='test', name='test')
-    t = M.Thread(discussion_id=d._id, subject='Test Thread', num_replies=2)
-    M.Post(discussion_id=d._id, thread_id=t._id, status='ok')
-    ThreadLocalORMSession.flush_all()
-    p1 = M.Post(discussion_id=d._id, thread_id=t._id, status='spam')
-    p1.spam()
-    assert t.num_replies == 1
-
-
-def test_deleted_thread_index():
-    d = M.Discussion(shortname='test', name='test')
-    t = M.Thread(discussion_id=d._id, subject='Test Thread')
-    p = M.Post(discussion_id=d._id, thread_id=t._id, status='ok')
-    t.delete()
-    ThreadLocalORMSession.flush_all()
-
-    # re-query, so relationships get reloaded
-    ThreadLocalORMSession.close_all()
-    p = M.Post.query.get(_id=p._id)
-
-    # just make sure this doesn't fail
-    p.index()
\ No newline at end of file
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread(discussion_id=d._id, subject='Test Thread')
+        role = M.ProjectRole.by_name('*anonymous')._id
+        post_permission = M.ACE.allow(role, 'post')
+        unmoderated_post_permission = M.ACE.allow(role, 'unmoderated_post')
+        t.acl.append(post_permission)
+        t.acl.append(unmoderated_post_permission)
+        with h.push_config(c, user=M.User.anonymous()):
+            post = t.post('Hey')
+        assert post.status == 'pending'
+        assert notify_moderators.call_count == 1
+
+    @mock.patch('allura.controllers.discuss.g.spam_checker')
+    def test_thread_subject_not_included_in_text_checked(self, spam_checker):
+        spam_checker.check.return_value = False
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread(discussion_id=d._id, subject='Test Thread')
+        t.post('Hello')
+        assert spam_checker.check.call_count == 1
+        assert spam_checker.check.call_args[0][0] == 'Hello'
+
+    def test_post_count(self):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread(discussion_id=d._id, subject='Test Thread')
+        M.Post(discussion_id=d._id, thread_id=t._id, status='spam')
+        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 t.post_count == 2
+
+    @mock.patch('allura.controllers.discuss.g.spam_checker')
+    def test_spam_num_replies(self, spam_checker):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread(discussion_id=d._id, subject='Test Thread', num_replies=2)
+        M.Post(discussion_id=d._id, thread_id=t._id, status='ok')
+        ThreadLocalORMSession.flush_all()
+        p1 = M.Post(discussion_id=d._id, thread_id=t._id, status='spam')
+        p1.spam()
+        assert t.num_replies == 1
+
+    def test_deleted_thread_index(self):
+        d = M.Discussion(shortname='test', name='test')
+        t = M.Thread(discussion_id=d._id, subject='Test Thread')
+        p = M.Post(discussion_id=d._id, thread_id=t._id, status='ok')
+        t.delete()
+        ThreadLocalORMSession.flush_all()
+
+        # re-query, so relationships get reloaded
+        ThreadLocalORMSession.close_all()
+        p = M.Post.query.get(_id=p._id)
+
+        # just make sure this doesn't fail
+        p.index()
diff --git a/Allura/allura/tests/model/test_monq.py b/Allura/allura/tests/model/test_monq.py
index 9dc682149..9b2d51754 100644
--- a/Allura/allura/tests/model/test_monq.py
+++ b/Allura/allura/tests/model/test_monq.py
@@ -16,7 +16,6 @@
 #       under the License.
 
 import pprint
-from alluratest.tools import with_setup
 
 from ming.orm import ThreadLocalORMSession
 
@@ -24,14 +23,13 @@ from alluratest.controller import setup_basic_test, setup_global_objects
 from allura import model as M
 
 
-def setup_method():
+def setup_module():
     setup_basic_test()
     ThreadLocalORMSession.close_all()
     setup_global_objects()
     M.MonQTask.query.remove({})
 
 
-@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 26a773331..247370888 100644
--- a/Allura/allura/tests/model/test_neighborhood.py
+++ b/Allura/allura/tests/model/test_neighborhood.py
@@ -25,65 +25,64 @@ from allura.tests import decorators as td
 from alluratest.controller import setup_basic_test, setup_global_objects
 
 
-def setup_method():
-    setup_basic_test()
-    setup_with_tools()
+class TestNeighboorhoodModel:
 
+    def setup_method(self):
+        setup_basic_test()
+        self.setup_with_tools()
 
-@td.with_wiki
-def setup_with_tools():
-    setup_global_objects()
+    @td.with_wiki
+    def setup_with_tools(self):
+        setup_global_objects()
 
+    def test_neighborhood(self):
+        neighborhood = M.Neighborhood.query.get(name='Projects')
+        # Check css output depends of neighborhood level
+        test_css = ".text{color:#000;}"
+        neighborhood.css = test_css
+        neighborhood.features['css'] = 'none'
+        assert neighborhood.get_custom_css() == ""
+        neighborhood.features['css'] = 'picker'
+        assert neighborhood.get_custom_css() == test_css
+        neighborhood.features['css'] = 'custom'
+        assert neighborhood.get_custom_css() == test_css
+        # Check max projects
+        neighborhood.features['max_projects'] = None
+        assert neighborhood.get_max_projects() is None
+        neighborhood.features['max_projects'] = 500
+        assert neighborhood.get_max_projects() == 500
 
-@with_setup(setup_method)
-def test_neighborhood():
-    neighborhood = M.Neighborhood.query.get(name='Projects')
-    # Check css output depends of neighborhood level
-    test_css = ".text{color:#000;}"
-    neighborhood.css = test_css
-    neighborhood.features['css'] = 'none'
-    assert neighborhood.get_custom_css() == ""
-    neighborhood.features['css'] = 'picker'
-    assert neighborhood.get_custom_css() == test_css
-    neighborhood.features['css'] = 'custom'
-    assert neighborhood.get_custom_css() == test_css
-    # Check max projects
-    neighborhood.features['max_projects'] = None
-    assert neighborhood.get_max_projects() is None
-    neighborhood.features['max_projects'] = 500
-    assert neighborhood.get_max_projects() == 500
+        # Check picker css styles
+        test_css_dict = {'barontop': '#444',
+                        'titlebarbackground': '#555',
+                        'projecttitlefont': 'arial,sans-serif',
+                        'projecttitlecolor': '#333',
+                        'titlebarcolor': '#666'}
+        css_text = neighborhood.compile_css_for_picker(test_css_dict)
+        assert '#333' in css_text
+        assert '#444' in css_text
+        assert '#555' in css_text
+        assert '#666' in css_text
+        assert 'arial,sans-serif' in css_text
+        neighborhood.css = css_text
+        styles_list = neighborhood.get_css_for_picker()
+        for style in styles_list:
+            assert test_css_dict[style['name']] == style['value']
 
-    # Check picker css styles
-    test_css_dict = {'barontop': '#444',
-                     'titlebarbackground': '#555',
-                     'projecttitlefont': 'arial,sans-serif',
-                     'projecttitlecolor': '#333',
-                     'titlebarcolor': '#666'}
-    css_text = neighborhood.compile_css_for_picker(test_css_dict)
-    assert '#333' in css_text
-    assert '#444' in css_text
-    assert '#555' in css_text
-    assert '#666' in css_text
-    assert 'arial,sans-serif' in css_text
-    neighborhood.css = css_text
-    styles_list = neighborhood.get_css_for_picker()
-    for style in styles_list:
-        assert test_css_dict[style['name']] == style['value']
+        # Check neighborhood custom css showing
+        neighborhood.features['css'] = 'none'
+        assert not neighborhood.allow_custom_css
+        neighborhood.features['css'] = 'picker'
+        assert neighborhood.allow_custom_css
+        neighborhood.features['css'] = 'custom'
+        assert neighborhood.allow_custom_css
 
-    # Check neighborhood custom css showing
-    neighborhood.features['css'] = 'none'
-    assert not neighborhood.allow_custom_css
-    neighborhood.features['css'] = 'picker'
-    assert neighborhood.allow_custom_css
-    neighborhood.features['css'] = 'custom'
-    assert neighborhood.allow_custom_css
+        neighborhood.anchored_tools = 'wiki:Wiki, tickets:Tickets'
+        assert neighborhood.get_anchored_tools()['wiki'] == 'Wiki'
+        assert neighborhood.get_anchored_tools()['tickets'] == 'Tickets'
 
-    neighborhood.anchored_tools = 'wiki:Wiki, tickets:Tickets'
-    assert neighborhood.get_anchored_tools()['wiki'] == 'Wiki'
-    assert neighborhood.get_anchored_tools()['tickets'] == 'Tickets'
+        neighborhood.prohibited_tools = 'wiki, tickets'
+        assert neighborhood.get_prohibited_tools() == ['wiki', 'tickets']
 
-    neighborhood.prohibited_tools = 'wiki, tickets'
-    assert neighborhood.get_prohibited_tools() == ['wiki', 'tickets']
-
-    # Check properties
-    assert neighborhood.shortname == "p"
+        # Check properties
+        assert neighborhood.shortname == "p"
diff --git a/Allura/allura/tests/model/test_oauth.py b/Allura/allura/tests/model/test_oauth.py
index 67d12b094..edb396abc 100644
--- a/Allura/allura/tests/model/test_oauth.py
+++ b/Allura/allura/tests/model/test_oauth.py
@@ -16,28 +16,26 @@
 #       under the License.
 
 
-from alluratest.tools import with_setup, assert_equal, assert_not_equal
-
 from ming.odm import ThreadLocalORMSession
 
 from allura import model as M
 from alluratest.controller import setup_basic_test, setup_global_objects
 
 
-def setup_method():
-    setup_basic_test()
-    ThreadLocalORMSession.close_all()
-    setup_global_objects()
+class TestOAuthModel:
 
+    def setup_method(self):
+        setup_basic_test()
+        ThreadLocalORMSession.close_all()
+        setup_global_objects()
 
-@with_setup(setup_method)
-def test_upsert():
-    admin = M.User.by_username('test-admin')
-    user = M.User.by_username('test-user')
-    name = 'test-token'
-    token1 = M.OAuthConsumerToken.upsert(name, admin)
-    token2 = M.OAuthConsumerToken.upsert(name, admin)
-    token3 = M.OAuthConsumerToken.upsert(name, user)
-    assert M.OAuthConsumerToken.query.find().count() == 2
-    assert token1._id == token2._id
-    assert token1._id != token3._id
+    def test_upsert(self):
+        admin = M.User.by_username('test-admin')
+        user = M.User.by_username('test-user')
+        name = 'test-token'
+        token1 = M.OAuthConsumerToken.upsert(name, admin)
+        token2 = M.OAuthConsumerToken.upsert(name, admin)
+        token3 = M.OAuthConsumerToken.upsert(name, user)
+        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 524754bc3..6a1d44a8a 100644
--- a/Allura/allura/tests/model/test_project.py
+++ b/Allura/allura/tests/model/test_project.py
@@ -31,172 +31,161 @@ from allura.lib.exceptions import ToolError, Invalid
 from mock import MagicMock, patch
 
 
-def setup_method():
-    setup_basic_test()
-    setup_with_tools()
-
-
-@td.with_wiki
-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()
-    old_proj = c.project
-    h.set_context('test/sub1', neighborhood='Projects')
-    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 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 c.project.app_config('wiki').tool_name == 'wiki'
-    ThreadLocalORMSession.flush_all()
-    with td.raises(ToolError):
-        # already installed
+class TestProjectModel:
+
+    def setup_method(self):
+        setup_basic_test()
+        self.setup_with_tools()
+
+    @td.with_wiki
+    def setup_with_tools(self):
+        setup_global_objects()
+
+    def test_project(self):
+        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 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 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')
-    ThreadLocalORMSession.flush_all()
-    c.project.uninstall_app('hello-test-mount-point')
-    ThreadLocalORMSession.flush_all()
-    with td.raises(ToolError):
-        # mount point reserved
-        c.project.install_app('Wiki', 'feed')
-    with td.raises(ToolError):
-        # mount point too long
-        c.project.install_app('Wiki', 'a' * 64)
-    with td.raises(ToolError):
-        # mount point must begin with letter
-        c.project.install_app('Wiki', '1')
-    # single letter mount points are allowed
-    c.project.install_app('Wiki', 'a')
-    # Make sure the project support page is reset if the tool it was pointing
-    # to is uninstalled.
-    assert c.project.support_page == ''
-    app_config = c.project.app_config('hello')
-    app_inst = c.project.app_instance(app_config)
-    app_inst = c.project.app_instance('hello')
-    app_inst = c.project.app_instance('hello2123')
-    c.project.breadcrumbs()
-    c.app.config.breadcrumbs()
-
-
-@with_setup(setup_method)
-def test_install_app_validates_options():
-    from forgetracker.tracker_main import ForgeTrackerApp
-    name = 'TicketMonitoringEmail'
-    opt = [o for o in ForgeTrackerApp.config_options if o.name == name][0]
-    opt.validator = fev.Email(not_empty=True)
-    with patch.object(ForgeTrackerApp, 'config_on_install', new=[opt.name]):
-        for v in [None, '', 'bad@email']:
-            with td.raises(ToolError):
-                c.project.install_app('Tickets', 'test-tickets', **{name: v})
-            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 app.config.options[name] == 'e@e.com'
-
-
-def test_project_index():
-    project, idx = c.project, c.project.index()
-    assert 'id' in idx
-    assert idx['id'] == project.index_id()
-    assert 'title' in idx
-    assert 'type_s' in idx
-    assert 'deleted_b' in idx
-    assert 'private_b' in idx
-    assert 'neighborhood_id_s' in idx
-    assert 'short_description_t' in idx
-    assert 'url_s' in idx
-
-
-def test_subproject():
-    project = M.Project.query.get(shortname='test')
-    with td.raises(ToolError):
-        with patch('allura.lib.plugin.ProjectRegistrationProvider') as Provider:
-            Provider.get().shortname_validator.to_python.side_effect = Invalid(
-                'name', 'value', {})
-            # name doesn't validate
-            sp = project.new_subproject('test-proj-nose')
-    sp = project.new_subproject('test-proj-nose')
-    spp = sp.new_subproject('spp')
-    ThreadLocalORMSession.flush_all()
-    sp.delete()
-    ThreadLocalORMSession.flush_all()
-
-
-@td.with_wiki
-def test_anchored_tools():
-    c.project.neighborhood.anchored_tools = 'wiki:Wiki, tickets:Ticket'
-    c.project.install_app = MagicMock()
-    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():
-    with h.push_config(c,
-                       user=M.User.by_username('test-admin'),
-                       project=M.Project.query.get(shortname='test')):
-        sm = c.project.sitemap()
-        assert sm[-1].tool_name == 'admin'
-
-
-@with_setup(setup_method)
-def test_users_and_roles():
-    p = M.Project.query.get(shortname='test')
-    sub = p.direct_subprojects[0]
-    u = M.User.by_username('test-admin')
-    assert p.users_with_role('Admin') == [u]
-    assert p.users_with_role('Admin') == sub.users_with_role('Admin')
-    assert p.users_with_role('Admin') == p.admins()
-
-    user = p.admins()[0]
-    user.disabled = True
-    ThreadLocalORMSession.flush_all()
-    assert p.users_with_role('Admin') == []
-    assert p.users_with_role('Admin') == p.admins()
-
-
-@with_setup(setup_method)
-def test_project_disabled_users():
-    p = M.Project.query.get(shortname='test')
-    users = p.users()
-    assert users[0].username == 'test-admin'
-    user = M.User.by_username('test-admin')
-    user.disabled = True
-    ThreadLocalORMSession.flush_all()
-    users = p.users()
-    assert users == []
-
-def test_screenshot_unicode_serialization():
-    p = M.Project.query.get(shortname='test')
-    screenshot_unicode = M.ProjectFile(project_id=p._id, category='screenshot', caption="ConSelección", filename='ConSelección.jpg')
-    screenshot_ascii = M.ProjectFile(project_id=p._id, category='screenshot', caption='test-screenshot', filename='test_file.jpg')
-    ThreadLocalORMSession.flush_all()
-
-    serialized = p.__json__()
-    screenshots = sorted(serialized['screenshots'], key=lambda k: k['caption'])
-
-    assert len(screenshots) == 2
-    assert screenshots[0]['url'] == 'http://localhost/p/test/screenshot/ConSelecci%C3%B3n.jpg'
-    assert screenshots[0]['caption'] == "ConSelección"
-    assert screenshots[0]['thumbnail_url'] == 'http://localhost/p/test/screenshot/ConSelecci%C3%B3n.jpg/thumb'
-
-    assert screenshots[1]['url'] == 'http://localhost/p/test/screenshot/test_file.jpg'
-    assert screenshots[1]['caption'] == 'test-screenshot'
-    assert screenshots[1]['thumbnail_url'] == 'http://localhost/p/test/screenshot/test_file.jpg/thumb'
+        c.project.support_page = 'hello-test-mount-point'
+        assert c.project.app_config('wiki').tool_name == 'wiki'
+        ThreadLocalORMSession.flush_all()
+        with td.raises(ToolError):
+            # already installed
+            c.project.install_app('Wiki', 'hello-test-mount-point')
+        ThreadLocalORMSession.flush_all()
+        c.project.uninstall_app('hello-test-mount-point')
+        ThreadLocalORMSession.flush_all()
+        with td.raises(ToolError):
+            # mount point reserved
+            c.project.install_app('Wiki', 'feed')
+        with td.raises(ToolError):
+            # mount point too long
+            c.project.install_app('Wiki', 'a' * 64)
+        with td.raises(ToolError):
+            # mount point must begin with letter
+            c.project.install_app('Wiki', '1')
+        # single letter mount points are allowed
+        c.project.install_app('Wiki', 'a')
+        # Make sure the project support page is reset if the tool it was pointing
+        # to is uninstalled.
+        assert c.project.support_page == ''
+        app_config = c.project.app_config('hello')
+        app_inst = c.project.app_instance(app_config)
+        app_inst = c.project.app_instance('hello')
+        app_inst = c.project.app_instance('hello2123')
+        c.project.breadcrumbs()
+        c.app.config.breadcrumbs()
+
+    def test_install_app_validates_options(self):
+        from forgetracker.tracker_main import ForgeTrackerApp
+        name = 'TicketMonitoringEmail'
+        opt = [o for o in ForgeTrackerApp.config_options if o.name == name][0]
+        opt.validator = fev.Email(not_empty=True)
+        with patch.object(ForgeTrackerApp, 'config_on_install', new=[opt.name]):
+            for v in [None, '', 'bad@email']:
+                with td.raises(ToolError):
+                    c.project.install_app('Tickets', 'test-tickets', **{name: v})
+                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 app.config.options[name] == 'e@e.com'
+
+    def test_project_index(self):
+        project, idx = c.project, c.project.index()
+        assert 'id' in idx
+        assert idx['id'] == project.index_id()
+        assert 'title' in idx
+        assert 'type_s' in idx
+        assert 'deleted_b' in idx
+        assert 'private_b' in idx
+        assert 'neighborhood_id_s' in idx
+        assert 'short_description_t' in idx
+        assert 'url_s' in idx
+
+    def test_subproject(self):
+        project = M.Project.query.get(shortname='test')
+        with td.raises(ToolError):
+            with patch('allura.lib.plugin.ProjectRegistrationProvider') as Provider:
+                Provider.get().shortname_validator.to_python.side_effect = Invalid(
+                    'name', 'value', {})
+                # name doesn't validate
+                sp = project.new_subproject('test-proj-nose')
+        sp = project.new_subproject('test-proj-nose')
+        spp = sp.new_subproject('spp')
+        ThreadLocalORMSession.flush_all()
+        sp.delete()
+        ThreadLocalORMSession.flush_all()
+
+    @td.with_wiki
+    def test_anchored_tools(self):
+        c.project.neighborhood.anchored_tools = 'wiki:Wiki, tickets:Ticket'
+        c.project.install_app = MagicMock()
+        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(self):
+        with h.push_config(c,
+                        user=M.User.by_username('test-admin'),
+                        project=M.Project.query.get(shortname='test')):
+            sm = c.project.sitemap()
+            assert sm[-1].tool_name == 'admin'
+
+    def test_users_and_roles(self):
+        p = M.Project.query.get(shortname='test')
+        sub = p.direct_subprojects[0]
+        u = M.User.by_username('test-admin')
+        assert p.users_with_role('Admin') == [u]
+        assert p.users_with_role('Admin') == sub.users_with_role('Admin')
+        assert p.users_with_role('Admin') == p.admins()
+
+        user = p.admins()[0]
+        user.disabled = True
+        ThreadLocalORMSession.flush_all()
+        assert p.users_with_role('Admin') == []
+        assert p.users_with_role('Admin') == p.admins()
+
+    def test_project_disabled_users(self):
+        p = M.Project.query.get(shortname='test')
+        users = p.users()
+        assert users[0].username == 'test-admin'
+        user = M.User.by_username('test-admin')
+        user.disabled = True
+        ThreadLocalORMSession.flush_all()
+        users = p.users()
+        assert users == []
+
+    def test_screenshot_unicode_serialization(self):
+        p = M.Project.query.get(shortname='test')
+        screenshot_unicode = M.ProjectFile(project_id=p._id, category='screenshot', caption="ConSelección", filename='ConSelección.jpg')
+        screenshot_ascii = M.ProjectFile(project_id=p._id, category='screenshot', caption='test-screenshot', filename='test_file.jpg')
+        ThreadLocalORMSession.flush_all()
+
+        serialized = p.__json__()
+        screenshots = sorted(serialized['screenshots'], key=lambda k: k['caption'])
+
+        assert len(screenshots) == 2
+        assert screenshots[0]['url'] == 'http://localhost/p/test/screenshot/ConSelecci%C3%B3n.jpg'
+        assert screenshots[0]['caption'] == "ConSelección"
+        assert screenshots[0]['thumbnail_url'] == 'http://localhost/p/test/screenshot/ConSelecci%C3%B3n.jpg/thumb'
+
+        assert screenshots[1]['url'] == 'http://localhost/p/test/screenshot/test_file.jpg'
+        assert screenshots[1]['caption'] == 'test-screenshot'
+        assert screenshots[1]['thumbnail_url'] == 'http://localhost/p/test/screenshot/test_file.jpg/thumb'
diff --git a/Allura/allura/tests/test_app.py b/Allura/allura/tests/test_app.py
index 71733a2d5..8a6924ce2 100644
--- a/Allura/allura/tests/test_app.py
+++ b/Allura/allura/tests/test_app.py
@@ -20,161 +20,152 @@ import mock
 from ming.base import Object
 import pytest
 from formencode import validators as fev
+from textwrap import dedent
 
 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 alluratest.pytest_helpers import with_nose_compatibility
-
-
-def setup_method():
-    setup_unit_test()
-    c.user._id = None
-    c.project = mock.Mock()
-    c.project.name = 'Test Project'
-    c.project.shortname = 'tp'
-    c.project._id = 'testproject/'
-    c.project.url = lambda: '/testproject/'
-    app_config = mock.Mock()
-    app_config._id = None
-    app_config.project_id = 'testproject/'
-    app_config.tool_name = 'tool'
-    app_config.options = Object(mount_point='foo')
-    c.app = mock.Mock()
-    c.app.config = app_config
-    c.app.config.script_name = lambda: '/testproject/test_application/'
-    c.app.config.url = lambda: 'http://testproject/test_application/'
-    c.app.url = c.app.config.url()
-    c.app.__version__ = '0.0'
-
-def test_config_options():
-    options = [
-        app.ConfigOption('test1', str, 'MyTestValue'),
-        app.ConfigOption('test2', str, lambda:'MyTestValue')]
-    assert options[0].default == 'MyTestValue'
-    assert options[1].default == 'MyTestValue'
-
-
-def test_config_options_render_attrs():
-    opt = app.ConfigOption('test1', str, None, extra_attrs={'type': 'url'})
-    assert opt.render_attrs() == 'type="url"'
-
-
-def test_config_option_without_validator():
-    opt = app.ConfigOption('test1', str, None)
-    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 opt.validate('val') == 'val'
-    pytest.raises(fev.Invalid, opt.validate, None)
-    pytest.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)]
-    class TestApp(app.Application):
-        config_options = app.Application.config_options + opts + [
-            app.ConfigOption('not_on_install', str, None),
-        ]
-        config_on_install = ['url', 'private']
-
-    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
-        def sitemap(self):
-            children = [app.SitemapEntry('New', 'new', ui_icon=Icon('some-icon')),
-                        app.SitemapEntry('Recent', 'recent'),
-                        ]
-            return [app.SitemapEntry('My Tool', '.')[children]]
-
-    a = TestApp(c.project, c.app.config)
-    main_menu = a.main_menu()
-    assert len(main_menu) == 1
-    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/'),
-        app.SitemapEntry('b', 'b/')]
-    sm[app.SitemapEntry(lambda app:app.config.script_name(), 'c/')]
-    bound_sm = sm.bind_app(c.app)
-    assert bound_sm.url == 'http://testproject/test_application/', bound_sm.url
-    assert bound_sm.children[
-        -1].label == '/testproject/test_application/', bound_sm.children[-1].label
-    assert len(sm.children) == 3
-    sm.extend([app.SitemapEntry('a', 'a/')[
-        app.SitemapEntry('d', 'd/')]])
-    assert len(sm.children) == 3
-
-
-@with_setup(setup_method)
-@mock.patch('allura.app.Application.PostClass.query.get')
-def test_handle_artifact_unicode(qg):
-    """
-    Tests that app.handle_artifact_message can accept utf strings
-    """
-    ticket = mock.MagicMock()
-    ticket.get_discussion_thread.return_value = (mock.MagicMock(), mock.MagicMock())
-    post = mock.MagicMock()
-    qg.return_value = post
-
-    a = app.Application(c.project, c.app.config)
-
-    msg = dict(payload='foo ƒ†©¥˙¨ˆ'.encode(), message_id=1, headers={})
-    a.handle_artifact_message(ticket, msg)
-    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 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 post.attach.call_args[0][1].getvalue() == '\x94my quote\x94'.encode()
-
-    # assert against prod example
-    msg_raw = """Message-Id: <15...@webmail.messagingengine.com>
-From: foo <fo...@bar.com>
-To: "[forge:site-support]" <15...@site-support.forge.p.re.sf.net>
-MIME-Version: 1.0
-Content-Transfer-Encoding: 7bit
-Content-Type: multipart/alternative; boundary="_----------=_150235203132168580"
-Date: Thu, 10 Aug 2017 10:00:31 +0200
-Subject: Re: [forge:site-support] #15391 Unable to join (my own) mailing list
-This is a multi-part message in MIME format.
---_----------=_150235203132168580
-Content-Transfer-Encoding: quoted-printable
-Content-Type: text/plain; charset="utf-8"
-Hi
---_----------=_150235203132168580
-Content-Transfer-Encoding: quoted-printable
-Content-Type: text/html; charset="utf-8"
-<!DOCTYPE html>
-<html><body>Hi</body></html>
---_----------=_150235203132168580--
-    """
-    msg = mail_util.parse_message(msg_raw)
-    for p in [p for p in msg['parts'] if p['payload'] is not None]:
-        # filter here mimics logic in `route_email`
-        a.handle_artifact_message(ticket, p)
+
+
+class TestApp:
+
+    def setup_method(self):
+        setup_unit_test()
+        c.user._id = None
+        c.project = mock.Mock()
+        c.project.name = 'Test Project'
+        c.project.shortname = 'tp'
+        c.project._id = 'testproject/'
+        c.project.url = lambda: '/testproject/'
+        app_config = mock.Mock()
+        app_config._id = None
+        app_config.project_id = 'testproject/'
+        app_config.tool_name = 'tool'
+        app_config.options = Object(mount_point='foo')
+        c.app = mock.Mock()
+        c.app.config = app_config
+        c.app.config.script_name = lambda: '/testproject/test_application/'
+        c.app.config.url = lambda: 'http://testproject/test_application/'
+        c.app.url = c.app.config.url()
+        c.app.__version__ = '0.0'
+
+    def test_config_options(self):
+        options = [
+            app.ConfigOption('test1', str, 'MyTestValue'),
+            app.ConfigOption('test2', str, lambda:'MyTestValue')]
+        assert options[0].default == 'MyTestValue'
+        assert options[1].default == 'MyTestValue'
+
+    def test_config_options_render_attrs(self):
+        opt = app.ConfigOption('test1', str, None, extra_attrs={'type': 'url'})
+        assert opt.render_attrs() == 'type="url"'
+
+    def test_config_option_without_validator(self):
+        opt = app.ConfigOption('test1', str, None)
+        assert opt.validate(None) == None
+        assert opt.validate('') == ''
+        assert opt.validate('val') == 'val'
+
+    def test_config_option_with_validator(self):
+        v = fev.NotEmpty()
+        opt = app.ConfigOption('test1', str, None, validator=v)
+        assert opt.validate('val') == 'val'
+        pytest.raises(fev.Invalid, opt.validate, None)
+        pytest.raises(fev.Invalid, opt.validate, '')
+
+    def test_options_on_install_default(self):
+        a = app.Application(c.project, c.app.config)
+        assert a.options_on_install() == []
+
+    def test_options_on_install(self):
+        opts = [app.ConfigOption('url', str, None),
+                app.ConfigOption('private', bool, None)]
+        class TestApp(app.Application):
+            config_options = app.Application.config_options + opts + [
+                app.ConfigOption('not_on_install', str, None),
+            ]
+            config_on_install = ['url', 'private']
+
+        a = TestApp(c.project, c.app.config)
+        assert a.options_on_install() == opts
+
+    def test_main_menu(self):
+        class TestApp(app.Application):
+            @property
+            def sitemap(self):
+                children = [app.SitemapEntry('New', 'new', ui_icon=Icon('some-icon')),
+                            app.SitemapEntry('Recent', 'recent'),
+                            ]
+                return [app.SitemapEntry('My Tool', '.')[children]]
+
+        a = TestApp(c.project, c.app.config)
+        main_menu = a.main_menu()
+        assert len(main_menu) == 1
+        assert main_menu[0].children == []  # default main_menu implementation should drop the children from sitemap()
+
+    def test_sitemap(self):
+        sm = app.SitemapEntry('test', '')[
+            app.SitemapEntry('a', 'a/'),
+            app.SitemapEntry('b', 'b/')]
+        sm[app.SitemapEntry(lambda app:app.config.script_name(), 'c/')]
+        bound_sm = sm.bind_app(c.app)
+        assert bound_sm.url == 'http://testproject/test_application/', bound_sm.url
+        assert bound_sm.children[
+            -1].label == '/testproject/test_application/', bound_sm.children[-1].label
+        assert len(sm.children) == 3
+        sm.extend([app.SitemapEntry('a', 'a/')[
+            app.SitemapEntry('d', 'd/')]])
+        assert len(sm.children) == 3
+
+    @mock.patch('allura.app.Application.PostClass.query.get')
+    def test_handle_artifact_unicode(self, qg):
+        """
+        Tests that app.handle_artifact_message can accept utf strings
+        """
+        ticket = mock.MagicMock()
+        ticket.get_discussion_thread.return_value = (mock.MagicMock(), mock.MagicMock())
+        post = mock.MagicMock()
+        qg.return_value = post
+
+        a = app.Application(c.project, c.app.config)
+
+        msg = dict(payload='foo ƒ†©¥˙¨ˆ'.encode(), message_id=1, headers={})
+        a.handle_artifact_message(ticket, msg)
+        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 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 post.attach.call_args[0][1].getvalue() == '\x94my quote\x94'.encode()
+
+        # assert against prod example
+        msg_raw = dedent("""\
+            Message-Id: <15...@webmail.messagingengine.com>
+            From: foo <fo...@bar.com>
+            To: "[forge:site-support]" <15...@site-support.forge.p.re.sf.net>
+            MIME-Version: 1.0
+            Content-Transfer-Encoding: 7bit
+            Content-Type: multipart/alternative; boundary="_----------=_150235203132168580"
+            Date: Thu, 10 Aug 2017 10:00:31 +0200
+            Subject: Re: [forge:site-support] #15391 Unable to join (my own) mailing list
+            This is a multi-part message in MIME format.
+            --_----------=_150235203132168580
+            Content-Transfer-Encoding: quoted-printable
+            Content-Type: text/plain; charset="utf-8"
+            Hi
+            --_----------=_150235203132168580
+            Content-Transfer-Encoding: quoted-printable
+            Content-Type: text/html; charset="utf-8"
+            <!DOCTYPE html>
+            <html><body>Hi</body></html>
+            --_----------=_150235203132168580--
+        """)
+        msg = mail_util.parse_message(msg_raw)
+        for p in [p for p in msg['parts'] if p['payload'] is not None]:
+            # filter here mimics logic in `route_email`
+            a.handle_artifact_message(ticket, p)


[allura] 02/10: 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-part2
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 54cc022b7c9ab60850ea5cfeeb453dec49414a84
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 27f62dfe8..affb5dcfb 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -47,6 +47,7 @@ from tg import tmpl_context as c, app_globals as g
 from allura.tests import TestController
 from allura.tests import decorators as td
 from allura.tests.decorators import audits, out_audits, assert_logmsg
+from allura.tests.pytest_helpers import with_nose_compatibility
 from alluratest.controller import setup_trove_categories, TestRestApiBase, oauth1_webtest
 from allura import model as M
 from allura.model.oauth import dummy_oauths
@@ -59,6 +60,7 @@ def unentity(s):
     return s.replace('&quot;', '"').replace('&#34;', '"')
 
 
+@with_nose_compatibility
 class TestAuth(TestController):
     def test_login(self):
         self.app.get('/auth/')
@@ -1140,6 +1142,7 @@ class TestAuth(TestController):
         assert r.content_length != 777
 
 
+@with_nose_compatibility
 class TestAuthRest(TestRestApiBase):
 
     def test_tools_list_anon(self):
@@ -1179,6 +1182,7 @@ class TestAuthRest(TestRestApiBase):
         }
 
 
+@with_nose_compatibility
 class TestPreferences(TestController):
     @td.with_user_project('test-admin')
     def test_personal_data(self):
@@ -1562,11 +1566,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'}
 
@@ -1813,6 +1818,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
@@ -2147,6 +2153,7 @@ class TestOAuthAccessToken(TestController):
         self.test_access_token_ok(signature_type='query')
 
 
+@with_nose_compatibility
 class TestDisableAccount(TestController):
     def test_not_authenticated(self):
         r = self.app.get(
@@ -2191,6 +2198,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'}
@@ -2386,6 +2394,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
@@ -2420,6 +2429,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'
@@ -2493,7 +2503,7 @@ class TestTwoFactor(TestController):
     def test_enable_totp(self):
         # create a separate session, for later use in the test
         other_session = TestController()
-        other_session.setUp()
+        other_session.setup_method(method)
         other_session.app.get('/auth/preferences/')
 
         with out_audits(user=True):
@@ -2535,7 +2545,7 @@ class TestTwoFactor(TestController):
         # Confirm any pre-existing sessions have to re-authenticate
         r = other_session.app.get('/auth/preferences/')
         assert '/auth/?return_to' in r.headers['Location']
-        other_session.tearDown()
+        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 7384dde8f..6cb550b60 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 1299a2bc2..362c8ee54 100644
--- a/AlluraTest/alluratest/controller.py
+++ b/AlluraTest/alluratest/controller.py
@@ -19,6 +19,7 @@
 from __future__ import annotations
 
 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
@@ -162,12 +163,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(
@@ -179,7 +181,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()
@@ -210,10 +212,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] 05/10: [#8455] allura pytest - fix misc test failures that popped up during pytest conversion

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

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

commit be8e22a97cfaa5456f2719606ea45d78d0bf7ad8
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Mon Sep 12 20:00:41 2022 +0000

    [#8455] allura pytest - fix misc test failures that popped up during pytest conversion
---
 Allura/allura/lib/macro.py                         |  15 +--
 Allura/allura/model/project.py                     |  20 +++-
 Allura/allura/tests/exclude_from_rewrite_hook.py   |  31 ++++++
 .../allura/tests/functional/test_neighborhood.py   |   8 +-
 .../allura/tests/functional/test_user_profile.py   |  70 +++++++------
 Allura/allura/tests/model/test_auth.py             |   2 +-
 Allura/allura/tests/test_globals.py                | 115 ++++++++++++---------
 Allura/allura/tests/test_plugin.py                 |  34 +++---
 Allura/allura/tests/test_tasks.py                  |  38 +++----
 Allura/allura/tests/test_webhooks.py               | 101 +++++++++---------
 10 files changed, 249 insertions(+), 185 deletions(-)

diff --git a/Allura/allura/lib/macro.py b/Allura/allura/lib/macro.py
index b1af50297..0e293037e 100644
--- a/Allura/allura/lib/macro.py
+++ b/Allura/allura/lib/macro.py
@@ -177,14 +177,15 @@ def project_blog_posts(max_number=5, sort='timestamp', summary=False, mount_poin
         'state': 'published',
     })
     posts = posts.sort(sort, pymongo.DESCENDING).limit(int(max_number)).all()
-    output = ((dict(
-        href=post.url(),
-        title=post.title,
-        author=post.author().display_name,
-        ago=h.ago(post.timestamp),
-        description=summary and '&nbsp;' or g.markdown.cached_convert(post, 'text')))
+    output = [
+        dict(href=post.url(),
+             title=post.title,
+             author=post.author().display_name,
+             ago=h.ago(post.timestamp),
+             description=summary and '&nbsp;' or g.markdown.cached_convert(post, 'text'))
         for post in posts if security.has_access(post, 'read', project=post.app.project)() and
-        security.has_access(post.app.project, 'read', project=post.app.project)())
+            security.has_access(post.app.project, 'read', project=post.app.project)()
+    ]
     posts = BlogPosts(posts=output)
     g.resource_manager.register(posts)
     response = posts.display(posts=output)
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index f0d3552b4..fad47aefc 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -1102,12 +1102,30 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject):
             ThreadLocalORMSession.flush_all()
 
     def add_user(self, user, role_names):
-        'Convenience method to add member with the given role(s).'
+        '''Convenience method to add member with the given role(s).'''
         pr = ProjectRole.by_user(user, project=self, upsert=True)
         for role_name in role_names:
             r = ProjectRole.by_name(role_name, self)
             pr.roles.append(r._id)
 
+    def remove_user(self, user, role_names=None):
+        '''Convenience method to add member with the given role(s).'''
+        pr = ProjectRole.by_user(user, project=self)
+        if not pr:
+            return
+
+        if not role_names or not isinstance(role_names, Iterable):
+            ProjectRole.query.remove({'_id': pr._id})
+            return
+
+        for role_name in role_names:
+            r = ProjectRole.by_name(role_name, self)
+            if r._id in pr.roles:
+                pr.roles.remove(r._id)
+
+        if not pr.roles:
+            pr.remove()
+
     @property
     def twitter_handle(self):
         return self.social_account('Twitter').accounturl
diff --git a/Allura/allura/tests/exclude_from_rewrite_hook.py b/Allura/allura/tests/exclude_from_rewrite_hook.py
new file mode 100644
index 000000000..509841776
--- /dev/null
+++ b/Allura/allura/tests/exclude_from_rewrite_hook.py
@@ -0,0 +1,31 @@
+import sys
+
+from allura.app import Application
+from allura.lib.decorators import task
+from allura.lib.exceptions import CompoundError
+
+
+class ThemeProviderTestApp(Application):
+    """
+    If this test class is added directly to a test module, pkg_resources internals
+    will throw this error:
+        NotImplementedError: Can't perform this operation for unregistered loader type
+    This is because pytest adds a hook to override the default assert behavior and this
+    conflicts/messes-with pkg_resources. Theoretically on python > py37, importlib.resources
+    can do the same things as pkg_resources and faster, but those solutions don't currently
+    work on py37.
+    """
+    icons = {
+        24: 'images/testapp_24.png',
+    }
+
+
+@task
+def raise_compound_exception():
+    errs = []
+    for x in range(10):
+        try:
+            assert False, 'assert %d' % x
+        except Exception:
+            errs.append(sys.exc_info())
+    raise CompoundError(*errs)
\ No newline at end of file
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index a1d1e7d57..ac7ee3c9d 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -43,12 +43,12 @@ from allura.tests.pytest_helpers import with_nose_compatibility
 @with_nose_compatibility
 class TestNeighborhood(TestController):
 
-    def setUp(self):
-        super().setUp()
+    def setup_method(self, method):
+        super().setup_method(method)
         self._cleanup_audit_log()
 
-    def tearDown(self):
-        super().tearDown()
+    def teardown_method(self, method):
+        super().teardown_method(method)
         self._cleanup_audit_log()
 
     def _cleanup_audit_log(self):
diff --git a/Allura/allura/tests/functional/test_user_profile.py b/Allura/allura/tests/functional/test_user_profile.py
index 4a54ffa69..e4df160e1 100644
--- a/Allura/allura/tests/functional/test_user_profile.py
+++ b/Allura/allura/tests/functional/test_user_profile.py
@@ -26,6 +26,46 @@ from allura.tests import TestController
 from allura.tests.pytest_helpers import with_nose_compatibility
 
 
+class TestUserProfileSections(TestController):
+
+    def teardown_method(self, method):
+        super().teardown_method(method)
+        project = Project.query.get(shortname='u/test-user')
+        app = project.app_instance('profile')
+        if hasattr(type(app), '_sections'):
+            delattr(type(app), '_sections')
+
+    @td.with_user_project('test-user')
+    def test_profile_sections(self):
+        project = Project.query.get(shortname='u/test-user')
+        app = project.app_instance('profile')
+
+        def ep(n):
+            m = mock.Mock()
+            m.name = n
+            m.load()().display.return_value = 'Section %s' % n
+            return m
+        eps = list(map(ep, ['a', 'b', 'c', 'd']))
+        order = {'user_profile_sections.order': 'b, d,c , f '}
+        if hasattr(type(app), '_sections'):
+            delattr(type(app), '_sections')
+        with mock.patch('allura.lib.helpers.iter_entry_points') as iep:
+            with mock.patch.dict(tg.config, order):
+                iep.return_value = eps
+                sections = app.profile_sections
+                assert sections == [
+                    eps[1].load(),
+                    eps[3].load(),
+                    eps[2].load(),
+                    eps[0].load()]
+        r = self.app.get('/u/test-user/profile')
+        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
+
+
 @with_nose_compatibility
 class TestUserProfile(TestController):
 
@@ -230,36 +270,6 @@ class TestUserProfile(TestController):
         r = self.app.get('/u/test-user/profile/send_message', status=200)
         assert 'you currently have user messages disabled' in r
 
-    @td.with_user_project('test-user')
-    def test_profile_sections(self):
-        project = Project.query.get(shortname='u/test-user')
-        app = project.app_instance('profile')
-
-        def ep(n):
-            m = mock.Mock()
-            m.name = n
-            m.load()().display.return_value = 'Section %s' % n
-            return m
-        eps = list(map(ep, ['a', 'b', 'c', 'd']))
-        order = {'user_profile_sections.order': 'b, d,c , f '}
-        if hasattr(type(app), '_sections'):
-            delattr(type(app), '_sections')
-        with mock.patch('allura.lib.helpers.iter_entry_points') as iep:
-            with mock.patch.dict(tg.config, order):
-                iep.return_value = eps
-                sections = app.profile_sections
-                assert sections == [
-                    eps[1].load(),
-                    eps[3].load(),
-                    eps[2].load(),
-                    eps[0].load()]
-        r = self.app.get('/u/test-user/profile')
-        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 'content="noindex, follow"' in r.text
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index 2d6aa75f7..7f48c49bc 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -352,8 +352,8 @@ def test_check_sent_user_message_times():
     assert not user1.can_send_user_message()
 
 
-@td.with_user_project('test-admin')
 @with_setup(setup_method)
+@td.with_user_project('test-admin')
 def test_user_track_active():
     # without this session flushing inside track_active raises Exception
     setup_functional_test()
diff --git a/Allura/allura/tests/test_globals.py b/Allura/allura/tests/test_globals.py
index 4d4b3b592..389c7a228 100644
--- a/Allura/allura/tests/test_globals.py
+++ b/Allura/allura/tests/test_globals.py
@@ -18,6 +18,7 @@
 
 import re
 import os
+from textwrap import dedent
 import allura
 import unittest
 import hashlib
@@ -48,11 +49,6 @@ from forgewiki import model as WM
 from forgeblog import model as BM
 
 
-def setup_module(module):
-    setup_basic_test()
-    setup_unit_test()
-
-
 def squish_spaces(text):
     # \s is whitespace
     # \xa0 is &nbsp; in unicode form
@@ -84,8 +80,28 @@ def get_projects_property_in_the_same_order(names, prop):
 @with_nose_compatibility
 class Test():
 
+    @classmethod
+    def setup_class(cls):
+        setup_basic_test()
+        setup_global_objects()
+
     def setup_method(self, method):
         setup_global_objects()
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+        p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
+        self.acl_bak = p_test.acl.copy()
+
+    def teardown_method(self, method):
+        user = M.User.by_username('test-admin')
+        user.display_name = 'Test Admin'
+
+        p_nbhd = M.Neighborhood.query.get(name='Projects')
+        p_test = M.Project.query.get(shortname='test', neighborhood_id=p_nbhd._id)
+        p_test.remove_user(M.User.by_username('test-user'))
+        p_test.remove_user(M.User.by_username('test-user-0'))
+        p_test.acl = self.acl_bak
+
+        ThreadLocalORMSession.flush_all()
 
     @td.with_wiki
     def test_app_globals(self):
@@ -358,17 +374,19 @@ class Test():
 
     def test_markdown_toc(self):
         with h.push_context('test', neighborhood='Projects'):
-            r = g.markdown_wiki.convert("""[TOC]
+            r = g.markdown_wiki.convert(dedent("""\
+                [TOC]
 
-    # Header 1
+                # 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
+                ## Header 2"""))
+        assert dedent('''\
+            <ul>
+            <li><a href="#header-1">Header 1</a><ul>
+            <li><a href="#header-2">Header 2</a></li>
+            </ul>
+            </li>
+            </ul>''') in r
 
     @td.with_wiki
     def test_wiki_artifact_links(self):
@@ -442,38 +460,35 @@ class Test():
             '<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.markdown.convert("<class 'foo'>") == \
+            '''<div class="markdown_content"><p>&lt;class 'foo'=""&gt;&lt;/class&gt;</p></div>'''
+
+        assert g.markdown.convert(dedent('''\
+            # Header
+
+            Some text in a regular paragraph
+
+                :::python
+                for i in range(10):
+                    print i
+            ''')) == dedent('''\
+                <div class="markdown_content"><h1 id="header">Header</h1>
+                <p>Some text in a regular paragraph</p>
+                <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>
+                    <span class="nb">print</span> <span class="n">i</span>
+                </code></pre></div>
+                </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>')
+        assert g.markdown.convert(dedent('''\
+            ~~~~
+            def foo(): pass
+            ~~~~''')) == dedent('''\
+                <div class="markdown_content"><div class="codehilite"><pre><span></span><code>def foo(): pass
+                </code></pre></div>
+                </div>''')
 
     def test_markdown_list_without_break(self):
         # this is not a valid way to make a list in original Markdown or python-markdown
@@ -482,37 +497,37 @@ class Test():
         # TODO: try https://github.com/adamb70/mdx-breakless-lists
         #       or https://gitlab.com/ayblaq/prependnewline
         assert (
-            g.markdown.convert('''\
+            g.markdown.convert(dedent('''\
     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>')
 
         assert (
-            g.markdown.convert('''\
+            g.markdown.convert(dedent('''\
     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>')
 
         assert (
-            g.markdown.convert('''\
+            g.markdown.convert(dedent('''\
     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>')
 
         assert (
-            g.markdown.convert('''\
+            g.markdown.convert(dedent('''\
     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>')
diff --git a/Allura/allura/tests/test_plugin.py b/Allura/allura/tests/test_plugin.py
index 22a7ed8bf..f11eae8d7 100644
--- a/Allura/allura/tests/test_plugin.py
+++ b/Allura/allura/tests/test_plugin.py
@@ -35,7 +35,6 @@ from alluratest.tools import (
 from mock import Mock, MagicMock, patch
 
 from allura import model as M
-from allura.app import Application
 from allura.lib import plugin
 from allura.lib import phone
 from allura.lib import helpers as h
@@ -44,15 +43,19 @@ from allura.lib.plugin import ProjectRegistrationProvider
 from allura.lib.plugin import ThemeProvider
 from allura.lib.exceptions import ProjectConflict, ProjectShortnameInvalid
 from allura.tests.decorators import audits
+from allura.tests.exclude_from_rewrite_hook import ThemeProviderTestApp
 from alluratest.controller import setup_basic_test, setup_global_objects
 from allura.tests.pytest_helpers import with_nose_compatibility
 
 
+def setup_module(module):
+    setup_basic_test()
+
+
 @with_nose_compatibility
 class TestProjectRegistrationProvider:
 
     def setup_method(self, method):
-        setup_basic_test()
         self.provider = ProjectRegistrationProvider()
 
     @patch('allura.lib.security.has_access')
@@ -170,7 +173,6 @@ class TestProjectRegistrationProviderPhoneVerification:
         self.user = UserMock()
         self.nbhd = MagicMock()
 
-
     def test_phone_verified_disabled(self):
         with h.push_config(tg.config, **{'project.verify_phone': 'false'}):
             assert self.p.phone_verified(self.user, self.nbhd)
@@ -280,36 +282,30 @@ 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):
-            icons = {
-                24: 'images/testapp_24.png',
-            }
-        plugin_g.entry_points = {'tool': {'testapp': TestApp}}
-        assert (ThemeProvider().app_icon_url('testapp', 24) ==
-                      app_g.theme_href.return_value)
+        plugin_g.entry_points = {'tool': {'testapp': ThemeProviderTestApp}}
+        app_icon = ThemeProvider().app_icon_url('testapp', 24)
+        other = app_g.theme_href.return_value
+        assert app_icon == other
+            
         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 (ThemeProvider().app_icon_url('invalid', 24) ==
-                      None)
+        assert ThemeProvider().app_icon_url('invalid', 24) == None
 
     @patch('allura.app.g')
     def test_app_icon_app(self, g):
-        class TestApp(Application):
-            icons = {
-                24: 'images/testapp_24.png',
-            }
-        app = TestApp(None, None)
-        assert (ThemeProvider().app_icon_url(app, 24) ==
-                      g.theme_href.return_value)
+        app = ThemeProviderTestApp(None, None)
+        assert ThemeProvider().app_icon_url(app, 24) == \
+            g.theme_href.return_value
         g.theme_href.assert_called_with('images/testapp_24.png')
 
 
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index 735b3294b..429effd80 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -18,6 +18,7 @@
 import operator
 import shutil
 import sys
+from textwrap import dedent
 import unittest
 
 import six
@@ -52,6 +53,7 @@ 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.tests.exclude_from_rewrite_hook import raise_compound_exception
 from allura.lib.decorators import event_handler, task
 
 
@@ -148,7 +150,7 @@ class TestEventTasks(unittest.TestCase):
         assert M.MonQTask.query.get(task_name='allura.tasks.event_tasks.event', args=['my_event4'])
 
     def test_compound_error(self):
-        t = raise_exc.post()
+        t = raise_compound_exception.post()
         with LogCapture(level=logging.ERROR) as l, \
                 mock.patch.dict(tg.config, {'monq.raise_errors': False}):  # match normal non-test behavior
             t()
@@ -158,7 +160,7 @@ class TestEventTasks(unittest.TestCase):
         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
+        assert ' (error) P:10 allura.tests.exclude_from_rewrite_hook.raise_compound_exception ' in msg
         for x in range(10):
             assert ('assert %d' % x) in t.result
 
@@ -510,16 +512,17 @@ class TestMailTasks(unittest.TestCase):
 
     @td.with_tool('test', 'Tickets', 'bugs')
     def test_receive_autoresponse(self):
-        message = '''Date: Wed, 30 Oct 2013 01:38:40 -0700
-From: <te...@domain.net>
-To: <1...@bugs.test.p.in.localhost>
-Message-ID: <super-unique-id>
-Subject: Not here Re: Message notification
-Precedence: bulk
-X-Autoreply: yes
-Auto-Submitted: auto-replied
-
-I'm not here'''
+        message = dedent('''\
+            Date: Wed, 30 Oct 2013 01:38:40 -0700
+            From: <te...@domain.net>
+            To: <1...@bugs.test.p.in.localhost>
+            Message-ID: <super-unique-id>
+            Subject: Not here Re: Message notification
+            Precedence: bulk
+            X-Autoreply: yes
+            Auto-Submitted: auto-replied
+
+            I'm not here''')
         import forgetracker
         c.user = M.User.by_username('test-admin')
         with mock.patch.object(forgetracker.tracker_main.ForgeTrackerApp, 'handle_message') as hm:
@@ -597,17 +600,6 @@ def _my_event(event_type, testcase, *args, **kwargs):
     testcase.called_with.append((args, kwargs))
 
 
-@task
-def raise_exc():
-    errs = []
-    for x in range(10):
-        try:
-            assert False, 'assert %d' % x
-        except Exception:
-            errs.append(sys.exc_info())
-    raise CompoundError(*errs)
-
-
 class _TestArtifact(M.Artifact):
     _shorthand_id = FieldProperty(str)
     text = FieldProperty(str)
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index 5b9d5b1f2..8237847d6 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -132,6 +132,7 @@ class TestValidators(TestWebhookBase):
 
 @with_nose_compatibility
 class TestWebhookController(TestController):
+
     def setup_method(self, method):
         super().setup_method(method)
         self.patches = self.monkey_patch()
@@ -182,6 +183,56 @@ class TestWebhookController(TestController):
         else:
             assert False, 'Validation error not found'
 
+    def test_AAAA_WORKAROUND__edit(self):
+        """
+        This must run first in this test class for unknown reasons ever since
+            https://github.com/TurboGears/tg2/commit/02fb49b14e70fdd8ac16973488fb3637e5e59114
+
+        If any test runs the self.app.post from create_webhook before this one, then this test will fail on:
+            with td.audits(msg):
+                r = form.submit()
+        because WebhookValidator's `value` will be "create" instead of an objectid str
+
+        Maybe something to do with WebhookControllerMeta setup of `validate` decorators?
+        """
+        data1 = {'url': 'http://httpbin.org/post',
+                 'secret': 'secret'}
+        data2 = {'url': 'http://example.com/hook',
+                 'secret': 'secret2'}
+        self.create_webhook(data1).follow()
+        self.create_webhook(data2).follow()
+        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 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(
+            data1['url'], form['url'].value, 'secret changed')
+        with td.audits(msg):
+            r = form.submit()
+        wf = json.loads(self.webflash(r))
+        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 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)
+        form = r.forms[0]
+        form['url'] = data2['url']
+        r = form.submit()
+        self.find_error(r, '_the_form',
+                        '"repo-push" webhook already exists for Git http://example.com/hook',
+                        form_type='edit')
+
     def test_access(self):
         self.app.get(self.url + '/repo-push/')
         self.app.get(self.url + '/repo-push/',
@@ -256,56 +307,6 @@ class TestWebhookController(TestController):
         self.find_error(r, 'url',
                         'You must provide a full domain name (like qwer.com)')
 
-    def test_AAAA_WORKAROUND__edit(self):
-        """
-        This must run first in this test class for unknown reasons ever since
-            https://github.com/TurboGears/tg2/commit/02fb49b14e70fdd8ac16973488fb3637e5e59114
-
-        If any test runs the self.app.post from create_webhook before this one, then this test will fail on:
-            with td.audits(msg):
-                r = form.submit()
-        because WebhookValidator's `value` will be "create" instead of an objectid str
-
-        Maybe something to do with WebhookControllerMeta setup of `validate` decorators?
-        """
-        data1 = {'url': 'http://httpbin.org/post',
-                 'secret': 'secret'}
-        data2 = {'url': 'http://example.com/hook',
-                 'secret': 'secret2'}
-        self.create_webhook(data1).follow()
-        self.create_webhook(data2).follow()
-        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 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(
-            data1['url'], form['url'].value, 'secret changed')
-        with td.audits(msg):
-            r = form.submit()
-        wf = json.loads(self.webflash(r))
-        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 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)
-        form = r.forms[0]
-        form['url'] = data2['url']
-        r = form.submit()
-        self.find_error(r, '_the_form',
-                        '"repo-push" webhook already exists for Git http://example.com/hook',
-                        form_type='edit')
-
     def test_edit_validation(self):
         invalid = M.Webhook(
             type='invalid type',


[allura] 08/10: [#8455] added pytest.ini

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-part2
in repository https://gitbox.apache.org/repos/asf/allura.git

commit e9e7bd1826b9db7c6e282131556eb54ecc36a656
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Fri Sep 23 17:21:15 2022 +0000

    [#8455] added pytest.ini
---
 pytest.ini | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 000000000..3d38df179
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,15 @@
+[pytest]
+# see python -W and pytest filterwarnings
+# https://docs.python.org/3/using/cmdline.html#cmdoption-w
+# https://docs.pytest.org/en/6.2.x/reference.html#ini-options-ref
+filterwarnings =
+    ignore::DeprecationWarning
+
+# our patterns are listed first:  then ".*" and following are defaults from https://github.com/pytest-dev/pytest/blob/main/src/_pytest/main.py#L52
+norecursedirs = templates_responsive resources images js data docs public *.egg-info __pycache__ .* *.egg _darcs build CVS dist node_modules venv {arch}
+
+# legacy|xunit1|xunit2
+#junit_family = legacy
+
+# no|log|system-out|system-err|out-err|all
+junit_logging = all
\ No newline at end of file


[allura] 03/10: 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-part2
in repository https://gitbox.apache.org/repos/asf/allura.git

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

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

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


[allura] 01/10: 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-part2
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 94117777153803d986b867e5c44f33d25159f02d
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        | 482 ++++++++++-----------
 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                                   |  22 +-
 55 files changed, 1669 insertions(+), 1661 deletions(-)

diff --git a/Allura/allura/tests/decorators.py b/Allura/allura/tests/decorators.py
index 34854ad0f..4e6711a4a 100644
--- a/Allura/allura/tests/decorators.py
+++ b/Allura/allura/tests/decorators.py
@@ -237,7 +237,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 4c4dddc14..27f62dfe8 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -80,7 +80,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
@@ -88,8 +88,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(
@@ -137,8 +137,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')
@@ -158,11 +158,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):
@@ -191,8 +191,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')
@@ -243,19 +243,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',
@@ -269,9 +269,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
@@ -283,24 +283,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):
@@ -530,25 +530,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():
@@ -566,8 +566,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):
@@ -611,8 +611,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):
@@ -633,17 +633,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')
@@ -677,7 +677,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')
@@ -688,7 +688,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
@@ -706,7 +706,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):
@@ -725,14 +725,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',
@@ -756,22 +756,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 = {
@@ -782,28 +782,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 = {
@@ -817,20 +817,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):
@@ -938,10 +938,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(
@@ -985,7 +985,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',
@@ -999,7 +999,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'}):
@@ -1024,7 +1024,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'}):
@@ -1085,7 +1085,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()
@@ -1093,8 +1093,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()
@@ -1104,28 +1104,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()
@@ -1136,28 +1136,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',
@@ -1176,7 +1176,7 @@ class TestAuthRest(TestRestApiBase):
                     'api_url': 'http://localhost/rest/p/test/docs/',
                 },
             ]
-        })
+        }
 
 
 class TestPreferences(TestController):
@@ -1259,8 +1259,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'
@@ -1272,8 +1272,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',
@@ -1283,7 +1283,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',
@@ -1292,7 +1292,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',
@@ -1301,7 +1301,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'
@@ -1389,8 +1389,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',
@@ -1446,8 +1446,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(
@@ -1558,7 +1558,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)
 
 
@@ -1624,7 +1624,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')
@@ -1644,7 +1644,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')
@@ -1684,9 +1684,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):
@@ -1695,21 +1695,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()
@@ -1739,16 +1739,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):
@@ -1799,7 +1799,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()
@@ -1823,7 +1823,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
@@ -1836,24 +1836,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):
         user = M.User.by_username('test-admin')
@@ -2140,8 +2140,8 @@ class TestOAuthAccessToken(TestController):
         oauth_params = dict(self.oauth_params, signature_type=signature_type)
         r = self.app.get(*oauth1_webtest('/rest/oauth/access_token', self.oauth_params))
         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
 
     def test_access_token_ok_by_query(self):
         self.test_access_token_ok(signature_type='query')
@@ -2152,8 +2152,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):
@@ -2162,8 +2162,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/')
@@ -2174,21 +2174,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):
@@ -2204,14 +2204,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):
@@ -2228,12 +2228,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):
@@ -2243,31 +2243,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')
@@ -2279,28 +2279,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',
@@ -2308,8 +2308,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
@@ -2319,14 +2319,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')
@@ -2338,32 +2338,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'
@@ -2371,7 +2371,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)
@@ -2383,7 +2383,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):
@@ -2402,13 +2402,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/')
@@ -2417,7 +2417,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):
@@ -2457,13 +2457,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
@@ -2473,22 +2473,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
@@ -2498,13 +2498,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']
 
@@ -2512,9 +2512,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())
@@ -2522,20 +2522,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):
@@ -2543,28 +2542,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']
@@ -2573,13 +2572,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()
@@ -2592,26 +2590,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()
@@ -2636,7 +2633,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
@@ -2647,7 +2644,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):
@@ -2669,7 +2666,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)
@@ -2678,7 +2675,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):
@@ -2705,11 +2702,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()
@@ -2735,7 +2731,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
@@ -2748,11 +2744,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):
@@ -2785,7 +2781,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):
@@ -2793,13 +2789,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()
@@ -2807,14 +2803,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):
@@ -2822,9 +2818,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')
@@ -2834,7 +2830,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 038fb21c8..7384dde8f 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 2d938e146..8f71e00ca 100644
--- a/requirements.in
+++ b/requirements.in
@@ -55,6 +55,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 840326b53..b0cffa15b 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
@@ -64,9 +66,14 @@ html5lib==1.1
 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
@@ -93,6 +100,8 @@ oauthlib==3.2.1
     # via
     #   -r requirements.in
     #   requests-oauthlib
+packaging==21.3
+    # via pytest
 paginate==0.5.6
     # via -r requirements.in
 paste==3.5.1
@@ -108,8 +117,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
@@ -121,10 +134,15 @@ pymongo==3.11.4
     #   -r requirements.in
     #   activitystream
     #   ming
+pyparsing==2.4.7
+    # via
+    #   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
@@ -184,6 +202,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] 04/10: 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-part2
in repository https://gitbox.apache.org/repos/asf/allura.git

commit fae235ff06d74be38633d82af93491caec9c5136
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, 880 insertions(+), 869 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..4d4b3b592 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
@@ -49,759 +48,15 @@ from forgewiki import model as WM
 from forgeblog import model as BM
 
 
-def squish_spaces(text):
-    # \s is whitespace
-    # \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"""
+def setup_module(module):
     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 squish_spaces(text):
+    # \s is whitespace
+    # \xa0 is &nbsp; in unicode form
+    return re.sub(r'[\s\xa0]+', ' ', text)
 
 
 def get_project_names(r):
@@ -826,10 +81,701 @@ 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_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 +971,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 +1032,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 9121f6085..02ff29132 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] 09/10: [#8455] converted yield test to pytest.mark.parametrize

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-part2
in repository https://gitbox.apache.org/repos/asf/allura.git

commit b92975160a9569c97ec52df57038093116128254
Author: Dillon Walls <di...@slashdotmedia.com>
AuthorDate: Fri Sep 23 18:43:16 2022 +0000

    [#8455] converted yield test to pytest.mark.parametrize
---
 Allura/allura/tests/functional/test_auth.py | 52 +++++++++++++++--------------
 1 file changed, 27 insertions(+), 25 deletions(-)

diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index 06a3daf9d..c016a39b1 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -32,6 +32,7 @@ from ming.orm.ormsession import ThreadLocalORMSession, session
 from tg import config, expose
 from mock import patch, Mock
 import mock
+import pytest
 from tg import tmpl_context as c, app_globals as g
 
 from allura.tests import TestController
@@ -562,36 +563,37 @@ class TestAuth(TestController):
         assert hash_expiry == '04-08-2020'
         return user
 
-    def test_token_generator(self):
+    @pytest.mark.parametrize(['change_params'], [
+        pytest.param({'new_addr.addr': 'test_abcd@domain.net',  # Change primary address
+                      'primary_addr': 'test@example.com'},
+                     id='change_primary'),
+        pytest.param({'new_addr.addr': 'test@example.com',  # Claim new address
+                      'new_addr.claim': 'Claim Address',
+                      'primary_addr': 'test-admin@users.localhost',
+                      'password': 'foo',
+                      'preferences.email_format': 'plain'},
+                     id='claim_new'),
+        pytest.param({'addr-1.ord': '1',  # remove test-admin@users.localhost
+                      'addr-1.delete': 'on',
+                      'addr-2.ord': '2',
+                      'new_addr.addr': '',
+                      'primary_addr': 'test-admin@users.localhost',
+                      'password': 'foo',
+                      'preferences.email_format': 'plain'},
+                     id='remove_one'),
+        pytest.param({'addr-1.ord': '1',  # Remove email
+                      'addr-2.ord': '2',
+                      'addr-2.delete': 'on',
+                      'new_addr.addr': '',
+                      'primary_addr': 'test-admin@users.localhost'},
+                     id='remove_all'),
+    ])
+    def test_email_change_invalidates_token(self, change_params):
         """ Generates new token invalidation tests.
 
         The tests cover: changing, claiming, updating, removing email addresses.
         :returns: email_change_invalidates_token
         """
-        _params = [{'new_addr.addr': 'test_abcd@domain.net',  # Change primary address
-                    'primary_addr': 'test@example.com', },
-                   {'new_addr.addr': 'test@example.com',  # Claim new address
-                    'new_addr.claim': 'Claim Address',
-                    'primary_addr': 'test-admin@users.localhost',
-                    'password': 'foo',
-                    'preferences.email_format': 'plain'},
-                   {'addr-1.ord': '1',  # remove test-admin@users.localhost
-                    'addr-1.delete': 'on',
-                    'addr-2.ord': '2',
-                    'new_addr.addr': '',
-                    'primary_addr': 'test-admin@users.localhost',
-                    'password': 'foo',
-                    'preferences.email_format': 'plain'},
-                   {'addr-1.ord': '1',  # Remove email
-                    'addr-2.ord': '2',
-                    'addr-2.delete': 'on',
-                    'new_addr.addr': '',
-                    'primary_addr': 'test-admin@users.localhost'}]
-
-        for param in _params:
-            yield self.email_change_invalidates_token, param
-
-    def email_change_invalidates_token(self, change_params):
         user = self._create_password_reset_hash()
         session(user).flush(user)