Merge branch 'clean-up-metadata' into 'master'

collection of cleanups, tests and minor refactoring related to metadata

See merge request fdroid/fdroidserver!1351
This commit is contained in:
Michael Pöhn 2023-05-04 12:55:38 +00:00
commit acc774f91e
8 changed files with 231 additions and 206 deletions

View file

@ -110,7 +110,7 @@ def check_tags(app, pattern):
vcs.gotorevision(None) vcs.gotorevision(None)
last_build = app.get_last_build() last_build = get_last_build_from_app(app)
try_init_submodules(app, last_build, vcs) try_init_submodules(app, last_build, vcs)
@ -252,10 +252,7 @@ def check_repomanifest(app, branch=None):
elif repotype == 'bzr': elif repotype == 'bzr':
vcs.gotorevision(None) vcs.gotorevision(None)
last_build = metadata.Build() last_build = get_last_build_from_app(app)
if app.get('Builds', []):
last_build = app.get('Builds', [])[-1]
try_init_submodules(app, last_build, vcs) try_init_submodules(app, last_build, vcs)
hpak = None hpak = None
@ -364,7 +361,7 @@ def possible_subdirs(app):
else: else:
build_dir = Path('build') / app.id build_dir = Path('build') / app.id
last_build = app.get_last_build() last_build = get_last_build_from_app(app)
for d in dirs_with_manifest(build_dir): for d in dirs_with_manifest(build_dir):
m_paths = common.manifest_paths(d, last_build.gradle) m_paths = common.manifest_paths(d, last_build.gradle)
@ -399,7 +396,7 @@ def fetch_autoname(app, tag):
except VCSException: except VCSException:
return None return None
last_build = app.get_last_build() last_build = get_last_build_from_app(app)
logging.debug("...fetch auto name from " + str(build_dir)) logging.debug("...fetch auto name from " + str(build_dir))
new_name = None new_name = None
@ -579,6 +576,13 @@ def checkupdates_app(app):
raise FDroidException("Git commit failed") raise FDroidException("Git commit failed")
def get_last_build_from_app(app):
if app.get('Builds'):
return app['Builds'][-1]
else:
return metadata.Build()
def status_update_json(processed, failed): def status_update_json(processed, failed):
"""Output a JSON file with metadata about this run.""" """Output a JSON file with metadata about this run."""
logging.debug(_('Outputting JSON')) logging.debug(_('Outputting JSON'))

View file

@ -24,14 +24,12 @@ import platform
import os import os
import re import re
import logging import logging
import importlib import ruamel.yaml
from collections import OrderedDict from collections import OrderedDict
from ruamel.yaml import YAML, YAMLError
from . import common from . import common
from . import _ from . import _
from .exception import MetaDataException, FDroidException from .exception import MetaDataException
srclibs = None srclibs = None
warnings_action = None warnings_action = None
@ -109,7 +107,6 @@ yaml_app_fields = [x for x in yaml_app_field_order if x != '\n']
class App(dict): class App(dict):
def __init__(self, copydict=None): def __init__(self, copydict=None):
if copydict: if copydict:
super().__init__(copydict) super().__init__(copydict)
@ -177,12 +174,6 @@ class App(dict):
else: else:
raise AttributeError("No such attribute: " + name) raise AttributeError("No such attribute: " + name)
def get_last_build(self):
if len(self.Builds) > 0:
return self.Builds[-1]
else:
return Build()
TYPE_STRING = 2 TYPE_STRING = 2
TYPE_BOOL = 3 TYPE_BOOL = 3
@ -252,7 +243,6 @@ build_flags = [
class Build(dict): class Build(dict):
def __init__(self, copydict=None): def __init__(self, copydict=None):
super().__init__() super().__init__()
self.disable = '' self.disable = ''
@ -307,6 +297,10 @@ class Build(dict):
else: else:
raise AttributeError("No such attribute: " + name) raise AttributeError("No such attribute: " + name)
@classmethod
def to_yaml(cls, representer, node):
return representer.represent_dict(node)
def build_method(self): def build_method(self):
for f in ['maven', 'gradle']: for f in ['maven', 'gradle']:
if self.get(f): if self.get(f):
@ -375,7 +369,7 @@ def flagtype(name):
return TYPE_STRING return TYPE_STRING
class FieldValidator(): class FieldValidator:
"""Designate App metadata field types and checks that it matches. """Designate App metadata field types and checks that it matches.
'name' - The long name of the field type 'name' - The long name of the field type
@ -399,8 +393,13 @@ class FieldValidator():
values = [v] values = [v]
for v in values: for v in values:
if not self.compiled.match(v): if not self.compiled.match(v):
_warn_or_exception(_("'{value}' is not a valid {field} in {appid}. Regex pattern: {pattern}") _warn_or_exception(
.format(value=v, field=self.name, appid=appid, pattern=self.matching)) _(
"'{value}' is not a valid {field} in {appid}. Regex pattern: {pattern}"
).format(
value=v, field=self.name, appid=appid, pattern=self.matching
)
)
# Generic value types # Generic value types
@ -471,21 +470,19 @@ def check_metadata(app):
def parse_yaml_srclib(metadatapath): def parse_yaml_srclib(metadatapath):
thisinfo = {'RepoType': '', 'Repo': '', 'Subdir': None, 'Prepare': None}
thisinfo = {'RepoType': '',
'Repo': '',
'Subdir': None,
'Prepare': None}
if not metadatapath.exists(): if not metadatapath.exists():
_warn_or_exception(_("Invalid scrlib metadata: '{file}' " _warn_or_exception(
"does not exist" _("Invalid scrlib metadata: '{file}' does not exist").format(
.format(file=metadatapath))) file=metadatapath
)
)
return thisinfo return thisinfo
with metadatapath.open("r", encoding="utf-8") as f: with metadatapath.open("r", encoding="utf-8") as f:
try: try:
yaml = YAML(typ='safe') yaml = ruamel.yaml.YAML(typ='safe')
data = yaml.load(f) data = yaml.load(f)
if type(data) is not dict: if type(data) is not dict:
if platform.system() == 'Windows': if platform.system() == 'Windows':
@ -495,9 +492,10 @@ def parse_yaml_srclib(metadatapath):
with symlink.open("r", encoding="utf-8") as s: with symlink.open("r", encoding="utf-8") as s:
data = yaml.load(s) data = yaml.load(s)
if type(data) is not dict: if type(data) is not dict:
raise YAMLError(_('{file} is blank or corrupt!') raise ruamel.yaml.YAMLError(
.format(file=metadatapath)) _('{file} is blank or corrupt!').format(file=metadatapath)
except YAMLError as e: )
except ruamel.yaml.YAMLError as e:
_warn_or_exception(_("Invalid srclib metadata: could not " _warn_or_exception(_("Invalid srclib metadata: could not "
"parse '{file}'") "parse '{file}'")
.format(file=metadatapath) + '\n' .format(file=metadatapath) + '\n'
@ -507,9 +505,11 @@ def parse_yaml_srclib(metadatapath):
for key in data: for key in data:
if key not in thisinfo: if key not in thisinfo:
_warn_or_exception(_("Invalid srclib metadata: unknown key " _warn_or_exception(
"'{key}' in '{file}'") _("Invalid srclib metadata: unknown key '{key}' in '{file}'").format(
.format(key=key, file=metadatapath)) key=key, file=metadatapath
)
)
return thisinfo return thisinfo
else: else:
if key == 'Subdir': if key == 'Subdir':
@ -580,7 +580,8 @@ def read_metadata(appids={}, sort_by_time=False):
metadatafiles = common.get_metadata_files(vercodes) metadatafiles = common.get_metadata_files(vercodes)
else: else:
metadatafiles = list(Path('metadata').glob('*.yml')) + list( metadatafiles = list(Path('metadata').glob('*.yml')) + list(
Path('.').glob('.fdroid.yml')) Path('.').glob('.fdroid.yml')
)
if sort_by_time: if sort_by_time:
entries = ((path.stat().st_mtime, path) for path in metadatafiles) entries = ((path.stat().st_mtime, path) for path in metadatafiles)
@ -594,11 +595,15 @@ def read_metadata(appids={}, sort_by_time=False):
for metadatapath in metadatafiles: for metadatapath in metadatafiles:
appid = metadatapath.stem appid = metadatapath.stem
if appid != '.fdroid' and not common.is_valid_package_name(appid): if appid != '.fdroid' and not common.is_valid_package_name(appid):
_warn_or_exception(_("{appid} from {path} is not a valid Java Package Name!") _warn_or_exception(
.format(appid=appid, path=metadatapath)) _("{appid} from {path} is not a valid Java Package Name!").format(
appid=appid, path=metadatapath
)
)
if appid in apps: if appid in apps:
_warn_or_exception(_("Found multiple metadata files for {appid}") _warn_or_exception(
.format(appid=appid)) _("Found multiple metadata files for {appid}").format(appid=appid)
)
app = parse_metadata(metadatapath) app = parse_metadata(metadatapath)
check_metadata(app) check_metadata(app)
apps[app.id] = app apps[app.id] = app
@ -654,8 +659,9 @@ def parse_metadata(metadatapath):
with metadatapath.open('r', encoding='utf-8') as mf: with metadatapath.open('r', encoding='utf-8') as mf:
app.update(parse_yaml_metadata(mf)) app.update(parse_yaml_metadata(mf))
else: else:
_warn_or_exception(_('Unknown metadata format: {path} (use: *.yml)') _warn_or_exception(
.format(path=metadatapath)) _('Unknown metadata format: {path} (use: *.yml)').format(path=metadatapath)
)
if metadatapath.name != '.fdroid.yml' and app.Repo: if metadatapath.name != '.fdroid.yml' and app.Repo:
build_dir = common.get_build_dir(app) build_dir = common.get_build_dir(app)
@ -663,11 +669,15 @@ def parse_metadata(metadatapath):
if metadata_in_repo.is_file(): if metadata_in_repo.is_file():
try: try:
commit_id = common.get_head_commit_id(git.Repo(build_dir)) commit_id = common.get_head_commit_id(git.Repo(build_dir))
logging.debug(_('Including metadata from %s@%s') % (metadata_in_repo, commit_id)) logging.debug(
_('Including metadata from %s@%s') % (metadata_in_repo, commit_id)
)
# See https://github.com/PyCQA/pylint/issues/2856 . # See https://github.com/PyCQA/pylint/issues/2856 .
# pylint: disable-next=no-member # pylint: disable-next=no-member
except git.exc.InvalidGitRepositoryError: except git.exc.InvalidGitRepositoryError:
logging.debug(_('Including metadata from {path}').format(metadata_in_repo)) logging.debug(
_('Including metadata from {path}').format(metadata_in_repo)
)
app_in_repo = parse_metadata(metadata_in_repo) app_in_repo = parse_metadata(metadata_in_repo)
for k, v in app_in_repo.items(): for k, v in app_in_repo.items():
if k not in app: if k not in app:
@ -708,13 +718,15 @@ def parse_yaml_metadata(mf):
""" """
try: try:
yaml = YAML(typ='safe') yaml = ruamel.yaml.YAML(typ='safe')
yamldata = yaml.load(mf) yamldata = yaml.load(mf)
except YAMLError as e: except ruamel.yaml.YAMLError as e:
_warn_or_exception(_("could not parse '{path}'") _warn_or_exception(
.format(path=mf.name) + '\n' _("could not parse '{path}'").format(path=mf.name)
+ '\n'
+ common.run_yamllint(mf.name, indent=4), + common.run_yamllint(mf.name, indent=4),
cause=e) cause=e,
)
if yamldata is None or yamldata == '': if yamldata is None or yamldata == '':
yamldata = dict() yamldata = dict()
@ -788,15 +800,16 @@ def post_parse_yaml_metadata(yamldata):
""" """
for k, v in yamldata.items(): for k, v in yamldata.items():
if fieldtype(k) == TYPE_LIST: _fieldtype = fieldtype(k)
if _fieldtype == TYPE_LIST:
if isinstance(v, str): if isinstance(v, str):
yamldata[k] = [v, ] yamldata[k] = [v]
elif v: elif v:
yamldata[k] = [str(i) for i in v] yamldata[k] = [str(i) for i in v]
elif fieldtype(k) == TYPE_INT: elif _fieldtype == TYPE_INT:
if v: if v:
yamldata[k] = int(v) yamldata[k] = int(v)
elif fieldtype(k) == TYPE_STRING: elif _fieldtype == TYPE_STRING:
if v or v == 0: if v or v == 0:
yamldata[k] = _normalize_type_string(v) yamldata[k] = _normalize_type_string(v)
else: else:
@ -810,10 +823,10 @@ def post_parse_yaml_metadata(yamldata):
continue continue
_flagtype = flagtype(k) _flagtype = flagtype(k)
if _flagtype is TYPE_STRING: if _flagtype == TYPE_STRING:
if v or v == 0: if v or v == 0:
build[k] = _normalize_type_string(v) build[k] = _normalize_type_string(v)
elif _flagtype is TYPE_INT: elif _flagtype == TYPE_INT:
build[k] = v build[k] = v
# versionCode must be int # versionCode must be int
if not isinstance(v, int): if not isinstance(v, int):
@ -851,33 +864,18 @@ def write_yaml(mf, app):
app app
app metadata to written to the yaml file app metadata to written to the yaml file
""" """
# import rumael.yaml and check version
try:
import ruamel.yaml
except ImportError as e:
raise FDroidException('ruamel.yaml not installed, can not write metadata.') from e
if not ruamel.yaml.__version__:
raise FDroidException('ruamel.yaml.__version__ not accessible. Please make sure a ruamel.yaml >= 0.13 is installed..')
m = re.match(r'(?P<major>[0-9]+)\.(?P<minor>[0-9]+)\.(?P<patch>[0-9]+)(-.+)?',
ruamel.yaml.__version__)
if not m:
raise FDroidException('ruamel.yaml version malfored, please install an upstream version of ruamel.yaml')
if int(m.group('major')) < 0 or int(m.group('minor')) < 13:
raise FDroidException('currently installed version of ruamel.yaml ({}) is too old, >= 1.13 required.'.format(ruamel.yaml.__version__))
# suiteable version ruamel.yaml imported successfully
def _field_to_yaml(typ, value): def _field_to_yaml(typ, value):
"""Convert data to YAML 1.2 format that keeps the right TYPE_*.""" """Convert data to YAML 1.2 format that keeps the right TYPE_*."""
if typ is TYPE_STRING: if typ == TYPE_STRING:
return str(value) return str(value)
elif typ is TYPE_INT: elif typ == TYPE_INT:
return int(value) return int(value)
elif typ is TYPE_MULTILINE: elif typ == TYPE_MULTILINE:
if '\n' in value: if '\n' in value:
return ruamel.yaml.scalarstring.preserve_literal(str(value)) return ruamel.yaml.scalarstring.preserve_literal(str(value))
else: else:
return str(value) return str(value)
elif typ is TYPE_SCRIPT: elif typ == TYPE_SCRIPT:
if type(value) == list: if type(value) == list:
if len(value) == 1: if len(value) == 1:
return value[0] return value[0]
@ -930,7 +928,9 @@ def write_yaml(mf, app):
if hasattr(build, field): if hasattr(build, field):
value = getattr(build, field) value = getattr(build, field)
if field == 'gradle' and value == ['off']: if field == 'gradle' and value == ['off']:
value = [ruamel.yaml.scalarstring.SingleQuotedScalarString('off')] value = [
ruamel.yaml.scalarstring.SingleQuotedScalarString('off')
]
typ = flagtype(field) typ = flagtype(field)
# don't check value == True for TYPE_INT as it could be 0 # don't check value == True for TYPE_INT as it could be 0
if value is not None and (typ == TYPE_INT or value): if value is not None and (typ == TYPE_INT or value):
@ -945,27 +945,25 @@ def write_yaml(mf, app):
return builds return builds
yaml_app = _app_to_yaml(app) yaml_app = _app_to_yaml(app)
try:
yaml = ruamel.yaml.YAML() yaml = ruamel.yaml.YAML()
yaml.indent(mapping=4, sequence=4, offset=2) yaml.indent(mapping=4, sequence=4, offset=2)
yaml.dump(yaml_app, stream=mf) yaml.dump(yaml_app, stream=mf)
except AttributeError: # Debian/stretch's version does not have YAML()
ruamel.yaml.round_trip_dump(yaml_app, mf, indent=4, block_seq_indent=2)
def write_metadata(metadatapath, app): def write_metadata(metadatapath, app):
metadatapath = Path(metadatapath) metadatapath = Path(metadatapath)
if metadatapath.suffix == '.yml': if metadatapath.suffix == '.yml':
if importlib.util.find_spec('ruamel.yaml'):
with metadatapath.open('w') as mf: with metadatapath.open('w') as mf:
return write_yaml(mf, app) return write_yaml(mf, app)
else:
raise FDroidException(_('ruamel.yaml not installed, can not write metadata.'))
_warn_or_exception(_('Unknown metadata format: %s') % metadatapath) _warn_or_exception(_('Unknown metadata format: %s') % metadatapath)
def add_metadata_arguments(parser): def add_metadata_arguments(parser):
"""Add common command line flags related to metadata processing.""" """Add common command line flags related to metadata processing."""
parser.add_argument("-W", choices=['error', 'warn', 'ignore'], default='error', parser.add_argument(
help=_("force metadata errors (default) to be warnings, or to be ignored.")) "-W",
choices=['error', 'warn', 'ignore'],
default='error',
help=_("force metadata errors (default) to be warnings, or to be ignored."),
)

View file

@ -6,22 +6,17 @@ import logging
import optparse import optparse
import os import os
import random import random
import ruamel.yaml
import shutil import shutil
import sys import sys
import unittest import unittest
from unittest import mock
import tempfile import tempfile
import textwrap import textwrap
from collections import OrderedDict from collections import OrderedDict
from pathlib import Path from pathlib import Path
from testcommon import TmpCwd from testcommon import TmpCwd
from ruamel.yaml import YAML
yaml = YAML(typ='safe')
localmodule = Path(__file__).resolve().parent.parent localmodule = Path(__file__).resolve().parent.parent
print('localmodule: ' + str(localmodule)) print('localmodule: ' + str(localmodule))
if localmodule not in sys.path: if localmodule not in sys.path:
@ -32,6 +27,12 @@ from fdroidserver import metadata
from fdroidserver.exception import MetaDataException from fdroidserver.exception import MetaDataException
def _get_mock_mf(s):
mf = io.StringIO(s)
mf.name = 'mock_filename.yaml'
return mf
class MetadataTest(unittest.TestCase): class MetadataTest(unittest.TestCase):
'''fdroidserver/metadata.py''' '''fdroidserver/metadata.py'''
@ -39,6 +40,7 @@ class MetadataTest(unittest.TestCase):
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
self.basedir = localmodule / 'tests' self.basedir = localmodule / 'tests'
os.chdir(self.basedir) os.chdir(self.basedir)
fdroidserver.metadata.warnings_action = 'error'
def tearDown(self): def tearDown(self):
# auto-generated dirs by functions, not tests, so they are not always cleaned up # auto-generated dirs by functions, not tests, so they are not always cleaned up
@ -67,8 +69,6 @@ class MetadataTest(unittest.TestCase):
break break
self.assertIsNotNone(validator, "could not find 'Bitcoin address' validator") self.assertIsNotNone(validator, "could not find 'Bitcoin address' validator")
fdroidserver.metadata.warnings_action = 'error'
# some valid addresses (P2PKH, P2SH, Bech32) # some valid addresses (P2PKH, P2SH, Bech32)
self.assertIsNone( self.assertIsNone(
validator.check('1BrrrrErsrWetrTrnrrrrm4GFg7xJaNVN2', 'fake.app.id') validator.check('1BrrrrErsrWetrTrnrrrrm4GFg7xJaNVN2', 'fake.app.id')
@ -126,8 +126,6 @@ class MetadataTest(unittest.TestCase):
break break
self.assertIsNotNone(validator, "could not find 'Litecoin address' validator") self.assertIsNotNone(validator, "could not find 'Litecoin address' validator")
fdroidserver.metadata.warnings_action = 'error'
# some valid addresses (L, M, 3, segwit) # some valid addresses (L, M, 3, segwit)
self.assertIsNone( self.assertIsNone(
validator.check('LgeGrrrrJAxyXprrPrrBrrX5Qrrrrrrrrd', 'fake.app.id') validator.check('LgeGrrrrJAxyXprrPrrBrrX5Qrrrrrrrrd', 'fake.app.id')
@ -180,9 +178,30 @@ class MetadataTest(unittest.TestCase):
'fake.app.id', '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): def test_valid_funding_yml_regex(self):
"""Check the regex can find all the cases""" """Check the regex can find all the cases"""
with (self.basedir / 'funding-usernames.yaml').open() as fp: with (self.basedir / 'funding-usernames.yaml').open() as fp:
yaml = ruamel.yaml.YAML(typ='safe')
data = yaml.load(fp) data = yaml.load(fp)
for k, entries in data.items(): for k, entries in data.items():
@ -200,10 +219,7 @@ class MetadataTest(unittest.TestCase):
) )
def test_read_metadata(self): def test_read_metadata(self):
def _build_yaml_representer(dumper, data): """Read specified metadata files included in tests/, compare to stored output"""
'''Creates a YAML representation of a Build instance'''
return dumper.represent_dict(data)
self.maxDiff = None self.maxDiff = None
config = dict() config = dict()
@ -211,6 +227,7 @@ class MetadataTest(unittest.TestCase):
fdroidserver.common.config = config fdroidserver.common.config = config
fdroidserver.metadata.warnings_action = None fdroidserver.metadata.warnings_action = None
yaml = ruamel.yaml.YAML(typ='safe')
apps = fdroidserver.metadata.read_metadata() apps = fdroidserver.metadata.read_metadata()
for appid in ( for appid in (
'org.smssecure.smssecure', 'org.smssecure.smssecure',
@ -226,9 +243,10 @@ class MetadataTest(unittest.TestCase):
self.assertEqual(frommeta, from_yaml) self.assertEqual(frommeta, from_yaml)
# comment above assert and uncomment below to update test # comment above assert and uncomment below to update test
# files when new metadata fields are added # files when new metadata fields are added
# with savepath.open('w') as f: # with savepath.open('w') as fp:
# yaml.add_representer(fdroidserver.metadata.Build, _build_yaml_representer) # yaml.default_flow_style = False
# yaml.dump(frommeta, f, default_flow_style=False) # yaml.register_class(metadata.Build)
# yaml.dump(frommeta, fp)
def test_rewrite_yaml_fakeotaupdate(self): def test_rewrite_yaml_fakeotaupdate(self):
with tempfile.TemporaryDirectory() as testdir: with tempfile.TemporaryDirectory() as testdir:
@ -306,7 +324,20 @@ class MetadataTest(unittest.TestCase):
self.assertEqual('true', metadata._normalize_type_string(True)) self.assertEqual('true', metadata._normalize_type_string(True))
def test_post_parse_yaml_metadata(self): def test_post_parse_yaml_metadata(self):
fdroidserver.metadata.warnings_action = 'error' yamldata = dict()
metadata.post_parse_yaml_metadata(yamldata)
yamldata[
'AllowedAPKSigningKeys'
] = 'c03dac71394d6c26766f1b04d3e31cfcac5d03b55d8aa40cc9b9fa6b74354c66'
metadata.post_parse_yaml_metadata(yamldata)
def test_post_parse_yaml_metadata_fails(self):
yamldata = {'AllowedAPKSigningKeys': True}
with self.assertRaises(TypeError):
metadata.post_parse_yaml_metadata(yamldata)
def test_post_parse_yaml_metadata_builds(self):
yamldata = OrderedDict() yamldata = OrderedDict()
builds = [] builds = []
yamldata['Builds'] = builds yamldata['Builds'] = builds
@ -380,17 +411,45 @@ class MetadataTest(unittest.TestCase):
self.assertEqual(randomlist, allappids) self.assertEqual(randomlist, allappids)
def test_parse_yaml_metadata_0size_file(self): def test_parse_yaml_metadata_0size_file(self):
mf = io.StringIO('') self.assertEqual(dict(), metadata.parse_yaml_metadata(_get_mock_mf('')))
mf.name = 'mock_filename.yaml'
self.assertEqual(fdroidserver.metadata.parse_yaml_metadata(mf), dict())
def test_parse_yaml_metadata_empty_dict_file(self): def test_parse_yaml_metadata_empty_dict_file(self):
mf = io.StringIO('{}') self.assertEqual(dict(), metadata.parse_yaml_metadata(_get_mock_mf('{}')))
mf.name = 'mock_filename.yaml'
self.assertEqual(fdroidserver.metadata.parse_yaml_metadata(mf), dict())
def test_parse_yaml_metadata_empty_string_file(self): def test_parse_yaml_metadata_empty_string_file(self):
mf = io.StringIO('""') self.assertEqual(dict(), metadata.parse_yaml_metadata(_get_mock_mf('""')))
def test_parse_yaml_metadata_fail_on_root_list(self):
with self.assertRaises(MetaDataException):
metadata.parse_yaml_metadata(_get_mock_mf('-'))
with self.assertRaises(MetaDataException):
metadata.parse_yaml_metadata(_get_mock_mf('[]'))
with self.assertRaises(MetaDataException):
metadata.parse_yaml_metadata(_get_mock_mf('- AutoName: fake'))
def test_parse_yaml_metadata_type_list_str(self):
v = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
mf = _get_mock_mf('AllowedAPKSigningKeys: "%s"' % v)
self.assertEqual(
v,
metadata.parse_yaml_metadata(mf)['AllowedAPKSigningKeys'][0],
)
def test_parse_yaml_metadata_type_list_build_str(self):
mf = _get_mock_mf('Builds: [{versionCode: 1, rm: s}]')
self.assertEqual(
metadata.parse_yaml_metadata(mf),
{'Builds': [{'rm': ['s'], 'versionCode': 1}]},
)
def test_parse_yaml_metadata_app_type_list_fails(self):
mf = _get_mock_mf('AllowedAPKSigningKeys: true')
with self.assertRaises(TypeError):
metadata.parse_yaml_metadata(mf)
mf = _get_mock_mf('AllowedAPKSigningKeys: 1')
with self.assertRaises(TypeError):
metadata.parse_yaml_metadata(mf)
mf.name = 'mock_filename.yaml' mf.name = 'mock_filename.yaml'
self.assertEqual(fdroidserver.metadata.parse_yaml_metadata(mf), dict()) self.assertEqual(fdroidserver.metadata.parse_yaml_metadata(mf), dict())
@ -405,7 +464,6 @@ class MetadataTest(unittest.TestCase):
) )
) )
mf.name = 'mock_filename.yaml' mf.name = 'mock_filename.yaml'
with mock.patch('fdroidserver.metadata.warnings_action', 'error'):
with self.assertRaises(MetaDataException): with self.assertRaises(MetaDataException):
fdroidserver.metadata.parse_yaml_metadata(mf) fdroidserver.metadata.parse_yaml_metadata(mf)
@ -420,7 +478,6 @@ class MetadataTest(unittest.TestCase):
) )
) )
mf.name = 'mock_filename.yaml' mf.name = 'mock_filename.yaml'
with mock.patch('fdroidserver.metadata.warnings_action', 'error'):
with self.assertRaises(MetaDataException): with self.assertRaises(MetaDataException):
fdroidserver.metadata.parse_yaml_metadata(mf) fdroidserver.metadata.parse_yaml_metadata(mf)
@ -436,8 +493,7 @@ class MetadataTest(unittest.TestCase):
""" """
fdroidserver.metadata.warnings_action = None fdroidserver.metadata.warnings_action = None
mf = io.StringIO('[AntiFeatures: Tracking]') mf = _get_mock_mf('[AntiFeatures: Tracking]')
mf.name = 'mock_filename.yaml'
self.assertEqual(fdroidserver.metadata.parse_yaml_metadata(mf), dict()) self.assertEqual(fdroidserver.metadata.parse_yaml_metadata(mf), dict())
def test_parse_yaml_srclib_corrupt_file(self): def test_parse_yaml_srclib_corrupt_file(self):
@ -454,7 +510,6 @@ class MetadataTest(unittest.TestCase):
""" """
) )
) )
with mock.patch('fdroidserver.metadata.warnings_action', 'error'):
with self.assertRaises(MetaDataException): with self.assertRaises(MetaDataException):
fdroidserver.metadata.parse_yaml_srclib(srclibfile) fdroidserver.metadata.parse_yaml_srclib(srclibfile)
@ -535,7 +590,6 @@ class MetadataTest(unittest.TestCase):
) )
mf.name = 'mock_filename.yaml' mf.name = 'mock_filename.yaml'
mf.seek(0) mf.seek(0)
with mock.patch('fdroidserver.metadata.warnings_action', 'error'):
result = fdroidserver.metadata.parse_yaml_metadata(mf) result = fdroidserver.metadata.parse_yaml_metadata(mf)
self.maxDiff = None self.maxDiff = None
self.assertDictEqual( self.assertDictEqual(
@ -590,7 +644,6 @@ class MetadataTest(unittest.TestCase):
) )
mf.name = 'mock_filename.yaml' mf.name = 'mock_filename.yaml'
mf.seek(0) mf.seek(0)
with mock.patch('fdroidserver.metadata.warnings_action', 'error'):
result = fdroidserver.metadata.parse_yaml_metadata(mf) result = fdroidserver.metadata.parse_yaml_metadata(mf)
self.maxDiff = None self.maxDiff = None
self.assertDictEqual( self.assertDictEqual(
@ -640,7 +693,6 @@ class MetadataTest(unittest.TestCase):
) )
mf.name = 'mock_filename.yaml' mf.name = 'mock_filename.yaml'
mf.seek(0) mf.seek(0)
with mock.patch('fdroidserver.metadata.warnings_action', 'error'):
result = fdroidserver.metadata.parse_yaml_metadata(mf) result = fdroidserver.metadata.parse_yaml_metadata(mf)
self.assertDictEqual( self.assertDictEqual(
result, result,
@ -674,7 +726,6 @@ class MetadataTest(unittest.TestCase):
) )
mf.name = 'mock_filename.yaml' mf.name = 'mock_filename.yaml'
mf.seek(0) mf.seek(0)
with mock.patch('fdroidserver.metadata.warnings_action', 'error'):
result = fdroidserver.metadata.parse_yaml_metadata(mf) result = fdroidserver.metadata.parse_yaml_metadata(mf)
self.assertNotIn('Provides', result) self.assertNotIn('Provides', result)
self.assertNotIn('provides', result) self.assertNotIn('provides', result)
@ -903,7 +954,6 @@ class MetadataTest(unittest.TestCase):
) )
def test_parse_yaml_srclib_unknown_key(self): def test_parse_yaml_srclib_unknown_key(self):
fdroidserver.metadata.warnings_action = 'error'
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir): with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
with Path('test.yml').open('w', encoding='utf-8') as f: with Path('test.yml').open('w', encoding='utf-8') as f:
f.write( f.write(
@ -922,7 +972,6 @@ class MetadataTest(unittest.TestCase):
fdroidserver.metadata.parse_yaml_srclib(Path('test.yml')) fdroidserver.metadata.parse_yaml_srclib(Path('test.yml'))
def test_parse_yaml_srclib_does_not_exists(self): def test_parse_yaml_srclib_does_not_exists(self):
fdroidserver.metadata.warnings_action = 'error'
with self.assertRaisesRegex( with self.assertRaisesRegex(
MetaDataException, MetaDataException,
"Invalid scrlib metadata: " "Invalid scrlib metadata: "
@ -934,7 +983,6 @@ class MetadataTest(unittest.TestCase):
) )
def test_parse_yaml_srclib_simple(self): def test_parse_yaml_srclib_simple(self):
fdroidserver.metadata.warnings_action = 'error'
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir): with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
with Path('simple.yml').open('w', encoding='utf-8') as f: with Path('simple.yml').open('w', encoding='utf-8') as f:
f.write( f.write(
@ -958,7 +1006,6 @@ class MetadataTest(unittest.TestCase):
) )
def test_parse_yaml_srclib_simple_with_blanks(self): def test_parse_yaml_srclib_simple_with_blanks(self):
fdroidserver.metadata.warnings_action = 'error'
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir): with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
with Path('simple.yml').open('w', encoding='utf-8') as f: with Path('simple.yml').open('w', encoding='utf-8') as f:
f.write( f.write(
@ -988,7 +1035,6 @@ class MetadataTest(unittest.TestCase):
) )
def test_parse_yaml_srclib_Changelog_cketti(self): def test_parse_yaml_srclib_Changelog_cketti(self):
fdroidserver.metadata.warnings_action = 'error'
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir): with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
with Path('Changelog-cketti.yml').open('w', encoding='utf-8') as f: with Path('Changelog-cketti.yml').open('w', encoding='utf-8') as f:
f.write( f.write(
@ -1020,7 +1066,6 @@ class MetadataTest(unittest.TestCase):
) )
def test_read_srclibs_yml_subdir_list(self): def test_read_srclibs_yml_subdir_list(self):
fdroidserver.metadata.warnings_action = 'error'
fdroidserver.metadata.srclibs = None fdroidserver.metadata.srclibs = None
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir): with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
Path('srclibs').mkdir() Path('srclibs').mkdir()
@ -1072,7 +1117,6 @@ class MetadataTest(unittest.TestCase):
) )
def test_read_srclibs_yml_prepare_list(self): def test_read_srclibs_yml_prepare_list(self):
fdroidserver.metadata.warnings_action = 'error'
fdroidserver.metadata.srclibs = None fdroidserver.metadata.srclibs = None
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir): with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
Path('srclibs').mkdir() Path('srclibs').mkdir()
@ -1113,7 +1157,6 @@ class MetadataTest(unittest.TestCase):
) )
def test_read_srclibs(self): def test_read_srclibs(self):
fdroidserver.metadata.warnings_action = 'error'
fdroidserver.metadata.srclibs = None fdroidserver.metadata.srclibs = None
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir): with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
Path('srclibs').mkdir() Path('srclibs').mkdir()

View file

@ -1,5 +1,6 @@
AllowedAPKSigningKeys: [] AllowedAPKSigningKeys: []
AntiFeatures: ['NonFreeNet'] AntiFeatures:
- NonFreeNet
ArchivePolicy: 4 versions ArchivePolicy: 4 versions
AuthorEmail: null AuthorEmail: null
AuthorName: null AuthorName: null
@ -130,7 +131,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null

View file

@ -15,7 +15,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: ea5378a94ee0dc1d99d2cec95fae7e6d81afb2b9 commit: ea5378a94ee0dc1d99d2cec95fae7e6d81afb2b9
disable: '' disable: ''
encoding: null encoding: null
@ -51,7 +51,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: 4128e59da2eac5c2904c7c7568d298ca51e79540 commit: 4128e59da2eac5c2904c7c7568d298ca51e79540
disable: '' disable: ''
encoding: null encoding: null
@ -88,7 +88,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: 0b9985398b9eef7baf6aadd0dbb12002bc199d2e commit: 0b9985398b9eef7baf6aadd0dbb12002bc199d2e
disable: '' disable: ''
encoding: null encoding: null
@ -125,7 +125,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: ab27f4dab5f3ea5e228cfb4a6b0e1fbf53695f22 commit: ab27f4dab5f3ea5e228cfb4a6b0e1fbf53695f22
disable: '' disable: ''
encoding: null encoding: null
@ -162,7 +162,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: 695e3801e4081026c8f7213a2345fc451d5eb89c commit: 695e3801e4081026c8f7213a2345fc451d5eb89c
disable: '' disable: ''
encoding: null encoding: null
@ -199,7 +199,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: 65138c11cc8b6affd28b68e125fbc1dff0886a4e commit: 65138c11cc8b6affd28b68e125fbc1dff0886a4e
disable: '' disable: ''
encoding: null encoding: null
@ -271,7 +271,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: f811e53e1e1d2ee047b18715fd7d2072b90ae76b commit: f811e53e1e1d2ee047b18715fd7d2072b90ae76b
disable: '' disable: ''
encoding: null encoding: null
@ -308,7 +308,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: ff97932761cdee68638dc2550751a64b2cbe18e7 commit: ff97932761cdee68638dc2550751a64b2cbe18e7
disable: '' disable: ''
encoding: null encoding: null
@ -345,7 +345,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: 33d4d80998f30bafc88c04c80cbae00b03916f99 commit: 33d4d80998f30bafc88c04c80cbae00b03916f99
disable: '' disable: ''
encoding: null encoding: null
@ -382,7 +382,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: 743d25a7e287505461f33f4b8e57e4cf988fffea commit: 743d25a7e287505461f33f4b8e57e4cf988fffea
disable: '' disable: ''
encoding: null encoding: null
@ -419,7 +419,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: eaa07f4 commit: eaa07f4
disable: '' disable: ''
encoding: null encoding: null
@ -688,7 +688,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: v2.1 commit: v2.1
disable: '' disable: ''
encoding: null encoding: null
@ -738,7 +738,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: v2.3 commit: v2.3
disable: '' disable: ''
encoding: null encoding: null
@ -784,7 +784,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: v2.6 commit: v2.6
disable: '' disable: ''
encoding: null encoding: null
@ -792,7 +792,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -822,7 +822,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: v2.7 commit: v2.7
disable: '' disable: ''
encoding: null encoding: null
@ -830,7 +830,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -860,7 +860,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: v2.8 commit: v2.8
disable: '' disable: ''
encoding: null encoding: null
@ -868,7 +868,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -898,7 +898,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: v2.8.1 commit: v2.8.1
disable: '' disable: ''
encoding: null encoding: null
@ -906,7 +906,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -936,7 +936,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: v2.9 commit: v2.9
disable: '' disable: ''
encoding: null encoding: null
@ -944,7 +944,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -974,7 +974,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: v2.9.1 commit: v2.9.1
disable: '' disable: ''
encoding: null encoding: null
@ -982,7 +982,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -1012,7 +1012,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: v2.9.2 commit: v2.9.2
disable: '' disable: ''
encoding: null encoding: null
@ -1020,7 +1020,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -1050,7 +1050,7 @@ Builds:
binary: null binary: null
build: '' build: ''
buildjni: buildjni:
- 'yes' - yes
commit: v3.0 commit: v3.0
disable: '' disable: ''
encoding: null encoding: null
@ -1058,7 +1058,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null

View file

@ -22,7 +22,7 @@ Builds:
forcevercode: true forcevercode: true
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -83,7 +83,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -126,7 +126,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -167,7 +167,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -208,7 +208,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -248,7 +248,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -288,7 +288,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null
@ -328,7 +328,7 @@ Builds:
forcevercode: false forcevercode: false
forceversion: false forceversion: false
gradle: gradle:
- 'yes' - yes
gradleprops: [] gradleprops: []
init: '' init: ''
maven: null maven: null

View file

@ -2640,7 +2640,7 @@ UpdateCheckIgnore: null
UpdateCheckMode: Tags UpdateCheckMode: Tags
UpdateCheckName: null UpdateCheckName: null
VercodeOperation: VercodeOperation:
- '%c + 5' - '%c + 5'
WebSite: http://www.videolan.org/vlc/download-android.html WebSite: http://www.videolan.org/vlc/download-android.html
added: null added: null
id: org.videolan.vlc id: org.videolan.vlc

View file

@ -7,10 +7,8 @@ import sys
import unittest import unittest
import tempfile import tempfile
import textwrap import textwrap
from unittest import mock
from pathlib import Path from pathlib import Path
from testcommon import TmpCwd from testcommon import TmpCwd
localmodule = Path(__file__).resolve().parent.parent localmodule = Path(__file__).resolve().parent.parent
@ -20,7 +18,6 @@ if localmodule not in sys.path:
from fdroidserver import common from fdroidserver import common
from fdroidserver import rewritemeta from fdroidserver import rewritemeta
from fdroidserver.exception import FDroidException
class RewriteMetaTest(unittest.TestCase): class RewriteMetaTest(unittest.TestCase):
@ -71,24 +68,6 @@ class RewriteMetaTest(unittest.TestCase):
), ),
) )
def test_rewrite_scenario_yml_no_ruamel(self):
sys.argv = ['rewritemeta', 'a']
with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
Path('metadata').mkdir()
with Path('metadata/a.yml').open('w') as f:
f.write('AutoName: a')
def boom(*args):
raise FDroidException(' '.join((str(x) for x in args)))
with mock.patch('importlib.util.find_spec', boom):
with self.assertRaises(FDroidException):
rewritemeta.main()
self.assertEqual(
Path('metadata/a.yml').read_text(encoding='utf-8'), 'AutoName: a'
)
if __name__ == "__main__": if __name__ == "__main__":
parser = optparse.OptionParser() parser = optparse.OptionParser()