chiark / gitweb /
Merge branch 'indexing-fixes' into 'master'
[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 sys
12 import tempfile
13 import unittest
14 import yaml
15 from binascii import unhexlify
16
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)
22
23 import fdroidserver.common
24 import fdroidserver.metadata
25 import fdroidserver.update
26 from fdroidserver.common import FDroidPopen
27
28
29 class UpdateTest(unittest.TestCase):
30     '''fdroid update'''
31
32     def testInsertStoreMetadata(self):
33         config = dict()
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'))
39
40         shutil.rmtree(os.path.join('repo', 'info.guardianproject.urzip'), ignore_errors=True)
41
42         apps = dict()
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)
49
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')))
53
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']))
77
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!')
83             return
84         tmpdir = os.path.join(localmodule, '.testfiles')
85         if not os.path.exists(tmpdir):
86             os.makedirs(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)
90
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:
94             remote.fetch()
95         repo.git.reset('--hard', 'b9e5d1a0d8d6fc31d4674b2f0514fef10762ed4f')
96         repo.git.clean('-fdx')
97
98         os.mkdir(os.path.join(tmptestsdir, 'metadata'))
99         metadata = dict()
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)
103
104         config = dict()
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)
111
112         apps = fdroidserver.metadata.read_metadata(xref=True)
113         fdroidserver.update.copy_triple_t_store_metadata(apps)
114
115         # TODO ideally, this would compare the whole dict like in metadata.TestCase's test_read_metadata()
116         correctlocales = [
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',
125         ]
126         locales = sorted(list(apps['org.fdroid.ci.test.app']['localized'].keys()))
127         self.assertEqual(correctlocales, locales)
128
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)
133             sys.exit(1)
134         # FDroidPopen needs some config to work
135         config = dict()
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)])
140         sig = None
141         for line in p.output.splitlines():
142             if line.startswith('Result:'):
143                 sig = line[7:].strip()
144                 break
145         if p.returncode == 0:
146             return sig
147         else:
148             return None
149
150     def testGoodGetsig(self):
151         # config needed to use jarsigner and keytool
152         config = dict()
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!")
164         try:
165             self.assertEqual(unhexlify(sig), unhexlify(pysig),
166                              "the length of the two sigs are different!")
167         except TypeError as e:
168             print(e)
169             self.assertTrue(False, 'TypeError!')
170
171     def testBadGetsig(self):
172         # config needed to use jarsigner and keytool
173         config = dict()
174         fdroidserver.common.fill_config_defaults(config)
175         fdroidserver.update.config = config
176         apkfile = os.path.join(os.path.dirname(__file__), 'urzip-badsig.apk')
177         sig = self.javagetsig(apkfile)
178         self.assertIsNone(sig, "sig should be None: " + str(sig))
179         pysig = fdroidserver.update.getsig(apkfile)
180         self.assertIsNone(pysig, "python sig should be None: " + str(sig))
181
182         apkfile = os.path.join(os.path.dirname(__file__), 'urzip-badcert.apk')
183         sig = self.javagetsig(apkfile)
184         self.assertIsNone(sig, "sig should be None: " + str(sig))
185         pysig = fdroidserver.update.getsig(apkfile)
186         self.assertIsNone(pysig, "python sig should be None: " + str(sig))
187
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')
192
193         config = dict()
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
199
200         fdroidserver.update.options = type('', (), {})()
201         fdroidserver.update.options.clean = True
202         fdroidserver.update.options.delete_unknown = True
203
204         apps = fdroidserver.metadata.read_metadata(xref=True)
205         knownapks = fdroidserver.common.KnownApks()
206         apks, cachechanged = fdroidserver.update.scan_apks({}, 'repo', knownapks, False)
207         self.assertEqual(len(apks), 6)
208         apk = apks[0]
209         self.assertEqual(apk['minSdkVersion'], '4')
210         self.assertEqual(apk['targetSdkVersion'], '18')
211         self.assertFalse('maxSdkVersion' in apk)
212
213         fdroidserver.update.insert_obbs('repo', apps, apks)
214         for apk in apks:
215             if apk['packageName'] == 'obb.mainpatch.current':
216                 self.assertEqual(apk.get('obbMainFile'), 'main.1619.obb.mainpatch.current.obb')
217                 self.assertEqual(apk.get('obbPatchFile'), 'patch.1619.obb.mainpatch.current.obb')
218             elif apk['packageName'] == 'obb.main.oldversion':
219                 self.assertEqual(apk.get('obbMainFile'), 'main.1434483388.obb.main.oldversion.obb')
220                 self.assertIsNone(apk.get('obbPatchFile'))
221             elif apk['packageName'] == 'obb.main.twoversions':
222                 self.assertIsNone(apk.get('obbPatchFile'))
223                 if apk['versionCode'] == 1101613:
224                     self.assertEqual(apk.get('obbMainFile'), 'main.1101613.obb.main.twoversions.obb')
225                 elif apk['versionCode'] == 1101615:
226                     self.assertEqual(apk.get('obbMainFile'), 'main.1101615.obb.main.twoversions.obb')
227                 elif apk['versionCode'] == 1101617:
228                     self.assertEqual(apk.get('obbMainFile'), 'main.1101615.obb.main.twoversions.obb')
229                 else:
230                     self.assertTrue(False)
231             elif apk['packageName'] == 'info.guardianproject.urzip':
232                 self.assertIsNone(apk.get('obbMainFile'))
233                 self.assertIsNone(apk.get('obbPatchFile'))
234
235     def testScanApkMetadata(self):
236
237         def _build_yaml_representer(dumper, data):
238             '''Creates a YAML representation of a Build instance'''
239             return dumper.represent_dict(data)
240
241         config = dict()
242         fdroidserver.common.fill_config_defaults(config)
243         fdroidserver.update.config = config
244         os.chdir(os.path.dirname(__file__))
245         if os.path.basename(os.getcwd()) != 'tests':
246             raise Exception('This test must be run in the "tests/" subdir')
247
248         config['ndk_paths'] = dict()
249         config['accepted_formats'] = ['json', 'txt', 'yml']
250         fdroidserver.common.config = config
251         fdroidserver.update.config = config
252
253         fdroidserver.update.options = type('', (), {})()
254         fdroidserver.update.options.clean = True
255         fdroidserver.update.options.rename_apks = False
256         fdroidserver.update.options.delete_unknown = True
257
258         for icon_dir in fdroidserver.update.get_all_icon_dirs('repo'):
259             if not os.path.exists(icon_dir):
260                 os.makedirs(icon_dir)
261
262         knownapks = fdroidserver.common.KnownApks()
263         apkList = ['../urzip.apk', '../org.dyndns.fules.ck_20.apk']
264
265         for apkName in apkList:
266             _, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'repo', knownapks, False)
267             # Don't care about the date added to the repo and relative apkName
268             del apk['added']
269             del apk['apkName']
270             # avoid AAPT application name bug
271             del apk['name']
272
273             savepath = os.path.join('metadata', 'apk', apk['packageName'] + '.yaml')
274             # Uncomment to save APK metadata
275             # with open(savepath, 'w') as f:
276             #     yaml.add_representer(fdroidserver.metadata.Build, _build_yaml_representer)
277             #     yaml.dump(apk, f, default_flow_style=False)
278
279             with open(savepath, 'r') as f:
280                 frompickle = yaml.load(f)
281             self.maxDiff = None
282             self.assertEqual(apk, frompickle)
283
284     def test_scan_invalid_apk(self):
285         os.chdir(os.path.join(localmodule, 'tests'))
286         if os.path.basename(os.getcwd()) != 'tests':
287             raise Exception('This test must be run in the "tests/" subdir')
288
289         config = dict()
290         fdroidserver.common.fill_config_defaults(config)
291         fdroidserver.common.config = config
292         fdroidserver.update.config = config
293         fdroidserver.update.options.delete_unknown = False
294
295         knownapks = fdroidserver.common.KnownApks()
296         apk = 'fake.ota.update_1234.zip'  # this is not an APK, scanning should fail
297         (skip, apk, cachechanged) = fdroidserver.update.scan_apk({}, apk, 'repo', knownapks, False)
298
299         self.assertTrue(skip)
300         self.assertIsNone(apk)
301         self.assertFalse(cachechanged)
302
303
304 if __name__ == "__main__":
305     parser = optparse.OptionParser()
306     parser.add_option("-v", "--verbose", action="store_true", default=False,
307                       help="Spew out even more information than normal")
308     (fdroidserver.common.options, args) = parser.parse_args(['--verbose'])
309
310     newSuite = unittest.TestSuite()
311     newSuite.addTest(unittest.makeSuite(UpdateTest))
312     unittest.main()