3 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
15 from binascii import unhexlify
17 localmodule = os.path.realpath(
18 os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..'))
19 print('localmodule: ' + localmodule)
20 if localmodule not in sys.path:
21 sys.path.insert(0, localmodule)
23 import fdroidserver.common
24 import fdroidserver.metadata
25 import fdroidserver.update
26 from fdroidserver.common import FDroidPopen
29 class UpdateTest(unittest.TestCase):
32 def testInsertStoreMetadata(self):
34 fdroidserver.common.fill_config_defaults(config)
35 config['accepted_formats'] = ('txt', 'yml')
36 fdroidserver.update.config = config
37 fdroidserver.update.options = fdroidserver.common.options
38 os.chdir(os.path.join(localmodule, 'tests'))
40 shutil.rmtree(os.path.join('repo', 'info.guardianproject.urzip'), ignore_errors=True)
43 for packageName in ('info.guardianproject.urzip', 'org.videolan.vlc', 'obb.mainpatch.current'):
44 apps[packageName] = dict()
45 apps[packageName]['id'] = packageName
46 apps[packageName]['CurrentVersionCode'] = 0xcafebeef
47 apps['info.guardianproject.urzip']['CurrentVersionCode'] = 100
48 fdroidserver.update.insert_localized_app_metadata(apps)
50 appdir = os.path.join('repo', 'info.guardianproject.urzip', 'en-US')
51 self.assertTrue(os.path.isfile(os.path.join(appdir, 'icon.png')))
52 self.assertTrue(os.path.isfile(os.path.join(appdir, 'featureGraphic.png')))
54 self.assertEqual(3, len(apps))
55 for packageName, app in apps.items():
56 self.assertTrue('localized' in app)
57 self.assertTrue('en-US' in app['localized'])
58 self.assertEqual(1, len(app['localized']))
59 if packageName == 'info.guardianproject.urzip':
60 self.assertEqual(7, len(app['localized']['en-US']))
61 self.assertEqual('full description\n', app['localized']['en-US']['description'])
62 self.assertEqual('title\n', app['localized']['en-US']['name'])
63 self.assertEqual('short description\n', app['localized']['en-US']['summary'])
64 self.assertEqual('video\n', app['localized']['en-US']['video'])
65 self.assertEqual('icon.png', app['localized']['en-US']['icon'])
66 self.assertEqual('featureGraphic.png', app['localized']['en-US']['featureGraphic'])
67 self.assertEqual('100\n', app['localized']['en-US']['whatsNew'])
68 elif packageName == 'org.videolan.vlc':
69 self.assertEqual('icon.png', app['localized']['en-US']['icon'])
70 self.assertEqual(9, len(app['localized']['en-US']['phoneScreenshots']))
71 self.assertEqual(15, len(app['localized']['en-US']['sevenInchScreenshots']))
72 elif packageName == 'obb.mainpatch.current':
73 self.assertEqual('icon.png', app['localized']['en-US']['icon'])
74 self.assertEqual('featureGraphic.png', app['localized']['en-US']['featureGraphic'])
75 self.assertEqual(1, len(app['localized']['en-US']['phoneScreenshots']))
76 self.assertEqual(1, len(app['localized']['en-US']['sevenInchScreenshots']))
78 def test_insert_triple_t_metadata(self):
79 importer = os.path.join(localmodule, 'tests', 'tmp', 'importer')
80 packageName = 'org.fdroid.ci.test.app'
81 if not os.path.isdir(importer):
82 logging.warning('skipping test_insert_triple_t_metadata, import.TestCase must run first!')
84 tmpdir = os.path.join(localmodule, '.testfiles')
85 if not os.path.exists(tmpdir):
87 tmptestsdir = tempfile.mkdtemp(prefix='test_insert_triple_t_metadata-', dir=tmpdir)
88 packageDir = os.path.join(tmptestsdir, 'build', packageName)
89 shutil.copytree(importer, packageDir)
91 # always use the same commit so these tests work when ci-test-app.git is updated
92 repo = git.Repo(packageDir)
93 for remote in repo.remotes:
95 repo.git.reset('--hard', 'b9e5d1a0d8d6fc31d4674b2f0514fef10762ed4f')
96 repo.git.clean('-fdx')
98 os.mkdir(os.path.join(tmptestsdir, 'metadata'))
100 metadata['Description'] = 'This is just a test app'
101 with open(os.path.join(tmptestsdir, 'metadata', packageName + '.yml'), 'w') as fp:
102 yaml.dump(metadata, fp)
105 fdroidserver.common.fill_config_defaults(config)
106 config['accepted_formats'] = ('yml')
107 fdroidserver.common.config = config
108 fdroidserver.update.config = config
109 fdroidserver.update.options = fdroidserver.common.options
110 os.chdir(tmptestsdir)
112 apps = fdroidserver.metadata.read_metadata(xref=True)
113 fdroidserver.update.copy_triple_t_store_metadata(apps)
115 # TODO ideally, this would compare the whole dict like in metadata.TestCase's test_read_metadata()
117 'ar', 'ast_ES', 'az', 'ca', 'ca_ES', 'cs-CZ', 'cs_CZ', 'da',
118 'da-DK', 'de', 'de-DE', 'el', 'en-US', 'es', 'es-ES', 'es_ES', 'et',
119 'fi', 'fr', 'fr-FR', 'he_IL', 'hi-IN', 'hi_IN', 'hu', 'id', 'it',
120 'it-IT', 'it_IT', 'iw-IL', 'ja', 'ja-JP', 'kn_IN', 'ko', 'ko-KR',
121 'ko_KR', 'lt', 'nb', 'nb_NO', 'nl', 'nl-NL', 'no', 'pl', 'pl-PL',
122 'pl_PL', 'pt', 'pt-BR', 'pt-PT', 'pt_BR', 'ro', 'ro_RO', 'ru-RU',
123 'ru_RU', 'sv-SE', 'sv_SE', 'te', 'tr', 'tr-TR', 'uk', 'uk_UA', 'vi',
124 'vi_VN', 'zh-CN', 'zh_CN', 'zh_TW',
126 locales = sorted(list(apps['org.fdroid.ci.test.app']['localized'].keys()))
127 self.assertEqual(correctlocales, locales)
129 def javagetsig(self, apkfile):
130 getsig_dir = os.path.join(os.path.dirname(__file__), 'getsig')
131 if not os.path.exists(getsig_dir + "/getsig.class"):
132 logging.critical("getsig.class not found. To fix: cd '%s' && ./make.sh" % getsig_dir)
134 # FDroidPopen needs some config to work
136 fdroidserver.common.fill_config_defaults(config)
137 fdroidserver.common.config = config
138 p = FDroidPopen(['java', '-cp', os.path.join(os.path.dirname(__file__), 'getsig'),
139 'getsig', os.path.join(os.getcwd(), apkfile)])
141 for line in p.output.splitlines():
142 if line.startswith('Result:'):
143 sig = line[7:].strip()
145 if p.returncode == 0:
150 def testGoodGetsig(self):
151 # config needed to use jarsigner and keytool
153 fdroidserver.common.fill_config_defaults(config)
154 fdroidserver.update.config = config
155 apkfile = os.path.join(os.path.dirname(__file__), 'urzip.apk')
156 sig = self.javagetsig(apkfile)
157 self.assertIsNotNone(sig, "sig is None")
158 pysig = fdroidserver.update.getsig(apkfile)
159 self.assertIsNotNone(pysig, "pysig is None")
160 self.assertEqual(sig, fdroidserver.update.getsig(apkfile),
161 "python sig not equal to java sig!")
162 self.assertEqual(len(sig), len(pysig),
163 "the length of the two sigs are different!")
165 self.assertEqual(unhexlify(sig), unhexlify(pysig),
166 "the length of the two sigs are different!")
167 except TypeError as e:
169 self.assertTrue(False, 'TypeError!')
171 def testBadGetsig(self):
172 """getsig() should still be able to fetch the fingerprint of bad signatures"""
173 # config needed to use jarsigner and keytool
175 fdroidserver.common.fill_config_defaults(config)
176 fdroidserver.update.config = config
178 apkfile = os.path.join(os.path.dirname(__file__), 'urzip-badsig.apk')
179 sig = fdroidserver.update.getsig(apkfile)
180 self.assertEqual(sig, 'e0ecb5fc2d63088e4a07ae410a127722',
181 "python sig should be: " + str(sig))
183 apkfile = os.path.join(os.path.dirname(__file__), 'urzip-badcert.apk')
184 sig = fdroidserver.update.getsig(apkfile)
185 self.assertEqual(sig, 'e0ecb5fc2d63088e4a07ae410a127722',
186 "python sig should be: " + str(sig))
188 def testScanApksAndObbs(self):
189 os.chdir(os.path.join(localmodule, 'tests'))
190 if os.path.basename(os.getcwd()) != 'tests':
191 raise Exception('This test must be run in the "tests/" subdir')
194 fdroidserver.common.fill_config_defaults(config)
195 config['ndk_paths'] = dict()
196 config['accepted_formats'] = ['json', 'txt', 'yml']
197 fdroidserver.common.config = config
198 fdroidserver.update.config = config
200 fdroidserver.update.options = type('', (), {})()
201 fdroidserver.update.options.clean = True
202 fdroidserver.update.options.delete_unknown = True
203 fdroidserver.update.options.rename_apks = False
204 fdroidserver.update.options.allow_disabled_algorithms = False
206 apps = fdroidserver.metadata.read_metadata(xref=True)
207 knownapks = fdroidserver.common.KnownApks()
208 apks, cachechanged = fdroidserver.update.scan_apks({}, 'repo', knownapks, False)
209 self.assertEqual(len(apks), 11)
211 self.assertEqual(apk['packageName'], 'com.politedroid')
212 self.assertEqual(apk['versionCode'], 3)
213 self.assertEqual(apk['minSdkVersion'], '3')
214 self.assertEqual(apk['targetSdkVersion'], '3')
215 self.assertFalse('maxSdkVersion' in apk)
217 self.assertEqual(apk['packageName'], 'obb.main.oldversion')
218 self.assertEqual(apk['versionCode'], 1444412523)
219 self.assertEqual(apk['minSdkVersion'], '4')
220 self.assertEqual(apk['targetSdkVersion'], '18')
221 self.assertFalse('maxSdkVersion' in apk)
223 fdroidserver.update.insert_obbs('repo', apps, apks)
225 if apk['packageName'] == 'obb.mainpatch.current':
226 self.assertEqual(apk.get('obbMainFile'), 'main.1619.obb.mainpatch.current.obb')
227 self.assertEqual(apk.get('obbPatchFile'), 'patch.1619.obb.mainpatch.current.obb')
228 elif apk['packageName'] == 'obb.main.oldversion':
229 self.assertEqual(apk.get('obbMainFile'), 'main.1434483388.obb.main.oldversion.obb')
230 self.assertIsNone(apk.get('obbPatchFile'))
231 elif apk['packageName'] == 'obb.main.twoversions':
232 self.assertIsNone(apk.get('obbPatchFile'))
233 if apk['versionCode'] == 1101613:
234 self.assertEqual(apk.get('obbMainFile'), 'main.1101613.obb.main.twoversions.obb')
235 elif apk['versionCode'] == 1101615:
236 self.assertEqual(apk.get('obbMainFile'), 'main.1101615.obb.main.twoversions.obb')
237 elif apk['versionCode'] == 1101617:
238 self.assertEqual(apk.get('obbMainFile'), 'main.1101615.obb.main.twoversions.obb')
240 self.assertTrue(False)
241 elif apk['packageName'] == 'info.guardianproject.urzip':
242 self.assertIsNone(apk.get('obbMainFile'))
243 self.assertIsNone(apk.get('obbPatchFile'))
245 def testScanApkMetadata(self):
247 def _build_yaml_representer(dumper, data):
248 '''Creates a YAML representation of a Build instance'''
249 return dumper.represent_dict(data)
252 fdroidserver.common.fill_config_defaults(config)
253 fdroidserver.update.config = config
254 os.chdir(os.path.join(localmodule, 'tests'))
255 if os.path.basename(os.getcwd()) != 'tests':
256 raise Exception('This test must be run in the "tests/" subdir')
258 config['ndk_paths'] = dict()
259 config['accepted_formats'] = ['json', 'txt', 'yml']
260 fdroidserver.common.config = config
261 fdroidserver.update.config = config
263 fdroidserver.update.options = type('', (), {})()
264 fdroidserver.update.options.clean = True
265 fdroidserver.update.options.rename_apks = False
266 fdroidserver.update.options.delete_unknown = True
267 fdroidserver.update.options.allow_disabled_algorithms = False
269 for icon_dir in fdroidserver.update.get_all_icon_dirs('repo'):
270 if not os.path.exists(icon_dir):
271 os.makedirs(icon_dir)
273 knownapks = fdroidserver.common.KnownApks()
274 apkList = ['../urzip.apk', '../org.dyndns.fules.ck_20.apk']
276 for apkName in apkList:
277 _, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'repo', knownapks, False)
278 # Don't care about the date added to the repo and relative apkName
281 # avoid AAPT application name bug
284 savepath = os.path.join('metadata', 'apk', apk['packageName'] + '.yaml')
285 # Uncomment to save APK metadata
286 # with open(savepath, 'w') as f:
287 # yaml.add_representer(fdroidserver.metadata.Build, _build_yaml_representer)
288 # yaml.dump(apk, f, default_flow_style=False)
290 with open(savepath, 'r') as f:
291 frompickle = yaml.load(f)
293 self.assertEqual(apk, frompickle)
295 def test_scan_apk_signed_by_disabled_algorithms(self):
296 os.chdir(os.path.join(localmodule, 'tests'))
297 if os.path.basename(os.getcwd()) != 'tests':
298 raise Exception('This test must be run in the "tests/" subdir')
301 fdroidserver.common.fill_config_defaults(config)
302 fdroidserver.update.config = config
304 config['ndk_paths'] = dict()
305 config['accepted_formats'] = ['json', 'txt', 'yml']
306 fdroidserver.common.config = config
307 fdroidserver.update.config = config
309 fdroidserver.update.options = type('', (), {})()
310 fdroidserver.update.options.clean = True
311 fdroidserver.update.options.verbose = True
312 fdroidserver.update.options.rename_apks = False
313 fdroidserver.update.options.delete_unknown = True
314 fdroidserver.update.options.allow_disabled_algorithms = False
316 knownapks = fdroidserver.common.KnownApks()
317 apksourcedir = os.getcwd()
318 tmpdir = os.path.join(localmodule, '.testfiles')
319 if not os.path.exists(tmpdir):
321 tmptestsdir = tempfile.mkdtemp(prefix='test_scan_apk_signed_by_disabled_algorithms-', dir=tmpdir)
322 print('tmptestsdir', tmptestsdir)
323 os.chdir(tmptestsdir)
326 # setup the repo, create icons dirs, etc.
327 fdroidserver.update.scan_apks({}, 'repo', knownapks)
328 fdroidserver.update.scan_apks({}, 'archive', knownapks)
330 disabledsigs = ['org.bitbucket.tickytacky.mirrormirror_2.apk', ]
331 for apkName in disabledsigs:
332 shutil.copy(os.path.join(apksourcedir, apkName),
333 os.path.join(tmptestsdir, 'repo'))
335 skip, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'repo', knownapks,
336 allow_disabled_algorithms=True,
337 archive_bad_sig=False)
338 self.assertFalse(skip)
339 self.assertIsNotNone(apk)
340 self.assertTrue(cachechanged)
341 self.assertFalse(os.path.exists(os.path.join('archive', apkName)))
342 self.assertTrue(os.path.exists(os.path.join('repo', apkName)))
344 # this test only works on systems with fully updated Java/jarsigner
345 # that has MD5 listed in jdk.jar.disabledAlgorithms in java.security
346 skip, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'repo', knownapks,
347 allow_disabled_algorithms=False,
348 archive_bad_sig=True)
349 self.assertTrue(skip)
350 self.assertIsNone(apk)
351 self.assertFalse(cachechanged)
352 self.assertTrue(os.path.exists(os.path.join('archive', apkName)))
353 self.assertFalse(os.path.exists(os.path.join('repo', apkName)))
355 skip, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'archive', knownapks,
356 allow_disabled_algorithms=False,
357 archive_bad_sig=False)
358 self.assertFalse(skip)
359 self.assertIsNotNone(apk)
360 self.assertTrue(cachechanged)
361 self.assertTrue(os.path.exists(os.path.join('archive', apkName)))
362 self.assertFalse(os.path.exists(os.path.join('repo', apkName)))
364 badsigs = ['urzip-badcert.apk', 'urzip-badsig.apk', 'urzip-release-unsigned.apk', ]
365 for apkName in badsigs:
366 shutil.copy(os.path.join(apksourcedir, apkName),
367 os.path.join(tmptestsdir, 'repo'))
369 skip, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'repo', knownapks,
370 allow_disabled_algorithms=False,
371 archive_bad_sig=False)
372 self.assertTrue(skip)
373 self.assertIsNone(apk)
374 self.assertFalse(cachechanged)
376 def test_scan_invalid_apk(self):
377 os.chdir(os.path.join(localmodule, 'tests'))
378 if os.path.basename(os.getcwd()) != 'tests':
379 raise Exception('This test must be run in the "tests/" subdir')
382 fdroidserver.common.fill_config_defaults(config)
383 fdroidserver.common.config = config
384 fdroidserver.update.config = config
385 fdroidserver.update.options.delete_unknown = False
387 knownapks = fdroidserver.common.KnownApks()
388 apk = 'fake.ota.update_1234.zip' # this is not an APK, scanning should fail
389 (skip, apk, cachechanged) = fdroidserver.update.scan_apk({}, apk, 'repo', knownapks, False)
391 self.assertTrue(skip)
392 self.assertIsNone(apk)
393 self.assertFalse(cachechanged)
396 if __name__ == "__main__":
397 parser = optparse.OptionParser()
398 parser.add_option("-v", "--verbose", action="store_true", default=False,
399 help="Spew out even more information than normal")
400 (fdroidserver.common.options, args) = parser.parse_args(['--verbose'])
402 newSuite = unittest.TestSuite()
403 newSuite.addTest(unittest.makeSuite(UpdateTest))