1
0
mirror of https://gitlab.com/fdroid/fdroidserver.git synced 2024-07-07 09:50:07 +02:00
fdroidserver/tests/update.TestCase
Hans-Christoph Steiner 0f4cbc7224 allow APKs with same packageName/versionCode but different signer
There are many APKs out in the wild that claim to be the same app and
version and each other, but they are signed by different keys.  fdroid
should be able to index these, and work with them.   This supports having
the developer's signature via reproducible builds, random collections of
APKs like repomaker, etc.
2017-06-01 10:30:41 +02:00

313 lines
14 KiB
Python
Executable File

#!/usr/bin/env python3
# http://www.drdobbs.com/testing/unit-testing-with-python/240165163
import git
import inspect
import logging
import optparse
import os
import shutil
import sys
import tempfile
import unittest
import yaml
from binascii import unhexlify
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)
import fdroidserver.common
import fdroidserver.metadata
import fdroidserver.update
from fdroidserver.common import FDroidPopen
class UpdateTest(unittest.TestCase):
'''fdroid update'''
def testInsertStoreMetadata(self):
config = dict()
fdroidserver.common.fill_config_defaults(config)
config['accepted_formats'] = ('txt', 'yml')
fdroidserver.update.config = config
fdroidserver.update.options = fdroidserver.common.options
os.chdir(os.path.join(localmodule, 'tests'))
shutil.rmtree(os.path.join('repo', 'info.guardianproject.urzip'), ignore_errors=True)
apps = dict()
for packageName in ('info.guardianproject.urzip', 'org.videolan.vlc', 'obb.mainpatch.current'):
apps[packageName] = dict()
apps[packageName]['id'] = packageName
apps[packageName]['CurrentVersionCode'] = 0xcafebeef
apps['info.guardianproject.urzip']['CurrentVersionCode'] = 100
fdroidserver.update.insert_localized_app_metadata(apps)
appdir = os.path.join('repo', 'info.guardianproject.urzip', 'en-US')
self.assertTrue(os.path.isfile(os.path.join(appdir, 'icon.png')))
self.assertTrue(os.path.isfile(os.path.join(appdir, 'featureGraphic.png')))
self.assertEqual(3, len(apps))
for packageName, app in apps.items():
self.assertTrue('localized' in app)
self.assertTrue('en-US' in app['localized'])
self.assertEqual(1, len(app['localized']))
if packageName == 'info.guardianproject.urzip':
self.assertEqual(7, len(app['localized']['en-US']))
self.assertEqual('full description\n', app['localized']['en-US']['description'])
self.assertEqual('title\n', app['localized']['en-US']['name'])
self.assertEqual('short description\n', app['localized']['en-US']['summary'])
self.assertEqual('video\n', app['localized']['en-US']['video'])
self.assertEqual('icon.png', app['localized']['en-US']['icon'])
self.assertEqual('featureGraphic.png', app['localized']['en-US']['featureGraphic'])
self.assertEqual('100\n', app['localized']['en-US']['whatsNew'])
elif packageName == 'org.videolan.vlc':
self.assertEqual('icon.png', app['localized']['en-US']['icon'])
self.assertEqual(9, len(app['localized']['en-US']['phoneScreenshots']))
self.assertEqual(15, len(app['localized']['en-US']['sevenInchScreenshots']))
elif packageName == 'obb.mainpatch.current':
self.assertEqual('icon.png', app['localized']['en-US']['icon'])
self.assertEqual('featureGraphic.png', app['localized']['en-US']['featureGraphic'])
self.assertEqual(1, len(app['localized']['en-US']['phoneScreenshots']))
self.assertEqual(1, len(app['localized']['en-US']['sevenInchScreenshots']))
def test_insert_triple_t_metadata(self):
importer = os.path.join(localmodule, 'tests', 'tmp', 'importer')
packageName = 'org.fdroid.ci.test.app'
if not os.path.isdir(importer):
logging.warning('skipping test_insert_triple_t_metadata, import.TestCase must run first!')
return
tmpdir = os.path.join(localmodule, '.testfiles')
if not os.path.exists(tmpdir):
os.makedirs(tmpdir)
tmptestsdir = tempfile.mkdtemp(prefix='test_insert_triple_t_metadata-', dir=tmpdir)
packageDir = os.path.join(tmptestsdir, 'build', packageName)
shutil.copytree(importer, packageDir)
# always use the same commit so these tests work when ci-test-app.git is updated
repo = git.Repo(packageDir)
for remote in repo.remotes:
remote.fetch()
repo.git.reset('--hard', 'b9e5d1a0d8d6fc31d4674b2f0514fef10762ed4f')
repo.git.clean('-fdx')
os.mkdir(os.path.join(tmptestsdir, 'metadata'))
metadata = dict()
metadata['Description'] = 'This is just a test app'
with open(os.path.join(tmptestsdir, 'metadata', packageName + '.yml'), 'w') as fp:
yaml.dump(metadata, fp)
config = dict()
fdroidserver.common.fill_config_defaults(config)
config['accepted_formats'] = ('yml')
fdroidserver.common.config = config
fdroidserver.update.config = config
fdroidserver.update.options = fdroidserver.common.options
os.chdir(tmptestsdir)
apps = fdroidserver.metadata.read_metadata(xref=True)
fdroidserver.update.copy_triple_t_store_metadata(apps)
# TODO ideally, this would compare the whole dict like in metadata.TestCase's test_read_metadata()
correctlocales = [
'ar', 'ast_ES', 'az', 'ca', 'ca_ES', 'cs-CZ', 'cs_CZ', 'da',
'da-DK', 'de', 'de-DE', 'el', 'en-US', 'es', 'es-ES', 'es_ES', 'et',
'fi', 'fr', 'fr-FR', 'he_IL', 'hi-IN', 'hi_IN', 'hu', 'id', 'it',
'it-IT', 'it_IT', 'iw-IL', 'ja', 'ja-JP', 'kn_IN', 'ko', 'ko-KR',
'ko_KR', 'lt', 'nb', 'nb_NO', 'nl', 'nl-NL', 'no', 'pl', 'pl-PL',
'pl_PL', 'pt', 'pt-BR', 'pt-PT', 'pt_BR', 'ro', 'ro_RO', 'ru-RU',
'ru_RU', 'sv-SE', 'sv_SE', 'te', 'tr', 'tr-TR', 'uk', 'uk_UA', 'vi',
'vi_VN', 'zh-CN', 'zh_CN', 'zh_TW',
]
locales = sorted(list(apps['org.fdroid.ci.test.app']['localized'].keys()))
self.assertEqual(correctlocales, locales)
def javagetsig(self, apkfile):
getsig_dir = os.path.join(os.path.dirname(__file__), 'getsig')
if not os.path.exists(getsig_dir + "/getsig.class"):
logging.critical("getsig.class not found. To fix: cd '%s' && ./make.sh" % getsig_dir)
sys.exit(1)
# FDroidPopen needs some config to work
config = dict()
fdroidserver.common.fill_config_defaults(config)
fdroidserver.common.config = config
p = FDroidPopen(['java', '-cp', os.path.join(os.path.dirname(__file__), 'getsig'),
'getsig', os.path.join(os.getcwd(), apkfile)])
sig = None
for line in p.output.splitlines():
if line.startswith('Result:'):
sig = line[7:].strip()
break
if p.returncode == 0:
return sig
else:
return None
def testGoodGetsig(self):
# config needed to use jarsigner and keytool
config = dict()
fdroidserver.common.fill_config_defaults(config)
fdroidserver.update.config = config
apkfile = os.path.join(os.path.dirname(__file__), 'urzip.apk')
sig = self.javagetsig(apkfile)
self.assertIsNotNone(sig, "sig is None")
pysig = fdroidserver.update.getsig(apkfile)
self.assertIsNotNone(pysig, "pysig is None")
self.assertEqual(sig, fdroidserver.update.getsig(apkfile),
"python sig not equal to java sig!")
self.assertEqual(len(sig), len(pysig),
"the length of the two sigs are different!")
try:
self.assertEqual(unhexlify(sig), unhexlify(pysig),
"the length of the two sigs are different!")
except TypeError as e:
print(e)
self.assertTrue(False, 'TypeError!')
def testBadGetsig(self):
# config needed to use jarsigner and keytool
config = dict()
fdroidserver.common.fill_config_defaults(config)
fdroidserver.update.config = config
apkfile = os.path.join(os.path.dirname(__file__), 'urzip-badsig.apk')
sig = self.javagetsig(apkfile)
self.assertIsNone(sig, "sig should be None: " + str(sig))
pysig = fdroidserver.update.getsig(apkfile)
self.assertIsNone(pysig, "python sig should be None: " + str(sig))
apkfile = os.path.join(os.path.dirname(__file__), 'urzip-badcert.apk')
sig = self.javagetsig(apkfile)
self.assertIsNone(sig, "sig should be None: " + str(sig))
pysig = fdroidserver.update.getsig(apkfile)
self.assertIsNone(pysig, "python sig should be None: " + str(sig))
def testScanApksAndObbs(self):
os.chdir(os.path.join(localmodule, 'tests'))
if os.path.basename(os.getcwd()) != 'tests':
raise Exception('This test must be run in the "tests/" subdir')
config = dict()
fdroidserver.common.fill_config_defaults(config)
config['ndk_paths'] = dict()
config['accepted_formats'] = ['json', 'txt', 'yml']
fdroidserver.common.config = config
fdroidserver.update.config = config
fdroidserver.update.options = type('', (), {})()
fdroidserver.update.options.clean = True
fdroidserver.update.options.delete_unknown = True
apps = fdroidserver.metadata.read_metadata(xref=True)
knownapks = fdroidserver.common.KnownApks()
apks, cachechanged = fdroidserver.update.scan_apks({}, 'repo', knownapks, False)
self.assertEqual(len(apks), 7)
apk = apks[0]
self.assertEqual(apk['minSdkVersion'], '4')
self.assertEqual(apk['targetSdkVersion'], '18')
self.assertFalse('maxSdkVersion' in apk)
fdroidserver.update.insert_obbs('repo', apps, apks)
for apk in apks:
if apk['packageName'] == 'obb.mainpatch.current':
self.assertEqual(apk.get('obbMainFile'), 'main.1619.obb.mainpatch.current.obb')
self.assertEqual(apk.get('obbPatchFile'), 'patch.1619.obb.mainpatch.current.obb')
elif apk['packageName'] == 'obb.main.oldversion':
self.assertEqual(apk.get('obbMainFile'), 'main.1434483388.obb.main.oldversion.obb')
self.assertIsNone(apk.get('obbPatchFile'))
elif apk['packageName'] == 'obb.main.twoversions':
self.assertIsNone(apk.get('obbPatchFile'))
if apk['versionCode'] == 1101613:
self.assertEqual(apk.get('obbMainFile'), 'main.1101613.obb.main.twoversions.obb')
elif apk['versionCode'] == 1101615:
self.assertEqual(apk.get('obbMainFile'), 'main.1101615.obb.main.twoversions.obb')
elif apk['versionCode'] == 1101617:
self.assertEqual(apk.get('obbMainFile'), 'main.1101615.obb.main.twoversions.obb')
else:
self.assertTrue(False)
elif apk['packageName'] == 'info.guardianproject.urzip':
self.assertIsNone(apk.get('obbMainFile'))
self.assertIsNone(apk.get('obbPatchFile'))
def testScanApkMetadata(self):
def _build_yaml_representer(dumper, data):
'''Creates a YAML representation of a Build instance'''
return dumper.represent_dict(data)
config = dict()
fdroidserver.common.fill_config_defaults(config)
fdroidserver.update.config = config
os.chdir(os.path.dirname(__file__))
if os.path.basename(os.getcwd()) != 'tests':
raise Exception('This test must be run in the "tests/" subdir')
config['ndk_paths'] = dict()
config['accepted_formats'] = ['json', 'txt', 'yml']
fdroidserver.common.config = config
fdroidserver.update.config = config
fdroidserver.update.options = type('', (), {})()
fdroidserver.update.options.clean = True
fdroidserver.update.options.rename_apks = False
fdroidserver.update.options.delete_unknown = True
for icon_dir in fdroidserver.update.get_all_icon_dirs('repo'):
if not os.path.exists(icon_dir):
os.makedirs(icon_dir)
knownapks = fdroidserver.common.KnownApks()
apkList = ['../urzip.apk', '../org.dyndns.fules.ck_20.apk']
for apkName in apkList:
_, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'repo', knownapks, False)
# Don't care about the date added to the repo and relative apkName
del apk['added']
del apk['apkName']
# avoid AAPT application name bug
del apk['name']
savepath = os.path.join('metadata', 'apk', apk['packageName'] + '.yaml')
# Uncomment to save APK metadata
# with open(savepath, 'w') as f:
# yaml.add_representer(fdroidserver.metadata.Build, _build_yaml_representer)
# yaml.dump(apk, f, default_flow_style=False)
with open(savepath, 'r') as f:
frompickle = yaml.load(f)
self.maxDiff = None
self.assertEqual(apk, frompickle)
def test_scan_invalid_apk(self):
os.chdir(os.path.join(localmodule, 'tests'))
if os.path.basename(os.getcwd()) != 'tests':
raise Exception('This test must be run in the "tests/" subdir')
config = dict()
fdroidserver.common.fill_config_defaults(config)
fdroidserver.common.config = config
fdroidserver.update.config = config
fdroidserver.update.options.delete_unknown = False
knownapks = fdroidserver.common.KnownApks()
apk = 'fake.ota.update_1234.zip' # this is not an APK, scanning should fail
(skip, apk, cachechanged) = fdroidserver.update.scan_apk({}, apk, 'repo', knownapks, False)
self.assertTrue(skip)
self.assertIsNone(apk)
self.assertFalse(cachechanged)
if __name__ == "__main__":
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(UpdateTest))
unittest.main()