chiark / gitweb /
Merge branch 'log_git' into 'master'
authorrelan <relan@airpost.net>
Sun, 3 Dec 2017 06:03:03 +0000 (06:03 +0000)
committerrelan <relan@airpost.net>
Sun, 3 Dec 2017 06:03:03 +0000 (06:03 +0000)
build: log vcs tools version on every build attempt

See merge request fdroid/fdroidserver!391

36 files changed:
buildserver/provision-android-sdk
completion/bash-completion
examples/config.py
fdroid
fdroidserver/build.py
fdroidserver/common.py
fdroidserver/init.py
fdroidserver/metadata.py
fdroidserver/mirror.py [new file with mode: 0644]
fdroidserver/nightly.py
fdroidserver/server.py
fdroidserver/update.py
fdroidserver/vmtools.py
jenkins-build-all
setup.py
tests/build.TestCase
tests/common.TestCase
tests/import.TestCase
tests/index.TestCase
tests/lint.TestCase
tests/metadata.TestCase
tests/publish.TestCase
tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/full_description.txt [new file with mode: 0644]
tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/short_description.txt [new file with mode: 0644]
tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/title.txt [new file with mode: 0644]
tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/full_description.txt [new file with mode: 0644]
tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/short_description.txt [new file with mode: 0644]
tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/title.txt [new file with mode: 0644]
tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/full_description.txt [new file with mode: 0644]
tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/short_description.txt [new file with mode: 0644]
tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/title.txt [new file with mode: 0644]
tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/full_description.txt [new file with mode: 0644]
tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/short_description.txt [new file with mode: 0644]
tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/title.txt [new file with mode: 0644]
tests/source-files/fdroid/fdroidclient/build.gradle
tests/update.TestCase

index b79c2e4a11638f1deeb03c74df9fbfec7ab26310..7cd96e0c371a0f09c500212012b1c3791fc089fc 100644 (file)
@@ -74,12 +74,25 @@ y
 EOH
 
 mkdir -p $ANDROID_HOME/licenses/
+
 cat << EOF > $ANDROID_HOME/licenses/android-sdk-license
 
 8933bad161af4178b1185d1a37fbf41ea5269c55
+
 d56f5187479451eabf01fb78af6dfcb131a6481e
 EOF
-echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > $ANDROID_HOME/licenses/android-sdk-preview-license
+
+cat <<EOF > $ANDROID_HOME/licenses/android-sdk-preview-license
+
+84831b9409646a918e30573bab4c9c91346d8abd
+EOF
+
+cat <<EOF > $ANDROID_HOME/licenses/android-sdk-preview-license-old
+79120722343a6f314e0719f863036c702b0e6b2a
+
+84831b9409646a918e30573bab4c9c91346d8abd
+EOF
+
 echo y | $ANDROID_HOME/tools/bin/sdkmanager "extras;m2repository;com;android;support;constraint;constraint-layout;1.0.1"
 echo y | $ANDROID_HOME/tools/bin/sdkmanager "extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.1"
 echo y | $ANDROID_HOME/tools/bin/sdkmanager "extras;m2repository;com;android;support;constraint;constraint-layout;1.0.2"
index 8bdd333f0fddf2b96af196f723c15d3d05841140..d18edd3501d8e17f815f0aee899cd115a6a2f4e1 100644 (file)
@@ -264,6 +264,12 @@ __complete_btlog() {
        __complete_options
 }
 
+__complete_mirror() {
+       opts="-v"
+       lopts="--archive --output-dir"
+       __complete_options
+}
+
 __complete_nightly() {
        opts="-v -q"
        lopts="--show-secret-var"
@@ -316,6 +322,7 @@ import \
 init \
 install \
 lint \
+mirror \
 nightly \
 publish \
 readmeta \
index 0551e1c0b6568646d752a89dc3507e70a0e91ebd..f36d51d24a4e75013196e245e9dbd07b5d47d3a8 100644 (file)
@@ -216,11 +216,12 @@ The repository of older versions of applications from the main demo repository.
 # sync_from_local_copy_dir = True
 
 
-# To upload the repo to an Amazon S3 bucket using `fdroid server update`.
-# Warning, this deletes and recreates the whole fdroid/ directory each
-# time. This is based on apache-libcloud, which supports basically all cloud
-# storage services, so it should be easy to port the fdroid server tools to
-# any of them.
+# To upload the repo to an Amazon S3 bucket using `fdroid server
+# update`.  Warning, this deletes and recreates the whole fdroid/
+# directory each time. This prefers s3cmd, but can also use
+# apache-libcloud.  To customize how s3cmd interacts with the cloud
+# provider, create a 's3cfg' file next to this file (config.py), and
+# those settings will be used instead of any 'aws' variable below.
 #
 # awsbucket = 'myawsfdroid'
 # awsaccesskeyid = 'SEE0CHAITHEIMAUR2USA'
diff --git a/fdroid b/fdroid
index a07a4ecfb124047a1d900c7650dd15e10305e610..f5e6c92b5dce805a11109987e948531f1cf44599 100755 (executable)
--- a/fdroid
+++ b/fdroid
@@ -48,6 +48,7 @@ commands = OrderedDict([
     ("btlog", _("Update the binary transparency log for a URL")),
     ("signatures", _("Extract signatures from APKs")),
     ("nightly", _("Set up an app build for a nightly build repo")),
+    ("mirror", _("Download complete mirrors of small repos")),
 ])
 
 
index 0d0f687a01f9c84a7b0492c18ad855385d463063..2bacd5550fd5adfd220d61906bf5d644a94f5ac9 100644 (file)
@@ -23,6 +23,7 @@ import shutil
 import glob
 import subprocess
 import re
+import resource
 import tarfile
 import traceback
 import time
@@ -1110,7 +1111,7 @@ def main():
 
     # Read all app and srclib metadata
     pkgs = common.read_pkg_args(options.appid, True)
-    allapps = metadata.read_metadata(not options.onserver, pkgs)
+    allapps = metadata.read_metadata(not options.onserver, pkgs, sort_by_time=True)
     apps = common.read_app_args(options.appid, allapps, True)
 
     for appid, app in list(apps.items()):
@@ -1120,6 +1121,19 @@ def main():
     if not apps:
         raise FDroidException("No apps to process.")
 
+    # make sure enough open files are allowed to process everything
+    soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
+    if len(apps) > soft:
+        try:
+            soft = len(apps) * 2
+            if soft > hard:
+                soft = hard
+            resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard))
+            logging.debug(_('Set open file limit to {integer}')
+                          .format(integer=soft))
+        except (OSError, ValueError) as e:
+            logging.warning(_('Setting open file limit failed: ') + str(e))
+
     if options.latest:
         for app in apps.values():
             for build in reversed(app.builds):
@@ -1331,7 +1345,10 @@ def main():
         logging.info(ngettext("{} build failed",
                               "{} builds failed", len(failed_apps)).format(len(failed_apps)))
 
-    sys.exit(0)
+    # hack to ensure this exits, even is some threads are still running
+    sys.stdout.flush()
+    sys.stderr.flush()
+    os._exit(0)
 
 
 if __name__ == "__main__":
index 89c1a1d5ac84414f353f62f53d61a0a8eb04af4f..e7a1f1ece58ca159a8ffbe4f702d48e054c88efd 100644 (file)
@@ -40,7 +40,7 @@ import json
 import xml.etree.ElementTree as XMLElementTree
 
 from binascii import hexlify
-from datetime import datetime
+from datetime import datetime, timedelta
 from distutils.version import LooseVersion
 from queue import Queue
 from zipfile import ZipFile
@@ -444,17 +444,16 @@ def get_local_metadata_files():
     return glob.glob('.fdroid.[a-jl-z]*[a-rt-z]')
 
 
-def read_pkg_args(args, allow_vercodes=False):
+def read_pkg_args(appid_versionCode_pairs, allow_vercodes=False):
     """
-    :param args: arguments in the form of multiple appid:[vc] strings
+    :param appids: arguments in the form of multiple appid:[vc] strings
     :returns: a dictionary with the set of vercodes specified for each package
     """
-
     vercodes = {}
-    if not args:
+    if not appid_versionCode_pairs:
         return vercodes
 
-    for p in args:
+    for p in appid_versionCode_pairs:
         if allow_vercodes and ':' in p:
             package, vercode = p.split(':')
         else:
@@ -468,13 +467,17 @@ def read_pkg_args(args, allow_vercodes=False):
     return vercodes
 
 
-def read_app_args(args, allapps, allow_vercodes=False):
-    """
-    On top of what read_pkg_args does, this returns the whole app metadata, but
-    limiting the builds list to the builds matching the vercodes specified.
+def read_app_args(appid_versionCode_pairs, allapps, allow_vercodes=False):
+    """Build a list of App instances for processing
+
+    On top of what read_pkg_args does, this returns the whole app
+    metadata, but limiting the builds list to the builds matching the
+    appid_versionCode_pairs and vercodes specified.  If no appid_versionCode_pairs are specified, then
+    all App and Build instances are returned.
+
     """
 
-    vercodes = read_pkg_args(args, allow_vercodes)
+    vercodes = read_pkg_args(appid_versionCode_pairs, allow_vercodes)
 
     if not vercodes:
         return allapps
@@ -1299,27 +1302,58 @@ def parse_androidmanifests(paths, app):
         vercode = None
         package = None
 
+        flavour = ""
+        if app.builds and 'gradle' in app.builds[-1] and app.builds[-1].gradle:
+                flavour = app.builds[-1].gradle[-1]
+
         if has_extension(path, 'gradle'):
+            # first try to get version name and code from correct flavour
             with open(path, 'r') as f:
-                for line in f:
-                    if gradle_comment.match(line):
-                        continue
-                    # Grab first occurence of each to avoid running into
-                    # alternative flavours and builds.
-                    if not package:
-                        matches = psearch_g(line)
-                        if matches:
-                            s = matches.group(2)
-                            if app_matches_packagename(app, s):
-                                package = s
-                    if not version:
-                        matches = vnsearch_g(line)
-                        if matches:
-                            version = matches.group(2)
-                    if not vercode:
-                        matches = vcsearch_g(line)
-                        if matches:
-                            vercode = matches.group(1)
+                buildfile = f.read()
+
+                regex_string = r"" + flavour + ".*?}"
+                search = re.compile(regex_string, re.DOTALL)
+                result = search.search(buildfile)
+
+            if result is not None:
+                resultgroup = result.group()
+
+                if not package:
+                    matches = psearch_g(resultgroup)
+                    if matches:
+                        s = matches.group(2)
+                        if app_matches_packagename(app, s):
+                            package = s
+                if not version:
+                    matches = vnsearch_g(resultgroup)
+                    if matches:
+                        version = matches.group(2)
+                if not vercode:
+                    matches = vcsearch_g(resultgroup)
+                    if matches:
+                        vercode = matches.group(1)
+            else:
+                # fall back to parse file line by line
+                with open(path, 'r') as f:
+                    for line in f:
+                        if gradle_comment.match(line):
+                            continue
+                        # Grab first occurence of each to avoid running into
+                        # alternative flavours and builds.
+                        if not package:
+                            matches = psearch_g(line)
+                            if matches:
+                                s = matches.group(2)
+                                if app_matches_packagename(app, s):
+                                    package = s
+                        if not version:
+                            matches = vnsearch_g(line)
+                            if matches:
+                                version = matches.group(2)
+                        if not vercode:
+                            matches = vcsearch_g(line)
+                            if matches:
+                                vercode = matches.group(1)
         else:
             try:
                 xml = parse_xml(path)
@@ -1716,6 +1750,23 @@ def natural_key(s):
     return [int(sp) if sp.isdigit() else sp for sp in re.split(r'(\d+)', s)]
 
 
+def check_system_clock(dt_obj, path):
+    """Check if system clock is updated based on provided date
+
+    If an APK has files newer than the system time, suggest updating
+    the system clock.  This is useful for offline systems, used for
+    signing, which do not have another source of clock sync info. It
+    has to be more than 24 hours newer because ZIP/APK files do not
+    store timezone info
+
+    """
+    checkdt = dt_obj - timedelta(1)
+    if datetime.today() < checkdt:
+        logging.warning(_('System clock is older than date in {path}!').format(path=path)
+                        + '\n' + _('Set clock to that time using:') + '\n'
+                        + 'sudo date -s "' + str(dt_obj) + '"')
+
+
 class KnownApks:
     """permanent store of existing APKs with the date they were added
 
@@ -1744,6 +1795,7 @@ class KnownApks:
                         date = datetime.strptime(t[-1], '%Y-%m-%d')
                         filename = line[0:line.rfind(appid) - 1]
                         self.apks[filename] = (appid, date)
+                        check_system_clock(date, self.path)
         self.changed = False
 
     def writeifchanged(self):
@@ -1918,6 +1970,7 @@ def FDroidPopenBytes(commands, cwd=None, envs=None, output=True, stderr_to_stdou
         raise BuildException("OSError while trying to execute " +
                              ' '.join(commands) + ': ' + str(e))
 
+    # TODO are these AsynchronousFileReader threads always exiting?
     if not stderr_to_stdout and options.verbose:
         stderr_queue = Queue()
         stderr_reader = AsynchronousFileReader(p.stderr, stderr_queue)
index 9fdb5836c0831285ce834d553f03f50882444a9d..9d03e0b9284b94dbed0d00bd7c9330e2dc758144 100644 (file)
@@ -178,6 +178,7 @@ def main():
                              + '" does not exist, creating a new keystore there.')
     common.write_to_config(test_config, 'keystore', keystore)
     repo_keyalias = None
+    keydname = None
     if options.repo_keyalias:
         repo_keyalias = options.repo_keyalias
         common.write_to_config(test_config, 'repo_keyalias', repo_keyalias)
@@ -211,7 +212,16 @@ def main():
                                    flags=re.MULTILINE)
             with open('opensc-fdroid.cfg', 'w') as f:
                 f.write(opensc_fdroid)
-    elif not os.path.exists(keystore):
+    elif os.path.exists(keystore):
+        to_set = ['keystorepass', 'keypass', 'repo_keyalias', 'keydname']
+        if repo_keyalias:
+            to_set.remove('repo_keyalias')
+        if keydname:
+            to_set.remove('keydname')
+        logging.warning('\n' + _('Using existing keystore "{path}"').format(path=keystore)
+                        + '\n' + _('Now set these in config.py:') + ' '
+                        + ', '.join(to_set) + '\n')
+    else:
         password = common.genpassword()
         c = dict(test_config)
         c['keystorepass'] = password
index d65eb81a69c8afcc713505e38fe3db93cd2b5e1c..c29c1e4c29a079e545f60d678df0d5c11b7c6c5f 100644 (file)
@@ -26,6 +26,7 @@ import logging
 import textwrap
 import io
 import yaml
+from collections import OrderedDict
 # use libyaml if it is available
 try:
     from yaml import CLoader
@@ -703,35 +704,50 @@ def read_srclibs():
         srclibs[srclibname] = parse_srclib(metadatapath)
 
 
-def read_metadata(xref=True, check_vcs=[]):
-    """
-    Read all metadata. Returns a list of 'app' objects (which are dictionaries as
-    returned by the parse_txt_metadata function.
+def read_metadata(xref=True, check_vcs=[], sort_by_time=False):
+    """Return a list of App instances sorted newest first
+
+    This reads all of the metadata files in a 'data' repository, then
+    builds a list of App instances from those files.  The list is
+    sorted based on creation time, newest first.  Most of the time,
+    the newer files are the most interesting.
+
+    If there are multiple metadata files for a single appid, then the first
+    file that is parsed wins over all the others, and the rest throw an
+    exception. So the original .txt format is parsed first, at least until
+    newer formats stabilize.
 
     check_vcs is the list of packageNames to check for .fdroid.yml in source
+
     """
 
     # Always read the srclibs before the apps, since they can use a srlib as
     # their source repository.
     read_srclibs()
 
-    apps = {}
+    apps = OrderedDict()
 
     for basedir in ('metadata', 'tmp'):
         if not os.path.exists(basedir):
             os.makedirs(basedir)
 
-    # If there are multiple metadata files for a single appid, then the first
-    # file that is parsed wins over all the others, and the rest throw an
-    # exception. So the original .txt format is parsed first, at least until
-    # newer formats stabilize.
-
-    for metadatapath in sorted(glob.glob(os.path.join('metadata', '*.txt'))
-                               + glob.glob(os.path.join('metadata', '*.json'))
-                               + glob.glob(os.path.join('metadata', '*.yml'))
-                               + glob.glob('.fdroid.txt')
-                               + glob.glob('.fdroid.json')
-                               + glob.glob('.fdroid.yml')):
+    metadatafiles = (glob.glob(os.path.join('metadata', '*.txt'))
+                     + glob.glob(os.path.join('metadata', '*.json'))
+                     + glob.glob(os.path.join('metadata', '*.yml'))
+                     + glob.glob('.fdroid.txt')
+                     + glob.glob('.fdroid.json')
+                     + glob.glob('.fdroid.yml'))
+
+    if sort_by_time:
+        entries = ((os.stat(path).st_mtime, path) for path in metadatafiles)
+        metadatafiles = []
+        for _ignored, path in sorted(entries, reverse=True):
+            metadatafiles.append(path)
+    else:
+        # most things want the index alpha sorted for stability
+        metadatafiles = sorted(metadatafiles)
+
+    for metadatapath in metadatafiles:
         packageName, _ignored = fdroidserver.common.get_extension(os.path.basename(metadatapath))
         if packageName in apps:
             warn_or_exception(_("Found multiple metadata files for {appid}")
diff --git a/fdroidserver/mirror.py b/fdroidserver/mirror.py
new file mode 100644 (file)
index 0000000..0aa4372
--- /dev/null
@@ -0,0 +1,190 @@
+#!/usr/bin/env python3
+
+import ipaddress
+import logging
+import os
+import posixpath
+import socket
+import subprocess
+import sys
+from argparse import ArgumentParser
+import urllib.parse
+
+from . import _
+from . import common
+from . import index
+from . import update
+
+options = None
+
+
+def _run_wget(path, urls):
+    if options.verbose:
+        verbose = '--verbose'
+    else:
+        verbose = '--no-verbose'
+
+    if not urls:
+        return
+    logging.debug(_('Running wget in {path}').format(path=path))
+    os.makedirs(path, exist_ok=True)
+    os.chdir(path)
+    urls_file = '.fdroid-mirror-wget-input-file'
+    with open(urls_file, 'w') as fp:
+        for url in urls:
+            fp.write(url.split('?')[0] + '\n')  # wget puts query string in the filename
+    subprocess.call(['wget', verbose, '--continue', '--user-agent="fdroid mirror"',
+                     '--input-file=' + urls_file])
+    os.remove(urls_file)
+
+
+def main():
+    global options
+
+    parser = ArgumentParser(usage=_("%(prog)s [options] url"))
+    common.setup_global_opts(parser)
+    parser.add_argument("url", nargs='?',
+                        help=_('Base URL to mirror, can include the index signing key '
+                               + 'using the query string: ?fingerprint='))
+    parser.add_argument("--archive", action='store_true', default=False,
+                        help=_("Also mirror the full archive section"))
+    parser.add_argument("--output-dir", default=None,
+                        help=_("The directory to write the mirror to"))
+    options = parser.parse_args()
+
+    if options.url is None:
+        logging.error(_('A URL is required as an argument!') + '\n')
+        parser.print_help()
+        sys.exit(1)
+
+    scheme, hostname, path, params, query, fragment = urllib.parse.urlparse(options.url)
+    fingerprint = urllib.parse.parse_qs(query).get('fingerprint')
+
+    def _append_to_url_path(*args):
+        '''Append the list of path components to URL, keeping the rest the same'''
+        newpath = posixpath.join(path, *args)
+        return urllib.parse.urlunparse((scheme, hostname, newpath, params, query, fragment))
+
+    if fingerprint:
+        config = common.read_config(options)
+        if not ('jarsigner' in config or 'apksigner' in config):
+            logging.error(_('Java JDK not found! Install in standard location or set java_paths!'))
+            sys.exit(1)
+
+        def _get_index(section, etag=None):
+            url = _append_to_url_path(section)
+            return index.download_repo_index(url, etag=etag)
+    else:
+        def _get_index(section, etag=None):
+            import io
+            import json
+            import zipfile
+            from . import net
+            url = _append_to_url_path(section, 'index-v1.jar')
+            content, etag = net.http_get(url)
+            with zipfile.ZipFile(io.BytesIO(content)) as zip:
+                jsoncontents = zip.open('index-v1.json').read()
+            data = json.loads(jsoncontents.decode('utf-8'))
+            return data, etag
+
+    ip = None
+    try:
+        ip = ipaddress.ip_address(hostname)
+    except ValueError:
+        pass
+    if hostname == 'f-droid.org' \
+       or (ip is not None and hostname in socket.gethostbyname_ex('f-droid.org')[2]):
+        print(_('ERROR: this command should never be used to mirror f-droid.org!\n'
+                'A full mirror of f-droid.org requires more than 200GB.'))
+        sys.exit(1)
+
+    path = path.rstrip('/')
+    if path.endswith('repo') or path.endswith('archive'):
+        logging.warning(_('Do not include "{path}" in URL!')
+                        .format(path=path.split('/')[-1]))
+    elif not path.endswith('fdroid'):
+        logging.warning(_('{url} does not end with "fdroid", check the URL path!')
+                        .format(url=options.url))
+
+    icondirs = ['icons', ]
+    for density in update.screen_densities:
+        icondirs.append('icons-' + density)
+
+    if options.output_dir:
+        basedir = options.output_dir
+    else:
+        basedir = os.path.join(os.getcwd(), hostname, path.strip('/'))
+        os.makedirs(basedir, exist_ok=True)
+
+    if options.archive:
+        sections = ('repo', 'archive')
+    else:
+        sections = ('repo', )
+
+    for section in sections:
+        sectiondir = os.path.join(basedir, section)
+
+        data, etag = _get_index(section)
+
+        os.makedirs(sectiondir, exist_ok=True)
+        os.chdir(sectiondir)
+        for icondir in icondirs:
+            os.makedirs(os.path.join(sectiondir, icondir), exist_ok=True)
+
+        urls = []
+        for packageName, packageList in data['packages'].items():
+            for package in packageList:
+                to_fetch = []
+                for k in ('apkName', 'srcname'):
+                    if k in package:
+                        to_fetch.append(package[k])
+                    elif k == 'apkName':
+                        logging.error(_('{appid} is missing {name}')
+                                      .format(appid=package['packageName'], name=k))
+                for f in to_fetch:
+                    if not os.path.exists(f) \
+                       or (f.endswith('.apk') and os.path.getsize(f) != package['size']):
+                        urls.append(_append_to_url_path(section, f))
+                        urls.append(_append_to_url_path(section, f + '.asc'))
+        _run_wget(sectiondir, urls)
+
+        for app in data['apps']:
+            localized = app.get('localized')
+            if localized:
+                for locale, d in localized.items():
+                    urls = []
+                    components = (section, app['packageName'], locale)
+                    for k in update.GRAPHIC_NAMES:
+                        f = d.get(k)
+                        if f:
+                            filepath_tuple = components + (f, )
+                            urls.append(_append_to_url_path(*filepath_tuple))
+                    _run_wget(os.path.join(basedir, *components), urls)
+                    for k in update.SCREENSHOT_DIRS:
+                        urls = []
+                        filelist = d.get(k)
+                        if filelist:
+                            components = (section, app['packageName'], locale, k)
+                            for f in filelist:
+                                filepath_tuple = components + (f, )
+                                urls.append(_append_to_url_path(*filepath_tuple))
+                            _run_wget(os.path.join(basedir, *components), urls)
+
+        urls = dict()
+        for app in data['apps']:
+            if 'icon' not in app:
+                logging.error(_('no "icon" in {appid}').format(appid=app['packageName']))
+                continue
+            icon = app['icon']
+            for icondir in icondirs:
+                url = _append_to_url_path(section, icondir, icon)
+                if icondir not in urls:
+                    urls[icondir] = []
+                urls[icondir].append(url)
+
+        for icondir in icondirs:
+            _run_wget(os.path.join(basedir, section, icondir), urls[icondir])
+
+
+if __name__ == "__main__":
+    main()
index 3834f79e9752ebf9194b87d68b9ee2644f5948b2..454616e4965318f3428685930a5baea8cc0c70d4 100644 (file)
@@ -155,6 +155,7 @@ def main():
         repo_url = repo_base + '/repo'
         git_mirror_path = os.path.join(repo_basedir, 'git-mirror')
         git_mirror_repodir = os.path.join(git_mirror_path, 'fdroid', 'repo')
+        git_mirror_metadatadir = os.path.join(git_mirror_path, 'fdroid', 'metadata')
         if not os.path.isdir(git_mirror_repodir):
             logging.debug(_('cloning {url}').format(url=clone_url))
             try:
@@ -186,10 +187,10 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base,
         mirror_git_repo.git.add(all=True)
         mirror_git_repo.index.commit("update README")
 
-        icon_path = os.path.join(repo_basedir, 'icon.png')
+        icon_path = os.path.join(git_mirror_path, 'icon.png')
         try:
             import qrcode
-            img = qrcode.make('Some data here')
+            img = qrcode.make(repo_url)
             with open(icon_path, 'wb') as fp:
                 fp.write(img)
         except Exception:
@@ -197,9 +198,13 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base,
             shutil.copy(exampleicon, icon_path)
         mirror_git_repo.git.add(all=True)
         mirror_git_repo.index.commit("update repo/website icon")
+        shutil.copy(icon_path, repo_basedir)
 
         os.chdir(repo_basedir)
-        common.local_rsync(options, git_mirror_repodir + '/', 'repo/')
+        if os.path.isdir(git_mirror_repodir):
+            common.local_rsync(options, git_mirror_repodir + '/', 'repo/')
+        if os.path.isdir(git_mirror_metadatadir):
+            common.local_rsync(options, git_mirror_metadatadir + '/', 'metadata/')
 
         ssh_private_key_file = _ssh_key_from_debug_keystore()
         # this is needed for GitPython to find the SSH key
@@ -254,7 +259,11 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base,
             except subprocess.CalledProcessError:
                 pass
 
-        subprocess.check_call(['fdroid', 'update', '--rename-apks', '--verbose'], cwd=repo_basedir)
+        subprocess.check_call(['fdroid', 'update', '--rename-apks', '--create-metadata', '--verbose'],
+                              cwd=repo_basedir)
+        common.local_rsync(options, repo_basedir + '/metadata/', git_mirror_metadatadir + '/')
+        mirror_git_repo.git.add(all=True)
+        mirror_git_repo.index.commit("update app metadata")
         try:
             subprocess.check_call(['fdroid', 'server', 'update', '--verbose'], cwd=repo_basedir)
         except subprocess.CalledProcessError:
index 116c27f8f1a55a788be65b5a4acb996befb8286c..004a606879c30d156446dff5046b1005a927d0ce 100644 (file)
@@ -38,6 +38,9 @@ options = None
 
 BINARY_TRANSPARENCY_DIR = 'binary_transparency'
 
+AUTO_S3CFG = '.fdroid-server-update-s3cfg'
+USER_S3CFG = 's3cfg'
+
 
 def update_awsbucket(repo_section):
     '''
@@ -72,12 +75,17 @@ def update_awsbucket_s3cmd(repo_section):
     logging.debug(_('Using s3cmd to sync with: {url}')
                   .format(url=config['awsbucket']))
 
-    configfilename = '.s3cfg'
-    fd = os.open(configfilename, os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0o600)
-    os.write(fd, '[default]\n'.encode('utf-8'))
-    os.write(fd, ('access_key = ' + config['awsaccesskeyid'] + '\n').encode('utf-8'))
-    os.write(fd, ('secret_key = ' + config['awssecretkey'] + '\n').encode('utf-8'))
-    os.close(fd)
+    if os.path.exists(USER_S3CFG):
+        logging.info(_('Using "{path}" for configuring s3cmd.').format(path=USER_S3CFG))
+        configfilename = USER_S3CFG
+    else:
+        fd = os.open(AUTO_S3CFG, os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0o600)
+        logging.debug(_('Creating "{path}" for configuring s3cmd.').format(path=AUTO_S3CFG))
+        os.write(fd, '[default]\n'.encode('utf-8'))
+        os.write(fd, ('access_key = ' + config['awsaccesskeyid'] + '\n').encode('utf-8'))
+        os.write(fd, ('secret_key = ' + config['awssecretkey'] + '\n').encode('utf-8'))
+        os.close(fd)
+        configfilename = AUTO_S3CFG
 
     s3bucketurl = 's3://' + config['awsbucket']
     s3cmd = [config['s3cmd'], '--config=' + configfilename]
@@ -151,6 +159,10 @@ def update_awsbucket_libcloud(repo_section):
             _('To use awsbucket, awssecretkey and awsaccesskeyid must also be set in config.py!'))
     awsbucket = config['awsbucket']
 
+    if os.path.exists(USER_S3CFG):
+        raise FDroidException(_('"{path}" exists but s3cmd is not installed!')
+                              .format(path=USER_S3CFG))
+
     cls = get_driver(Provider.S3)
     driver = cls(config['awsaccesskeyid'], config['awssecretkey'])
     try:
@@ -501,7 +513,7 @@ def upload_to_virustotal(repo_section, vt_apikey):
                             with open(outputfilename, 'w') as fp:
                                 json.dump(response, fp, indent=2, sort_keys=True)
 
-                        if response.get('positives') > 0:
+                        if response.get('positives', 0) > 0:
                             logging.warning(repofilename + ' has been flagged by virustotal '
                                             + str(response['positives']) + ' times:'
                                             + '\n\t' + response['permalink'])
index 2019063c6e7df8c2183a40d9411a84c8187cfe0e..71c4e430d4edee755dcae42f10ed0f09a81e4a2c 100644 (file)
@@ -29,7 +29,7 @@ import zipfile
 import hashlib
 import pickle
 import time
-from datetime import datetime, timedelta
+from datetime import datetime
 from argparse import ArgumentParser
 
 import collections
@@ -772,7 +772,8 @@ def insert_localized_app_metadata(apps):
 
     """
 
-    sourcedirs = glob.glob(os.path.join('build', '[A-Za-z]*', 'fastlane', 'metadata', 'android', '[a-z][a-z]*'))
+    sourcedirs = glob.glob(os.path.join('build', '[A-Za-z]*', 'src', '[A-Za-z]*', 'fastlane', 'metadata', 'android', '[a-z][a-z]*'))
+    sourcedirs += glob.glob(os.path.join('build', '[A-Za-z]*', 'fastlane', 'metadata', 'android', '[a-z][a-z]*'))
     sourcedirs += glob.glob(os.path.join('build', '[A-Za-z]*', 'metadata', '[a-z][a-z]*'))
     sourcedirs += glob.glob(os.path.join('metadata', '[A-Za-z]*', '[a-z][a-z]*'))
 
@@ -787,6 +788,17 @@ def insert_localized_app_metadata(apps):
                 continue
             locale = segments[-1]
             destdir = os.path.join('repo', packageName, locale)
+
+            # flavours specified in build receipt
+            build_flavours = ""
+            if apps[packageName] and 'builds' in apps[packageName] and len(apps[packageName].builds) > 0\
+                    and 'gradle' in apps[packageName].builds[-1]:
+                build_flavours = apps[packageName].builds[-1].gradle
+
+            if len(segments) >= 5 and segments[4] == "fastlane" and segments[3] not in build_flavours:
+                logging.debug("ignoring due to wrong flavour")
+                continue
+
             for f in files:
                 if f in ('description.txt', 'full_description.txt'):
                     _set_localized_text_entry(apps[packageName], locale, 'description',
@@ -1297,22 +1309,11 @@ def process_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk=Fal
 
         apkzip = zipfile.ZipFile(apkfile, 'r')
 
-        # if an APK has files newer than the system time, suggest updating
-        # the system clock.  This is useful for offline systems, used for
-        # signing, which do not have another source of clock sync info. It
-        # has to be more than 24 hours newer because ZIP/APK files do not
-        # store timezone info
         manifest = apkzip.getinfo('AndroidManifest.xml')
         if manifest.date_time[1] == 0:  # month can't be zero
             logging.debug(_('AndroidManifest.xml has no date'))
         else:
-            dt_obj = datetime(*manifest.date_time)
-            checkdt = dt_obj - timedelta(1)
-            if datetime.today() < checkdt:
-                logging.warning('System clock is older than manifest in: '
-                                + apkfilename
-                                + '\nSet clock to that time using:\n'
-                                + 'sudo date -s "' + str(dt_obj) + '"')
+            common.check_system_clock(datetime(*manifest.date_time), apkfilename)
 
         # extract icons from APK zip file
         iconfilename = "%s.%s.png" % (apk['packageName'], apk['versionCode'])
index aae46c75cc8cfe44a093529a84d655c5e8163b60..2142beb7db0d532bedc24e8ce7fb33b8e17fadeb 100644 (file)
@@ -22,13 +22,14 @@ import os
 import math
 import json
 import tarfile
-import time
 import shutil
 import subprocess
 import textwrap
 from .common import FDroidException
 from logging import getLogger
 
+from fdroidserver import _
+
 logger = getLogger('fdroidserver-vmtools')
 
 
@@ -192,8 +193,6 @@ class FDroidBuildVm():
     def up(self, provision=True):
         try:
             self.vgrnt.up(provision=provision)
-            logger.info('...waiting a sec...')
-            time.sleep(10)
             self.srvuuid = self._vagrant_fetch_uuid()
         except subprocess.CalledProcessError as e:
             raise FDroidBuildVmException("could not bring up vm '%s'" % self.srvname) from e
@@ -202,8 +201,6 @@ class FDroidBuildVm():
         logger.info('suspending buildserver')
         try:
             self.vgrnt.suspend()
-            logger.info('...waiting a sec...')
-            time.sleep(10)
         except subprocess.CalledProcessError as e:
             raise FDroidBuildVmException("could not suspend vm '%s'" % self.srvname) from e
 
@@ -350,16 +347,12 @@ class LibvirtBuildVm(FDroidBuildVm):
         # (eg. lookupByName only works on running VMs)
         try:
             _check_call(('virsh', '-c', 'qemu:///system', 'destroy', self.srvname))
-            logger.info("...waiting a sec...")
-            time.sleep(10)
         except subprocess.CalledProcessError as e:
             logger.info("could not force libvirt domain '%s' off: %s", self.srvname, e)
         try:
             # libvirt python bindings do not support all flags required
             # for undefining domains correctly.
             _check_call(('virsh', '-c', 'qemu:///system', 'undefine', self.srvname, '--nvram', '--managed-save', '--remove-all-storage', '--snapshots-metadata'))
-            logger.info("...waiting a sec...")
-            time.sleep(10)
         except subprocess.CalledProcessError as e:
             logger.info("could not undefine libvirt domain '%s': %s", self.srvname, e)
 
@@ -383,7 +376,9 @@ class LibvirtBuildVm(FDroidBuildVm):
             vol = storagePool.storageVolLookupByName(self.srvname + '.img')
             imagepath = vol.path()
             # TODO use a libvirt storage pool to ensure the img file is readable
-            _check_call(['sudo', '/bin/chmod', '-R', 'a+rX', '/var/lib/libvirt/images'])
+            if not os.access(imagepath, os.R_OK):
+                logger.warning(_('Cannot read "{path}"!').format(path=imagepath))
+                _check_call(['sudo', '/bin/chmod', '-R', 'a+rX', '/var/lib/libvirt/images'])
             shutil.copy2(imagepath, 'box.img')
             _check_call(['qemu-img', 'rebase', '-p', '-b', '', 'box.img'])
             img_info_raw = _check_output(['qemu-img', 'info', '--output=json', 'box.img'])
@@ -454,8 +449,6 @@ class LibvirtBuildVm(FDroidBuildVm):
         logger.info("creating snapshot '%s' for vm '%s'", snapshot_name, self.srvname)
         try:
             _check_call(['virsh', '-c', 'qemu:///system', 'snapshot-create-as', self.srvname, snapshot_name])
-            logger.info('...waiting a sec...')
-            time.sleep(10)
         except subprocess.CalledProcessError as e:
             raise FDroidBuildVmException("could not cerate snapshot '%s' "
                                          "of libvirt vm '%s'"
@@ -484,8 +477,6 @@ class LibvirtBuildVm(FDroidBuildVm):
             dom = self.conn.lookupByName(self.srvname)
             snap = dom.snapshotLookupByName(snapshot_name)
             dom.revertToSnapshot(snap)
-            logger.info('...waiting a sec...')
-            time.sleep(10)
         except libvirt.libvirtError as e:
             raise FDroidBuildVmException('could not revert domain \'%s\' to snapshot \'%s\''
                                          % (self.srvname, snapshot_name)) from e
@@ -501,8 +492,6 @@ class VirtualboxBuildVm(FDroidBuildVm):
         logger.info("creating snapshot '%s' for vm '%s'", snapshot_name, self.srvname)
         try:
             _check_call(['VBoxManage', 'snapshot', self.srvuuid, 'take', 'fdroidclean'], cwd=self.srvdir)
-            logger.info('...waiting a sec...')
-            time.sleep(10)
         except subprocess.CalledProcessError as e:
             raise FDroidBuildVmException('could not cerate snapshot '
                                          'of virtualbox vm %s'
index 2abac58cc5c3b3d07fb76b3bc766a0336f9739bd..d41d920f79ef5e4b43a638fbbc2007d18babf5d1 100755 (executable)
@@ -31,7 +31,6 @@ else
         echo "No virtualization is used."
 fi
 sudo /bin/chmod -R a+rX /var/lib/libvirt/images
-ulimit -n 2048
 echo 'maximum allowed number of open file descriptors: ' `ulimit -n`
 ls -ld /var/lib/libvirt/images
 ls -l /var/lib/libvirt/images || echo no access
index 1552a63681f02aab55cf43c699a09a926a044a1a..a382b254d86de5cea3b906b6df1e7545c479a662 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -89,6 +89,7 @@ setup(name='fdroidserver',
           'pyasn1-modules',
           'python-vagrant',
           'PyYAML',
+          'qrcode',
           'ruamel.yaml >= 0.13',
           'requests >= 2.5.2, != 2.11.0, != 2.12.2, != 2.18.0',
           'docker-py >= 1.9, < 2.0',
index b595f3fab7dc32588fd5924aa730d90a9addf68c..c9ffe97c5bcc067e5176d3a094a281510c4786a9 100755 (executable)
@@ -3,6 +3,7 @@
 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
 
 import inspect
+import logging
 import optparse
 import os
 import re
@@ -46,22 +47,27 @@ class BuildTest(unittest.TestCase):
                 self.assertTrue(os.path.exists(path))
                 self.assertTrue(os.path.isfile(path))
 
+    def setUp(self):
+        logging.basicConfig(level=logging.DEBUG)
+        self.basedir = os.path.join(localmodule, 'tests')
+        self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles'))
+        if not os.path.exists(self.tmpdir):
+            os.makedirs(self.tmpdir)
+        os.chdir(self.basedir)
+
     def test_force_gradle_build_tools(self):
-        testsbase = os.path.join(os.path.dirname(__file__), '..', '.testfiles')
-        if not os.path.exists(testsbase):
-            os.makedirs(testsbase)
-        testsdir = tempfile.mkdtemp(prefix='test_adapt_gradle', dir=testsbase)
+        testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
         shutil.copytree(os.path.join(os.path.dirname(__file__), 'source-files'),
-                        os.path.join(testsdir, 'source-files'))
+                        os.path.join(testdir, 'source-files'))
         teststring = 'FAKE_VERSION_FOR_TESTING'
-        fdroidserver.build.force_gradle_build_tools(testsdir, teststring)
+        fdroidserver.build.force_gradle_build_tools(testdir, teststring)
         pattern = re.compile(bytes("buildToolsVersion[\s=]+'%s'\s+" % teststring, 'utf8'))
         for p in ('source-files/fdroid/fdroidclient/build.gradle',
                   'source-files/Zillode/syncthing-silk/build.gradle',
                   'source-files/open-keychain/open-keychain/build.gradle',
                   'source-files/osmandapp/osmand/build.gradle',
                   'source-files/open-keychain/open-keychain/OpenKeychain/build.gradle'):
-            with open(os.path.join(testsdir, p), 'rb') as f:
+            with open(os.path.join(testdir, p), 'rb') as f:
                 filedata = f.read()
             self.assertIsNotNone(pattern.search(filedata))
 
index a5d52f75eccf6a77b4387b0cfa4796b92030bdf3..dfb4380a23e9317d0e8ff4fff9dbc9741c7300f1 100755 (executable)
@@ -96,8 +96,8 @@ class CommonTest(unittest.TestCase):
                 print('no build-tools found: ' + build_tools)
 
     def test_find_java_root_path(self):
-        tmptestsdir = tempfile.mkdtemp(prefix='test_find_java_root_path', dir=self.tmpdir)
-        os.chdir(tmptestsdir)
+        testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
+        os.chdir(testdir)
 
         all_pathlists = [
             ([  # Debian
@@ -170,11 +170,11 @@ class CommonTest(unittest.TestCase):
         testint = 99999999
         teststr = 'FAKE_STR_FOR_TESTING'
 
-        tmptestsdir = tempfile.mkdtemp(prefix='test_prepare_sources', dir=self.tmpdir)
+        testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
         shutil.copytree(os.path.join(self.basedir, 'source-files'),
-                        os.path.join(tmptestsdir, 'source-files'))
+                        os.path.join(testdir, 'source-files'))
 
-        testdir = os.path.join(tmptestsdir, 'source-files', 'fdroid', 'fdroidclient')
+        fdroidclient_testdir = os.path.join(testdir, 'source-files', 'fdroid', 'fdroidclient')
 
         config = dict()
         config['sdk_path'] = os.getenv('ANDROID_HOME')
@@ -201,13 +201,14 @@ class CommonTest(unittest.TestCase):
             def getsrclib(self):
                 return None
 
-        fdroidserver.common.prepare_source(FakeVcs(), app, build, testdir, testdir, testdir)
+        fdroidserver.common.prepare_source(FakeVcs(), app, build,
+                                           fdroidclient_testdir, fdroidclient_testdir, fdroidclient_testdir)
 
-        with open(os.path.join(testdir, 'build.gradle'), 'r') as f:
+        with open(os.path.join(fdroidclient_testdir, 'build.gradle'), 'r') as f:
             filedata = f.read()
         self.assertIsNotNone(re.search("\s+compileSdkVersion %s\s+" % testint, filedata))
 
-        with open(os.path.join(testdir, 'AndroidManifest.xml')) as f:
+        with open(os.path.join(fdroidclient_testdir, 'AndroidManifest.xml')) as f:
             filedata = f.read()
         self.assertIsNone(re.search('android:debuggable', filedata))
         self.assertIsNotNone(re.search('android:versionName="%s"' % build.versionName, filedata))
@@ -215,7 +216,7 @@ class CommonTest(unittest.TestCase):
 
     def test_prepare_sources_refresh(self):
         packageName = 'org.fdroid.ci.test.app'
-        testdir = tempfile.mkdtemp(prefix='test_prepare_sources_refresh', dir=self.tmpdir)
+        testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
         print('testdir', testdir)
         os.chdir(testdir)
         os.mkdir('build')
@@ -262,7 +263,7 @@ class CommonTest(unittest.TestCase):
         fdroidserver.signindex.config = config
 
         sourcedir = os.path.join(self.basedir, 'signindex')
-        testsdir = tempfile.mkdtemp(prefix='test_signjar', dir=self.tmpdir)
+        testsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
         for f in ('testy.jar', 'guardianproject.jar',):
             sourcefile = os.path.join(sourcedir, f)
             testfile = os.path.join(testsdir, f)
@@ -291,7 +292,7 @@ class CommonTest(unittest.TestCase):
 
         sourceapk = os.path.join(self.basedir, 'urzip.apk')
 
-        testdir = tempfile.mkdtemp(prefix='test_verify_apks', dir=self.tmpdir)
+        testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
         print('testdir', testdir)
 
         copyapk = os.path.join(testdir, 'urzip-copy.apk')
@@ -491,6 +492,35 @@ class CommonTest(unittest.TestCase):
         sig = fdroidserver.common.metadata_find_developer_signature('org.smssecure.smssecure')
         self.assertEqual('b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868', sig)
 
+    def test_parse_androidmanifests(self):
+        source_files_dir = os.path.join(os.path.dirname(__file__), 'source-files')
+        app = fdroidserver.metadata.App()
+        app.id = 'org.fdroid.fdroid'
+        paths = [
+            os.path.join(source_files_dir, 'fdroid', 'fdroidclient', 'AndroidManifest.xml'),
+            os.path.join(source_files_dir, 'fdroid', 'fdroidclient', 'build.gradle'),
+        ]
+        for path in paths:
+            self.assertTrue(os.path.isfile(path))
+        self.assertEqual(('0.94-test', '940', 'org.fdroid.fdroid'),
+                         fdroidserver.common.parse_androidmanifests(paths, app))
+
+    def test_parse_androidmanifests_with_flavor(self):
+        source_files_dir = os.path.join(os.path.dirname(__file__), 'source-files')
+        app = fdroidserver.metadata.App()
+        build = fdroidserver.metadata.Build()
+        build.gradle = ['devVersion']
+        app.builds = [build]
+        app.id = 'org.fdroid.fdroid.dev'
+        paths = [
+            os.path.join(source_files_dir, 'fdroid', 'fdroidclient', 'AndroidManifest.xml'),
+            os.path.join(source_files_dir, 'fdroid', 'fdroidclient', 'build.gradle'),
+        ]
+        for path in paths:
+            self.assertTrue(os.path.isfile(path))
+        self.assertEqual(('0.95-dev', '949', 'org.fdroid.fdroid.dev'),
+                         fdroidserver.common.parse_androidmanifests(paths, app))
+
 
 if __name__ == "__main__":
     parser = optparse.OptionParser()
@@ -500,4 +530,4 @@ if __name__ == "__main__":
 
     newSuite = unittest.TestSuite()
     newSuite.addTest(unittest.makeSuite(CommonTest))
-    unittest.main(failfast=True)
+    unittest.main(failfast=False)
index a41028c91c81371c0c652c5803527a44fc4365de..76427c5932672be789aa6d6720dd1a30fbbed878 100755 (executable)
@@ -3,6 +3,7 @@
 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
 
 import inspect
+import logging
 import optparse
 import os
 import requests
@@ -24,8 +25,15 @@ import import_proxy
 class ImportTest(unittest.TestCase):
     '''fdroid import'''
 
+    def setUp(self):
+        logging.basicConfig(level=logging.DEBUG)
+        self.basedir = os.path.join(localmodule, 'tests')
+        self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles'))
+        if not os.path.exists(self.tmpdir):
+            os.makedirs(self.tmpdir)
+        os.chdir(self.basedir)
+
     def test_import_gitlab(self):
-        os.chdir(os.path.dirname(__file__))
         # FDroidPopen needs some config to work
         config = dict()
         fdroidserver.common.fill_config_defaults(config)
index 4a7463fb523d37aeccd829d677f81644a7db23af..671370c3e318aeb140dd57d93a6f7c5579ad3742 100755 (executable)
@@ -1,6 +1,7 @@
 #!/usr/bin/env python3
 
 import inspect
+import logging
 import optparse
 import os
 import sys
@@ -31,30 +32,33 @@ GP_FINGERPRINT = 'B7C2EEFD8DAC7806AF67DFCD92EB18126BC08312A7F2D6F3862E46013C7A61
 class IndexTest(unittest.TestCase):
 
     def setUp(self):
+        logging.basicConfig(level=logging.DEBUG)
+        self.basedir = os.path.join(localmodule, 'tests')
+        self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles'))
+        if not os.path.exists(self.tmpdir):
+            os.makedirs(self.tmpdir)
+        os.chdir(self.basedir)
+
         fdroidserver.common.config = None
         config = fdroidserver.common.read_config(fdroidserver.common.options)
         config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
         fdroidserver.common.config = config
         fdroidserver.signindex.config = config
 
-    @staticmethod
-    def test_verify_jar_signature_succeeds():
-        basedir = os.path.dirname(__file__)
-        source_dir = os.path.join(basedir, 'signindex')
+    def test_verify_jar_signature_succeeds(self):
+        source_dir = os.path.join(self.basedir, 'signindex')
         for f in ('testy.jar', 'guardianproject.jar'):
             testfile = os.path.join(source_dir, f)
             fdroidserver.common.verify_jar_signature(testfile)
 
     def test_verify_jar_signature_fails(self):
-        basedir = os.path.dirname(__file__)
-        source_dir = os.path.join(basedir, 'signindex')
+        source_dir = os.path.join(self.basedir, 'signindex')
         testfile = os.path.join(source_dir, 'unsigned.jar')
         with self.assertRaises(fdroidserver.index.VerificationException):
             fdroidserver.common.verify_jar_signature(testfile)
 
     def test_get_public_key_from_jar_succeeds(self):
-        basedir = os.path.dirname(__file__)
-        source_dir = os.path.join(basedir, 'signindex')
+        source_dir = os.path.join(self.basedir, 'signindex')
         for f in ('testy.jar', 'guardianproject.jar'):
             testfile = os.path.join(source_dir, f)
             jar = zipfile.ZipFile(testfile)
@@ -68,8 +72,7 @@ class IndexTest(unittest.TestCase):
                 self.assertTrue(fingerprint == GP_FINGERPRINT)
 
     def test_get_public_key_from_jar_fails(self):
-        basedir = os.path.dirname(__file__)
-        source_dir = os.path.join(basedir, 'signindex')
+        source_dir = os.path.join(self.basedir, 'signindex')
         testfile = os.path.join(source_dir, 'unsigned.jar')
         jar = zipfile.ZipFile(testfile)
         with self.assertRaises(fdroidserver.index.VerificationException):
@@ -226,9 +229,6 @@ class IndexTest(unittest.TestCase):
 
 
 if __name__ == "__main__":
-    if os.path.basename(os.getcwd()) != 'tests' and os.path.isdir('tests'):
-        os.chdir('tests')
-
     parser = optparse.OptionParser()
     parser.add_option("-v", "--verbose", action="store_true", default=False,
                       help="Spew out even more information than normal")
index f2a411c5f61917c1016a2b6cb5833f98c7a142aa..433c07875d8f39755f6af072bc7c113d3b2455e6 100755 (executable)
@@ -32,8 +32,7 @@ class LintTest(unittest.TestCase):
         self.assertTrue(fdroidserver.lint.check_for_unsupported_metadata_files())
 
         tmpdir = os.path.join(localmodule, '.testfiles')
-        tmptestsdir = tempfile.mkdtemp(prefix='test_check_for_unsupported_metadata_files-',
-                                       dir=tmpdir)
+        tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=tmpdir)
         self.assertFalse(fdroidserver.lint.check_for_unsupported_metadata_files(tmptestsdir + '/'))
         shutil.copytree(os.path.join(localmodule, 'tests', 'metadata'),
                         os.path.join(tmptestsdir, 'metadata'),
index 306f1c46949ba855eeda81daf1a0856f0a119979..247f6a89facf8d946776ffc5520ba1459bd35cd6 100755 (executable)
@@ -2,9 +2,13 @@
 
 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
 
+import glob
 import inspect
+import logging
 import optparse
 import os
+import random
+import shutil
 import sys
 import unittest
 import yaml
@@ -23,15 +27,20 @@ import fdroidserver.metadata
 class MetadataTest(unittest.TestCase):
     '''fdroidserver/metadata.py'''
 
+    def setUp(self):
+        logging.basicConfig(level=logging.DEBUG)
+        self.basedir = os.path.join(localmodule, 'tests')
+        self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles'))
+        if not os.path.exists(self.tmpdir):
+            os.makedirs(self.tmpdir)
+        os.chdir(self.basedir)
+
     def test_read_metadata(self):
 
         def _build_yaml_representer(dumper, data):
             '''Creates a YAML representation of a Build instance'''
             return dumper.represent_dict(data)
 
-        testsdir = os.path.dirname(__file__)
-        os.chdir(testsdir)
-
         self.maxDiff = None
 
         # these need to be set to prevent code running on None, only
@@ -58,12 +67,7 @@ class MetadataTest(unittest.TestCase):
             #     yaml.dump(frommeta, f, default_flow_style=False)
 
     def test_rewrite_yaml_fakeotaupdate(self):
-
-        # setup/reset test dir if necessary and setup params
-        tmpdir = os.path.join(os.path.dirname(__file__), '..', '.testfiles')
-        if not os.path.exists(tmpdir):
-            os.makedirs(tmpdir)
-        testdir = tempfile.mkdtemp(prefix='test_rewrite_metadata_', dir=tmpdir)
+        testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
         fdroidserver.common.config = {'accepted_formats': ['txt', 'yml']}
 
         # rewrite metadata
@@ -79,12 +83,7 @@ class MetadataTest(unittest.TestCase):
                 self.assertEqual(result.read(), orig.read())
 
     def test_rewrite_yaml_fdroidclient(self):
-
-        # setup/reset test dir if necessary and setup params
-        tmpdir = os.path.join(os.path.dirname(__file__), '..', '.testfiles')
-        if not os.path.exists(tmpdir):
-            os.makedirs(tmpdir)
-        testdir = tempfile.mkdtemp(prefix='test_rewrite_metadata_', dir=tmpdir)
+        testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
         fdroidserver.common.config = {'accepted_formats': ['txt', 'yml']}
 
         # rewrite metadata
@@ -100,12 +99,7 @@ class MetadataTest(unittest.TestCase):
                 self.assertEqual(result.read(), orig.read())
 
     def test_rewrite_yaml_special_build_params(self):
-
-        # setup/reset test dir if necessary and setup params
-        tmpdir = os.path.join(os.path.dirname(__file__), '..', '.testfiles')
-        if not os.path.exists(tmpdir):
-            os.makedirs(tmpdir)
-        testdir = tempfile.mkdtemp(prefix='test_rewrite_metadata_', dir=tmpdir)
+        testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
         fdroidserver.common.config = {'accepted_formats': ['txt', 'yml']}
 
         # rewrite metadata
@@ -120,6 +114,31 @@ class MetadataTest(unittest.TestCase):
                 self.maxDiff = None
                 self.assertEqual(result.read(), orig.read())
 
+    def test_read_metadata_sort_by_time(self):
+        testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
+        metadatadir = os.path.join(testdir, 'metadata')
+        os.makedirs(metadatadir)
+        fdroidserver.common.config = {'accepted_formats': ['txt']}
+
+        randomlist = []
+        randomapps = glob.glob(os.path.join(self.basedir, 'metadata', '*.txt'))
+        random.shuffle(randomapps)
+        i = 1
+        for f in randomapps:
+            shutil.copy(f, metadatadir)
+            new = os.path.join(metadatadir, os.path.basename(f))
+            stat = os.stat(new)
+            os.utime(new, (stat.st_ctime, stat.st_mtime + i))
+            # prepend new item so newest is always first
+            randomlist = [os.path.basename(f)[:-4]] + randomlist
+            i += 1
+        os.chdir(testdir)
+        allapps = fdroidserver.metadata.read_metadata(xref=True, sort_by_time=True)
+        allappids = []
+        for appid, app in allapps.items():
+            allappids.append(appid)
+        self.assertEqual(randomlist, allappids)
+
 
 if __name__ == "__main__":
     parser = optparse.OptionParser()
index 8e165608682141205ad79a25d8c3b1bd0e2b00d9..e296a48a038749d1f1b282319e0f56f256facc63 100755 (executable)
@@ -11,6 +11,7 @@
 #
 
 import inspect
+import logging
 import optparse
 import os
 import sys
@@ -32,6 +33,14 @@ from fdroidserver.exception import FDroidException
 class PublishTest(unittest.TestCase):
     '''fdroidserver/publish.py'''
 
+    def setUp(self):
+        logging.basicConfig(level=logging.DEBUG)
+        self.basedir = os.path.join(localmodule, 'tests')
+        self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles'))
+        if not os.path.exists(self.tmpdir):
+            os.makedirs(self.tmpdir)
+        os.chdir(self.basedir)
+
     def test_key_alias(self):
         publish.config = {}
         self.assertEqual('a163ec9b', publish.key_alias('com.example.app'))
@@ -77,39 +86,35 @@ class PublishTest(unittest.TestCase):
                   'com.example.anotherapp',
                   'org.org.org']
 
-        with tempfile.TemporaryDirectory() as tmpdir:
-            orig_cwd = os.getcwd()
-            try:
-                os.chdir(tmpdir)
-                with open('config.py', 'w') as f:
-                    pass
-
-                publish.store_stats_fdroid_signing_key_fingerprints(appids, indent=2)
-
-                self.maxDiff = None
-                expected = {
-                    "com.example.anotherapp": {
-                        "signer": "fa3f6a017541ee7fe797be084b1bcfbf92418a7589ef1f7fdeb46741b6d2e9c3"
-                    },
-                    "com.example.app": {
-                        "signer": "d34f678afbaa8f2fa6cc0edd6f0c2d1d2e2e9eb08bea521b24c740806016bff4"
-                    },
-                    "org.org.org": {
-                        "signer": "277655a6235bc6b0ef2d824396c51ba947f5ebc738c293d887e7083ff338af82"
-                    },
-                    "org.test.testy": {
-                        "signer": "6ae5355157a47ddcc3834a71f57f6fb5a8c2621c8e0dc739e9ddf59f865e497c"
-                    }
-                }
-                self.assertEqual(expected, common.load_stats_fdroid_signing_key_fingerprints())
-
-                with open('config.py', 'r') as f:
-                    self.assertEqual(textwrap.dedent('''\
-
-                        repo_key_sha256 = "c58460800c7b250a619c30c13b07b7359a43e5af71a4352d86c58ae18c9f6d41"
-                        '''), f.read())
-            finally:
-                os.chdir(orig_cwd)
+        testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
+        os.chdir(testdir)
+        with open('config.py', 'w') as f:
+            pass
+
+        publish.store_stats_fdroid_signing_key_fingerprints(appids, indent=2)
+
+        self.maxDiff = None
+        expected = {
+            "com.example.anotherapp": {
+                "signer": "fa3f6a017541ee7fe797be084b1bcfbf92418a7589ef1f7fdeb46741b6d2e9c3"
+            },
+            "com.example.app": {
+                "signer": "d34f678afbaa8f2fa6cc0edd6f0c2d1d2e2e9eb08bea521b24c740806016bff4"
+            },
+            "org.org.org": {
+                "signer": "277655a6235bc6b0ef2d824396c51ba947f5ebc738c293d887e7083ff338af82"
+            },
+            "org.test.testy": {
+                "signer": "6ae5355157a47ddcc3834a71f57f6fb5a8c2621c8e0dc739e9ddf59f865e497c"
+            }
+        }
+        self.assertEqual(expected, common.load_stats_fdroid_signing_key_fingerprints())
+
+        with open('config.py', 'r') as f:
+            self.assertEqual(textwrap.dedent('''\
+
+                repo_key_sha256 = "c58460800c7b250a619c30c13b07b7359a43e5af71a4352d86c58ae18c9f6d41"
+                '''), f.read())
 
     def test_store_and_load_fdroid_signing_key_fingerprints_with_missmatch(self):
         common.config = {}
@@ -122,21 +127,14 @@ class PublishTest(unittest.TestCase):
         publish.config['repo_keyalias'] = 'repokey'
         publish.config['repo_key_sha256'] = 'bad bad bad bad bad bad bad bad bad bad bad bad'
 
-        with tempfile.TemporaryDirectory() as tmpdir:
-            orig_cwd = os.getcwd()
-            try:
-                os.chdir(tmpdir)
-                publish.store_stats_fdroid_signing_key_fingerprints({}, indent=2)
-                with self.assertRaises(FDroidException):
-                    common.load_stats_fdroid_signing_key_fingerprints()
-            finally:
-                os.chdir(orig_cwd)
+        testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir)
+        os.chdir(testdir)
+        publish.store_stats_fdroid_signing_key_fingerprints({}, indent=2)
+        with self.assertRaises(FDroidException):
+            common.load_stats_fdroid_signing_key_fingerprints()
 
 
 if __name__ == "__main__":
-    if os.path.basename(os.getcwd()) != 'tests' and os.path.isdir('tests'):
-        os.chdir('tests')
-
     parser = optparse.OptionParser()
     parser.add_option("-v", "--verbose", action="store_true", default=False,
                       help="Spew out even more information than normal")
diff --git a/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/full_description.txt b/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/full_description.txt
new file mode 100644 (file)
index 0000000..f11ffa1
--- /dev/null
@@ -0,0 +1 @@
+The copyleft libre software Nextcloud Android app, gives you access to all the files in your Nextcloud.\n\nFeatures:\n* Easy, modern interface, suited to the theme of your server\n* Upload files to your Nextcloud server\n* Share them with others\n* Keep your favorite files and folders synced\n* Search across all folders on your server\n* Auto Upload for photos and videos taken by your device\n* Keep up to date with notifications\n* Multi-account support\n* Secure access to your data with fingerprint or PIN\n* Integration with DAVdroid for easy setup of calendar & Contacts synchronization\n\nPlease report all issues at https://github.com/nextcloud/android/issues and discuss this app at https://help.nextcloud.com/c/clients/android\n\nNew to Nextcloud? Nextcloud is a private file sync & share and communication server. It is libre software, and you can host it yourself or pay a company to do it for you. That way, you are in control of your photos, your calendar and contact data, your documents and everything else.\n\nCheck out Nextcloud at https://nextcloud.com
\ No newline at end of file
diff --git a/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/short_description.txt b/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/short_description.txt
new file mode 100644 (file)
index 0000000..69b7d99
--- /dev/null
@@ -0,0 +1 @@
+The Nextcloud Android app gives you access to all your files in your Nextcloud
\ No newline at end of file
diff --git a/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/title.txt b/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/title.txt
new file mode 100644 (file)
index 0000000..9928b03
--- /dev/null
@@ -0,0 +1 @@
+Nextcloud
\ No newline at end of file
diff --git a/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/full_description.txt b/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/full_description.txt
new file mode 100644 (file)
index 0000000..8593fc4
--- /dev/null
@@ -0,0 +1 @@
+The Open Source Nextcloud Android app allows you to access all your files on your Nextcloud.\nThis is a dev version of the official Nextcloud app and includes brand-new, untested features which might lead to instabilities and data loss. The app is designed for users willing to test the new features and to report bugs if they occur. Do not use it for your productive work!\n\nThe dev version can be installed alongside the official Nextcloud app which is available at F-Droid, too. Once a day it is checked if the source code was updated, so there can be longer pauses between builds.
diff --git a/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/short_description.txt b/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/short_description.txt
new file mode 100644 (file)
index 0000000..42b3df3
--- /dev/null
@@ -0,0 +1 @@
+The Nextcloud Dev app is a development snapshot and can be installed parallel.
diff --git a/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/title.txt b/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/title.txt
new file mode 100644 (file)
index 0000000..dc9e619
--- /dev/null
@@ -0,0 +1 @@
+Nextcloud Dev
\ No newline at end of file
diff --git a/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/full_description.txt b/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/full_description.txt
new file mode 100644 (file)
index 0000000..f11ffa1
--- /dev/null
@@ -0,0 +1 @@
+The copyleft libre software Nextcloud Android app, gives you access to all the files in your Nextcloud.\n\nFeatures:\n* Easy, modern interface, suited to the theme of your server\n* Upload files to your Nextcloud server\n* Share them with others\n* Keep your favorite files and folders synced\n* Search across all folders on your server\n* Auto Upload for photos and videos taken by your device\n* Keep up to date with notifications\n* Multi-account support\n* Secure access to your data with fingerprint or PIN\n* Integration with DAVdroid for easy setup of calendar & Contacts synchronization\n\nPlease report all issues at https://github.com/nextcloud/android/issues and discuss this app at https://help.nextcloud.com/c/clients/android\n\nNew to Nextcloud? Nextcloud is a private file sync & share and communication server. It is libre software, and you can host it yourself or pay a company to do it for you. That way, you are in control of your photos, your calendar and contact data, your documents and everything else.\n\nCheck out Nextcloud at https://nextcloud.com
\ No newline at end of file
diff --git a/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/short_description.txt b/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/short_description.txt
new file mode 100644 (file)
index 0000000..69b7d99
--- /dev/null
@@ -0,0 +1 @@
+The Nextcloud Android app gives you access to all your files in your Nextcloud
\ No newline at end of file
diff --git a/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/title.txt b/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/title.txt
new file mode 100644 (file)
index 0000000..9928b03
--- /dev/null
@@ -0,0 +1 @@
+Nextcloud
\ No newline at end of file
diff --git a/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/full_description.txt b/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/full_description.txt
new file mode 100644 (file)
index 0000000..8593fc4
--- /dev/null
@@ -0,0 +1 @@
+The Open Source Nextcloud Android app allows you to access all your files on your Nextcloud.\nThis is a dev version of the official Nextcloud app and includes brand-new, untested features which might lead to instabilities and data loss. The app is designed for users willing to test the new features and to report bugs if they occur. Do not use it for your productive work!\n\nThe dev version can be installed alongside the official Nextcloud app which is available at F-Droid, too. Once a day it is checked if the source code was updated, so there can be longer pauses between builds.
diff --git a/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/short_description.txt b/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/short_description.txt
new file mode 100644 (file)
index 0000000..42b3df3
--- /dev/null
@@ -0,0 +1 @@
+The Nextcloud Dev app is a development snapshot and can be installed parallel.
diff --git a/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/title.txt b/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/title.txt
new file mode 100644 (file)
index 0000000..dc9e619
--- /dev/null
@@ -0,0 +1 @@
+Nextcloud Dev
\ No newline at end of file
index 1d994dc8556b25422499b25084da9816161220c7..c81ff357828b84f7c5547baa45dfdbcd60fb912b 100644 (file)
@@ -128,6 +128,21 @@ task binaryDeps(type: Copy, dependsOn: ':F-Droid:prepareReleaseDependencies') {
 android {
     compileSdkVersion 21
     buildToolsVersion '22.0.1'
+    
+    defaultConfig {
+    
+        flavorDimensions "default"
+                
+        productFlavors {
+            devVersion {
+                applicationId "org.fdroid.fdroid.dev"
+                dimension "default"
+                versionCode 949
+                versionName "0.95-dev"
+            }
+        }
+    
+    }
 
     sourceSets {
         main {
index 8a88fc4e936a9742769e231963a8f3301a446422..8e4ac3196377fbf2c83c8abec643b6f872e15326 100755 (executable)
@@ -42,19 +42,37 @@ class UpdateTest(unittest.TestCase):
 
         shutil.rmtree(os.path.join('repo', 'info.guardianproject.urzip'), ignore_errors=True)
 
+        shutil.rmtree(os.path.join('build', 'com.nextcloud.client'), ignore_errors=True)
+        shutil.copytree(os.path.join('source-files', 'com.nextcloud.client'), os.path.join('build', 'com.nextcloud.client'))
+
+        shutil.rmtree(os.path.join('build', 'com.nextcloud.client.dev'), ignore_errors=True)
+        shutil.copytree(os.path.join('source-files', 'com.nextcloud.client.dev'),
+                        os.path.join('build', 'com.nextcloud.client.dev'))
+
         apps = dict()
-        for packageName in ('info.guardianproject.urzip', 'org.videolan.vlc', 'obb.mainpatch.current'):
-            apps[packageName] = dict()
+        for packageName in ('info.guardianproject.urzip', 'org.videolan.vlc', 'obb.mainpatch.current',
+                            'com.nextcloud.client', 'com.nextcloud.client.dev'):
+            apps[packageName] = fdroidserver.metadata.App()
             apps[packageName]['id'] = packageName
             apps[packageName]['CurrentVersionCode'] = 0xcafebeef
+
         apps['info.guardianproject.urzip']['CurrentVersionCode'] = 100
+
+        buildnextcloudclient = fdroidserver.metadata.Build()
+        buildnextcloudclient.gradle = ['generic']
+        apps['com.nextcloud.client']['builds'] = [buildnextcloudclient]
+
+        buildnextclouddevclient = fdroidserver.metadata.Build()
+        buildnextclouddevclient.gradle = ['versionDev']
+        apps['com.nextcloud.client.dev']['builds'] = [buildnextclouddevclient]
+
         fdroidserver.update.insert_localized_app_metadata(apps)
 
         appdir = os.path.join('repo', 'info.guardianproject.urzip', 'en-US')
         self.assertTrue(os.path.isfile(os.path.join(appdir, 'icon.png')))
         self.assertTrue(os.path.isfile(os.path.join(appdir, 'featureGraphic.png')))
 
-        self.assertEqual(3, len(apps))
+        self.assertEqual(5, len(apps))
         for packageName, app in apps.items():
             self.assertTrue('localized' in app)
             self.assertTrue('en-US' in app['localized'])
@@ -77,6 +95,14 @@ class UpdateTest(unittest.TestCase):
                 self.assertEqual('featureGraphic.png', app['localized']['en-US']['featureGraphic'])
                 self.assertEqual(1, len(app['localized']['en-US']['phoneScreenshots']))
                 self.assertEqual(1, len(app['localized']['en-US']['sevenInchScreenshots']))
+            elif packageName == 'com.nextcloud.client':
+                self.assertEqual('Nextcloud', app['localized']['en-US']['name'])
+                self.assertEqual(1073, len(app['localized']['en-US']['description']))
+                self.assertEqual(78, len(app['localized']['en-US']['summary']))
+            elif packageName == 'com.nextcloud.client.dev':
+                self.assertEqual('Nextcloud Dev', app['localized']['en-US']['name'])
+                self.assertEqual(586, len(app['localized']['en-US']['description']))
+                self.assertEqual(79, len(app['localized']['en-US']['summary']))
 
     def test_insert_triple_t_metadata(self):
         importer = os.path.join(localmodule, 'tests', 'tmp', 'importer')
@@ -87,7 +113,7 @@ class UpdateTest(unittest.TestCase):
         tmpdir = os.path.join(localmodule, '.testfiles')
         if not os.path.exists(tmpdir):
             os.makedirs(tmpdir)
-        tmptestsdir = tempfile.mkdtemp(prefix='test_insert_triple_t_metadata-', dir=tmpdir)
+        tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=tmpdir)
         packageDir = os.path.join(tmptestsdir, 'build', packageName)
         shutil.copytree(importer, packageDir)
 
@@ -374,7 +400,7 @@ class UpdateTest(unittest.TestCase):
         tmpdir = os.path.join(localmodule, '.testfiles')
         if not os.path.exists(tmpdir):
             os.makedirs(tmpdir)
-        tmptestsdir = tempfile.mkdtemp(prefix='test_process_apk_signed_by_disabled_algorithms-',
+        tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name,
                                        dir=tmpdir)
         print('tmptestsdir', tmptestsdir)
         os.chdir(tmptestsdir)
@@ -504,7 +530,7 @@ class UpdateTest(unittest.TestCase):
         tmpdir = os.path.join(localmodule, '.testfiles')
         if not os.path.exists(tmpdir):
             os.makedirs(tmpdir)
-        tmptestsdir = tempfile.mkdtemp(prefix='test_create_metadata_from_template-',
+        tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name,
                                        dir=tmpdir)
         print('tmptestsdir', tmptestsdir)
         os.chdir(tmptestsdir)