1
0
mirror of https://gitlab.com/fdroid/fdroidserver.git synced 2024-11-04 14:30:11 +01:00
fdroidserver/tests/common.TestCase

2040 lines
92 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 difflib
import git
import glob
import inspect
import json
import logging
import optparse
import os
import re
import shutil
import subprocess
import sys
import tempfile
import time
import unittest
import textwrap
import yaml
import gzip
from zipfile import ZipFile
from unittest import mock
from pathlib import Path
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.index
import fdroidserver.signindex
import fdroidserver.common
import fdroidserver.metadata
from testcommon import TmpCwd
from fdroidserver.exception import FDroidException, VCSException, MetaDataException
class CommonTest(unittest.TestCase):
'''fdroidserver/common.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')
self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles'))
if not os.path.exists(self.tmpdir):
os.makedirs(self.tmpdir)
os.chdir(self.basedir)
fdroidserver.common.config = None
self.path = os.environ['PATH']
self.android_home = os.environ.get('ANDROID_HOME')
def tearDown(self):
os.environ['PATH'] = self.path
if self.android_home:
os.environ['ANDROID_HOME'] = self.android_home
def test_parse_human_readable_size(self):
for k, v in (
(9827, 9827),
(123.456, 123),
('123b', 123),
('1.2', 1),
('10.43 KiB', 10680),
('11GB', 11000000000),
('59kb', 59000),
('343.1 mb', 343100000),
('99.9GiB', 107266808217),
):
self.assertEqual(fdroidserver.common.parse_human_readable_size(k), v)
for v in ((12, 123), '0xfff', [], None, '12,123', '123GG', '982374bb', self):
with self.assertRaises(ValueError):
fdroidserver.common.parse_human_readable_size(v)
def test_assert_config_keystore(self):
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
with self.assertRaises(FDroidException):
fdroidserver.common.assert_config_keystore({})
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
c = {
'repo_keyalias': 'localhost',
'keystore': 'keystore.jks',
'keystorepass': '12345',
'keypass': '12345',
}
with open('keystore.jks', 'w'):
pass
fdroidserver.common.assert_config_keystore(c)
def _set_build_tools(self):
build_tools = os.path.join(
fdroidserver.common.config['sdk_path'], 'build-tools'
)
if os.path.exists(build_tools):
for f in sorted(os.listdir(build_tools), reverse=True):
versioned = os.path.join(build_tools, f)
if os.path.isdir(versioned) and os.path.isfile(
os.path.join(versioned, 'apksigner')
):
break
return True
else:
print('no build-tools found: ' + build_tools)
return False
def _find_all(self):
tools = ['aapt', 'adb', 'zipalign']
if os.path.exists(os.path.join(os.getenv('ANDROID_HOME'), 'tools', 'android')):
tools.append('android')
for cmd in tools:
try:
path = fdroidserver.common.find_sdk_tools_cmd(cmd)
self.assertTrue(os.path.exists(path))
self.assertTrue(os.path.isfile(path))
except fdroidserver.exception.FDroidException:
pass
def test_find_sdk_tools_cmd(self):
fdroidserver.common.config = dict()
# TODO add this once everything works without sdk_path set in config
# self._find_all()
sdk_path = os.getenv('ANDROID_HOME')
if os.path.exists(sdk_path):
fdroidserver.common.config['sdk_path'] = sdk_path
build_tools = os.path.join(sdk_path, 'build-tools')
if self._set_build_tools() or os.path.exists('/usr/bin/aapt'):
self._find_all()
else:
print('no build-tools found: ' + build_tools)
def test_find_java_root_path(self):
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
all_pathlists = [
(
[ # Debian
'/usr/lib/jvm/java-1.5.0-gcj-5-amd64',
'/usr/lib/jvm/java-8-openjdk-amd64',
'/usr/lib/jvm/java-1.8.0-openjdk-amd64',
],
'/usr/lib/jvm/java-8-openjdk-amd64',
),
(
[ # OSX
'/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk',
'/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk',
'/System/Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk',
],
'/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk',
),
]
for pathlist, choice in all_pathlists:
# strip leading / to make relative paths to test without root
pathlist = [p[1:] for p in pathlist]
# create test file used in common._add_java_paths_to_config()
for p in pathlist:
if p.startswith('/System') or p.startswith('/Library'):
basedir = os.path.join(p, 'Contents', 'Home', 'bin')
else:
basedir = os.path.join(p, 'bin')
os.makedirs(basedir)
open(os.path.join(basedir, 'javac'), 'w').close()
config = dict()
config['java_paths'] = dict()
fdroidserver.common._add_java_paths_to_config(pathlist, config)
self.assertEqual(config['java_paths']['8'], choice[1:])
def test_is_apk_and_debuggable(self):
config = dict()
fdroidserver.common.fill_config_defaults(config)
fdroidserver.common.config = config
# these are set debuggable
testfiles = []
testfiles.append(os.path.join(self.basedir, 'urzip.apk'))
testfiles.append(os.path.join(self.basedir, 'urzip-badsig.apk'))
testfiles.append(os.path.join(self.basedir, 'urzip-badcert.apk'))
for apkfile in testfiles:
self.assertTrue(
fdroidserver.common.is_apk_and_debuggable(apkfile),
"debuggable APK state was not properly parsed!",
)
# these are set NOT debuggable
testfiles = []
testfiles.append(os.path.join(self.basedir, 'urzip-release.apk'))
testfiles.append(os.path.join(self.basedir, 'urzip-release-unsigned.apk'))
testfiles.append(os.path.join(self.basedir, 'v2.only.sig_2.apk'))
for apkfile in testfiles:
self.assertFalse(
fdroidserver.common.is_apk_and_debuggable(apkfile),
"debuggable APK state was not properly parsed!",
)
VALID_STRICT_PACKAGE_NAMES = [
"An.stop",
"SpeedoMeterApp.main",
"a2dp.Vol",
"au.com.darkside.XServer",
"click.dummer.UartSmartwatch",
"com.Bisha.TI89EmuDonation",
"com.MarcosDiez.shareviahttp",
"com.Pau.ImapNotes2",
"com.app.Zensuren",
"com.darshancomputing.BatteryIndicator",
"com.geecko.QuickLyric",
"com.genonbeta.TrebleShot",
"com.gpl.rpg.AndorsTrail",
"com.hobbyone.HashDroid",
"com.moez.QKSMS",
"com.platypus.SAnd",
"com.prhlt.aemus.Read4SpeechExperiments",
"de.syss.MifareClassicTool",
"org.fdroid.fdroid",
"org.f_droid.fdr0ID",
]
def test_is_valid_package_name(self):
for name in self.VALID_STRICT_PACKAGE_NAMES + [
"_SpeedoMeterApp.main",
"05041684efd9b16c2888b1eddbadd0359f655f311b89bdd1737f560a10d20fb8",
]:
self.assertTrue(
fdroidserver.common.is_valid_package_name(name),
"{0} should be a valid package name".format(name),
)
for name in [
"0rg.fdroid.fdroid",
".f_droid.fdr0ID",
"trailingdot.",
"org.fdroid/fdroid",
"/org.fdroid.fdroid",
]:
self.assertFalse(
fdroidserver.common.is_valid_package_name(name),
"{0} should not be a valid package name".format(name),
)
def test_is_strict_application_id(self):
"""see also tests/valid-package-names/"""
for name in self.VALID_STRICT_PACKAGE_NAMES:
self.assertTrue(
fdroidserver.common.is_strict_application_id(name),
"{0} should be a strict application id".format(name),
)
for name in [
"0rg.fdroid.fdroid",
".f_droid.fdr0ID",
"oneword",
"trailingdot.",
"cafebabe",
"org.fdroid/fdroid",
"/org.fdroid.fdroid",
"_SpeedoMeterApp.main",
"05041684efd9b16c2888b1eddbadd0359f655f311b89bdd1737f560a10d20fb8",
]:
self.assertFalse(
fdroidserver.common.is_strict_application_id(name),
"{0} should not be a strict application id".format(name),
)
def test_prepare_sources(self):
testint = 99999999
teststr = 'FAKE_STR_FOR_TESTING'
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
shutil.copytree(
os.path.join(self.basedir, 'source-files'),
os.path.join(testdir, 'source-files'),
)
fdroidclient_testdir = os.path.join(
testdir, 'source-files', 'fdroid', 'fdroidclient'
)
config = dict()
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 = 'org.fdroid.froid'
build = fdroidserver.metadata.Build()
build.commit = 'master'
build.forceversion = True
build.forcevercode = True
build.gradle = ['yes']
build.target = 'android-' + str(testint)
build.versionName = teststr
build.versionCode = testint
class FakeVcs:
# no need to change to the correct commit here
def gotorevision(self, rev, refresh=True):
pass
# no srclib info needed, but it could be added...
def getsrclib(self):
return None
fdroidserver.common.prepare_source(FakeVcs(), app, build,
fdroidclient_testdir, fdroidclient_testdir, fdroidclient_testdir)
with open(os.path.join(fdroidclient_testdir, 'build.gradle'), 'r') as f:
filedata = f.read()
self.assertIsNotNone(
re.search(r"\s+compileSdkVersion %s\s+" % testint, filedata)
)
with open(os.path.join(fdroidclient_testdir, 'AndroidManifest.xml')) as f:
filedata = f.read()
self.assertIsNone(re.search('android:debuggable', filedata))
self.assertIsNotNone(
re.search('android:versionName="%s"' % build.versionName, filedata)
)
self.assertIsNotNone(
re.search('android:versionCode="%s"' % build.versionCode, filedata)
)
def test_prepare_sources_with_prebuild_subdir(self):
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
app_build_dir = os.path.join(testdir, 'build', 'com.example')
shutil.copytree(
os.path.join(self.basedir, 'source-files', 'fdroid', 'fdroidclient'),
app_build_dir,
)
subdir = 'baz/bar'
subdir_path = os.path.join(app_build_dir, subdir)
os.makedirs(subdir_path)
with open(os.path.join(subdir_path, 'build.gradle'), 'w') as fp:
fp.write('// just a test placeholder')
config = dict()
fdroidserver.common.fill_config_defaults(config)
fdroidserver.common.config = config
srclibname = 'FakeSrcLib'
srclib_testdir = os.path.join(testdir, 'build', 'srclib')
os.makedirs(os.path.join(srclib_testdir, srclibname, 'testdirshouldexist'))
fdroidserver.metadata.srclibs = {
srclibname: {
'RepoType': 'git',
'Repo': 'https://example.com/foo/fakesrclib',
'Subdir': None,
'Prepare': None,
}
}
app = fdroidserver.metadata.App()
app.id = 'app.has.srclibs'
build = fdroidserver.metadata.Build()
build.commit = 'master'
build.gradle = ['yes']
build.prebuild = 'test -d $$FakeSrcLib$$/testdirshouldexist' # actual test condition
build.srclibs = [srclibname + '@1.2.3']
build.subdir = subdir
build.versionCode = 0xCAFE
build.versionName = 'vCAFE'
class FakeVcs:
# no need to change to the correct commit here
def gotorevision(self, rev, refresh=True):
pass
# no srclib info needed, but it could be added...
def getsrclib(self):
return None
fdroidserver.common.prepare_source(FakeVcs(), app, build,
app_build_dir, srclib_testdir, app_build_dir,
onserver=True, refresh=False) # do not clone in this test
def test_prepare_sources_refresh(self):
packageName = 'org.fdroid.ci.test.app'
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
print('testdir', testdir)
os.chdir(testdir)
os.mkdir('build')
os.mkdir('metadata')
# use a local copy if available to avoid hitting the network
tmprepo = os.path.join(self.basedir, 'tmp', 'importer')
if os.path.exists(tmprepo):
git_url = tmprepo
else:
git_url = 'https://gitlab.com/fdroid/ci-test-app.git'
metadata = dict()
metadata['Description'] = 'This is just a test app'
metadata['RepoType'] = 'git'
metadata['Repo'] = git_url
with open(os.path.join('metadata', packageName + '.yml'), 'w') as fp:
yaml.dump(metadata, fp)
gitrepo = os.path.join(testdir, 'build', packageName)
vcs0 = fdroidserver.common.getvcs('git', git_url, gitrepo)
vcs0.gotorevision('0.3', refresh=True)
vcs1 = fdroidserver.common.getvcs('git', git_url, gitrepo)
vcs1.gotorevision('0.3', refresh=False)
def test_fdroid_popen_stderr_redirect(self):
config = dict()
fdroidserver.common.fill_config_defaults(config)
fdroidserver.common.config = config
commands = ['sh', '-c', 'echo stdout message && echo stderr message 1>&2']
p = fdroidserver.common.FDroidPopen(commands)
self.assertEqual(p.output, 'stdout message\nstderr message\n')
p = fdroidserver.common.FDroidPopen(commands, stderr_to_stdout=False)
self.assertEqual(p.output, 'stdout message\n')
def test_signjar(self):
config = fdroidserver.common.read_config(fdroidserver.common.options)
config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
fdroidserver.common.config = config
fdroidserver.signindex.config = config
sourcedir = os.path.join(self.basedir, 'signindex')
testsdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
for f in ('testy.jar', 'guardianproject.jar'):
sourcefile = os.path.join(sourcedir, f)
testfile = os.path.join(testsdir, f)
shutil.copy(sourcefile, testsdir)
fdroidserver.signindex.sign_jar(testfile)
# these should be resigned, and therefore different
self.assertNotEqual(
open(sourcefile, 'rb').read(), open(testfile, 'rb').read()
)
def test_verify_apk_signature(self):
config = fdroidserver.common.read_config(fdroidserver.common.options)
fdroidserver.common.config = config
self.assertTrue(fdroidserver.common.verify_apk_signature('bad-unicode-πÇÇ现代通用字-български-عربي1.apk'))
if 'apksigner' in fdroidserver.common.config: # apksigner considers MD5 signatures valid
self.assertTrue(fdroidserver.common.verify_apk_signature('org.bitbucket.tickytacky.mirrormirror_1.apk'))
self.assertTrue(fdroidserver.common.verify_apk_signature('org.bitbucket.tickytacky.mirrormirror_2.apk'))
self.assertTrue(fdroidserver.common.verify_apk_signature('org.bitbucket.tickytacky.mirrormirror_3.apk'))
self.assertTrue(fdroidserver.common.verify_apk_signature('org.bitbucket.tickytacky.mirrormirror_4.apk'))
else:
self.assertFalse(fdroidserver.common.verify_apk_signature('org.bitbucket.tickytacky.mirrormirror_1.apk'))
self.assertFalse(fdroidserver.common.verify_apk_signature('org.bitbucket.tickytacky.mirrormirror_2.apk'))
self.assertFalse(fdroidserver.common.verify_apk_signature('org.bitbucket.tickytacky.mirrormirror_3.apk'))
self.assertFalse(fdroidserver.common.verify_apk_signature('org.bitbucket.tickytacky.mirrormirror_4.apk'))
self.assertTrue(fdroidserver.common.verify_apk_signature('org.dyndns.fules.ck_20.apk'))
self.assertTrue(fdroidserver.common.verify_apk_signature('urzip.apk'))
self.assertFalse(fdroidserver.common.verify_apk_signature('urzip-badcert.apk'))
self.assertFalse(fdroidserver.common.verify_apk_signature('urzip-badsig.apk'))
self.assertTrue(fdroidserver.common.verify_apk_signature('urzip-release.apk'))
self.assertFalse(fdroidserver.common.verify_apk_signature('urzip-release-unsigned.apk'))
def test_verify_old_apk_signature(self):
config = fdroidserver.common.read_config(fdroidserver.common.options)
config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
fdroidserver.common.config = config
self.assertTrue(fdroidserver.common.verify_old_apk_signature('bad-unicode-πÇÇ现代通用字-български-عربي1.apk'))
self.assertTrue(fdroidserver.common.verify_old_apk_signature('org.bitbucket.tickytacky.mirrormirror_1.apk'))
self.assertTrue(fdroidserver.common.verify_old_apk_signature('org.bitbucket.tickytacky.mirrormirror_2.apk'))
self.assertTrue(fdroidserver.common.verify_old_apk_signature('org.bitbucket.tickytacky.mirrormirror_3.apk'))
self.assertTrue(fdroidserver.common.verify_old_apk_signature('org.bitbucket.tickytacky.mirrormirror_4.apk'))
self.assertTrue(fdroidserver.common.verify_old_apk_signature('org.dyndns.fules.ck_20.apk'))
self.assertTrue(fdroidserver.common.verify_old_apk_signature('urzip.apk'))
self.assertFalse(fdroidserver.common.verify_old_apk_signature('urzip-badcert.apk'))
self.assertFalse(fdroidserver.common.verify_old_apk_signature('urzip-badsig.apk'))
self.assertTrue(fdroidserver.common.verify_old_apk_signature('urzip-release.apk'))
self.assertFalse(fdroidserver.common.verify_old_apk_signature('urzip-release-unsigned.apk'))
def test_verify_jar_signature_succeeds(self):
config = fdroidserver.common.read_config(fdroidserver.common.options)
fdroidserver.common.config = config
source_dir = os.path.join(self.basedir, 'signindex')
for f in ('testy.jar', 'guardianproject.jar'):
testfile = os.path.join(source_dir, f)
fdroidserver.common.verify_jar_signature(testfile)
def test_verify_jar_signature_fails(self):
config = fdroidserver.common.read_config(fdroidserver.common.options)
config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
fdroidserver.common.config = config
source_dir = os.path.join(self.basedir, 'signindex')
testfile = os.path.join(source_dir, 'unsigned.jar')
with self.assertRaises(fdroidserver.index.VerificationException):
fdroidserver.common.verify_jar_signature(testfile)
def test_verify_apks(self):
config = fdroidserver.common.read_config(fdroidserver.common.options)
fdroidserver.common.config = config
sourceapk = os.path.join(self.basedir, 'urzip.apk')
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
print('testdir', testdir)
copyapk = os.path.join(testdir, 'urzip-copy.apk')
shutil.copy(sourceapk, copyapk)
self.assertTrue(fdroidserver.common.verify_apk_signature(copyapk))
self.assertIsNone(
fdroidserver.common.verify_apks(sourceapk, copyapk, self.tmpdir)
)
unsignedapk = os.path.join(testdir, 'urzip-unsigned.apk')
with ZipFile(sourceapk, 'r') as apk:
with ZipFile(unsignedapk, 'w') as testapk:
for info in apk.infolist():
if not info.filename.startswith('META-INF/'):
testapk.writestr(info, apk.read(info.filename))
self.assertIsNone(
fdroidserver.common.verify_apks(sourceapk, unsignedapk, self.tmpdir)
)
twosigapk = os.path.join(testdir, 'urzip-twosig.apk')
otherapk = ZipFile(os.path.join(self.basedir, 'urzip-release.apk'), 'r')
with ZipFile(sourceapk, 'r') as apk:
with ZipFile(twosigapk, 'w') as testapk:
for info in apk.infolist():
testapk.writestr(info, apk.read(info.filename))
if info.filename.startswith('META-INF/'):
testapk.writestr(info.filename, otherapk.read(info.filename))
otherapk.close()
self.assertFalse(fdroidserver.common.verify_apk_signature(twosigapk))
self.assertIsNone(fdroidserver.common.verify_apks(sourceapk, twosigapk, self.tmpdir))
def test_write_to_config(self):
with tempfile.TemporaryDirectory() as tmpPath:
cfgPath = os.path.join(tmpPath, 'config.py')
with open(cfgPath, 'w') as f:
f.write(
textwrap.dedent(
"""\
# abc
# test = 'example value'
default_me= '%%%'
# comment
do_not_touch = "good value"
default_me="!!!"
key="123" # inline"""
)
)
cfg = {'key': '111', 'default_me_orig': 'orig'}
fdroidserver.common.write_to_config(cfg, 'key', config_file=cfgPath)
fdroidserver.common.write_to_config(cfg, 'default_me', config_file=cfgPath)
fdroidserver.common.write_to_config(cfg, 'test', value='test value', config_file=cfgPath)
fdroidserver.common.write_to_config(cfg, 'new_key', value='new', config_file=cfgPath)
with open(cfgPath, 'r') as f:
self.assertEqual(
f.read(),
textwrap.dedent(
"""\
# abc
test = 'test value'
default_me = 'orig'
# comment
do_not_touch = "good value"
key = "111" # inline
new_key = "new"
"""
),
)
def test_write_to_config_when_empty(self):
with tempfile.TemporaryDirectory() as tmpPath:
cfgPath = os.path.join(tmpPath, 'config.py')
with open(cfgPath, 'w') as f:
pass
fdroidserver.common.write_to_config({}, 'key', 'val', cfgPath)
with open(cfgPath, 'r') as f:
self.assertEqual(
f.read(),
textwrap.dedent(
"""\
key = "val"
"""
),
)
def test_apk_name_regex(self):
good = [
'urzipπÇÇπÇÇ现代汉语通用字българскиعربي1234ö_-123456.apk',
'urzipπÇÇπÇÇ现代汉语通用字българскиعربي1234ö_123456_abcdef0.apk',
'urzip_-123456.apk',
'a0_0.apk',
'Z0_0.apk',
'a0_0_abcdef0.apk',
'a_a_a_a_0_abcdef0.apk',
'a_____0.apk',
'a_____123456_abcdef0.apk',
'org.fdroid.fdroid_123456.apk',
# valid, but "_99999" is part of packageName rather than versionCode
'org.fdroid.fdroid_99999_123456.apk',
# should be valid, but I can't figure out the regex since \w includes digits
# 'πÇÇπÇÇ现代汉语通用字българскиعربي1234ö_0_123bafd.apk',
]
for name in good:
m = fdroidserver.common.APK_NAME_REGEX.match(name)
self.assertIsNotNone(m)
self.assertIn(m.group(2), ('-123456', '0', '123456'))
self.assertIn(m.group(3), ('abcdef0', None))
bad = [
'urzipπÇÇπÇÇ现代汉语通用字българскиعربي1234ö_123456_abcdefg.apk',
'urzip-_-198274.apk',
'urzip-_0_123bafd.apk',
'no spaces allowed_123.apk',
'0_0.apk',
'0_0_abcdef0.apk',
]
for name in bad:
self.assertIsNone(fdroidserver.common.APK_NAME_REGEX.match(name))
def test_standard_file_name_regex(self):
good = [
'urzipπÇÇπÇÇ现代汉语通用字българскиعربي1234ö_-123456.mp3',
'urzipπÇÇπÇÇ现代汉语通用字българскиعربي1234ö_123456.mov',
'Document_-123456.pdf',
'WTF_0.MOV',
'Z0_0.ebk',
'a_a_a_a_0.txt',
'org.fdroid.fdroid.privileged.ota_123456.zip',
'πÇÇπÇÇ现代汉语通用字българскиعربي1234ö_0.jpeg',
'a_____0.PNG',
# valid, but "_99999" is part of packageName rather than versionCode
'a_____99999_123456.zip',
'org.fdroid.fdroid_99999_123456.zip',
]
for name in good:
m = fdroidserver.common.STANDARD_FILE_NAME_REGEX.match(name)
self.assertIsNotNone(m)
self.assertIn(m.group(2), ('-123456', '0', '123456'))
bad = [
'urzipπÇÇπÇÇ现代汉语通用字българскиعربي1234ö_abcdefg.JPEG',
'urzip-_-198274.zip',
'urzip-_123bafd.pdf',
'no spaces allowed_123.foobar',
'a_____0.',
]
for name in bad:
self.assertIsNone(fdroidserver.common.STANDARD_FILE_NAME_REGEX.match(name))
def test_apk_signer_fingerprint(self):
# fingerprints fetched with: keytool -printcert -file ____.RSA
testapks = (('repo/obb.main.oldversion_1444412523.apk',
'818e469465f96b704e27be2fee4c63ab9f83ddf30e7a34c7371a4728d83b0bc1'),
('repo/obb.main.twoversions_1101613.apk',
'32a23624c201b949f085996ba5ed53d40f703aca4989476949cae891022e0ed6'),
('repo/obb.main.twoversions_1101617.apk',
'32a23624c201b949f085996ba5ed53d40f703aca4989476949cae891022e0ed6'))
for apkfile, keytoolcertfingerprint in testapks:
self.assertEqual(keytoolcertfingerprint,
fdroidserver.common.apk_signer_fingerprint(apkfile))
def test_apk_signer_fingerprint_short(self):
# fingerprints fetched with: keytool -printcert -file ____.RSA
testapks = (('repo/obb.main.oldversion_1444412523.apk', '818e469'),
('repo/obb.main.twoversions_1101613.apk', '32a2362'),
('repo/obb.main.twoversions_1101617.apk', '32a2362'))
for apkfile, keytoolcertfingerprint in testapks:
self.assertEqual(keytoolcertfingerprint,
fdroidserver.common.apk_signer_fingerprint_short(apkfile))
def test_find_apksigner_system_package_default_path(self):
"""apksigner should be automatically used from the PATH"""
usr_bin_apksigner = '/usr/bin/apksigner'
if not os.path.isfile(usr_bin_apksigner):
self.skipTest('SKIPPING since %s is not installed!' % usr_bin_apksigner)
os.environ['PATH'] = '/usr/local/bin:/usr/bin:/bin'
config = {}
fdroidserver.common.find_apksigner(config)
self.assertEqual(usr_bin_apksigner, config.get('apksigner'))
def test_find_apksigner_config_overrides(self):
"""apksigner should come from config before any auto-detection"""
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
android_home = os.path.join(testdir, 'ANDROID_HOME')
do_not_use = os.path.join(android_home, 'build-tools', '30.0.3', 'apksigner')
os.makedirs(os.path.dirname(do_not_use))
with open(do_not_use, 'w') as fp:
fp.write('#!/bin/sh\ndate\n')
os.chmod(do_not_use, 0o0755)
apksigner = os.path.join(testdir, 'apksigner')
config = {'apksigner': apksigner}
os.environ['ANDROID_HOME'] = android_home
os.environ['PATH'] = '%s:/usr/local/bin:/usr/bin:/bin' % android_home
fdroidserver.common.find_apksigner(config)
self.assertEqual(apksigner, config.get('apksigner'))
def test_find_apksigner_prefer_path(self):
"""apksigner should come from PATH before ANDROID_HOME"""
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
apksigner = os.path.join(testdir, 'apksigner')
with open(apksigner, 'w') as fp:
fp.write('#!/bin/sh\ndate\n')
os.chmod(apksigner, 0o0755)
android_home = os.path.join(testdir, 'ANDROID_HOME')
do_not_use = os.path.join(android_home, 'build-tools', '30.0.3', 'apksigner')
os.makedirs(os.path.dirname(do_not_use))
with open(do_not_use, 'w') as fp:
fp.write('#!/bin/sh\ndate\n')
os.chmod(do_not_use, 0o0755)
config = {'sdk_path': android_home}
os.environ['ANDROID_HOME'] = android_home
os.environ['PATH'] = '%s:/usr/local/bin:/usr/bin:/bin' % os.path.dirname(apksigner)
fdroidserver.common.find_apksigner(config)
self.assertEqual(apksigner, config.get('apksigner'))
def test_find_apksigner_prefer_newest(self):
"""apksigner should be the newest available in ANDROID_HOME"""
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
android_home = os.path.join(testdir, 'ANDROID_HOME')
apksigner = os.path.join(android_home, 'build-tools', '30.0.3', 'apksigner')
os.makedirs(os.path.dirname(apksigner))
with open(apksigner, 'w') as fp:
fp.write('#!/bin/sh\necho 30.0.3\n')
os.chmod(apksigner, 0o0755)
do_not_use = os.path.join(android_home, 'build-tools', '29.0.3', 'apksigner')
os.makedirs(os.path.dirname(do_not_use))
with open(do_not_use, 'w') as fp:
fp.write('#!/bin/sh\necho 29.0.3\n')
os.chmod(do_not_use, 0o0755)
config = {'sdk_path': android_home}
os.environ['PATH'] = '/fake/path/to/avoid/conflicts'
fdroidserver.common.find_apksigner(config)
self.assertEqual(apksigner, config.get('apksigner'))
def test_find_apksigner_system_package_android_home(self):
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
android_home = os.getenv('ANDROID_HOME')
if not android_home or not os.path.isdir(android_home):
self.skipTest('SKIPPING since ANDROID_HOME (%s) is not a dir!' % android_home)
if not glob.glob(os.path.join(android_home, 'build-tools', '*', 'apksigner')):
self.skipTest('SKIPPING since ANDROID_HOME (%s) build-tools has no apksigner!' % android_home)
fdroidserver.common.config = {'sdk_path': android_home}
os.environ['PATH'] = '/fake/path/to/avoid/conflicts'
config = fdroidserver.common.read_config()
fdroidserver.common.find_apksigner(config)
self.assertEqual(
os.path.join(android_home, 'build-tools'),
os.path.dirname(os.path.dirname(config.get('apksigner'))),
)
def test_sign_apk(self):
config = fdroidserver.common.read_config(fdroidserver.common.options)
if 'apksigner' not in config:
self.skipTest('SKIPPING test_sign_apk, apksigner not installed!')
config['keyalias'] = 'sova'
config['keystorepass'] = 'r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI='
config['keypass'] = 'r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI='
config['keystore'] = os.path.join(self.basedir, 'keystore.jks')
fdroidserver.common.config = config
fdroidserver.signindex.config = config
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
unsigned = os.path.join(testdir, 'urzip-release-unsigned.apk')
signed = os.path.join(testdir, 'urzip-release.apk')
shutil.copy(os.path.join(self.basedir, 'urzip-release-unsigned.apk'), testdir)
self.assertFalse(fdroidserver.common.verify_apk_signature(unsigned))
fdroidserver.common.sign_apk(unsigned, signed, config['keyalias'])
self.assertTrue(os.path.isfile(signed))
self.assertFalse(os.path.isfile(unsigned))
self.assertTrue(fdroidserver.common.verify_apk_signature(signed))
# now sign an APK with minSdkVersion >= 18
unsigned = os.path.join(testdir, 'duplicate.permisssions_9999999-unsigned.apk')
signed = os.path.join(testdir, 'duplicate.permisssions_9999999.apk')
shutil.copy(
os.path.join(self.basedir, 'repo', 'duplicate.permisssions_9999999.apk'),
os.path.join(unsigned),
)
fdroidserver.common.apk_strip_v1_signatures(unsigned, strip_manifest=True)
fdroidserver.common.sign_apk(unsigned, signed, config['keyalias'])
self.assertTrue(os.path.isfile(signed))
self.assertFalse(os.path.isfile(unsigned))
self.assertTrue(fdroidserver.common.verify_apk_signature(signed))
self.assertEqual('18', fdroidserver.common._get_androguard_APK(signed).get_min_sdk_version())
shutil.copy(os.path.join(self.basedir, 'minimal_targetsdk_30_unsigned.apk'), testdir)
unsigned = os.path.join(testdir, 'minimal_targetsdk_30_unsigned.apk')
signed = os.path.join(testdir, 'minimal_targetsdk_30.apk')
self.assertFalse(fdroidserver.common.verify_apk_signature(unsigned))
fdroidserver.common.sign_apk(unsigned, signed, config['keyalias'])
self.assertTrue(os.path.isfile(signed))
self.assertFalse(os.path.isfile(unsigned))
self.assertTrue(fdroidserver.common.verify_apk_signature(signed))
# verify it has a v2 signature
self.assertTrue(fdroidserver.common._get_androguard_APK(signed).is_signed_v2())
shutil.copy(os.path.join(self.basedir, 'no_targetsdk_minsdk30_unsigned.apk'), testdir)
unsigned = os.path.join(testdir, 'no_targetsdk_minsdk30_unsigned.apk')
signed = os.path.join(testdir, 'no_targetsdk_minsdk30_signed.apk')
fdroidserver.common.sign_apk(unsigned, signed, config['keyalias'])
self.assertTrue(fdroidserver.common.verify_apk_signature(signed))
self.assertTrue(fdroidserver.common._get_androguard_APK(signed).is_signed_v2())
shutil.copy(os.path.join(self.basedir, 'no_targetsdk_minsdk1_unsigned.apk'), testdir)
unsigned = os.path.join(testdir, 'no_targetsdk_minsdk1_unsigned.apk')
signed = os.path.join(testdir, 'no_targetsdk_minsdk1_signed.apk')
self.assertFalse(fdroidserver.common.verify_apk_signature(unsigned))
fdroidserver.common.sign_apk(unsigned, signed, config['keyalias'])
self.assertTrue(os.path.isfile(signed))
self.assertFalse(os.path.isfile(unsigned))
self.assertTrue(fdroidserver.common.verify_apk_signature(signed))
def test_get_apk_id(self):
config = dict()
fdroidserver.common.fill_config_defaults(config)
fdroidserver.common.config = config
self._set_build_tools()
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'),
('org.bitbucket.tickytacky.mirrormirror_1.apk', 'org.bitbucket.tickytacky.mirrormirror', '1', '1.0'),
('org.bitbucket.tickytacky.mirrormirror_2.apk', 'org.bitbucket.tickytacky.mirrormirror', '2', '1.0.1'),
('org.bitbucket.tickytacky.mirrormirror_3.apk', 'org.bitbucket.tickytacky.mirrormirror', '3', '1.0.2'),
('org.bitbucket.tickytacky.mirrormirror_4.apk', 'org.bitbucket.tickytacky.mirrormirror', '4', '1.0.3'),
('org.dyndns.fules.ck_20.apk', 'org.dyndns.fules.ck', '20', 'v1.6pre2'),
('urzip.apk', 'info.guardianproject.urzip', '100', '0.1'),
('urzip-badcert.apk', 'info.guardianproject.urzip', '100', '0.1'),
('urzip-badsig.apk', 'info.guardianproject.urzip', '100', '0.1'),
('urzip-release.apk', 'info.guardianproject.urzip', '100', '0.1'),
('urzip-release-unsigned.apk', 'info.guardianproject.urzip', '100', '0.1'),
('repo/com.politedroid_3.apk', 'com.politedroid', '3', '1.2'),
('repo/com.politedroid_4.apk', 'com.politedroid', '4', '1.3'),
('repo/com.politedroid_5.apk', 'com.politedroid', '5', '1.4'),
('repo/com.politedroid_6.apk', 'com.politedroid', '6', '1.5'),
('repo/duplicate.permisssions_9999999.apk', 'duplicate.permisssions', '9999999', ''),
('repo/info.zwanenburg.caffeinetile_4.apk', 'info.zwanenburg.caffeinetile', '4', '1.3'),
('repo/obb.main.oldversion_1444412523.apk', 'obb.main.oldversion', '1444412523', '0.1'),
('repo/obb.mainpatch.current_1619_another-release-key.apk', 'obb.mainpatch.current', '1619', '0.1'),
('repo/obb.mainpatch.current_1619.apk', 'obb.mainpatch.current', '1619', '0.1'),
('repo/obb.main.twoversions_1101613.apk', 'obb.main.twoversions', '1101613', '0.1'),
('repo/obb.main.twoversions_1101615.apk', 'obb.main.twoversions', '1101615', '0.1'),
('repo/obb.main.twoversions_1101617.apk', 'obb.main.twoversions', '1101617', '0.1'),
('repo/urzip-; Рахма́, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢·.apk', 'info.guardianproject.urzip', '100', '0.1'),
]
for apkfilename, appid, versionCode, versionName in testcases:
a, vc, vn = fdroidserver.common.get_apk_id(apkfilename)
self.assertEqual(appid, a, 'androguard appid parsing failed for ' + apkfilename)
self.assertEqual(versionName, vn, 'androguard versionName parsing failed for ' + apkfilename)
self.assertEqual(versionCode, vc, 'androguard versionCode parsing failed for ' + apkfilename)
if 'aapt' in config:
a, vc, vn = fdroidserver.common.get_apk_id_aapt(apkfilename)
self.assertEqual(appid, a, 'aapt appid parsing failed for ' + apkfilename)
self.assertEqual(versionCode, vc, 'aapt versionCode parsing failed for ' + apkfilename)
self.assertEqual(versionName, vn, 'aapt versionName parsing failed for ' + apkfilename)
with self.assertRaises(FDroidException):
fdroidserver.common.get_apk_id('nope')
def test_get_apk_id_aapt_regex(self):
files = glob.glob(os.path.join(self.basedir, 'build-tools', '[1-9]*.*', '*.txt'))
self.assertNotEqual(0, len(files))
for f in files:
appid, versionCode = os.path.splitext(os.path.basename(f))[0][12:].split('_')
with open(f) as fp:
m = fdroidserver.common.APK_ID_TRIPLET_REGEX.match(fp.read())
if m:
self.assertEqual(appid, m.group(1))
self.assertEqual(versionCode, m.group(2))
else:
self.fail('could not parse aapt output: {}'.format(f))
def test_get_native_code(self):
testcases = [
('repo/obb.main.twoversions_1101613.apk', []),
('org.bitbucket.tickytacky.mirrormirror_1.apk', []),
('org.bitbucket.tickytacky.mirrormirror_2.apk', []),
('org.bitbucket.tickytacky.mirrormirror_3.apk', []),
('org.bitbucket.tickytacky.mirrormirror_4.apk', []),
('org.dyndns.fules.ck_20.apk', ['arm64-v8a', 'armeabi', 'armeabi-v7a', 'mips', 'mips64', 'x86', 'x86_64']),
('urzip.apk', []),
('urzip-badcert.apk', []),
('urzip-badsig.apk', []),
('urzip-release.apk', []),
('urzip-release-unsigned.apk', []),
('repo/com.politedroid_3.apk', []),
('repo/com.politedroid_4.apk', []),
('repo/com.politedroid_5.apk', []),
('repo/com.politedroid_6.apk', []),
('repo/duplicate.permisssions_9999999.apk', []),
('repo/info.zwanenburg.caffeinetile_4.apk', []),
('repo/obb.main.oldversion_1444412523.apk', []),
('repo/obb.mainpatch.current_1619_another-release-key.apk', []),
('repo/obb.mainpatch.current_1619.apk', []),
('repo/obb.main.twoversions_1101613.apk', []),
('repo/obb.main.twoversions_1101615.apk', []),
('repo/obb.main.twoversions_1101617.apk', []),
('repo/urzip-; Рахма́, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢·.apk', []),
]
for apkfilename, native_code in testcases:
nc = fdroidserver.common.get_native_code(apkfilename)
self.assertEqual(native_code, nc)
def test_get_sdkversions_androguard(self):
"""This is a sanity test that androguard isn't broken"""
def get_minSdkVersion(apkfile):
apk = fdroidserver.common._get_androguard_APK(apkfile)
return fdroidserver.common.get_min_sdk_version(apk)
def get_targetSdkVersion(apkfile):
apk = fdroidserver.common._get_androguard_APK(apkfile)
return apk.get_effective_target_sdk_version()
self.assertEqual(4, get_minSdkVersion('bad-unicode-πÇÇ现代通用字-български-عربي1.apk'))
self.assertEqual(14, get_minSdkVersion('org.bitbucket.tickytacky.mirrormirror_1.apk'))
self.assertEqual(14, get_minSdkVersion('org.bitbucket.tickytacky.mirrormirror_2.apk'))
self.assertEqual(14, get_minSdkVersion('org.bitbucket.tickytacky.mirrormirror_3.apk'))
self.assertEqual(14, get_minSdkVersion('org.bitbucket.tickytacky.mirrormirror_4.apk'))
self.assertEqual(7, get_minSdkVersion('org.dyndns.fules.ck_20.apk'))
self.assertEqual(4, get_minSdkVersion('urzip.apk'))
self.assertEqual(4, get_minSdkVersion('urzip-badcert.apk'))
self.assertEqual(4, get_minSdkVersion('urzip-badsig.apk'))
self.assertEqual(4, get_minSdkVersion('urzip-release.apk'))
self.assertEqual(4, get_minSdkVersion('urzip-release-unsigned.apk'))
self.assertEqual(3, get_minSdkVersion('repo/com.politedroid_3.apk'))
self.assertEqual(3, get_minSdkVersion('repo/com.politedroid_4.apk'))
self.assertEqual(3, get_minSdkVersion('repo/com.politedroid_5.apk'))
self.assertEqual(14, get_minSdkVersion('repo/com.politedroid_6.apk'))
self.assertEqual(4, get_minSdkVersion('repo/obb.main.oldversion_1444412523.apk'))
self.assertEqual(4, get_minSdkVersion('repo/obb.mainpatch.current_1619_another-release-key.apk'))
self.assertEqual(4, get_minSdkVersion('repo/obb.mainpatch.current_1619.apk'))
self.assertEqual(4, get_minSdkVersion('repo/obb.main.twoversions_1101613.apk'))
self.assertEqual(4, get_minSdkVersion('repo/obb.main.twoversions_1101615.apk'))
self.assertEqual(4, get_minSdkVersion('repo/obb.main.twoversions_1101617.apk'))
self.assertEqual(4, get_minSdkVersion('repo/urzip-; Рахма́, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢·.apk'))
self.assertEqual(30, get_targetSdkVersion('minimal_targetsdk_30_unsigned.apk'))
self.assertEqual(1, get_targetSdkVersion('no_targetsdk_minsdk1_unsigned.apk'))
self.assertEqual(30, get_targetSdkVersion('no_targetsdk_minsdk30_unsigned.apk'))
def test_apk_release_name(self):
appid, vercode, sigfp = fdroidserver.common.apk_parse_release_filename('com.serwylo.lexica_905.apk')
self.assertEqual(appid, 'com.serwylo.lexica')
self.assertEqual(vercode, '905')
self.assertEqual(sigfp, None)
appid, vercode, sigfp = fdroidserver.common.apk_parse_release_filename('com.serwylo.lexica_905_c82e0f6.apk')
self.assertEqual(appid, 'com.serwylo.lexica')
self.assertEqual(vercode, '905')
self.assertEqual(sigfp, 'c82e0f6')
appid, vercode, sigfp = fdroidserver.common.apk_parse_release_filename('beverly_hills-90210.apk')
self.assertEqual(appid, None)
self.assertEqual(vercode, None)
self.assertEqual(sigfp, None)
def test_metadata_find_developer_signature(self):
sig = fdroidserver.common.metadata_find_developer_signature('org.smssecure.smssecure')
self.assertEqual('b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868', sig)
def test_parse_xml(self):
manifest = os.path.join('source-files', 'fdroid', 'fdroidclient', 'AndroidManifest.xml')
parsed = fdroidserver.common.parse_xml(manifest)
self.assertIsNotNone(parsed)
self.assertEqual(str(type(parsed)), "<class 'xml.etree.ElementTree.Element'>")
def test_parse_androidmanifests(self):
app = fdroidserver.metadata.App()
app.id = 'org.fdroid.fdroid'
paths = [
os.path.join('source-files', 'fdroid', 'fdroidclient', 'AndroidManifest.xml'),
os.path.join('source-files', 'fdroid', 'fdroidclient', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('0.94-test', '940', 'org.fdroid.fdroid'),
fdroidserver.common.parse_androidmanifests(paths, app))
app = fdroidserver.metadata.App()
app.AutoName = 'android-chat'
app.RepoType = 'git'
url = 'https://github.com/wildfirechat/android-chat.git'
app.SourceCode = url.rstrip('.git')
app.Repo = url
paths = [
os.path.join('source-files', 'cn.wildfirechat.chat', 'avenginekit', 'build.gradle'),
os.path.join('source-files', 'cn.wildfirechat.chat', 'build.gradle'),
os.path.join('source-files', 'cn.wildfirechat.chat', 'client', 'build.gradle'),
os.path.join('source-files', 'cn.wildfirechat.chat', 'client', 'src', 'main', 'AndroidManifest.xml'),
os.path.join('source-files', 'cn.wildfirechat.chat', 'emojilibrary', 'build.gradle'),
os.path.join('source-files', 'cn.wildfirechat.chat', 'gradle', 'build_libraries.gradle'),
os.path.join('source-files', 'cn.wildfirechat.chat', 'imagepicker', 'build.gradle'),
os.path.join('source-files', 'cn.wildfirechat.chat', 'mars-core-release', 'build.gradle'),
os.path.join('source-files', 'cn.wildfirechat.chat', 'push', 'build.gradle'),
os.path.join('source-files', 'cn.wildfirechat.chat', 'settings.gradle'),
os.path.join('source-files', 'cn.wildfirechat.chat', 'chat', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('0.6.9', '23', 'cn.wildfirechat.chat'),
fdroidserver.common.parse_androidmanifests(paths, app))
app = fdroidserver.metadata.App()
app.Repo = 'https://github.com/Integreight/1Sheeld-Android-App'
paths = [
os.path.join('source-files', 'com.integreight.onesheeld', 'pagerIndicator', 'src', 'main', 'AndroidManifest.xml'),
os.path.join('source-files', 'com.integreight.onesheeld', 'pagerIndicator', 'build.gradle'),
os.path.join('source-files', 'com.integreight.onesheeld', 'oneSheeld', 'src', 'main', 'AndroidManifest.xml'),
os.path.join('source-files', 'com.integreight.onesheeld', 'oneSheeld', 'build.gradle'),
os.path.join('source-files', 'com.integreight.onesheeld', 'localeapi', 'src', 'main', 'AndroidManifest.xml'),
os.path.join('source-files', 'com.integreight.onesheeld', 'localeapi', 'build.gradle'),
os.path.join('source-files', 'com.integreight.onesheeld', 'build.gradle'),
os.path.join('source-files', 'com.integreight.onesheeld', 'settings.gradle'),
os.path.join('source-files', 'com.integreight.onesheeld', 'quickReturnHeader', 'src', 'main', 'AndroidManifest.xml'),
os.path.join('source-files', 'com.integreight.onesheeld', 'quickReturnHeader', 'build.gradle'),
os.path.join('source-files', 'com.integreight.onesheeld', 'pullToRefreshlibrary', 'src', 'main', 'AndroidManifest.xml'),
os.path.join('source-files', 'com.integreight.onesheeld', 'pullToRefreshlibrary', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('1.9.0', '170521', 'com.integreight.onesheeld'),
fdroidserver.common.parse_androidmanifests(paths, app))
app = fdroidserver.metadata.App()
app.id = 'dev.patrickgold.florisboard'
paths = [
os.path.join('source-files', 'dev.patrickgold.florisboard', 'app', 'build.gradle.kts'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('0.3.10', '29', 'dev.patrickgold.florisboard'),
fdroidserver.common.parse_androidmanifests(paths, app))
def test_parse_androidmanifests_ignore(self):
app = fdroidserver.metadata.App()
app.id = 'org.fdroid.fdroid'
app.UpdateCheckIgnore = '-test'
paths = [
os.path.join('source-files', 'fdroid', 'fdroidclient', 'AndroidManifest.xml'),
os.path.join('source-files', 'fdroid', 'fdroidclient', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('Ignore', None, 'org.fdroid.fdroid'),
fdroidserver.common.parse_androidmanifests(paths, app))
def test_parse_androidmanifests_with_flavor(self):
app = fdroidserver.metadata.App()
build = fdroidserver.metadata.Build()
build.gradle = ['devVersion']
app['Builds'] = [build]
app.id = 'org.fdroid.fdroid.dev'
paths = [
os.path.join('source-files', 'fdroid', 'fdroidclient', 'AndroidManifest.xml'),
os.path.join('source-files', 'fdroid', 'fdroidclient', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('0.95-dev', '949', 'org.fdroid.fdroid.dev'),
fdroidserver.common.parse_androidmanifests(paths, app))
app = fdroidserver.metadata.App()
build = fdroidserver.metadata.Build()
build.gradle = ['free']
app['Builds'] = [build]
app.id = 'eu.siacs.conversations'
paths = [
os.path.join('source-files', 'eu.siacs.conversations', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('1.23.1', '245', 'eu.siacs.conversations'),
fdroidserver.common.parse_androidmanifests(paths, app))
app = fdroidserver.metadata.App()
build = fdroidserver.metadata.Build()
build.gradle = ['generic']
app['Builds'] = [build]
app.id = 'com.nextcloud.client'
paths = [
os.path.join('source-files', 'com.nextcloud.client', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('2.0.0', '20000099', 'com.nextcloud.client'),
fdroidserver.common.parse_androidmanifests(paths, app))
app = fdroidserver.metadata.App()
build = fdroidserver.metadata.Build()
build.gradle = ['versionDev']
app['Builds'] = [build]
app.id = 'com.nextcloud.android.beta'
paths = [
os.path.join('source-files', 'com.nextcloud.client', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('20171223', '20171223', 'com.nextcloud.android.beta'),
fdroidserver.common.parse_androidmanifests(paths, app))
app = fdroidserver.metadata.App()
build = fdroidserver.metadata.Build()
build.gradle = ['standard']
app['Builds'] = [build]
app.id = 'at.bitfire.davdroid'
paths = [
os.path.join('source-files', 'at.bitfire.davdroid', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('1.9.8.1-ose', '197', 'at.bitfire.davdroid'),
fdroidserver.common.parse_androidmanifests(paths, app))
app = fdroidserver.metadata.App()
build = fdroidserver.metadata.Build()
build.gradle = ['libre']
app['Builds'] = [build]
app.id = 'com.kunzisoft.fdroidtest.applicationidsuffix.libre'
paths = [
os.path.join('source-files', 'com.kunzisoft.testcase', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('1.0-libre', '1', 'com.kunzisoft.fdroidtest.applicationidsuffix.libre'),
fdroidserver.common.parse_androidmanifests(paths, app))
app = fdroidserver.metadata.App()
build = fdroidserver.metadata.Build()
build.gradle = ['pro']
app['Builds'] = [build]
app.id = 'com.kunzisoft.fdroidtest.applicationidsuffix.pro'
paths = [
os.path.join('source-files', 'com.kunzisoft.testcase', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('20180430-pro', '20180430', 'com.kunzisoft.fdroidtest.applicationidsuffix.pro'),
fdroidserver.common.parse_androidmanifests(paths, app))
app = fdroidserver.metadata.App()
build = fdroidserver.metadata.Build()
build.gradle = ['free']
app['Builds'] = [build]
app.id = 'com.kunzisoft.fdroidtest.applicationidsuffix'
paths = [
os.path.join('source-files', 'com.kunzisoft.testcase', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('1.0-free', '1', 'com.kunzisoft.fdroidtest.applicationidsuffix'),
fdroidserver.common.parse_androidmanifests(paths, app))
app = fdroidserver.metadata.App()
build = fdroidserver.metadata.Build()
build.gradle = ['underscore']
app['Builds'] = [build]
app.id = 'com.kunzisoft.fdroidtest.applicationidsuffix.underscore'
paths = [
os.path.join('source-files', 'com.kunzisoft.testcase', 'build.gradle'),
]
for path in paths:
self.assertTrue(os.path.isfile(path))
self.assertEqual(('20180430-underscore', '2018_04_30', 'com.kunzisoft.fdroidtest.applicationidsuffix.underscore'),
fdroidserver.common.parse_androidmanifests(paths, app))
def test_get_all_gradle_and_manifests(self):
"""Test whether the function works with relative and absolute paths"""
a = fdroidserver.common.get_all_gradle_and_manifests(Path('source-files/cn.wildfirechat.chat'))
paths = [
'avenginekit/build.gradle',
'build.gradle',
'chat/build.gradle',
'client/build.gradle',
'client/src/main/AndroidManifest.xml',
'emojilibrary/build.gradle',
'gradle/build_libraries.gradle',
'imagepicker/build.gradle',
'mars-core-release/build.gradle',
'push/build.gradle',
'settings.gradle',
]
paths = [Path('source-files/cn.wildfirechat.chat') / path for path in paths]
self.assertEqual(sorted(paths), sorted(a))
abspath = Path(self.basedir) / 'source-files/realm'
p = fdroidserver.common.get_all_gradle_and_manifests(abspath)
self.assertEqual(1, len(p))
# TODO: Pathon3.9: self.assertTrue(p[0].is_relative_to(abspath))
self.assertTrue(abspath in p[0].parents)
def test_get_gradle_subdir(self):
subdirs = {
'cn.wildfirechat.chat': 'chat',
'com.anpmech.launcher': 'app',
'org.tasks': 'app',
'ut.ewh.audiometrytest': 'app',
}
for k, v in subdirs.items():
build_dir = Path('source-files') / k
paths = fdroidserver.common.get_all_gradle_and_manifests(build_dir)
logging.info(paths)
subdir = fdroidserver.common.get_gradle_subdir(build_dir, paths)
self.assertEqual(v, str(subdir))
def test_parse_srclib_spec_good(self):
self.assertEqual(fdroidserver.common.parse_srclib_spec('osmand-external-skia@android/oreo'),
('osmand-external-skia', 'android/oreo', None, None))
self.assertEqual(fdroidserver.common.parse_srclib_spec('1:appcompat@v7'),
('appcompat', 'v7', '1', None))
self.assertEqual(fdroidserver.common.parse_srclib_spec('1:Support/v7/appcompat@android-4.4_r1.1'),
('Support', 'android-4.4_r1.1', '1', 'v7/appcompat'))
def test_parse_srclib_spec_bad(self):
with self.assertRaises(MetaDataException):
self.assertEqual(fdroidserver.common.parse_srclib_spec(None))
with self.assertRaises(MetaDataException):
self.assertEqual(fdroidserver.common.parse_srclib_spec('no-ref'))
with self.assertRaises(MetaDataException):
self.assertEqual(fdroidserver.common.parse_srclib_spec('@multi@at-signs@'))
def test_bad_urls(self):
for url in (
'asdf',
'file://thing.git',
'https:///github.com/my/project',
'git:///so/many/slashes',
'ssh:/notabug.org/missing/a/slash',
'git:notabug.org/missing/some/slashes',
'https//github.com/bar/baz',
):
with self.assertRaises(ValueError):
fdroidserver.common.get_app_from_url(url)
def test_remove_signing_keys(self):
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
print(testdir)
shutil.copytree(
os.path.join(self.basedir, 'source-files'),
os.path.join(testdir, 'source-files'),
)
os.chdir(testdir)
with_signingConfigs = [
'source-files/com.seafile.seadroid2/app/build.gradle',
'source-files/eu.siacs.conversations/build.gradle',
'source-files/info.guardianproject.ripple/build.gradle',
'source-files/open-keychain/open-keychain/build.gradle',
'source-files/open-keychain/open-keychain/OpenKeychain/build.gradle',
'source-files/org.tasks/app/build.gradle.kts',
'source-files/osmandapp/osmand/build.gradle',
'source-files/ut.ewh.audiometrytest/app/build.gradle',
]
for f in with_signingConfigs:
build_dir = os.path.join(*f.split(os.sep)[:2])
if not os.path.isdir(build_dir):
continue
fdroidserver.common.remove_signing_keys(build_dir)
fromfile = os.path.join(self.basedir, f)
with open(f) as fp:
content = fp.read()
if 'signingConfig' in content:
with open(f) as fp:
b = fp.readlines()
with open(fromfile) as fp:
a = fp.readlines()
diff = difflib.unified_diff(a, b, fromfile, f)
sys.stdout.writelines(diff)
self.assertFalse(True)
do_not_modify = [
'source-files/Zillode/syncthing-silk/build.gradle',
'source-files/at.bitfire.davdroid/build.gradle',
'source-files/com.kunzisoft.testcase/build.gradle',
'source-files/com.nextcloud.client/build.gradle',
'source-files/fdroid/fdroidclient/build.gradle',
'source-files/firebase-suspect/app/build.gradle',
'source-files/firebase-suspect/build.gradle',
'source-files/firebase-whitelisted/app/build.gradle',
'source-files/firebase-whitelisted/build.gradle',
'source-files/org.mozilla.rocket/app/build.gradle',
'source-files/realm/react-native/android/build.gradle',
'triple-t-2/build/org.piwigo.android/app/build.gradle',
]
for f in do_not_modify:
build_dir = os.path.join(*f.split(os.sep)[:2])
if not os.path.isdir(build_dir):
continue
fdroidserver.common.remove_signing_keys(build_dir)
fromfile = os.path.join(self.basedir, f)
with open(fromfile) as fp:
a = fp.readlines()
with open(f) as fp:
b = fp.readlines()
diff = list(difflib.unified_diff(a, b, fromfile, f))
self.assertEqual(0, len(diff), 'This file should not have been modified:\n' + ''.join(diff))
def test_calculate_math_string(self):
self.assertEqual(1234,
fdroidserver.common.calculate_math_string('1234'))
self.assertEqual((1 + 1) * 2,
fdroidserver.common.calculate_math_string('(1 + 1) * 2'))
self.assertEqual((1 - 1) * 2 + 3 * 1 - 1,
fdroidserver.common.calculate_math_string('(1 - 1) * 2 + 3 * 1 - 1'))
self.assertEqual(0 - 12345,
fdroidserver.common.calculate_math_string('0 - 12345'))
self.assertEqual(0xffff,
fdroidserver.common.calculate_math_string('0xffff'))
self.assertEqual(0xcafe * 123,
fdroidserver.common.calculate_math_string('0xcafe * 123'))
self.assertEqual(-1,
fdroidserver.common.calculate_math_string('-1'))
with self.assertRaises(SyntaxError):
fdroidserver.common.calculate_math_string('__import__("urllib")')
with self.assertRaises(SyntaxError):
fdroidserver.common.calculate_math_string('self')
with self.assertRaises(SyntaxError):
fdroidserver.common.calculate_math_string('Ox9()')
with self.assertRaises(SyntaxError):
fdroidserver.common.calculate_math_string('1+1; print(1)')
with self.assertRaises(SyntaxError):
fdroidserver.common.calculate_math_string('1-1 # no comment')
def test_deploy_build_log_with_rsync_with_id_file(self):
mocklogcontent = bytes(
textwrap.dedent(
"""\
build started
building...
build completed
profit!"""
),
'utf-8',
)
fdroidserver.common.options = mock.Mock()
fdroidserver.common.options.verbose = False
fdroidserver.common.options.quiet = False
fdroidserver.common.config = {}
fdroidserver.common.config['serverwebroot'] = [
'example.com:/var/www/fdroid/',
'example.com:/var/www/fbot/',
]
fdroidserver.common.config['deploy_process_logs'] = True
fdroidserver.common.config['identity_file'] = 'ssh/id_rsa'
assert_subprocess_call_iteration = 0
def assert_subprocess_call(cmd):
nonlocal assert_subprocess_call_iteration
logging.debug(cmd)
if assert_subprocess_call_iteration == 0:
self.assertListEqual(['rsync',
'--archive',
'--delete-after',
'--safe-links',
'-e',
'ssh -oBatchMode=yes -oIdentitiesOnly=yes -i ssh/id_rsa',
cmd[6],
'example.com:/var/www/fdroid/repo/'],
cmd)
self.assertTrue(cmd[6].endswith('/com.example.app_4711.log.gz'))
with gzip.open(cmd[6], 'r') as f:
self.assertTrue(f.read(), mocklogcontent)
elif assert_subprocess_call_iteration == 1:
self.assertListEqual(['rsync',
'--archive',
'--delete-after',
'--safe-links',
'-e',
'ssh -oBatchMode=yes -oIdentitiesOnly=yes -i ssh/id_rsa',
cmd[6],
'example.com:/var/www/fbot/repo/'],
cmd)
self.assertTrue(cmd[6].endswith('/com.example.app_4711.log.gz'))
with gzip.open(cmd[6], 'r') as f:
self.assertTrue(f.read(), mocklogcontent)
else:
self.fail('unexpected subprocess.call invocation ({})'
.format(assert_subprocess_call_iteration))
assert_subprocess_call_iteration += 1
return 0
with mock.patch('subprocess.call',
side_effect=assert_subprocess_call):
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
fdroidserver.common.deploy_build_log_with_rsync(
'com.example.app', '4711', mocklogcontent)
expected_log_path = os.path.join(tmpdir, 'repo', 'com.example.app_4711.log.gz')
self.assertTrue(os.path.isfile(expected_log_path))
with gzip.open(expected_log_path, 'r') as f:
self.assertEqual(f.read(), mocklogcontent)
def test_deploy_status_json(self):
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
fakesubcommand = 'fakesubcommand'
fake_timestamp = 1234567890
fakeserver = 'example.com:/var/www/fbot/'
expected_dir = os.path.join(testdir, fakeserver.replace(':', ''), 'repo', 'status')
fdroidserver.common.options = mock.Mock()
fdroidserver.common.config = {}
fdroidserver.common.config['serverwebroot'] = [fakeserver]
fdroidserver.common.config['identity_file'] = 'ssh/id_rsa'
def assert_subprocess_call(cmd):
dest_path = os.path.join(testdir, cmd[-1].replace(':', ''))
if not os.path.exists(dest_path):
os.makedirs(dest_path)
return subprocess.run(cmd[:-1] + [dest_path]).returncode
with mock.patch('subprocess.call', side_effect=assert_subprocess_call):
with mock.patch.object(sys, 'argv', ['fdroid ' + fakesubcommand]):
output = fdroidserver.common.setup_status_output(time.localtime(fake_timestamp))
self.assertFalse(os.path.exists(os.path.join(expected_dir, 'running.json')))
with mock.patch.object(sys, 'argv', ['fdroid ' + fakesubcommand]):
fdroidserver.common.write_status_json(output)
self.assertFalse(os.path.exists(os.path.join(expected_dir, fakesubcommand + '.json')))
fdroidserver.common.config['deploy_process_logs'] = True
output = fdroidserver.common.setup_status_output(time.localtime(fake_timestamp))
expected_path = os.path.join(expected_dir, 'running.json')
self.assertTrue(os.path.isfile(expected_path))
with open(expected_path) as fp:
data = json.load(fp)
self.assertEqual(fake_timestamp * 1000, data['startTimestamp'])
self.assertFalse('endTimestamp' in data)
testvalue = 'asdfasd'
output['testvalue'] = testvalue
fdroidserver.common.write_status_json(output)
expected_path = os.path.join(expected_dir, fakesubcommand + '.json')
self.assertTrue(os.path.isfile(expected_path))
with open(expected_path) as fp:
data = json.load(fp)
self.assertEqual(fake_timestamp * 1000, data['startTimestamp'])
self.assertTrue('endTimestamp' in data)
self.assertEqual(testvalue, output.get('testvalue'))
def test_string_is_integer(self):
self.assertTrue(fdroidserver.common.string_is_integer('0x10'))
self.assertTrue(fdroidserver.common.string_is_integer('010'))
self.assertTrue(fdroidserver.common.string_is_integer('123'))
self.assertFalse(fdroidserver.common.string_is_integer('0xgg'))
self.assertFalse(fdroidserver.common.string_is_integer('01g'))
self.assertFalse(fdroidserver.common.string_is_integer('o123'))
def test_version_code_string_to_int(self):
self.assertEqual(16, fdroidserver.common.version_code_string_to_int('0x10'))
self.assertEqual(198712389, fdroidserver.common.version_code_string_to_int('198712389'))
self.assertEqual(8, fdroidserver.common.version_code_string_to_int('0o10'))
self.assertEqual(10, fdroidserver.common.version_code_string_to_int('010'))
self.assertEqual(123, fdroidserver.common.version_code_string_to_int('0000123'))
self.assertEqual(-42, fdroidserver.common.version_code_string_to_int('-42'))
def test_getsrclibvcs(self):
fdroidserver.metadata.srclibs = {'somelib': {'RepoType': 'git'},
'yeslib': {'RepoType': 'hg'},
'nolib': {'RepoType': 'git-svn'}}
self.assertEqual(fdroidserver.common.getsrclibvcs('somelib'), 'git')
self.assertEqual(fdroidserver.common.getsrclibvcs('yeslib'), 'hg')
self.assertEqual(fdroidserver.common.getsrclibvcs('nolib'), 'git-svn')
with self.assertRaises(VCSException):
fdroidserver.common.getsrclibvcs('nonexistentlib')
def test_getsrclib_not_found(self):
fdroidserver.common.config = {'sdk_path': '',
'java_paths': {}}
fdroidserver.metadata.srclibs = {}
with self.assertRaisesRegex(VCSException, 'srclib SDL not found.'):
fdroidserver.common.getsrclib('SDL@release-2.0.3', 'srclib')
def test_getsrclib_gotorevision_raw(self):
fdroidserver.common.config = {'sdk_path': '',
'java_paths': {}}
fdroidserver.metadata.srclibs = {'SDL': {'RepoType': 'git',
'Repo': ''}}
vcs = mock.Mock()
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
os.makedirs(os.path.join('srclib', 'SDL'))
with mock.patch('fdroidserver.common.getvcs', return_value=vcs):
ret = fdroidserver.common.getsrclib('SDL', 'srclib', raw=True)
self.assertEqual(vcs.srclib, ('SDL', None, 'srclib/SDL'))
self.assertEqual(ret, vcs)
def test_getsrclib_gotorevision_ref(self):
fdroidserver.common.config = {'sdk_path': '',
'java_paths': {}}
fdroidserver.metadata.srclibs = {'ACRA': {'RepoType': 'git',
'Repo': 'https://github.com/ACRA/acra.git',
'Subdir': None,
'Prepare': None}}
vcs = mock.Mock()
skm = mock.Mock()
dfm = mock.Mock()
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
os.makedirs(os.path.join('srclib', 'ACRA'))
with mock.patch('fdroidserver.common.getvcs', return_value=vcs):
with mock.patch('fdroidserver.common.remove_signing_keys', skm):
with mock.patch('fdroidserver.common.remove_debuggable_flags', dfm):
ret = fdroidserver.common.getsrclib('ACRA@acra-4.6.2', 'srclib')
self.assertEqual(vcs.srclib, ('ACRA', None, 'srclib/ACRA'))
vcs.gotorevision.assert_called_once_with('acra-4.6.2', True)
skm.assert_called_once_with('srclib/ACRA')
dfm.assert_called_once_with('srclib/ACRA')
self.assertEqual(ret, ('ACRA', None, 'srclib/ACRA'))
def test_run_yamllint_wellformed(self):
try:
import yamllint.config
yamllint.config # make pyflakes ignore this
except ImportError:
self.skipTest('yamllint not installed')
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
with open('wellformed.yml', 'w') as f:
f.write(
textwrap.dedent(
'''\
yaml:
file:
- for
- test
purposeses: true
'''
)
)
result = fdroidserver.common.run_yamllint('wellformed.yml')
self.assertEqual(result, '')
def test_run_yamllint_malformed(self):
try:
import yamllint.config
yamllint.config # make pyflakes ignore this
except ImportError:
self.skipTest('yamllint not installed')
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
with open('malformed.yml', 'w') as f:
f.write(
textwrap.dedent(
'''\
yaml:
- that
fails
- test
'''
)
)
result = fdroidserver.common.run_yamllint('malformed.yml')
self.assertIsNotNone(result)
self.assertNotEqual(result, '')
def test_with_no_config(self):
"""It should set defaults if no config file is found"""
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
self.assertFalse(os.path.exists('config.yml'))
self.assertFalse(os.path.exists('config.py'))
config = fdroidserver.common.read_config(fdroidserver.common.options)
self.assertIsNone(config.get('stats_server'))
self.assertIsNotNone(config.get('char_limits'))
def test_with_config_yml(self):
"""Make sure it is possible to use config.yml alone."""
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
with open('config.yml', 'w') as fp:
fp.write('apksigner: yml')
self.assertTrue(os.path.exists('config.yml'))
self.assertFalse(os.path.exists('config.py'))
config = fdroidserver.common.read_config(fdroidserver.common.options)
self.assertEqual('yml', config.get('apksigner'))
def test_with_config_yml_with_env_var(self):
"""Make sure it is possible to use config.yml alone."""
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
os.environ['SECRET'] = 'mysecretpassword'
with open('config.yml', 'w') as fp:
fp.write("""keypass: {'env': 'SECRET'}""")
self.assertTrue(os.path.exists('config.yml'))
self.assertFalse(os.path.exists('config.py'))
config = fdroidserver.common.read_config(fdroidserver.common.options)
self.assertEqual(os.getenv('SECRET', 'fail'), config.get('keypass'))
def test_with_config_py(self):
"""Make sure it is still possible to use config.py alone."""
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
with open('config.py', 'w') as fp:
fp.write('apksigner = "py"')
self.assertFalse(os.path.exists('config.yml'))
self.assertTrue(os.path.exists('config.py'))
config = fdroidserver.common.read_config(fdroidserver.common.options)
self.assertEqual("py", config.get('apksigner'))
def test_config_perm_warning(self):
"""Exercise the code path that issues a warning about unsafe permissions."""
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
with open('config.yml', 'w') as fp:
fp.write('keystore: foo.jks')
self.assertTrue(os.path.exists(fp.name))
os.chmod(fp.name, 0o666)
fdroidserver.common.read_config(fdroidserver.common.options)
os.remove(fp.name)
fdroidserver.common.config = None
with open('config.py', 'w') as fp:
fp.write('keystore = "foo.jks"')
self.assertTrue(os.path.exists(fp.name))
os.chmod(fp.name, 0o666)
fdroidserver.common.read_config(fdroidserver.common.options)
def test_with_both_config_yml_py(self):
"""If config.yml and config.py are present, config.py should be ignored."""
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
with open('config.yml', 'w') as fp:
fp.write('apksigner: yml')
with open('config.py', 'w') as fp:
fp.write('apksigner = "py"')
self.assertTrue(os.path.exists('config.yml'))
self.assertTrue(os.path.exists('config.py'))
config = fdroidserver.common.read_config(fdroidserver.common.options)
self.assertEqual('yml', config.get('apksigner'))
def test_write_to_config_yml(self):
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
with open('config.yml', 'w') as fp:
fp.write('apksigner: yml')
self.assertTrue(os.path.exists(fp.name))
self.assertFalse(os.path.exists('config.py'))
config = fdroidserver.common.read_config(fdroidserver.common.options)
self.assertFalse('keypass' in config)
self.assertEqual('yml', config.get('apksigner'))
fdroidserver.common.write_to_config(config, 'keypass', 'mysecretpassword')
with open(fp.name) as fp:
print(fp.read())
fdroidserver.common.config = None
config = fdroidserver.common.read_config(fdroidserver.common.options)
self.assertEqual('mysecretpassword', config['keypass'])
def test_write_to_config_py(self):
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
with open('config.py', 'w') as fp:
fp.write('apksigner = "py"')
self.assertTrue(os.path.exists(fp.name))
self.assertFalse(os.path.exists('config.yml'))
config = fdroidserver.common.read_config(fdroidserver.common.options)
self.assertFalse('keypass' in config)
self.assertEqual('py', config.get('apksigner'))
fdroidserver.common.write_to_config(config, 'keypass', 'mysecretpassword')
fdroidserver.common.config = None
config = fdroidserver.common.read_config(fdroidserver.common.options)
self.assertEqual('mysecretpassword', config['keypass'])
def test_config_dict_with_int_keys(self):
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
with open('config.yml', 'w') as fp:
fp.write('java_paths:\n 8: /usr/lib/jvm/java-8-openjdk\n')
self.assertTrue(os.path.exists(fp.name))
self.assertFalse(os.path.exists('config.py'))
config = fdroidserver.common.read_config(fdroidserver.common.options)
self.assertEqual('/usr/lib/jvm/java-8-openjdk', config.get('java_paths', {}).get('8'))
def test_loading_config_buildserver_yml(self):
"""Smoke check to make sure this file is properly parsed"""
testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
os.chdir(testdir)
shutil.copy(os.path.join(self.basedir, '..', 'buildserver', 'config.buildserver.yml'),
'config.yml')
self.assertFalse(os.path.exists('config.py'))
fdroidserver.common.read_config(fdroidserver.common.options)
def test_setup_status_output(self):
testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
print(testdir)
os.chdir(testdir)
start_timestamp = time.gmtime()
subcommand = 'test'
fakecmd = ['fdroid ' + subcommand, '--option']
sys.argv = fakecmd
fdroidserver.common.config = dict()
fdroidserver.common.setup_status_output(start_timestamp)
with open(os.path.join('repo', 'status', 'running.json')) as fp:
data = json.load(fp)
self.assertFalse(os.path.exists('.git'))
self.assertFalse('fdroiddata' in data)
self.assertEqual(fakecmd, data['commandLine'])
self.assertEqual(subcommand, data['subcommand'])
def test_setup_status_output_in_git_repo(self):
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
os.chdir(testdir)
logging.getLogger('git.cmd').setLevel(logging.INFO)
git_repo = git.Repo.init(testdir)
file_in_git = 'README.md'
with open(file_in_git, 'w') as fp:
fp.write('this is just a test')
git_repo.git.add(all=True)
git_repo.index.commit("update README")
start_timestamp = time.gmtime()
fakecmd = ['fdroid test2', '--option']
sys.argv = fakecmd
fdroidserver.common.config = dict()
fdroidserver.common.setup_status_output(start_timestamp)
with open(os.path.join('repo', 'status', 'running.json')) as fp:
data = json.load(fp)
self.assertTrue(os.path.exists('.git'))
self.assertIsNotNone(re.match(r'[0-9a-f]{40}', data['fdroiddata']['commitId']),
'Must be a valid git SHA1 commit ID!')
self.assertFalse(data['fdroiddata']['isDirty'])
self.assertEqual(fakecmd, data['commandLine'])
self.assertEqual([],
data['fdroiddata']['untrackedFiles'])
dirtyfile = 'dirtyfile'
with open(dirtyfile, 'w') as fp:
fp.write('this is just a test')
with open(file_in_git, 'a') as fp:
fp.write('\nappend some stuff')
self.assertEqual([],
data['fdroiddata']['modifiedFiles'])
fdroidserver.common.setup_status_output(start_timestamp)
with open(os.path.join('repo', 'status', 'running.json')) as fp:
data = json.load(fp)
self.assertTrue(data['fdroiddata']['isDirty'])
self.assertEqual([file_in_git],
data['fdroiddata']['modifiedFiles'])
self.assertEqual([dirtyfile, 'repo/status/running.json'],
data['fdroiddata']['untrackedFiles'])
def test_get_app_display_name(self):
testvalue = 'WIN!'
for app in [
{'Name': testvalue},
{'AutoName': testvalue},
{'id': testvalue},
{'id': 'a', 'localized': {'de-AT': {'name': testvalue}}},
{'id': 'a', 'localized': {
'de-AT': {'name': 'nope'},
'en-US': {'name': testvalue},
}},
{'AutoName': 'ignore me', 'Name': testvalue, 'id': 'nope'},
{'AutoName': testvalue, 'id': 'nope'}]:
self.assertEqual(testvalue, fdroidserver.common.get_app_display_name(app))
def test_get_android_tools_versions(self):
sdk_path = os.path.join(self.basedir, 'get_android_tools_versions')
fdroidserver.common.config = {'sdk_path': sdk_path}
components = fdroidserver.common.get_android_tools_versions()
expected = (
('android-ndk/android-ndk-r21d', '21.3.6528147'),
('android-ndk/r11c', '11.2.2725575'),
('android-ndk/r17c', '17.2.4988734'),
('android-sdk/patcher/v4', '1'),
('android-sdk/platforms/android-30', '3'),
('android-sdk/skiaparser/1', '6'),
('android-sdk/tools', '26.1.1'),
)
self.assertSequenceEqual(expected, sorted(components))
def test_read_pkg_args(self):
allow_vercodes = False
self.assertEqual(
{'org.fdroid.fdroid': []},
fdroidserver.common.read_pkg_args(['org.fdroid.fdroid'], allow_vercodes),
)
self.assertNotEqual(
{'com.example': ['123456']},
fdroidserver.common.read_pkg_args(['com.example:123456'], allow_vercodes),
)
allow_vercodes = True
self.assertEqual(
{'org.fdroid.fdroid': []},
fdroidserver.common.read_pkg_args(['org.fdroid.fdroid'], allow_vercodes),
)
self.assertEqual(
{'com.example': ['123456']},
fdroidserver.common.read_pkg_args(['com.example:123456'], allow_vercodes),
)
self.assertEqual(
{'org.debian_kit': ['6']},
fdroidserver.common.read_pkg_args(['org.debian_kit_6.apk'], allow_vercodes),
)
appid_versionCode_pairs = (
'org.fdroid.fdroid:1',
'com.example:12345',
'com.example:67890',
)
self.assertEqual(
{'com.example': ['12345', '67890'], 'org.fdroid.fdroid': ['1']},
fdroidserver.common.read_pkg_args(appid_versionCode_pairs, allow_vercodes)
)
appid_versionCode_pairs = (
'com.example:67890',
'org.c_base.c_beam_29.apk',
)
self.assertEqual(
{'com.example': ['67890'], 'org.c_base.c_beam': ['29']},
fdroidserver.common.read_pkg_args(appid_versionCode_pairs, allow_vercodes),
)
def test_apk_strip_v1_signatures(self):
testdir = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
before = os.path.join(self.basedir, 'no_targetsdk_minsdk1_unsigned.apk')
after = os.path.join(testdir, 'after.apk')
shutil.copy(before, after)
fdroidserver.common.apk_strip_v1_signatures(after, strip_manifest=False)
def test_metadata_find_developer_signing_files(self):
appid = 'org.smssecure.smssecure'
self.assertIsNone(
fdroidserver.common.metadata_find_developer_signing_files(appid, 133)
)
vc = '135'
self.assertEqual(
(
os.path.join('metadata', appid, 'signatures', vc, '28969C09.RSA'),
os.path.join('metadata', appid, 'signatures', vc, '28969C09.SF'),
os.path.join('metadata', appid, 'signatures', vc, 'MANIFEST.MF'),
None,
),
fdroidserver.common.metadata_find_developer_signing_files(appid, vc),
)
vc = '134'
self.assertEqual(
(
os.path.join('metadata', appid, 'signatures', vc, '28969C09.RSA'),
os.path.join('metadata', appid, 'signatures', vc, '28969C09.SF'),
os.path.join('metadata', appid, 'signatures', vc, 'MANIFEST.MF'),
None,
),
fdroidserver.common.metadata_find_developer_signing_files(appid, vc),
)
def test_auto_install_ndk(self):
"""Test all possible field data types for build.ndk"""
build = fdroidserver.metadata.Build()
none_entry = mock.Mock()
with mock.patch('fdroidserver.common._install_ndk', none_entry):
fdroidserver.common.auto_install_ndk(build)
none_entry.assert_not_called()
empty_list = mock.Mock()
build.ndk = []
with mock.patch('fdroidserver.common._install_ndk', empty_list):
fdroidserver.common.auto_install_ndk(build)
empty_list.assert_not_called()
release_entry = mock.Mock()
build.ndk = 'r21e'
with mock.patch('fdroidserver.common._install_ndk', release_entry):
fdroidserver.common.auto_install_ndk(build)
release_entry.assert_called_once_with('r21e')
revision_entry = mock.Mock()
build.ndk = '21.4.7075529'
with mock.patch('fdroidserver.common._install_ndk', revision_entry):
fdroidserver.common.auto_install_ndk(build)
revision_entry.assert_called_once_with('21.4.7075529')
list_entry = mock.Mock()
calls = []
build.ndk = ['r10e', '11.0.2655954', 'r12b', 'r21e']
for n in build.ndk:
calls.append(mock.call(n))
with mock.patch('fdroidserver.common._install_ndk', list_entry):
fdroidserver.common.auto_install_ndk(build)
list_entry.assert_has_calls(calls)
@unittest.skip("This test downloads and unzips a 1GB file.")
def test_install_ndk(self):
"""NDK r10e is a special case since its missing source.properties"""
sdk_path = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
config = {'sdk_path': sdk_path}
fdroidserver.common.config = config
fdroidserver.common._install_ndk('r10e')
r10e = os.path.join(sdk_path, 'ndk', 'r10e')
self.assertEqual('r10e', fdroidserver.common.get_ndk_version(r10e))
fdroidserver.common.fill_config_defaults(config)
self.assertEqual({'r10e': r10e}, config['ndk_paths'])
def test_fill_config_defaults(self):
"""Test the auto-detection of NDKs installed in standard paths"""
sdk_path = tempfile.mkdtemp(
prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir
)
ndk_bundle = os.path.join(sdk_path, 'ndk-bundle')
os.makedirs(ndk_bundle)
with open(os.path.join(ndk_bundle, 'source.properties'), 'w') as fp:
fp.write('Pkg.Desc = Android NDK\nPkg.Revision = 17.2.4988734\n')
config = {'sdk_path': sdk_path}
fdroidserver.common.fill_config_defaults(config)
self.assertEqual({'r17c': ndk_bundle}, config['ndk_paths'])
r21e = os.path.join(sdk_path, 'ndk', '21.4.7075529')
os.makedirs(r21e)
with open(os.path.join(r21e, 'source.properties'), 'w') as fp:
fp.write('Pkg.Desc = Android NDK\nPkg.Revision = 21.4.7075529\n')
config = {'sdk_path': sdk_path}
fdroidserver.common.fill_config_defaults(config)
self.assertEqual({'r17c': ndk_bundle, 'r21e': r21e}, config['ndk_paths'])
r10e = os.path.join(sdk_path, 'ndk', 'r10e')
os.makedirs(r10e)
with open(os.path.join(r10e, 'RELEASE.TXT'), 'w') as fp:
fp.write('r10e-rc4 (64-bit)\n')
config = {'sdk_path': sdk_path}
fdroidserver.common.fill_config_defaults(config)
self.assertEqual(
{'r10e': r10e, 'r17c': ndk_bundle, 'r21e': r21e}, config['ndk_paths']
)
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(CommonTest))
unittest.main(failfast=False)