1
0
mirror of https://gitlab.com/fdroid/fdroidserver.git synced 2024-11-19 05:10:10 +01:00
fdroidserver/tests/build.TestCase

643 lines
24 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# http://www.drdobbs.com/testing/unit-testing-with-python/240165163
import inspect
import logging
import optparse
import os
import shutil
import sys
import tempfile
import textwrap
import unittest
import yaml
import zipfile
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
class FakeProcess:
output = 'fake output'
returncode = 0
def __init__(self, args, **kwargs):
print('FakeFDroidPopen', args, kwargs)
class BuildTest(unittest.TestCase):
'''fdroidserver/build.py'''
def setUp(self):
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('androguard.axml')
logger.setLevel(logging.INFO) # tame the axml debug messages
self.basedir = os.path.join(localmodule, 'tests')
os.chdir(self.basedir)
fdroidserver.common.config = None
fdroidserver.build.config = 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)
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
vcs = mock.Mock()
def make_fake_apk(output, build):
with open(build.output, 'w') as fp:
fp.write('APK PLACEHOLDER')
return output
def fake_download_file(_ignored, local_filename):
_ignored # silence the linters
with zipfile.ZipFile(local_filename, 'x') as zipfp:
zipfp.writestr(
'android-ndk-r21e/source.properties',
'Pkg.Revision = 21.4.7075529\n',
)
# 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.is_apk_and_debuggable', return_value=False
) as _ignored, mock.patch(
'fdroidserver.common.sha256sum',
return_value='ad7ce5467e18d40050dc51b8e7affc3e635c85bd8c59be62de32352328ed467e',
) as _ignored, mock.patch(
'fdroidserver.common.is_apk_and_debuggable', return_value=False
) as _ignored, mock.patch(
'fdroidserver.build.FDroidPopen', FakeProcess
) as _ignored, mock.patch(
'fdroidserver.net.download_file', wraps=fake_download_file
) 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`
self.assertTrue('r21e' not 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(os.path.exists(config['ndk_paths']['r21e']))
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(
"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project basedir="." default="clean" name="mockapp">
<target name="release"/>
<target name="clean"/>
</project>"""
)
)
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_apk_and_debuggable', 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.get_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
# 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}, fp)
os.chmod('config.yml', 0o600)
fdroidserver.common.build = 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))
@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)
if __name__ == "__main__":
os.chdir(os.path.dirname(__file__))
parser = optparse.OptionParser()
parser.add_option(
"-v",
"--verbose",
action="store_true",
default=False,
help="Spew out even more information than normal",
)
(fdroidserver.common.options, args) = parser.parse_args(['--verbose'])
newSuite = unittest.TestSuite()
newSuite.addTest(unittest.makeSuite(BuildTest))
unittest.main(failfast=False)