diff --git a/.gitignore b/.gitignore
index c8785a33..04e92ad6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,11 +7,11 @@ TAGS
.ropeproject/
# files generated by build
-build/
-dist/
+/build/
+/dist/
env/
ENV/
-fdroidserver.egg-info/
+/fdroidserver.egg-info/
pylint.parseable
/.testfiles/
README.rst
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b9317954..f26a093a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -41,7 +41,7 @@ metadata_v0:
image: registry.gitlab.com/fdroid/fdroidserver:buildserver
variables:
GIT_DEPTH: 1000
- RELEASE_COMMIT_ID: 58cfce106b6d68dc8ebde7842cf01225f5adfd1b # 2.2b
+ RELEASE_COMMIT_ID: 0124b9dde99f9cab19c034cbc7d8cc6005a99b48 # 2.3a0
script:
- git fetch https://gitlab.com/fdroid/fdroidserver.git $RELEASE_COMMIT_ID
- cd tests
@@ -125,6 +125,10 @@ ubuntu_lts_ppa:
- apt-get update
- apt-get dist-upgrade
- apt-get install --install-recommends dexdump fdroidserver git jq python3-setuptools sdkmanager
+
+ # Test things work with a default branch other than 'master'
+ - git config --global init.defaultBranch thisisnotmasterormain
+
- cd tests
- ./run-tests
@@ -584,12 +588,12 @@ docker:
RELEASE_IMAGE: $CI_REGISTRY_IMAGE:buildserver
script:
# git ref names can contain many chars that are not allowed in docker tags
- - export TEST_IMAGE=$CI_REGISTRY_IMAGE:$(printf $CI_BUILD_REF_NAME | sed 's,[^a-zA-Z0-9_.-],_,g')
+ - export TEST_IMAGE=$CI_REGISTRY_IMAGE:$(printf $CI_COMMIT_REF_NAME | sed 's,[^a-zA-Z0-9_.-],_,g')
- cd buildserver
- docker build -t $TEST_IMAGE --build-arg GIT_REV_PARSE_HEAD=$(git rev-parse HEAD) .
- docker tag $TEST_IMAGE $RELEASE_IMAGE
- docker tag $TEST_IMAGE ${RELEASE_IMAGE}-bullseye
- - echo $CI_BUILD_TOKEN | docker login -u gitlab-ci-token --password-stdin registry.gitlab.com
+ - echo $CI_JOB_TOKEN | docker login -u gitlab-ci-token --password-stdin registry.gitlab.com
# This avoids filling up gitlab.com free tier accounts with unused docker images.
- if test -z "$FDROID_PUSH_DOCKER_IMAGE"; then
echo "Skipping docker push to save quota on your gitlab namespace.";
diff --git a/MANIFEST.in b/MANIFEST.in
index 6fb0a57e..e534b0da 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -545,6 +545,22 @@ include tests/check-fdroid-apk
include tests/checkupdates.TestCase
include tests/common.TestCase
include tests/config.py
+include tests/config/antiFeatures.yml
+include tests/config/de/antiFeatures.yml
+include tests/config/fa/antiFeatures.yml
+include tests/config/ic_antifeature_ads.xml
+include tests/config/ic_antifeature_disabledalgorithm.xml
+include tests/config/ic_antifeature_knownvuln.xml
+include tests/config/ic_antifeature_nonfreeadd.xml
+include tests/config/ic_antifeature_nonfreeassets.xml
+include tests/config/ic_antifeature_nonfreedep.xml
+include tests/config/ic_antifeature_nonfreenet.xml
+include tests/config/ic_antifeature_nosourcesince.xml
+include tests/config/ic_antifeature_nsfw.xml
+include tests/config/ic_antifeature_tracking.xml
+include tests/config/ic_antifeature_upstreamnonfree.xml
+include tests/config/ro/antiFeatures.yml
+include tests/config/zh-rCN/antiFeatures.yml
include tests/corrupt-featureGraphic.png
include tests/deploy.TestCase
include tests/dummy-keystore.jks
diff --git a/fdroidserver/btlog.py b/fdroidserver/btlog.py
index 870ff93b..ea79bc8a 100755
--- a/fdroidserver/btlog.py
+++ b/fdroidserver/btlog.py
@@ -65,7 +65,7 @@ def make_binary_transparency_log(
else:
if not os.path.exists(btrepo):
os.mkdir(btrepo)
- gitrepo = git.Repo.init(btrepo)
+ gitrepo = git.Repo.init(btrepo, initial_branch=deploy.GIT_BRANCH)
if not url:
url = common.config['repo_url'].rstrip('/')
diff --git a/fdroidserver/common.py b/fdroidserver/common.py
index 550e7b5a..f401b71f 100644
--- a/fdroidserver/common.py
+++ b/fdroidserver/common.py
@@ -77,6 +77,9 @@ from . import apksigcopier, common
# The path to this fdroidserver distribution
FDROID_PATH = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
+# There needs to be a default, and this is the most common for software.
+DEFAULT_LOCALE = 'en-US'
+
# this is the build-tools version, aapt has a separate version that
# has to be manually set in test_aapt_version()
MINIMUM_AAPT_BUILD_TOOLS_VERSION = '26.0.0'
@@ -487,6 +490,52 @@ def read_config(opts=None):
return config
+def file_entry(filename, hash_value=None):
+ meta = {}
+ meta["name"] = "/" + filename.split("/", 1)[1]
+ meta["sha256"] = hash_value or common.sha256sum(filename)
+ meta["size"] = os.stat(filename).st_size
+ return meta
+
+
+def load_localized_config(name, repodir):
+ """Load localized config files and put them into internal dict format.
+
+ This will maintain the order as came from the data files, e.g
+ YAML. The locale comes from unsorted paths on the filesystem, so
+ that is separately sorted.
+
+ """
+ ret = dict()
+ for f in Path().glob("config/**/{name}.yml".format(name=name)):
+ locale = f.parts[1]
+ if len(f.parts) == 2:
+ locale = DEFAULT_LOCALE
+ with open(f, encoding="utf-8") as fp:
+ elem = yaml.safe_load(fp)
+ for afname, field_dict in elem.items():
+ if afname not in ret:
+ ret[afname] = dict()
+ for key, value in field_dict.items():
+ if key not in ret[afname]:
+ ret[afname][key] = dict()
+ if key == "icon":
+ icons_dir = os.path.join(repodir, 'icons')
+ if not os.path.exists(icons_dir):
+ os.mkdir(icons_dir)
+ shutil.copy(os.path.join("config", value), icons_dir)
+ ret[afname][key][locale] = file_entry(
+ os.path.join(icons_dir, value)
+ )
+ else:
+ ret[afname][key][locale] = value
+
+ for elem in ret.values():
+ for afname in elem:
+ elem[afname] = {locale: v for locale, v in sorted(elem[afname].items())}
+ return ret
+
+
def parse_human_readable_size(size):
units = {
'b': 1,
@@ -3866,7 +3915,7 @@ def get_app_display_name(app):
if app.get('Name'):
return app['Name']
if app.get('localized'):
- localized = app['localized'].get('en-US')
+ localized = app['localized'].get(DEFAULT_LOCALE)
if not localized:
for v in app['localized'].values():
localized = v
diff --git a/fdroidserver/deploy.py b/fdroidserver/deploy.py
index 0415f7f4..54496f78 100644
--- a/fdroidserver/deploy.py
+++ b/fdroidserver/deploy.py
@@ -39,6 +39,8 @@ config = None
options = None
start_timestamp = time.gmtime()
+GIT_BRANCH = 'master'
+
BINARY_TRANSPARENCY_DIR = 'binary_transparency'
AUTO_S3CFG = '.fdroid-deploy-s3cfg'
@@ -407,7 +409,7 @@ def update_servergitmirrors(servergitmirrors, repo_section):
elif 'identity_file' in config:
ssh_cmd += ' -oIdentitiesOnly=yes -i "%s"' % config['identity_file']
- repo = git.Repo.init(git_mirror_path)
+ repo = git.Repo.init(git_mirror_path, initial_branch=GIT_BRANCH)
enabled_remotes = []
for remote_url in servergitmirrors:
@@ -480,7 +482,9 @@ def update_servergitmirrors(servergitmirrors, repo_section):
logging.debug(_('Pushing to {url}').format(url=remote.url))
with repo.git.custom_environment(GIT_SSH_COMMAND=ssh_cmd):
- pushinfos = remote.push('master', force=True, set_upstream=True, progress=progress)
+ pushinfos = remote.push(
+ GIT_BRANCH, force=True, set_upstream=True, progress=progress
+ )
for pushinfo in pushinfos:
if pushinfo.flags & (git.remote.PushInfo.ERROR
| git.remote.PushInfo.REJECTED
@@ -691,7 +695,7 @@ def push_binary_transparency(git_repo_path, git_remote):
remote_path = os.path.abspath(git_repo_path)
if not os.path.isdir(os.path.join(git_remote, '.git')):
os.makedirs(git_remote, exist_ok=True)
- thumbdriverepo = git.Repo.init(git_remote)
+ thumbdriverepo = git.Repo.init(git_remote, initial_branch=GIT_BRANCH)
local = thumbdriverepo.create_remote('local', remote_path)
else:
thumbdriverepo = git.Repo(git_remote)
@@ -702,7 +706,7 @@ def push_binary_transparency(git_repo_path, git_remote):
local.set_url(remote_path)
else:
local = thumbdriverepo.create_remote('local', remote_path)
- local.pull('master')
+ local.pull(GIT_BRANCH)
else:
# from online machine to remote on a server on the internet
gitrepo = git.Repo(git_repo_path)
@@ -713,7 +717,7 @@ def push_binary_transparency(git_repo_path, git_remote):
origin.set_url(git_remote)
else:
origin = gitrepo.create_remote('origin', git_remote)
- origin.push('master')
+ origin.push(GIT_BRANCH)
def main():
diff --git a/fdroidserver/index.py b/fdroidserver/index.py
index d93ec586..48b51ffe 100644
--- a/fdroidserver/index.py
+++ b/fdroidserver/index.py
@@ -29,7 +29,6 @@ import re
import shutil
import tempfile
import urllib.parse
-import yaml
import zipfile
import calendar
import qrcode
@@ -43,7 +42,7 @@ from . import common
from . import metadata
from . import net
from . import signindex
-from fdroidserver.common import FDroidPopen, FDroidPopenBytes, load_stats_fdroid_signing_key_fingerprints
+from fdroidserver.common import DEFAULT_LOCALE, FDroidPopen, FDroidPopenBytes, load_stats_fdroid_signing_key_fingerprints
from fdroidserver.exception import FDroidException, VerificationException
@@ -472,37 +471,6 @@ def dict_diff(source, target):
return result
-def file_entry(filename, hash_value=None):
- meta = {}
- meta["name"] = "/" + filename.split("/", 1)[1]
- meta["sha256"] = hash_value or common.sha256sum(filename)
- meta["size"] = os.stat(filename).st_size
- return meta
-
-
-def load_locale(name, repodir):
- lst = {}
- for yml in Path().glob("config/**/{name}.yml".format(name=name)):
- locale = yml.parts[1]
- if len(yml.parts) == 2:
- locale = "en-US"
- with open(yml, encoding="utf-8") as fp:
- elem = yaml.safe_load(fp)
- for akey, avalue in elem.items():
- if akey not in lst:
- lst[akey] = {}
- for key, value in avalue.items():
- if key not in lst[akey]:
- lst[akey][key] = {}
- if key == "icon":
- shutil.copy(os.path.join("config", value), os.path.join(repodir, "icons"))
- lst[akey][key][locale] = file_entry(os.path.join(repodir, "icons", value))
- else:
- lst[akey][key][locale] = value
-
- return lst
-
-
def convert_datetime(obj):
if isinstance(obj, datetime):
# Java prefers milliseconds
@@ -550,14 +518,14 @@ def package_metadata(app, repodir):
):
element_new = element[:1].lower() + element[1:]
if element in app and app[element]:
- meta[element_new] = {"en-US": convert_datetime(app[element])}
+ meta[element_new] = {DEFAULT_LOCALE: convert_datetime(app[element])}
elif "localized" in app:
localized = {k: v[element_new] for k, v in app["localized"].items() if element_new in v}
if localized:
meta[element_new] = localized
if "name" not in meta and app["AutoName"]:
- meta["name"] = {"en-US": app["AutoName"]}
+ meta["name"] = {DEFAULT_LOCALE: app["AutoName"]}
# fdroidserver/metadata.py App default
if meta["license"] == "Unknown":
@@ -568,7 +536,8 @@ def package_metadata(app, repodir):
# TODO handle different resolutions
if app.get("icon"):
- meta["icon"] = {"en-US": file_entry(os.path.join(repodir, "icons", app["icon"]))}
+ icon_path = os.path.join(repodir, "icons", app["icon"])
+ meta["icon"] = {DEFAULT_LOCALE: common.file_entry(icon_path)}
if "iconv2" in app:
meta["icon"] = app["iconv2"]
@@ -594,16 +563,16 @@ def convert_version(version, app, repodir):
ver["file"]["ipfsCIDv1"] = ipfsCIDv1
if "srcname" in version:
- ver["src"] = file_entry(os.path.join(repodir, version["srcname"]))
+ ver["src"] = common.file_entry(os.path.join(repodir, version["srcname"]))
if "obbMainFile" in version:
- ver["obbMainFile"] = file_entry(
+ ver["obbMainFile"] = common.file_entry(
os.path.join(repodir, version["obbMainFile"]),
version["obbMainFileSha256"],
)
if "obbPatchFile" in version:
- ver["obbPatchFile"] = file_entry(
+ ver["obbPatchFile"] = common.file_entry(
os.path.join(repodir, version["obbPatchFile"]),
version["obbPatchFileSha256"],
)
@@ -684,11 +653,13 @@ def convert_version(version, app, repodir):
def v2_repo(repodict, repodir, archive):
repo = {}
- repo["name"] = {"en-US": repodict["name"]}
- repo["description"] = {"en-US": repodict["description"]}
- repo["icon"] = {"en-US": file_entry("{}/icons/{}".format(repodir, repodict["icon"]))}
+ repo["name"] = {DEFAULT_LOCALE: repodict["name"]}
+ repo["description"] = {DEFAULT_LOCALE: repodict["description"]}
+ repo["icon"] = {
+ DEFAULT_LOCALE: common.file_entry("%s/icons/%s" % (repodir, repodict["icon"]))
+ }
- config = load_locale("config", repodir)
+ config = common.load_localized_config("config", repodir)
if config:
repo["name"] = config["archive" if archive else "repo"]["name"]
repo["description"] = config["archive" if archive else "repo"]["description"]
@@ -702,15 +673,15 @@ def v2_repo(repodict, repodir, archive):
repo["timestamp"] = repodict["timestamp"]
- antiFeatures = load_locale("antiFeatures", repodir)
+ antiFeatures = common.load_localized_config("antiFeatures", repodir)
if antiFeatures:
repo["antiFeatures"] = antiFeatures
- categories = load_locale("categories", repodir)
+ categories = common.load_localized_config("categories", repodir)
if categories:
repo["categories"] = categories
- channels = load_locale("channels", repodir)
+ channels = common.load_localized_config("channels", repodir)
if channels:
repo["releaseChannels"] = channels
@@ -735,7 +706,7 @@ def make_v2(apps, packages, repodir, repodict, requestsdict, fdroid_signing_key_
output = collections.OrderedDict()
output["repo"] = v2_repo(repodict, repodir, archive)
- if requestsdict and requestsdict["install"] or requestsdict["uninstall"]:
+ if requestsdict and (requestsdict["install"] or requestsdict["uninstall"]):
output["repo"]["requests"] = requestsdict
# establish sort order of the index
@@ -792,7 +763,7 @@ def make_v2(apps, packages, repodir, repodict, requestsdict, fdroid_signing_key_
else:
json.dump(output, fp, default=_index_encoder_default, ensure_ascii=False)
- entry["index"] = file_entry(index_file)
+ entry["index"] = common.file_entry(index_file)
entry["index"]["numPackages"] = len(output.get("packages", []))
indexes = sorted(Path().glob("tmp/{}*.json".format(repodir)), key=lambda x: x.name)
@@ -819,7 +790,7 @@ def make_v2(apps, packages, repodir, repodict, requestsdict, fdroid_signing_key_
else:
json.dump(diff, fp, default=_index_encoder_default, ensure_ascii=False)
- entry["diffs"][old["repo"]["timestamp"]] = file_entry(diff_file)
+ entry["diffs"][old["repo"]["timestamp"]] = common.file_entry(diff_file)
entry["diffs"][old["repo"]["timestamp"]]["numPackages"] = len(diff.get("packages", []))
json_name = "entry.json"
@@ -872,10 +843,10 @@ def make_v1(apps, packages, repodir, repodict, requestsdict, fdroid_signing_key_
appslist = []
output['apps'] = appslist
- for packageName, appdict in apps.items():
+ for packageName, app_dict in apps.items():
d = collections.OrderedDict()
appslist.append(d)
- for k, v in sorted(appdict.items()):
+ for k, v in sorted(app_dict.items()):
if not v:
continue
if k in ('Builds', 'metadatapath',
@@ -901,20 +872,20 @@ def make_v1(apps, packages, repodir, repodict, requestsdict, fdroid_signing_key_
d[k] = v
# establish sort order in lists, sets, and localized dicts
- for app in output['apps']:
- localized = app.get('localized')
+ for app_dict in output['apps']:
+ localized = app_dict.get('localized')
if localized:
lordered = collections.OrderedDict()
for lkey, lvalue in sorted(localized.items()):
lordered[lkey] = collections.OrderedDict()
for ikey, iname in sorted(lvalue.items()):
lordered[lkey][ikey] = iname
- app['localized'] = lordered
- antiFeatures = app.get('antiFeatures', [])
- if apps[app["packageName"]].get("NoSourceSince"):
+ app_dict['localized'] = lordered
+ antiFeatures = app_dict.get('antiFeatures', [])
+ if apps[app_dict["packageName"]].get("NoSourceSince"):
antiFeatures.append("NoSourceSince")
if antiFeatures:
- app['antiFeatures'] = sorted(set(antiFeatures))
+ app_dict['antiFeatures'] = sorted(set(antiFeatures))
output_packages = collections.OrderedDict()
output['packages'] = output_packages
@@ -1050,7 +1021,7 @@ def make_v0(apps, apks, repodir, repodict, requestsdict, fdroid_signing_key_fing
lkey = key[:1].lower() + key[1:]
localized = app.get('localized')
if not value and localized:
- for lang in ['en-US'] + [x for x in localized.keys()]:
+ for lang in [DEFAULT_LOCALE] + [x for x in localized.keys()]:
if not lang.startswith('en'):
continue
if lang in localized:
@@ -1096,8 +1067,8 @@ def make_v0(apps, apks, repodir, repodict, requestsdict, fdroid_signing_key_fing
root.appendChild(element)
element.setAttribute('packageName', packageName)
- for appid, appdict in apps.items():
- app = metadata.App(appdict)
+ for appid, app_dict in apps.items():
+ app = metadata.App(app_dict)
if app.get('Disabled') is not None:
continue
@@ -1294,7 +1265,7 @@ def make_v0(apps, apks, repodir, repodict, requestsdict, fdroid_signing_key_fing
namefield = common.config['current_version_name_source']
name = app.get(namefield)
if not name and namefield == 'Name':
- name = app.get('localized', {}).get('en-US', {}).get('name')
+ name = app.get('localized', {}).get(DEFAULT_LOCALE, {}).get('name')
if not name:
name = app.id
sanitized_name = re.sub(b'''[ '"&%?+=/]''', b'', str(name).encode('utf-8'))
diff --git a/fdroidserver/lint.py b/fdroidserver/lint.py
index fb94e258..1283409b 100644
--- a/fdroidserver/lint.py
+++ b/fdroidserver/lint.py
@@ -220,6 +220,19 @@ locale_pattern = re.compile(r"[a-z]{2,3}(-([A-Z][a-zA-Z]+|\d+|[a-z]+))*")
versioncode_check_pattern = re.compile(r"(\\d|\[(0-9|\\d)_?(a-fA-F)?])[+]")
+ANTIFEATURES_KEYS = None
+ANTIFEATURES_PATTERN = None
+
+
+def load_antiFeatures_config():
+ """Lazy loading, since it might read a lot of files."""
+ global ANTIFEATURES_KEYS, ANTIFEATURES_PATTERN
+ k = 'antiFeatures' # internal dict uses camelCase key name
+ if not ANTIFEATURES_KEYS or k not in common.config:
+ common.config[k] = common.load_localized_config(k, 'repo')
+ ANTIFEATURES_KEYS = sorted(common.config[k].keys())
+ ANTIFEATURES_PATTERN = ','.join(ANTIFEATURES_KEYS)
+
def check_regexes(app):
for f, checks in regex_checks.items():
@@ -613,6 +626,26 @@ def check_app_field_types(app):
)
+def check_antiFeatures(app):
+ """Check the Anti-Features keys match those declared in the config."""
+ pattern = ANTIFEATURES_PATTERN
+ msg = _("'{value}' is not a valid {field} in {appid}. Regex pattern: {pattern}")
+
+ field = 'AntiFeatures' # App entries use capitalized CamelCase
+ for value in app.get(field, []):
+ if value not in ANTIFEATURES_KEYS:
+ yield msg.format(value=value, field=field, appid=app.id, pattern=pattern)
+
+ field = 'antifeatures' # Build entries use all lowercase
+ for build in app.get('Builds', []):
+ build_antiFeatures = build.get(field, [])
+ for value in build_antiFeatures:
+ if value not in ANTIFEATURES_KEYS:
+ yield msg.format(
+ value=value, field=field, appid=app.id, pattern=pattern
+ )
+
+
def check_for_unsupported_metadata_files(basedir=""):
"""Check whether any non-metadata files are in metadata/."""
basedir = Path(basedir)
@@ -745,6 +778,7 @@ def main():
metadata.warnings_action = options.W
config = common.read_config(options)
+ load_antiFeatures_config()
# Get all apps...
allapps = metadata.read_metadata(options.appid)
@@ -801,6 +835,7 @@ def main():
app_check_funcs = [
check_app_field_types,
+ check_antiFeatures,
check_regexes,
check_update_check_data_url,
check_update_check_data_int,
diff --git a/fdroidserver/metadata.py b/fdroidserver/metadata.py
index 896c224e..787de9a0 100644
--- a/fdroidserver/metadata.py
+++ b/fdroidserver/metadata.py
@@ -448,10 +448,6 @@ valuetypes = {
r'^[0-9]+ versions$',
["ArchivePolicy"]),
- FieldValidator("Anti-Feature",
- r'^(Ads|Tracking|NonFreeNet|NonFreeDep|NonFreeAdd|UpstreamNonFree|NonFreeAssets|KnownVuln|ApplicationDebuggable|NoSourceSince|NSFW)$',
- ["AntiFeatures"]),
-
FieldValidator("Auto Update Mode",
r"^(Version.*|None)$",
["AutoUpdateMode"]),
@@ -676,7 +672,7 @@ def parse_metadata(metadatapath):
# pylint: disable-next=no-member
except git.exc.InvalidGitRepositoryError:
logging.debug(
- _('Including metadata from {path}').format(metadata_in_repo)
+ _('Including metadata from {path}').format(path=metadata_in_repo)
)
app_in_repo = parse_metadata(metadata_in_repo)
for k, v in app_in_repo.items():
diff --git a/fdroidserver/rewritemeta.py b/fdroidserver/rewritemeta.py
index 0e7ad021..7413b029 100644
--- a/fdroidserver/rewritemeta.py
+++ b/fdroidserver/rewritemeta.py
@@ -44,6 +44,22 @@ def proper_format(app):
return content == cur_content
+def remove_blank_flags_from_builds(builds):
+ """Remove unset entries from Builds so they are not written out."""
+ if not builds:
+ return list()
+ newbuilds = list()
+ for build in builds:
+ new = dict()
+ for k in metadata.build_flags:
+ v = build[k]
+ if v is None or v is False or v == [] or v == '':
+ continue
+ new[k] = v
+ newbuilds.append(new)
+ return newbuilds
+
+
def main():
global config, options
@@ -82,16 +98,9 @@ def main():
print(path)
continue
- newbuilds = []
- for build in app.get('Builds', []):
- new = metadata.Build()
- for k in metadata.build_flags:
- v = build[k]
- if v is None or v is False or v == [] or v == '':
- continue
- new[k] = v
- newbuilds.append(new)
- app['Builds'] = newbuilds
+ builds = remove_blank_flags_from_builds(app.get('Builds'))
+ if builds:
+ app['Builds'] = builds
# rewrite to temporary file before overwriting existing
# file in case there's a bug in write_metadata
diff --git a/fdroidserver/update.py b/fdroidserver/update.py
index b9490a53..45d66d33 100644
--- a/fdroidserver/update.py
+++ b/fdroidserver/update.py
@@ -51,6 +51,7 @@ from . import _
from . import common
from . import index
from . import metadata
+from .common import DEFAULT_LOCALE
from .exception import BuildException, FDroidException, VerificationException
from PIL import Image, PngImagePlugin
@@ -1037,7 +1038,7 @@ def insert_localized_app_metadata(apps):
base = "iconv2"
if base not in apps[packageName] or not isinstance(apps[packageName][base], collections.OrderedDict):
apps[packageName][base] = collections.OrderedDict()
- apps[packageName][base][locale] = index.file_entry(dst)
+ apps[packageName][base][locale] = common.file_entry(dst)
for d in dirs:
if d in SCREENSHOT_DIRS:
if locale == 'images':
@@ -1090,7 +1091,7 @@ def insert_localized_app_metadata(apps):
base = "iconv2"
if base not in apps[packageName] or not isinstance(apps[packageName][base], collections.OrderedDict):
apps[packageName][base] = collections.OrderedDict()
- apps[packageName][base][locale] = index.file_entry(index_file)
+ apps[packageName][base][locale] = common.file_entry(index_file)
elif screenshotdir in SCREENSHOT_DIRS:
# there can any number of these per locale
logging.debug(_('adding to {name}: {path}').format(name=screenshotdir, path=f))
@@ -1105,7 +1106,7 @@ def insert_localized_app_metadata(apps):
apps[packageName]["screenshots"][newKey] = collections.OrderedDict()
if locale not in apps[packageName]["screenshots"][newKey]:
apps[packageName]["screenshots"][newKey][locale] = []
- apps[packageName]["screenshots"][newKey][locale].append(index.file_entry(f))
+ apps[packageName]["screenshots"][newKey][locale].append(common.file_entry(f))
else:
logging.warning(_('Unsupported graphics file found: {path}').format(path=f))
@@ -2034,7 +2035,7 @@ def insert_missing_app_names_from_apks(apps, apks):
The name from the APK is set as the default name for the app if
there is no other default set, e.g. app['Name'] or
- app['localized']['en-US']['name']. The en-US locale is defined in
+ app['localized'][DEFAULT_LOCALE]['name']. The default is defined in
the F-Droid ecosystem as the locale of last resort, as in the one
that should always be present. en-US is used since it is the
locale of the source strings.
@@ -2050,7 +2051,7 @@ def insert_missing_app_names_from_apks(apps, apks):
for appid, app in apps.items():
if app.get('Name') is not None:
continue
- if app.get('localized', {}).get('en-US', {}).get('name') is not None:
+ if app.get('localized', {}).get(DEFAULT_LOCALE, {}).get('name') is not None:
continue
bestver = UNSET_VERSION_CODE
@@ -2063,9 +2064,9 @@ def insert_missing_app_names_from_apks(apps, apks):
if bestver != UNSET_VERSION_CODE:
if 'localized' not in app:
app['localized'] = {}
- if 'en-US' not in app['localized']:
- app['localized']['en-US'] = {}
- app['localized']['en-US']['name'] = bestapk.get('name')
+ if DEFAULT_LOCALE not in app['localized']:
+ app['localized'][DEFAULT_LOCALE] = {}
+ app['localized'][DEFAULT_LOCALE]['name'] = bestapk.get('name')
def get_apps_with_packages(apps, apks):
@@ -2342,10 +2343,10 @@ def main():
add_apks_to_per_app_repos(repodirs[0], apks)
for appid, app in apps.items():
repodir = os.path.join(appid, 'fdroid', 'repo')
- appdict = dict()
- appdict[appid] = app
+ app_dict = dict()
+ app_dict[appid] = app
if os.path.isdir(repodir):
- index.make(appdict, apks, repodir, False)
+ index.make(app_dict, apks, repodir, False)
else:
logging.info(_('Skipping index generation for {appid}').format(appid=appid))
return
diff --git a/tests/build/info.guardianproject.urzip/.fdroid.yml b/tests/build/info.guardianproject.urzip/.fdroid.yml
new file mode 100644
index 00000000..7f2b3c1c
--- /dev/null
+++ b/tests/build/info.guardianproject.urzip/.fdroid.yml
@@ -0,0 +1,4 @@
+
+Summary: This should be overridden by metadata/info.guardianproject.urzip.yml
+Builds:
+ - versionCode: 50
diff --git a/tests/common.TestCase b/tests/common.TestCase
index 758b4881..8820004d 100755
--- a/tests/common.TestCase
+++ b/tests/common.TestCase
@@ -2667,6 +2667,38 @@ class CommonTest(unittest.TestCase):
config['smartcardoptions'],
)
+ def test_load_localized_config(self):
+ """It should load"""
+ antiFeatures = fdroidserver.common.load_localized_config('antiFeatures', 'repo')
+ self.assertEqual(
+ [
+ 'Ads',
+ 'DisabledAlgorithm',
+ 'KnownVuln',
+ 'NSFW',
+ 'NoSourceSince',
+ 'NonFreeAdd',
+ 'NonFreeAssets',
+ 'NonFreeDep',
+ 'NonFreeNet',
+ 'Tracking',
+ 'UpstreamNonFree',
+ ],
+ list(antiFeatures.keys()),
+ )
+ self.assertEqual(
+ ['de', 'en-US', 'fa', 'ro', 'zh-rCN'],
+ list(antiFeatures['Ads']['description'].keys()),
+ )
+ self.assertEqual(
+ ['en-US'],
+ list(antiFeatures['NoSourceSince']['description'].keys()),
+ )
+ # it should have copied the icon files into place
+ for v in antiFeatures.values():
+ p = Path(os.path.dirname(__file__) + '/repo' + v['icon']['en-US']['name'])
+ self.assertTrue(p.exists())
+
if __name__ == "__main__":
os.chdir(os.path.dirname(__file__))
diff --git a/tests/config/antiFeatures.yml b/tests/config/antiFeatures.yml
new file mode 100644
index 00000000..e4c1a107
--- /dev/null
+++ b/tests/config/antiFeatures.yml
@@ -0,0 +1,45 @@
+Ads:
+ description: This app contains advertising
+ icon: ic_antifeature_ads.xml
+ name: Ads
+DisabledAlgorithm:
+ description: This app has a weak security signature
+ icon: ic_antifeature_disabledalgorithm.xml
+ name: Signed Using An Unsafe Algorithm
+KnownVuln:
+ description: This app contains a known security vulnerability
+ icon: ic_antifeature_knownvuln.xml
+ name: Known Vulnerability
+NSFW:
+ description: This app contains content that should not be publicized or visible
+ everywhere
+ icon: ic_antifeature_nsfw.xml
+ name: NSFW
+NoSourceSince:
+ description: The source code is no longer available, no updates possible.
+ icon: ic_antifeature_nosourcesince.xml
+ name: Newer Source Not Available
+NonFreeAdd:
+ description: This app promotes non-free add-ons
+ icon: ic_antifeature_nonfreeadd.xml
+ name: Non-Free Addons
+NonFreeAssets:
+ description: This app contains non-free assets
+ icon: ic_antifeature_nonfreeassets.xml
+ name: Non-Free Assets
+NonFreeDep:
+ description: This app depends on other non-free apps
+ icon: ic_antifeature_nonfreedep.xml
+ name: Non-Free Dependencies
+NonFreeNet:
+ description: This app promotes or depends entirely on a non-free network service
+ icon: ic_antifeature_nonfreenet.xml
+ name: Non-Free Network Services
+Tracking:
+ description: This app tracks and reports your activity
+ icon: ic_antifeature_tracking.xml
+ name: Tracking
+UpstreamNonFree:
+ description: The upstream source code is not entirely Free
+ icon: ic_antifeature_upstreamnonfree.xml
+ name: Upstream Non-Free
diff --git a/tests/config/de/antiFeatures.yml b/tests/config/de/antiFeatures.yml
new file mode 100644
index 00000000..3053e41a
--- /dev/null
+++ b/tests/config/de/antiFeatures.yml
@@ -0,0 +1,44 @@
+Ads:
+ description: Diese App enthält Werbung
+ icon: ic_antifeature_ads.xml
+ name: Werbung
+DisabledAlgorithm:
+ description: Diese App hat eine schwache Sicherheitssignatur
+ icon: ic_antifeature_disabledalgorithm.xml
+ name: Mit einem unsicheren Algorithmus signiert
+KnownVuln:
+ description: Diese App enthält eine bekannte Sicherheitslücke
+ icon: ic_antifeature_knownvuln.xml
+ name: Bekannte Sicherheitslücke
+NSFW:
+ description: Diese App enthält Inhalte, die nicht überall veröffentlicht oder sichtbar
+ sein sollten
+ icon: ic_antifeature_nsfw.xml
+ name: NSFW
+NoSourceSince:
+ icon: ic_antifeature_nosourcesince.xml
+ name: Der Quellcode ist nicht mehr erhältlich, keine Aktualisierungen möglich.
+NonFreeAdd:
+ description: Diese App bewirbt nicht-quelloffene Erweiterungen
+ icon: ic_antifeature_nonfreeadd.xml
+ name: Nicht-quelloffene Erweiterungen
+NonFreeAssets:
+ description: Diese App enthält nicht-quelloffene Bestandteile
+ icon: ic_antifeature_nonfreeassets.xml
+ name: Nicht-quelloffene Bestandteile
+NonFreeDep:
+ description: Diese App ist abhängig von anderen nicht-quelloffenen Apps
+ icon: ic_antifeature_nonfreedep.xml
+ name: Nicht-quelloffene Abhängigkeiten
+NonFreeNet:
+ description: Diese App bewirbt nicht-quelloffene Netzwerkdienste
+ icon: ic_antifeature_nonfreenet.xml
+ name: Nicht-quelloffene Netzwerkdienste
+Tracking:
+ description: Diese App verfolgt und versendet Ihre Aktivitäten
+ icon: ic_antifeature_tracking.xml
+ name: Tracking
+UpstreamNonFree:
+ description: Der Originalcode ist nicht völlig quelloffen
+ icon: ic_antifeature_upstreamnonfree.xml
+ name: Originalcode nicht-quelloffen
diff --git a/tests/config/fa/antiFeatures.yml b/tests/config/fa/antiFeatures.yml
new file mode 100644
index 00000000..554dcee9
--- /dev/null
+++ b/tests/config/fa/antiFeatures.yml
@@ -0,0 +1,43 @@
+Ads:
+ description: این کاره دارای تبلیغات است
+ icon: ic_antifeature_ads.xml
+ name: تبلیغات
+DisabledAlgorithm:
+ description: این کاره، امضای امنیتی ضعیفی دارد
+ icon: ic_antifeature_disabledalgorithm.xml
+ name: امضا شده با الگوریتمی ناامن
+KnownVuln:
+ description: این کاره، آسیبپذیری امنیتی شناختهشدهای دارد
+ icon: ic_antifeature_knownvuln.xml
+ name: آسیبپذیری شناخته
+NSFW:
+ description: این کاره محتوایی دارد که نباید عمومی شده یا همهحا نمایان باشد
+ icon: ic_antifeature_nsfw.xml
+ name: NSFW
+NoSourceSince:
+ icon: ic_antifeature_nosourcesince.xml
+ name: کد مبدأ دیگر در دسترس نیست. بهروز رسانی ناممکن است.
+NonFreeAdd:
+ description: این کاره، افزونههای ناآزاد را تبلیغ میکند
+ icon: ic_antifeature_nonfreeadd.xml
+ name: افزونههای ناآزاد
+NonFreeAssets:
+ description: این کاره دارای بخشهای ناآزاد است
+ icon: ic_antifeature_nonfreeassets.xml
+ name: بخشهای ناآزاد
+NonFreeDep:
+ description: این کاره به دیگر کارههای ناآزاد وابسته است
+ icon: ic_antifeature_nonfreedep.xml
+ name: وابستگیهای ناآزاد
+NonFreeNet:
+ description: این کاره، خدمات شبکههای ناآزاد را ترویج میکند
+ icon: ic_antifeature_nonfreenet.xml
+ name: خدمات شبکهای ناآزاد
+Tracking:
+ description: این کاره، فعّالیتتان را ردیابی و گزارش میکند
+ icon: ic_antifeature_tracking.xml
+ name: ردیابی
+UpstreamNonFree:
+ description: کد مبدأ بالادستی کاملاً آزاد نیست
+ icon: ic_antifeature_upstreamnonfree.xml
+ name: بالادست ناآزاد
diff --git a/tests/config/ic_antifeature_ads.xml b/tests/config/ic_antifeature_ads.xml
new file mode 100644
index 00000000..99eb6f5e
--- /dev/null
+++ b/tests/config/ic_antifeature_ads.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/tests/config/ic_antifeature_disabledalgorithm.xml b/tests/config/ic_antifeature_disabledalgorithm.xml
new file mode 100644
index 00000000..0b231666
--- /dev/null
+++ b/tests/config/ic_antifeature_disabledalgorithm.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/tests/config/ic_antifeature_knownvuln.xml b/tests/config/ic_antifeature_knownvuln.xml
new file mode 100644
index 00000000..cfa81345
--- /dev/null
+++ b/tests/config/ic_antifeature_knownvuln.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/tests/config/ic_antifeature_nonfreeadd.xml b/tests/config/ic_antifeature_nonfreeadd.xml
new file mode 100644
index 00000000..adca82ca
--- /dev/null
+++ b/tests/config/ic_antifeature_nonfreeadd.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/tests/config/ic_antifeature_nonfreeassets.xml b/tests/config/ic_antifeature_nonfreeassets.xml
new file mode 100644
index 00000000..20619b4e
--- /dev/null
+++ b/tests/config/ic_antifeature_nonfreeassets.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/tests/config/ic_antifeature_nonfreedep.xml b/tests/config/ic_antifeature_nonfreedep.xml
new file mode 100644
index 00000000..2b37b8d8
--- /dev/null
+++ b/tests/config/ic_antifeature_nonfreedep.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/tests/config/ic_antifeature_nonfreenet.xml b/tests/config/ic_antifeature_nonfreenet.xml
new file mode 100644
index 00000000..4d726f09
--- /dev/null
+++ b/tests/config/ic_antifeature_nonfreenet.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/config/ic_antifeature_nosourcesince.xml b/tests/config/ic_antifeature_nosourcesince.xml
new file mode 100644
index 00000000..b524fc42
--- /dev/null
+++ b/tests/config/ic_antifeature_nosourcesince.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/tests/config/ic_antifeature_nsfw.xml b/tests/config/ic_antifeature_nsfw.xml
new file mode 100644
index 00000000..a1bf3b84
--- /dev/null
+++ b/tests/config/ic_antifeature_nsfw.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/tests/config/ic_antifeature_tracking.xml b/tests/config/ic_antifeature_tracking.xml
new file mode 100644
index 00000000..984fe20c
--- /dev/null
+++ b/tests/config/ic_antifeature_tracking.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/tests/config/ic_antifeature_upstreamnonfree.xml b/tests/config/ic_antifeature_upstreamnonfree.xml
new file mode 100644
index 00000000..f3598e67
--- /dev/null
+++ b/tests/config/ic_antifeature_upstreamnonfree.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/tests/config/ro/antiFeatures.yml b/tests/config/ro/antiFeatures.yml
new file mode 100644
index 00000000..97d61172
--- /dev/null
+++ b/tests/config/ro/antiFeatures.yml
@@ -0,0 +1,44 @@
+Ads:
+ description: Aplicația conține reclamă
+ icon: ic_antifeature_ads.xml
+ name: Reclame
+DisabledAlgorithm:
+ description: Aplicația are o semnătură slab securizată
+ icon: ic_antifeature_disabledalgorithm.xml
+ name: Algoritm nesigur semnătură
+KnownVuln:
+ description: Aplicația conține o vulnerabilitate de securitate cunoscută
+ icon: ic_antifeature_knownvuln.xml
+ name: Vulnerabilitate cunoscută
+NSFW:
+ description: Această aplicație conține conținut care nu ar trebui să fie făcut public
+ sau vizibil peste tot
+ icon: ic_antifeature_nsfw.xml
+ name: NSFW
+NoSourceSince:
+ icon: ic_antifeature_nosourcesince.xml
+ name: Codul sursă nu mai este disponibil, nu mai există posibilitatea de a actualiza.
+NonFreeAdd:
+ description: Aplicația promovează anexe ce nu sunt software liber
+ icon: ic_antifeature_nonfreeadd.xml
+ name: Anexe ne-libere
+NonFreeAssets:
+ description: Aceasta aplicație conține resurse ce nu sunt la disponibile la liber
+ icon: ic_antifeature_nonfreeassets.xml
+ name: Resurse ne-libere
+NonFreeDep:
+ description: Aplicația depinde de alte aplicații ce nu sunt software liber
+ icon: ic_antifeature_nonfreedep.xml
+ name: Dependențe ne-libere
+NonFreeNet:
+ description: Aplicația promovează servicii de rețea ce nu sunt accesibile la liber
+ icon: ic_antifeature_nonfreenet.xml
+ name: Servicii de rețea ne-libere
+Tracking:
+ description: Aplicația îți înregistrează și raportează activitatea undeva
+ icon: ic_antifeature_tracking.xml
+ name: Urmărire
+UpstreamNonFree:
+ description: Codul sursa originar nu este în totalitatea lui software liber
+ icon: ic_antifeature_upstreamnonfree.xml
+ name: Surse ne-libere
diff --git a/tests/config/zh-rCN/antiFeatures.yml b/tests/config/zh-rCN/antiFeatures.yml
new file mode 100644
index 00000000..a1b287b9
--- /dev/null
+++ b/tests/config/zh-rCN/antiFeatures.yml
@@ -0,0 +1,43 @@
+Ads:
+ description: 此应用包含广告
+ icon: ic_antifeature_ads.xml
+ name: 广告
+DisabledAlgorithm:
+ description: 此应用的安全签名较弱
+ icon: ic_antifeature_disabledalgorithm.xml
+ name: 使用不安全算法签名
+KnownVuln:
+ description: 此应用包含已知的安全漏洞
+ icon: ic_antifeature_knownvuln.xml
+ name: 含有已知漏洞
+NSFW:
+ description: 此应用包含不应宣扬或随处可见的内容
+ icon: ic_antifeature_nsfw.xml
+ name: NSFW
+NoSourceSince:
+ icon: ic_antifeature_nosourcesince.xml
+ name: 源代码不再可用,无法更新。
+NonFreeAdd:
+ description: 此应用推广非自由的附加组件
+ icon: ic_antifeature_nonfreeadd.xml
+ name: 非自由附加组件
+NonFreeAssets:
+ description: 此应用包含非自由资源
+ icon: ic_antifeature_nonfreeassets.xml
+ name: 非自由资产
+NonFreeDep:
+ description: 此应用依赖于其它非自由应用
+ icon: ic_antifeature_nonfreedep.xml
+ name: 非自由依赖项
+NonFreeNet:
+ description: 此应用推广非自由的网络服务
+ icon: ic_antifeature_nonfreenet.xml
+ name: 非自由网络服务
+Tracking:
+ description: 此应用会记录并报告你的活动
+ icon: ic_antifeature_tracking.xml
+ name: 跟踪用户
+UpstreamNonFree:
+ description: 上游源代码不是完全自由的
+ icon: ic_antifeature_upstreamnonfree.xml
+ name: 上游代码非自由
diff --git a/tests/lint.TestCase b/tests/lint.TestCase
index 6fbe76cf..544b18c8 100755
--- a/tests/lint.TestCase
+++ b/tests/lint.TestCase
@@ -132,23 +132,9 @@ class LintTest(unittest.TestCase):
app.Description = 'These are some back'
fields = {
- 'AntiFeatures': {
- 'good': [
- [
- 'KnownVuln',
- ],
- ['NonFreeNet', 'KnownVuln'],
- ],
- 'bad': [
- 'KnownVuln',
- 'NonFreeNet,KnownVuln',
- ],
- },
'Categories': {
'good': [
- [
- 'Sports & Health',
- ],
+ ['Sports & Health'],
['Multimedia', 'Graphics'],
],
'bad': [
@@ -328,6 +314,53 @@ class LintTest(unittest.TestCase):
self.assertFalse(anywarns)
+class LintAntiFeaturesTest(unittest.TestCase):
+ def setUp(self):
+ self.basedir = localmodule / 'tests'
+ os.chdir(self.basedir)
+ fdroidserver.common.config = dict()
+ fdroidserver.lint.load_antiFeatures_config()
+
+ def test_check_antiFeatures_empty(self):
+ app = fdroidserver.metadata.App()
+ self.assertEqual([], list(fdroidserver.lint.check_antiFeatures(app)))
+
+ def test_check_antiFeatures_empty_AntiFeatures(self):
+ app = fdroidserver.metadata.App()
+ app['AntiFeatures'] = []
+ self.assertEqual([], list(fdroidserver.lint.check_antiFeatures(app)))
+
+ def test_check_antiFeatures(self):
+ app = fdroidserver.metadata.App()
+ app['AntiFeatures'] = ['Ads', 'UpstreamNonFree']
+ self.assertEqual([], list(fdroidserver.lint.check_antiFeatures(app)))
+
+ def test_check_antiFeatures_fails_one(self):
+ app = fdroidserver.metadata.App()
+ app['AntiFeatures'] = ['Ad']
+ self.assertEqual(1, len(list(fdroidserver.lint.check_antiFeatures(app))))
+
+ def test_check_antiFeatures_fails_many(self):
+ app = fdroidserver.metadata.App()
+ app['AntiFeatures'] = ['Adss', 'Tracker', 'NoSourceSince', 'FAKE', 'NonFree']
+ self.assertEqual(4, len(list(fdroidserver.lint.check_antiFeatures(app))))
+
+ def test_check_antiFeatures_build_empty(self):
+ app = fdroidserver.metadata.App()
+ app['Builds'] = [{'antifeatures': []}]
+ self.assertEqual([], list(fdroidserver.lint.check_antiFeatures(app)))
+
+ def test_check_antiFeatures_build(self):
+ app = fdroidserver.metadata.App()
+ app['Builds'] = [{'antifeatures': ['Tracking']}]
+ self.assertEqual(0, len(list(fdroidserver.lint.check_antiFeatures(app))))
+
+ def test_check_antiFeatures_build_fail(self):
+ app = fdroidserver.metadata.App()
+ app['Builds'] = [{'antifeatures': ['Ads', 'Tracker']}]
+ self.assertEqual(1, len(list(fdroidserver.lint.check_antiFeatures(app))))
+
+
if __name__ == "__main__":
parser = optparse.OptionParser()
parser.add_option(
diff --git a/tests/metadata.TestCase b/tests/metadata.TestCase
index b4301fba..b90abf9f 100755
--- a/tests/metadata.TestCase
+++ b/tests/metadata.TestCase
@@ -14,8 +14,9 @@ import tempfile
import textwrap
from collections import OrderedDict
from pathlib import Path
+from unittest import mock
-from testcommon import TmpCwd
+from testcommon import TmpCwd, mkdtemp
localmodule = Path(__file__).resolve().parent.parent
print('localmodule: ' + str(localmodule))
@@ -40,10 +41,13 @@ class MetadataTest(unittest.TestCase):
logging.basicConfig(level=logging.DEBUG)
self.basedir = localmodule / 'tests'
os.chdir(self.basedir)
+ self._td = mkdtemp()
+ self.testdir = self._td.name
fdroidserver.metadata.warnings_action = 'error'
def tearDown(self):
# auto-generated dirs by functions, not tests, so they are not always cleaned up
+ self._td.cleanup()
try:
os.rmdir("srclibs")
except OSError:
@@ -178,26 +182,6 @@ class MetadataTest(unittest.TestCase):
'fake.app.id',
)
- def test_check_metadata_AntiFeatures(self):
- fdroidserver.metadata.warnings_action = 'error'
-
- app = fdroidserver.metadata.App()
- self.assertIsNone(metadata.check_metadata(app))
-
- app['AntiFeatures'] = []
- self.assertIsNone(metadata.check_metadata(app))
-
- app['AntiFeatures'] = ['Ads', 'UpstreamNonFree']
- self.assertIsNone(metadata.check_metadata(app))
-
- app['AntiFeatures'] = ['Ad']
- with self.assertRaises(fdroidserver.exception.MetaDataException):
- metadata.check_metadata(app)
-
- app['AntiFeatures'] = ['Adss']
- with self.assertRaises(fdroidserver.exception.MetaDataException):
- metadata.check_metadata(app)
-
def test_valid_funding_yml_regex(self):
"""Check the regex can find all the cases"""
with (self.basedir / 'funding-usernames.yaml').open() as fp:
@@ -218,8 +202,10 @@ class MetadataTest(unittest.TestCase):
m, 'this is a valid %s username: {%s}' % (k, entry)
)
- def test_read_metadata(self):
+ @mock.patch('git.Repo')
+ def test_read_metadata(self, git_repo):
"""Read specified metadata files included in tests/, compare to stored output"""
+
self.maxDiff = None
config = dict()
@@ -248,7 +234,27 @@ class MetadataTest(unittest.TestCase):
# yaml.register_class(metadata.Build)
# yaml.dump(frommeta, fp)
- def test_rewrite_yaml_fakeotaupdate(self):
+ @mock.patch('git.Repo')
+ def test_metadata_overrides_dot_fdroid_yml(self, git_Repo):
+ """Fields in metadata files should override anything in .fdroid.yml."""
+ app = metadata.parse_metadata('metadata/info.guardianproject.urzip.yml')
+ self.assertEqual(app['Summary'], '一个实用工具,获取已安装在您的设备上的应用的有关信息')
+
+ def test_dot_fdroid_yml_works_without_git(self):
+ """Parsing should work if .fdroid.yml is present and it is not a git repo."""
+ os.chdir(self.testdir)
+ yml = Path('metadata/test.yml')
+ yml.parent.mkdir()
+ with yml.open('w') as fp:
+ fp.write('Repo: https://example.com/not/git/or/anything')
+ fdroid_yml = Path('build/test/.fdroid.yml')
+ fdroid_yml.parent.mkdir(parents=True)
+ with fdroid_yml.open('w') as fp:
+ fp.write('OpenCollective: test')
+ metadata.parse_metadata(yml) # should not throw an exception
+
+ @mock.patch('git.Repo')
+ def test_rewrite_yaml_fakeotaupdate(self, git_Repo):
with tempfile.TemporaryDirectory() as testdir:
testdir = Path(testdir)
fdroidserver.common.config = {'accepted_formats': ['yml']}
@@ -270,7 +276,8 @@ class MetadataTest(unittest.TestCase):
(Path('metadata-rewrite-yml') / file_name).read_text(encoding='utf-8'),
)
- def test_rewrite_yaml_fdroidclient(self):
+ @mock.patch('git.Repo')
+ def test_rewrite_yaml_fdroidclient(self, git_Repo):
with tempfile.TemporaryDirectory() as testdir:
testdir = Path(testdir)
fdroidserver.common.config = {'accepted_formats': ['yml']}
@@ -291,7 +298,8 @@ class MetadataTest(unittest.TestCase):
(Path('metadata-rewrite-yml') / file_name).read_text(encoding='utf-8'),
)
- def test_rewrite_yaml_special_build_params(self):
+ @mock.patch('git.Repo')
+ def test_rewrite_yaml_special_build_params(self, git_Repo):
with tempfile.TemporaryDirectory() as testdir:
testdir = Path(testdir)
@@ -450,7 +458,6 @@ class MetadataTest(unittest.TestCase):
with self.assertRaises(TypeError):
metadata.parse_yaml_metadata(mf)
- mf.name = 'mock_filename.yaml'
self.assertEqual(fdroidserver.metadata.parse_yaml_metadata(mf), dict())
def test_parse_yaml_metadata_unknown_app_field(self):
@@ -481,7 +488,9 @@ class MetadataTest(unittest.TestCase):
with self.assertRaises(MetaDataException):
fdroidserver.metadata.parse_yaml_metadata(mf)
- def test_parse_yaml_metadata_continue_on_warning(self):
+ @mock.patch('logging.warning')
+ @mock.patch('logging.error')
+ def test_parse_yaml_metadata_continue_on_warning(self, _error, _warning):
"""When errors are disabled, parsing should provide something that can work.
When errors are disabled, then it should try to give data that
@@ -495,6 +504,8 @@ class MetadataTest(unittest.TestCase):
fdroidserver.metadata.warnings_action = None
mf = _get_mock_mf('[AntiFeatures: Tracking]')
self.assertEqual(fdroidserver.metadata.parse_yaml_metadata(mf), dict())
+ _warning.assert_called_once()
+ _error.assert_called_once()
def test_parse_yaml_srclib_corrupt_file(self):
with tempfile.TemporaryDirectory() as testdir:
diff --git a/tests/repo/entry.json b/tests/repo/entry.json
index 2fb77d2a..f13e622b 100644
--- a/tests/repo/entry.json
+++ b/tests/repo/entry.json
@@ -3,8 +3,8 @@
"version": 20002,
"index": {
"name": "/index-v2.json",
- "sha256": "07fa4500736ae77fcc6434e4d70ab315b8e018aef52c2afca9f2834ddc73747d",
- "size": 32946,
+ "sha256": "a3c7e88a522a7228937e5c3d760fc239e3578e292035d88478d32fec9ff5eb54",
+ "size": 52314,
"numPackages": 10
},
"diffs": {}
diff --git a/tests/repo/index-v2.json b/tests/repo/index-v2.json
index 7addd167..b59f17bf 100644
--- a/tests/repo/index-v2.json
+++ b/tests/repo/index-v2.json
@@ -27,6 +27,477 @@
}
],
"timestamp": 1676634233000,
+ "antiFeatures": {
+ "Ads": {
+ "description": {
+ "de": "Diese App enthält Werbung",
+ "en-US": "This app contains advertising",
+ "fa": "این کاره دارای تبلیغات است",
+ "ro": "Aplicația conține reclamă",
+ "zh-rCN": "此应用包含广告"
+ },
+ "icon": {
+ "de": {
+ "name": "/icons/ic_antifeature_ads.xml",
+ "sha256": "b333528573134c5de73484862a1b567a0bdfd6878d183f8500287abadc0ba60e",
+ "size": 1564
+ },
+ "en-US": {
+ "name": "/icons/ic_antifeature_ads.xml",
+ "sha256": "b333528573134c5de73484862a1b567a0bdfd6878d183f8500287abadc0ba60e",
+ "size": 1564
+ },
+ "fa": {
+ "name": "/icons/ic_antifeature_ads.xml",
+ "sha256": "b333528573134c5de73484862a1b567a0bdfd6878d183f8500287abadc0ba60e",
+ "size": 1564
+ },
+ "ro": {
+ "name": "/icons/ic_antifeature_ads.xml",
+ "sha256": "b333528573134c5de73484862a1b567a0bdfd6878d183f8500287abadc0ba60e",
+ "size": 1564
+ },
+ "zh-rCN": {
+ "name": "/icons/ic_antifeature_ads.xml",
+ "sha256": "b333528573134c5de73484862a1b567a0bdfd6878d183f8500287abadc0ba60e",
+ "size": 1564
+ }
+ },
+ "name": {
+ "de": "Werbung",
+ "en-US": "Ads",
+ "fa": "تبلیغات",
+ "ro": "Reclame",
+ "zh-rCN": "广告"
+ }
+ },
+ "DisabledAlgorithm": {
+ "description": {
+ "de": "Diese App hat eine schwache Sicherheitssignatur",
+ "en-US": "This app has a weak security signature",
+ "fa": "این کاره، امضای امنیتی ضعیفی دارد",
+ "ro": "Aplicația are o semnătură slab securizată",
+ "zh-rCN": "此应用的安全签名较弱"
+ },
+ "icon": {
+ "de": {
+ "name": "/icons/ic_antifeature_disabledalgorithm.xml",
+ "sha256": "94dea590c7c0aa37d351ab62a69fc7eefbc2cdbb84b79df3934c2e9332e1dcfb",
+ "size": 2313
+ },
+ "en-US": {
+ "name": "/icons/ic_antifeature_disabledalgorithm.xml",
+ "sha256": "94dea590c7c0aa37d351ab62a69fc7eefbc2cdbb84b79df3934c2e9332e1dcfb",
+ "size": 2313
+ },
+ "fa": {
+ "name": "/icons/ic_antifeature_disabledalgorithm.xml",
+ "sha256": "94dea590c7c0aa37d351ab62a69fc7eefbc2cdbb84b79df3934c2e9332e1dcfb",
+ "size": 2313
+ },
+ "ro": {
+ "name": "/icons/ic_antifeature_disabledalgorithm.xml",
+ "sha256": "94dea590c7c0aa37d351ab62a69fc7eefbc2cdbb84b79df3934c2e9332e1dcfb",
+ "size": 2313
+ },
+ "zh-rCN": {
+ "name": "/icons/ic_antifeature_disabledalgorithm.xml",
+ "sha256": "94dea590c7c0aa37d351ab62a69fc7eefbc2cdbb84b79df3934c2e9332e1dcfb",
+ "size": 2313
+ }
+ },
+ "name": {
+ "de": "Mit einem unsicheren Algorithmus signiert",
+ "en-US": "Signed Using An Unsafe Algorithm",
+ "fa": "امضا شده با الگوریتمی ناامن",
+ "ro": "Algoritm nesigur semnătură",
+ "zh-rCN": "使用不安全算法签名"
+ }
+ },
+ "KnownVuln": {
+ "description": {
+ "de": "Diese App enthält eine bekannte Sicherheitslücke",
+ "en-US": "This app contains a known security vulnerability",
+ "fa": "این کاره، آسیبپذیری امنیتی شناختهشدهای دارد",
+ "ro": "Aplicația conține o vulnerabilitate de securitate cunoscută",
+ "zh-rCN": "此应用包含已知的安全漏洞"
+ },
+ "icon": {
+ "de": {
+ "name": "/icons/ic_antifeature_knownvuln.xml",
+ "sha256": "743ddcad0120896b03bf62bca9b3b9902878ac9366959a0b77b2c50beeb37f9d",
+ "size": 1415
+ },
+ "en-US": {
+ "name": "/icons/ic_antifeature_knownvuln.xml",
+ "sha256": "743ddcad0120896b03bf62bca9b3b9902878ac9366959a0b77b2c50beeb37f9d",
+ "size": 1415
+ },
+ "fa": {
+ "name": "/icons/ic_antifeature_knownvuln.xml",
+ "sha256": "743ddcad0120896b03bf62bca9b3b9902878ac9366959a0b77b2c50beeb37f9d",
+ "size": 1415
+ },
+ "ro": {
+ "name": "/icons/ic_antifeature_knownvuln.xml",
+ "sha256": "743ddcad0120896b03bf62bca9b3b9902878ac9366959a0b77b2c50beeb37f9d",
+ "size": 1415
+ },
+ "zh-rCN": {
+ "name": "/icons/ic_antifeature_knownvuln.xml",
+ "sha256": "743ddcad0120896b03bf62bca9b3b9902878ac9366959a0b77b2c50beeb37f9d",
+ "size": 1415
+ }
+ },
+ "name": {
+ "de": "Bekannte Sicherheitslücke",
+ "en-US": "Known Vulnerability",
+ "fa": "آسیبپذیری شناخته",
+ "ro": "Vulnerabilitate cunoscută",
+ "zh-rCN": "含有已知漏洞"
+ }
+ },
+ "NSFW": {
+ "description": {
+ "de": "Diese App enthält Inhalte, die nicht überall veröffentlicht oder sichtbar sein sollten",
+ "en-US": "This app contains content that should not be publicized or visible everywhere",
+ "fa": "این کاره محتوایی دارد که نباید عمومی شده یا همهحا نمایان باشد",
+ "ro": "Această aplicație conține conținut care nu ar trebui să fie făcut public sau vizibil peste tot",
+ "zh-rCN": "此应用包含不应宣扬或随处可见的内容"
+ },
+ "icon": {
+ "de": {
+ "name": "/icons/ic_antifeature_nsfw.xml",
+ "sha256": "acab2a7a846700529cd7f2b7a7980f7d04a291f22db8434f3e966f7350ed1465",
+ "size": 871
+ },
+ "en-US": {
+ "name": "/icons/ic_antifeature_nsfw.xml",
+ "sha256": "acab2a7a846700529cd7f2b7a7980f7d04a291f22db8434f3e966f7350ed1465",
+ "size": 871
+ },
+ "fa": {
+ "name": "/icons/ic_antifeature_nsfw.xml",
+ "sha256": "acab2a7a846700529cd7f2b7a7980f7d04a291f22db8434f3e966f7350ed1465",
+ "size": 871
+ },
+ "ro": {
+ "name": "/icons/ic_antifeature_nsfw.xml",
+ "sha256": "acab2a7a846700529cd7f2b7a7980f7d04a291f22db8434f3e966f7350ed1465",
+ "size": 871
+ },
+ "zh-rCN": {
+ "name": "/icons/ic_antifeature_nsfw.xml",
+ "sha256": "acab2a7a846700529cd7f2b7a7980f7d04a291f22db8434f3e966f7350ed1465",
+ "size": 871
+ }
+ },
+ "name": {
+ "de": "NSFW",
+ "en-US": "NSFW",
+ "fa": "NSFW",
+ "ro": "NSFW",
+ "zh-rCN": "NSFW"
+ }
+ },
+ "NoSourceSince": {
+ "description": {
+ "en-US": "The source code is no longer available, no updates possible."
+ },
+ "icon": {
+ "de": {
+ "name": "/icons/ic_antifeature_nosourcesince.xml",
+ "sha256": "69c880b075967fe9598c777e18d600e1c1612bf061111911421fe8f6b9d88d4f",
+ "size": 1102
+ },
+ "en-US": {
+ "name": "/icons/ic_antifeature_nosourcesince.xml",
+ "sha256": "69c880b075967fe9598c777e18d600e1c1612bf061111911421fe8f6b9d88d4f",
+ "size": 1102
+ },
+ "fa": {
+ "name": "/icons/ic_antifeature_nosourcesince.xml",
+ "sha256": "69c880b075967fe9598c777e18d600e1c1612bf061111911421fe8f6b9d88d4f",
+ "size": 1102
+ },
+ "ro": {
+ "name": "/icons/ic_antifeature_nosourcesince.xml",
+ "sha256": "69c880b075967fe9598c777e18d600e1c1612bf061111911421fe8f6b9d88d4f",
+ "size": 1102
+ },
+ "zh-rCN": {
+ "name": "/icons/ic_antifeature_nosourcesince.xml",
+ "sha256": "69c880b075967fe9598c777e18d600e1c1612bf061111911421fe8f6b9d88d4f",
+ "size": 1102
+ }
+ },
+ "name": {
+ "de": "Der Quellcode ist nicht mehr erhältlich, keine Aktualisierungen möglich.",
+ "en-US": "Newer Source Not Available",
+ "fa": "کد مبدأ دیگر در دسترس نیست. بهروز رسانی ناممکن است.",
+ "ro": "Codul sursă nu mai este disponibil, nu mai există posibilitatea de a actualiza.",
+ "zh-rCN": "源代码不再可用,无法更新。"
+ }
+ },
+ "NonFreeAdd": {
+ "description": {
+ "de": "Diese App bewirbt nicht-quelloffene Erweiterungen",
+ "en-US": "This app promotes non-free add-ons",
+ "fa": "این کاره، افزونههای ناآزاد را تبلیغ میکند",
+ "ro": "Aplicația promovează anexe ce nu sunt software liber",
+ "zh-rCN": "此应用推广非自由的附加组件"
+ },
+ "icon": {
+ "de": {
+ "name": "/icons/ic_antifeature_nonfreeadd.xml",
+ "sha256": "a1d1f2070bdaabf80ca5a55bccef98c82031ea2f31cc040be5ec009f44ddeef2",
+ "size": 1846
+ },
+ "en-US": {
+ "name": "/icons/ic_antifeature_nonfreeadd.xml",
+ "sha256": "a1d1f2070bdaabf80ca5a55bccef98c82031ea2f31cc040be5ec009f44ddeef2",
+ "size": 1846
+ },
+ "fa": {
+ "name": "/icons/ic_antifeature_nonfreeadd.xml",
+ "sha256": "a1d1f2070bdaabf80ca5a55bccef98c82031ea2f31cc040be5ec009f44ddeef2",
+ "size": 1846
+ },
+ "ro": {
+ "name": "/icons/ic_antifeature_nonfreeadd.xml",
+ "sha256": "a1d1f2070bdaabf80ca5a55bccef98c82031ea2f31cc040be5ec009f44ddeef2",
+ "size": 1846
+ },
+ "zh-rCN": {
+ "name": "/icons/ic_antifeature_nonfreeadd.xml",
+ "sha256": "a1d1f2070bdaabf80ca5a55bccef98c82031ea2f31cc040be5ec009f44ddeef2",
+ "size": 1846
+ }
+ },
+ "name": {
+ "de": "Nicht-quelloffene Erweiterungen",
+ "en-US": "Non-Free Addons",
+ "fa": "افزونههای ناآزاد",
+ "ro": "Anexe ne-libere",
+ "zh-rCN": "非自由附加组件"
+ }
+ },
+ "NonFreeAssets": {
+ "description": {
+ "de": "Diese App enthält nicht-quelloffene Bestandteile",
+ "en-US": "This app contains non-free assets",
+ "fa": "این کاره دارای بخشهای ناآزاد است",
+ "ro": "Aceasta aplicație conține resurse ce nu sunt la disponibile la liber",
+ "zh-rCN": "此应用包含非自由资源"
+ },
+ "icon": {
+ "de": {
+ "name": "/icons/ic_antifeature_nonfreeassets.xml",
+ "sha256": "b39fe384386fc67fb30fa2f91402594110e2e42c961d76adc93141b8bd774008",
+ "size": 1784
+ },
+ "en-US": {
+ "name": "/icons/ic_antifeature_nonfreeassets.xml",
+ "sha256": "b39fe384386fc67fb30fa2f91402594110e2e42c961d76adc93141b8bd774008",
+ "size": 1784
+ },
+ "fa": {
+ "name": "/icons/ic_antifeature_nonfreeassets.xml",
+ "sha256": "b39fe384386fc67fb30fa2f91402594110e2e42c961d76adc93141b8bd774008",
+ "size": 1784
+ },
+ "ro": {
+ "name": "/icons/ic_antifeature_nonfreeassets.xml",
+ "sha256": "b39fe384386fc67fb30fa2f91402594110e2e42c961d76adc93141b8bd774008",
+ "size": 1784
+ },
+ "zh-rCN": {
+ "name": "/icons/ic_antifeature_nonfreeassets.xml",
+ "sha256": "b39fe384386fc67fb30fa2f91402594110e2e42c961d76adc93141b8bd774008",
+ "size": 1784
+ }
+ },
+ "name": {
+ "de": "Nicht-quelloffene Bestandteile",
+ "en-US": "Non-Free Assets",
+ "fa": "بخشهای ناآزاد",
+ "ro": "Resurse ne-libere",
+ "zh-rCN": "非自由资产"
+ }
+ },
+ "NonFreeDep": {
+ "description": {
+ "de": "Diese App ist abhängig von anderen nicht-quelloffenen Apps",
+ "en-US": "This app depends on other non-free apps",
+ "fa": "این کاره به دیگر کارههای ناآزاد وابسته است",
+ "ro": "Aplicația depinde de alte aplicații ce nu sunt software liber",
+ "zh-rCN": "此应用依赖于其它非自由应用"
+ },
+ "icon": {
+ "de": {
+ "name": "/icons/ic_antifeature_nonfreedep.xml",
+ "sha256": "c1b4052a8f58125b2120d9ca07adb725d47bfa7cfcea80c4d6bbbc432b5cb83a",
+ "size": 1396
+ },
+ "en-US": {
+ "name": "/icons/ic_antifeature_nonfreedep.xml",
+ "sha256": "c1b4052a8f58125b2120d9ca07adb725d47bfa7cfcea80c4d6bbbc432b5cb83a",
+ "size": 1396
+ },
+ "fa": {
+ "name": "/icons/ic_antifeature_nonfreedep.xml",
+ "sha256": "c1b4052a8f58125b2120d9ca07adb725d47bfa7cfcea80c4d6bbbc432b5cb83a",
+ "size": 1396
+ },
+ "ro": {
+ "name": "/icons/ic_antifeature_nonfreedep.xml",
+ "sha256": "c1b4052a8f58125b2120d9ca07adb725d47bfa7cfcea80c4d6bbbc432b5cb83a",
+ "size": 1396
+ },
+ "zh-rCN": {
+ "name": "/icons/ic_antifeature_nonfreedep.xml",
+ "sha256": "c1b4052a8f58125b2120d9ca07adb725d47bfa7cfcea80c4d6bbbc432b5cb83a",
+ "size": 1396
+ }
+ },
+ "name": {
+ "de": "Nicht-quelloffene Abhängigkeiten",
+ "en-US": "Non-Free Dependencies",
+ "fa": "وابستگیهای ناآزاد",
+ "ro": "Dependențe ne-libere",
+ "zh-rCN": "非自由依赖项"
+ }
+ },
+ "NonFreeNet": {
+ "description": {
+ "de": "Diese App bewirbt nicht-quelloffene Netzwerkdienste",
+ "en-US": "This app promotes or depends entirely on a non-free network service",
+ "fa": "این کاره، خدمات شبکههای ناآزاد را ترویج میکند",
+ "ro": "Aplicația promovează servicii de rețea ce nu sunt accesibile la liber",
+ "zh-rCN": "此应用推广非自由的网络服务"
+ },
+ "icon": {
+ "de": {
+ "name": "/icons/ic_antifeature_nonfreenet.xml",
+ "sha256": "7fff45c847ed2ecc94e85ba2341685c8f113fa5fdf7267a25637dc38ee0275f6",
+ "size": 3038
+ },
+ "en-US": {
+ "name": "/icons/ic_antifeature_nonfreenet.xml",
+ "sha256": "7fff45c847ed2ecc94e85ba2341685c8f113fa5fdf7267a25637dc38ee0275f6",
+ "size": 3038
+ },
+ "fa": {
+ "name": "/icons/ic_antifeature_nonfreenet.xml",
+ "sha256": "7fff45c847ed2ecc94e85ba2341685c8f113fa5fdf7267a25637dc38ee0275f6",
+ "size": 3038
+ },
+ "ro": {
+ "name": "/icons/ic_antifeature_nonfreenet.xml",
+ "sha256": "7fff45c847ed2ecc94e85ba2341685c8f113fa5fdf7267a25637dc38ee0275f6",
+ "size": 3038
+ },
+ "zh-rCN": {
+ "name": "/icons/ic_antifeature_nonfreenet.xml",
+ "sha256": "7fff45c847ed2ecc94e85ba2341685c8f113fa5fdf7267a25637dc38ee0275f6",
+ "size": 3038
+ }
+ },
+ "name": {
+ "de": "Nicht-quelloffene Netzwerkdienste",
+ "en-US": "Non-Free Network Services",
+ "fa": "خدمات شبکهای ناآزاد",
+ "ro": "Servicii de rețea ne-libere",
+ "zh-rCN": "非自由网络服务"
+ }
+ },
+ "Tracking": {
+ "description": {
+ "de": "Diese App verfolgt und versendet Ihre Aktivitäten",
+ "en-US": "This app tracks and reports your activity",
+ "fa": "این کاره، فعّالیتتان را ردیابی و گزارش میکند",
+ "ro": "Aplicația îți înregistrează și raportează activitatea undeva",
+ "zh-rCN": "此应用会记录并报告你的活动"
+ },
+ "icon": {
+ "de": {
+ "name": "/icons/ic_antifeature_tracking.xml",
+ "sha256": "4779337b5b0a12c4b4a8a83d0d8a994a2477460db702784df4c8d3e3730be961",
+ "size": 2493
+ },
+ "en-US": {
+ "name": "/icons/ic_antifeature_tracking.xml",
+ "sha256": "4779337b5b0a12c4b4a8a83d0d8a994a2477460db702784df4c8d3e3730be961",
+ "size": 2493
+ },
+ "fa": {
+ "name": "/icons/ic_antifeature_tracking.xml",
+ "sha256": "4779337b5b0a12c4b4a8a83d0d8a994a2477460db702784df4c8d3e3730be961",
+ "size": 2493
+ },
+ "ro": {
+ "name": "/icons/ic_antifeature_tracking.xml",
+ "sha256": "4779337b5b0a12c4b4a8a83d0d8a994a2477460db702784df4c8d3e3730be961",
+ "size": 2493
+ },
+ "zh-rCN": {
+ "name": "/icons/ic_antifeature_tracking.xml",
+ "sha256": "4779337b5b0a12c4b4a8a83d0d8a994a2477460db702784df4c8d3e3730be961",
+ "size": 2493
+ }
+ },
+ "name": {
+ "de": "Tracking",
+ "en-US": "Tracking",
+ "fa": "ردیابی",
+ "ro": "Urmărire",
+ "zh-rCN": "跟踪用户"
+ }
+ },
+ "UpstreamNonFree": {
+ "description": {
+ "de": "Der Originalcode ist nicht völlig quelloffen",
+ "en-US": "The upstream source code is not entirely Free",
+ "fa": "کد مبدأ بالادستی کاملاً آزاد نیست",
+ "ro": "Codul sursa originar nu este în totalitatea lui software liber",
+ "zh-rCN": "上游源代码不是完全自由的"
+ },
+ "icon": {
+ "de": {
+ "name": "/icons/ic_antifeature_upstreamnonfree.xml",
+ "sha256": "06a9af843ff56ecd7a270f98c0b19b3154edf3ffa854e6d50a84ef00d0ce1a86",
+ "size": 1442
+ },
+ "en-US": {
+ "name": "/icons/ic_antifeature_upstreamnonfree.xml",
+ "sha256": "06a9af843ff56ecd7a270f98c0b19b3154edf3ffa854e6d50a84ef00d0ce1a86",
+ "size": 1442
+ },
+ "fa": {
+ "name": "/icons/ic_antifeature_upstreamnonfree.xml",
+ "sha256": "06a9af843ff56ecd7a270f98c0b19b3154edf3ffa854e6d50a84ef00d0ce1a86",
+ "size": 1442
+ },
+ "ro": {
+ "name": "/icons/ic_antifeature_upstreamnonfree.xml",
+ "sha256": "06a9af843ff56ecd7a270f98c0b19b3154edf3ffa854e6d50a84ef00d0ce1a86",
+ "size": 1442
+ },
+ "zh-rCN": {
+ "name": "/icons/ic_antifeature_upstreamnonfree.xml",
+ "sha256": "06a9af843ff56ecd7a270f98c0b19b3154edf3ffa854e6d50a84ef00d0ce1a86",
+ "size": 1442
+ }
+ },
+ "name": {
+ "de": "Originalcode nicht-quelloffen",
+ "en-US": "Upstream Non-Free",
+ "fa": "بالادست ناآزاد",
+ "ro": "Surse ne-libere",
+ "zh-rCN": "上游代码非自由"
+ }
+ }
+ },
"requests": {
"install": [
"org.adaway"
diff --git a/tests/rewritemeta.TestCase b/tests/rewritemeta.TestCase
index 283e9c44..0bcc704e 100755
--- a/tests/rewritemeta.TestCase
+++ b/tests/rewritemeta.TestCase
@@ -9,15 +9,14 @@ import tempfile
import textwrap
from pathlib import Path
-from testcommon import TmpCwd
+from testcommon import TmpCwd, mkdtemp
localmodule = Path(__file__).resolve().parent.parent
print('localmodule: ' + str(localmodule))
if localmodule not in sys.path:
sys.path.insert(0, str(localmodule))
-from fdroidserver import common
-from fdroidserver import rewritemeta
+from fdroidserver import common, metadata, rewritemeta
class RewriteMetaTest(unittest.TestCase):
@@ -27,6 +26,123 @@ class RewriteMetaTest(unittest.TestCase):
logging.basicConfig(level=logging.DEBUG)
self.basedir = localmodule / 'tests'
os.chdir(self.basedir)
+ metadata.warnings_action = 'error'
+ self._td = mkdtemp()
+ self.testdir = self._td.name
+
+ def tearDown(self):
+ self._td.cleanup()
+
+ def test_remove_blank_flags_from_builds_com_politedroid_3(self):
+ """Unset fields in Builds: entries should be removed."""
+ appid = 'com.politedroid'
+ app = metadata.read_metadata({appid: -1})[appid]
+ builds = rewritemeta.remove_blank_flags_from_builds(app.get('Builds'))
+ self.assertEqual(
+ builds[0],
+ {
+ 'versionName': '1.2',
+ 'versionCode': 3,
+ 'commit': '6a548e4b19',
+ 'target': 'android-10',
+ 'antifeatures': [
+ 'KnownVuln',
+ 'UpstreamNonFree',
+ 'NonFreeAssets',
+ ],
+ },
+ )
+
+ def test_remove_blank_flags_from_builds_com_politedroid_4(self):
+ """Unset fields in Builds: entries should be removed."""
+ appid = 'com.politedroid'
+ app = metadata.read_metadata({appid: -1})[appid]
+ builds = rewritemeta.remove_blank_flags_from_builds(app.get('Builds'))
+ self.assertEqual(
+ builds[1],
+ {
+ 'versionName': '1.3',
+ 'versionCode': 4,
+ 'commit': 'ad865b57bf3ac59580f38485608a9b1dda4fa7dc',
+ 'target': 'android-15',
+ },
+ )
+
+ def test_remove_blank_flags_from_builds_no_builds(self):
+ """Unset fields in Builds: entries should be removed."""
+ self.assertEqual(
+ rewritemeta.remove_blank_flags_from_builds(None),
+ list(),
+ )
+ self.assertEqual(
+ rewritemeta.remove_blank_flags_from_builds(dict()),
+ list(),
+ )
+
+ def test_rewrite_no_builds(self):
+ os.chdir(self.testdir)
+ Path('metadata').mkdir()
+ with Path('metadata/a.yml').open('w') as f:
+ f.write('AutoName: a')
+ rewritemeta.main()
+ self.assertEqual(
+ Path('metadata/a.yml').read_text(encoding='utf-8'),
+ textwrap.dedent(
+ '''\
+ License: Unknown
+
+ AutoName: a
+
+ AutoUpdateMode: None
+ UpdateCheckMode: None
+ '''
+ ),
+ )
+
+ def test_rewrite_empty_build_field(self):
+ os.chdir(self.testdir)
+ Path('metadata').mkdir()
+ with Path('metadata/a.yml').open('w') as fp:
+ fp.write(
+ textwrap.dedent(
+ """
+ License: Apache-2.0
+ Builds:
+ - versionCode: 4
+ versionName: a
+ rm:
+ """
+ )
+ )
+ rewritemeta.main()
+ self.assertEqual(
+ Path('metadata/a.yml').read_text(encoding='utf-8'),
+ textwrap.dedent(
+ '''\
+ License: Apache-2.0
+
+ Builds:
+ - versionName: a
+ versionCode: 4
+
+ AutoUpdateMode: None
+ UpdateCheckMode: None
+ '''
+ ),
+ )
+
+ def test_remove_blank_flags_from_builds_app_with_special_build_params(self):
+ appid = 'app.with.special.build.params'
+ app = metadata.read_metadata({appid: -1})[appid]
+ builds = rewritemeta.remove_blank_flags_from_builds(app.get('Builds'))
+ self.assertEqual(
+ builds[-1],
+ {
+ 'versionName': '2.1.2',
+ 'versionCode': 51,
+ 'disable': 'Labelled as pre-release, so skipped',
+ },
+ )
def test_rewrite_scenario_trivial(self):
sys.argv = ['rewritemeta', 'a', 'b']
diff --git a/tests/run-tests b/tests/run-tests
index 5b114cdf..49acb5aa 100755
--- a/tests/run-tests
+++ b/tests/run-tests
@@ -213,16 +213,6 @@ if use_apksigner; then
fi
-#------------------------------------------------------------------------------#
-echo_header "TODO remove once buildserver image is upgraded to bullseye with apksigner"
-
-if java -version 2>&1 | grep -F 1.8.0; then
- echo "Skipping the rest because they require apksigner 30.0.0+ which does not run on Java8"
- echo SUCCESS
- exit
-fi
-
-
#------------------------------------------------------------------------------#
echo_header "test UTF-8 metadata"
@@ -271,7 +261,12 @@ REPOROOT=`create_test_dir`
GNUPGHOME=$REPOROOT/gnupghome
cd $REPOROOT
fdroid_init_with_prebuilt_keystore
-cp -a $WORKSPACE/tests/metadata $WORKSPACE/tests/repo $WORKSPACE/tests/stats $REPOROOT/
+cp -a \
+ $WORKSPACE/tests/config \
+ $WORKSPACE/tests/metadata \
+ $WORKSPACE/tests/repo \
+ $WORKSPACE/tests/stats \
+ $REPOROOT/
cp -a $WORKSPACE/tests/gnupghome $GNUPGHOME
chmod 0700 $GNUPGHOME
echo "install_list: org.adaway" >> config.yml
@@ -1139,7 +1134,7 @@ echo_header "setup a new repo from scratch using ANDROID_HOME with git mirror"
# fake git remote server for repo mirror
SERVER_GIT_MIRROR=`create_test_dir`
cd $SERVER_GIT_MIRROR
-$git init
+$git init --initial-branch=master
$git config receive.denyCurrentBranch updateInstead
REPOROOT=`create_test_dir`
@@ -1201,7 +1196,7 @@ SERVERWEBROOT=`create_test_dir`/fdroid
cd $OFFLINE_ROOT
mkdir binary_transparency
cd binary_transparency
-$git init
+$git init --initial-branch=master
# fake git remote server for binary transparency log
BINARY_TRANSPARENCY_REMOTE=`create_test_dir`
@@ -1209,7 +1204,7 @@ BINARY_TRANSPARENCY_REMOTE=`create_test_dir`
# fake git remote server for repo mirror
SERVER_GIT_MIRROR=`create_test_dir`
cd $SERVER_GIT_MIRROR
-$git init
+$git init --initial-branch=master
$git config receive.denyCurrentBranch updateInstead
cd $OFFLINE_ROOT