#!/usr/bin/env python3 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163 import git import logging import os import shutil import sys import tempfile import time import unittest from unittest import mock from pathlib import Path localmodule = Path(__file__).resolve().parent.parent print('localmodule: ' + str(localmodule)) if localmodule not in sys.path: sys.path.insert(0, str(localmodule)) import fdroidserver.checkupdates import fdroidserver.metadata from fdroidserver.exception import FDroidException class CheckupdatesTest(unittest.TestCase): '''fdroidserver/checkupdates.py''' def setUp(self): logging.basicConfig(level=logging.DEBUG) self.basedir = localmodule / 'tests' os.chdir(self.basedir) self.testdir = tempfile.TemporaryDirectory( str(time.time()), self._testMethodName + '_' ) def tearDown(self): self.testdir.cleanup() def test_autoupdatemode_no_suffix(self): fdroidserver.checkupdates.config = {} app = fdroidserver.metadata.App() app.id = 'loop.starts.shooting' app.metadatapath = 'metadata/' + app.id + '.yml' app.CurrentVersion = '1.1.8-fdroid' app.CurrentVersionCode = 10108 app.UpdateCheckMode = 'HTTP' app.AutoUpdateMode = 'Version %v' build = fdroidserver.metadata.Build() build.versionCode = app.CurrentVersionCode build.versionName = app.CurrentVersion app['Builds'].append(build) with mock.patch( 'fdroidserver.checkupdates.check_http', lambda app: ('1.1.9', 10109) ): with mock.patch('fdroidserver.metadata.write_metadata', mock.Mock()): with mock.patch('subprocess.call', lambda cmd: 0): fdroidserver.checkupdates.checkupdates_app(app, auto=True) build = app['Builds'][-1] self.assertEqual(build.versionName, '1.1.9') self.assertEqual(build.commit, '1.1.9') with mock.patch( 'fdroidserver.checkupdates.check_http', lambda app: ('1.7.9', 10107) ): with mock.patch('fdroidserver.metadata.write_metadata', mock.Mock()): with mock.patch('subprocess.call', lambda cmd: 0): with self.assertRaises(FDroidException): fdroidserver.checkupdates.checkupdates_app(app, auto=True) build = app['Builds'][-1] self.assertEqual(build.versionName, '1.1.9') self.assertEqual(build.commit, '1.1.9') def test_autoupdatemode_suffix(self): fdroidserver.checkupdates.config = {} app = fdroidserver.metadata.App() app.id = 'loop.starts.shooting' app.metadatapath = 'metadata/' + app.id + '.yml' app.CurrentVersion = '1.1.8-fdroid' app.CurrentVersionCode = 10108 app.UpdateCheckMode = 'HTTP' app.AutoUpdateMode = r'Version +.%c-fdroid v%v_%c' build = fdroidserver.metadata.Build() build.versionCode = app.CurrentVersionCode build.versionName = app.CurrentVersion app['Builds'].append(build) with mock.patch( 'fdroidserver.checkupdates.check_http', lambda app: ('1.1.9', 10109) ): with mock.patch('fdroidserver.metadata.write_metadata', mock.Mock()): with mock.patch('subprocess.call', lambda cmd: 0): fdroidserver.checkupdates.checkupdates_app(app, auto=True) build = app['Builds'][-1] self.assertEqual(build.versionName, '1.1.9.10109-fdroid') self.assertEqual(build.commit, 'v1.1.9_10109') def test_autoupdate_multi_variants(self): fdroidserver.checkupdates.config = {} app = fdroidserver.metadata.App() app.id = 'loop.starts.shooting' app.metadatapath = 'metadata/' + app.id + '.yml' app.CurrentVersion = '1.1.8' app.CurrentVersionCode = 101083 app.UpdateCheckMode = 'Tags' app.AutoUpdateMode = r'Version' app.VercodeOperation = [ "10*%c+1", "10*%c+3", ] build = fdroidserver.metadata.Build() build.versionCode = app.CurrentVersionCode - 2 build.versionName = app.CurrentVersion build.gradle = ["arm"] app['Builds'].append(build) build = fdroidserver.metadata.Build() build.versionCode = app.CurrentVersionCode build.versionName = app.CurrentVersion build.gradle = ["x86"] app['Builds'].append(build) with mock.patch( 'fdroidserver.checkupdates.check_tags', lambda app, pattern: ('1.1.9', 10109, 'v1.1.9'), ): with mock.patch('fdroidserver.metadata.write_metadata', mock.Mock()): with mock.patch('subprocess.call', lambda cmd: 0): fdroidserver.checkupdates.checkupdates_app(app, auto=True) build = app['Builds'][-2] self.assertEqual(build.versionName, '1.1.9') self.assertEqual(build.versionCode, 101091) self.assertEqual(build.gradle, ["arm"]) build = app['Builds'][-1] self.assertEqual(build.versionName, '1.1.9') self.assertEqual(build.versionCode, 101093) self.assertEqual(build.gradle, ["x86"]) self.assertEqual(app.CurrentVersion, '1.1.9') self.assertEqual(app.CurrentVersionCode, 101093) def test_checkupdates_app_http(self): fdroidserver.checkupdates.config = {} app = fdroidserver.metadata.App() app.id = 'loop.starts.shooting' app.metadatapath = 'metadata/' + app.id + '.yml' app.CurrentVersionCode = 10108 app.UpdateCheckMode = 'HTTP' app.UpdateCheckData = 'mock' with mock.patch( 'fdroidserver.checkupdates.check_http', lambda app: (None, 'bla') ): with self.assertRaises(FDroidException): fdroidserver.checkupdates.checkupdates_app(app, auto=True) with mock.patch( 'fdroidserver.checkupdates.check_http', lambda app: ('1.1.9', 10109) ): with mock.patch( 'fdroidserver.metadata.write_metadata', mock.Mock() ) as wrmock: with mock.patch('subprocess.call', lambda cmd: 0): fdroidserver.checkupdates.checkupdates_app(app, auto=True) wrmock.assert_called_with(app.metadatapath, app) def test_checkupdates_app_tags(self): fdroidserver.checkupdates.config = {} app = fdroidserver.metadata.App() app.id = 'loop.starts.shooting' app.metadatapath = 'metadata/' + app.id + '.yml' app.CurrentVersion = '1.1.8' app.CurrentVersionCode = 10108 app.UpdateCheckMode = 'Tags' app.AutoUpdateMode = 'Version' build = fdroidserver.metadata.Build() build.versionCode = app.CurrentVersionCode build.versionName = app.CurrentVersion app['Builds'].append(build) with mock.patch( 'fdroidserver.checkupdates.check_tags', lambda app, pattern: (None, 'bla', None), ): with self.assertRaises(FDroidException): fdroidserver.checkupdates.checkupdates_app(app, auto=True) with mock.patch( 'fdroidserver.checkupdates.check_tags', lambda app, pattern: ('1.1.9', 10109, 'v1.1.9'), ): with mock.patch('fdroidserver.metadata.write_metadata', mock.Mock()): with mock.patch('subprocess.call', lambda cmd: 0): fdroidserver.checkupdates.checkupdates_app(app, auto=True) build = app['Builds'][-1] self.assertEqual(build.versionName, '1.1.9') self.assertEqual(build.commit, 'v1.1.9') def test_check_http(self): app = fdroidserver.metadata.App() app.id = 'loop.starts.shooting' app.metadatapath = 'metadata/' + app.id + '.yml' app.CurrentVersionCode = 10108 app.UpdateCheckMode = 'HTTP' app.UpdateCheckData = r'https://a.net/b.txt|c(.*)|https://d.net/e.txt|v(.*)' app.UpdateCheckIgnore = 'beta' respmock = mock.Mock() respmock.read = lambda: 'v1.1.9\nc10109'.encode('utf-8') with mock.patch('urllib.request.urlopen', lambda a, b, c: respmock): vername, vercode = fdroidserver.checkupdates.check_http(app) self.assertEqual(vername, '1.1.9') self.assertEqual(vercode, 10109) def test_check_http_blocks_unknown_schemes(self): app = fdroidserver.metadata.App() for scheme in ('file', 'ssh', 'http', ';pwn'): app.id = scheme faked = scheme + '://fake.url/for/testing/scheme' app.UpdateCheckData = faked + '|ignored|' + faked + '|ignored' app.metadatapath = 'metadata/' + app.id + '.yml' with self.assertRaises(FDroidException): fdroidserver.checkupdates.check_http(app) def test_check_http_ignore(self): app = fdroidserver.metadata.App() app.id = 'loop.starts.shooting' app.metadatapath = 'metadata/' + app.id + '.yml' app.CurrentVersionCode = 10108 app.UpdateCheckMode = 'HTTP' app.UpdateCheckData = r'https://a.net/b.txt|c(.*)|https://d.net/e.txt|v(.*)' app.UpdateCheckIgnore = 'beta' respmock = mock.Mock() respmock.read = lambda: 'v1.1.9-beta\nc10109'.encode('utf-8') with mock.patch('urllib.request.urlopen', lambda a, b, c: respmock): vername, vercode = fdroidserver.checkupdates.check_http(app) self.assertEqual(vername, None) def test_check_tags_data(self): app = fdroidserver.metadata.App() app.id = 'loop.starts.shooting' app.metadatapath = 'metadata/' + app.id + '.yml' app.RepoType = 'git' app.CurrentVersionCode = 10108 app.UpdateCheckMode = 'Tags' app.UpdateCheckData = r'b.txt|c(.*)|e.txt|v(.*)' vcs = mock.Mock() vcs.latesttags.return_value = ['1.1.9', '1.1.8'] with mock.patch( 'pathlib.Path.read_text', lambda a: 'v1.1.9\nc10109' ) as _ignored, mock.patch.object(Path, 'is_file') as mock_path, mock.patch( 'fdroidserver.common.getvcs', return_value=vcs ): _ignored # silence the linters mock_path.is_file.return_falue = True vername, vercode, _tag = fdroidserver.checkupdates.check_tags(app, None) self.assertEqual(vername, '1.1.9') self.assertEqual(vercode, 10109) app.UpdateCheckData = r'b.txt|c(.*)|.|v(.*)' with mock.patch( 'pathlib.Path.read_text', lambda a: 'v1.1.0\nc10109' ) as _ignored, mock.patch.object(Path, 'is_file') as mock_path, mock.patch( 'fdroidserver.common.getvcs', return_value=vcs ): _ignored # silence the linters mock_path.is_file.return_falue = True vername, vercode, _tag = fdroidserver.checkupdates.check_tags(app, None) self.assertEqual(vername, '1.1.0') self.assertEqual(vercode, 10109) app.UpdateCheckData = r'b.txt|c(.*)||' with mock.patch( 'pathlib.Path.read_text', lambda a: 'v1.1.9\nc10109' ) as _ignored, mock.patch.object(Path, 'is_file') as mock_path, mock.patch( 'fdroidserver.common.getvcs', return_value=vcs ): _ignored # silence the linters mock_path.is_file.return_falue = True vername, vercode, _tag = fdroidserver.checkupdates.check_tags(app, None) self.assertEqual(vername, '1.1.9') self.assertEqual(vercode, 10109) vcs.latesttags.return_value = ['Android-1.1.0', '1.1.8'] app.UpdateCheckData = r'b.txt|c(.*)||Android-([\d.]+)' with mock.patch( 'pathlib.Path.read_text', lambda a: 'v1.1.9\nc10109' ) as _ignored, mock.patch.object(Path, 'is_file') as mock_path, mock.patch( 'fdroidserver.common.getvcs', return_value=vcs ): _ignored # silence the linters mock_path.is_file.return_falue = True vername, vercode, _tag = fdroidserver.checkupdates.check_tags(app, None) self.assertEqual(vername, '1.1.0') self.assertEqual(vercode, 10109) app.UpdateCheckData = r'|\+(\d+)||Android-([\d.]+)' vcs.latesttags.return_value = ['Android-1.1.0+1'] with mock.patch('fdroidserver.common.getvcs', return_value=vcs): vername, vercode, _tag = fdroidserver.checkupdates.check_tags(app, None) self.assertEqual(vername, '1.1.0') self.assertEqual(vercode, 1) app.UpdateCheckData = '|||' vcs.latesttags.return_value = ['2'] with mock.patch('fdroidserver.common.getvcs', return_value=vcs): vername, vercode, _tag = fdroidserver.checkupdates.check_tags(app, None) self.assertEqual(vername, '2') self.assertEqual(vercode, 2) def _get_test_git_repos(self): testdir = self.testdir.name os.chdir(testdir) os.mkdir('metadata') for f in (self.basedir / 'metadata').glob('*.yml'): shutil.copy(f, 'metadata') git_repo = git.Repo.init(testdir) git_repo.git.add(all=True) git_repo.index.commit("all metadata files") git_remote_upstream = os.path.join(testdir, 'git_remote_upstream') upstream_repo = git.Repo.init(git_remote_upstream, bare=True) with upstream_repo.config_writer() as cw: cw.set_value('receive', 'advertisePushOptions', True) git_repo.create_remote('upstream', 'file://' + git_remote_upstream) git_remote_origin = os.path.join(testdir, 'git_remote_origin') origin_repo = git.Repo.init(git_remote_origin, bare=True) with origin_repo.config_writer() as cw: cw.set_value('receive', 'advertisePushOptions', True) git_repo.create_remote('origin', 'file://' + git_remote_origin) return git_repo, origin_repo, upstream_repo def test_push_commits(self): git_repo, origin_repo, upstream_repo = self._get_test_git_repos() for remote in git_repo.remotes: remote.push(git_repo.active_branch) self.assertEqual(git_repo.head, upstream_repo.head) self.assertEqual(origin_repo.head, upstream_repo.head) # pretend that checkupdates ran but didn't create any new commits fdroidserver.checkupdates.push_commits() appid = 'org.adaway' self.assertNotIn(appid, git_repo.branches) self.assertNotIn(appid, origin_repo.branches) self.assertNotIn(appid, upstream_repo.branches) self.assertNotIn('checkupdates', git_repo.branches) # now make commit app = fdroidserver.metadata.read_metadata({appid: -1})[appid] build = fdroidserver.metadata.Build() build.versionName = 'fake' build.versionCode = 999999999 app.Builds.append(build) metadata_file = 'metadata/%s.yml' % appid fdroidserver.metadata.write_metadata(metadata_file, app) git_repo.index.add(metadata_file) git_repo.index.commit('changed ' + appid) # and push the new commit to the dynamic branch fdroidserver.checkupdates.push_commits() self.assertIn(appid, git_repo.branches) self.assertIn(appid, git_repo.remotes.origin.refs) self.assertNotIn('checkupdates', git_repo.branches) self.assertNotIn(appid, git_repo.remotes.upstream.refs) def test_push_commits_verbose(self): class Options: verbose = True fdroidserver.checkupdates.options = Options repos = self._get_test_git_repos() git_repo = repos[0] git_repo.remotes.origin.push(git_repo.active_branch) git_repo.remotes.upstream.push(git_repo.active_branch) # make commit appid = 'org.adaway' app = fdroidserver.metadata.read_metadata({appid: -1})[appid] build = fdroidserver.metadata.Build() build.versionName = 'fake' build.versionCode = 999999999 app.Builds.append(build) metadata_file = 'metadata/%s.yml' % appid fdroidserver.metadata.write_metadata(metadata_file, app) git_repo.index.add(metadata_file) git_repo.index.commit('changed ' + appid) # and push the new commit to the dynamic branch fdroidserver.checkupdates.push_commits() self.assertIn(appid, git_repo.branches) self.assertIn(appid, git_repo.remotes.origin.refs) def test_prune_empty_appid_branches(self): git_repo, origin_repo, upstream_repo = self._get_test_git_repos() for remote in git_repo.remotes: remote.push(git_repo.active_branch) self.assertEqual(git_repo.head, upstream_repo.head) self.assertEqual(origin_repo.head, upstream_repo.head) appid = 'org.adaway' git_repo.create_head(appid, force=True) git_repo.remotes.origin.push(appid, force=True) self.assertIn(appid, git_repo.branches) self.assertIn(appid, origin_repo.branches) self.assertIn(appid, git_repo.remotes.origin.refs) self.assertNotIn(appid, git_repo.remotes.upstream.refs) fdroidserver.checkupdates.prune_empty_appid_branches() self.assertNotIn(appid, origin_repo.branches) self.assertNotIn(appid, git_repo.remotes.origin.refs) self.assertNotIn(appid, git_repo.remotes.upstream.refs) @mock.patch('sys.exit') @mock.patch('fdroidserver.metadata.read_metadata') def test_merge_requests_flag(self, read_metadata, sys_exit): def _sys_exit(return_code=0): assert return_code != 0 raise fdroidserver.exception.FDroidException('sys.exit() ran') def _read_metadata(a=None, b=None): raise StopIteration('read_metadata() ran, test is successful') appid = 'com.example' # read_metadata.return_value = dict() # {appid: dict()} read_metadata.side_effect = _read_metadata sys_exit.side_effect = _sys_exit # set up clean git repo os.chdir(self.testdir.name) git_repo = git.Repo.init() open('foo', 'w').close() git_repo.git.add(all=True) git_repo.index.commit("all files") with mock.patch('sys.argv', ['fdroid checkupdates', '--merge-request']): with self.assertRaises(fdroidserver.exception.FDroidException): fdroidserver.checkupdates.main() sys_exit.assert_called() sys_exit.reset_mock() with mock.patch('sys.argv', ['fdroid checkupdates', '--merge-request', appid]): with self.assertRaises(StopIteration): fdroidserver.checkupdates.main() sys_exit.assert_not_called() def test_get_git_repo_and_main_branch(self): os.chdir(self.testdir.name) git_repo = git.Repo.init() open('foo', 'w').close() git_repo.git.add(all=True) git_repo.index.commit("all files") repo, branch = fdroidserver.checkupdates.get_git_repo_and_main_branch() self.assertTrue(branch in ('main', 'master')) self.assertTrue(branch in repo.heads) @mock.patch('sys.exit') @mock.patch('fdroidserver.common.read_app_args') @mock.patch('fdroidserver.checkupdates.checkupdates_app') def test_merge_requests_branch(self, checkupdates_app, read_app_args, sys_exit): def _sys_exit(return_code=0): assert return_code == 0 def _checkupdates_app(app, auto, commit): # pylint: disable=unused-argument os.mkdir('metadata') Path(f'metadata/{app["packageName"]}.yml').write_text('AutoName: Example') git_repo.git.add(all=True) git_repo.index.commit("Example") def _read_app_args(apps=[]): appid = apps[0] return {appid: {'packageName': appid}} appid = 'com.example' read_app_args.side_effect = _read_app_args checkupdates_app.side_effect = _checkupdates_app sys_exit.side_effect = _sys_exit # set up clean git repo os.chdir(self.testdir.name) git_repo = git.Repo.init() open('foo', 'w').close() git_repo.git.add(all=True) git_repo.index.commit("all files") # --merge-request assumes remotes called 'origin' and 'upstream' git_repo.create_remote('origin', os.getcwd()).fetch() git_repo.create_remote('upstream', os.getcwd()).fetch() assert appid not in git_repo.heads with mock.patch('sys.argv', ['fdroid checkupdates', '--merge-request', appid]): fdroidserver.checkupdates.main() sys_exit.assert_called_once() assert appid in git_repo.heads if __name__ == "__main__": import argparse from testcommon import parse_args_for_test parser = argparse.ArgumentParser() parser.add_argument( "-v", "--verbose", action="store_true", default=False, help="Spew out even more information than normal", ) parse_args_for_test(parser, sys.argv) newSuite = unittest.TestSuite() newSuite.addTest(unittest.makeSuite(CheckupdatesTest)) unittest.main(failfast=False)