You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by he...@apache.org on 2015/05/29 22:40:31 UTC
[09/45] allura git commit: [#7878] Used 2to3 to see what issues would
come up
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/model/test_repo.py
----------------------------------------------------------------------
diff --git a/tests/model/test_repo.py b/tests/model/test_repo.py
new file mode 100644
index 0000000..6e7f3b2
--- /dev/null
+++ b/tests/model/test_repo.py
@@ -0,0 +1,816 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from datetime import datetime
+from collections import defaultdict, OrderedDict
+
+import unittest
+import mock
+from nose.tools import assert_equal
+from pylons import tmpl_context as c
+from bson import ObjectId
+from ming.orm import session
+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
+
+
+class TestGitLikeTree(object):
+
+ def test_set_blob(self):
+ 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'})
+
+ def test_hex(self):
+ tree = M.GitLikeTree()
+ tree.set_blob('/dir/dir2/file', 'file-oid')
+ 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')),
+ 'b file-oid file')
+ assert_equal(repr(tree),
+ 't 96af1772ecce1e6044e6925e595d9373ffcd2615 dir')
+ # the hex() value shouldn't change, it's an important key
+ assert_equal(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)
+
+
+class RepoImplTestBase(object):
+
+ def test_commit_run(self):
+ M.repository.CommitRunDoc.m.remove()
+ commit_ids = list(self.repo.all_commit_ids())
+ # simulate building up a commit run from multiple pushes
+ for c_id in commit_ids:
+ crb = M.repo_refresh.CommitRunBuilder([c_id])
+ crb.run()
+ crb.cleanup()
+ runs = M.repository.CommitRunDoc.m.find().all()
+ self.assertEqual(len(runs), 1)
+ run = runs[0]
+ self.assertEqual(run.commit_ids, commit_ids)
+ self.assertEqual(len(run.commit_ids), len(run.commit_times))
+ self.assertEqual(run.parent_commit_ids, [])
+
+ def test_repair_commit_run(self):
+ commit_ids = list(self.repo.all_commit_ids())
+ # simulate building up a commit run from multiple pushes, but skip the
+ # last commit to simulate a broken commit run
+ for c_id in commit_ids[:-1]:
+ crb = M.repo_refresh.CommitRunBuilder([c_id])
+ crb.run()
+ crb.cleanup()
+ # now repair the commitrun by rebuilding with all commit ids
+ crb = M.repo_refresh.CommitRunBuilder(commit_ids)
+ crb.run()
+ crb.cleanup()
+ runs = M.repository.CommitRunDoc.m.find().all()
+ self.assertEqual(len(runs), 1)
+ run = runs[0]
+ self.assertEqual(run.commit_ids, commit_ids)
+ self.assertEqual(len(run.commit_ids), len(run.commit_times))
+ self.assertEqual(run.parent_commit_ids, [])
+
+
+class RepoTestBase(unittest.TestCase):
+
+ def setUp(self):
+ setup_basic_test()
+
+ @mock.patch('allura.model.repository.Repository.url')
+ def test_refresh_url(self, url):
+ url.return_value = '/p/test/repo'
+ c.app = mock.Mock(**{'config._id': 'deadbeef'})
+ repo = M.repository.Repository()
+ cases = [
+ [
+ None,
+ 'http://localhost:8080/auth/refresh_repo/p/test/repo',
+ ],
+ [
+ 'https://somewhere.com',
+ 'https://somewhere.com/auth/refresh_repo/p/test/repo',
+ ],
+ [
+ 'http://somewhere.com/',
+ 'http://somewhere.com/auth/refresh_repo/p/test/repo',
+ ]]
+ for base_url, result in cases:
+ values = {}
+ if base_url:
+ values['base_url'] = base_url
+ with mock.patch.dict(config, values, clear=True):
+ self.assertEqual(result, repo.refresh_url())
+
+
+class TestLastCommit(unittest.TestCase):
+
+ def setUp(self):
+ setup_basic_test()
+ setup_global_objects()
+ self.repo = mock.Mock(
+ name='repo',
+ _commits=OrderedDict(),
+ _last_commit=None,
+ spec=M.Repository)
+ self.repo.paged_diffs.return_value = {
+ 'added': [],
+ 'removed': [],
+ 'changed': [],
+ 'total': 0,
+ }
+ self.repo.shorthand_for_commit = lambda _id: _id[:6]
+ self.repo.rev_to_commit_id = lambda rev: rev
+ self.repo.log = self._log
+ self._changes = defaultdict(list)
+ self.repo.get_changes = lambda _id: self._changes[_id]
+ self._last_commits = [(None, set())]
+ self.repo._get_last_commit = lambda i, p: self._last_commits.pop()
+ lcids = M.repository.RepositoryImplementation.last_commit_ids.__func__
+ self.repo.last_commit_ids = lambda *a, **k: lcids(self.repo, *a, **k)
+ c.lcid_cache = {}
+
+ def _build_tree(self, commit, path, tree_paths):
+ tree_nodes = []
+ blob_nodes = []
+ sub_paths = defaultdict(list)
+
+ def n(p):
+ m = mock.Mock()
+ m.name = p
+ return m
+ for p in tree_paths:
+ if '/' in p:
+ node, sub = p.split('/', 1)
+ if node not in sub_paths:
+ tree_nodes.append(n(node))
+ sub_paths[node].append(sub)
+ else:
+ blob_nodes.append(n(p))
+ tree = mock.Mock(
+ commit=commit,
+ path=mock.Mock(return_value=path),
+ tree_ids=tree_nodes,
+ blob_ids=blob_nodes,
+ other_ids=[],
+ repo=self.repo,
+ )
+ tree.get_obj_by_path = lambda p: self._build_tree(
+ commit, p, sub_paths[p])
+ tree.__getitem__ = lambda s, p: self._build_tree(
+ commit, p, sub_paths[p])
+ return tree
+
+ def _add_commit(self, msg, tree_paths, diff_paths=None, parents=[]):
+ suser = dict(
+ name='test',
+ email='test@example.com',
+ date=datetime(2013, 1, 1 + len(self.repo._commits)),
+ )
+ commit = M.repository.Commit(
+ _id=str(ObjectId()),
+ message=msg,
+ parent_ids=[parent._id for parent in parents],
+ commited=suser,
+ authored=suser,
+ repo=self.repo,
+ )
+ commit.tree = self._build_tree(commit, '/', tree_paths)
+ commit.get_tree = lambda c: commit.tree
+ self._changes[commit._id].extend(diff_paths or tree_paths)
+ self.repo._commits[commit._id] = commit
+ self._last_commits.append((commit._id, set(diff_paths or tree_paths)))
+ return commit
+
+ def _log(self, revs, path, id_only=True):
+ for commit_id, commit in reversed(list(self.repo._commits.items())):
+ if path in commit.changed_paths:
+ yield commit_id
+
+ def test_single_commit(self):
+ commit1 = self._add_commit('Commit 1', [
+ 'file1',
+ 'dir1/file2',
+ ])
+ lcd = M.repository.LastCommit.get(commit1.tree)
+ self.assertEqual(
+ self.repo._commits[lcd.commit_id].message, commit1.message)
+ self.assertEqual(lcd.path, '')
+ self.assertEqual(len(lcd.entries), 2)
+ self.assertEqual(lcd.by_name['file1'], commit1._id)
+ self.assertEqual(lcd.by_name['dir1'], commit1._id)
+
+ def test_multiple_commits_no_overlap(self):
+ commit1 = self._add_commit('Commit 1', ['file1'])
+ commit2 = self._add_commit(
+ 'Commit 2', ['file1', 'dir1/file1'], ['dir1/file1'], [commit1])
+ commit3 = self._add_commit(
+ 'Commit 3', ['file1', 'dir1/file1', 'file2'], ['file2'], [commit2])
+ lcd = M.repository.LastCommit.get(commit3.tree)
+ self.assertEqual(
+ self.repo._commits[lcd.commit_id].message, commit3.message)
+ self.assertEqual(lcd.commit_id, commit3._id)
+ self.assertEqual(lcd.path, '')
+ self.assertEqual(len(lcd.entries), 3)
+ self.assertEqual(lcd.by_name['file1'], commit1._id)
+ self.assertEqual(lcd.by_name['dir1'], commit2._id)
+ self.assertEqual(lcd.by_name['file2'], commit3._id)
+
+ def test_multiple_commits_with_overlap(self):
+ commit1 = self._add_commit('Commit 1', ['file1'])
+ commit2 = self._add_commit(
+ 'Commit 2', ['file1', 'dir1/file1'], ['dir1/file1'], [commit1])
+ commit3 = self._add_commit(
+ 'Commit 3', ['file1', 'dir1/file1', 'file2'], ['file1', 'file2'], [commit2])
+ lcd = M.repository.LastCommit.get(commit3.tree)
+ self.assertEqual(
+ self.repo._commits[lcd.commit_id].message, commit3.message)
+ self.assertEqual(lcd.path, '')
+ self.assertEqual(len(lcd.entries), 3)
+ self.assertEqual(lcd.by_name['file1'], commit3._id)
+ self.assertEqual(lcd.by_name['dir1'], commit2._id)
+ self.assertEqual(lcd.by_name['file2'], commit3._id)
+
+ def test_multiple_commits_subdir_change(self):
+ commit1 = self._add_commit('Commit 1', ['file1', 'dir1/file1'])
+ commit2 = self._add_commit(
+ 'Commit 2', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file2'], [commit1])
+ commit3 = self._add_commit(
+ 'Commit 3', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file1'], [commit2])
+ lcd = M.repository.LastCommit.get(commit3.tree)
+ self.assertEqual(
+ self.repo._commits[lcd.commit_id].message, commit3.message)
+ self.assertEqual(lcd.path, '')
+ self.assertEqual(len(lcd.entries), 2)
+ self.assertEqual(lcd.by_name['file1'], commit1._id)
+ self.assertEqual(lcd.by_name['dir1'], commit3._id)
+
+ def test_subdir_lcd(self):
+ commit1 = self._add_commit('Commit 1', ['file1', 'dir1/file1'])
+ commit2 = self._add_commit(
+ 'Commit 2', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file2'], [commit1])
+ commit3 = self._add_commit(
+ 'Commit 3', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file1'], [commit2])
+ tree = self._build_tree(commit3, '/dir1', ['file1', 'file2'])
+ lcd = M.repository.LastCommit.get(tree)
+ self.assertEqual(
+ self.repo._commits[lcd.commit_id].message, commit3.message)
+ self.assertEqual(lcd.path, 'dir1')
+ self.assertEqual(len(lcd.entries), 2)
+ self.assertEqual(lcd.by_name['file1'], commit3._id)
+ self.assertEqual(lcd.by_name['file2'], commit2._id)
+
+ def test_subdir_lcd_prev_commit(self):
+ commit1 = self._add_commit('Commit 1', ['file1', 'dir1/file1'])
+ commit2 = self._add_commit(
+ 'Commit 2', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file2'], [commit1])
+ commit3 = self._add_commit(
+ 'Commit 3', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file1'], [commit2])
+ commit4 = self._add_commit(
+ 'Commit 4', ['file1', 'dir1/file1', 'dir1/file2', 'file2'], ['file2'], [commit3])
+ tree = self._build_tree(commit4, '/dir1', ['file1', 'file2'])
+ lcd = M.repository.LastCommit.get(tree)
+ self.assertEqual(
+ self.repo._commits[lcd.commit_id].message, commit3.message)
+ self.assertEqual(lcd.path, 'dir1')
+ self.assertEqual(len(lcd.entries), 2)
+ self.assertEqual(lcd.by_name['file1'], commit3._id)
+ self.assertEqual(lcd.by_name['file2'], commit2._id)
+
+ def test_subdir_lcd_always_empty(self):
+ commit1 = self._add_commit('Commit 1', ['file1', 'dir1'])
+ commit2 = self._add_commit(
+ 'Commit 2', ['file1', 'file2'], ['file2'], [commit1])
+ tree = self._build_tree(commit2, '/dir1', [])
+ lcd = M.repository.LastCommit.get(tree)
+ self.assertEqual(
+ self.repo._commits[lcd.commit_id].message, commit1.message)
+ self.assertEqual(lcd.path, 'dir1')
+ self.assertEqual(lcd.entries, [])
+
+ def test_subdir_lcd_emptied(self):
+ commit1 = self._add_commit('Commit 1', ['file1', 'dir1/file1'])
+ commit2 = self._add_commit(
+ 'Commit 2', ['file1'], ['dir1/file1'], [commit1])
+ tree = self._build_tree(commit2, '/dir1', [])
+ lcd = M.repository.LastCommit.get(tree)
+ self.assertEqual(
+ self.repo._commits[lcd.commit_id].message, commit2.message)
+ self.assertEqual(lcd.path, 'dir1')
+ self.assertEqual(lcd.entries, [])
+
+ def test_existing_lcd_unchained(self):
+ commit1 = self._add_commit('Commit 1', ['file1', 'dir1/file1'])
+ commit2 = self._add_commit(
+ 'Commit 2', ['file1', 'dir1/file1', 'dir1/file2'], ['dir1/file2'], [commit1])
+ commit3 = self._add_commit(
+ 'Commit 3', ['file1', 'dir1/file1', 'dir1/file2'], ['file1'], [commit2])
+ prev_lcd = M.repository.LastCommit(
+ path='dir1',
+ commit_id=commit2._id,
+ entries=[
+ dict(
+ name='file1',
+ commit_id=commit1._id),
+ dict(
+ name='file2',
+ commit_id=commit2._id),
+ ],
+ )
+ session(prev_lcd).flush()
+ tree = self._build_tree(commit3, '/dir1', ['file1', 'file2'])
+ lcd = M.repository.LastCommit.get(tree)
+ self.assertEqual(lcd._id, prev_lcd._id)
+ self.assertEqual(
+ self.repo._commits[lcd.commit_id].message, commit2.message)
+ self.assertEqual(lcd.path, 'dir1')
+ self.assertEqual(lcd.entries, prev_lcd.entries)
+
+ def test_existing_lcd_partial(self):
+ commit1 = self._add_commit('Commit 1', ['file1'])
+ commit2 = self._add_commit(
+ 'Commit 2', ['file1', 'file2'], ['file2'], [commit1])
+ commit3 = self._add_commit(
+ 'Commit 3', ['file1', 'file2', 'file3'], ['file3'], [commit2])
+ commit4 = self._add_commit(
+ 'Commit 4', ['file1', 'file2', 'file3', 'file4'], ['file2', 'file4'], [commit3])
+ prev_lcd = M.repository.LastCommit(
+ path='',
+ commit_id=commit3._id,
+ entries=[
+ dict(
+ name='file1',
+ commit_id=commit1._id),
+ dict(
+ name='file2',
+ commit_id=commit2._id),
+ dict(
+ name='file3',
+ commit_id=commit3._id),
+ ],
+ )
+ session(prev_lcd).flush()
+ lcd = M.repository.LastCommit.get(commit4.tree)
+ self.assertEqual(
+ self.repo._commits[lcd.commit_id].message, commit4.message)
+ self.assertEqual(lcd.path, '')
+ self.assertEqual(len(lcd.entries), 4)
+ self.assertEqual(lcd.by_name['file1'], commit1._id)
+ self.assertEqual(lcd.by_name['file2'], commit4._id)
+ self.assertEqual(lcd.by_name['file3'], commit3._id)
+ self.assertEqual(lcd.by_name['file4'], commit4._id)
+
+ def test_missing_add_record(self):
+ self._add_commit('Commit 1', ['file1'])
+ commit2 = self._add_commit('Commit 2', ['file2'])
+ commit2.changed_paths = []
+ result = self.repo.last_commit_ids(commit2, ['file2'])
+ assert_equal(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})
+
+ def test_timeout(self):
+ commit1 = self._add_commit('Commit 1', ['file1'])
+ commit2 = self._add_commit(
+ 'Commit 2', ['file1', 'dir1/file1'], ['dir1/file1'], [commit1])
+ commit3 = self._add_commit(
+ 'Commit 3', ['file1', 'dir1/file1', 'file2'], ['file2'], [commit2])
+ with h.push_config(config, lcd_timeout=-1000):
+ lcd = M.repository.LastCommit.get(commit3.tree)
+ self.assertEqual(
+ self.repo._commits[lcd.commit_id].message, commit3.message)
+ self.assertEqual(lcd.commit_id, commit3._id)
+ self.assertEqual(lcd.path, '')
+ self.assertEqual(len(lcd.entries), 1)
+ self.assertEqual(lcd.by_name['file2'], commit3._id)
+
+ def test_loop(self):
+ commit1 = self._add_commit('Commit 1', ['file1'])
+ commit2 = self._add_commit(
+ 'Commit 2', ['file1', 'dir1/file1'], ['dir1/file1'], [commit1])
+ commit3 = self._add_commit(
+ 'Commit 3', ['file1', 'dir1/file1', 'file2'], ['file2'], [commit2])
+ commit2.parent_ids = [commit3._id]
+ session(commit2).flush(commit2)
+ lcd = M.repository.LastCommit.get(commit3.tree)
+ self.assertEqual(
+ self.repo._commits[lcd.commit_id].message, commit3.message)
+ self.assertEqual(lcd.commit_id, commit3._id)
+ self.assertEqual(lcd.path, '')
+ self.assertEqual(len(lcd.entries), 3)
+ self.assertEqual(lcd.by_name['dir1'], commit2._id)
+ self.assertEqual(lcd.by_name['file2'], commit3._id)
+
+
+class TestModelCache(unittest.TestCase):
+
+ def setUp(self):
+ self.cache = M.repository.ModelCache()
+
+ def test_normalize_query(self):
+ self.assertEqual(self.cache._normalize_query(
+ {'foo': 1, 'bar': 2}), (('bar', 2), ('foo', 1)))
+
+ def test_model_query(self):
+ q = mock.Mock(spec_set=['query'], query='foo')
+ m = mock.Mock(spec_set=['m'], m='bar')
+ n = mock.Mock(spec_set=['foo'], foo='qux')
+ self.assertEqual(self.cache._model_query(q), 'foo')
+ self.assertEqual(self.cache._model_query(m), 'bar')
+ self.assertRaises(AttributeError, self.cache._model_query, [n])
+
+ @mock.patch.object(M.repository.Tree.query, 'get')
+ @mock.patch.object(M.repository.LastCommit.query, 'get')
+ def test_get(self, lc_get, tr_get):
+ tree = tr_get.return_value = mock.Mock(
+ spec=['_id', 'val'], _id='foo', val='bar')
+ lcd = lc_get.return_value = mock.Mock(
+ spec=['_id', 'val'], _id='foo', val='qux')
+
+ val = self.cache.get(M.repository.Tree, {'_id': 'foo'})
+ tr_get.assert_called_with(_id='foo')
+ self.assertEqual(val, tree)
+
+ val = self.cache.get(M.repository.LastCommit, {'_id': 'foo'})
+ lc_get.assert_called_with(_id='foo')
+ self.assertEqual(val, lcd)
+
+ @mock.patch.object(M.repository.Tree.query, 'get')
+ def test_get_no_query(self, tr_get):
+ tree1 = tr_get.return_value = mock.Mock(
+ spec=['_id', 'val'], _id='foo', val='bar')
+ val = self.cache.get(M.repository.Tree, {'_id': 'foo'})
+ tr_get.assert_called_once_with(_id='foo')
+ self.assertEqual(val, tree1)
+
+ tree2 = tr_get.return_value = mock.Mock(_id='foo', val='qux')
+ val = self.cache.get(M.repository.Tree, {'_id': 'foo'})
+ tr_get.assert_called_once_with(_id='foo')
+ self.assertEqual(val, tree1)
+
+ @mock.patch.object(M.repository.TreesDoc.m, 'get')
+ def test_get_doc(self, tr_get):
+ trees = tr_get.return_value = mock.Mock(
+ spec=['_id', 'val'], _id='foo', val='bar')
+ val = self.cache.get(M.repository.TreesDoc, {'_id': 'foo'})
+ tr_get.assert_called_once_with(_id='foo')
+ self.assertEqual(val, trees)
+
+ def test_set(self):
+ tree = mock.Mock(spec=['_id', 'test_set'], _id='foo', val='test_set')
+ self.cache.set(M.repository.Tree, {'val': 'test_set'}, tree)
+ self.assertEqual(self.cache._query_cache,
+ {M.repository.Tree: {(('val', 'test_set'),): 'foo'}})
+ self.assertEqual(self.cache._instance_cache,
+ {M.repository.Tree: {'foo': tree}})
+
+ @mock.patch('bson.ObjectId')
+ def test_set_none_id(self, obj_id):
+ obj_id.return_value = 'OBJID'
+ tree = mock.Mock(spec=['_id', 'test_set'], _id=None, val='test_set')
+ self.cache.set(M.repository.Tree, {'val1': 'test_set1'}, tree)
+ self.cache.set(M.repository.Tree, {'val2': 'test_set2'}, tree)
+ self.assertEqual(dict(self.cache._query_cache[M.repository.Tree]), {
+ (('val1', 'test_set1'),): 'OBJID',
+ (('val2', 'test_set2'),): 'OBJID',
+ })
+ self.assertEqual(self.cache._instance_cache,
+ {M.repository.Tree: {'OBJID': tree}})
+ tree._id = '_id'
+ self.assertEqual(
+ self.cache.get(M.repository.Tree, {'val1': 'test_set1'}), tree)
+ self.assertEqual(
+ self.cache.get(M.repository.Tree, {'val2': 'test_set2'}), tree)
+ self.cache.set(M.repository.Tree, {'val1': 'test_set2'}, tree)
+ self.assertEqual(
+ self.cache.get(M.repository.Tree, {'val1': 'test_set1'}), tree)
+ self.assertEqual(
+ self.cache.get(M.repository.Tree, {'val2': 'test_set2'}), tree)
+
+ @mock.patch('bson.ObjectId')
+ def test_set_none_val(self, obj_id):
+ obj_id.return_value = 'OBJID'
+ self.cache.set(M.repository.Tree, {'val1': 'test_set1'}, None)
+ self.cache.set(M.repository.Tree, {'val2': 'test_set2'}, None)
+ self.assertEqual(dict(self.cache._query_cache[M.repository.Tree]), {
+ (('val1', 'test_set1'),): None,
+ (('val2', 'test_set2'),): None,
+ })
+ self.assertEqual(dict(self.cache._instance_cache[M.repository.Tree]), {})
+ tree1 = mock.Mock(spec=['_id', 'val'], _id='tree1', val='test_set')
+ tree2 = mock.Mock(spec=['_model_cache_id', '_id', 'val'],
+ _model_cache_id='tree2', _id='tree1', val='test_set2')
+ self.cache.set(M.repository.Tree, {'val1': 'test_set1'}, tree1)
+ self.cache.set(M.repository.Tree, {'val2': 'test_set2'}, tree2)
+ self.assertEqual(dict(self.cache._query_cache[M.repository.Tree]), {
+ (('val1', 'test_set1'),): 'tree1',
+ (('val2', 'test_set2'),): 'tree2',
+ })
+ self.assertEqual(dict(self.cache._instance_cache[M.repository.Tree]), {
+ 'tree1': tree1,
+ 'tree2': tree2,
+ })
+
+ def test_instance_ids(self):
+ tree1 = mock.Mock(spec=['_id', 'val'], _id='id1', val='tree1')
+ tree2 = mock.Mock(spec=['_id', 'val'], _id='id2', val='tree2')
+ self.cache.set(M.repository.Tree, {'val': 'tree1'}, tree1)
+ self.cache.set(M.repository.Tree, {'val': 'tree2'}, tree2)
+ self.assertEqual(set(self.cache.instance_ids(M.repository.Tree)),
+ set(['id1', 'id2']))
+ self.assertEqual(self.cache.instance_ids(M.repository.LastCommit), [])
+
+ @mock.patch.object(M.repository.Tree.query, 'find')
+ def test_batch_load(self, tr_find):
+ # cls, query, attrs
+ m1 = mock.Mock(spec=['_id', 'foo', 'qux'], _id='id1', foo=1, qux=3)
+ m2 = mock.Mock(spec=['_id', 'foo', 'qux'], _id='id2', foo=2, qux=5)
+ tr_find.return_value = [m1, m2]
+
+ self.cache.batch_load(M.repository.Tree, {'foo': {'$in': 'bar'}})
+ tr_find.assert_called_with({'foo': {'$in': 'bar'}})
+ self.assertEqual(self.cache._query_cache[M.repository.Tree], {
+ (('foo', 1),): 'id1',
+ (('foo', 2),): 'id2',
+ })
+ self.assertEqual(self.cache._instance_cache[M.repository.Tree], {
+ 'id1': m1,
+ 'id2': m2,
+ })
+
+ @mock.patch.object(M.repository.Tree.query, 'find')
+ def test_batch_load_attrs(self, tr_find):
+ # cls, query, attrs
+ m1 = mock.Mock(spec=['_id', 'foo', 'qux'], _id='id1', foo=1, qux=3)
+ m2 = mock.Mock(spec=['_id', 'foo', 'qux'], _id='id2', foo=2, qux=5)
+ tr_find.return_value = [m1, m2]
+
+ self.cache.batch_load(M.repository.Tree, {'foo': {'$in': 'bar'}}, ['qux'])
+ tr_find.assert_called_with({'foo': {'$in': 'bar'}})
+ self.assertEqual(self.cache._query_cache[M.repository.Tree], {
+ (('qux', 3),): 'id1',
+ (('qux', 5),): 'id2',
+ })
+ self.assertEqual(self.cache._instance_cache[M.repository.Tree], {
+ 'id1': m1,
+ 'id2': m2,
+ })
+
+ def test_pruning(self):
+ cache = M.repository.ModelCache(max_queries=3, max_instances=2)
+ # ensure cache expires as LRU
+ tree1 = mock.Mock(spec=['_id', '_val'], _id='foo', val='bar')
+ tree2 = mock.Mock(spec=['_id', '_val'], _id='qux', val='fuz')
+ tree3 = mock.Mock(spec=['_id', '_val'], _id='f00', val='b4r')
+ tree4 = mock.Mock(spec=['_id', '_val'], _id='foo', val='zaz')
+ cache.set(M.repository.Tree, {'_id': 'foo'}, tree1)
+ cache.set(M.repository.Tree, {'_id': 'qux'}, tree2)
+ cache.set(M.repository.Tree, {'_id': 'f00'}, tree3)
+ cache.set(M.repository.Tree, {'_id': 'foo'}, tree4)
+ cache.get(M.repository.Tree, {'_id': 'f00'})
+ cache.set(M.repository.Tree, {'val': 'b4r'}, tree3)
+ self.assertEqual(cache._query_cache, {
+ M.repository.Tree: {
+ (('_id', 'foo'),): 'foo',
+ (('_id', 'f00'),): 'f00',
+ (('val', 'b4r'),): 'f00',
+ },
+ })
+ self.assertEqual(cache._instance_cache, {
+ M.repository.Tree: {
+ 'f00': tree3,
+ 'foo': tree4,
+ },
+ })
+
+ def test_pruning_query_vs_instance(self):
+ cache = M.repository.ModelCache(max_queries=3, max_instances=2)
+ # ensure cache expires as LRU
+ tree1 = mock.Mock(spec=['_id', '_val'], _id='keep', val='bar')
+ tree2 = mock.Mock(spec=['_id', '_val'], _id='tree2', val='fuz')
+ tree3 = mock.Mock(spec=['_id', '_val'], _id='tree3', val='b4r')
+ tree4 = mock.Mock(spec=['_id', '_val'], _id='tree4', val='zaz')
+ cache.set(M.repository.Tree, {'keep_query_1': 'bar'}, tree1)
+ cache.set(M.repository.Tree, {'drop_query_1': 'bar'}, tree2)
+ # should refresh tree1 in _instance_cache
+ cache.set(M.repository.Tree, {'keep_query_2': 'bar'}, tree1)
+ # should drop tree2, not tree1, from _instance_cache
+ cache.set(M.repository.Tree, {'drop_query_2': 'bar'}, tree3)
+ self.assertEqual(cache._query_cache[M.repository.Tree], {
+ (('drop_query_1', 'bar'),): 'tree2',
+ (('keep_query_2', 'bar'),): 'keep',
+ (('drop_query_2', 'bar'),): 'tree3',
+ })
+ self.assertEqual(cache._instance_cache[M.repository.Tree], {
+ 'keep': tree1,
+ 'tree3': tree3,
+ })
+
+ @mock.patch('bson.ObjectId')
+ def test_pruning_no_id(self, obj_id):
+ obj_id.side_effect = ['id1', 'id2', 'id3']
+ cache = M.repository.ModelCache(max_queries=3, max_instances=2)
+ # ensure cache considers same instance equal to itself, even if no _id
+ tree1 = mock.Mock(spec=['val'], val='bar')
+ cache.set(M.repository.Tree, {'query_1': 'bar'}, tree1)
+ cache.set(M.repository.Tree, {'query_2': 'bar'}, tree1)
+ cache.set(M.repository.Tree, {'query_3': 'bar'}, tree1)
+ self.assertEqual(cache._instance_cache[M.repository.Tree], {
+ 'id1': tree1,
+ })
+ self.assertEqual(cache._query_cache[M.repository.Tree], {
+ (('query_1', 'bar'),): 'id1',
+ (('query_2', 'bar'),): 'id1',
+ (('query_3', 'bar'),): 'id1',
+ })
+
+ @mock.patch('bson.ObjectId')
+ def test_pruning_none(self, obj_id):
+ obj_id.side_effect = ['id1', 'id2', 'id3']
+ cache = M.repository.ModelCache(max_queries=3, max_instances=2)
+ # ensure cache doesn't store None instances
+ cache.set(M.repository.Tree, {'query_1': 'bar'}, None)
+ cache.set(M.repository.Tree, {'query_2': 'bar'}, None)
+ cache.set(M.repository.Tree, {'query_3': 'bar'}, None)
+ self.assertEqual(cache._instance_cache[M.repository.Tree], {})
+ self.assertEqual(cache._query_cache[M.repository.Tree], {
+ (('query_1', 'bar'),): None,
+ (('query_2', 'bar'),): None,
+ (('query_3', 'bar'),): None,
+ })
+
+ @mock.patch('allura.model.repository.session')
+ @mock.patch.object(M.repository.Tree.query, 'get')
+ def test_pruning_query_flush(self, tr_get, session):
+ cache = M.repository.ModelCache(max_queries=3, max_instances=2)
+ # ensure cache doesn't store None instances
+ tree1 = mock.Mock(name='tree1',
+ spec=['_id', '_val'], _id='tree1', val='bar')
+ tree2 = mock.Mock(name='tree2',
+ spec=['_id', '_val'], _id='tree2', val='fuz')
+ tr_get.return_value = tree2
+ cache.set(M.repository.Tree, {'_id': 'tree1'}, tree1)
+ cache.set(M.repository.Tree, {'_id': 'tree2'}, tree2)
+ cache.get(M.repository.Tree, {'query_1': 'tree2'})
+ cache.get(M.repository.Tree, {'query_2': 'tree2'})
+ cache.get(M.repository.Tree, {'query_3': 'tree2'})
+ self.assertEqual(cache._query_cache[M.repository.Tree], {
+ (('query_1', 'tree2'),): 'tree2',
+ (('query_2', 'tree2'),): 'tree2',
+ (('query_3', 'tree2'),): 'tree2',
+ })
+ self.assertEqual(cache._instance_cache[M.repository.Tree], {
+ 'tree1': tree1,
+ 'tree2': tree2,
+ })
+ self.assertEqual(session.call_args_list,
+ [mock.call(tree1), mock.call(tree2)])
+ self.assertEqual(session.return_value.flush.call_args_list,
+ [mock.call(tree1), mock.call(tree2)])
+ assert not session.return_value.expunge.called
+
+ @mock.patch('allura.model.repository.session')
+ def test_pruning_instance_flush(self, session):
+ cache = M.repository.ModelCache(max_queries=3, max_instances=2)
+ # ensure cache doesn't store None instances
+ tree1 = mock.Mock(spec=['_id', '_val'], _id='tree1', val='bar')
+ tree2 = mock.Mock(spec=['_id', '_val'], _id='tree2', val='fuz')
+ tree3 = mock.Mock(spec=['_id', '_val'], _id='tree3', val='qux')
+ cache.set(M.repository.Tree, {'_id': 'tree1'}, tree1)
+ cache.set(M.repository.Tree, {'_id': 'tree2'}, tree2)
+ cache.set(M.repository.Tree, {'_id': 'tree3'}, tree3)
+ self.assertEqual(cache._query_cache[M.repository.Tree], {
+ (('_id', 'tree1'),): 'tree1',
+ (('_id', 'tree2'),): 'tree2',
+ (('_id', 'tree3'),): 'tree3',
+ })
+ self.assertEqual(cache._instance_cache[M.repository.Tree], {
+ 'tree2': tree2,
+ 'tree3': tree3,
+ })
+ session.assert_called_once_with(tree1)
+ session.return_value.flush.assert_called_once_with(tree1)
+ session.return_value.expunge.assert_called_once_with(tree1)
+
+
+class TestMergeRequest(object):
+
+ def setUp(self):
+ setup_basic_test()
+ setup_global_objects()
+ self.mr = M.MergeRequest(
+ app_config=mock.Mock(_id=ObjectId()),
+ downstream={'commit_id': '12345'},
+ )
+ self.mr.app = mock.Mock(forkable=True)
+ self.mr.app.repo.commit.return_value = mock.Mock(_id='09876')
+ self.mr.merge_allowed = mock.Mock(return_value=True)
+
+ def test_can_merge_cache_key(self):
+ key = self.mr.can_merge_cache_key()
+ assert_equal(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)
+ self.mr.can_merge_cache[key] = True
+ assert_equal(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)
+
+ def test_set_can_merge_cache(self):
+ key = self.mr.can_merge_cache_key()
+ assert_equal(self.mr.can_merge_cache, {})
+ self.mr.set_can_merge_cache(True)
+ assert_equal(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})
+
+ def test_can_merge_merged(self):
+ self.mr.status = 'merged'
+ assert_equal(self.mr.can_merge(), True)
+
+ @mock.patch('allura.tasks.repo_tasks.can_merge', autospec=True)
+ def test_can_merge_cached(self, can_merge_task):
+ self.mr.set_can_merge_cache(False)
+ assert_equal(self.mr.can_merge(), False)
+ self.mr.set_can_merge_cache(True)
+ assert_equal(self.mr.can_merge(), True)
+ assert_equal(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)
+ 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)
+
+ @mock.patch('allura.tasks.repo_tasks.merge', autospec=True)
+ def test_merge(self, merge_task):
+ self.mr.merge_task_status = lambda: None
+ self.mr.merge()
+ merge_task.post.assert_called_once_with(self.mr._id)
+
+ merge_task.reset_mock()
+ self.mr.merge_task_status = lambda: 'ready'
+ self.mr.merge()
+ assert_equal(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)
+ repo_tasks.merge.post(self.mr._id)
+ assert_equal(self.mr.merge_task_status(), 'ready')
+ M.MonQTask.run_ready()
+ assert_equal(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)
+ repo_tasks.can_merge.post(self.mr._id)
+ assert_equal(self.mr.can_merge_task_status(), 'ready')
+ M.MonQTask.run_ready()
+ assert_equal(self.mr.can_merge_task_status(), 'complete')
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/templates/__init__.py
----------------------------------------------------------------------
diff --git a/tests/templates/__init__.py b/tests/templates/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/tests/templates/__init__.py
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/templates/jinja_master/__init__.py
----------------------------------------------------------------------
diff --git a/tests/templates/jinja_master/__init__.py b/tests/templates/jinja_master/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/tests/templates/jinja_master/__init__.py
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/templates/jinja_master/test_lib.py
----------------------------------------------------------------------
diff --git a/tests/templates/jinja_master/test_lib.py b/tests/templates/jinja_master/test_lib.py
new file mode 100644
index 0000000..10701d0
--- /dev/null
+++ b/tests/templates/jinja_master/test_lib.py
@@ -0,0 +1,86 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from tg import config
+from mock import Mock
+from nose.tools import assert_equal
+
+from allura.config.app_cfg import ForgeConfig
+from alluratest.controller import setup_config_test
+
+
+def strip_space(s):
+ return ''.join(s.split())
+
+
+class TemplateTest(object):
+ def setUp(self):
+ setup_config_test()
+ forge_config = ForgeConfig()
+ forge_config.setup_jinja_renderer()
+ self.jinja2_env = config['pylons.app_globals'].jinja2_env
+
+
+class TestRelatedArtifacts(TemplateTest):
+
+ def _render_related_artifacts(self, artifact):
+ html = self.jinja2_env.from_string('''
+ {% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+ {{ lib.related_artifacts(artifact) }}
+ ''').render(artifact=artifact)
+ return strip_space(html)
+
+ def test_none(self):
+ artifact = Mock(related_artifacts = lambda: [])
+ assert_equal(self._render_related_artifacts(artifact), '')
+
+ def test_simple(self):
+ other = Mock()
+ other.url.return_value = '/p/test/foo/bar'
+ other.project.name = 'Test Project'
+ other.app_config.options.mount_label = 'Foo'
+ other.link_text.return_value = 'Bar'
+ artifact = Mock(related_artifacts = lambda: [other])
+ assert_equal(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
+ class CommitThing(object):
+ type_s = 'Commit'
+
+ def link_text(self):
+ return '[deadbeef]'
+
+ def url(self):
+ return '/p/test/code/ci/deadbeef'
+
+ artifact = Mock(related_artifacts = lambda: [CommitThing()])
+ assert_equal(self._render_related_artifacts(artifact), strip_space('''
+ <h4>Related</h4>
+ <p>
+ <a href="/p/test/code/ci/deadbeef">Commit: [deadbeef]</a><br>
+ </p>
+ '''))
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/test_app.py
----------------------------------------------------------------------
diff --git a/tests/test_app.py b/tests/test_app.py
new file mode 100644
index 0000000..0953066
--- /dev/null
+++ b/tests/test_app.py
@@ -0,0 +1,71 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from pylons import tmpl_context as c
+import mock
+from ming.base import Object
+
+from alluratest.controller import setup_unit_test
+from allura import app
+
+
+def setUp():
+ 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_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
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/test_commands.py
----------------------------------------------------------------------
diff --git a/tests/test_commands.py b/tests/test_commands.py
new file mode 100644
index 0000000..de8ba1c
--- /dev/null
+++ b/tests/test_commands.py
@@ -0,0 +1,478 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from nose.tools import assert_raises, assert_in
+from datadiff.tools import assert_equal
+from ming.orm import ThreadLocalORMSession
+from mock import Mock, call, patch
+import pymongo
+import pkg_resources
+
+from alluratest.controller import setup_basic_test, setup_global_objects
+from allura.command import base, script, set_neighborhood_features, \
+ create_neighborhood, show_models, taskd_cleanup
+from allura import model as M
+from allura.lib.exceptions import InvalidNBFeatureValueError
+from allura.tests import decorators as td
+
+test_config = pkg_resources.resource_filename(
+ 'allura', '../test.ini') + '#main'
+
+
+class EmptyClass(object):
+ pass
+
+
+def setUp(self):
+ """Method called by nose before running each test"""
+ setup_basic_test()
+ setup_global_objects()
+
+
+def test_script():
+ cmd = script.ScriptCommand('script')
+ cmd.run(
+ [test_config, pkg_resources.resource_filename('allura', 'tests/tscript.py')])
+ assert_raises(ValueError, cmd.run,
+ [test_config, pkg_resources.resource_filename('allura', 'tests/tscript_error.py')])
+
+
+def test_set_neighborhood_max_projects():
+ neighborhood = M.Neighborhood.query.find().first()
+ n_id = neighborhood._id
+ cmd = set_neighborhood_features.SetNeighborhoodFeaturesCommand(
+ 'setnbfeatures')
+
+ # a valid number
+ cmd.run([test_config, str(n_id), 'max_projects', '50'])
+ neighborhood = M.Neighborhood.query.get(_id=n_id)
+ assert neighborhood.features['max_projects'] == 50
+
+ # none is also valid
+ cmd.run([test_config, str(n_id), 'max_projects', 'None'])
+ neighborhood = M.Neighborhood.query.get(_id=n_id)
+ assert neighborhood.features['max_projects'] == None
+
+ # check validation
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'max_projects', 'string'])
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'max_projects', '2.8'])
+
+
+def test_set_neighborhood_private():
+ neighborhood = M.Neighborhood.query.find().first()
+ n_id = neighborhood._id
+ cmd = set_neighborhood_features.SetNeighborhoodFeaturesCommand(
+ 'setnbfeatures')
+
+ # allow private projects
+ cmd.run([test_config, str(n_id), 'private_projects', 'True'])
+ neighborhood = M.Neighborhood.query.get(_id=n_id)
+ assert neighborhood.features['private_projects']
+
+ # disallow private projects
+ cmd.run([test_config, str(n_id), 'private_projects', 'False'])
+ neighborhood = M.Neighborhood.query.get(_id=n_id)
+ assert not neighborhood.features['private_projects']
+
+ # check validation
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'private_projects', 'string'])
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'private_projects', '1'])
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'private_projects', '2.8'])
+
+
+def test_set_neighborhood_google_analytics():
+ neighborhood = M.Neighborhood.query.find().first()
+ n_id = neighborhood._id
+ cmd = set_neighborhood_features.SetNeighborhoodFeaturesCommand(
+ 'setnbfeatures')
+
+ # allow private projects
+ cmd.run([test_config, str(n_id), 'google_analytics', 'True'])
+ neighborhood = M.Neighborhood.query.get(_id=n_id)
+ assert neighborhood.features['google_analytics']
+
+ # disallow private projects
+ cmd.run([test_config, str(n_id), 'google_analytics', 'False'])
+ neighborhood = M.Neighborhood.query.get(_id=n_id)
+ assert not neighborhood.features['google_analytics']
+
+ # check validation
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'google_analytics', 'string'])
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'google_analytics', '1'])
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'google_analytics', '2.8'])
+
+
+def test_set_neighborhood_css():
+ neighborhood = M.Neighborhood.query.find().first()
+ n_id = neighborhood._id
+ cmd = set_neighborhood_features.SetNeighborhoodFeaturesCommand(
+ 'setnbfeatures')
+
+ # none
+ cmd.run([test_config, str(n_id), 'css', 'none'])
+ neighborhood = M.Neighborhood.query.get(_id=n_id)
+ assert neighborhood.features['css'] == 'none'
+
+ # picker
+ cmd.run([test_config, str(n_id), 'css', 'picker'])
+ neighborhood = M.Neighborhood.query.get(_id=n_id)
+ assert neighborhood.features['css'] == 'picker'
+
+ # custom
+ cmd.run([test_config, str(n_id), 'css', 'custom'])
+ neighborhood = M.Neighborhood.query.get(_id=n_id)
+ assert neighborhood.features['css'] == 'custom'
+
+ # check validation
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'css', 'string'])
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'css', '1'])
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'css', '2.8'])
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'css', 'None'])
+ assert_raises(InvalidNBFeatureValueError, cmd.run,
+ [test_config, str(n_id), 'css', 'True'])
+
+
+def test_update_neighborhood():
+ cmd = create_neighborhood.UpdateNeighborhoodCommand('update-neighborhood')
+ cmd.run([test_config, 'Projects', 'True'])
+ # make sure the app_configs get freshly queried
+ ThreadLocalORMSession.close_all()
+ nb = M.Neighborhood.query.get(name='Projects')
+ assert nb.has_home_tool == True
+
+ cmd = create_neighborhood.UpdateNeighborhoodCommand('update-neighborhood')
+ cmd.run([test_config, 'Projects', 'False'])
+ # make sure the app_configs get freshly queried
+ ThreadLocalORMSession.close_all()
+ nb = M.Neighborhood.query.get(name='Projects')
+ assert nb.has_home_tool == False
+
+
+class TestEnsureIndexCommand(object):
+
+ def test_run(self):
+ cmd = show_models.EnsureIndexCommand('ensure_index')
+ cmd.run([test_config])
+
+ def test_update_indexes_order(self):
+ collection = Mock(name='collection')
+ collection.index_information.return_value = {
+ '_id_': {'key': '_id'},
+ '_foo_bar': {'key': [('foo', 1), ('bar', 1)]},
+ }
+ indexes = [
+ Mock(unique=False, index_spec=[('foo', 1)]),
+ ]
+ cmd = show_models.EnsureIndexCommand('ensure_index')
+ cmd._update_indexes(collection, indexes)
+
+ collection_call_order = {}
+ for i, call_ in enumerate(collection.mock_calls):
+ method_name = call_[0]
+ collection_call_order[method_name] = i
+ assert collection_call_order['ensure_index'] < collection_call_order['drop_index'], collection.mock_calls
+
+ def test_update_indexes_unique_changes(self):
+ collection = Mock(name='collection')
+ # expecting these ensure_index calls, we'll make their return values normal
+ # for easier assertions later
+ collection.ensure_index.side_effect = [
+ '_foo_bar_temporary_extra_field_for_indexing',
+ '_foo_bar',
+ '_foo_baz_temporary_extra_field_for_indexing',
+ '_foo_baz',
+ '_foo_baz',
+ '_foo_bar',
+ ]
+ collection.index_information.return_value = {
+ '_id_': {'key': '_id'},
+ '_foo_bar': {'key': [('foo', 1), ('bar', 1)], 'unique': True},
+ '_foo_baz': {'key': [('foo', 1), ('baz', 1)]},
+ }
+ indexes = [
+ Mock(index_spec=[('foo', 1), ('bar', 1)], unique=False, ),
+ Mock(index_spec=[('foo', 1), ('baz', 1)], unique=True, ),
+ ]
+
+ cmd = show_models.EnsureIndexCommand('ensure_index')
+ cmd._update_indexes(collection, indexes)
+
+ assert_equal(collection.mock_calls, [
+ call.index_information(),
+ call.ensure_index(
+ [('foo', 1), ('bar', 1), ('temporary_extra_field_for_indexing', 1)]),
+ call.drop_index('_foo_bar'),
+ call.ensure_index([('foo', 1), ('bar', 1)], unique=False),
+ call.drop_index('_foo_bar_temporary_extra_field_for_indexing'),
+ call.ensure_index(
+ [('foo', 1), ('baz', 1), ('temporary_extra_field_for_indexing', 1)]),
+ call.drop_index('_foo_baz'),
+ call.ensure_index([('foo', 1), ('baz', 1)], unique=True),
+ call.drop_index('_foo_baz_temporary_extra_field_for_indexing'),
+ call.ensure_index([('foo', 1), ('baz', 1)], unique=True),
+ call.ensure_index([('foo', 1), ('bar', 1)], background=True)
+ ])
+
+
+class TestTaskdCleanupCommand(object):
+
+ def setUp(self):
+ 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'
+ self.old_check_task = self.cmd_class._check_task
+ self.cmd_class._check_task = lambda x, p, t: 'OK'
+ self.old_busy_tasks = self.cmd_class._busy_tasks
+ self.cmd_class._busy_tasks = lambda x: []
+ self.old_taskd_pids = self.cmd_class._taskd_pids
+ self.cmd_class._taskd_pids = lambda x: ['1111']
+ self.old_kill_stuck_taskd = self.cmd_class._kill_stuck_taskd
+ self.cmd_class._kill_stuck_taskd = Mock()
+ self.old_complete_suspicious_tasks = self.cmd_class._complete_suspicious_tasks
+ self.cmd_class._complete_suspicious_tasks = lambda x: []
+
+ def tearDown(self):
+ # 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
+ self.cmd_class._busy_tasks = self.old_busy_tasks
+ self.cmd_class._taskd_pids = self.old_taskd_pids
+ self.cmd_class._kill_stuck_taskd = self.old_kill_stuck_taskd
+ self.cmd_class._complete_suspicious_tasks = self.old_complete_suspicious_tasks
+
+ def test_forsaken_tasks(self):
+ # forsaken task
+ task = Mock(state='busy', process='host pid 1111', result='')
+ self.cmd_class._busy_tasks = lambda x: [task]
+ self.cmd_class._taskd_pids = lambda x: ['2222']
+
+ cmd = self.cmd_class('taskd_command')
+ cmd.run([test_config, 'fake.log'])
+ assert task.state == 'error', task.state
+ assert task.result == 'Can\'t find taskd with given pid', task.result
+ assert cmd.error_tasks == [task]
+
+ # task actually running taskd pid == task.process pid == 2222
+ task = Mock(state='busy', process='host pid 2222', result='')
+ self.cmd_class._busy_tasks = lambda x: [task]
+ self.cmd_class._taskd_pids = lambda x: ['2222']
+
+ cmd = self.cmd_class('taskd_command')
+ cmd.run([test_config, 'fake.log'])
+ # nothing should change
+ assert task.state == 'busy', task.state
+ assert task.result == '', task.result
+ assert cmd.error_tasks == []
+
+ def test_stuck_taskd(self):
+ # does not stuck
+ cmd = self.cmd_class('taskd_command')
+ cmd.run([test_config, 'fake.log'])
+ assert cmd.stuck_pids == [], cmd.stuck_pids
+
+ # stuck
+ self.cmd_class._check_taskd_status = lambda x, p: 'STUCK'
+ cmd = self.cmd_class('taskd_command')
+ cmd.run([test_config, 'fake.log'])
+ assert cmd.stuck_pids == ['1111'], cmd.stuck_pids
+
+ # stuck with -k option
+ self.cmd_class._check_taskd_status = lambda x, p: 'STUCK'
+ cmd = self.cmd_class('taskd_command')
+ cmd.run([test_config, '-k', 'fake.log'])
+ cmd._kill_stuck_taskd.assert_called_with('1111')
+ assert cmd.stuck_pids == ['1111'], cmd.stuck_pids
+
+ def test_suspicious_tasks(self):
+ # task1 is lost
+ task1 = Mock(state='busy', process='host pid 1111', result='', _id=1)
+ task2 = Mock(state='busy', process='host pid 1111', result='', _id=2)
+ self.cmd_class._busy_tasks = lambda x: [task1, task2]
+ self.cmd_class._check_task = lambda x, p, t: 'FAIL' if t._id == 1 else 'OK'
+ cmd = self.cmd_class('taskd_command')
+ cmd.run([test_config, 'fake.log'])
+ assert cmd.suspicious_tasks == [task1], cmd.suspicious_tasks
+ assert cmd.error_tasks == [task1], cmd.error_tasks
+ assert task1.state == 'error'
+ assert task1.result == 'Forsaken task'
+
+ # task1 seems lost, but it just moved quickly
+ task1 = Mock(state='complete',
+ process='host pid 1111', result='', _id=1)
+ task2 = Mock(state='busy', process='host pid 1111', result='', _id=2)
+ self.cmd_class._complete_suspicious_tasks = lambda x: [1]
+ self.cmd_class._busy_tasks = lambda x: [task1, task2]
+ self.cmd_class._check_task = lambda x, p, t: 'FAIL' if t._id == 1 else 'OK'
+ cmd = self.cmd_class('taskd_command')
+ cmd.run([test_config, 'fake.log'])
+ assert cmd.suspicious_tasks == [task1], cmd.suspicious_tasks
+ assert cmd.error_tasks == [], cmd.error_tasks
+ assert task1.state == 'complete'
+
+
+# taskd_cleanup unit tests
+def test_status_log_retries():
+ cmd = taskd_cleanup.TaskdCleanupCommand('taskd_command')
+ cmd._taskd_status = Mock()
+ cmd._taskd_status.return_value = ''
+ cmd.options = Mock(num_retry=10)
+ cmd._check_taskd_status(123)
+ expected_calls = [call(123, False if i == 0 else True) for i in range(10)]
+ assert cmd._taskd_status.mock_calls == expected_calls
+
+ cmd._taskd_status = Mock()
+ cmd._taskd_status.return_value = ''
+ cmd.options = Mock(num_retry=3)
+ cmd._check_task(123, Mock())
+ expected_calls = [call(123, False if i == 0 else True) for i in range(3)]
+ assert cmd._taskd_status.mock_calls == expected_calls
+
+
+class TestBackgroundCommand(object):
+
+ cmd = 'allura.command.show_models.ReindexCommand'
+ task_name = 'allura.command.base.run_command'
+
+ 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)
+ task = tasks[0]
+ assert_equal(task.args, [self.cmd, '-p "project 3"'])
+
+ def test_command_doc(self):
+ assert_in('Usage:', show_models.ReindexCommand.__doc__)
+
+ @patch('allura.command.show_models.ReindexCommand')
+ def test_run_command(self, command):
+ command.__name__ = 'ReindexCommand'
+ base.run_command(self.cmd, 'dev.ini -p "project 3"')
+ command(command.__name__).run.assert_called_with(
+ ['dev.ini', '-p', 'project 3'])
+
+ def test_invalid_args(self):
+ M.MonQTask.query.remove()
+ show_models.ReindexCommand.post('--invalid-option')
+ with td.raises(Exception) as e:
+ M.MonQTask.run_ready()
+ assert_in('Error parsing args', str(e.exc))
+
+
+class TestReindexCommand(object):
+
+ @patch('allura.command.show_models.g')
+ def test_skip_solr_delete(self, g):
+ cmd = show_models.ReindexCommand('reindex')
+ cmd.run([test_config, '-p', 'test', '--solr'])
+ assert g.solr.delete.called, 'solr.delete() must be called'
+ g.solr.delete.reset_mock()
+ cmd.run([test_config, '-p', 'test', '--solr', '--skip-solr-delete'])
+ assert not g.solr.delete.called, 'solr.delete() must not be called'
+
+ @patch('pysolr.Solr')
+ def test_solr_hosts_1(self, Solr):
+ cmd = show_models.ReindexCommand('reindex')
+ 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')
+
+ @patch('pysolr.Solr')
+ def test_solr_hosts_list(self, Solr):
+ cmd = show_models.ReindexCommand('reindex')
+ cmd.options, args = cmd.parser.parse_args([
+ '-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(
+ set([Solr.call_args_list[0][0][0], Solr.call_args_list[1][0][0]]),
+ set(['http://blah.com/solr/forge',
+ 'https://other.net/solr/forge'])
+ )
+
+ @patch('allura.command.show_models.utils')
+ def test_project_regex(self, utils):
+ cmd = show_models.ReindexCommand('reindex')
+ cmd.run([test_config, '--project-regex', '^test'])
+ utils.chunked_find.assert_called_once_with(
+ M.Project, {'shortname': {'$regex': '^test'}})
+
+ @patch('allura.command.show_models.add_artifacts')
+ def test_chunked_add_artifacts(self, add_artifacts):
+ cmd = show_models.ReindexCommand('reindex')
+ 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)
+
+ @patch('allura.command.show_models.add_artifacts')
+ def test_post_add_artifacts_too_large(self, add_artifacts):
+ def on_post(chunk, **kw):
+ if len(chunk) > 1:
+ e = pymongo.errors.InvalidDocument(
+ "BSON document too large (16906035 bytes) - the connected server supports BSON document sizes up to 16777216 bytes.")
+ # ming injects a 2nd arg with the document, so we do too
+ e.args = e.args + ("doc: {'task_name': 'allura.tasks.index_tasks.add_artifacts', ........",)
+ raise e
+ add_artifacts.post.side_effect = on_post
+ cmd = show_models.ReindexCommand('reindex')
+ cmd.options, args = cmd.parser.parse_args([])
+ cmd._post_add_artifacts(list(range(5)))
+ kw = {'update_solr': cmd.options.solr, 'update_refs': cmd.options.refs}
+ expected = [
+ call([0, 1, 2, 3, 4], **kw),
+ call([0, 1], **kw),
+ call([0], **kw),
+ call([1], **kw),
+ call([2, 3, 4], **kw),
+ call([2], **kw),
+ call([3, 4], **kw),
+ call([3], **kw),
+ call([4], **kw)
+ ]
+ assert_equal(expected, add_artifacts.post.call_args_list)
+
+ @patch('allura.command.show_models.add_artifacts')
+ def test_post_add_artifacts_other_error(self, add_artifacts):
+ def on_post(chunk, **kw):
+ raise pymongo.errors.InvalidDocument("Cannot encode object...")
+ add_artifacts.post.side_effect = on_post
+ cmd = show_models.ReindexCommand('reindex')
+ cmd.options = Mock(ming_config=None)
+ with td.raises(pymongo.errors.InvalidDocument):
+ cmd._post_add_artifacts(list(range(5)))
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/test_decorators.py
----------------------------------------------------------------------
diff --git a/tests/test_decorators.py b/tests/test_decorators.py
new file mode 100644
index 0000000..ff1775b
--- /dev/null
+++ b/tests/test_decorators.py
@@ -0,0 +1,60 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from unittest import TestCase
+
+from mock import patch
+
+from allura.lib.decorators import task
+
+
+class TestTask(TestCase):
+
+ def test_no_params(self):
+ @task
+ def func():
+ pass
+ self.assertTrue(hasattr(func, 'post'))
+
+ def test_with_params(self):
+ @task(disable_notifications=True)
+ def func():
+ pass
+ self.assertTrue(hasattr(func, 'post'))
+
+ @patch('allura.lib.decorators.c')
+ @patch('allura.model.MonQTask')
+ def test_post(self, c, MonQTask):
+ @task(disable_notifications=True)
+ def func(s, foo=None, **kw):
+ pass
+
+ def mock_post(f, args, kw, delay=None):
+ self.assertTrue(c.project.notifications_disabled)
+ self.assertFalse('delay' in kw)
+ self.assertEqual(delay, 1)
+ self.assertEqual(kw, dict(foo=2))
+ self.assertEqual(args, ('test',))
+ self.assertEqual(f, func)
+
+ c.project.notifications_disabled = False
+ MonQTask.post.side_effect = mock_post
+ func.post('test', foo=2, delay=1)
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/test_diff.py
----------------------------------------------------------------------
diff --git a/tests/test_diff.py b/tests/test_diff.py
new file mode 100644
index 0000000..ca40531
--- /dev/null
+++ b/tests/test_diff.py
@@ -0,0 +1,150 @@
+# -*- coding: utf-8 -*-
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+import unittest
+
+from allura.lib.diff import HtmlSideBySideDiff
+
+
+class TestHtmlSideBySideDiff(unittest.TestCase):
+
+ def setUp(self):
+ self.diff = HtmlSideBySideDiff()
+
+ def test_render_change(self):
+ html = self.diff._render_change(
+ 'aline', 'aline <span class="diff-add">bline</span>',
+ 1, 2, 'aclass', 'bclass')
+ expected = '''
+<tr>
+ <td class="lineno">1</td>
+ <td class="aclass"><pre>aline</pre></td>
+ <td class="lineno">2</td>
+ <td class="bclass"><pre>aline <span class="diff-add">bline</span></pre></td>
+</tr>'''.strip()
+ self.assertEqual(html, expected)
+
+ def test_render_change_default_args(self):
+ html = self.diff._render_change('aline', 'bline')
+ expected = '''
+<tr>
+ <td class="lineno"></td>
+ <td><pre>aline</pre></td>
+ <td class="lineno"></td>
+ <td><pre>bline</pre></td>
+</tr>'''.strip()
+ self.assertEqual(html, expected)
+
+ def test_preprocess(self):
+ d = self.diff
+ self.assertEqual(d._preprocess(None), None)
+ self.assertEqual(d._preprocess('<br> '), '<br>&nbsp;')
+ self.assertEqual(d._preprocess('\ttabbed'), ' tabbed')
+ # test non default tab size
+ d = HtmlSideBySideDiff(2)
+ self.assertEqual(d._preprocess('\ttabbed'), ' tabbed')
+
+ def test_replace_marks(self):
+ line, flag = self.diff._replace_marks('\0+line added\1')
+ self.assertEqual(line, 'line added')
+ self.assertEqual(flag, 'diff-add')
+ line, flag = self.diff._replace_marks('\0-line removed\1')
+ self.assertEqual(line, 'line removed')
+ self.assertEqual(flag, 'diff-rem')
+ line, flag = self.diff._replace_marks('\0^line changed\1')
+ self.assertEqual(line, '<span class="diff-chg">line changed</span>')
+ self.assertEqual(flag, 'diff-chg')
+ line, flag = self.diff._replace_marks('chunk \0+add\1ed')
+ self.assertEqual(line, 'chunk <span class="diff-add">add</span>ed')
+ self.assertEqual(flag, 'diff-chg')
+ line, flag = self.diff._replace_marks('chunk \0-remov\1ed')
+ self.assertEqual(line, 'chunk <span class="diff-rem">remov</span>ed')
+ self.assertEqual(flag, 'diff-chg')
+ line, flag = self.diff._replace_marks('chunk \0^chang\1ed')
+ self.assertEqual(line, 'chunk <span class="diff-chg">chang</span>ed')
+ self.assertEqual(flag, 'diff-chg')
+
+ def test_make_line(self):
+ # context separation
+ d = (None, None, None)
+ expected = '''
+<tr>
+ <td class="lineno"></td>
+ <td class="diff-gap"><pre>...</pre></td>
+ <td class="lineno"></td>
+ <td class="diff-gap"><pre>...</pre></td>
+</tr>'''.strip()
+ self.assertEqual(self.diff._make_line(d), expected)
+ # no change
+ d = ((1, 'aline'), (1, 'aline'), False)
+ expected = '''
+<tr>
+ <td class="lineno">1</td>
+ <td><pre>aline</pre></td>
+ <td class="lineno">1</td>
+ <td><pre>aline</pre></td>
+</tr>'''.strip()
+ self.assertEqual(self.diff._make_line(d), expected)
+ # has change
+ d = ((1, '\0^a\1line'), (1, '\0^b\1line'), True)
+ expected = '''
+<tr>
+ <td class="lineno">1</td>
+ <td class="diff-chg"><pre><span class="diff-chg">a</span>line</pre></td>
+ <td class="lineno">1</td>
+ <td class="diff-chg"><pre><span class="diff-chg">b</span>line</pre></td>
+</tr>'''.strip()
+ self.assertEqual(self.diff._make_line(d), expected)
+
+ def test_make_table(self):
+ a = 'line 1\nline 2'.split('\n')
+ b = 'changed line 1\nchanged line 2'.split('\n')
+ expected = '''
+<table class="side-by-side-diff">
+ <thead>
+ <th class="lineno"></th>
+ <th>file a</th>
+ <th class="lineno"></th>
+ <th>file b</th>
+ </thead>
+<tr>
+ <td class="lineno">1</td>
+ <td class="diff-rem"><pre>line 1</pre></td>
+ <td class="lineno">1</td>
+ <td class="diff-add"><pre>changed line 1</pre></td>
+</tr>
+<tr>
+ <td class="lineno">2</td>
+ <td class="diff-rem"><pre>line 2</pre></td>
+ <td class="lineno">2</td>
+ <td class="diff-add"><pre>changed line 2</pre></td>
+</tr>
+</table>
+'''.strip()
+ html = self.diff.make_table(a, b, 'file a', 'file b')
+ self.assertEqual(html, expected)
+
+ def test_unicode_make_table(self):
+ a = ['строка']
+ b = ['измененная строка']
+ html = self.diff.make_table(a, b, 'file a', 'file b')
+ assert 'строка' in html
http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/tests/test_dispatch.py
----------------------------------------------------------------------
diff --git a/tests/test_dispatch.py b/tests/test_dispatch.py
new file mode 100644
index 0000000..882e559
--- /dev/null
+++ b/tests/test_dispatch.py
@@ -0,0 +1,38 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from allura.tests import TestController
+
+app = None
+
+
+class TestDispatch(TestController):
+
+ validate_skip = True
+
+ def test_dispatch(self):
+ r = self.app.get('/dispatch/foo/')
+ assert r.body == 'index foo', r
+ r = self.app.get('/dispatch/foo/bar')
+ assert r.body == "default(foo)(('bar',))", r
+ self.app.get('/not_found', status=404)
+ self.app.get('/dispatch/', status=404)
+ # self.app.get('/hello/foo/bar', status=404)