import nose import os import shutil import sys import tempfile import unittest import urllib2 try: from cStringIO import StringIO except ImportError: from StringIO import StringIO try: import json except ImportError: import simplejson as json from rbtools.postreview import execute, load_config_file from rbtools.postreview import APIError, GitClient, RepositoryInfo, \ ReviewBoardServer import rbtools.postreview FOO = """\ ARMA virumque cano, Troiae qui primus ab oris Italiam, fato profugus, Laviniaque venit litora, multum ille et terris iactatus et alto vi superum saevae memorem Iunonis ob iram; multa quoque et bello passus, dum conderet urbem, inferretque deos Latio, genus unde Latinum, Albanique patres, atque altae moenia Romae. Musa, mihi causas memora, quo numine laeso, quidve dolens, regina deum tot volvere casus insignem pietate virum, tot adire labores impulerit. Tantaene animis caelestibus irae? """ FOO1 = """\ ARMA virumque cano, Troiae qui primus ab oris Italiam, fato profugus, Laviniaque venit litora, multum ille et terris iactatus et alto vi superum saevae memorem Iunonis ob iram; multa quoque et bello passus, dum conderet urbem, inferretque deos Latio, genus unde Latinum, Albanique patres, atque altae moenia Romae. Musa, mihi causas memora, quo numine laeso, """ FOO2 = """\ ARMA virumque cano, Troiae qui primus ab oris ARMA virumque cano, Troiae qui primus ab oris ARMA virumque cano, Troiae qui primus ab oris Italiam, fato profugus, Laviniaque venit litora, multum ille et terris iactatus et alto vi superum saevae memorem Iunonis ob iram; multa quoque et bello passus, dum conderet urbem, inferretque deos Latio, genus unde Latinum, Albanique patres, atque altae moenia Romae. Musa, mihi causas memora, quo numine laeso, """ FOO3 = """\ ARMA virumque cano, Troiae qui primus ab oris ARMA virumque cano, Troiae qui primus ab oris Italiam, fato profugus, Laviniaque venit litora, multum ille et terris iactatus et alto vi superum saevae memorem Iunonis ob iram; dum conderet urbem, inferretque deos Latio, genus unde Latinum, Albanique patres, atque altae moenia Romae. Albanique patres, atque altae moenia Romae. Musa, mihi causas memora, quo numine laeso, """ def is_exe_in_path(name): """Checks whether an executable is in the user's search path. This expects a name without any system-specific executable extension. It will append the proper extension as necessary. For example, use "myapp" and not "myapp.exe". This will return True if the app is in the path, or False otherwise. Taken from djblets.util.filesystem to avoid an extra dependency """ if sys.platform == 'win32' and not name.endswith('.exe'): name += ".exe" for dir in os.environ['PATH'].split(os.pathsep): if os.path.exists(os.path.join(dir, name)): return True return False class MockHttpUnitTest(unittest.TestCase): def setUp(self): # Save the old http_get and http_post self.saved_http_get = ReviewBoardServer.http_get self.saved_http_post = ReviewBoardServer.http_post self.server = ReviewBoardServer('http://localhost:8080/', RepositoryInfo(), None) ReviewBoardServer.http_get = self._http_method ReviewBoardServer.http_post = self._http_method self.http_response = "" rbtools.postreview.options = OptionsStub() def tearDown(self): ReviewBoardServer.http_get = self.saved_http_get ReviewBoardServer.http_post = self.saved_http_post def _http_method(self, *args, **kwargs): if isinstance(self.http_response, Exception): raise self.http_response else: return self.http_response class OptionsStub(object): def __init__(self): self.debug = True self.guess_summary = False self.guess_description = False self.tracking = None class GitClientTests(unittest.TestCase): TESTSERVER = "http://127.0.0.1:8080" def _gitcmd(self, command, env=None, split_lines=False, ignore_errors=False, extra_ignore_errors=(), translate_newlines=True, git_dir=None): if git_dir: full_command = ['git', '--git-dir=%s/.git' % git_dir] else: full_command = ['git'] full_command.extend(command) return execute(full_command, env, split_lines, ignore_errors, extra_ignore_errors, translate_newlines) def _git_add_file_commit(self, file, data, msg): """Add a file to a git repository with the content of data and commit with msg. """ foo = open(file, 'w') foo.write(data) foo.close() self._gitcmd(['add', file]) self._gitcmd(['commit', '-m', msg]) def setUp(self): if not is_exe_in_path('git'): raise nose.SkipTest('git not found in path') self.orig_dir = os.getcwd() self.git_dir = tempfile.mkdtemp() os.chdir(self.git_dir) self._gitcmd(['init'], git_dir=self.git_dir) foo = open(os.path.join(self.git_dir, 'foo.txt'), 'w') foo.write(FOO) foo.close() self._gitcmd(['add', 'foo.txt']) self._gitcmd(['commit', '-m', 'initial commit']) self.clone_dir = tempfile.mkdtemp() os.rmdir(self.clone_dir) self._gitcmd(['clone', self.git_dir, self.clone_dir]) self.client = GitClient() os.chdir(self.orig_dir) rbtools.postreview.user_config = load_config_file('') rbtools.postreview.options = OptionsStub() rbtools.postreview.options.parent_branch = None def tearDown(self): os.chdir(self.orig_dir) shutil.rmtree(self.git_dir) shutil.rmtree(self.clone_dir) def test_get_repository_info_simple(self): """Test GitClient get_repository_info, simple case""" os.chdir(self.clone_dir) ri = self.client.get_repository_info() self.assert_(isinstance(ri, RepositoryInfo)) self.assertEqual(ri.base_path, '') self.assertEqual(ri.path.rstrip("/.git"), self.git_dir) self.assertTrue(ri.supports_parent_diffs) self.assertFalse(ri.supports_changesets) def test_scan_for_server_simple(self): """Test GitClient scan_for_server, simple case""" os.chdir(self.clone_dir) ri = self.client.get_repository_info() server = self.client.scan_for_server(ri) self.assert_(server is None) def test_scan_for_server_reviewboardrc(self): "Test GitClient scan_for_server, .reviewboardrc case""" os.chdir(self.clone_dir) rc = open(os.path.join(self.clone_dir, '.reviewboardrc'), 'w') rc.write('REVIEWBOARD_URL = "%s"' % self.TESTSERVER) rc.close() ri = self.client.get_repository_info() server = self.client.scan_for_server(ri) self.assertEqual(server, self.TESTSERVER) def test_scan_for_server_property(self): """Test GitClientscan_for_server using repo property""" os.chdir(self.clone_dir) self._gitcmd(['config', 'reviewboard.url', self.TESTSERVER]) ri = self.client.get_repository_info() self.assertEqual(self.client.scan_for_server(ri), self.TESTSERVER) def test_diff_simple(self): """Test GitClient simple diff case""" diff = "diff --git a/foo.txt b/foo.txt\n" \ "index 634b3e8ff85bada6f928841a9f2c505560840b3a..5e98e9540e1b741b5be24fcb33c40c1c8069c1fb 100644\n" \ "--- a/foo.txt\n" \ "+++ b/foo.txt\n" \ "@@ -6,7 +6,4 @@ multa quoque et bello passus, dum conderet urbem,\n" \ " inferretque deos Latio, genus unde Latinum,\n" \ " Albanique patres, atque altae moenia Romae.\n" \ " Musa, mihi causas memora, quo numine laeso,\n" \ "-quidve dolens, regina deum tot volvere casus\n" \ "-insignem pietate virum, tot adire labores\n" \ "-impulerit. Tantaene animis caelestibus irae?\n" \ " \n" os.chdir(self.clone_dir) ri = self.client.get_repository_info() self._git_add_file_commit('foo.txt', FOO1, 'delete and modify stuff') self.assertEqual(self.client.diff(None), (diff, None)) def test_diff_simple_multiple(self): """Test GitClient simple diff with multiple commits case""" diff = "diff --git a/foo.txt b/foo.txt\n" \ "index 634b3e8ff85bada6f928841a9f2c505560840b3a..63036ed3fcafe870d567a14dd5884f4fed70126c 100644\n" \ "--- a/foo.txt\n" \ "+++ b/foo.txt\n" \ "@@ -1,12 +1,11 @@\n" \ " ARMA virumque cano, Troiae qui primus ab oris\n" \ "+ARMA virumque cano, Troiae qui primus ab oris\n" \ " Italiam, fato profugus, Laviniaque venit\n" \ " litora, multum ille et terris iactatus et alto\n" \ " vi superum saevae memorem Iunonis ob iram;\n" \ "-multa quoque et bello passus, dum conderet urbem,\n" \ "+dum conderet urbem,\n" \ " inferretque deos Latio, genus unde Latinum,\n" \ " Albanique patres, atque altae moenia Romae.\n" \ "+Albanique patres, atque altae moenia Romae.\n" \ " Musa, mihi causas memora, quo numine laeso,\n" \ "-quidve dolens, regina deum tot volvere casus\n" \ "-insignem pietate virum, tot adire labores\n" \ "-impulerit. Tantaene animis caelestibus irae?\n" \ " \n" os.chdir(self.clone_dir) ri = self.client.get_repository_info() self._git_add_file_commit('foo.txt', FOO1, 'commit 1') self._git_add_file_commit('foo.txt', FOO2, 'commit 1') self._git_add_file_commit('foo.txt', FOO3, 'commit 1') self.assertEqual(self.client.diff(None), (diff, None)) def test_diff_branch_diverge(self): """Test GitClient diff with divergent branches""" diff1 = "diff --git a/foo.txt b/foo.txt\n" \ "index 634b3e8ff85bada6f928841a9f2c505560840b3a..e619c1387f5feb91f0ca83194650bfe4f6c2e347 100644\n" \ "--- a/foo.txt\n" \ "+++ b/foo.txt\n" \ "@@ -1,4 +1,6 @@\n" \ " ARMA virumque cano, Troiae qui primus ab oris\n" \ "+ARMA virumque cano, Troiae qui primus ab oris\n" \ "+ARMA virumque cano, Troiae qui primus ab oris\n" \ " Italiam, fato profugus, Laviniaque venit\n" \ " litora, multum ille et terris iactatus et alto\n" \ " vi superum saevae memorem Iunonis ob iram;\n" \ "@@ -6,7 +8,4 @@ multa quoque et bello passus, dum conderet urbem,\n" \ " inferretque deos Latio, genus unde Latinum,\n" \ " Albanique patres, atque altae moenia Romae.\n" \ " Musa, mihi causas memora, quo numine laeso,\n" \ "-quidve dolens, regina deum tot volvere casus\n" \ "-insignem pietate virum, tot adire labores\n" \ "-impulerit. Tantaene animis caelestibus irae?\n" \ " \n" diff2 = "diff --git a/foo.txt b/foo.txt\n" \ "index 634b3e8ff85bada6f928841a9f2c505560840b3a..5e98e9540e1b741b5be24fcb33c40c1c8069c1fb 100644\n" \ "--- a/foo.txt\n" \ "+++ b/foo.txt\n" \ "@@ -6,7 +6,4 @@ multa quoque et bello passus, dum conderet urbem,\n" \ " inferretque deos Latio, genus unde Latinum,\n" \ " Albanique patres, atque altae moenia Romae.\n" \ " Musa, mihi causas memora, quo numine laeso,\n" \ "-quidve dolens, regina deum tot volvere casus\n" \ "-insignem pietate virum, tot adire labores\n" \ "-impulerit. Tantaene animis caelestibus irae?\n" \ " \n" os.chdir(self.clone_dir) self._git_add_file_commit('foo.txt', FOO1, 'commit 1') self._gitcmd(['checkout', '-b', 'mybranch', '--track', 'origin/master']) self._git_add_file_commit('foo.txt', FOO2, 'commit 2') ri = self.client.get_repository_info() self.assertEqual(self.client.diff(None), (diff1, None)) self._gitcmd(['checkout', 'master']) ri = self.client.get_repository_info() self.assertEqual(self.client.diff(None), (diff2, None)) def test_diff_tracking_no_origin(self): """Test GitClient diff with a tracking branch, but no origin remote""" diff = "diff --git a/foo.txt b/foo.txt\n" \ "index 634b3e8ff85bada6f928841a9f2c505560840b3a..5e98e9540e1b741b5be24fcb33c40c1c8069c1fb 100644\n" \ "--- a/foo.txt\n" \ "+++ b/foo.txt\n" \ "@@ -6,7 +6,4 @@ multa quoque et bello passus, dum conderet urbem,\n" \ " inferretque deos Latio, genus unde Latinum,\n" \ " Albanique patres, atque altae moenia Romae.\n" \ " Musa, mihi causas memora, quo numine laeso,\n" \ "-quidve dolens, regina deum tot volvere casus\n" \ "-insignem pietate virum, tot adire labores\n" \ "-impulerit. Tantaene animis caelestibus irae?\n" \ " \n" os.chdir(self.clone_dir) self._gitcmd(['remote', 'add', 'quux', self.git_dir]) self._gitcmd(['fetch', 'quux']) self._gitcmd(['checkout', '-b', 'mybranch', '--track', 'quux/master']) self._git_add_file_commit('foo.txt', FOO1, 'delete and modify stuff') ri = self.client.get_repository_info() self.assertEqual(self.client.diff(None), (diff, None)) def test_diff_local_tracking(self): """Test GitClient diff with a local tracking branch""" diff = "diff --git a/foo.txt b/foo.txt\n" \ "index 634b3e8ff85bada6f928841a9f2c505560840b3a..e619c1387f5feb91f0ca83194650bfe4f6c2e347 100644\n" \ "--- a/foo.txt\n" \ "+++ b/foo.txt\n" \ "@@ -1,4 +1,6 @@\n" \ " ARMA virumque cano, Troiae qui primus ab oris\n" \ "+ARMA virumque cano, Troiae qui primus ab oris\n" \ "+ARMA virumque cano, Troiae qui primus ab oris\n" \ " Italiam, fato profugus, Laviniaque venit\n" \ " litora, multum ille et terris iactatus et alto\n" \ " vi superum saevae memorem Iunonis ob iram;\n" \ "@@ -6,7 +8,4 @@ multa quoque et bello passus, dum conderet urbem,\n" \ " inferretque deos Latio, genus unde Latinum,\n" \ " Albanique patres, atque altae moenia Romae.\n" \ " Musa, mihi causas memora, quo numine laeso,\n" \ "-quidve dolens, regina deum tot volvere casus\n" \ "-insignem pietate virum, tot adire labores\n" \ "-impulerit. Tantaene animis caelestibus irae?\n" \ " \n" os.chdir(self.clone_dir) self._git_add_file_commit('foo.txt', FOO1, 'commit 1') self._gitcmd(['checkout', '-b', 'mybranch', '--track', 'master']) self._git_add_file_commit('foo.txt', FOO2, 'commit 2') ri = self.client.get_repository_info() self.assertEqual(self.client.diff(None), (diff, None)) def test_diff_tracking_override(self): """Test GitClient diff with option override for tracking branch""" diff = "diff --git a/foo.txt b/foo.txt\n" \ "index 634b3e8ff85bada6f928841a9f2c505560840b3a..5e98e9540e1b741b5be24fcb33c40c1c8069c1fb 100644\n" \ "--- a/foo.txt\n" \ "+++ b/foo.txt\n" \ "@@ -6,7 +6,4 @@ multa quoque et bello passus, dum conderet urbem,\n" \ " inferretque deos Latio, genus unde Latinum,\n" \ " Albanique patres, atque altae moenia Romae.\n" \ " Musa, mihi causas memora, quo numine laeso,\n" \ "-quidve dolens, regina deum tot volvere casus\n" \ "-insignem pietate virum, tot adire labores\n" \ "-impulerit. Tantaene animis caelestibus irae?\n" \ " \n" os.chdir(self.clone_dir) rbtools.postreview.options.tracking = 'origin/master' self._gitcmd(['remote', 'add', 'bad', self.git_dir]) self._gitcmd(['fetch', 'bad']) self._gitcmd(['checkout', '-b', 'mybranch', '--track', 'bad/master']) self._git_add_file_commit('foo.txt', FOO1, 'commit 1') ri = self.client.get_repository_info() self.assertEqual(self.client.diff(None), (diff, None)) class ApiTests(MockHttpUnitTest): SAMPLE_ERROR_STR = json.dumps({ 'stat': 'fail', 'err': { 'code': 100, 'msg': 'This is a test failure', } }) def test_parse_get_error_http_200(self): self.http_response = self.SAMPLE_ERROR_STR try: data = self.server.api_get('/foo/') # Shouldn't be reached self._assert(False) except APIError, e: self.assertEqual(e.http_status, 200) self.assertEqual(e.error_code, 100) self.assertEqual(e.rsp['stat'], 'fail') self.assertEqual(str(e), 'This is a test failure (HTTP 200, API Error 100)') def test_parse_post_error_http_200(self): self.http_response = self.SAMPLE_ERROR_STR try: data = self.server.api_post('/foo/') # Shouldn't be reached self._assert(False) except APIError, e: self.assertEqual(e.http_status, 200) self.assertEqual(e.error_code, 100) self.assertEqual(e.rsp['stat'], 'fail') self.assertEqual(str(e), 'This is a test failure (HTTP 200, API Error 100)') def test_parse_get_error_http_400(self): self.http_response = self._make_http_error('/foo/', 400, self.SAMPLE_ERROR_STR) try: data = self.server.api_get('/foo/') # Shouldn't be reached self._assert(False) except APIError, e: self.assertEqual(e.http_status, 400) self.assertEqual(e.error_code, 100) self.assertEqual(e.rsp['stat'], 'fail') self.assertEqual(str(e), 'This is a test failure (HTTP 400, API Error 100)') def test_parse_post_error_http_400(self): self.http_response = self._make_http_error('/foo/', 400, self.SAMPLE_ERROR_STR) try: data = self.server.api_post('/foo/') # Shouldn't be reached self._assert(False) except APIError, e: self.assertEqual(e.http_status, 400) self.assertEqual(e.error_code, 100) self.assertEqual(e.rsp['stat'], 'fail') self.assertEqual(str(e), 'This is a test failure (HTTP 400, API Error 100)') def _make_http_error(self, url, code, body): return urllib2.HTTPError(url, code, body, {}, StringIO(body))