chiark / gitweb /
makebuildserver: change mem default 2 GB
[fdroidserver.git] / tests / update.TestCase
1 #!/usr/bin/env python3
2
3 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
4
5 import git
6 import inspect
7 import logging
8 import optparse
9 import os
10 import shutil
11 import subprocess
12 import sys
13 import tempfile
14 import unittest
15 import yaml
16 from binascii import unhexlify
17 from distutils.version import LooseVersion
18
19 localmodule = os.path.realpath(
20     os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..'))
21 print('localmodule: ' + localmodule)
22 if localmodule not in sys.path:
23     sys.path.insert(0, localmodule)
24
25 import fdroidserver.common
26 import fdroidserver.exception
27 import fdroidserver.metadata
28 import fdroidserver.update
29 from fdroidserver.common import FDroidPopen
30
31
32 class UpdateTest(unittest.TestCase):
33     '''fdroid update'''
34
35     def testInsertStoreMetadata(self):
36         config = dict()
37         fdroidserver.common.fill_config_defaults(config)
38         config['accepted_formats'] = ('txt', 'yml')
39         fdroidserver.update.config = config
40         fdroidserver.update.options = fdroidserver.common.options
41         os.chdir(os.path.join(localmodule, 'tests'))
42
43         shutil.rmtree(os.path.join('repo', 'info.guardianproject.urzip'), ignore_errors=True)
44
45         apps = dict()
46         for packageName in ('info.guardianproject.urzip', 'org.videolan.vlc', 'obb.mainpatch.current'):
47             apps[packageName] = dict()
48             apps[packageName]['id'] = packageName
49             apps[packageName]['CurrentVersionCode'] = 0xcafebeef
50         apps['info.guardianproject.urzip']['CurrentVersionCode'] = 100
51         fdroidserver.update.insert_localized_app_metadata(apps)
52
53         appdir = os.path.join('repo', 'info.guardianproject.urzip', 'en-US')
54         self.assertTrue(os.path.isfile(os.path.join(appdir, 'icon.png')))
55         self.assertTrue(os.path.isfile(os.path.join(appdir, 'featureGraphic.png')))
56
57         self.assertEqual(3, len(apps))
58         for packageName, app in apps.items():
59             self.assertTrue('localized' in app)
60             self.assertTrue('en-US' in app['localized'])
61             self.assertEqual(1, len(app['localized']))
62             if packageName == 'info.guardianproject.urzip':
63                 self.assertEqual(7, len(app['localized']['en-US']))
64                 self.assertEqual('full description\n', app['localized']['en-US']['description'])
65                 self.assertEqual('title\n', app['localized']['en-US']['name'])
66                 self.assertEqual('short description\n', app['localized']['en-US']['summary'])
67                 self.assertEqual('video\n', app['localized']['en-US']['video'])
68                 self.assertEqual('icon.png', app['localized']['en-US']['icon'])
69                 self.assertEqual('featureGraphic.png', app['localized']['en-US']['featureGraphic'])
70                 self.assertEqual('100\n', app['localized']['en-US']['whatsNew'])
71             elif packageName == 'org.videolan.vlc':
72                 self.assertEqual('icon.png', app['localized']['en-US']['icon'])
73                 self.assertEqual(9, len(app['localized']['en-US']['phoneScreenshots']))
74                 self.assertEqual(15, len(app['localized']['en-US']['sevenInchScreenshots']))
75             elif packageName == 'obb.mainpatch.current':
76                 self.assertEqual('icon.png', app['localized']['en-US']['icon'])
77                 self.assertEqual('featureGraphic.png', app['localized']['en-US']['featureGraphic'])
78                 self.assertEqual(1, len(app['localized']['en-US']['phoneScreenshots']))
79                 self.assertEqual(1, len(app['localized']['en-US']['sevenInchScreenshots']))
80
81     def test_insert_triple_t_metadata(self):
82         importer = os.path.join(localmodule, 'tests', 'tmp', 'importer')
83         packageName = 'org.fdroid.ci.test.app'
84         if not os.path.isdir(importer):
85             logging.warning('skipping test_insert_triple_t_metadata, import.TestCase must run first!')
86             return
87         tmpdir = os.path.join(localmodule, '.testfiles')
88         if not os.path.exists(tmpdir):
89             os.makedirs(tmpdir)
90         tmptestsdir = tempfile.mkdtemp(prefix='test_insert_triple_t_metadata-', dir=tmpdir)
91         packageDir = os.path.join(tmptestsdir, 'build', packageName)
92         shutil.copytree(importer, packageDir)
93
94         # always use the same commit so these tests work when ci-test-app.git is updated
95         repo = git.Repo(packageDir)
96         for remote in repo.remotes:
97             remote.fetch()
98         repo.git.reset('--hard', 'b9e5d1a0d8d6fc31d4674b2f0514fef10762ed4f')
99         repo.git.clean('-fdx')
100
101         os.mkdir(os.path.join(tmptestsdir, 'metadata'))
102         metadata = dict()
103         metadata['Description'] = 'This is just a test app'
104         with open(os.path.join(tmptestsdir, 'metadata', packageName + '.yml'), 'w') as fp:
105             yaml.dump(metadata, fp)
106
107         config = dict()
108         fdroidserver.common.fill_config_defaults(config)
109         config['accepted_formats'] = ('yml')
110         fdroidserver.common.config = config
111         fdroidserver.update.config = config
112         fdroidserver.update.options = fdroidserver.common.options
113         os.chdir(tmptestsdir)
114
115         apps = fdroidserver.metadata.read_metadata(xref=True)
116         fdroidserver.update.copy_triple_t_store_metadata(apps)
117
118         # TODO ideally, this would compare the whole dict like in metadata.TestCase's test_read_metadata()
119         correctlocales = [
120             'ar', 'ast_ES', 'az', 'ca', 'ca_ES', 'cs-CZ', 'cs_CZ', 'da',
121             'da-DK', 'de', 'de-DE', 'el', 'en-US', 'es', 'es-ES', 'es_ES', 'et',
122             'fi', 'fr', 'fr-FR', 'he_IL', 'hi-IN', 'hi_IN', 'hu', 'id', 'it',
123             'it-IT', 'it_IT', 'iw-IL', 'ja', 'ja-JP', 'kn_IN', 'ko', 'ko-KR',
124             'ko_KR', 'lt', 'nb', 'nb_NO', 'nl', 'nl-NL', 'no', 'pl', 'pl-PL',
125             'pl_PL', 'pt', 'pt-BR', 'pt-PT', 'pt_BR', 'ro', 'ro_RO', 'ru-RU',
126             'ru_RU', 'sv-SE', 'sv_SE', 'te', 'tr', 'tr-TR', 'uk', 'uk_UA', 'vi',
127             'vi_VN', 'zh-CN', 'zh_CN', 'zh_TW',
128         ]
129         locales = sorted(list(apps['org.fdroid.ci.test.app']['localized'].keys()))
130         self.assertEqual(correctlocales, locales)
131
132     def javagetsig(self, apkfile):
133         getsig_dir = os.path.join(os.path.dirname(__file__), 'getsig')
134         if not os.path.exists(getsig_dir + "/getsig.class"):
135             logging.critical("getsig.class not found. To fix: cd '%s' && ./make.sh" % getsig_dir)
136             sys.exit(1)
137         # FDroidPopen needs some config to work
138         config = dict()
139         fdroidserver.common.fill_config_defaults(config)
140         fdroidserver.common.config = config
141         p = FDroidPopen(['java', '-cp', os.path.join(os.path.dirname(__file__), 'getsig'),
142                          'getsig', os.path.join(os.getcwd(), apkfile)])
143         sig = None
144         for line in p.output.splitlines():
145             if line.startswith('Result:'):
146                 sig = line[7:].strip()
147                 break
148         if p.returncode == 0:
149             return sig
150         else:
151             return None
152
153     def testGoodGetsig(self):
154         # config needed to use jarsigner and keytool
155         config = dict()
156         fdroidserver.common.fill_config_defaults(config)
157         fdroidserver.update.config = config
158         apkfile = os.path.join(os.path.dirname(__file__), 'urzip.apk')
159         sig = self.javagetsig(apkfile)
160         self.assertIsNotNone(sig, "sig is None")
161         pysig = fdroidserver.update.getsig(apkfile)
162         self.assertIsNotNone(pysig, "pysig is None")
163         self.assertEqual(sig, fdroidserver.update.getsig(apkfile),
164                          "python sig not equal to java sig!")
165         self.assertEqual(len(sig), len(pysig),
166                          "the length of the two sigs are different!")
167         try:
168             self.assertEqual(unhexlify(sig), unhexlify(pysig),
169                              "the length of the two sigs are different!")
170         except TypeError as e:
171             print(e)
172             self.assertTrue(False, 'TypeError!')
173
174     def testBadGetsig(self):
175         """getsig() should still be able to fetch the fingerprint of bad signatures"""
176         # config needed to use jarsigner and keytool
177         config = dict()
178         fdroidserver.common.fill_config_defaults(config)
179         fdroidserver.update.config = config
180
181         apkfile = os.path.join(os.path.dirname(__file__), 'urzip-badsig.apk')
182         sig = fdroidserver.update.getsig(apkfile)
183         self.assertEqual(sig, 'e0ecb5fc2d63088e4a07ae410a127722',
184                          "python sig should be: " + str(sig))
185
186         apkfile = os.path.join(os.path.dirname(__file__), 'urzip-badcert.apk')
187         sig = fdroidserver.update.getsig(apkfile)
188         self.assertEqual(sig, 'e0ecb5fc2d63088e4a07ae410a127722',
189                          "python sig should be: " + str(sig))
190
191     def testScanApksAndObbs(self):
192         os.chdir(os.path.join(localmodule, 'tests'))
193         if os.path.basename(os.getcwd()) != 'tests':
194             raise Exception('This test must be run in the "tests/" subdir')
195
196         config = dict()
197         fdroidserver.common.fill_config_defaults(config)
198         config['ndk_paths'] = dict()
199         config['accepted_formats'] = ['json', 'txt', 'yml']
200         fdroidserver.common.config = config
201         fdroidserver.update.config = config
202
203         fdroidserver.update.options = type('', (), {})()
204         fdroidserver.update.options.clean = True
205         fdroidserver.update.options.delete_unknown = True
206         fdroidserver.update.options.rename_apks = False
207         fdroidserver.update.options.allow_disabled_algorithms = False
208
209         apps = fdroidserver.metadata.read_metadata(xref=True)
210         knownapks = fdroidserver.common.KnownApks()
211         apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
212         self.assertEqual(len(apks), 11)
213         apk = apks[0]
214         self.assertEqual(apk['packageName'], 'com.politedroid')
215         self.assertEqual(apk['versionCode'], 3)
216         self.assertEqual(apk['minSdkVersion'], '3')
217         self.assertEqual(apk['targetSdkVersion'], '3')
218         self.assertFalse('maxSdkVersion' in apk)
219         apk = apks[4]
220         self.assertEqual(apk['packageName'], 'obb.main.oldversion')
221         self.assertEqual(apk['versionCode'], 1444412523)
222         self.assertEqual(apk['minSdkVersion'], '4')
223         self.assertEqual(apk['targetSdkVersion'], '18')
224         self.assertFalse('maxSdkVersion' in apk)
225
226         fdroidserver.update.insert_obbs('repo', apps, apks)
227         for apk in apks:
228             if apk['packageName'] == 'obb.mainpatch.current':
229                 self.assertEqual(apk.get('obbMainFile'), 'main.1619.obb.mainpatch.current.obb')
230                 self.assertEqual(apk.get('obbPatchFile'), 'patch.1619.obb.mainpatch.current.obb')
231             elif apk['packageName'] == 'obb.main.oldversion':
232                 self.assertEqual(apk.get('obbMainFile'), 'main.1434483388.obb.main.oldversion.obb')
233                 self.assertIsNone(apk.get('obbPatchFile'))
234             elif apk['packageName'] == 'obb.main.twoversions':
235                 self.assertIsNone(apk.get('obbPatchFile'))
236                 if apk['versionCode'] == 1101613:
237                     self.assertEqual(apk.get('obbMainFile'), 'main.1101613.obb.main.twoversions.obb')
238                 elif apk['versionCode'] == 1101615:
239                     self.assertEqual(apk.get('obbMainFile'), 'main.1101615.obb.main.twoversions.obb')
240                 elif apk['versionCode'] == 1101617:
241                     self.assertEqual(apk.get('obbMainFile'), 'main.1101615.obb.main.twoversions.obb')
242                 else:
243                     self.assertTrue(False)
244             elif apk['packageName'] == 'info.guardianproject.urzip':
245                 self.assertIsNone(apk.get('obbMainFile'))
246                 self.assertIsNone(apk.get('obbPatchFile'))
247
248     def test_scan_apk(self):
249         config = dict()
250         fdroidserver.common.fill_config_defaults(config)
251         fdroidserver.update.config = config
252         os.chdir(os.path.join(localmodule, 'tests'))
253         if os.path.basename(os.getcwd()) != 'tests':
254             raise Exception('This test must be run in the "tests/" subdir')
255
256         apk_info = fdroidserver.update.scan_apk('org.dyndns.fules.ck_20.apk')
257
258         self.assertEqual(apk_info['icons_src'], {'240': 'res/drawable-hdpi-v4/icon_launcher.png',
259                                                  '120': 'res/drawable-ldpi-v4/icon_launcher.png',
260                                                  '160': 'res/drawable-mdpi-v4/icon_launcher.png',
261                                                  '-1': 'res/drawable-mdpi-v4/icon_launcher.png'})
262         self.assertEqual(apk_info['icons'], {})
263         self.assertEqual(apk_info['features'], [])
264         self.assertEqual(apk_info['antiFeatures'], set())
265         self.assertEqual(apk_info['versionName'], 'v1.6pre2')
266         self.assertEqual(apk_info['hash'],
267                          '897486e1f857c6c0ee32ccbad0e1b8cd82f6d0e65a44a23f13f852d2b63a18c8')
268         self.assertEqual(apk_info['packageName'], 'org.dyndns.fules.ck')
269         self.assertEqual(apk_info['versionCode'], 20)
270         self.assertEqual(apk_info['size'], 132453)
271         self.assertEqual(apk_info['nativecode'],
272                          ['arm64-v8a', 'armeabi', 'armeabi-v7a', 'mips', 'mips64', 'x86', 'x86_64'])
273         self.assertEqual(apk_info['minSdkVersion'], '7')
274         self.assertEqual(apk_info['sig'], '9bf7a6a67f95688daec75eab4b1436ac')
275         self.assertEqual(apk_info['hashType'], 'sha256')
276         self.assertEqual(apk_info['targetSdkVersion'], '8')
277
278     def test_scan_apk_no_sig(self):
279         config = dict()
280         fdroidserver.common.fill_config_defaults(config)
281         fdroidserver.update.config = config
282         os.chdir(os.path.join(localmodule, 'tests'))
283         if os.path.basename(os.getcwd()) != 'tests':
284             raise Exception('This test must be run in the "tests/" subdir')
285
286         with self.assertRaises(fdroidserver.exception.BuildException):
287             fdroidserver.update.scan_apk('urzip-release-unsigned.apk')
288
289     def test_process_apk(self):
290
291         def _build_yaml_representer(dumper, data):
292             '''Creates a YAML representation of a Build instance'''
293             return dumper.represent_dict(data)
294
295         config = dict()
296         fdroidserver.common.fill_config_defaults(config)
297         fdroidserver.update.config = config
298         os.chdir(os.path.join(localmodule, 'tests'))
299         if os.path.basename(os.getcwd()) != 'tests':
300             raise Exception('This test must be run in the "tests/" subdir')
301
302         config['ndk_paths'] = dict()
303         config['accepted_formats'] = ['json', 'txt', 'yml']
304         fdroidserver.common.config = config
305         fdroidserver.update.config = config
306
307         fdroidserver.update.options = type('', (), {})()
308         fdroidserver.update.options.clean = True
309         fdroidserver.update.options.rename_apks = False
310         fdroidserver.update.options.delete_unknown = True
311         fdroidserver.update.options.allow_disabled_algorithms = False
312
313         for icon_dir in fdroidserver.update.get_all_icon_dirs('repo'):
314             if not os.path.exists(icon_dir):
315                 os.makedirs(icon_dir)
316
317         knownapks = fdroidserver.common.KnownApks()
318         apkList = ['../urzip.apk', '../org.dyndns.fules.ck_20.apk']
319
320         for apkName in apkList:
321             _, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo', knownapks,
322                                                                    False)
323             # Don't care about the date added to the repo and relative apkName
324             del apk['added']
325             del apk['apkName']
326             # avoid AAPT application name bug
327             del apk['name']
328
329             # ensure that icons have been extracted properly
330             if apkName == '../urzip.apk':
331                 self.assertEqual(apk['icon'], 'info.guardianproject.urzip.100.png')
332             if apkName == '../org.dyndns.fules.ck_20.apk':
333                 self.assertEqual(apk['icon'], 'org.dyndns.fules.ck.20.png')
334             for density in fdroidserver.update.screen_densities:
335                 icon_path = os.path.join(fdroidserver.update.get_icon_dir('repo', density),
336                                          apk['icon'])
337                 self.assertTrue(os.path.isfile(icon_path))
338                 self.assertTrue(os.path.getsize(icon_path) > 1)
339
340             savepath = os.path.join('metadata', 'apk', apk['packageName'] + '.yaml')
341             # Uncomment to save APK metadata
342             # with open(savepath, 'w') as f:
343             #     yaml.add_representer(fdroidserver.metadata.Build, _build_yaml_representer)
344             #     yaml.dump(apk, f, default_flow_style=False)
345
346             with open(savepath, 'r') as f:
347                 frompickle = yaml.load(f)
348             self.maxDiff = None
349             self.assertEqual(apk, frompickle)
350
351     def test_process_apk_signed_by_disabled_algorithms(self):
352         os.chdir(os.path.join(localmodule, 'tests'))
353         if os.path.basename(os.getcwd()) != 'tests':
354             raise Exception('This test must be run in the "tests/" subdir')
355
356         config = dict()
357         fdroidserver.common.fill_config_defaults(config)
358         fdroidserver.update.config = config
359
360         config['ndk_paths'] = dict()
361         config['accepted_formats'] = ['json', 'txt', 'yml']
362         fdroidserver.common.config = config
363         fdroidserver.update.config = config
364
365         fdroidserver.update.options = type('', (), {})()
366         fdroidserver.update.options.clean = True
367         fdroidserver.update.options.verbose = True
368         fdroidserver.update.options.rename_apks = False
369         fdroidserver.update.options.delete_unknown = True
370         fdroidserver.update.options.allow_disabled_algorithms = False
371
372         knownapks = fdroidserver.common.KnownApks()
373         apksourcedir = os.getcwd()
374         tmpdir = os.path.join(localmodule, '.testfiles')
375         if not os.path.exists(tmpdir):
376             os.makedirs(tmpdir)
377         tmptestsdir = tempfile.mkdtemp(prefix='test_process_apk_signed_by_disabled_algorithms-',
378                                        dir=tmpdir)
379         print('tmptestsdir', tmptestsdir)
380         os.chdir(tmptestsdir)
381         os.mkdir('repo')
382         os.mkdir('archive')
383         # setup the repo, create icons dirs, etc.
384         fdroidserver.update.process_apks({}, 'repo', knownapks)
385         fdroidserver.update.process_apks({}, 'archive', knownapks)
386
387         disabledsigs = ['org.bitbucket.tickytacky.mirrormirror_2.apk', ]
388         for apkName in disabledsigs:
389             shutil.copy(os.path.join(apksourcedir, apkName),
390                         os.path.join(tmptestsdir, 'repo'))
391
392             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
393                                                                       knownapks,
394                                                                       allow_disabled_algorithms=True,
395                                                                       archive_bad_sig=False)
396             self.assertFalse(skip)
397             self.assertIsNotNone(apk)
398             self.assertTrue(cachechanged)
399             self.assertFalse(os.path.exists(os.path.join('archive', apkName)))
400             self.assertTrue(os.path.exists(os.path.join('repo', apkName)))
401
402             javac = config['jarsigner'].replace('jarsigner', 'javac')
403             v = subprocess.check_output([javac, '-version'], stderr=subprocess.STDOUT)[6:-1].decode('utf-8')
404             if LooseVersion(v) < LooseVersion('1.8.0_132'):
405                 print('SKIPPING: running tests with old Java (' + v + ')')
406                 return
407
408             # this test only works on systems with fully updated Java/jarsigner
409             # that has MD5 listed in jdk.jar.disabledAlgorithms in java.security
410             # https://blogs.oracle.com/java-platform-group/oracle-jre-will-no-longer-trust-md5-signed-code-by-default
411             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
412                                                                       knownapks,
413                                                                       allow_disabled_algorithms=False,
414                                                                       archive_bad_sig=True)
415             self.assertTrue(skip)
416             self.assertIsNone(apk)
417             self.assertFalse(cachechanged)
418             self.assertTrue(os.path.exists(os.path.join('archive', apkName)))
419             self.assertFalse(os.path.exists(os.path.join('repo', apkName)))
420
421             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'archive',
422                                                                       knownapks,
423                                                                       allow_disabled_algorithms=False,
424                                                                       archive_bad_sig=False)
425             self.assertFalse(skip)
426             self.assertIsNotNone(apk)
427             self.assertTrue(cachechanged)
428             self.assertTrue(os.path.exists(os.path.join('archive', apkName)))
429             self.assertFalse(os.path.exists(os.path.join('repo', apkName)))
430
431             # ensure that icons have been moved to the archive as well
432             for density in fdroidserver.update.screen_densities:
433                 icon_path = os.path.join(fdroidserver.update.get_icon_dir('archive', density),
434                                          apk['icon'])
435                 self.assertTrue(os.path.isfile(icon_path))
436                 self.assertTrue(os.path.getsize(icon_path) > 1)
437
438         badsigs = ['urzip-badcert.apk', 'urzip-badsig.apk', 'urzip-release-unsigned.apk', ]
439         for apkName in badsigs:
440             shutil.copy(os.path.join(apksourcedir, apkName),
441                         os.path.join(tmptestsdir, 'repo'))
442
443             skip, apk, cachechanged = fdroidserver.update.process_apk({}, apkName, 'repo',
444                                                                       knownapks,
445                                                                       allow_disabled_algorithms=False,
446                                                                       archive_bad_sig=False)
447             self.assertTrue(skip)
448             self.assertIsNone(apk)
449             self.assertFalse(cachechanged)
450
451     def test_process_invalid_apk(self):
452         os.chdir(os.path.join(localmodule, 'tests'))
453         if os.path.basename(os.getcwd()) != 'tests':
454             raise Exception('This test must be run in the "tests/" subdir')
455
456         config = dict()
457         fdroidserver.common.fill_config_defaults(config)
458         fdroidserver.common.config = config
459         fdroidserver.update.config = config
460         fdroidserver.update.options.delete_unknown = False
461
462         knownapks = fdroidserver.common.KnownApks()
463         apk = 'fake.ota.update_1234.zip'  # this is not an APK, scanning should fail
464         (skip, apk, cachechanged) = fdroidserver.update.process_apk({}, apk, 'repo', knownapks,
465                                                                     False)
466
467         self.assertTrue(skip)
468         self.assertIsNone(apk)
469         self.assertFalse(cachechanged)
470
471     def test_translate_per_build_anti_features(self):
472         os.chdir(os.path.join(localmodule, 'tests'))
473         if os.path.basename(os.getcwd()) != 'tests':
474             raise Exception('This test must be run in the "tests/" subdir')
475
476         config = dict()
477         fdroidserver.common.fill_config_defaults(config)
478         config['ndk_paths'] = dict()
479         config['accepted_formats'] = ['json', 'txt', 'yml']
480         fdroidserver.common.config = config
481         fdroidserver.update.config = config
482
483         fdroidserver.update.options = type('', (), {})()
484         fdroidserver.update.options.clean = True
485         fdroidserver.update.options.delete_unknown = True
486         fdroidserver.update.options.rename_apks = False
487         fdroidserver.update.options.allow_disabled_algorithms = False
488
489         apps = fdroidserver.metadata.read_metadata(xref=True)
490         knownapks = fdroidserver.common.KnownApks()
491         apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
492         fdroidserver.update.translate_per_build_anti_features(apps, apks)
493         self.assertEqual(len(apks), 11)
494         foundtest = False
495         for apk in apks:
496             if apk['packageName'] == 'com.politedroid' and apk['versionCode'] == 3:
497                 antiFeatures = apk.get('antiFeatures')
498                 self.assertTrue('KnownVuln' in antiFeatures)
499                 self.assertEqual(3, len(antiFeatures))
500                 foundtest = True
501         self.assertTrue(foundtest)
502
503     def test_create_metadata_from_template(self):
504         tmpdir = os.path.join(localmodule, '.testfiles')
505         if not os.path.exists(tmpdir):
506             os.makedirs(tmpdir)
507         tmptestsdir = tempfile.mkdtemp(prefix='test_create_metadata_from_template-',
508                                        dir=tmpdir)
509         print('tmptestsdir', tmptestsdir)
510         os.chdir(tmptestsdir)
511         os.mkdir('repo')
512         os.mkdir('metadata')
513         shutil.copy(os.path.join(localmodule, 'tests', 'urzip.apk'), 'repo')
514
515         config = dict()
516         fdroidserver.common.fill_config_defaults(config)
517         config['ndk_paths'] = dict()
518         config['accepted_formats'] = ['json', 'txt', 'yml']
519         fdroidserver.common.config = config
520         fdroidserver.update.config = config
521
522         fdroidserver.update.options = type('', (), {})()
523         fdroidserver.update.options.clean = True
524         fdroidserver.update.options.delete_unknown = False
525         fdroidserver.update.options.rename_apks = False
526         fdroidserver.update.options.allow_disabled_algorithms = False
527
528         knownapks = fdroidserver.common.KnownApks()
529         apks, cachechanged = fdroidserver.update.process_apks({}, 'repo', knownapks, False)
530         self.assertEqual(1, len(apks))
531         apk = apks[0]
532
533         testfile = 'metadata/info.guardianproject.urzip.yml'
534         # create empty 0 byte .yml file, run read_metadata, it should work
535         open(testfile, 'a').close()
536         apps = fdroidserver.metadata.read_metadata(xref=True)
537         self.assertEqual(1, len(apps))
538         os.remove(testfile)
539
540         # test using internal template
541         apps = fdroidserver.metadata.read_metadata(xref=True)
542         self.assertEqual(0, len(apps))
543         fdroidserver.update.create_metadata_from_template(apk)
544         self.assertTrue(os.path.exists(testfile))
545         apps = fdroidserver.metadata.read_metadata(xref=True)
546         self.assertEqual(1, len(apps))
547         for app in apps.values():
548             self.assertEqual('urzip', app['Name'])
549             self.assertEqual(1, len(app['Categories']))
550             break
551
552         # test using external template.yml
553         os.remove(testfile)
554         self.assertFalse(os.path.exists(testfile))
555         shutil.copy(os.path.join(localmodule, 'examples', 'template.yml'), tmptestsdir)
556         fdroidserver.update.create_metadata_from_template(apk)
557         self.assertTrue(os.path.exists(testfile))
558         apps = fdroidserver.metadata.read_metadata(xref=True)
559         self.assertEqual(1, len(apps))
560         for app in apps.values():
561             self.assertEqual('urzip', app['Name'])
562             self.assertEqual(1, len(app['Categories']))
563             self.assertEqual('Internet', app['Categories'][0])
564             break
565         with open(testfile) as fp:
566             data = yaml.load(fp)
567         self.assertEqual('urzip', data['Name'])
568         self.assertEqual('urzip', data['Summary'])
569
570
571 if __name__ == "__main__":
572     parser = optparse.OptionParser()
573     parser.add_option("-v", "--verbose", action="store_true", default=False,
574                       help="Spew out even more information than normal")
575     (fdroidserver.common.options, args) = parser.parse_args(['--verbose'])
576
577     newSuite = unittest.TestSuite()
578     newSuite.addTest(unittest.makeSuite(UpdateTest))
579     unittest.main()