#!/usr/bin/env python3 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163 import inspect import logging import os import shutil import sys import tempfile import textwrap import unittest import yaml from pathlib import Path from unittest import mock localmodule = os.path.realpath( os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..') ) print('localmodule: ' + localmodule) if localmodule not in sys.path: sys.path.insert(0, localmodule) from testcommon import TmpCwd import fdroidserver.build import fdroidserver.common import fdroidserver.metadata import fdroidserver.scanner import fdroidserver.vmtools from testcommon import mkdtemp, parse_args_for_test class FakeProcess: output = 'fake output' returncode = 0 def __init__(self, args, **kwargs): print('FakeFDroidPopen', args, kwargs) class Options: keep_when_not_allowed = False class BuildTest(unittest.TestCase): '''fdroidserver/build.py''' def setUp(self): logging.basicConfig(level=logging.DEBUG) self.basedir = os.path.join(localmodule, 'tests') os.chdir(self.basedir) fdroidserver.common.config = None fdroidserver.build.config = None fdroidserver.build.options = None self._td = mkdtemp() self.testdir = self._td.name def tearDown(self): os.chdir(self.basedir) self._td.cleanup() def create_fake_android_home(self, d): os.makedirs(os.path.join(d, 'build-tools'), exist_ok=True) os.makedirs(os.path.join(d, 'platform-tools'), exist_ok=True) os.makedirs(os.path.join(d, 'tools'), exist_ok=True) def test_get_apk_metadata(self): config = dict() fdroidserver.common.fill_config_defaults(config) fdroidserver.common.config = config fdroidserver.build.config = config try: config['aapt'] = fdroidserver.common.find_sdk_tools_cmd('aapt') except fdroidserver.exception.FDroidException: pass # aapt is not required if androguard is present testcases = [ ( 'repo/obb.main.twoversions_1101613.apk', 'obb.main.twoversions', 1101613, '0.1', None, ), ( 'org.bitbucket.tickytacky.mirrormirror_1.apk', 'org.bitbucket.tickytacky.mirrormirror', 1, '1.0', None, ), ( 'org.bitbucket.tickytacky.mirrormirror_2.apk', 'org.bitbucket.tickytacky.mirrormirror', 2, '1.0.1', None, ), ( 'org.bitbucket.tickytacky.mirrormirror_3.apk', 'org.bitbucket.tickytacky.mirrormirror', 3, '1.0.2', None, ), ( 'org.bitbucket.tickytacky.mirrormirror_4.apk', 'org.bitbucket.tickytacky.mirrormirror', 4, '1.0.3', None, ), ( 'org.dyndns.fules.ck_20.apk', 'org.dyndns.fules.ck', 20, 'v1.6pre2', [ 'arm64-v8a', 'armeabi', 'armeabi-v7a', 'mips', 'mips64', 'x86', 'x86_64', ], ), ('urzip.apk', 'info.guardianproject.urzip', 100, '0.1', None), ('urzip-badcert.apk', 'info.guardianproject.urzip', 100, '0.1', None), ('urzip-badsig.apk', 'info.guardianproject.urzip', 100, '0.1', None), ('urzip-release.apk', 'info.guardianproject.urzip', 100, '0.1', None), ( 'urzip-release-unsigned.apk', 'info.guardianproject.urzip', 100, '0.1', None, ), ('repo/com.politedroid_3.apk', 'com.politedroid', 3, '1.2', None), ('repo/com.politedroid_4.apk', 'com.politedroid', 4, '1.3', None), ('repo/com.politedroid_5.apk', 'com.politedroid', 5, '1.4', None), ('repo/com.politedroid_6.apk', 'com.politedroid', 6, '1.5', None), ( 'repo/duplicate.permisssions_9999999.apk', 'duplicate.permisssions', 9999999, '', None, ), ( 'repo/info.zwanenburg.caffeinetile_4.apk', 'info.zwanenburg.caffeinetile', 4, '1.3', None, ), ( 'repo/obb.main.oldversion_1444412523.apk', 'obb.main.oldversion', 1444412523, '0.1', None, ), ( 'repo/obb.mainpatch.current_1619_another-release-key.apk', 'obb.mainpatch.current', 1619, '0.1', None, ), ( 'repo/obb.mainpatch.current_1619.apk', 'obb.mainpatch.current', 1619, '0.1', None, ), ( 'repo/obb.main.twoversions_1101613.apk', 'obb.main.twoversions', 1101613, '0.1', None, ), ( 'repo/obb.main.twoversions_1101615.apk', 'obb.main.twoversions', 1101615, '0.1', None, ), ( 'repo/obb.main.twoversions_1101617.apk', 'obb.main.twoversions', 1101617, '0.1', None, ), ( 'repo/urzip-; Рахма́, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢·.apk', 'info.guardianproject.urzip', 100, '0.1', None, ), ] for apkfilename, appid, versionCode, versionName, nativecode in testcases: app = fdroidserver.metadata.App() app.id = appid build = fdroidserver.metadata.Build() build.buildjni = ['yes'] if nativecode else build.buildjni build.versionCode = versionCode build.versionName = versionName vc, vn = fdroidserver.build.get_metadata_from_apk(app, build, apkfilename) self.assertEqual(versionCode, vc) self.assertEqual(versionName, vn) @mock.patch('fdroidserver.common.get_apk_id') @mock.patch('fdroidserver.build.FDroidPopen') @mock.patch('fdroidserver.common.is_debuggable_or_testOnly', lambda f: False) @mock.patch('fdroidserver.common.get_native_code', lambda f: 'x86') def test_build_local_maven(self, fake_FDroidPopen, fake_get_apk_id): """Test build_local() with a maven project""" # pylint: disable=unused-argument def _side_effect(cmd, cwd=None): p = mock.MagicMock() p.output = '[INFO] fake apkbuilder target/no.apk' with open(os.path.join(self.testdir, 'target', 'no.apk'), 'w') as fp: fp.write('placeholder') p.returncode = 0 return p fake_FDroidPopen.side_effect = _side_effect os.chdir(self.testdir) os.mkdir('target') config = dict() fdroidserver.common.fill_config_defaults(config) fdroidserver.common.config = config fdroidserver.build.config = config fdroidserver.build.options = mock.Mock() fdroidserver.build.options.scan_binary = False fdroidserver.build.options.notarball = True fdroidserver.build.options.skipscan = False app = fdroidserver.metadata.App() app.id = 'mocked.app.id' build = fdroidserver.metadata.Build() build.commit = '1.0' build.versionCode = 1 build.versionName = '1.0' fake_get_apk_id.side_effect = lambda f: ( app.id, build.versionCode, build.versionName, ) vcs = mock.Mock() build.maven = 'yes@..' fdroidserver.build.build_local( app, build, vcs, build_dir=self.testdir, output_dir=self.testdir, log_dir=os.getcwd(), srclib_dir=None, extlib_dir=None, tmp_dir=None, force=False, onserver=True, refresh=False, ) build.maven = 'yes' fdroidserver.build.build_local( app, build, vcs, build_dir=self.testdir, output_dir=self.testdir, log_dir=os.getcwd(), srclib_dir=None, extlib_dir=None, tmp_dir=None, force=False, onserver=True, refresh=False, ) @mock.patch('sdkmanager.build_package_list', lambda use_net: None) def test_build_local_ndk(self): """Test if `fdroid build` detects installed NDKs and auto-installs when missing""" with tempfile.TemporaryDirectory() as testdir, TmpCwd( testdir ), tempfile.TemporaryDirectory() as sdk_path: config = {'ndk_paths': {}, 'sdk_path': sdk_path} fdroidserver.common.config = config fdroidserver.build.config = config fdroidserver.build.options = mock.Mock() fdroidserver.build.options.scan_binary = False fdroidserver.build.options.notarball = True fdroidserver.build.options.skipscan = True app = fdroidserver.metadata.App() app.id = 'mocked.app.id' build = fdroidserver.metadata.Build() build.commit = '1.0' build.output = app.id + '.apk' build.versionCode = 1 build.versionName = '1.0' build.ndk = 'r21e' # aka 21.4.7075529 ndk_version = '21.4.7075529' ndk_dir = Path(config['sdk_path']) / 'ndk' / ndk_version vcs = mock.Mock() def make_fake_apk(output, build): with open(build.output, 'w') as fp: fp.write('APK PLACEHOLDER') return output # pylint: disable=unused-argument def fake_sdkmanager_install(to_install, android_home=None): ndk_dir.mkdir(parents=True) self.assertNotEqual(ndk_version, to_install) # converts r21e to version with (ndk_dir / 'source.properties').open('w') as fp: fp.write('Pkg.Revision = %s\n' % ndk_version) # use "as _ignored" just to make a pretty layout with mock.patch( 'fdroidserver.common.replace_build_vars', wraps=make_fake_apk ) as _ignored, mock.patch( 'fdroidserver.common.get_native_code', return_value='x86' ) as _ignored, mock.patch( 'fdroidserver.common.get_apk_id', return_value=(app.id, build.versionCode, build.versionName), ) as _ignored, mock.patch( 'fdroidserver.common.sha256sum', return_value='ad7ce5467e18d40050dc51b8e7affc3e635c85bd8c59be62de32352328ed467e', ) as _ignored, mock.patch( 'fdroidserver.common.is_debuggable_or_testOnly', return_value=False, ) as _ignored, mock.patch( 'fdroidserver.build.FDroidPopen', FakeProcess ) as _ignored, mock.patch( 'sdkmanager.install', wraps=fake_sdkmanager_install ) as _ignored: _ignored # silence the linters with self.assertRaises( fdroidserver.exception.FDroidException, msg="No NDK setup, `fdroid build` should fail with error", ): fdroidserver.build.build_local( app, build, vcs, build_dir=testdir, output_dir=testdir, log_dir=None, srclib_dir=None, extlib_dir=None, tmp_dir=None, force=False, onserver=False, refresh=False, ) # now run `fdroid build --onserver` print('now run `fdroid build --onserver`') self.assertFalse(ndk_dir.exists()) self.assertFalse('r21e' in config['ndk_paths']) self.assertFalse(ndk_version in config['ndk_paths']) fdroidserver.build.build_local( app, build, vcs, build_dir=testdir, output_dir=testdir, log_dir=os.getcwd(), srclib_dir=None, extlib_dir=None, tmp_dir=None, force=False, onserver=True, refresh=False, ) self.assertTrue(ndk_dir.exists()) self.assertTrue(os.path.exists(config['ndk_paths'][ndk_version])) # All paths in the config must be strings, never pathlib.Path instances self.assertIsInstance(config['ndk_paths'][ndk_version], str) @mock.patch('sdkmanager.build_package_list', lambda use_net: None) @mock.patch('fdroidserver.build.FDroidPopen', FakeProcess) @mock.patch('fdroidserver.common.get_native_code', lambda _ignored: 'x86') @mock.patch('fdroidserver.common.is_debuggable_or_testOnly', lambda _ignored: False) @mock.patch( 'fdroidserver.common.sha256sum', lambda f: 'ad7ce5467e18d40050dc51b8e7affc3e635c85bd8c59be62de32352328ed467e', ) def test_build_local_ndk_some_installed(self): """Test if `fdroid build` detects installed NDKs and auto-installs when missing""" with tempfile.TemporaryDirectory() as testdir, TmpCwd( testdir ), tempfile.TemporaryDirectory() as sdk_path: ndk_r24 = os.path.join(sdk_path, 'ndk', '24.0.8215888') os.makedirs(ndk_r24) with open(os.path.join(ndk_r24, 'source.properties'), 'w') as fp: fp.write('Pkg.Revision = 24.0.8215888\n') config = {'ndk_paths': {'r24': ndk_r24}, 'sdk_path': sdk_path} fdroidserver.common.config = config fdroidserver.build.config = config fdroidserver.build.options = mock.Mock() fdroidserver.build.options.scan_binary = False fdroidserver.build.options.notarball = True fdroidserver.build.options.skipscan = True app = fdroidserver.metadata.App() app.id = 'mocked.app.id' build = fdroidserver.metadata.Build() build.commit = '1.0' build.output = app.id + '.apk' build.versionCode = 1 build.versionName = '1.0' build.ndk = 'r21e' # aka 21.4.7075529 ndk_version = '21.4.7075529' ndk_dir = Path(config['sdk_path']) / 'ndk' / ndk_version vcs = mock.Mock() def make_fake_apk(output, build): with open(build.output, 'w') as fp: fp.write('APK PLACEHOLDER') return output # pylint: disable=unused-argument def fake_sdkmanager_install(to_install, android_home=None): ndk_dir.mkdir(parents=True) self.assertNotEqual(ndk_version, to_install) # converts r21e to version with (ndk_dir / 'source.properties').open('w') as fp: fp.write('Pkg.Revision = %s\n' % ndk_version) # use "as _ignored" just to make a pretty layout with mock.patch( 'fdroidserver.common.replace_build_vars', wraps=make_fake_apk ) as _ignored, mock.patch( 'fdroidserver.common.get_apk_id', return_value=(app.id, build.versionCode, build.versionName), ) as _ignored, mock.patch( 'sdkmanager.install', wraps=fake_sdkmanager_install ) as _ignored: _ignored # silence the linters self.assertFalse(ndk_dir.exists()) self.assertFalse('r21e' in config['ndk_paths']) self.assertFalse(ndk_version in config['ndk_paths']) fdroidserver.build.build_local( app, build, vcs, build_dir=testdir, output_dir=testdir, log_dir=os.getcwd(), srclib_dir=None, extlib_dir=None, tmp_dir=None, force=False, onserver=True, refresh=False, ) self.assertTrue(ndk_dir.exists()) self.assertTrue(os.path.exists(config['ndk_paths'][ndk_version])) def test_build_local_clean(self): """Test if `fdroid build` cleans ant and gradle build products""" os.chdir(self.testdir) config = dict() fdroidserver.common.fill_config_defaults(config) fdroidserver.common.config = config fdroidserver.build.config = config fdroidserver.build.options = mock.Mock() fdroidserver.build.options.scan_binary = False fdroidserver.build.options.notarball = True fdroidserver.build.options.skipscan = False app = fdroidserver.metadata.App() app.id = 'mocked.app.id' build = fdroidserver.metadata.Build() build.commit = '1.0' build.output = app.id + '.apk' build.scandelete = ['baz.so'] build.scanignore = ['foo.aar'] build.versionCode = 1 build.versionName = '1.0' vcs = mock.Mock() os.mkdir('reports') os.mkdir('target') for f in ('baz.so', 'foo.aar', 'gradle-wrapper.jar'): with open(f, 'w') as fp: fp.write('placeholder') self.assertTrue(os.path.exists(f)) os.mkdir('build') os.mkdir('build/reports') with open('build.gradle', 'w', encoding='utf-8') as fp: fp.write('// placeholder') os.mkdir('bin') os.mkdir('gen') with open('build.xml', 'w', encoding='utf-8') as fp: fp.write( textwrap.dedent( """ """ ) ) def make_fake_apk(output, build): with open(build.output, 'w') as fp: fp.write('APK PLACEHOLDER') return output with mock.patch('fdroidserver.common.replace_build_vars', wraps=make_fake_apk): with mock.patch('fdroidserver.common.get_native_code', return_value='x86'): with mock.patch( 'fdroidserver.common.get_apk_id', return_value=(app.id, build.versionCode, build.versionName), ): with mock.patch( 'fdroidserver.common.is_debuggable_or_testOnly', return_value=False, ): fdroidserver.build.build_local( app, build, vcs, build_dir=self.testdir, output_dir=self.testdir, log_dir=None, srclib_dir=None, extlib_dir=None, tmp_dir=None, force=False, onserver=False, refresh=False, ) self.assertTrue(os.path.exists('foo.aar')) self.assertTrue(os.path.isdir('build')) self.assertTrue(os.path.isdir('reports')) self.assertTrue(os.path.isdir('target')) self.assertFalse(os.path.exists('baz.so')) self.assertFalse(os.path.exists('bin')) self.assertFalse(os.path.exists('build/reports')) self.assertFalse(os.path.exists('gen')) self.assertFalse(os.path.exists('gradle-wrapper.jar')) def test_scan_with_extlib(self): os.chdir(self.testdir) os.mkdir("build") config = fdroidserver.common.read_config() config['sdk_path'] = os.getenv('ANDROID_HOME') config['ndk_paths'] = {'r10d': os.getenv('ANDROID_NDK_HOME')} fdroidserver.common.config = config app = fdroidserver.metadata.App() app.id = 'com.gpl.rpg.AndorsTrail' build = fdroidserver.metadata.Build() build.commit = 'master' build.androidupdate = ['no'] os.makedirs("extlib/android") # write a fake binary jar file the scanner should definitely error on with open('extlib/android/android-support-v4r11.jar', 'wb') as file: file.write( b'PK\x03\x04\x14\x00\x08\x00\x08\x00-\x0eiA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x04\x00META-INF/\xfe\xca\x00\x00\x03\x00PK\x07\x08\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00' ) class FakeVcs: # no need to change to the correct commit here def gotorevision(self, rev, refresh=True): pass def getsrclib(self): return None def deinitsubmodules(self): pass # Test we trigger a scanner error without extlibs build.extlibs = [] os.makedirs('build/libs') shutil.copy('extlib/android/android-support-v4r11.jar', 'build/libs') fdroidserver.common.prepare_source( FakeVcs(), app, build, "build", "ignore", "extlib" ) count = fdroidserver.scanner.scan_source("build", build) self.assertEqual(1, count, "Should produce a scanner error without extlib") # Now try again as an extlib build.extlibs = ['android/android-support-v4r11.jar'] fdroidserver.common.prepare_source( FakeVcs(), app, build, "build", "ignore", "extlib" ) count = fdroidserver.scanner.scan_source("build", build) self.assertEqual(0, count, "Shouldn't error on jar from extlib") def test_failed_verifies_are_not_in_unsigned(self): os.chdir(self.testdir) sdk_path = os.path.join(self.testdir, 'android-sdk') self.create_fake_android_home(sdk_path) with open('config.yml', 'w') as fp: yaml.dump({'sdk_path': sdk_path, 'keep_when_not_allowed': True}, fp) os.chmod('config.yml', 0o600) fdroidserver.build.config = fdroidserver.common.read_config() os.mkdir('metadata') appid = 'info.guardianproject.checkey' metadata_file = os.path.join('metadata', appid + '.yml') shutil.copy(os.path.join(self.basedir, metadata_file), 'metadata') with open(metadata_file) as fp: app = fdroidserver.metadata.App(yaml.safe_load(fp)) app['RepoType'] = 'git' app['Binaries'] = ( 'https://example.com/fdroid/repo/info.guardianproject.checkey_%v.apk' ) build = fdroidserver.metadata.Build( { 'versionCode': 123, 'versionName': '1.2.3', 'commit': '1.2.3', 'disable': False, } ) app['Builds'] = [build] fdroidserver.metadata.write_metadata(metadata_file, app) os.makedirs(os.path.join('unsigned', 'binaries')) production_result = os.path.join( 'unsigned', '%s_%d.apk' % (appid, build['versionCode']) ) production_compare_file = os.path.join( 'unsigned', 'binaries', '%s_%d.binary.apk' % (appid, build['versionCode']) ) os.makedirs(os.path.join('tmp', 'binaries')) test_result = os.path.join('tmp', '%s_%d.apk' % (appid, build['versionCode'])) test_compare_file = os.path.join( 'tmp', 'binaries', '%s_%d.binary.apk' % (appid, build['versionCode']) ) with mock.patch( 'fdroidserver.common.force_exit', lambda *args: None ) as a, mock.patch( 'fdroidserver.common.get_android_tools_version_log', lambda: 'fake' ) as b, mock.patch( 'fdroidserver.common.FDroidPopen', FakeProcess ) as c, mock.patch( 'fdroidserver.build.FDroidPopen', FakeProcess ) as d, mock.patch( 'fdroidserver.build.trybuild', lambda *args: True ) as e, mock.patch( 'fdroidserver.net.download_file', lambda *args, **kwargs: None ) as f: a, b, c, d, e, f # silence linters' "unused" warnings with mock.patch('sys.argv', ['fdroid build', appid]): # successful comparison open(production_result, 'w').close() open(production_compare_file, 'w').close() with mock.patch('fdroidserver.common.verify_apks', lambda *args: None): fdroidserver.build.main() self.assertTrue(os.path.exists(production_result)) self.assertTrue(os.path.exists(production_compare_file)) # failed comparison open(production_result, 'w').close() open(production_compare_file, 'w').close() with mock.patch( 'fdroidserver.common.verify_apks', lambda *args: 'failed' ): fdroidserver.build.main() self.assertFalse(os.path.exists(production_result)) self.assertFalse(os.path.exists(production_compare_file)) with mock.patch('sys.argv', ['fdroid build', '--test', appid]): # successful comparison open(test_result, 'w').close() open(test_compare_file, 'w').close() with mock.patch('fdroidserver.common.verify_apks', lambda *args: None): fdroidserver.build.main() self.assertTrue(os.path.exists(test_result)) self.assertTrue(os.path.exists(test_compare_file)) self.assertFalse(os.path.exists(production_result)) self.assertFalse(os.path.exists(production_compare_file)) # failed comparison open(test_result, 'w').close() open(test_compare_file, 'w').close() with mock.patch( 'fdroidserver.common.verify_apks', lambda *args: 'failed' ): fdroidserver.build.main() self.assertTrue(os.path.exists(test_result)) self.assertFalse(os.path.exists(test_compare_file)) self.assertFalse(os.path.exists(production_result)) self.assertFalse(os.path.exists(production_compare_file)) def test_failed_allowedapksigningkeys_are_not_in_unsigned(self): os.chdir(self.testdir) os.mkdir('metadata') appid = 'info.guardianproject.checkey' metadata_file = os.path.join('metadata', appid + '.yml') shutil.copy(os.path.join(self.basedir, metadata_file), 'metadata') with open(metadata_file) as fp: app = fdroidserver.metadata.App(yaml.safe_load(fp)) app['RepoType'] = 'git' app['Binaries'] = ( 'https://example.com/fdroid/repo/info.guardianproject.checkey_%v.apk' ) build = fdroidserver.metadata.Build( { 'versionCode': 123, 'versionName': '1.2.3', 'commit': '1.2.3', 'disable': False, } ) app['Builds'] = [build] expected_key = 'a' * 64 bogus_key = 'b' * 64 app['AllowedAPKSigningKeys'] = [expected_key] fdroidserver.metadata.write_metadata(metadata_file, app) os.makedirs(os.path.join('unsigned', 'binaries')) production_result = os.path.join( 'unsigned', '%s_%d.apk' % (appid, build['versionCode']) ) production_compare_file = os.path.join( 'unsigned', 'binaries', '%s_%d.binary.apk' % (appid, build['versionCode']) ) os.makedirs(os.path.join('tmp', 'binaries')) test_result = os.path.join('tmp', '%s_%d.apk' % (appid, build['versionCode'])) test_compare_file = os.path.join( 'tmp', 'binaries', '%s_%d.binary.apk' % (appid, build['versionCode']) ) with mock.patch( 'fdroidserver.common.force_exit', lambda *args: None ) as a, mock.patch( 'fdroidserver.common.get_android_tools_version_log', lambda: 'fake' ) as b, mock.patch( 'fdroidserver.common.FDroidPopen', FakeProcess ) as c, mock.patch( 'fdroidserver.build.FDroidPopen', FakeProcess ) as d, mock.patch( 'fdroidserver.build.trybuild', lambda *args: True ) as e, mock.patch( 'fdroidserver.net.download_file', lambda *args, **kwargs: None ) as f: a, b, c, d, e, f # silence linters' "unused" warnings with mock.patch('sys.argv', ['fdroid build', appid]): # successful comparison, successful signer open(production_result, 'w').close() open(production_compare_file, 'w').close() with mock.patch( 'fdroidserver.common.verify_apks', lambda *args: None ) as g, mock.patch( 'fdroidserver.common.apk_signer_fingerprint', lambda *args: expected_key, ) as h: g, h fdroidserver.build.main() self.assertTrue(os.path.exists(production_result)) self.assertTrue(os.path.exists(production_compare_file)) # successful comparison, failed signer open(production_result, 'w').close() open(production_compare_file, 'w').close() with mock.patch( 'fdroidserver.common.verify_apks', lambda *args: None ) as g, mock.patch( 'fdroidserver.common.apk_signer_fingerprint', lambda *args: bogus_key, ) as h: g, h fdroidserver.build.main() self.assertFalse(os.path.exists(production_result)) self.assertFalse(os.path.exists(production_compare_file)) # failed comparison open(production_result, 'w').close() open(production_compare_file, 'w').close() with mock.patch( 'fdroidserver.common.verify_apks', lambda *args: 'failed' ): fdroidserver.build.main() self.assertFalse(os.path.exists(production_result)) self.assertFalse(os.path.exists(production_compare_file)) with mock.patch('sys.argv', ['fdroid build', '--test', appid]): # successful comparison, successful signer open(test_result, 'w').close() open(test_compare_file, 'w').close() with mock.patch( 'fdroidserver.common.verify_apks', lambda *args: None ) as g, mock.patch( 'fdroidserver.common.apk_signer_fingerprint', lambda *args: expected_key, ) as h: g, h fdroidserver.build.main() self.assertTrue(os.path.exists(test_result)) self.assertTrue(os.path.exists(test_compare_file)) self.assertFalse(os.path.exists(production_result)) self.assertFalse(os.path.exists(production_compare_file)) # successful comparison, failed signer open(test_result, 'w').close() open(test_compare_file, 'w').close() with mock.patch( 'fdroidserver.common.verify_apks', lambda *args: None ) as g, mock.patch( 'fdroidserver.common.apk_signer_fingerprint', lambda *args: bogus_key, ) as h: g, h fdroidserver.build.main() self.assertTrue(os.path.exists(test_result)) self.assertFalse(os.path.exists(test_compare_file)) self.assertFalse(os.path.exists(production_result)) self.assertFalse(os.path.exists(production_compare_file)) # failed comparison open(test_result, 'w').close() open(test_compare_file, 'w').close() with mock.patch( 'fdroidserver.common.verify_apks', lambda *args: 'failed' ): fdroidserver.build.main() self.assertTrue(os.path.exists(test_result)) self.assertFalse(os.path.exists(test_compare_file)) self.assertFalse(os.path.exists(production_result)) self.assertFalse(os.path.exists(production_compare_file)) @mock.patch('fdroidserver.vmtools.get_build_vm') @mock.patch('fdroidserver.vmtools.get_clean_builder') @mock.patch('paramiko.SSHClient') @mock.patch('subprocess.check_output') def test_build_server_cmdline( self, subprocess_check_output, paramiko_SSHClient, fdroidserver_vmtools_get_clean_builder, fdroidserver_vmtools_get_build_vm, ): """Test command line flags passed to the buildserver""" global cmdline_args test_flag = ['', False] def _exec_command(args): flag = test_flag[0] if test_flag[1]: self.assertTrue(flag in args, flag + ' should be present') else: self.assertFalse(flag in args, flag + ' should not be present') os.chdir(self.testdir) os.mkdir('tmp') chan = mock.MagicMock() chan.exec_command = _exec_command chan.recv_exit_status = lambda: 0 transport = mock.MagicMock() transport.open_session = mock.Mock(return_value=chan) sshs = mock.MagicMock() sshs.get_transport = mock.Mock(return_value=transport) paramiko_SSHClient.return_value = sshs subprocess_check_output.return_value = ( b'0123456789abcdef0123456789abcdefcafebabe' ) fdroidserver_vmtools_get_clean_builder.side_effect = lambda s: { 'hostname': 'example.com', 'idfile': '/path/to/id/file', 'port': 123, 'user': 'fake', } fdroidserver.common.config = {'sdk_path': '/fake/android/sdk/path'} fdroidserver.build.options = mock.MagicMock() vcs = mock.Mock() vcs.getsrclib = mock.Mock(return_value=None) app = fdroidserver.metadata.App() app['metadatapath'] = 'metadata/fake.id.yml' app['id'] = 'fake.id' app['RepoType'] = 'git' build = fdroidserver.metadata.Build( { 'versionCode': 123, 'versionName': '1.2.3', 'commit': '1.2.3', 'disable': False, } ) app['Builds'] = [build] test_flag = ('--on-server', True) fdroidserver.build.build_server(app, build, vcs, '', '', '', False) self.assertTrue(fdroidserver_vmtools_get_build_vm.called) for force in (True, False): test_flag = ('--force', force) fdroidserver.build.build_server(app, build, vcs, '', '', '', force) fdroidserver.build.options.notarball = True test_flag = ('--no-tarball', True) fdroidserver.build.build_server(app, build, vcs, '', '', '', False) fdroidserver.build.options.notarball = False test_flag = ('--no-tarball', False) fdroidserver.build.build_server(app, build, vcs, '', '', '', False) fdroidserver.build.options.skipscan = False test_flag = ('--scan-binary', True) fdroidserver.build.build_server(app, build, vcs, '', '', '', False) fdroidserver.build.options.skipscan = True test_flag = ('--scan-binary', False) fdroidserver.build.build_server(app, build, vcs, '', '', '', False) test_flag = ('--skip-scan', True) fdroidserver.build.build_server(app, build, vcs, '', '', '', False) @mock.patch('fdroidserver.vmtools.get_build_vm') @mock.patch('fdroidserver.vmtools.get_clean_builder') @mock.patch('paramiko.SSHClient') @mock.patch('subprocess.check_output') @mock.patch('fdroidserver.common.getsrclib') @mock.patch('fdroidserver.common.prepare_source') @mock.patch('fdroidserver.build.build_local') @mock.patch('fdroidserver.common.get_android_tools_version_log', lambda: 'versions') @mock.patch('fdroidserver.common.deploy_build_log_with_rsync', lambda a, b, c: None) def test_build_server_no_local_prepare( self, build_build_local, common_prepare_source, common_getsrclib, subprocess_check_output, paramiko_SSHClient, fdroidserver_vmtools_get_clean_builder, fdroidserver_vmtools_get_build_vm, ): """srclibs Prepare: should only be executed in the buildserver""" def _exec_command(args): print('chan.exec_command', args) def _getsrclib( spec, srclib_dir, basepath=False, raw=False, prepare=True, preponly=False, refresh=True, build=None, ): name, ref = spec.split('@') libdir = os.path.join(srclib_dir, name) os.mkdir(libdir) self.assertFalse(prepare, 'Prepare: scripts should never run on host') return name, None, libdir # TODO os.chdir(self.testdir) chan = mock.MagicMock() chan.exec_command = _exec_command chan.recv_exit_status = lambda: 0 transport = mock.MagicMock() transport.open_session = mock.Mock(return_value=chan) sshs = mock.MagicMock() sshs.get_transport = mock.Mock(return_value=transport) paramiko_SSHClient.return_value = sshs subprocess_check_output.return_value = ( b'0123456789abcdef0123456789abcdefcafebabe' ) fdroidserver_vmtools_get_clean_builder.side_effect = lambda s: { 'hostname': 'example.com', 'idfile': '/path/to/id/file', 'port': 123, 'user': 'fake', } fdroidserver.metadata.srclibs = { 'flutter': { 'RepoType': 'git', 'Repo': 'https://github.com/flutter/flutter', } } os.mkdir('srclibs') with open('srclibs/flutter.yml', 'w') as fp: yaml.dump(fdroidserver.metadata.srclibs, fp) common_getsrclib.side_effect = _getsrclib options = mock.MagicMock() options.force = False options.notarball = True options.onserver = False options.refresh = False options.scan_binary = False options.server = True options.skipscan = True options.test = False options.verbose = True fdroidserver.build.options = options fdroidserver.build.config = {'sdk_path': '/fake/android/sdk/path'} vcs = mock.Mock() vcs.getsrclib = mock.Mock(return_value=None) app = fdroidserver.metadata.App() app['metadatapath'] = 'metadata/fake.id.yml' app['id'] = 'fake.id' app['RepoType'] = 'git' spec = 'flutter@v1.7.8' build = fdroidserver.metadata.Build( { 'versionCode': 123, 'versionName': '1.2.3', 'commit': '1.2.3', 'disable': False, 'srclibs': [spec], } ) app['Builds'] = [build] build_dir = 'build' srclib_dir = os.path.join(build_dir, 'srclib') extlib_dir = os.path.join(build_dir, 'extlib') os.mkdir('tmp') os.mkdir(build_dir) os.mkdir(srclib_dir) fdroidserver.build.trybuild( app, build, build_dir, 'unsigned', 'logs', None, srclib_dir, extlib_dir, 'tmp', 'repo', vcs, options.test, options.server, options.force, options.onserver, options.refresh, ) common_getsrclib.assert_called_once_with( spec, srclib_dir, basepath=True, prepare=False ) common_prepare_source.assert_not_called() build_build_local.assert_not_called() def test_keep_when_not_allowed_default(self): self.assertFalse(fdroidserver.build.keep_when_not_allowed()) def test_keep_when_not_allowed_config_true(self): fdroidserver.build.config = {'keep_when_not_allowed': True} self.assertTrue(fdroidserver.build.keep_when_not_allowed()) def test_keep_when_not_allowed_config_false(self): fdroidserver.build.config = {'keep_when_not_allowed': False} self.assertFalse(fdroidserver.build.keep_when_not_allowed()) def test_keep_when_not_allowed_options_true(self): fdroidserver.build.options = Options fdroidserver.build.options.keep_when_not_allowed = True self.assertTrue(fdroidserver.build.keep_when_not_allowed()) def test_keep_when_not_allowed_options_false(self): fdroidserver.build.options = Options fdroidserver.build.options.keep_when_not_allowed = False self.assertFalse(fdroidserver.build.keep_when_not_allowed()) def test_keep_when_not_allowed_options_true_override_config(self): fdroidserver.build.options = Options fdroidserver.build.options.keep_when_not_allowed = True fdroidserver.build.config = {'keep_when_not_allowed': False} self.assertTrue(fdroidserver.build.keep_when_not_allowed()) def test_keep_when_not_allowed_options_default_does_not_override(self): fdroidserver.build.options = Options fdroidserver.build.options.keep_when_not_allowed = False fdroidserver.build.config = {'keep_when_not_allowed': True} self.assertTrue(fdroidserver.build.keep_when_not_allowed()) def test_keep_when_not_allowed_all_true(self): fdroidserver.build.options = Options fdroidserver.build.options.keep_when_not_allowed = True fdroidserver.build.config = {'keep_when_not_allowed': True} self.assertTrue(fdroidserver.build.keep_when_not_allowed()) def test_keep_when_not_allowed_all_false(self): fdroidserver.build.options = Options fdroidserver.build.options.keep_when_not_allowed = False fdroidserver.build.config = {'keep_when_not_allowed': False} self.assertFalse(fdroidserver.build.keep_when_not_allowed()) if __name__ == "__main__": os.chdir(os.path.dirname(__file__)) import argparse 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(BuildTest)) unittest.main(failfast=False)