mirror of
https://gitlab.com/fdroid/fdroidserver.git
synced 2025-02-20 18:03:01 +01:00

https://gitlab.com/fdroid/fdroidserver/-/merge_requests/1587#note_2273747610 This happened when this test was a shell script as well: https://gitlab.com/fdroid/fdroidserver/-/blob/2.3.5/tests/run-tests#L1244
1554 lines
67 KiB
Python
Executable file
1554 lines
67 KiB
Python
Executable file
import itertools
|
|
import os
|
|
import platform
|
|
import re
|
|
import shlex
|
|
import shutil
|
|
import subprocess
|
|
import threading
|
|
import unittest
|
|
from datetime import datetime, timezone
|
|
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
|
from pathlib import Path
|
|
|
|
from ruamel.yaml import YAML
|
|
|
|
try:
|
|
from androguard.core.bytecodes.apk import get_apkid # androguard <4
|
|
except ModuleNotFoundError:
|
|
from androguard.core.apk import get_apkid
|
|
|
|
from .shared_test_code import mkdir_testfiles
|
|
|
|
# TODO: port generic tests that use index.xml to index-v2 (test that
|
|
# explicitly test index-v0 should still use index.xml)
|
|
|
|
|
|
basedir = Path(__file__).parent
|
|
FILES = basedir
|
|
|
|
try:
|
|
WORKSPACE = Path(os.environ["WORKSPACE"])
|
|
except KeyError:
|
|
WORKSPACE = basedir.parent
|
|
|
|
from fdroidserver import common
|
|
|
|
conf = {"sdk_path": os.getenv("ANDROID_HOME", "")}
|
|
common.find_apksigner(conf)
|
|
USE_APKSIGNER = "apksigner" in conf
|
|
|
|
|
|
class IntegrationTest(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
try:
|
|
cls.fdroid_cmd = shlex.split(os.environ["fdroid"])
|
|
except KeyError:
|
|
cls.fdroid_cmd = [WORKSPACE / "fdroid"]
|
|
|
|
os.environ.update(
|
|
{
|
|
"GIT_AUTHOR_NAME": "Test",
|
|
"GIT_AUTHOR_EMAIL": "no@mail",
|
|
"GIT_COMMITTER_NAME": "Test",
|
|
"GIT_COMMITTER_EMAIL": "no@mail",
|
|
"GIT_ALLOW_PROTOCOL": "file:https",
|
|
}
|
|
)
|
|
|
|
def setUp(self):
|
|
self.prev_cwd = Path()
|
|
self.testdir = mkdir_testfiles(WORKSPACE, self)
|
|
self.tmp_repo_root = self.testdir / "fdroid"
|
|
self.tmp_repo_root.mkdir(parents=True)
|
|
os.chdir(self.tmp_repo_root)
|
|
|
|
def tearDown(self):
|
|
os.chdir(self.prev_cwd)
|
|
shutil.rmtree(self.testdir)
|
|
|
|
def assert_run(self, *args, **kwargs):
|
|
proc = subprocess.run(*args, **kwargs)
|
|
self.assertEqual(proc.returncode, 0)
|
|
return proc
|
|
|
|
def assert_run_fail(self, *args, **kwargs):
|
|
proc = subprocess.run(*args, **kwargs)
|
|
self.assertNotEqual(proc.returncode, 0)
|
|
return proc
|
|
|
|
@staticmethod
|
|
def update_yaml(path, items, replace=False):
|
|
"""Update a .yml file, e.g. config.yml, with the given items."""
|
|
yaml = YAML()
|
|
doc = {}
|
|
if not replace:
|
|
try:
|
|
with open(path) as f:
|
|
doc = yaml.load(f)
|
|
except FileNotFoundError:
|
|
pass
|
|
doc.update(items)
|
|
with open(path, "w") as f:
|
|
yaml.dump(doc, f)
|
|
|
|
@staticmethod
|
|
def remove_lines(path, unwanted_strings):
|
|
"""Remove the lines in the path that contain the unwanted strings."""
|
|
|
|
def contains_unwanted(line, unwanted_strings):
|
|
for str in unwanted_strings:
|
|
if str in line:
|
|
return True
|
|
return False
|
|
|
|
with open(path) as f:
|
|
filtered = [
|
|
line for line in f if not contains_unwanted(line, unwanted_strings)
|
|
]
|
|
|
|
with open(path, "w") as f:
|
|
for line in filtered:
|
|
f.write(line)
|
|
|
|
@staticmethod
|
|
def copy_apks_into_repo():
|
|
def to_skip(name):
|
|
for str in [
|
|
"unaligned",
|
|
"unsigned",
|
|
"badsig",
|
|
"badcert",
|
|
"bad-unicode",
|
|
"janus.apk",
|
|
]:
|
|
if str in name:
|
|
return True
|
|
return False
|
|
|
|
for f in FILES.glob("*.apk"):
|
|
if not to_skip(f.name):
|
|
appid, versionCode, _ignored = get_apkid(f)
|
|
shutil.copy(
|
|
f,
|
|
Path("repo") / common.get_release_apk_filename(appid, versionCode),
|
|
)
|
|
|
|
@staticmethod
|
|
def create_fake_android_home(path):
|
|
(path / "tools").mkdir()
|
|
(path / "platform-tools").mkdir()
|
|
(path / "build-tools/34.0.0").mkdir(parents=True)
|
|
(path / "build-tools/34.0.0/aapt").touch()
|
|
|
|
def fdroid_init_with_prebuilt_keystore(self, keystore_path=FILES / "keystore.jks"):
|
|
self.assert_run(
|
|
self.fdroid_cmd
|
|
+ ["init", "--keystore", keystore_path, "--repo-keyalias", "sova"]
|
|
)
|
|
self.update_yaml(
|
|
"config.yml",
|
|
{
|
|
"keystorepass": "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI=",
|
|
"keypass": "r9aquRHYoI8+dYz6jKrLntQ5/NJNASFBacJh7Jv2BlI=",
|
|
},
|
|
)
|
|
|
|
@unittest.skipUnless(USE_APKSIGNER, "requires apksigner")
|
|
def test_run_process_when_building_and_signing_are_on_separate_machines(self):
|
|
shutil.copy(FILES / "keystore.jks", "keystore.jks")
|
|
self.fdroid_init_with_prebuilt_keystore("keystore.jks")
|
|
self.update_yaml(
|
|
"config.yml",
|
|
{
|
|
"make_current_version_link": True,
|
|
"keydname": "CN=Birdman, OU=Cell, O=Alcatraz, L=Alcatraz, S=California, C=US",
|
|
},
|
|
)
|
|
|
|
Path("metadata").mkdir()
|
|
shutil.copy(FILES / "metadata/info.guardianproject.urzip.yml", "metadata")
|
|
Path("unsigned").mkdir()
|
|
shutil.copy(
|
|
FILES / "urzip-release-unsigned.apk",
|
|
"unsigned/info.guardianproject.urzip_100.apk",
|
|
)
|
|
|
|
self.assert_run(self.fdroid_cmd + ["publish", "--verbose"])
|
|
self.assert_run(self.fdroid_cmd + ["update", "--verbose", "--nosign"])
|
|
self.assert_run(self.fdroid_cmd + ["signindex", "--verbose"])
|
|
|
|
self.assertIn(
|
|
'<application id="info.guardianproject.urzip">',
|
|
Path("repo/index.xml").read_text(),
|
|
)
|
|
self.assertTrue(Path("repo/index.jar").is_file())
|
|
self.assertTrue(Path("repo/index-v1.jar").is_file())
|
|
apkcache = Path("tmp/apkcache.json")
|
|
self.assertTrue(apkcache.is_file())
|
|
self.assertTrue(apkcache.stat().st_size > 0)
|
|
self.assertTrue(Path("urzip.apk").is_symlink())
|
|
|
|
def test_utf8_metadata(self):
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
self.update_yaml(
|
|
"config.yml",
|
|
{
|
|
"repo_description": "获取已安装在您的设备上的应用的",
|
|
"mirrors": ["https://foo.bar/fdroid", "http://secret.onion/fdroid"],
|
|
},
|
|
)
|
|
shutil.copy(FILES / "urzip.apk", "repo")
|
|
shutil.copy(FILES / "bad-unicode-πÇÇ现代通用字-български-عربي1.apk", "repo")
|
|
Path("metadata").mkdir()
|
|
shutil.copy(FILES / "metadata/info.guardianproject.urzip.yml", "metadata")
|
|
|
|
self.assert_run(self.fdroid_cmd + ["readmeta"])
|
|
self.assert_run(self.fdroid_cmd + ["update"])
|
|
|
|
def test_copy_git_import_and_run_fdroid_scanner_on_it(self):
|
|
url = "https://gitlab.com/fdroid/ci-test-app.git"
|
|
Path("metadata").mkdir()
|
|
self.update_yaml(
|
|
"metadata/org.fdroid.ci.test.app.yml",
|
|
{
|
|
"AutoName": "Just A Test",
|
|
"WebSite": None,
|
|
"Builds": [
|
|
{
|
|
"versionName": "0.3",
|
|
"versionCode": 300,
|
|
"commit": "0.3",
|
|
"subdir": "app",
|
|
"gradle": ["yes"],
|
|
}
|
|
],
|
|
"Repo": url,
|
|
"RepoType": "git",
|
|
},
|
|
)
|
|
|
|
self.assert_run(["git", "clone", url, "build/org.fdroid.ci.test.app"])
|
|
self.assert_run(
|
|
self.fdroid_cmd + ["scanner", "org.fdroid.ci.test.app", "--verbose"]
|
|
)
|
|
|
|
@unittest.skipUnless(shutil.which("gpg"), "requires command line gpg")
|
|
def test_copy_repo_generate_java_gpg_keys_update_and_gpgsign(self):
|
|
"""Needs tricks to make gpg-agent run in a test harness."""
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
shutil.copytree(FILES / "repo", "repo", dirs_exist_ok=True)
|
|
for dir in ["config", "metadata"]:
|
|
shutil.copytree(FILES / dir, dir)
|
|
# gpg requires a short path to the socket to talk to gpg-agent
|
|
gnupghome = (WORKSPACE / '.testfiles/gnupghome').resolve()
|
|
shutil.rmtree(gnupghome, ignore_errors=True)
|
|
shutil.copytree(FILES / "gnupghome", gnupghome)
|
|
os.chmod(gnupghome, 0o700)
|
|
self.update_yaml(
|
|
"config.yml",
|
|
{
|
|
"install_list": "org.adaway",
|
|
"uninstall_list": ["com.android.vending", "com.facebook.orca"],
|
|
"gpghome": str(gnupghome),
|
|
"gpgkey": "CE71F7FB",
|
|
"mirrors": [
|
|
"http://foobarfoobarfoobar.onion/fdroid",
|
|
"https://foo.bar/fdroid",
|
|
],
|
|
},
|
|
)
|
|
self.assert_run(
|
|
self.fdroid_cmd + ["update", "--verbose", "--pretty"],
|
|
env=os.environ | {"LC_MESSAGES": "C.UTF-8"},
|
|
)
|
|
index_xml = Path("repo/index.xml").read_text()
|
|
self.assertIn("<application id=", index_xml)
|
|
self.assertIn("<install packageName=", index_xml)
|
|
self.assertIn("<uninstall packageName=", index_xml)
|
|
self.assertTrue(Path("repo/index.jar").is_file())
|
|
self.assertTrue(Path("repo/index-v1.jar").is_file())
|
|
|
|
self.assert_run(self.fdroid_cmd + ["gpgsign", "--verbose"])
|
|
env = os.environ.copy()
|
|
env["GNUPGHOME"] = gnupghome
|
|
self.assert_run(['gpgconf', '--kill', 'gpg-agent'], env=env)
|
|
|
|
self.assertTrue(Path("repo/obb.mainpatch.current_1619.apk.asc").is_file())
|
|
self.assertTrue(
|
|
Path("repo/obb.main.twoversions_1101617_src.tar.gz.asc").is_file()
|
|
)
|
|
self.assertFalse(Path("repo/obb.mainpatch.current_1619.apk.asc.asc").exists())
|
|
self.assertFalse(
|
|
Path("repo/obb.main.twoversions_1101617_src.tar.gz.asc.asc").exists()
|
|
)
|
|
self.assertFalse(Path("repo/index.xml.asc").exists())
|
|
|
|
index_v1_json = Path("repo/index-v1.json").read_text()
|
|
v0_timestamp = re.search(r'timestamp="(\d+)"', index_xml).group(1)
|
|
v1_timestamp = re.search(r'"timestamp": (\d+)', index_v1_json).group(1)[:-3]
|
|
self.assertEqual(v0_timestamp, v1_timestamp)
|
|
|
|
# we can't easily reproduce the timestamps for things, so just hardcode them
|
|
index_xml = re.sub(r'timestamp="\d+"', 'timestamp="1676634233"', index_xml)
|
|
self.assertEqual((FILES / "repo/index.xml").read_text(), index_xml)
|
|
index_v1_json = re.sub(
|
|
r'"timestamp": (\d+)', '"timestamp": 1676634233000', index_v1_json
|
|
)
|
|
self.assertEqual((FILES / "repo/index-v1.json").read_text(), index_v1_json)
|
|
|
|
expected_index_v2_json = (FILES / "repo/index-v2.json").read_text()
|
|
expected_index_v2_json = re.sub(
|
|
r',\s*"ipfsCIDv1":\s*"[\w]+"', "", expected_index_v2_json
|
|
)
|
|
|
|
index_v2_json = Path("repo/index-v2.json").read_text()
|
|
index_v2_json = re.sub(
|
|
r'"timestamp": (\d+)', '"timestamp": 1676634233000', index_v2_json
|
|
)
|
|
index_v2_json = re.sub(r',\s*"ipfsCIDv1":\s*"[\w]+"', "", index_v2_json)
|
|
self.assertEqual(index_v2_json, expected_index_v2_json)
|
|
|
|
def test_moving_lots_of_apks_to_the_archive(self):
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
Path("metadata").mkdir()
|
|
for path in (FILES / "metadata").glob("*.yml"):
|
|
shutil.copy(path, "metadata")
|
|
self.update_yaml(
|
|
"metadata/info.guardianproject.urzip.yml",
|
|
{"Summary": "good test version of urzip"},
|
|
replace=True,
|
|
)
|
|
self.update_yaml(
|
|
"metadata/org.bitbucket.tickytacky.mirrormirror.yml",
|
|
{"Summary": "good MD5 sig, which is disabled algorithm"},
|
|
replace=True,
|
|
)
|
|
for f in Path("metadata").glob("*.yml"):
|
|
self.remove_lines(f, ["ArchivePolicy:"])
|
|
for f in itertools.chain(
|
|
FILES.glob("urzip.apk"),
|
|
FILES.glob("org.bitbucket.tickytacky.mirrormirror_[0-9].apk"),
|
|
FILES.glob("repo/com.politedroid_[0-9].apk"),
|
|
FILES.glob("repo/obb.main.twoversions_110161[357].apk"),
|
|
):
|
|
shutil.copy(f, "repo")
|
|
self.update_yaml("config.yml", {"archive_older": 3})
|
|
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
with open("archive/index.xml") as f:
|
|
archive_cnt = sum(1 for line in f if "<package>" in line)
|
|
with open("repo/index.xml") as f:
|
|
repo_cnt = sum(1 for line in f if "<package>" in line)
|
|
if USE_APKSIGNER:
|
|
self.assertEqual(archive_cnt, 2)
|
|
self.assertEqual(repo_cnt, 10)
|
|
else:
|
|
# This will fail when jarsigner allows MD5 for APK signatures
|
|
self.assertEqual(archive_cnt, 5)
|
|
self.assertEqual(repo_cnt, 7)
|
|
|
|
@unittest.skipIf(USE_APKSIGNER, "runs only without apksigner")
|
|
def test_per_app_archive_policy(self):
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
Path("metadata").mkdir()
|
|
shutil.copy(FILES / "metadata/com.politedroid.yml", "metadata")
|
|
for f in FILES.glob("repo/com.politedroid_[0-9].apk"):
|
|
shutil.copy(f, "repo")
|
|
self.update_yaml("config.yml", {"archive_older": 3})
|
|
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
repo = Path("repo/index.xml").read_text()
|
|
repo_cnt = sum(1 for line in repo.splitlines() if "<package>" in line)
|
|
archive = Path("archive/index.xml").read_text()
|
|
archive_cnt = sum(1 for line in archive.splitlines() if "<package>" in line)
|
|
self.assertEqual(repo_cnt, 4)
|
|
self.assertEqual(archive_cnt, 0)
|
|
self.assertIn("com.politedroid_3.apk", repo)
|
|
self.assertIn("com.politedroid_4.apk", repo)
|
|
self.assertIn("com.politedroid_5.apk", repo)
|
|
self.assertIn("com.politedroid_6.apk", repo)
|
|
self.assertTrue(Path("repo/com.politedroid_3.apk").is_file())
|
|
self.assertTrue(Path("repo/com.politedroid_4.apk").is_file())
|
|
self.assertTrue(Path("repo/com.politedroid_5.apk").is_file())
|
|
self.assertTrue(Path("repo/com.politedroid_6.apk").is_file())
|
|
|
|
# enable one app in the repo
|
|
self.update_yaml("metadata/com.politedroid.yml", {"ArchivePolicy": 1})
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
repo = Path("repo/index.xml").read_text()
|
|
repo_cnt = sum(1 for line in repo.splitlines() if "<package>" in line)
|
|
archive = Path("archive/index.xml").read_text()
|
|
archive_cnt = sum(1 for line in archive.splitlines() if "<package>" in line)
|
|
self.assertEqual(repo_cnt, 1)
|
|
self.assertEqual(archive_cnt, 3)
|
|
self.assertIn("com.politedroid_6.apk", repo)
|
|
self.assertIn("com.politedroid_3.apk", archive)
|
|
self.assertIn("com.politedroid_4.apk", archive)
|
|
self.assertIn("com.politedroid_5.apk", archive)
|
|
self.assertTrue(Path("repo/com.politedroid_6.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_4.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_5.apk").is_file())
|
|
|
|
# remove all apps from the repo
|
|
self.update_yaml("metadata/com.politedroid.yml", {"ArchivePolicy": 0})
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
repo = Path("repo/index.xml").read_text()
|
|
repo_cnt = sum(1 for line in repo.splitlines() if "<package>" in line)
|
|
archive = Path("archive/index.xml").read_text()
|
|
archive_cnt = sum(1 for line in archive.splitlines() if "<package>" in line)
|
|
self.assertEqual(repo_cnt, 0)
|
|
self.assertEqual(archive_cnt, 4)
|
|
self.assertIn("com.politedroid_3.apk", archive)
|
|
self.assertIn("com.politedroid_4.apk", archive)
|
|
self.assertIn("com.politedroid_5.apk", archive)
|
|
self.assertIn("com.politedroid_6.apk", archive)
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_4.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_5.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_6.apk").is_file())
|
|
self.assertFalse(Path("repo/com.politedroid_6.apk").exists())
|
|
|
|
# move back one from archive to the repo
|
|
self.update_yaml("metadata/com.politedroid.yml", {"ArchivePolicy": 1})
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
repo = Path("repo/index.xml").read_text()
|
|
repo_cnt = sum(1 for line in repo.splitlines() if "<package>" in line)
|
|
archive = Path("archive/index.xml").read_text()
|
|
archive_cnt = sum(1 for line in archive.splitlines() if "<package>" in line)
|
|
self.assertEqual(repo_cnt, 1)
|
|
self.assertEqual(archive_cnt, 3)
|
|
self.assertIn("com.politedroid_6.apk", repo)
|
|
self.assertIn("com.politedroid_3.apk", archive)
|
|
self.assertIn("com.politedroid_4.apk", archive)
|
|
self.assertIn("com.politedroid_5.apk", archive)
|
|
self.assertTrue(Path("repo/com.politedroid_6.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_4.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_5.apk").is_file())
|
|
self.assertFalse(Path("archive/com.politedroid_6.apk").exists())
|
|
|
|
# set an earlier version as CVC and test that it's the only one not archived
|
|
self.update_yaml("metadata/com.politedroid.yml", {"CurrentVersionCode": 5})
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
repo = Path("repo/index.xml").read_text()
|
|
repo_cnt = sum(1 for line in repo.splitlines() if "<package>" in line)
|
|
archive = Path("archive/index.xml").read_text()
|
|
archive_cnt = sum(1 for line in archive.splitlines() if "<package>" in line)
|
|
self.assertEqual(repo_cnt, 1)
|
|
self.assertEqual(archive_cnt, 3)
|
|
self.assertIn("com.politedroid_5.apk", repo)
|
|
self.assertIn("com.politedroid_3.apk", archive)
|
|
self.assertIn("com.politedroid_4.apk", archive)
|
|
self.assertIn("com.politedroid_6.apk", archive)
|
|
self.assertTrue(Path("repo/com.politedroid_5.apk").is_file())
|
|
self.assertFalse(Path("repo/com.politedroid_6.apk").exists())
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_4.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_6.apk").is_file())
|
|
|
|
def test_moving_old_apks_to_and_from_the_archive(self):
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
Path("metadata").mkdir()
|
|
shutil.copy(FILES / "metadata/com.politedroid.yml", "metadata")
|
|
self.remove_lines("metadata/com.politedroid.yml", ["ArchivePolicy:"])
|
|
for f in FILES.glob("repo/com.politedroid_[0-9].apk"):
|
|
shutil.copy(f, "repo")
|
|
self.update_yaml("config.yml", {"archive_older": 3})
|
|
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
repo = Path("repo/index.xml").read_text()
|
|
repo_cnt = sum(1 for line in repo.splitlines() if "<package>" in line)
|
|
self.assertEqual(repo_cnt, 3)
|
|
self.assertIn("com.politedroid_4.apk", repo)
|
|
self.assertIn("com.politedroid_5.apk", repo)
|
|
self.assertIn("com.politedroid_6.apk", repo)
|
|
self.assertTrue(Path("repo/com.politedroid_4.apk").is_file())
|
|
self.assertTrue(Path("repo/com.politedroid_5.apk").is_file())
|
|
self.assertTrue(Path("repo/com.politedroid_6.apk").is_file())
|
|
archive = Path("archive/index.xml").read_text()
|
|
archive_cnt = sum(1 for line in archive.splitlines() if "<package>" in line)
|
|
self.assertEqual(archive_cnt, 1)
|
|
self.assertIn("com.politedroid_3.apk", archive)
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
|
|
self.update_yaml("config.yml", {"archive_older": 1})
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
repo = Path("repo/index.xml").read_text()
|
|
repo_cnt = sum(1 for line in repo.splitlines() if "<package>" in line)
|
|
self.assertEqual(repo_cnt, 1)
|
|
self.assertIn("com.politedroid_6.apk", repo)
|
|
self.assertTrue(Path("repo/com.politedroid_6.apk").is_file())
|
|
archive = Path("archive/index.xml").read_text()
|
|
archive_cnt = sum(1 for line in archive.splitlines() if "<package>" in line)
|
|
self.assertEqual(archive_cnt, 3)
|
|
self.assertIn("com.politedroid_3.apk", archive)
|
|
self.assertIn("com.politedroid_4.apk", archive)
|
|
self.assertIn("com.politedroid_5.apk", archive)
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_4.apk").is_file())
|
|
self.assertTrue(Path("archive/com.politedroid_5.apk").is_file())
|
|
|
|
# disabling deletes from the archive
|
|
metadata_path = Path("metadata/com.politedroid.yml")
|
|
metadata = metadata_path.read_text()
|
|
metadata = re.sub(
|
|
"versionCode: 4", "versionCode: 4\n disable: testing deletion", metadata
|
|
)
|
|
metadata_path.write_text(metadata)
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
repo = Path("repo/index.xml").read_text()
|
|
repo_cnt = sum(1 for line in repo.splitlines() if "<package>" in line)
|
|
self.assertEqual(repo_cnt, 1)
|
|
self.assertIn("com.politedroid_6.apk", repo)
|
|
self.assertTrue(Path("repo/com.politedroid_6.apk").is_file())
|
|
archive = Path("archive/index.xml").read_text()
|
|
archive_cnt = sum(1 for line in archive.splitlines() if "<package>" in line)
|
|
self.assertEqual(archive_cnt, 2)
|
|
self.assertIn("com.politedroid_3.apk", archive)
|
|
self.assertNotIn("com.politedroid_4.apk", archive)
|
|
self.assertIn("com.politedroid_5.apk", archive)
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
self.assertFalse(Path("archive/com.politedroid_4.apk").exists())
|
|
self.assertTrue(Path("archive/com.politedroid_5.apk").is_file())
|
|
|
|
# disabling deletes from the repo, and promotes one from the archive
|
|
metadata = re.sub(
|
|
"versionCode: 6", "versionCode: 6\n disable: testing deletion", metadata
|
|
)
|
|
metadata_path.write_text(metadata)
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
repo = Path("repo/index.xml").read_text()
|
|
repo_cnt = sum(1 for line in repo.splitlines() if "<package>" in line)
|
|
self.assertEqual(repo_cnt, 1)
|
|
self.assertIn("com.politedroid_5.apk", repo)
|
|
self.assertNotIn("com.politedroid_6.apk", repo)
|
|
self.assertTrue(Path("repo/com.politedroid_5.apk").is_file())
|
|
self.assertFalse(Path("repo/com.politedroid_6.apk").exists())
|
|
archive = Path("archive/index.xml").read_text()
|
|
archive_cnt = sum(1 for line in archive.splitlines() if "<package>" in line)
|
|
self.assertEqual(archive_cnt, 1)
|
|
self.assertIn("com.politedroid_3.apk", archive)
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
self.assertFalse(Path("archive/com.politedroid_6.apk").exists())
|
|
|
|
def test_that_verify_can_succeed_and_fail(self):
|
|
Path("tmp").mkdir()
|
|
Path("unsigned").mkdir()
|
|
shutil.copy(FILES / "repo/com.politedroid_6.apk", "tmp")
|
|
shutil.copy(FILES / "repo/com.politedroid_6.apk", "unsigned")
|
|
self.assert_run(
|
|
self.fdroid_cmd
|
|
+ ["verify", "--reuse-remote-apk", "--verbose", "com.politedroid"]
|
|
)
|
|
# force a fail
|
|
shutil.copy(
|
|
FILES / "repo/com.politedroid_5.apk", "unsigned/com.politedroid_6.apk"
|
|
)
|
|
self.assert_run_fail(
|
|
self.fdroid_cmd
|
|
+ ["verify", "--reuse-remote-apk", "--verbose", "com.politedroid"]
|
|
)
|
|
|
|
def test_allowing_disabled_signatures_in_repo_and_archive(self):
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
self.update_yaml(
|
|
"config.yml", {"allow_disabled_algorithms": True, "archive_older": 3}
|
|
)
|
|
Path("metadata").mkdir()
|
|
shutil.copy(FILES / "metadata/com.politedroid.yml", "metadata")
|
|
self.update_yaml(
|
|
"metadata/info.guardianproject.urzip.yml",
|
|
{"Summary": "good test version of urzip"},
|
|
replace=True,
|
|
)
|
|
self.update_yaml(
|
|
"metadata/org.bitbucket.tickytacky.mirrormirror.yml",
|
|
{"Summary": "good MD5 sig, disabled algorithm"},
|
|
replace=True,
|
|
)
|
|
for f in Path("metadata").glob("*.yml"):
|
|
self.remove_lines(f, ["ArchivePolicy:"])
|
|
for f in itertools.chain(
|
|
FILES.glob("urzip-badsig.apk"),
|
|
FILES.glob("org.bitbucket.tickytacky.mirrormirror_[0-9].apk"),
|
|
FILES.glob("repo/com.politedroid_[0-9].apk"),
|
|
):
|
|
shutil.copy(f, "repo")
|
|
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
repo = Path("repo/index.xml").read_text()
|
|
repo_cnt = sum(1 for line in repo.splitlines() if "<package>" in line)
|
|
archive = Path("archive/index.xml").read_text()
|
|
archive_cnt = sum(1 for line in archive.splitlines() if "<package>" in line)
|
|
self.assertEqual(repo_cnt, 6)
|
|
self.assertEqual(archive_cnt, 2)
|
|
self.assertIn("com.politedroid_4.apk", repo)
|
|
self.assertIn("com.politedroid_5.apk", repo)
|
|
self.assertIn("com.politedroid_6.apk", repo)
|
|
self.assertIn("com.politedroid_3.apk", archive)
|
|
self.assertIn("org.bitbucket.tickytacky.mirrormirror_2.apk", repo)
|
|
self.assertIn("org.bitbucket.tickytacky.mirrormirror_3.apk", repo)
|
|
self.assertIn("org.bitbucket.tickytacky.mirrormirror_4.apk", repo)
|
|
self.assertIn("org.bitbucket.tickytacky.mirrormirror_1.apk", archive)
|
|
self.assertNotIn("urzip-badsig.apk", repo)
|
|
self.assertNotIn("urzip-badsig.apk", archive)
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
self.assertTrue(Path("repo/com.politedroid_4.apk").is_file())
|
|
self.assertTrue(Path("repo/com.politedroid_5.apk").is_file())
|
|
self.assertTrue(Path("repo/com.politedroid_6.apk").is_file())
|
|
self.assertTrue(
|
|
Path("archive/org.bitbucket.tickytacky.mirrormirror_1.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
Path("repo/org.bitbucket.tickytacky.mirrormirror_2.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
Path("repo/org.bitbucket.tickytacky.mirrormirror_3.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
Path("repo/org.bitbucket.tickytacky.mirrormirror_4.apk").is_file()
|
|
)
|
|
self.assertTrue(Path("archive/urzip-badsig.apk").is_file())
|
|
|
|
if not USE_APKSIGNER:
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
repo = Path("repo/index.xml").read_text()
|
|
repo_cnt = sum(1 for line in repo.splitlines() if "<package>" in line)
|
|
archive = Path("archive/index.xml").read_text()
|
|
archive_cnt = sum(1 for line in archive.splitlines() if "<package>" in line)
|
|
self.assertEqual(repo_cnt, 3)
|
|
self.assertEqual(archive_cnt, 5)
|
|
self.assertIn("com.politedroid_4.apk", repo)
|
|
self.assertIn("com.politedroid_5.apk", repo)
|
|
self.assertIn("com.politedroid_6.apk", repo)
|
|
self.assertNotIn("urzip-badsig.apk", repo)
|
|
self.assertIn("org.bitbucket.tickytacky.mirrormirror_1.apk", archive)
|
|
self.assertIn("org.bitbucket.tickytacky.mirrormirror_2.apk", archive)
|
|
self.assertIn("org.bitbucket.tickytacky.mirrormirror_3.apk", archive)
|
|
self.assertIn("org.bitbucket.tickytacky.mirrormirror_4.apk", archive)
|
|
self.assertIn("com.politedroid_3.apk", archive)
|
|
self.assertNotIn("urzip-badsig.apk", archive)
|
|
self.assertTrue(Path("repo/com.politedroid_4.apk").is_file())
|
|
self.assertTrue(Path("repo/com.politedroid_5.apk").is_file())
|
|
self.assertTrue(Path("repo/com.politedroid_6.apk").is_file())
|
|
self.assertTrue(
|
|
Path("archive/org.bitbucket.tickytacky.mirrormirror_1.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
Path("archive/org.bitbucket.tickytacky.mirrormirror_2.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
Path("archive/org.bitbucket.tickytacky.mirrormirror_3.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
Path("archive/org.bitbucket.tickytacky.mirrormirror_4.apk").is_file()
|
|
)
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
self.assertTrue(Path("archive/urzip-badsig.apk").is_file())
|
|
|
|
# test unarchiving when disabled_algorithms are allowed again
|
|
self.update_yaml("config.yml", {"allow_disabled_algorithms": True})
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
with open("archive/index.xml") as f:
|
|
archive_cnt = sum(1 for line in f if "<package>" in line)
|
|
with open("repo/index.xml") as f:
|
|
repo_cnt = sum(1 for line in f if "<package>" in line)
|
|
self.assertEqual(repo_cnt, 6)
|
|
self.assertEqual(archive_cnt, 2)
|
|
self.assertIn("com.politedroid_4.apk", repo)
|
|
self.assertIn("com.politedroid_5.apk", repo)
|
|
self.assertIn("com.politedroid_6.apk", repo)
|
|
self.assertIn("org.bitbucket.tickytacky.mirrormirror_2.apk", repo)
|
|
self.assertIn("org.bitbucket.tickytacky.mirrormirror_3.apk", repo)
|
|
self.assertIn("org.bitbucket.tickytacky.mirrormirror_4.apk", repo)
|
|
self.assertNotIn("urzip-badsig.apk", repo)
|
|
self.assertIn("com.politedroid_3.apk", archive)
|
|
self.assertIn("org.bitbucket.tickytacky.mirrormirror_1.apk", archive)
|
|
self.assertNotIn("urzip-badsig.apk", archive)
|
|
self.assertTrue(Path("repo/com.politedroid_4.apk").is_file())
|
|
self.assertTrue(Path("repo/com.politedroid_5.apk").is_file())
|
|
self.assertTrue(Path("repo/com.politedroid_6.apk").is_file())
|
|
self.assertTrue(
|
|
Path("repo/org.bitbucket.tickytacky.mirrormirror_2.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
Path("repo/org.bitbucket.tickytacky.mirrormirror_3.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
Path("repo/org.bitbucket.tickytacky.mirrormirror_4.apk").is_file()
|
|
)
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
self.assertTrue(
|
|
Path("archive/org.bitbucket.tickytacky.mirrormirror_1.apk").is_file()
|
|
)
|
|
self.assertTrue(Path("archive/urzip-badsig.apk").is_file())
|
|
|
|
def test_rename_apks_with_fdroid_update_rename_apks_opt_nosign_opt_for_speed(self):
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
self.update_yaml(
|
|
"config.yml",
|
|
{
|
|
"keydname": "CN=Birdman, OU=Cell, O=Alcatraz, L=Alcatraz, S=California, C=US"
|
|
},
|
|
)
|
|
Path("metadata").mkdir()
|
|
shutil.copy(FILES / "metadata/info.guardianproject.urzip.yml", "metadata")
|
|
shutil.copy(
|
|
FILES / "urzip.apk",
|
|
"repo/asdfiuhk urzip-πÇÇπÇÇ现代汉语通用字-български-عربي1234 ö.apk",
|
|
)
|
|
self.assert_run(
|
|
self.fdroid_cmd + ["update", "--rename-apks", "--pretty", "--nosign"]
|
|
)
|
|
self.assertTrue(Path("repo/info.guardianproject.urzip_100.apk").is_file())
|
|
index_xml = Path("repo/index.xml").read_text()
|
|
index_v1_json = Path("repo/index-v1.json").read_text()
|
|
self.assertIn("info.guardianproject.urzip_100.apk", index_v1_json)
|
|
self.assertIn("info.guardianproject.urzip_100.apk", index_xml)
|
|
|
|
shutil.copy(FILES / "urzip-release.apk", "repo")
|
|
self.assert_run(
|
|
self.fdroid_cmd + ["update", "--rename-apks", "--pretty", "--nosign"]
|
|
)
|
|
self.assertTrue(Path("repo/info.guardianproject.urzip_100.apk").is_file())
|
|
self.assertTrue(
|
|
Path("repo/info.guardianproject.urzip_100_b4964fd.apk").is_file()
|
|
)
|
|
index_xml = Path("repo/index.xml").read_text()
|
|
index_v1_json = Path("repo/index-v1.json").read_text()
|
|
self.assertIn("info.guardianproject.urzip_100.apk", index_v1_json)
|
|
self.assertIn("info.guardianproject.urzip_100.apk", index_xml)
|
|
self.assertIn("info.guardianproject.urzip_100_b4964fd.apk", index_v1_json)
|
|
self.assertNotIn("info.guardianproject.urzip_100_b4964fd.apk", index_xml)
|
|
|
|
shutil.copy(FILES / "urzip-release.apk", "repo")
|
|
self.assert_run(
|
|
self.fdroid_cmd + ["update", "--rename-apks", "--pretty", "--nosign"]
|
|
)
|
|
self.assertTrue(Path("repo/info.guardianproject.urzip_100.apk").is_file())
|
|
self.assertTrue(
|
|
Path("repo/info.guardianproject.urzip_100_b4964fd.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
Path("duplicates/repo/info.guardianproject.urzip_100_b4964fd.apk").is_file()
|
|
)
|
|
index_xml = Path("repo/index.xml").read_text()
|
|
index_v1_json = Path("repo/index-v1.json").read_text()
|
|
self.assertIn("info.guardianproject.urzip_100.apk", index_v1_json)
|
|
self.assertIn("info.guardianproject.urzip_100.apk", index_xml)
|
|
self.assertIn("info.guardianproject.urzip_100_b4964fd.apk", index_v1_json)
|
|
self.assertNotIn("info.guardianproject.urzip_100_b4964fd.apk", index_xml)
|
|
|
|
def test_for_added_date_being_set_correctly_for_repo_and_archive(self):
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
self.update_yaml("config.yml", {"archive_older": 3})
|
|
Path("metadata").mkdir()
|
|
Path("archive").mkdir()
|
|
Path("stats").mkdir()
|
|
shutil.copy(FILES / "repo/com.politedroid_6.apk", "repo")
|
|
shutil.copy(FILES / "repo/index-v2.json", "repo")
|
|
shutil.copy(FILES / "repo/com.politedroid_5.apk", "archive")
|
|
shutil.copy(FILES / "metadata/com.politedroid.yml", "metadata")
|
|
|
|
# TODO: the timestamp of the oldest apk in the file should be used, even
|
|
# if that doesn't exist anymore
|
|
self.update_yaml("metadata/com.politedroid.yml", {"ArchivePolicy": 1})
|
|
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
timestamp = int(datetime(2017, 6, 23, tzinfo=timezone.utc).timestamp()) * 1000
|
|
index_v1_json = Path("repo/index-v1.json").read_text()
|
|
self.assertIn(f'"added": {timestamp}', index_v1_json)
|
|
# the archive will have the added timestamp for the app and for the apk,
|
|
# both need to be there
|
|
with open("archive/index-v1.json") as f:
|
|
count = sum(1 for line in f if f'"added": {timestamp}' in line)
|
|
self.assertEqual(count, 2)
|
|
|
|
def test_whatsnew_from_fastlane_without_cvc_set(self):
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
Path("metadata/com.politedroid/en-US/changelogs").mkdir(parents=True)
|
|
shutil.copy(FILES / "repo/com.politedroid_6.apk", "repo")
|
|
shutil.copy(FILES / "metadata/com.politedroid.yml", "metadata")
|
|
self.remove_lines("metadata/com.politedroid.yml", ["CurrentVersion:"])
|
|
Path("metadata/com.politedroid/en-US/changelogs/6.txt").write_text(
|
|
"whatsnew test"
|
|
)
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty", "--nosign"])
|
|
index_v1_json = Path("repo/index-v1.json").read_text()
|
|
self.assertIn("whatsnew test", index_v1_json)
|
|
|
|
def test_metadata_checks(self):
|
|
Path("repo").mkdir()
|
|
shutil.copy(FILES / "urzip.apk", "repo")
|
|
# this should fail because there is no metadata
|
|
self.assert_run_fail(self.fdroid_cmd + ["build"])
|
|
Path("metadata").mkdir()
|
|
shutil.copy(FILES / "metadata/org.smssecure.smssecure.yml", "metadata")
|
|
self.assert_run(self.fdroid_cmd + ["readmeta"])
|
|
|
|
def test_ensure_commands_that_dont_need_the_jdk_work_without_a_jdk_configured(self):
|
|
Path("repo").mkdir()
|
|
Path("metadata").mkdir()
|
|
self.update_yaml(
|
|
"metadata/fake.yml",
|
|
{
|
|
"License": "GPL-2.0-only",
|
|
"Summary": "Yup still fake",
|
|
"Categories": ["Internet"],
|
|
"Description": "this is fake",
|
|
},
|
|
)
|
|
# fake that no JDKs are available
|
|
self.update_yaml(
|
|
"config.yml", {"categories": ["Internet"], "java_paths": {}}, replace=True
|
|
)
|
|
local_copy_dir = self.testdir / "local_copy_dir/fdroid"
|
|
(local_copy_dir / "repo").mkdir(parents=True)
|
|
self.update_yaml(
|
|
"config.yml", {"local_copy_dir": str(local_copy_dir.resolve())}
|
|
)
|
|
|
|
subprocess.run(self.fdroid_cmd + ["checkupdates", "--allow-dirty"])
|
|
if shutil.which("gpg"):
|
|
self.assert_run(self.fdroid_cmd + ["gpgsign"])
|
|
self.assert_run(self.fdroid_cmd + ["lint"])
|
|
self.assert_run(self.fdroid_cmd + ["readmeta"])
|
|
self.assert_run(self.fdroid_cmd + ["rewritemeta", "fake"])
|
|
self.assert_run(self.fdroid_cmd + ["deploy"])
|
|
self.assert_run(self.fdroid_cmd + ["scanner"])
|
|
|
|
# run these to get their output, but the are not setup, so don't fail
|
|
subprocess.run(self.fdroid_cmd + ["build"])
|
|
subprocess.run(self.fdroid_cmd + ["import"])
|
|
subprocess.run(self.fdroid_cmd + ["install", "-n"])
|
|
|
|
def test_config_checks_of_local_copy_dir(self):
|
|
self.assert_run(self.fdroid_cmd + ["init"])
|
|
self.assert_run(self.fdroid_cmd + ["update", "--create-metadata", "--verbose"])
|
|
self.assert_run(self.fdroid_cmd + ["readmeta"])
|
|
local_copy_dir = (self.testdir / "local_copy_dir/fdroid").resolve()
|
|
local_copy_dir.mkdir(parents=True)
|
|
self.assert_run(
|
|
self.fdroid_cmd + ["deploy", "--local-copy-dir", local_copy_dir]
|
|
)
|
|
self.assert_run(
|
|
self.fdroid_cmd
|
|
+ ["deploy", "--local-copy-dir", local_copy_dir, "--verbose"]
|
|
)
|
|
|
|
# this should fail because thisisnotanabsolutepath is not an absolute path
|
|
self.assert_run_fail(
|
|
self.fdroid_cmd + ["deploy", "--local-copy-dir", "thisisnotanabsolutepath"]
|
|
)
|
|
# this should fail because the path doesn't end with "fdroid"
|
|
self.assert_run_fail(
|
|
self.fdroid_cmd
|
|
+ [
|
|
"deploy",
|
|
"--local-copy-dir",
|
|
"/tmp/IReallyDoubtThisPathExistsasdfasdf", # nosec B108
|
|
]
|
|
)
|
|
# this should fail because the dirname path does not exist
|
|
self.assert_run_fail(
|
|
self.fdroid_cmd
|
|
+ [
|
|
"deploy",
|
|
"--local-copy-dir",
|
|
"/tmp/IReallyDoubtThisPathExistsasdfasdf/fdroid", # nosec B108
|
|
]
|
|
)
|
|
|
|
def test_setup_a_new_repo_from_scratch_using_android_home_and_do_a_local_sync(self):
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
self.copy_apks_into_repo()
|
|
self.assert_run(self.fdroid_cmd + ["update", "--create-metadata", "--verbose"])
|
|
self.assert_run(self.fdroid_cmd + ["readmeta"])
|
|
self.assertIn("<application id=", Path("repo/index.xml").read_text())
|
|
|
|
local_copy_dir = self.testdir / "local_copy_dir/fdroid"
|
|
local_copy_dir.parent.mkdir()
|
|
self.assert_run(
|
|
self.fdroid_cmd + ["deploy", "--local-copy-dir", local_copy_dir]
|
|
)
|
|
|
|
new_tmp_repo = self.testdir / "new_repo"
|
|
new_tmp_repo.mkdir()
|
|
os.chdir(new_tmp_repo)
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
self.update_yaml("config.yml", {"sync_from_local_copy_dir": True})
|
|
self.assert_run(
|
|
self.fdroid_cmd + ["deploy", "--local-copy-dir", local_copy_dir]
|
|
)
|
|
|
|
def test_check_that_android_home_opt_fails_when_dir_does_not_exist_or_is_not_a_dir(
|
|
self,
|
|
):
|
|
# this should fail because /opt/fakeandroidhome does not exist
|
|
self.assert_run_fail(
|
|
self.fdroid_cmd
|
|
+ [
|
|
"init",
|
|
"--keystore",
|
|
"keystore.p12",
|
|
"--android-home",
|
|
"/opt/fakeandroidhome",
|
|
]
|
|
)
|
|
Path("test_file").touch()
|
|
# this should fail because test_file is not a directory
|
|
self.assert_run_fail(
|
|
self.fdroid_cmd
|
|
+ ["init", "--keystore", "keystore.p12", "--android-home", "test_file"]
|
|
)
|
|
|
|
def test_check_that_fake_android_home_passes_fdroid_init(self):
|
|
android_home = self.testdir / "android-sdk"
|
|
android_home.mkdir()
|
|
self.create_fake_android_home(android_home)
|
|
self.assert_run(
|
|
self.fdroid_cmd
|
|
+ ["init", "--keystore", "keystore.p12", "--android-home", android_home]
|
|
)
|
|
|
|
@unittest.skip
|
|
def test_check_that_fdroid_init_fails_when_build_tools_cannot_be_found(self):
|
|
fake_android_home = self.testdir / "android-sdk"
|
|
fake_android_home.mkdir()
|
|
self.create_fake_android_home(fake_android_home)
|
|
(fake_android_home / "build-tools/34.0.0/aapt").unlink()
|
|
self.assert_run_fail(
|
|
self.fdroid_cmd
|
|
+ [
|
|
"init",
|
|
"--keystore",
|
|
"keystore.p12",
|
|
"--android-home",
|
|
fake_android_home,
|
|
]
|
|
)
|
|
|
|
def check_that_android_home_opt_overrides_android_home_env_var(self):
|
|
fake_android_home = self.testdir / "android-sdk"
|
|
fake_android_home.mkdir()
|
|
self.create_fake_android_home(fake_android_home)
|
|
self.assert_run(
|
|
self.fdroid_cmd
|
|
+ [
|
|
"init",
|
|
"--keystore",
|
|
"keystore.p12",
|
|
"--android-home",
|
|
fake_android_home,
|
|
]
|
|
)
|
|
# the value set in --android-home should override $ANDROID_HOME
|
|
self.assertIn(str(fake_android_home), Path("config.yml").read_text())
|
|
|
|
@unittest.skipUnless(
|
|
"ANDROID_HOME" in os.environ, "runs only with ANDROID_HOME set"
|
|
)
|
|
def setup_a_new_repo_from_scratch_with_keystore_and_android_home_opt_set_on_cmd_line(
|
|
self,
|
|
):
|
|
"""Test with broken setup in ANDROID_HOME.
|
|
|
|
In this case, ANDROID_HOME is set to a fake, non-working
|
|
version that will be detected by fdroid as an Android SDK
|
|
install. It should use the path set by --android-home over the
|
|
one in ANDROID_HOME, therefore if it uses the one in
|
|
ANDROID_HOME, it won't work because it is a fake one. Only
|
|
--android-home provides a working one.
|
|
|
|
"""
|
|
real_android_home = os.environ["ANDROID_HOME"]
|
|
fake_android_home = self.testdir / "android-sdk"
|
|
fake_android_home.mkdir()
|
|
env = os.environ.copy()
|
|
env["ANDROID_HOME"] = str(fake_android_home)
|
|
self.assert_run(
|
|
self.fdroid_cmd
|
|
+ [
|
|
"init",
|
|
"--keystore",
|
|
"keystore.p12",
|
|
"--android-home",
|
|
real_android_home,
|
|
"--no-prompt",
|
|
],
|
|
env=env,
|
|
)
|
|
self.assertTrue(Path("keystore.p12").is_file())
|
|
self.copy_apks_into_repo()
|
|
self.assert_run(
|
|
self.fdroid_cmd + ["update", "--create-metadata", "--verbose"], env=env
|
|
)
|
|
self.assert_run(self.fdroid_cmd + ["readmeta"], env=env)
|
|
self.assertIn("<application id=", Path("repo/index.xml").read_text())
|
|
self.assertTrue(Path("repo/index.jar").is_file())
|
|
self.assertTrue(Path("repo/index-v1.jar").is_file())
|
|
apkcache = Path("tmp/apkcache.json")
|
|
self.assertTrue(apkcache.is_file())
|
|
self.assertTrue(apkcache.stat().st_size > 0)
|
|
|
|
def test_check_duplicate_files_are_properly_handled_by_fdroid_update(self):
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
Path("metadata").mkdir()
|
|
shutil.copy(FILES / "metadata/obb.mainpatch.current.yml", "metadata")
|
|
shutil.copy(FILES / "repo/obb.mainpatch.current_1619.apk", "repo")
|
|
shutil.copy(
|
|
FILES / "repo/obb.mainpatch.current_1619_another-release-key.apk", "repo"
|
|
)
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty"])
|
|
index_xml = Path("repo/index.xml").read_text()
|
|
index_v1_json = Path("repo/index-v1.json").read_text()
|
|
self.assertNotIn(
|
|
"obb.mainpatch.current_1619_another-release-key.apk", index_xml
|
|
)
|
|
self.assertIn("obb.mainpatch.current_1619.apk", index_xml)
|
|
self.assertIn("obb.mainpatch.current_1619.apk", index_v1_json)
|
|
self.assertIn(
|
|
"obb.mainpatch.current_1619_another-release-key.apk", index_v1_json
|
|
)
|
|
# die if there are exact duplicates
|
|
shutil.copy(FILES / "repo/obb.mainpatch.current_1619.apk", "repo/duplicate.apk")
|
|
self.assert_run_fail(self.fdroid_cmd + ["update"])
|
|
|
|
def test_setup_new_repo_from_scratch_using_android_home_env_var_putting_apks_in_repo_first(
|
|
self,
|
|
):
|
|
Path("repo").mkdir()
|
|
self.copy_apks_into_repo()
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
self.assert_run(self.fdroid_cmd + ["update", "--create-metadata", "--verbose"])
|
|
self.assert_run(self.fdroid_cmd + ["readmeta"])
|
|
self.assertIn("<application id=", Path("repo/index.xml").read_text())
|
|
|
|
def test_setup_a_new_repo_from_scratch_and_generate_a_keystore(self):
|
|
self.assert_run(self.fdroid_cmd + ["init", "--keystore", "keystore.p12"])
|
|
self.assertTrue(Path("keystore.p12").is_file())
|
|
self.copy_apks_into_repo()
|
|
self.assert_run(self.fdroid_cmd + ["update", "--create-metadata", "--verbose"])
|
|
self.assert_run(self.fdroid_cmd + ["readmeta"])
|
|
self.assertIn("<application id=", Path("repo/index.xml").read_text())
|
|
self.assertTrue(Path("repo/index.jar").is_file())
|
|
self.assertTrue(Path("repo/index-v1.jar").is_file())
|
|
apkcache = Path("tmp/apkcache.json")
|
|
self.assertTrue(apkcache.is_file())
|
|
self.assertTrue(apkcache.stat().st_size > 0)
|
|
|
|
def test_setup_a_new_repo_manually_and_generate_a_keystore(self):
|
|
self.assertFalse(Path("keystore.p12").exists())
|
|
# this should fail because this repo has no keystore
|
|
self.assert_run_fail(self.fdroid_cmd + ["update"])
|
|
self.assert_run(self.fdroid_cmd + ["update", "--create-key"])
|
|
self.assertTrue(Path("keystore.p12").is_file())
|
|
self.copy_apks_into_repo()
|
|
self.assert_run(self.fdroid_cmd + ["update", "--create-metadata", "--verbose"])
|
|
self.assert_run(self.fdroid_cmd + ["readmeta"])
|
|
self.assertIn("<application id=", Path("repo/index.xml").read_text())
|
|
self.assertTrue(Path("repo/index.jar").is_file())
|
|
self.assertTrue(Path("repo/index-v1.jar").is_file())
|
|
apkcache = Path("tmp/apkcache.json")
|
|
self.assertTrue(apkcache.is_file())
|
|
self.assertTrue(apkcache.stat().st_size > 0)
|
|
|
|
def test_setup_a_new_repo_from_scratch_generate_a_keystore_then_add_apk_and_update(
|
|
self,
|
|
):
|
|
self.assert_run(self.fdroid_cmd + ["init", "--keystore", "keystore.p12"])
|
|
self.assertTrue(Path("keystore.p12").is_file())
|
|
self.copy_apks_into_repo()
|
|
self.assert_run(self.fdroid_cmd + ["update", "--create-metadata", "--verbose"])
|
|
self.assert_run(self.fdroid_cmd + ["readmeta"])
|
|
self.assertIn("<application id=", Path("repo/index.xml").read_text())
|
|
self.assertTrue(Path("repo/index.jar").is_file())
|
|
self.assertTrue(Path("repo/index-v1.jar").is_file())
|
|
|
|
if not Path("repo/info.guardianproject.urzip_100.apk").exists():
|
|
shutil.copy(FILES / "urzip.apk", "repo")
|
|
self.assert_run(self.fdroid_cmd + ["update", "--create-metadata", "--verbose"])
|
|
self.assert_run(self.fdroid_cmd + ["readmeta"])
|
|
self.assertIn("<application id=", Path("repo/index.xml").read_text())
|
|
self.assertTrue(Path("repo/index.jar").is_file())
|
|
self.assertTrue(Path("repo/index-v1.jar").is_file())
|
|
apkcache = Path("tmp/apkcache.json")
|
|
self.assertTrue(apkcache.is_file())
|
|
self.assertTrue(apkcache.stat().st_size > 0)
|
|
self.assertIn("<application id=", Path("repo/index.xml").read_text())
|
|
|
|
def test_setup_a_new_repo_from_scratch_with_a_hsm_or_smartcard(self):
|
|
self.assert_run(self.fdroid_cmd + ["init", "--keystore", "NONE"])
|
|
self.assertTrue(Path("opensc-fdroid.cfg").is_file())
|
|
self.assertFalse(Path("NONE").exists())
|
|
|
|
def test_setup_a_new_repo_with_no_keystore_add_apk_and_update(self):
|
|
Path("fdroid-icon.png").touch()
|
|
Path("repo").mkdir()
|
|
shutil.copy(FILES / "urzip.apk", "repo")
|
|
# this should fail because this repo has no keystore
|
|
self.assert_run_fail(
|
|
self.fdroid_cmd + ["update", "--create-metadata", "--verbose"]
|
|
)
|
|
|
|
# now set up fake, non-working keystore setup
|
|
Path("keystore.p12").touch()
|
|
self.update_yaml(
|
|
"config.yml",
|
|
{
|
|
"keystore": "keystore.p12",
|
|
"repo_keyalias": "foo",
|
|
"keystorepass": "foo",
|
|
"keypass": "foo",
|
|
},
|
|
)
|
|
# this should fail because this repo has a bad/fake keystore
|
|
self.assert_run_fail(
|
|
self.fdroid_cmd + ["update", "--create-metadata", "--verbose"]
|
|
)
|
|
|
|
def test_copy_tests_repo_update_with_binary_transparency_log(self):
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
shutil.copytree(FILES / "repo", "repo", dirs_exist_ok=True)
|
|
shutil.copytree(FILES / "metadata", "metadata")
|
|
git_remote = self.testdir / "git_remote"
|
|
self.update_yaml("config.yml", {"binary_transparency_remote": str(git_remote)})
|
|
self.assert_run(self.fdroid_cmd + ["update", "--verbose"])
|
|
self.assert_run(self.fdroid_cmd + ["deploy", "--verbose"])
|
|
self.assertIn("<application id=", Path("repo/index.xml").read_text())
|
|
self.assertTrue(Path("repo/index.jar").is_file())
|
|
self.assertTrue(Path("repo/index-v1.jar").is_file())
|
|
os.chdir("binary_transparency")
|
|
proc = self.assert_run(
|
|
["git", "rev-list", "--count", "HEAD"], capture_output=True
|
|
)
|
|
self.assertEqual(int(proc.stdout), 2)
|
|
os.chdir(git_remote)
|
|
proc = self.assert_run(
|
|
["git", "rev-list", "--count", "HEAD"], capture_output=True
|
|
)
|
|
self.assertEqual(int(proc.stdout), 2)
|
|
|
|
def test_setup_a_new_repo_with_keystore_with_apk_update_then_without_key(self):
|
|
shutil.copy(FILES / "keystore.jks", "keystore.jks")
|
|
self.fdroid_init_with_prebuilt_keystore("keystore.jks")
|
|
shutil.copy(FILES / "urzip.apk", "repo")
|
|
self.assert_run(self.fdroid_cmd + ["update", "--create-metadata", "--verbose"])
|
|
self.assert_run(self.fdroid_cmd + ["readmeta"])
|
|
self.assertIn("<application id=", Path("repo/index.xml").read_text())
|
|
self.assertTrue(Path("repo/index.jar").is_file())
|
|
self.assertTrue(Path("repo/index-v1.jar").is_file())
|
|
apkcache = Path("tmp/apkcache.json")
|
|
self.assertTrue(apkcache.is_file())
|
|
self.assertTrue(apkcache.stat().st_size > 0)
|
|
|
|
# now set fake repo_keyalias
|
|
self.update_yaml("config.yml", {"repo_keyalias": "fake"})
|
|
# this should fail because this repo has a bad repo_keyalias
|
|
self.assert_run_fail(self.fdroid_cmd + ["update"])
|
|
|
|
# this should fail because a keystore is already there
|
|
self.assert_run_fail(self.fdroid_cmd + ["update", "--create-key"])
|
|
|
|
# now actually create the key with the existing settings
|
|
Path("keystore.jks").unlink()
|
|
self.assert_run(self.fdroid_cmd + ["update", "--create-key"])
|
|
self.assertTrue(Path("keystore.jks").is_file())
|
|
|
|
def test_setup_a_new_repo_from_scratch_using_android_home_env_var_with_git_mirror(
|
|
self,
|
|
):
|
|
server_git_mirror = self.testdir / "server_git_mirror"
|
|
server_git_mirror.mkdir()
|
|
self.assert_run(
|
|
["git", "-C", server_git_mirror, "init", "--initial-branch", "master"]
|
|
)
|
|
self.assert_run(
|
|
[
|
|
"git",
|
|
"-C",
|
|
server_git_mirror,
|
|
"config",
|
|
"receive.denyCurrentBranch",
|
|
"updateInstead",
|
|
]
|
|
)
|
|
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
self.update_yaml(
|
|
"config.yml",
|
|
{"archive_older": 3, "servergitmirrors": str(server_git_mirror)},
|
|
)
|
|
for f in FILES.glob("repo/com.politedroid_[345].apk"):
|
|
shutil.copy(f, "repo")
|
|
self.assert_run(self.fdroid_cmd + ["update", "--create-metadata"])
|
|
self.assert_run(self.fdroid_cmd + ["deploy"])
|
|
git_mirror = Path("git-mirror")
|
|
self.assertTrue((git_mirror / "fdroid/repo/com.politedroid_3.apk").is_file())
|
|
self.assertTrue((git_mirror / "fdroid/repo/com.politedroid_4.apk").is_file())
|
|
self.assertTrue((git_mirror / "fdroid/repo/com.politedroid_5.apk").is_file())
|
|
self.assertTrue(
|
|
(server_git_mirror / "fdroid/repo/com.politedroid_3.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
(server_git_mirror / "fdroid/repo/com.politedroid_4.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
(server_git_mirror / "fdroid/repo/com.politedroid_5.apk").is_file()
|
|
)
|
|
(git_mirror / ".git/test-stamp").write_text(str(datetime.now()))
|
|
|
|
# add one more APK to trigger archiving
|
|
shutil.copy(FILES / "repo/com.politedroid_6.apk", "repo")
|
|
self.assert_run(self.fdroid_cmd + ["update"])
|
|
self.assert_run(self.fdroid_cmd + ["deploy"])
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
self.assertFalse((git_mirror / "fdroid/archive/com.politedroid_3.apk").exists())
|
|
self.assertFalse(
|
|
(server_git_mirror / "fdroid/archive/com.politedroid_3.apk").exists()
|
|
)
|
|
self.assertTrue((git_mirror / "fdroid/repo/com.politedroid_4.apk").is_file())
|
|
self.assertTrue((git_mirror / "fdroid/repo/com.politedroid_5.apk").is_file())
|
|
self.assertTrue((git_mirror / "fdroid/repo/com.politedroid_6.apk").is_file())
|
|
self.assertTrue(
|
|
(server_git_mirror / "fdroid/repo/com.politedroid_4.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
(server_git_mirror / "fdroid/repo/com.politedroid_5.apk").is_file()
|
|
)
|
|
self.assertTrue(
|
|
(server_git_mirror / "fdroid/repo/com.politedroid_6.apk").is_file()
|
|
)
|
|
before = sum(
|
|
f.stat().st_size for f in (git_mirror / ".git").glob("**/*") if f.is_file()
|
|
)
|
|
|
|
self.update_yaml("config.yml", {"git_mirror_size_limit": "60kb"})
|
|
self.assert_run(self.fdroid_cmd + ["update"])
|
|
self.assert_run(self.fdroid_cmd + ["deploy"])
|
|
self.assertTrue(Path("archive/com.politedroid_3.apk").is_file())
|
|
self.assertFalse(
|
|
(server_git_mirror / "fdroid/archive/com.politedroid_3.apk").exists()
|
|
)
|
|
after = sum(
|
|
f.stat().st_size for f in (git_mirror / ".git").glob("**/*") if f.is_file()
|
|
)
|
|
self.assertFalse((git_mirror / ".git/test-stamp").exists())
|
|
self.assert_run(["git", "-C", git_mirror, "gc"])
|
|
self.assert_run(["git", "-C", server_git_mirror, "gc"])
|
|
self.assertGreater(before, after)
|
|
|
|
def test_sign_binary_repo_in_offline_box_then_publishing_from_online_box(self):
|
|
offline_root = self.testdir / "offline_root"
|
|
offline_root.mkdir()
|
|
local_copy_dir = self.testdir / "local_copy_dir/fdroid"
|
|
local_copy_dir.mkdir(parents=True)
|
|
online_root = self.testdir / "online_root"
|
|
online_root.mkdir()
|
|
server_web_root = self.testdir / "server_web_root/fdroid"
|
|
server_web_root.mkdir(parents=True)
|
|
|
|
# create offline binary transparency log
|
|
(offline_root / "binary_transparency").mkdir()
|
|
os.chdir(offline_root / "binary_transparency")
|
|
self.assert_run(["git", "init", "--initial-branch", "master"])
|
|
|
|
# fake git remote server for binary transparency log
|
|
binary_transparency_remote = self.testdir / "binary_transparency_remote"
|
|
binary_transparency_remote.mkdir()
|
|
|
|
# fake git remote server for repo mirror
|
|
server_git_mirror = self.testdir / "server_git_mirror"
|
|
server_git_mirror.mkdir()
|
|
os.chdir(server_git_mirror)
|
|
self.assert_run(["git", "init", "--initial-branch", "master"])
|
|
self.assert_run(["git", "config", "receive.denyCurrentBranch", "updateInstead"])
|
|
|
|
os.chdir(offline_root)
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
shutil.copytree(FILES / "repo", "repo", dirs_exist_ok=True)
|
|
shutil.copytree(FILES / "metadata", "metadata")
|
|
Path("unsigned").mkdir()
|
|
shutil.copy(FILES / "urzip-release-unsigned.apk", "unsigned")
|
|
self.update_yaml(
|
|
"config.yml",
|
|
{
|
|
"archive_older": 3,
|
|
"mirrors": [
|
|
"http://foo.bar/fdroid",
|
|
"http://asdflkdsfjafdsdfhkjh.onion/fdroid",
|
|
],
|
|
"servergitmirrors": str(server_git_mirror),
|
|
"local_copy_dir": str(local_copy_dir),
|
|
},
|
|
)
|
|
self.assert_run(self.fdroid_cmd + ["update", "--pretty"])
|
|
index_xml = Path("repo/index.xml").read_text()
|
|
self.assertIn("<application id=", index_xml)
|
|
self.assertIn("/fdroid/repo</mirror>", index_xml)
|
|
mirror_cnt = sum(1 for line in index_xml.splitlines() if "<mirror>" in line)
|
|
self.assertEqual(mirror_cnt, 2)
|
|
|
|
archive_xml = Path("archive/index.xml").read_text()
|
|
self.assertIn("/fdroid/archive</mirror>", archive_xml)
|
|
mirror_cnt = sum(1 for line in archive_xml.splitlines() if "<mirror>" in line)
|
|
self.assertEqual(mirror_cnt, 2)
|
|
|
|
os.chdir("binary_transparency")
|
|
proc = self.assert_run(
|
|
["git", "rev-list", "--count", "HEAD"], capture_output=True
|
|
)
|
|
self.assertEqual(int(proc.stdout), 1)
|
|
os.chdir(offline_root)
|
|
self.assert_run(self.fdroid_cmd + ["deploy", "--verbose"])
|
|
self.assertTrue(
|
|
Path(local_copy_dir / "unsigned/urzip-release-unsigned.apk").is_file()
|
|
)
|
|
self.assertIn(
|
|
"<application id=", (local_copy_dir / "repo/index.xml").read_text()
|
|
)
|
|
os.chdir(online_root)
|
|
self.update_yaml(
|
|
"config.yml",
|
|
{
|
|
"local_copy_dir": str(local_copy_dir),
|
|
"sync_from_local_copy_dir": True,
|
|
"serverwebroot": str(server_web_root),
|
|
"servergitmirrors": str(server_git_mirror),
|
|
"binary_transparency_remote": str(binary_transparency_remote),
|
|
},
|
|
)
|
|
self.assert_run(self.fdroid_cmd + ["deploy", "--verbose"])
|
|
self.assertTrue((online_root / "unsigned/urzip-release-unsigned.apk").is_file())
|
|
self.assertTrue(
|
|
(server_web_root / "unsigned/urzip-release-unsigned.apk").is_file()
|
|
)
|
|
os.chdir(binary_transparency_remote)
|
|
proc = self.assert_run(
|
|
["git", "rev-list", "--count", "HEAD"], capture_output=True
|
|
)
|
|
self.assertEqual(int(proc.stdout), 1)
|
|
|
|
if platform.system() == 'Darwin':
|
|
self.skipTest('FIXME the last step of this test fails only on macOS')
|
|
os.chdir(server_git_mirror)
|
|
proc = self.assert_run(
|
|
["git", "rev-list", "--count", "HEAD"], capture_output=True
|
|
)
|
|
self.assertEqual(int(proc.stdout), 1)
|
|
|
|
@unittest.skipUnless(USE_APKSIGNER, "requires apksigner")
|
|
def test_extracting_and_publishing_with_developer_signature(self):
|
|
self.fdroid_init_with_prebuilt_keystore()
|
|
self.update_yaml(
|
|
"config.yml",
|
|
{
|
|
"keydname": "CN=Birdman, OU=Cell, O=Alcatraz, L=Alcatraz, S=California, C=US"
|
|
},
|
|
)
|
|
Path("metadata").mkdir()
|
|
shutil.copy(FILES / "metadata/com.politedroid.yml", "metadata")
|
|
Path("unsigned").mkdir()
|
|
shutil.copy(FILES / "repo/com.politedroid_6.apk", "unsigned")
|
|
self.assert_run(
|
|
self.fdroid_cmd + ["signatures", "unsigned/com.politedroid_6.apk"]
|
|
)
|
|
self.assertTrue(
|
|
Path("metadata/com.politedroid/signatures/6/MANIFEST.MF").is_file()
|
|
)
|
|
self.assertTrue(
|
|
Path("metadata/com.politedroid/signatures/6/RELEASE.RSA").is_file()
|
|
)
|
|
self.assertTrue(
|
|
Path("metadata/com.politedroid/signatures/6/RELEASE.SF").is_file()
|
|
)
|
|
self.assertFalse(Path("repo/com.politedroid_6.apk").exists())
|
|
self.assert_run(self.fdroid_cmd + ["publish"])
|
|
self.assertTrue(Path("repo/com.politedroid_6.apk").is_file())
|
|
if shutil.which("apksigner"):
|
|
self.assert_run(["apksigner", "verify", "repo/com.politedroid_6.apk"])
|
|
if shutil.which("jarsigner"):
|
|
self.assert_run(["jarsigner", "-verify", "repo/com.politedroid_6.apk"])
|
|
|
|
@unittest.skipUnless(shutil.which("wget"), "requires wget")
|
|
def test_mirroring_a_repo(self):
|
|
"""Start a local webserver to mirror a fake repo from.
|
|
|
|
Proxy settings via environment variables can interfere with
|
|
this test. The requests library will automatically pick up
|
|
proxy settings from environment variables. Proxy settings can
|
|
force the local connection over the proxy, which might not
|
|
support that, then this fails with an error like 405 or
|
|
others.
|
|
|
|
"""
|
|
tmp_test = self.testdir / "tests"
|
|
tmp_test.mkdir()
|
|
shutil.copytree(FILES, tmp_test, dirs_exist_ok=True)
|
|
os.chdir(tmp_test)
|
|
Path("archive").mkdir(exist_ok=True)
|
|
shutil.copy("repo/index-v1.json", self.tmp_repo_root)
|
|
self.assert_run(self.fdroid_cmd + ["update"])
|
|
self.assert_run(self.fdroid_cmd + ["signindex"])
|
|
shutil.move(self.tmp_repo_root / "index-v1.json", "repo/index-v1.json")
|
|
|
|
class RequestHandler(SimpleHTTPRequestHandler):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, directory=tmp_test, **kwargs)
|
|
|
|
httpd = ThreadingHTTPServer(("127.0.0.1", 0), RequestHandler)
|
|
threading.Thread(target=httpd.serve_forever).start()
|
|
|
|
os.chdir(self.tmp_repo_root)
|
|
host, port = httpd.socket.getsockname()
|
|
url, output_dir = f"http://{host}:{port}/", Path(f"{host}:{port}")
|
|
self.assert_run(self.fdroid_cmd + ["mirror", url])
|
|
self.assertTrue((output_dir / "repo/souch.smsbypass_9.apk").is_file())
|
|
self.assertTrue((output_dir / "repo/icons-640/souch.smsbypass.9.png").is_file())
|
|
# the index shouldn't be saved unless it was verified
|
|
self.assertFalse((output_dir / "repo/index-v1.jar").exists())
|
|
self.assert_run_fail(
|
|
self.fdroid_cmd + ["mirror", f"{url}?fingerprint=asdfasdf"]
|
|
)
|
|
self.assertFalse((output_dir / "repo/index-v1.jar").exists())
|
|
self.assert_run(
|
|
self.fdroid_cmd
|
|
+ [
|
|
"mirror",
|
|
f"{url}?fingerprint=F49AF3F11EFDDF20DFFD70F5E3117B9976674167ADCA280E6B1932A0601B26F6",
|
|
],
|
|
)
|
|
self.assertTrue((output_dir / "repo/index-v1.jar").is_file())
|
|
|
|
httpd.shutdown()
|
|
|
|
def test_recovering_from_broken_git_submodules(self):
|
|
Path("foo").mkdir()
|
|
Path("bar").mkdir()
|
|
os.chdir("foo")
|
|
self.assert_run(["git", "init"])
|
|
Path("a").write_text("a")
|
|
self.assert_run(["git", "add", "a"])
|
|
self.assert_run(["git", "commit", "-m", "a"])
|
|
|
|
os.chdir("../bar")
|
|
self.assert_run(["git", "init"])
|
|
self.assert_run(
|
|
["git", "submodule", "add", f"file://{Path().resolve()}/../foo", "baz"]
|
|
)
|
|
Path(".gitmodules").unlink()
|
|
self.assert_run(["git", "commit", "-am", "a"])
|
|
shutil.rmtree("baz")
|
|
self.assert_run(["git", "checkout", "baz"])
|
|
self.assert_run(["git", "tag", "2"])
|
|
|
|
os.chdir("..")
|
|
Path("repo").mkdir()
|
|
Path("metadata").mkdir()
|
|
self.update_yaml(
|
|
"metadata/fake.yml",
|
|
{
|
|
"RepoType": "git",
|
|
"Repo": f"file://{Path().resolve()}/bar",
|
|
"AutoUpdateMode": "Version",
|
|
"UpdateCheckMode": "Tags",
|
|
"UpdateCheckData": "|||",
|
|
"CurrentVersion": 1,
|
|
"CurrentVersionCode": 1,
|
|
},
|
|
)
|
|
self.assert_run(self.fdroid_cmd + ["checkupdates", "--allow-dirty"])
|
|
self.assertIn("CurrentVersionCode: 2", Path("metadata/fake.yml").read_text())
|
|
|
|
def test_checkupdates_ignore_broken_submodule(self):
|
|
Path("foo").mkdir()
|
|
Path("bar").mkdir()
|
|
os.chdir("foo")
|
|
self.assert_run(["git", "init"])
|
|
Path("a").write_text("a")
|
|
self.assert_run(["git", "add", "a"])
|
|
self.assert_run(["git", "commit", "-m", "a"])
|
|
|
|
os.chdir("../bar")
|
|
self.assert_run(["git", "init"])
|
|
self.assert_run(
|
|
["git", "submodule", "add", f"file://{Path().resolve()}/../foo", "baz"]
|
|
)
|
|
self.assert_run(["git", "commit", "-am", "a"])
|
|
self.assert_run(["git", "tag", "2"])
|
|
|
|
os.chdir("../foo")
|
|
# delete the commit referenced in bar
|
|
self.assert_run(["git", "commit", "--amend", "-m", "aa"])
|
|
self.assert_run(["git", "reflog", "expire", "--expire", "now", "--all"])
|
|
self.assert_run(["git", "gc", "--aggressive", "--prune=now"])
|
|
|
|
os.chdir("..")
|
|
|
|
Path("repo").mkdir()
|
|
Path("metadata").mkdir()
|
|
self.update_yaml(
|
|
"metadata/fake.yml",
|
|
{
|
|
"RepoType": "git",
|
|
"Repo": f"file://{Path().resolve()}/bar",
|
|
"Builds": [{"versionName": 1, "versionCode": 1, "submodules": True}],
|
|
"AutoUpdateMode": "Version",
|
|
"UpdateCheckMode": "Tags",
|
|
"UpdateCheckData": "|||",
|
|
"CurrentVersion": 1,
|
|
"CurrentVersionCode": 1,
|
|
},
|
|
)
|
|
self.assert_run(self.fdroid_cmd + ["checkupdates", "--allow-dirty"])
|
|
self.assertIn("CurrentVersionCode: 2", Path("metadata/fake.yml").read_text())
|
|
|
|
def test_checkupdates_check_version_in_submodule(self):
|
|
Path("app").mkdir()
|
|
Path("sub").mkdir()
|
|
os.chdir("sub")
|
|
self.assert_run(["git", "init"])
|
|
Path("ver").write_text("1")
|
|
self.assert_run(["git", "add", "ver"])
|
|
self.assert_run(["git", "commit", "-m", "1"])
|
|
|
|
os.chdir("../app")
|
|
self.assert_run(["git", "init"])
|
|
self.assert_run(
|
|
["git", "submodule", "add", f"file://{Path().resolve()}/../sub"]
|
|
)
|
|
self.assert_run(["git", "commit", "-am", "1"])
|
|
self.assert_run(["git", "tag", "1"])
|
|
|
|
os.chdir("../sub")
|
|
Path("ver").write_text("2")
|
|
self.assert_run(["git", "commit", "-am", "2"])
|
|
|
|
os.chdir("../app")
|
|
self.assert_run(["git", "init"])
|
|
self.assert_run(["git", "submodule", "update", "--remote"])
|
|
self.assert_run(["git", "commit", "-am", "2"])
|
|
|
|
os.chdir("..")
|
|
Path("repo").mkdir()
|
|
Path("metadata").mkdir()
|
|
self.update_yaml(
|
|
"metadata/fake.yml",
|
|
{
|
|
"RepoType": "git",
|
|
"Repo": f"file://{Path().resolve()}/app",
|
|
"Builds": [{"versionName": 0, "versionCode": 0, "submodules": True}],
|
|
"AutoUpdateMode": "Version",
|
|
"UpdateCheckMode": "Tags",
|
|
"UpdateCheckData": r"sub/ver|(\d)||",
|
|
"CurrentVersion": 0,
|
|
"CurrentVersionCode": 0,
|
|
},
|
|
)
|
|
self.assert_run(
|
|
self.fdroid_cmd + ["checkupdates", "--allow-dirty", "--auto", "-v"]
|
|
)
|
|
self.assertIn("CurrentVersionCode: 1", Path("metadata/fake.yml").read_text())
|