diff -Nru click-0.4.45.1+16.10.20160916/bin/click click-0.4.46+16.10.20170607.3/bin/click --- click-0.4.45.1+16.10.20160916/bin/click 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/bin/click 2017-06-08 01:24:46.000000000 +0000 @@ -34,7 +34,7 @@ # There is an unfortunate name clash with # https://pypi.python.org/pypi/click; try to detect this and take evasive # action. -import click +import click_package as click if not getattr(click, "_CLICK_IS_A_PACKAGING_FORMAT_", None): import site wrong_click_mods = [ @@ -52,7 +52,7 @@ sys.exit(1) del sys.path[user_site_index] -from click import commands +from click_package import commands def fix_stdout(): diff -Nru click-0.4.45.1+16.10.20160916/click/arfile.py click-0.4.46+16.10.20170607.3/click/arfile.py --- click-0.4.45.1+16.10.20160916/click/arfile.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/arfile.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,102 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Basic support for writing ar archive files. - -We do things this way so that Click packages can be created with minimal -dependencies (e.g. on non-Ubuntu systems). No read support is needed, since -Click packages are always installed on systems that have dpkg. - -Some method names and general approach come from the tarfile module in -Python's standard library; details of the format come from dpkg. -""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'ArFile', - ] - -import os -import shutil -import time - - -class ArFile: - def __init__(self, name=None, mode="w", fileobj=None): - if mode != "w": - raise ValueError("only mode 'w' is supported") - self.mode = mode - self.real_mode = "wb" - - if fileobj: - if name is None and hasattr(fileobj, "name"): - name = fileobj.name - if hasattr(fileobj, "mode"): - if fileobj.mode != "wb": - raise ValueError("fileobj must be opened with mode='wb'") - self._mode = fileobj.mode - self.opened_fileobj = False - else: - fileobj = open(name, self.real_mode) - self.opened_fileobj = True - self.name = name - self.fileobj = fileobj - self.closed = False - - def close(self): - if self.opened_fileobj: - self.fileobj.close() - self.closed = True - - def _check(self): - if self.closed: - raise IOError("ArFile %s is closed" % self.name) - - def __enter__(self): - self._check() - return self - - def __exit__(self, *args): - self.close() - - def add_magic(self): - self.fileobj.write(b"!\n") - - def add_header(self, name, size): - if len(name) > 15: - raise ValueError("ar member name '%s' length too long" % name) - if size > 9999999999: - raise ValueError("ar member size %d too large" % size) - header = ("%-16s%-12u0 0 100644 %-10d`\n" % ( - name, int(time.time()), size)).encode() - assert len(header) == 60 # sizeof(struct ar_hdr) - self.fileobj.write(header) - - def add_data(self, name, data): - size = len(data) - self.add_header(name, size) - self.fileobj.write(data) - if size & 1: - self.fileobj.write(b"\n") # padding - - def add_file(self, name, path): - with open(path, "rb") as fobj: - size = os.fstat(fobj.fileno()).st_size - self.add_header(name, size) - shutil.copyfileobj(fobj, self.fileobj) - if size & 1: - self.fileobj.write(b"\n") # padding diff -Nru click-0.4.45.1+16.10.20160916/click/build.py click-0.4.46+16.10.20170607.3/click/build.py --- click-0.4.45.1+16.10.20160916/click/build.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/build.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,341 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Building Click packages.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'ClickBuildError', - 'ClickBuilder', - 'ClickSourceBuilder', - ] - - -import contextlib -import hashlib -import io -import json -import os -import re -import shutil -import subprocess -import sys -import tarfile -import tempfile -from textwrap import dedent - -try: - import apt_pkg - apt_pkg.init_system() -except ImportError: - # "click build" is required to work with only the Python standard library. - pass - -from click import osextras -from click.arfile import ArFile -from click.preinst import static_preinst -from click.versions import spec_version - -from click.framework import ( - validate_framework, - ClickFrameworkInvalid, -) - - -@contextlib.contextmanager -def make_temp_dir(): - temp_dir = tempfile.mkdtemp(prefix="click") - try: - os.chmod(temp_dir, 0o755) - yield temp_dir - finally: - shutil.rmtree(temp_dir) - - -class FakerootTarFile(tarfile.TarFile): - """A version of TarFile which pretends all files are owned by root:root.""" - - def gettarinfo(self, *args, **kwargs): - tarinfo = super(FakerootTarFile, self).gettarinfo(*args, **kwargs) - tarinfo.uid = tarinfo.gid = 0 - tarinfo.uname = tarinfo.gname = "root" - return tarinfo - - -class ClickBuildError(Exception): - pass - - -class ClickBuilderBase: - def __init__(self): - self.file_map = {} - # From @Dpkg::Source::Package::tar_ignore_default_pattern. - # (more in ClickSourceBuilder) - self._ignore_patterns = [ - "*.click", - ".*.sw?", - "*~", - ",,*", - ".[#~]*", - ".arch-ids", - ".arch-inventory", - ".bzr", - ".bzr-builddeb", - ".bzr.backup", - ".bzr.tags", - ".bzrignore", - ".cvsignore", - ".git", - ".gitattributes", - ".gitignore", - ".gitmodules", - ".hg", - ".hgignore", - ".hgsigs", - ".hgtags", - ".shelf", - ".svn", - "CVS", - "DEADJOE", - "RCS", - "_MTN", - "_darcs", - "{arch}", - ] - - def add_ignore_pattern(self, pattern): - self._ignore_patterns.append(pattern) - - def add_file(self, source_path, dest_path): - self.file_map[source_path] = dest_path - - def read_manifest(self, manifest_path): - with io.open(manifest_path, encoding="UTF-8") as manifest: - try: - self.manifest = json.load(manifest) - except Exception as e: - raise ClickBuildError( - "Error reading manifest from %s: %s" % (manifest_path, e)) - keys = sorted(self.manifest) - for key in keys: - if key.startswith("_"): - print( - "Ignoring reserved dynamic key '%s'." % key, - file=sys.stderr) - del self.manifest[key] - - @property - def name(self): - return self.manifest["name"] - - @property - def version(self): - return self.manifest["version"] - - @property - def epochless_version(self): - return re.sub(r"^\d+:", "", self.version) - - @property - def maintainer(self): - return self.manifest["maintainer"] - - @property - def title(self): - return self.manifest["title"] - - @property - def architecture(self): - manifest_arch = self.manifest.get("architecture", "all") - if isinstance(manifest_arch, list): - return "multi" - else: - return manifest_arch - - -class ClickBuilder(ClickBuilderBase): - - def list_files(self, root_path): - for dirpath, _, filenames in os.walk(root_path): - rel_dirpath = os.path.relpath(dirpath, root_path) - if rel_dirpath == ".": - rel_dirpath = "" - for filename in filenames: - yield os.path.join(rel_dirpath, filename) - - def _filter_dot_click(self, tarinfo): - """Filter out attempts to include .click at the top level.""" - if tarinfo.name == './.click' or tarinfo.name.startswith('./.click/'): - return None - return tarinfo - - def _pack(self, temp_dir, control_dir, data_dir, package_path): - data_tar_path = os.path.join(temp_dir, "data.tar.gz") - with contextlib.closing(FakerootTarFile.open( - name=data_tar_path, mode="w:gz", format=tarfile.GNU_FORMAT - )) as data_tar: - data_tar.add(data_dir, arcname="./", filter=self._filter_dot_click) - - control_tar_path = os.path.join(temp_dir, "control.tar.gz") - control_tar = tarfile.open( - name=control_tar_path, mode="w:gz", format=tarfile.GNU_FORMAT) - control_tar.add(control_dir, arcname="./") - control_tar.close() - - with ArFile(name=package_path, mode="w") as package: - package.add_magic() - package.add_data("debian-binary", b"2.0\n") - package.add_data( - "_click-binary", ("%s\n" % spec_version).encode("UTF-8")) - package.add_file("control.tar.gz", control_tar_path) - package.add_file("data.tar.gz", data_tar_path) - - def _validate_framework(self, framework_string): - """Apply policy checks to framework declarations.""" - try: - validate_framework( - framework_string, ignore_missing_frameworks=True) - except ClickFrameworkInvalid as e: - raise ClickBuildError(str(e)) - - def build(self, dest_dir, manifest_path="manifest.json"): - with make_temp_dir() as temp_dir: - # Prepare data area. - root_path = os.path.join(temp_dir, "data") - - for source_path, dest_path in self.file_map.items(): - if dest_path.startswith("/"): - dest_path = dest_path[1:] - real_dest_path = os.path.join(root_path, dest_path) - shutil.copytree( - source_path, real_dest_path, symlinks=True, - ignore=shutil.ignore_patterns(*self._ignore_patterns)) - - # Prepare control area. - control_dir = os.path.join(temp_dir, "DEBIAN") - osextras.ensuredir(control_dir) - - if os.path.isabs(manifest_path): - full_manifest_path = manifest_path - else: - full_manifest_path = os.path.join(root_path, manifest_path) - self.read_manifest(full_manifest_path) - if "framework" in self.manifest: - self._validate_framework(self.manifest["framework"]) - - du_output = subprocess.check_output( - ["du", "-k", "-s", "--apparent-size", "."], - cwd=temp_dir, universal_newlines=True).rstrip("\n") - match = re.match(r"^(\d+)\s+\.$", du_output) - if not match: - raise Exception("du gave unexpected output '%s'" % du_output) - installed_size = match.group(1) - self.manifest["installed-size"] = installed_size - control_path = os.path.join(control_dir, "control") - osextras.ensuredir(os.path.dirname(control_path)) - with io.open(control_path, "w", encoding="UTF-8") as control: - print(dedent("""\ - Package: %s - Version: %s - Click-Version: %s - Architecture: %s - Maintainer: %s - Installed-Size: %s - Description: %s""" % ( - self.name, self.version, spec_version, self.architecture, - self.maintainer, installed_size, self.title)), - file=control) - - # Control file names must not contain a dot, hence "manifest" - # rather than "manifest.json" in the control area. - real_manifest_path = os.path.join(control_dir, "manifest") - with io.open( - real_manifest_path, "w", encoding="UTF-8") as manifest: - print( - json.dumps( - self.manifest, ensure_ascii=False, sort_keys=True, - indent=4, separators=(",", ": ")), - file=manifest) - os.unlink(full_manifest_path) - os.chmod(real_manifest_path, 0o644) - - md5sums_path = os.path.join(control_dir, "md5sums") - with open(md5sums_path, "w") as md5sums: - for path in sorted(self.list_files(root_path)): - md5 = hashlib.md5() - p = os.path.join(root_path, path) - if not os.path.exists(p): - continue - with open(p, "rb") as f: - while True: - buf = f.read(16384) - if not buf: - break - md5.update(buf) - print("%s %s" % (md5.hexdigest(), path), file=md5sums) - - preinst_path = os.path.join(control_dir, "preinst") - with open(preinst_path, "w") as preinst: - preinst.write(static_preinst) - - # Pack everything up. - package_name = "%s_%s_%s.click" % ( - self.name, self.epochless_version, self.architecture) - package_path = os.path.join(dest_dir, package_name) - self._pack(temp_dir, control_dir, root_path, package_path) - return package_path - - -class ClickSourceBuilder(ClickBuilderBase): - - def __init__(self): - super(ClickSourceBuilder, self).__init__() - # From @Dpkg::Source::Package::tar_ignore_default_pattern. - # (more in ClickBuilderBase) - self._ignore_patterns += [ - "*.a", - ".be", - ".deps", - "*.la", - "*.o", - "*.so", - ] - - def build(self, dest_dir, manifest_path=None): - with make_temp_dir() as temp_dir: - root_path = os.path.join(temp_dir, "source") - for source_path, dest_path in self.file_map.items(): - if dest_path.startswith("/"): - dest_path = dest_path[1:] - real_dest_path = os.path.join(root_path, dest_path) - shutil.copytree( - source_path, real_dest_path, symlinks=True, - ignore=shutil.ignore_patterns(*self._ignore_patterns)) - - real_manifest_path = os.path.join(root_path, "manifest.json") - if manifest_path is not None: - shutil.copy2(manifest_path, real_manifest_path) - os.chmod(real_manifest_path, 0o644) - self.read_manifest(real_manifest_path) - - package_name = "%s_%s.tar.gz" % (self.name, self.epochless_version) - package_path = os.path.join(dest_dir, package_name) - with contextlib.closing(FakerootTarFile.open( - name=package_path, mode="w:gz", format=tarfile.GNU_FORMAT - )) as tar: - tar.add(root_path, arcname="./") - return package_path diff -Nru click-0.4.45.1+16.10.20160916/click/chroot.py click-0.4.46+16.10.20170607.3/click/chroot.py --- click-0.4.45.1+16.10.20160916/click/chroot.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/chroot.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,712 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Authors: Colin Watson , -# Brian Murray -# Michael Vogt -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Chroot management for building Click packages.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - "ClickChroot", - "ClickChrootException", - "ClickChrootAlreadyExistsException", - "ClickChrootDoesNotExistException", - ] - -try: - from urllib.error import URLError - from urllib.request import urlopen -except ImportError: - from urllib2 import URLError, urlopen -import os -import pwd -import re -import shutil -import stat -import subprocess -import sys -from textwrap import dedent -from xml.etree import ElementTree - - -framework_base = { - "ubuntu-sdk-13.10": "ubuntu-sdk-13.10", - # 14.04 - "ubuntu-sdk-14.04-html": "ubuntu-sdk-14.04", - "ubuntu-sdk-14.04-papi": "ubuntu-sdk-14.04", - "ubuntu-sdk-14.04-qml": "ubuntu-sdk-14.04", - # 14.10 - "ubuntu-sdk-14.10-html": "ubuntu-sdk-14.10", - "ubuntu-sdk-14.10-papi": "ubuntu-sdk-14.10", - "ubuntu-sdk-14.10-qml": "ubuntu-sdk-14.10", - # 15.04 - "ubuntu-sdk-15.04-html": "ubuntu-sdk-15.04", - "ubuntu-sdk-15.04-papi": "ubuntu-sdk-15.04", - "ubuntu-sdk-15.04-qml": "ubuntu-sdk-15.04", - # 15.10 - "ubuntu-sdk-15.10-html-dev1": "ubuntu-sdk-15.10-dev1", - "ubuntu-sdk-15.10-papi-dev1": "ubuntu-sdk-15.10-dev1", - "ubuntu-sdk-15.10-qml-dev1": "ubuntu-sdk-15.10-dev1", - } - - -framework_series = { - "ubuntu-sdk-13.10": "saucy", - "ubuntu-sdk-14.04": "trusty", - "ubuntu-sdk-14.10": "utopic", - "ubuntu-sdk-15.04": "vivid", - "ubuntu-sdk-15.10": "wily", - } - - -# Please keep the lists of package names sorted. -extra_packages = { - "ubuntu-sdk-13.10": [ - "libqt5opengl5-dev:{TARGET}", - "libqt5svg5-dev:{TARGET}", - "libqt5v8-5-dev:{TARGET}", - "libqt5webkit5-dev:{TARGET}", - "libqt5xmlpatterns5-dev:{TARGET}", - "qmlscene:{TARGET}", - "qt3d5-dev:{TARGET}", - "qt5-default:{TARGET}", - "qt5-qmake:{TARGET}", - "qtbase5-dev:{TARGET}", - "qtdeclarative5-dev:{TARGET}", - "qtmultimedia5-dev:{TARGET}", - "qtquick1-5-dev:{TARGET}", - "qtscript5-dev:{TARGET}", - "qtsensors5-dev:{TARGET}", - "qttools5-dev:{TARGET}", - "ubuntu-ui-toolkit-doc", - ], - "ubuntu-sdk-14.04": [ - "cmake", - "google-mock:{TARGET}", - "intltool", - "libboost1.54-dev:{TARGET}", - "libjsoncpp-dev:{TARGET}", - "libprocess-cpp-dev:{TARGET}", - "libproperties-cpp-dev:{TARGET}", - "libqt5svg5-dev:{TARGET}", - "libqt5webkit5-dev:{TARGET}", - "libqt5xmlpatterns5-dev:{TARGET}", - "libunity-scopes-dev:{TARGET}", - # bug #1316930, needed for autopilot - "python3", - "qmlscene:{TARGET}", - "qt3d5-dev:{TARGET}", - "qt5-default:{TARGET}", - "qtbase5-dev:{TARGET}", - "qtdeclarative5-dev:{TARGET}", - "qtdeclarative5-dev-tools", - "qtlocation5-dev:{TARGET}", - "qtmultimedia5-dev:{TARGET}", - "qtscript5-dev:{TARGET}", - "qtsensors5-dev:{TARGET}", - "qttools5-dev:{TARGET}", - "qttools5-dev-tools:{TARGET}", - "ubuntu-ui-toolkit-doc", - ], - "ubuntu-sdk-14.10": [ - "cmake", - "cmake-extras", - "google-mock:{TARGET}", - "intltool", - "libboost1.55-dev:{TARGET}", - "libcontent-hub-dev:{TARGET}", - "libjsoncpp-dev:{TARGET}", - "libnet-cpp-dev:{TARGET}", - "libprocess-cpp-dev:{TARGET}", - "libproperties-cpp-dev:{TARGET}", - "libqt5keychain0:{TARGET}", - "libqt5sensors5-dev:{TARGET}", - "libqt5svg5-dev:{TARGET}", - "libqt5webkit5-dev:{TARGET}", - "libqt5xmlpatterns5-dev:{TARGET}", - "libunity-scopes-dev:{TARGET}", - # bug #1316930, needed for autopilot - "python3", - "qml-module-qt-labs-settings:{TARGET}", - "qml-module-qtmultimedia:{TARGET}", - "qml-module-qtquick-layouts:{TARGET}", - "qml-module-qtsensors:{TARGET}", - "qml-module-qtwebkit:{TARGET}", - "qmlscene:{TARGET}", - "qt3d5-dev:{TARGET}", - "qt5-default:{TARGET}", - "qtdeclarative5-accounts-plugin:{TARGET}", - "qtdeclarative5-dev-tools", - "qtdeclarative5-folderlistmodel-plugin:{TARGET}", - "qtdeclarative5-localstorage-plugin:{TARGET}", - "qtdeclarative5-online-accounts-client0.1:{TARGET}", - "qtdeclarative5-particles-plugin:{TARGET}", - "qtdeclarative5-poppler1.0:{TARGET}", - "qtdeclarative5-qtlocation-plugin:{TARGET}", - "qtdeclarative5-qtorganizer-plugin:{TARGET}", - "qtdeclarative5-qtpositioning-plugin:{TARGET}", - "qtdeclarative5-u1db1.0:{TARGET}", - "qtdeclarative5-ubuntu-content0.1:{TARGET}", - "qtdeclarative5-ubuntu-download-manager0.1:{TARGET}", - "qtdeclarative5-ubuntu-mediascanner0.1:{TARGET}", - "qtdeclarative5-ubuntu-syncmonitor0.1:{TARGET}", - "qtdeclarative5-ubuntu-telephony-phonenumber0.1:{TARGET}", - "qtdeclarative5-ubuntu-ui-toolkit-plugin:{TARGET}", - "qtdeclarative5-usermetrics0.1:{TARGET}", - "qtdeclarative5-xmllistmodel-plugin:{TARGET}", - "qtlocation5-dev:{TARGET}", - "qtmultimedia5-dev:{TARGET}", - "qtscript5-dev:{TARGET}", - "qttools5-dev:{TARGET}", - "qttools5-dev-tools:{TARGET}", - "ubuntu-html5-theme:{TARGET}", - "ubuntu-ui-toolkit-doc", - ], - "ubuntu-sdk-15.04": [ - # the sdk libs - "ubuntu-sdk-libs:{TARGET}", - "ubuntu-sdk-libs-dev:{TARGET}", - # the native build tools - "ubuntu-sdk-libs-tools", - # FIXME: see - # http://pad.lv/~mvo/oxide/crossbuild-friendly/+merge/234093 - # we help the apt resolver here until the - # oxideqt-codecs/oxidec-codecs-extras is sorted - "oxideqt-codecs-extra", - ], - "ubuntu-sdk-15.10-dev1": [ - # the sdk libs - "ubuntu-sdk-libs:{TARGET}", - "ubuntu-sdk-libs-dev:{TARGET}", - # the native build tools - "ubuntu-sdk-libs-tools", - # FIXME: see - # http://pad.lv/~mvo/oxide/crossbuild-friendly/+merge/234093 - # we help the apt resolver here until the - # oxideqt-codecs/oxidec-codecs-extras is sorted - "oxideqt-codecs-extra", - ], - } - - -primary_arches = ["amd64", "i386"] - - -non_meta_re = re.compile(r'^[a-zA-Z0-9+,./:=@_-]+$') - - -GEOIP_SERVER = "http://geoip.ubuntu.com/lookup" - -overlay_ppa = "ci-train-ppa-service/stable-phone-overlay" - - -def get_geoip_country_code_prefix(): - click_no_local_mirror = os.environ.get('CLICK_NO_LOCAL_MIRROR', 'auto') - if click_no_local_mirror == '1': - return "" - try: - with urlopen(GEOIP_SERVER) as f: - xml_data = f.read() - et = ElementTree.fromstring(xml_data) - cc = et.find("CountryCode") - if not cc: - return "" - return cc.text.lower()+"." - except (ElementTree.ParseError, URLError): - pass - return "" - - -def generate_sources(series, native_arch, target_arch, - archive_mirror, ports_mirror, components): - """Generate a list of strings for apts sources.list. - Arguments: - series -- the distro series (e.g. vivid) - native_arch -- the native architecture (e.g. amd64) - target_arch -- the target architecture (e.g. armhf) - archive_mirror -- main mirror, e.g. http://archive.ubuntu.com/ubuntu - ports_mirror -- ports mirror, e.g. http://ports.ubuntu.com/ubuntu-ports - components -- the components as string, e.g. "main restricted universe" - """ - pockets = ['%s' % series] - for pocket in ['updates', 'security']: - pockets.append('%s-%s' % (series, pocket)) - sources = [] - # write binary lines - arches = [target_arch] - if native_arch != target_arch: - arches.append(native_arch) - for arch in arches: - if arch not in primary_arches: - mirror = ports_mirror - else: - mirror = archive_mirror - for pocket in pockets: - sources.append("deb [arch=%s] %s %s %s" % - (arch, mirror, pocket, components)) - # write source lines - for pocket in pockets: - sources.append("deb-src %s %s %s" % - (archive_mirror, pocket, components)) - return sources - - -def shell_escape(command): - escaped = [] - for arg in command: - if non_meta_re.match(arg): - escaped.append(arg) - else: - escaped.append("'%s'" % arg.replace("'", "'\\''")) - return " ".join(escaped) - - -def strip_dev_series_from_framework(framework): - """Remove trailing -dev[0-9]+ from a framework name""" - return re.sub(r'^(.*)-dev[0-9]+$', r'\1', framework) - - -class ClickChrootException(Exception): - """A generic issue with the chroot""" - pass - - -class ClickChrootAlreadyExistsException(ClickChrootException): - """The chroot already exists""" - pass - - -class ClickChrootDoesNotExistException(ClickChrootException): - """A chroot with that name does not exist yet""" - pass - - -class ClickChroot: - - DAEMON_POLICY = dedent("""\ - #!/bin/sh - while true; do - case "$1" in - -*) shift ;; - makedev) exit 0;; - x11-common) exit 0;; - *) exit 101;; - esac - done - """) - - def __init__(self, target_arch, framework, name=None, series=None, - session=None, chroots_dir=None): - self.target_arch = target_arch - self.framework = strip_dev_series_from_framework(framework) - if name is None: - name = "click" - self.name = name - if series is None: - series = framework_series[self.framework_base] - self.series = series - self.session = session - system_arch = subprocess.check_output( - ["dpkg", "--print-architecture"], - universal_newlines=True).strip() - self.native_arch = self._get_native_arch(system_arch, self.target_arch) - if chroots_dir is None: - chroots_dir = "/var/lib/schroot/chroots" - self.chroots_dir = chroots_dir - - if "SUDO_USER" in os.environ: - self.user = os.environ["SUDO_USER"] - elif "PKEXEC_UID" in os.environ: - self.user = pwd.getpwuid(int(os.environ["PKEXEC_UID"])).pw_name - else: - self.user = pwd.getpwuid(os.getuid()).pw_name - self.dpkg_architecture = self._dpkg_architecture() - - def _get_native_arch(self, system_arch, target_arch): - """Determine the proper native architecture for a chroot. - - Some combinations of system and target architecture do not require - cross-building, so in these cases we just create a chroot suitable - for native building. - """ - if (system_arch, target_arch) in ( - ("amd64", "i386"), - # This will only work if the system is running a 64-bit - # kernel; but there's no alternative since no i386-to-amd64 - # cross-compiler is available in the Ubuntu archive. - ("i386", "amd64"), - ): - return target_arch - else: - return system_arch - - def _dpkg_architecture(self): - dpkg_architecture = {} - command = ["dpkg-architecture", "-a%s" % self.target_arch] - env = dict(os.environ) - env["CC"] = "true" - # Force dpkg-architecture to recalculate everything rather than - # picking up values from the environment, which will be present when - # running the test suite under dpkg-buildpackage. - for key in list(env): - if key.startswith("DEB_BUILD_") or key.startswith("DEB_HOST_"): - del env[key] - lines = subprocess.check_output( - command, env=env, universal_newlines=True).splitlines() - for line in lines: - try: - key, value = line.split("=", 1) - except ValueError: - continue - dpkg_architecture[key] = value - if self.native_arch == self.target_arch: - # We may have overridden the native architecture (see - # _get_native_arch above), so we need to force DEB_BUILD_* to - # match. - for key in list(dpkg_architecture): - if key.startswith("DEB_HOST_"): - new_key = "DEB_BUILD_" + key[len("DEB_HOST_"):] - dpkg_architecture[new_key] = dpkg_architecture[key] - return dpkg_architecture - - def _generate_chroot_config(self, mount): - admin_user = "root" - users = [] - for key in ("users", "root-users", "source-root-users"): - users.append("%s=%s,%s" % (key, admin_user, self.user)) - with open(self.chroot_config, "w") as target: - target.write(dedent("""\ - [{full_name}] - description=Build chroot for click packages on {target_arch} - {users} - type=directory - profile=default - setup.fstab=click/fstab - # Not protocols or services see - # debian bug 557730 - setup.nssdatabases=sbuild/nssdatabases - union-type=overlayfs - directory={mount} - """).format(full_name=self.full_name, - target_arch=self.target_arch, - users="\n".join(users), - mount=mount)) - - def _generate_daemon_policy(self, mount): - daemon_policy = "%s/usr/sbin/policy-rc.d" % mount - with open(daemon_policy, "w") as policy: - policy.write(self.DAEMON_POLICY) - return daemon_policy - - def _generate_apt_proxy_file(self, mount, proxy): - apt_conf_d = os.path.join(mount, "etc", "apt", "apt.conf.d") - if not os.path.exists(apt_conf_d): - os.makedirs(apt_conf_d) - apt_conf_f = os.path.join(apt_conf_d, "99-click-chroot-proxy") - if proxy: - with open(apt_conf_f, "w") as f: - f.write(dedent("""\ - // proxy settings copied by click chroot - Acquire { - HTTP { - Proxy "%s"; - }; - }; - """) % proxy) - return apt_conf_f - - def _generate_finish_script(self, mount, build_pkgs): - finish_script = "%s/finish.sh" % mount - with open(finish_script, 'w') as finish: - finish.write(dedent("""\ - #!/bin/bash - set -e - # Configure target arch - dpkg --add-architecture {target_arch} - # Reload package lists - apt-get update || true - # Pull down signature requirements - apt-get -y --force-yes install gnupg ubuntu-keyring - """).format(target_arch=self.target_arch)) - if self.series == "vivid": - finish.write(dedent("""\ - apt-get -y --force-yes install software-properties-common - add-apt-repository -y ppa:{ppa} - echo "Package: *" \ - > /etc/apt/preferences.d/stable-phone-overlay.pref - echo \ - "Pin: release o=LP-PPA-{pin_ppa}" \ - >> /etc/apt/preferences.d/stable-phone-overlay.pref - echo "Pin-Priority: 1001" \ - >> /etc/apt/preferences.d/stable-phone-overlay.pref - """).format(ppa=overlay_ppa, - pin_ppa=re.sub('/', '-', overlay_ppa))) - finish.write(dedent("""\ - # Reload package lists - apt-get update || true - # Disable debconf questions - # so that automated builds won't prompt - echo set debconf/frontend Noninteractive | debconf-communicate - echo set debconf/priority critical | debconf-communicate - apt-get -y --force-yes dist-upgrade - # Install basic build tool set to match buildd - apt-get -y --force-yes install {build_pkgs} - # Set up expected /dev entries - if [ ! -r /dev/stdin ]; then - ln -s /proc/self/fd/0 /dev/stdin - fi - if [ ! -r /dev/stdout ]; then - ln -s /proc/self/fd/1 /dev/stdout - fi - if [ ! -r /dev/stderr ]; then - ln -s /proc/self/fd/2 /dev/stderr - fi - # Clean up - rm /finish.sh - apt-get clean - """).format(build_pkgs=' '.join(build_pkgs))) - return finish_script - - def _debootstrap(self, components, mount, archive_mirror, ports_mirror): - if self.native_arch in primary_arches: - mirror = archive_mirror - else: - mirror = ports_mirror - subprocess.check_call([ - "debootstrap", - "--arch", self.native_arch, - "--variant=buildd", - "--components=%s" % ','.join(components), - self.series, - mount, - mirror, - ]) - - @property - def framework_base(self): - if self.framework in framework_base: - return framework_base[self.framework] - else: - return self.framework - - @property - def full_name(self): - return "%s-%s-%s" % (self.name, self.framework_base, self.target_arch) - - @property - def full_session_name(self): - return "%s-%s" % (self.full_name, self.session) - - @property - def chroot_config(self): - return "/etc/schroot/chroot.d/%s" % self.full_name - - def exists(self): - command = ["schroot", "-c", self.full_name, "-i"] - with open("/dev/null", "w") as devnull: - return subprocess.call( - command, stdout=devnull, stderr=devnull) == 0 - - def _make_executable(self, path): - mode = stat.S_IMODE(os.stat(path).st_mode) - os.chmod(path, mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) - - def _make_cross_package(self, prefix): - if self.native_arch == self.target_arch: - return prefix - else: - target_tuple = self.dpkg_architecture["DEB_HOST_GNU_TYPE"] - return "%s-%s" % (prefix, target_tuple) - - def create(self, keep_broken_chroot_on_fail=False): - if self.exists(): - raise ClickChrootAlreadyExistsException( - "Chroot %s already exists" % self.full_name) - components = ["main", "restricted", "universe", "multiverse"] - mount = "%s/%s" % (self.chroots_dir, self.full_name) - proxy = None - if not proxy and "http_proxy" in os.environ: - proxy = os.environ["http_proxy"] - if not proxy: - proxy = subprocess.check_output( - 'unset x; eval "$(apt-config shell x Acquire::HTTP::Proxy)"; \ - echo "$x"', - shell=True, universal_newlines=True).strip() - build_pkgs = [ - # sort alphabetically - "apt-utils", - "build-essential", - "cmake", - "dpkg-cross", - "fakeroot", - "libc-dev:%s" % self.target_arch, - # build pkg names dynamically - self._make_cross_package("g++"), - self._make_cross_package("pkg-config"), - ] - for package in extra_packages.get(self.framework_base, []): - package = package.format(TARGET=self.target_arch) - build_pkgs.append(package) - os.makedirs(mount) - - country_code = get_geoip_country_code_prefix() - archive_mirror = "http://%sarchive.ubuntu.com/ubuntu" % country_code - ports_mirror = "http://%sports.ubuntu.com/ubuntu-ports" % country_code - # this doesn't work because we are running this under sudo - if 'DEBOOTSTRAP_MIRROR' in os.environ: - archive_mirror = os.environ['DEBOOTSTRAP_MIRROR'] - self._debootstrap(components, mount, archive_mirror, ports_mirror) - sources = generate_sources(self.series, self.native_arch, - self.target_arch, - archive_mirror, ports_mirror, - ' '.join(components)) - with open("%s/etc/apt/sources.list" % mount, "w") as sources_list: - for line in sources: - print(line, file=sources_list) - shutil.copy2("/etc/localtime", "%s/etc/" % mount) - shutil.copy2("/etc/timezone", "%s/etc/" % mount) - self._generate_chroot_config(mount) - daemon_policy = self._generate_daemon_policy(mount) - self._make_executable(daemon_policy) - initctl = "%s/sbin/initctl" % mount - if os.path.exists(initctl): - os.remove(initctl) - os.symlink("%s/bin/true" % mount, initctl) - self._generate_apt_proxy_file(mount, proxy) - finish_script = self._generate_finish_script(mount, build_pkgs) - self._make_executable(finish_script) - command = ["/finish.sh"] - ret_code = self.maint(*command) - if ret_code != 0 and not keep_broken_chroot_on_fail: - # cleanup on failure - self.destroy() - raise ClickChrootException( - "Failed to create chroot '{}' (exit status {})".format( - self.full_name, ret_code)) - return ret_code - - def run(self, *args): - if not self.exists(): - raise ClickChrootDoesNotExistException( - "Chroot %s does not exist" % self.full_name) - command = ["schroot", "-c"] - if self.session: - command.extend([self.full_session_name, "--run-session"]) - else: - command.append(self.full_name) - command.extend(["--", "env"]) - for key, value in self.dpkg_architecture.items(): - command.append("%s=%s" % (key, value)) - command.extend(args) - ret = subprocess.call(command) - if ret == 0: - return 0 - else: - print("Command returned %d: %s" % (ret, shell_escape(command)), - file=sys.stderr) - return ret - - def maint(self, *args): - command = ["schroot", "-u", "root", "-c"] - if self.session: - command.extend([self.full_session_name, "--run-session"]) - else: - command.append("source:%s" % self.full_name) - command.append("--") - command.extend(args) - ret = subprocess.call(command) - if ret == 0: - return 0 - else: - print("Command returned %d: %s" % (ret, shell_escape(command)), - file=sys.stderr) - return ret - - def install(self, *pkgs): - if not self.exists(): - raise ClickChrootDoesNotExistException( - "Chroot %s does not exist" % self.full_name) - ret = self.update() - if ret != 0: - return ret - command = ["apt-get", "install", "--yes"] - command.extend(pkgs) - ret = self.maint(*command) - if ret != 0: - return ret - return self.clean() - - def clean(self): - command = ["apt-get", "clean"] - return self.maint(*command) - - def update(self): - command = ["apt-get", "update", "--yes"] - return self.maint(*command) - - def upgrade(self): - if not self.exists(): - raise ClickChrootDoesNotExistException( - "Chroot %s does not exist" % self.full_name) - ret = self.update() - if ret != 0: - return ret - command = ["apt-get", "dist-upgrade", "--yes"] - ret = self.maint(*command) - if ret != 0: - return ret - return self.clean() - - def destroy(self): - # remove config - if os.path.exists(self.chroot_config): - os.remove(self.chroot_config) - # find all schroot mount points, this is actually quite complicated - mount_dir = os.path.abspath( - os.path.join(self.chroots_dir, "..", "mount")) - needle = os.path.join(mount_dir, self.full_name) - all_mounts = [] - with open("/proc/mounts") as f: - for line in f.readlines(): - mp = line.split()[1] - if mp.startswith(needle): - all_mounts.append(mp) - # reverse order is important in case of submounts - for mp in sorted(all_mounts, key=len, reverse=True): - subprocess.call(["umount", mp]) - # now remove the rest - chroot_dir = "%s/%s" % (self.chroots_dir, self.full_name) - if os.path.exists(chroot_dir): - shutil.rmtree(chroot_dir) - return 0 - - def begin_session(self): - if not self.exists(): - raise ClickChrootDoesNotExistException( - "Chroot %s does not exist" % self.full_name) - command = ["schroot", "-c", self.full_name, "--begin-session", - "--session-name", self.full_session_name] - subprocess.check_call(command) - return 0 - - def end_session(self): - if not self.exists(): - raise ClickChrootDoesNotExistException( - "Chroot %s does not exist" % self.full_name) - command = ["schroot", "-c", self.full_session_name, "--end-session"] - subprocess.check_call(command) - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/commands/build.py click-0.4.46+16.10.20170607.3/click/commands/build.py --- click-0.4.45.1+16.10.20160916/click/commands/build.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/build.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,77 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Build a Click package.""" - -from __future__ import print_function - -from optparse import OptionParser -import os -import sys -import subprocess - -from gi.repository import Click -from click.build import ClickBuildError, ClickBuilder - - -def run(argv): - parser = OptionParser("%prog build [options] DIRECTORY") - parser.add_option( - "-m", "--manifest", metavar="PATH", default="manifest.json", - help="read package manifest from PATH (default: manifest.json)") - parser.add_option( - "--no-validate", action="store_false", default=True, dest="validate", - help="Don't run click-reviewers-tools check on resulting .click") - parser.add_option( - "-I", "--ignore", metavar="file-pattern", action='append', default=[], - help="Ignore the given pattern when building the package") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need directory") - directory = args[0] - if not os.path.isdir(directory): - parser.error('directory "%s" does not exist' % directory) - if os.path.isdir(os.path.join(directory, options.manifest)): - options.manifest = os.path.join(options.manifest, "manifest.json") - if not os.path.exists(os.path.join(directory, options.manifest)): - parser.error( - 'directory "%s" does not contain manifest file "%s"' % - (directory, options.manifest)) - builder = ClickBuilder() - builder.add_file(directory, "./") - for ignore in options.ignore: - builder.add_ignore_pattern(ignore) - try: - path = builder.build(".", manifest_path=options.manifest) - except ClickBuildError as e: - print(e, file=sys.stderr) - return 1 - if options.validate and Click.find_on_path('click-review'): - print("Now executing: click-review %s" % path) - try: - subprocess.check_call(['click-review', path]) - except subprocess.CalledProcessError: - # qtcreator-plugin-ubuntu relies on return code 0 - # to establish if a .click package has been built - # at all. - # - # If we want to distinguish between - # - click build failed - # - click build succeeded, but validation failed - # both tools will have to learn this at the same - # time. - pass - print("Successfully built package in '%s'." % path) - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/commands/buildsource.py click-0.4.46+16.10.20170607.3/click/commands/buildsource.py --- click-0.4.45.1+16.10.20160916/click/commands/buildsource.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/buildsource.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Build a Click source package.""" - -from __future__ import print_function - -from optparse import OptionParser -import os -import sys - -from click.build import ClickBuildError, ClickSourceBuilder - - -def run(argv): - parser = OptionParser("%prog buildsource [options] DIRECTORY") - parser.add_option( - "-m", "--manifest", metavar="PATH", - help="read package manifest from PATH") - parser.add_option( - "-I", "--ignore", metavar="file-pattern", action='append', - default=[], - help="Ignore the given pattern when building the package") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need directory") - directory = args[0] - if not os.path.isdir(directory): - parser.error('directory "%s" does not exist' % directory) - if not options.manifest: - options.manifest = os.path.join(directory, "manifest.json") - if os.path.isdir(os.path.join(directory, options.manifest)): - options.manifest = os.path.join(options.manifest, "manifest.json") - if not os.path.exists(os.path.join(directory, options.manifest)): - parser.error( - 'directory "%s" does not contain manifest file "%s"' % - (directory, options.manifest)) - builder = ClickSourceBuilder() - builder.add_file(directory, "./") - for ignore in options.ignore: - builder.add_ignore_pattern(ignore) - try: - path = builder.build(".", manifest_path=options.manifest) - except ClickBuildError as e: - print(e, file=sys.stderr) - return 1 - print("Successfully built source package in '%s'." % path) - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/commands/chroot.py click-0.4.46+16.10.20170607.3/click/commands/chroot.py --- click-0.4.45.1+16.10.20160916/click/commands/chroot.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/chroot.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,266 +0,0 @@ -#! /usr/bin/python3 - -# Copyright (C) 2013 Canonical Ltd. -# Author: Brian Murray - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Use and manage a Click chroot.""" - -from __future__ import print_function - -from argparse import ArgumentParser, REMAINDER -from contextlib import contextmanager -import os - -from click.chroot import ( - ClickChroot, - ClickChrootAlreadyExistsException, - ClickChrootDoesNotExistException, -) -from click import osextras - - -def requires_root(parser): - if os.getuid() != 0: - parser.error("must be run as root; try sudo") - - -@contextmanager -def message_on_error(exc, msg): - """ - Context Manager that prints the error message 'msg' on exception 'exc' - """ - try: - yield - except exc: - print(msg) - - -# FIXME: i18n(?) -class ErrorMessages: - EXISTS = """A chroot for that name and architecture already exists. -Please see the man-page how to use it.""" - NOT_EXISTS = """A chroot for that name and architecture does not exist. -Please use 'create' to create it.""" - - -def create(parser, args): - if not osextras.find_on_path("debootstrap"): - parser.error( - "debootstrap not installed and configured; install click-dev and " - "debootstrap") - requires_root(parser) - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, series=args.series) - with message_on_error( - ClickChrootAlreadyExistsException, ErrorMessages.EXISTS): - return chroot.create(args.keep_broken_chroot) - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def install(parser, args): - packages = args.packages - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, - session=args.session) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.install(*packages) - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def destroy(parser, args): - requires_root(parser) - # ask for confirmation? - chroot = ClickChroot(args.architecture, args.framework, name=args.name) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.destroy() - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def execute(parser, args): - program = args.program - if not program: - program = ["/bin/bash"] - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, - session=args.session) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.run(*program) - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def maint(parser, args): - program = args.program - if not program: - program = ["/bin/bash"] - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, - session=args.session) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.maint(*program) - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def upgrade(parser, args): - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, - session=args.session) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.upgrade() - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def begin_session(parser, args): - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, - session=args.session) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.begin_session() - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def end_session(parser, args): - chroot = ClickChroot( - args.architecture, args.framework, name=args.name, - session=args.session) - with message_on_error( - ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): - return chroot.end_session() - # if we reach this point there was a error so return exit_status 1 - return 1 - - -def exists(parser, args): - chroot = ClickChroot(args.architecture, args.framework, name=args.name) - # return shell exit codes 0 on success, 1 on failure - if chroot.exists(): - return 0 - else: - return 1 - - -def run(argv): - parser = ArgumentParser("click chroot") - subparsers = parser.add_subparsers( - description="management subcommands", - help="valid commands") - parser.add_argument( - "-a", "--architecture", required=True, - help="architecture for the chroot") - parser.add_argument( - "-f", "--framework", default="ubuntu-sdk-14.04", - help="framework for the chroot (default: ubuntu-sdk-14.04)") - parser.add_argument( - "-s", "--series", - help="series to use for a newly-created chroot (defaults to a series " - "appropriate for the framework)") - parser.add_argument( - "-n", "--name", default="click", - help=( - "name of the chroot (default: click; the framework and " - "architecture will be appended)")) - create_parser = subparsers.add_parser( - "create", - help="create a chroot of the provided architecture") - create_parser.add_argument( - "-k", "--keep-broken-chroot", default=False, action="store_true", - help="Keep the chroot even if creating it fails (default is to delete " - "it)") - create_parser.set_defaults(func=create) - destroy_parser = subparsers.add_parser( - "destroy", - help="destroy the chroot") - destroy_parser.set_defaults(func=destroy) - upgrade_parser = subparsers.add_parser( - "upgrade", - help="upgrade the chroot") - upgrade_parser.add_argument( - "-n", "--session-name", - dest='session', - help="persistent chroot session name to upgrade") - upgrade_parser.set_defaults(func=upgrade) - install_parser = subparsers.add_parser( - "install", - help="install packages in the chroot") - install_parser.add_argument( - "-n", "--session-name", - dest='session', - help="persistent chroot session name to install packages in") - install_parser.add_argument( - "packages", nargs="+", - help="packages to install") - install_parser.set_defaults(func=install) - execute_parser = subparsers.add_parser( - "run", - help="run a program in the chroot") - execute_parser.add_argument( - "-n", "--session-name", - dest='session', - help="persistent chroot session name to run a program in") - execute_parser.add_argument( - "program", nargs=REMAINDER, - help="program to run with arguments") - execute_parser.set_defaults(func=execute) - maint_parser = subparsers.add_parser( - "maint", - help="run a maintenance command in the chroot") - maint_parser.add_argument( - "-n", "--session-name", - dest='session', - help="persistent chroot session name to run a maintenance command in") - maint_parser.add_argument( - "program", nargs=REMAINDER, - help="program to run with arguments") - maint_parser.set_defaults(func=maint) - begin_parser = subparsers.add_parser( - "begin-session", - help="begin a persistent chroot session") - begin_parser.add_argument( - "session", - help="new session name") - begin_parser.set_defaults(func=begin_session) - end_parser = subparsers.add_parser( - "end-session", - help="end a persistent chroot session") - end_parser.add_argument( - "session", - help="session name to end") - end_parser.set_defaults(func=end_session) - exists_parser = subparsers.add_parser( - "exists", - help="test if the given chroot exists") - exists_parser.set_defaults(func=exists) - args = parser.parse_args(argv) - if not hasattr(args, "func"): - parser.print_help() - return 1 - if (not osextras.find_on_path("schroot") or - not os.path.exists("/etc/schroot/click/fstab")): - parser.error( - "schroot not installed and configured; install click-dev and " - "schroot") - return args.func(parser, args) diff -Nru click-0.4.45.1+16.10.20160916/click/commands/contents.py click-0.4.46+16.10.20170607.3/click/commands/contents.py --- click-0.4.45.1+16.10.20160916/click/commands/contents.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/contents.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Show the file-list contents of a Click package file.""" - -from __future__ import print_function - -from optparse import OptionParser -import subprocess - - -def run(argv): - parser = OptionParser("%prog contents [options] PATH") - _, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need file name") - path = args[0] - subprocess.check_call(["dpkg-deb", "-c", path]) - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/commands/desktophook.py click-0.4.46+16.10.20170607.3/click/commands/desktophook.py --- click-0.4.45.1+16.10.20160916/click/commands/desktophook.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/desktophook.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,179 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Click desktop hook. (Temporary; do not rely on this.)""" - -from __future__ import print_function - -import errno -import io -import json -from optparse import OptionParser -import os - -from gi.repository import Click - -from click import osextras - - -COMMENT = \ - '# Generated by "click desktophook"; changes here will be overwritten.' - - -def desktop_entries(directory, only_ours=False): - for entry in osextras.listdir_force(directory): - if not entry.endswith(".desktop"): - continue - path = os.path.join(directory, entry) - if only_ours: - try: - with io.open(path, encoding="UTF-8") as f: - if COMMENT not in f.read(): - continue - except Exception: - continue - yield entry - - -def split_entry(entry): - entry = entry[:-8] # strip .desktop - return entry.split("_", 2) - - -def older(source_path, target_path): - """Return True iff source_path is older than target_path. - - It's also OK for target_path to be missing. - """ - try: - source_mtime = os.stat(source_path).st_mtime - except OSError as e: - if e.errno == errno.ENOENT: - return False - try: - target_mtime = os.stat(target_path).st_mtime - except OSError as e: - if e.errno == errno.ENOENT: - return True - return source_mtime < target_mtime - - -def read_hooks_for(path, package, app_name): - try: - directory = Click.find_package_directory(path) - manifest_path = os.path.join( - directory, ".click", "info", "%s.manifest" % package) - with io.open(manifest_path, encoding="UTF-8") as manifest: - return json.load(manifest).get("hooks", {}).get(app_name, {}) - except Exception: - return {} - - -def quote_for_desktop_exec(s): - """Quote a string for Exec in a .desktop file. - - The rules are fairly awful. See: - http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html - """ - for c in s: - if c in " \t\n\"'\\><~|&;$*?#()`%": - break - else: - return s - quoted = [] - for c in s: - if c in "\"`$\\": - quoted.append("\\" + c) - elif c == "%": - quoted.append("%%") - else: - quoted.append(c) - escaped = [] - for c in "".join(quoted): - if c == "\\": - escaped.append("\\\\") - else: - escaped.append(c) - return '"%s"' % "".join(escaped) - - -# TODO: This is a very crude .desktop file mangler; we should instead -# implement proper (de)serialisation. -def write_desktop_file(target_path, source_path, profile): - Click.ensuredir(os.path.dirname(target_path)) - with io.open(source_path, encoding="UTF-8") as source, \ - io.open(target_path, "w", encoding="UTF-8") as target: - source_dir = Click.find_package_directory(source_path) - written_comment = False - seen_path = False - for line in source: - if not line.rstrip("\n") or line.startswith("#"): - # Comment - target.write(line) - elif line.startswith("["): - # Group header - target.write(line) - if not written_comment: - print(COMMENT, file=target) - elif "=" not in line: - # Who knows? - target.write(line) - else: - key, value = line.split("=", 1) - key = key.strip() - value = value.strip() - if key == "Exec": - target.write( - "%s=aa-exec-click -p %s -- %s\n" % - (key, quote_for_desktop_exec(profile), value)) - elif key == "Path": - target.write("%s=%s\n" % (key, source_dir)) - seen_path = True - elif key == "Icon": - icon_path = os.path.join(source_dir, value) - if os.path.exists(icon_path): - target.write("%s=%s\n" % (key, icon_path)) - else: - target.write("%s=%s\n" % (key, value)) - else: - target.write("%s=%s\n" % (key, value)) - if not seen_path: - target.write("Path=%s\n" % source_dir) - - -def run(argv): - parser = OptionParser("%prog desktophook [options]") - parser.parse_args(argv) - source_dir = os.path.expanduser("~/.local/share/click/hooks/desktop") - target_dir = os.path.expanduser("~/.local/share/applications") - source_entries = set(desktop_entries(source_dir)) - target_entries = set(desktop_entries(target_dir, only_ours=True)) - - for new_entry in source_entries: - package, app_name, version = split_entry(new_entry) - source_path = os.path.join(source_dir, new_entry) - target_path = os.path.join(target_dir, new_entry) - if older(source_path, target_path): - hooks = read_hooks_for(source_path, package, app_name) - if "apparmor" in hooks: - profile = "%s_%s_%s" % (package, app_name, version) - else: - profile = "unconfined" - write_desktop_file(target_path, source_path, profile) - - for remove_entry in target_entries - source_entries: - os.unlink(os.path.join(target_dir, remove_entry)) - - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/commands/framework.py click-0.4.46+16.10.20170607.3/click/commands/framework.py --- click-0.4.45.1+16.10.20160916/click/commands/framework.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/framework.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,71 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""List available frameworks.""" - -from __future__ import print_function - -from argparse import ArgumentParser - -from gi.repository import Click - - -def list(parser, args): - for framework in Click.Framework.get_frameworks(): - print("%s" % framework.props.name) - return 0 - - -def info(parser, args): - framework = Click.Framework.open(args.framework_name) - for field in sorted(framework.get_fields()): - print("%s: %s" % (field, framework.get_field(field))) - - -def get_field(parser, args): - framework = Click.Framework.open(args.framework_name) - print(framework.get_field(args.field_name)) - - -def run(argv): - parser = ArgumentParser("click framework") - subparsers = parser.add_subparsers() - list_parser = subparsers.add_parser( - "list", - help="list available frameworks") - list_parser.set_defaults(func=list) - info_parser = subparsers.add_parser( - "info", - help="show info about a specific framework") - info_parser.add_argument( - "framework_name", - help="framework name with the information") - info_parser.set_defaults(func=info) - get_field_parser = subparsers.add_parser( - "get-field", - help="get a field from a given framework") - get_field_parser.add_argument( - "framework_name", - help="framework name with the information") - get_field_parser.add_argument( - "field_name", - help="the field name (e.g. base-version)") - get_field_parser.set_defaults(func=get_field) - - args = parser.parse_args(argv) - if not hasattr(args, "func"): - parser.print_help() - return 1 - return args.func(parser, args) diff -Nru click-0.4.45.1+16.10.20160916/click/commands/hook.py click-0.4.46+16.10.20170607.3/click/commands/hook.py --- click-0.4.45.1+16.10.20160916/click/commands/hook.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/hook.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,94 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Install or remove a Click system hook.""" - -from __future__ import print_function - -from optparse import OptionParser -import sys -from textwrap import dedent - -from gi.repository import Click, GLib - - -per_hook_subcommands = { - "install": "install", - "remove": "remove", - } - - -def run(argv): - parser = OptionParser(dedent("""\ - %prog hook [options] SUBCOMMAND [...] - - Subcommands are as follows: - - install HOOK - remove HOOK - run-system - run-user [--user=USER]""")) - parser.add_option( - "--root", metavar="PATH", help="look for additional packages in PATH") - parser.add_option( - "--user", metavar="USER", - help=( - "run user-level hooks for USER (default: current user; only " - "applicable to run-user)")) - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need subcommand (install, remove, run-system, run-user)") - subcommand = args[0] - if subcommand in per_hook_subcommands: - if len(args) < 2: - parser.error("need hook name") - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - name = args[1] - hook = Click.Hook.open(db, name) - getattr(hook, per_hook_subcommands[subcommand])(user_name=None) - elif subcommand == "run-system": - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - try: - Click.run_system_hooks(db) - except GLib.GError as e: - if e.domain == "click_hooks_error-quark": - print(e.message, file=sys.stderr) - return 1 - else: - raise - elif subcommand == "run-user": - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - try: - Click.run_user_hooks(db, user_name=options.user) - except GLib.GError as e: - if e.domain == "click_hooks_error-quark": - print(e.message, file=sys.stderr) - return 1 - else: - raise - else: - parser.error( - "unknown subcommand '%s' (known: install, remove, run-system," - "run-user)" % subcommand) - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/commands/info.py click-0.4.46+16.10.20170607.3/click/commands/info.py --- click-0.4.45.1+16.10.20160916/click/commands/info.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/info.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,88 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Show manifest information for a Click package.""" - -from __future__ import print_function - -from contextlib import closing -import glob -import json -from optparse import OptionParser -import os -import sys - -from gi.repository import Click - -from click.install import DebFile -from click.json_helpers import json_object_to_python - - -def _load_manifest(manifest_file): - manifest = json.load(manifest_file) - keys = list(manifest) - for key in keys: - if key.startswith("_"): - del manifest[key] - return manifest - - -def get_manifest(options, arg): - if "/" not in arg: - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - registry = Click.User.for_user(db, name=options.user) - if registry.has_package_name(arg): - return json_object_to_python(registry.get_manifest(arg)) - - try: - with closing(DebFile(filename=arg)) as package: - with package.control.get_file( - "manifest", encoding="UTF-8") as manifest_file: - return _load_manifest(manifest_file) - except Exception: - pkgdir = Click.find_package_directory(arg) - manifest_path = glob.glob( - os.path.join(pkgdir, ".click", "info", "*.manifest")) - if len(manifest_path) > 1: - raise Exception("Multiple manifest files found in '%s'" % ( - manifest_path)) - with open(manifest_path[0]) as f: - return _load_manifest(f) - - -def run(argv): - parser = OptionParser("%prog info [options] PATH") - parser.add_option( - "--root", metavar="PATH", help="look for additional packages in PATH") - parser.add_option( - "--user", metavar="USER", - help="look up PACKAGE-NAME for USER (if you have permission; " - "default: current user)") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need file name") - try: - manifest = get_manifest(options, args[0]) - except Exception as e: - print(e, file=sys.stderr) - return 1 - json.dump( - manifest, sys.stdout, ensure_ascii=False, sort_keys=True, indent=4, - separators=(",", ": ")) - print() - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/commands/__init__.py click-0.4.46+16.10.20170607.3/click/commands/__init__.py --- click-0.4.45.1+16.10.20160916/click/commands/__init__.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""click commands.""" - -import importlib - - -all_commands = ( - "build", - "buildsource", - "chroot", - "contents", - "desktophook", - "framework", - "hook", - "info", - "install", - "list", - "pkgdir", - "register", - "unregister", - "verify", - ) - - -hidden_commands = ( - "desktophook", - ) - - -def load_command(command): - return importlib.import_module("click.commands.%s" % command) - - -def help_text(): - lines = [] - for command in all_commands: - if command in hidden_commands: - continue - mod = load_command(command) - lines.append(" %-21s %s" % (command, mod.__doc__.splitlines()[0])) - return "\n".join(lines) diff -Nru click-0.4.45.1+16.10.20160916/click/commands/install.py click-0.4.46+16.10.20170607.3/click/commands/install.py --- click-0.4.45.1+16.10.20160916/click/commands/install.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/install.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,70 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Install a Click package (low-level; consider pkcon instead).""" - -from __future__ import print_function - -from optparse import OptionParser -import sys -from textwrap import dedent - -from gi.repository import Click - -from click.install import ClickInstaller, ClickInstallerError - - -def run(argv): - parser = OptionParser(dedent("""\ - %prog install [options] PACKAGE-FILE - - This is a low-level tool; to install a package as an ordinary user - you should generally use "pkcon install-local PACKAGE-FILE" - instead.""")) - parser.add_option( - "--root", metavar="PATH", help="install packages underneath PATH") - parser.add_option( - "--force-missing-framework", action="store_true", default=False, - help="install despite missing system framework") - parser.add_option( - "--user", metavar="USER", help="register package for USER") - parser.add_option( - "--all-users", default=False, action="store_true", - help="register package for all users") - parser.add_option( - "--allow-unauthenticated", default=False, action="store_true", - help="allow installing packages with no signatures") - parser.add_option( - "--verbose", default=False, action="store_true", - help="be more verbose on install") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need package file name") - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - package_path = args[0] - installer = ClickInstaller( - db=db, force_missing_framework=options.force_missing_framework, - allow_unauthenticated=options.allow_unauthenticated) - try: - installer.install( - package_path, user=options.user, all_users=options.all_users, - quiet=not options.verbose) - except ClickInstallerError as e: - print("Cannot install %s: %s" % (package_path, e), file=sys.stderr) - return 1 - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/commands/list.py click-0.4.46+16.10.20170607.3/click/commands/list.py --- click-0.4.45.1+16.10.20160916/click/commands/list.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/list.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,64 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""List installed Click packages.""" - -from __future__ import print_function - -import json -from optparse import OptionParser -import sys - -from gi.repository import Click - -from click.json_helpers import json_array_to_python - - -def list_packages(options): - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - if options.all: - return json_array_to_python(db.get_manifests(all_versions=True)) - else: - registry = Click.User.for_user(db, name=options.user) - return json_array_to_python(registry.get_manifests()) - - -def run(argv): - parser = OptionParser("%prog list [options]") - parser.add_option( - "--root", metavar="PATH", help="look for additional packages in PATH") - parser.add_option( - "--all", default=False, action="store_true", - help="list all installed packages") - parser.add_option( - "--user", metavar="USER", - help="list packages registered by USER (if you have permission)") - parser.add_option( - "--manifest", default=False, action="store_true", - help="format output as a JSON array of manifests") - options, _ = parser.parse_args(argv) - json_output = list_packages(options) - if options.manifest: - json.dump( - json_output, sys.stdout, ensure_ascii=False, sort_keys=True, - indent=4, separators=(",", ": ")) - print() - else: - for manifest in json_output: - print("%s\t%s" % (manifest["name"], manifest["version"])) - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/commands/pkgdir.py click-0.4.46+16.10.20170607.3/click/commands/pkgdir.py --- click-0.4.45.1+16.10.20160916/click/commands/pkgdir.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/pkgdir.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -#! /usr/bin/python3 - -# Copyright (C) 2013 Canonical Ltd. - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Print the directory where a Click package is unpacked.""" - -from __future__ import print_function - -from optparse import OptionParser -import sys - -from gi.repository import Click - - -def run(argv): - parser = OptionParser("%prog pkgdir [options] {PACKAGE-NAME|PATH}") - parser.add_option( - "--root", metavar="PATH", help="look for additional packages in PATH") - parser.add_option( - "--user", metavar="USER", - help="look up PACKAGE-NAME for USER (if you have permission; " - "default: current user)") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need package name") - try: - if "/" in args[0]: - print(Click.find_package_directory(args[0])) - else: - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - package_name = args[0] - registry = Click.User.for_user(db, name=options.user) - print(registry.get_path(package_name)) - except Exception as e: - print(e, file=sys.stderr) - return 1 - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/commands/register.py click-0.4.46+16.10.20170607.3/click/commands/register.py --- click-0.4.45.1+16.10.20160916/click/commands/register.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/register.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,57 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Register an installed Click package for a user.""" - -from __future__ import print_function - -from optparse import OptionParser - -from gi.repository import Click, GLib - - -def run(argv): - parser = OptionParser("%prog register [options] PACKAGE-NAME VERSION") - parser.add_option( - "--root", metavar="PATH", help="look for additional packages in PATH") - parser.add_option( - "--user", metavar="USER", - help="register package for USER (default: current user)") - parser.add_option( - "--all-users", default=False, action="store_true", - help="register package for all users") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need package name") - if len(args) < 2: - parser.error("need version") - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - package = args[0] - version = args[1] - if options.all_users: - registry = Click.User.for_all_users(db) - else: - registry = Click.User.for_user(db, name=options.user) - try: - old_version = registry.get_version(package) - except GLib.GError: - old_version = None - registry.set_version(package, version) - if old_version is not None: - db.maybe_remove(package, old_version) - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/commands/unregister.py click-0.4.46+16.10.20170607.3/click/commands/unregister.py --- click-0.4.45.1+16.10.20160916/click/commands/unregister.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/unregister.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unregister an installed Click package for a user.""" - -from __future__ import print_function - -from optparse import OptionParser -import os -import sys - -from gi.repository import Click - - -def run(argv): - parser = OptionParser("%prog unregister [options] PACKAGE-NAME [VERSION]") - parser.add_option( - "--root", metavar="PATH", help="look for additional packages in PATH") - parser.add_option( - "--user", metavar="USER", - help="unregister package for USER (default: $SUDO_USER, if known)") - parser.add_option( - "--all-users", default=False, action="store_true", - help="unregister package that was previously registered for all users") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need package name") - if os.geteuid() != 0: - parser.error( - "click unregister must be started as root, since it may need to " - "remove packages from disk") - if options.user is None and "SUDO_USER" in os.environ: - options.user = os.environ["SUDO_USER"] - db = Click.DB() - db.read(db_dir=None) - if options.root is not None: - db.add(options.root) - package = args[0] - if options.all_users: - registry = Click.User.for_all_users(db) - else: - registry = Click.User.for_user(db, name=options.user) - old_version = registry.get_version(package) - if len(args) >= 2 and old_version != args[1]: - print( - "Not removing %s %s; expected version %s" % - (package, old_version, args[1]), - file=sys.stderr) - sys.exit(1) - registry.remove(package) - db.maybe_remove(package, old_version) - # TODO: remove data - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/commands/verify.py click-0.4.46+16.10.20170607.3/click/commands/verify.py --- click-0.4.45.1+16.10.20160916/click/commands/verify.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/commands/verify.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,43 +0,0 @@ -#! /usr/bin/python3 - -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Verify a Click package.""" - -from __future__ import print_function - -from optparse import OptionParser - -from click.install import ClickInstaller - - -def run(argv): - parser = OptionParser("%prog verify [options] PACKAGE-FILE") - parser.add_option( - "--force-missing-framework", action="store_true", default=False, - help="ignore missing system framework") - parser.add_option( - "--allow-unauthenticated", action="store_true", default=False, - help="allow installing packages with no sigantures") - options, args = parser.parse_args(argv) - if len(args) < 1: - parser.error("need package file name") - package_path = args[0] - installer = ClickInstaller( - db=None, force_missing_framework=options.force_missing_framework, - allow_unauthenticated=options.allow_unauthenticated) - installer.audit(package_path, slow=True) - return 0 diff -Nru click-0.4.45.1+16.10.20160916/click/framework.py click-0.4.46+16.10.20170607.3/click/framework.py --- click-0.4.45.1+16.10.20160916/click/framework.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/framework.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,145 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Pure python click framework handling support.""" - -import logging -import os -import re - -try: - import apt_pkg -except: - pass - -import click.paths - - -class ClickFrameworkInvalid(Exception): - pass - - -# python version of the vala parse_deb822_file() -def parse_deb822_file(filename): - data = {} - with open(filename) as f: - for line in f: - line = line.strip() - # from deb822.vala - field_re_posix = ( - r'^([^:[:space:]]+)[[:space:]]*:[[:space:]]' - '([^[:space:]].*?)[[:space:]]*$') - # python does not do posix char classes - field_re = field_re_posix.replace("[:space:]", "\s") - blank_re_posix = r'^[[:space:]]*$' - blank_re = blank_re_posix.replace("[:space:]", "\s") - if re.match(blank_re, line): - break - match = re.match(field_re, line) - if match and match.group(1) and match.group(2): - data[match.group(1).lower()] = match.group(2) - return data - - -# python version of vala get_frameworks_dir -def get_frameworks_dir(): - return click.paths.frameworks_dir - - -def get_framework_path(framework_name): - framework_path = os.path.join( - get_frameworks_dir(), framework_name+".framework") - return framework_path - - -# python version of the vala click_framework_get_base_version() -def click_framework_get_base_version(framework_name): - deb822 = parse_deb822_file(get_framework_path(framework_name)) - return deb822.get("base-version", None) - - -# python version of the vala click_framework_get_base_version() -def click_framework_get_base_name(framework_name): - deb822 = parse_deb822_file(get_framework_path(framework_name)) - return deb822.get("base-name", None) - - -# python version of the vala click_framework_has_framework -def click_framework_has_framework(framework_name): - return os.path.exists(get_framework_path(framework_name)) - - -def validate_framework(framework_string, ignore_missing_frameworks=False): - try: - apt_pkg - except NameError: - logging.warning("No apt_pkg module, skipping validate_framework") - return - - try: - parsed_framework = apt_pkg.parse_depends(framework_string) - except ValueError: - raise ClickFrameworkInvalid( - 'Could not parse framework "%s"' % framework_string) - - base_name_versions = {} - missing_frameworks = [] - for or_dep in parsed_framework: - if len(or_dep) > 1: - raise ClickFrameworkInvalid( - 'Alternative dependencies in framework "%s" not yet ' - 'allowed' % framework_string) - if or_dep[0][1] or or_dep[0][2]: - raise ClickFrameworkInvalid( - 'Version relationship in framework "%s" not yet allowed' % - framework_string) - # now verify that different base versions are not mixed - framework_name = or_dep[0][0] - if not click_framework_has_framework(framework_name): - missing_frameworks.append(framework_name) - continue - # ensure we do not use different base versions for the same base-name - framework_base_name = click_framework_get_base_name( - framework_name) - framework_base_version = click_framework_get_base_version( - framework_name) - prev = base_name_versions.get(framework_base_name, None) - if prev and prev != framework_base_version: - raise ClickFrameworkInvalid( - 'Multiple frameworks with different base versions are not ' - 'allowed. Found: {} ({} != {})'.format( - framework_base_name, - framework_base_version, - base_name_versions[framework_base_name])) - base_name_versions[framework_base_name] = framework_base_version - - if not ignore_missing_frameworks: - if len(missing_frameworks) > 1: - raise ClickFrameworkInvalid( - 'Frameworks %s not present on system (use ' - '--force-missing-framework option to override)' % - ", ".join('"%s"' % f for f in missing_frameworks)) - elif missing_frameworks: - raise ClickFrameworkInvalid( - 'Framework "%s" not present on system (use ' - '--force-missing-framework option to override)' % - missing_frameworks[0]) - else: - if len(missing_frameworks) > 1: - logging.warning("Ignoring missing frameworks %s" % ( - ", ".join('"%s"' % f for f in missing_frameworks))) - elif missing_frameworks: - logging.warning('Ignoring missing framework "%s"' % ( - missing_frameworks[0])) diff -Nru click-0.4.45.1+16.10.20160916/click/__init__.py click-0.4.46+16.10.20170607.3/click/__init__.py --- click-0.4.45.1+16.10.20160916/click/__init__.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -# Marker to help resolve unfortunate name clash between this package and -# https://pypi.python.org/pypi/click. -_CLICK_IS_A_PACKAGING_FORMAT_ = 1 diff -Nru click-0.4.45.1+16.10.20160916/click/install.py click-0.4.46+16.10.20170607.3/click/install.py --- click-0.4.45.1+16.10.20160916/click/install.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/install.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,479 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Installing Click packages.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'ClickInstaller', - 'ClickInstallerAuditError', - 'ClickInstallerError', - 'ClickInstallerPermissionDenied', - ] - - -from functools import partial -import grp -import inspect -import json -import logging -import os -import pwd -import shutil -import stat -import subprocess -import sys -import tempfile -from textwrap import dedent - -from contextlib import closing - -from debian.debfile import DebFile as _DebFile -from debian.debian_support import Version -from gi.repository import Click - -from click.paths import preload_path -from click.preinst import static_preinst_matches -from click.versions import spec_version - -from click.framework import ( - validate_framework, - ClickFrameworkInvalid, -) - - -try: - _DebFile.close - DebFile = _DebFile -except AttributeError: - # Yay! The Ubuntu 13.04 version of python-debian 0.1.21 - # debian.debfile.DebFile has a .close() method but the PyPI version of - # 0.1.21 does not. It's worse than that because DebFile.close() really - # delegates to DebPart.close() and *that's* missing in the PyPI version. - # To get that working, we have to reach inside the object and name mangle - # the attribute. - class DebFile(_DebFile): - def close(self): - self.control._DebPart__member.close() - self.data._DebPart__member.close() - - -class DebsigVerifyError(Exception): - pass - - -class DebsigVerify: - """Tiny wrapper around the debsig-verify commandline""" - # from debsig-verify-0.9/debsigs.h - DS_SUCCESS = 0 - DS_FAIL_NOSIGS = 10 - DS_FAIL_UNKNOWN_ORIGIN = 11 - DS_FAIL_NOPOLICIES = 12 - DS_FAIL_BADSIG = 13 - DS_FAIL_INTERNAL = 14 - - # should be a property, but python does not support support - # class properties easily - @classmethod - def available(cls): - return Click.find_on_path("debsig-verify") - - @classmethod - def verify(cls, path, allow_unauthenticated): - command = ["debsig-verify"] + [path] - try: - subprocess.check_output(command, universal_newlines=True) - except subprocess.CalledProcessError as e: - if (allow_unauthenticated and - e.returncode in (DebsigVerify.DS_FAIL_NOSIGS, - DebsigVerify.DS_FAIL_UNKNOWN_ORIGIN, - DebsigVerify.DS_FAIL_NOPOLICIES)): - logging.warning( - "Signature check failed, but installing anyway " - "as requested") - else: - raise DebsigVerifyError( - "Signature verification error: %s" % e.output) - return True - - -class ClickInstallerError(Exception): - pass - - -class ClickInstallerPermissionDenied(ClickInstallerError): - pass - - -class ClickInstallerAuditError(ClickInstallerError): - pass - - -class ClickInstaller: - def __init__(self, db, force_missing_framework=False, - allow_unauthenticated=False): - self.db = db - self.force_missing_framework = force_missing_framework - self.allow_unauthenticated = allow_unauthenticated - - def _preload_path(self): - if "CLICK_PACKAGE_PRELOAD" in os.environ: - return os.environ["CLICK_PACKAGE_PRELOAD"] - my_path = inspect.getsourcefile(ClickInstaller) - preload = os.path.join( - os.path.dirname(my_path), os.pardir, "preload", ".libs", - "libclickpreload.so") - if os.path.exists(preload): - return os.path.abspath(preload) - return preload_path - - def _dpkg_architecture(self): - return subprocess.check_output( - ["dpkg", "--print-architecture"], - universal_newlines=True).rstrip("\n") - - def extract(self, path, target): - command = ["dpkg-deb", "-R", path, target] - with open(path, "rb") as fd: - env = dict(os.environ) - preloads = [self._preload_path()] - if "LD_PRELOAD" in env: - preloads.append(env["LD_PRELOAD"]) - env["LD_PRELOAD"] = " ".join(preloads) - env["CLICK_BASE_DIR"] = target - env["CLICK_PACKAGE_PATH"] = path - env["CLICK_PACKAGE_FD"] = str(fd.fileno()) - env.pop("HOME", None) - kwargs = {} - if sys.version >= "3.2": - kwargs["pass_fds"] = (fd.fileno(),) - subprocess.check_call(command, env=env, **kwargs) - - def audit(self, path, slow=False, check_arch=False): - # always do the signature check first - if DebsigVerify.available(): - try: - DebsigVerify.verify(path, self.allow_unauthenticated) - except DebsigVerifyError as e: - raise ClickInstallerAuditError(str(e)) - else: - logging.warning( - "debsig-verify not available; cannot check signatures") - - # fail early if the file cannot be opened - try: - with closing(DebFile(filename=path)) as package: - pass - except Exception as e: - raise ClickInstallerError("Failed to read %s: %s" % ( - path, str(e))) - - # then perform the audit - with closing(DebFile(filename=path)) as package: - control_fields = package.control.debcontrol() - - try: - click_version = Version(control_fields["Click-Version"]) - except KeyError: - raise ClickInstallerAuditError("No Click-Version field") - if click_version > spec_version: - raise ClickInstallerAuditError( - "Click-Version: %s newer than maximum supported version " - "%s" % (click_version, spec_version)) - - for field in ( - "Pre-Depends", "Depends", "Recommends", "Suggests", "Enhances", - "Conflicts", "Breaks", - "Provides", - ): - if field in control_fields: - raise ClickInstallerAuditError( - "%s field is forbidden in Click packages" % field) - - scripts = package.control.scripts() - if ("preinst" in scripts and - static_preinst_matches(scripts["preinst"])): - scripts.pop("preinst", None) - if scripts: - raise ClickInstallerAuditError( - "Maintainer scripts are forbidden in Click packages " - "(found: %s)" % - " ".join(sorted(scripts))) - - if not package.control.has_file("manifest"): - raise ClickInstallerAuditError("Package has no manifest") - with package.control.get_file("manifest", encoding="UTF-8") as f: - manifest = json.load(f) - - try: - package_name = manifest["name"] - except KeyError: - raise ClickInstallerAuditError('No "name" entry in manifest') - # TODO: perhaps just do full name validation? - if "/" in package_name: - raise ClickInstallerAuditError( - 'Invalid character "/" in "name" entry: %s' % package_name) - if "_" in package_name: - raise ClickInstallerAuditError( - 'Invalid character "_" in "name" entry: %s' % package_name) - - try: - package_version = manifest["version"] - except KeyError: - raise ClickInstallerAuditError( - 'No "version" entry in manifest') - # TODO: perhaps just do full version validation? - if "/" in package_version: - raise ClickInstallerAuditError( - 'Invalid character "/" in "version" entry: %s' % - package_version) - if "_" in package_version: - raise ClickInstallerAuditError( - 'Invalid character "_" in "version" entry: %s' % - package_version) - - try: - framework = manifest["framework"] - except KeyError: - raise ClickInstallerAuditError( - 'No "framework" entry in manifest') - try: - validate_framework(framework, self.force_missing_framework) - except ClickFrameworkInvalid as e: - raise ClickInstallerAuditError(str(e)) - - if check_arch: - architecture = manifest.get("architecture", "all") - if architecture != "all": - dpkg_architecture = self._dpkg_architecture() - if isinstance(architecture, list): - if dpkg_architecture not in architecture: - raise ClickInstallerAuditError( - 'Package architectures "%s" not compatible ' - 'with system architecture "%s"' % - (" ".join(architecture), dpkg_architecture)) - elif architecture != dpkg_architecture: - raise ClickInstallerAuditError( - 'Package architecture "%s" not compatible ' - 'with system architecture "%s"' % - (architecture, dpkg_architecture)) - - # This isn't ideally quick, since it has to decompress the data - # part of the package, but dpkg's path filtering code assumes - # that all paths start with "./" so we must check it before - # passing the package to dpkg. - for data_name in package.data: - if data_name != "." and not data_name.startswith("./"): - raise ClickInstallerAuditError( - 'File name "%s" in package does not start with "./"' % - data_name) - - if slow: - temp_dir = tempfile.mkdtemp(prefix="click") - try: - self.extract(path, temp_dir) - command = [ - "md5sum", "-c", "--quiet", - os.path.join("DEBIAN", "md5sums"), - ] - subprocess.check_call(command, cwd=temp_dir) - finally: - shutil.rmtree(temp_dir) - - return package_name, package_version - - def _drop_privileges(self, username): - if os.geteuid() != 0: - return - pw = pwd.getpwnam(username) - os.setgroups( - [g.gr_gid for g in grp.getgrall() if username in g.gr_mem]) - # Portability note: this assumes that we have [gs]etres[gu]id, which - # is true on Linux but not necessarily elsewhere. If you need to - # support something else, there are reasonably standard alternatives - # involving other similar calls; see e.g. gnulib/lib/idpriv-drop.c. - os.setresgid(pw.pw_gid, pw.pw_gid, pw.pw_gid) - os.setresuid(pw.pw_uid, pw.pw_uid, pw.pw_uid) - assert os.getresuid() == (pw.pw_uid, pw.pw_uid, pw.pw_uid) - assert os.getresgid() == (pw.pw_gid, pw.pw_gid, pw.pw_gid) - os.umask(0o022) - - def _euid_access(self, username, path, mode): - """Like os.access, but for the effective UID.""" - # TODO: Dropping privileges and calling - # os.access(effective_ids=True) ought to work, but for some reason - # appears not to return False when it should. It seems that we need - # a subprocess to check this reliably. At least we don't have to - # exec anything. - pid = os.fork() - if pid == 0: # child - self._drop_privileges(username) - os._exit(0 if os.access(path, mode) else 1) - else: # parent - _, status = os.waitpid(pid, 0) - return status == 0 - - def _check_write_permissions(self, path): - while True: - if os.path.exists(path): - break - path = os.path.dirname(path) - if path == "/": - break - if not self._euid_access("clickpkg", path, os.W_OK): - raise ClickInstallerPermissionDenied( - 'Cannot acquire permission to write to %s; either run as root ' - 'with --user, or use "pkcon install-local" instead' % path) - - def _install_preexec(self, inst_dir): - self._drop_privileges("clickpkg") - - admin_dir = os.path.join(inst_dir, ".click") - if not os.path.exists(admin_dir): - os.makedirs(admin_dir) - with open(os.path.join(admin_dir, "available"), "w"): - pass - with open(os.path.join(admin_dir, "status"), "w"): - pass - os.mkdir(os.path.join(admin_dir, "info")) - os.mkdir(os.path.join(admin_dir, "updates")) - os.mkdir(os.path.join(admin_dir, "triggers")) - - def _unpack(self, path, user=None, all_users=False, quiet=True): - package_name, package_version = self.audit(path, check_arch=True) - - # Is this package already unpacked in an underlay (non-topmost) - # database? - if self.db.has_package_version(package_name, package_version): - overlay = self.db.get(self.db.props.size - 1) - if not overlay.has_package_version(package_name, package_version): - return package_name, package_version, None - - package_dir = os.path.join(self.db.props.overlay, package_name) - inst_dir = os.path.join(package_dir, package_version) - assert ( - os.path.dirname(os.path.dirname(inst_dir)) == - self.db.props.overlay) - - self._check_write_permissions(self.db.props.overlay) - root_click = os.path.join(self.db.props.overlay, ".click") - if not os.path.exists(root_click): - os.makedirs(root_click) - if os.geteuid() == 0: - pw = pwd.getpwnam("clickpkg") - os.chown(root_click, pw.pw_uid, pw.pw_gid) - - # TODO: sandbox so that this can only write to the unpack directory - command = [ - "dpkg", - # We normally run dpkg as non-root. - "--force-not-root", - # /sbin and /usr/sbin may not necessarily be on $PATH; we don't - # use the tools dpkg gets from there. - "--force-bad-path", - # We check the package architecture ourselves in audit(). - "--force-architecture", - "--instdir", inst_dir, - "--admindir", os.path.join(inst_dir, ".click"), - "--path-exclude", "*/.click/*", - "--log", os.path.join(root_click, "log"), - "--no-triggers", - "--install", path, - ] - with open(path, "rb") as fd: - env = dict(os.environ) - preloads = [self._preload_path()] - if "LD_PRELOAD" in env: - preloads.append(env["LD_PRELOAD"]) - env["LD_PRELOAD"] = " ".join(preloads) - env["CLICK_BASE_DIR"] = self.db.props.overlay - env["CLICK_PACKAGE_PATH"] = path - env["CLICK_PACKAGE_FD"] = str(fd.fileno()) - env.pop("HOME", None) - kwargs = {} - if sys.version >= "3.2": - kwargs["pass_fds"] = (fd.fileno(),) - if quiet: - fn = subprocess.check_output - kwargs["stderr"] = subprocess.STDOUT - else: - fn = subprocess.check_call - try: - fn(command, - preexec_fn=partial(self._install_preexec, inst_dir), - env=env, universal_newlines=True, - **kwargs) - except subprocess.CalledProcessError as e: - logging.error("%s failed with exit_code %s:\n%s" % ( - command, e.returncode, e.output)) - raise - for dirpath, dirnames, filenames in os.walk(inst_dir): - for entry in dirnames + filenames: - entry_path = os.path.join(dirpath, entry) - entry_mode = os.lstat(entry_path).st_mode - new_entry_mode = entry_mode | stat.S_IRGRP | stat.S_IROTH - if entry_mode & stat.S_IXUSR: - new_entry_mode |= stat.S_IXGRP | stat.S_IXOTH - if new_entry_mode != entry_mode: - try: - os.chmod(entry_path, new_entry_mode) - except OSError: - pass - - current_path = os.path.join(package_dir, "current") - - if os.path.islink(current_path): - old_version = os.readlink(current_path) - if "/" in old_version: - old_version = None - else: - old_version = None - Click.package_install_hooks( - self.db, package_name, old_version, package_version, - user_name=None) - - new_path = os.path.join(package_dir, "current.new") - Click.symlink_force(package_version, new_path) - if os.geteuid() == 0: - # shutil.chown would be more convenient, but it doesn't support - # follow_symlinks=False in Python 3.3. - # http://bugs.python.org/issue18108 - pw = pwd.getpwnam("clickpkg") - os.chown(new_path, pw.pw_uid, pw.pw_gid, follow_symlinks=False) - os.rename(new_path, current_path) - - return package_name, package_version, old_version - - def install(self, path, user=None, all_users=False, quiet=True): - package_name, package_version, old_version = self._unpack( - path, user=user, all_users=all_users, quiet=quiet) - - if user is not None or all_users: - if all_users: - registry = Click.User.for_all_users(self.db) - else: - registry = Click.User.for_user(self.db, name=user) - registry.set_version(package_name, package_version) - else: - print(dedent("""\ - %s %s has not been registered for any users. - It may be garbage-collected the next time the system starts. - To avoid this, use "click register". - """) % (package_name, package_version)) - - if old_version is not None: - self.db.maybe_remove(package_name, old_version) diff -Nru click-0.4.45.1+16.10.20160916/click/json_helpers.py click-0.4.46+16.10.20170607.3/click/json_helpers.py --- click-0.4.45.1+16.10.20160916/click/json_helpers.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/json_helpers.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,59 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Helper functions to turn json-glib objects into Python objects.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'ClickJsonError', - 'json_array_to_python', - 'json_node_to_python', - 'json_object_to_python', - ] - - -from gi.repository import Json - - -class ClickJsonError(Exception): - pass - - -def json_array_to_python(array): - return [json_node_to_python(element) for element in array.get_elements()] - - -def json_object_to_python(obj): - ret = {} - for name in obj.get_members(): - ret[name] = json_node_to_python(obj.get_member(name)) - return ret - - -def json_node_to_python(node): - node_type = node.get_node_type() - if node_type == Json.NodeType.ARRAY: - return json_array_to_python(node.get_array()) - elif node_type == Json.NodeType.OBJECT: - return json_object_to_python(node.get_object()) - elif node_type == Json.NodeType.NULL: - return None - elif node_type == Json.NodeType.VALUE: - return node.get_value() - else: - raise ClickJsonError( - "Unknown JSON node type \"%s\"" % node_type.value_nick) diff -Nru click-0.4.45.1+16.10.20160916/click/Makefile.am click-0.4.46+16.10.20170607.3/click/Makefile.am --- click-0.4.45.1+16.10.20160916/click/Makefile.am 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -SUBDIRS = tests - -noinst_SCRIPTS = paths.py -CLEANFILES = $(noinst_SCRIPTS) - -do_subst = sed \ - -e 's,[@]sysconfdir[@],$(sysconfdir),g' \ - -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \ - -e 's,[@]pkglibdir[@],$(pkglibdir),g' \ - -e 's,[@]DEFAULT_ROOT[@],$(DEFAULT_ROOT),g' - -paths.py: paths.py.in Makefile - $(do_subst) < $(srcdir)/paths.py.in > $@ diff -Nru click-0.4.45.1+16.10.20160916/click/osextras.py click-0.4.46+16.10.20170607.3/click/osextras.py --- click-0.4.45.1+16.10.20160916/click/osextras.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/osextras.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,92 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Extra OS-level utility functions. - -Usually we can instead use the functions exported from -lib/click/osextras.vala via GObject Introspection. These pure-Python -versions are preserved so that they can be used from code that needs to be -maximally portable: for example, click.build is intended to be usable even -on systems that lack GObject, as long as they have a reasonably recent -version of Python. -""" - -__all__ = [ - 'ensuredir', - 'find_on_path', - 'unlink_force', - ] - - -import errno -import os - -try: - # Python 3.3 - from shutil import which - - def find_on_path(command): - # http://bugs.python.org/issue17012 - path = os.environ.get('PATH', os.pathsep) - return which(command, path=os.environ.get('PATH', path)) is not None -except ImportError: - # Python 2 - def find_on_path(command): - """Is command on the executable search path?""" - if 'PATH' not in os.environ: - return False - path = os.environ['PATH'] - for element in path.split(os.pathsep): - if not element: - continue - filename = os.path.join(element, command) - if os.path.isfile(filename) and os.access(filename, os.X_OK): - return True - return False - - -def ensuredir(directory): - if not os.path.isdir(directory): - os.makedirs(directory) - - -def listdir_force(directory): - try: - return os.listdir(directory) - except OSError as e: - if e.errno == errno.ENOENT: - return [] - raise - - -def unlink_force(path): - """Unlink path, without worrying about whether it exists.""" - try: - os.unlink(path) - except OSError as e: - if e.errno != errno.ENOENT: - raise - - -def symlink_force(source, link_name): - """Create symlink link_name -> source, even if link_name exists.""" - unlink_force(link_name) - os.symlink(source, link_name) - - -def get_umask(): - mask = os.umask(0) - os.umask(mask) - return mask diff -Nru click-0.4.45.1+16.10.20160916/click/paths.py.in click-0.4.46+16.10.20170607.3/click/paths.py.in --- click-0.4.45.1+16.10.20160916/click/paths.py.in 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/paths.py.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Click paths.""" - -preload_path = "@pkglibdir@/libclickpreload.so" -frameworks_dir = "@pkgdatadir@/frameworks" diff -Nru click-0.4.45.1+16.10.20160916/click/preinst.py click-0.4.46+16.10.20170607.3/click/preinst.py --- click-0.4.45.1+16.10.20160916/click/preinst.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/preinst.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,66 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Preinst for Click packages. - -In general there is a rule that Click packages may not have maintainer -scripts. However, there is one exception: a static preinst used to cause -dpkg to fail if people attempt to install Click packages directly using dpkg -rather than via "click install". This avoids accidents, since Click -packages use a different root of their filesystem tarball. -""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'static_preinst', - 'static_preinst_matches', - ] - - -_older_static_preinst = """\ -#! /bin/sh -echo "Click packages may not be installed directly using dpkg." -echo "Use click-install instead." -exit 1 -""" - - -_old_static_preinst = """\ -#! /bin/sh -echo "Click packages may not be installed directly using dpkg." -echo "Use 'click-package install' instead." -exit 1 -""" - - -static_preinst = """\ -#! /bin/sh -echo "Click packages may not be installed directly using dpkg." -echo "Use 'click install' instead." -exit 1 -""" - - -def static_preinst_matches(preinst): - for allow_preinst in ( - _older_static_preinst, - _old_static_preinst, - static_preinst, - ): - if preinst == allow_preinst.encode(): - return True - return False diff -Nru click-0.4.45.1+16.10.20160916/click/tests/config.py.in click-0.4.46+16.10.20170607.3/click/tests/config.py.in --- click-0.4.45.1+16.10.20160916/click/tests/config.py.in 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/config.py.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -abs_top_builddir = "@abs_top_builddir@" -STAT_OFFSET_UID = @STAT_OFFSET_UID@ -STAT_OFFSET_GID = @STAT_OFFSET_GID@ -STAT64_OFFSET_UID = @STAT64_OFFSET_UID@ -STAT64_OFFSET_GID = @STAT64_OFFSET_GID@ diff -Nru click-0.4.45.1+16.10.20160916/click/tests/gimock.py click-0.4.46+16.10.20170607.3/click/tests/gimock.py --- click-0.4.45.1+16.10.20160916/click/tests/gimock.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/gimock.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,551 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Mock function support based on GObject Introspection. - -(Note to reviewers: I expect to rewrite this from scratch on my own time as -a more generalised set of Python modules for unit testing of C code, -although using similar core ideas. This is a first draft for the purpose of -getting Click's test suite to work expediently, rather than an interface I'm -prepared to commit to long-term.) - -Python is a versatile and concise language for writing tests, and GObject -Introspection (GI) makes it straightforward (often trivial) to bind native -code into Python. However, writing tests for native code quickly runs into -the problem of how to build mock functions. You might reasonably have code -that calls chown(), for instance, and want to test how it's called rather -than worrying about setting up a fakeroot-type environment where chown() -will work. The obvious solution is to use `LD_PRELOAD` wrappers, but there -are various problems to overcome in practice: - - * You can only set up a new `LD_PRELOAD` by going through the run-time - linker; you can't just set it for a single in-process test case. - * Generating the preloaded wrapper involves a fair bit of boilerplate code. - * Having to write per-test mock code in C is inconvenient, and makes it - difficult to get information back out of the mock (such as "how often was - this function called, and with what arguments?"). - -The first problem can be solved by a decorator that knows how to run -individual tests in a subprocess. This is made somewhat more inconvenient -by the fact that there is no way for a context manager's `__enter__` method -to avoid executing the context-managed block other than by throwing an -exception, which makes it hard to silently avoid executing the test case in -the parent process, but we can work around this at the cost of an extra line -of code per invocation. - -For the rest, a combination of GI itself and ctypes can help. We can use GI -to keep track of argument and return types of the mocked C functions in a -reasonably sane way, by parsing header files. We're operating in the other -direction from how GI is normally used, so PyGObject can't deal with -bridging the two calling conventions for us. ctypes can: but we still need -to be careful! We have to construct the callback functions in the child -process, ensure that we keep references to them, and inject function -pointers into the preloaded library via specially-named helper functions; -until those function pointers are set up we must make sure to call the libc -functions instead (since some of them might be called during Python -startup). - -The combination of all of this allows us to bridge C functions somewhat -transparently into Python. This lets you supply a Python function or method -as the mock replacement for a C library function, making it much simpler to -record state. - -It's still not perfect: - - * We're using GI in an upside-down kind of way, and we specifically need - GIR files rather than typelibs so that we can extract the original C - type, so some fiddling is required for each new function you want to - mock. - - * The subprocess arrangements are unavoidably slow and it's possible that - they may cause problems with some test runners. - - * Some C functions (such as `stat`) tend to have multiple underlying entry - points in the C library which must be preloaded independently. - - * You have to be careful about how your libraries are linked, because `ld - -Wl,-Bsymbolic-functions` prevents `LD_PRELOAD` working for intra-library - calls. - - * `ctypes should return composite types from callbacks - `_. The least awful approach for now - seems to be to construct the composite type in question, stash a - reference to it forever, and then return a pointer to it as a void *; we - can only get away with this because tests are by nature relatively - short-lived. - - * The ctypes module's handling of 64-bit pointers is basically just awful. - The right answer is probably to use a different callback-generation - framework entirely (maybe extending PyGObject so that we can get at the - pieces we need), but I've hacked around it for now. - - * It doesn't appear to be possible to install mock replacements for - functions that are called directly from Python code using their GI - wrappers. You can work around this by simply patching the GI wrapper - instead, using `mock.patch`. - -I think the benefits, in terms of local clarity of tests, are worth the -downsides. -""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = ['GIMockTestCase'] - - -import contextlib -import ctypes -import fcntl -from functools import partial -import os -import pickle -import shutil -import subprocess -import sys -import tempfile -from textwrap import dedent -import traceback -import unittest -try: - from unittest import mock -except ImportError: - import mock -try: - import xml.etree.cElementTree as etree -except ImportError: - import xml.etree.ElementTree as etree - -from click.tests.gimock_types import Stat, Stat64 -from click.tests import config, get_executable - -# Borrowed from giscanner.girparser. -CORE_NS = "http://www.gtk.org/introspection/core/1.0" -C_NS = "http://www.gtk.org/introspection/c/1.0" -GLIB_NS = "http://www.gtk.org/introspection/glib/1.0" - - -def _corens(tag): - return '{%s}%s' % (CORE_NS, tag) - - -def _glibns(tag): - return '{%s}%s' % (GLIB_NS, tag) - - -def _cns(tag): - return '{%s}%s' % (C_NS, tag) - - -# Override some c:type annotations that g-ir-scanner gets a bit wrong. -_c_type_override = { - "passwd*": "struct passwd*", - "stat*": "struct stat*", - "stat64*": "struct stat64*", - } - - -# Mapping of GI type name -> ctypes type. -_typemap = { - "GError**": ctypes.c_void_p, - "gboolean": ctypes.c_int, - "gint": ctypes.c_int, - "gint*": ctypes.POINTER(ctypes.c_int), - "gint32": ctypes.c_int32, - "gpointer": ctypes.c_void_p, - "guint": ctypes.c_uint, - "guint8**": ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8)), - "guint32": ctypes.c_uint32, - "none": None, - "utf8": ctypes.c_char_p, - "utf8*": ctypes.POINTER(ctypes.c_char_p), - } - - -class GIMockTestCase(unittest.TestCase): - def setUp(self): - super(GIMockTestCase, self).setUp() - self._preload_func_refs = [] - self._composite_refs = [] - self._delegate_funcs = {} - - def tearDown(self): - self._preload_func_refs = [] - self._composite_refs = [] - self._delegate_funcs = {} - - def doCleanups(self): - # we do not want to run the cleanups twice, just run it in the parent - if "GIMOCK_SUBPROCESS" not in os.environ: - return super(GIMockTestCase, self).doCleanups() - - def _gir_get_type(self, obj): - ret = {} - arrayinfo = obj.find(_corens("array")) - if arrayinfo is not None: - typeinfo = arrayinfo.find(_corens("type")) - raw_ctype = arrayinfo.get(_cns("type")) - else: - typeinfo = obj.find(_corens("type")) - raw_ctype = typeinfo.get(_cns("type")) - gi_type = typeinfo.get("name") - if obj.get("direction", "in") == "out": - gi_type += "*" - if arrayinfo is not None: - gi_type += "*" - ret["gi"] = gi_type - ret["c"] = _c_type_override.get(raw_ctype, raw_ctype) - return ret - - def _parse_gir(self, path): - # A very, very crude GIR parser. We might have used - # giscanner.girparser, but it's not importable in Python 3 at the - # moment. - tree = etree.parse(path) - root = tree.getroot() - assert root.tag == _corens("repository") - assert root.get("version") == "1.2" - ns = root.find(_corens("namespace")) - assert ns is not None - funcs = {} - for func in ns.findall(_corens("function")): - name = func.get(_cns("identifier")) - # g-ir-scanner skips identifiers starting with "__", which we - # need in order to mock stat effectively. Work around this. - name = name.replace("under_under_", "__") - headers = None - for attr in func.findall(_corens("attribute")): - if attr.get("name") == "headers": - headers = attr.get("value") - break - rv = func.find(_corens("return-value")) - assert rv is not None - params = [] - paramnode = func.find(_corens("parameters")) - if paramnode is not None: - for param in paramnode.findall(_corens("parameter")): - params.append({ - "name": param.get("name"), - "type": self._gir_get_type(param), - }) - if func.get("throws", "0") == "1": - params.append({ - "name": "error", - "type": {"gi": "GError**", "c": "GError**"}, - }) - funcs[name] = { - "name": name, - "headers": headers, - "rv": self._gir_get_type(rv), - "params": params, - } - return funcs - - def _ctypes_type(self, gi_type): - return _typemap[gi_type["gi"]] - - def make_preloads(self, preloads): - rpreloads = [] - std_headers = set([ - "dlfcn.h", - # Not strictly needed, but convenient for ad-hoc debugging. - "stdio.h", - "stdint.h", - "stdlib.h", - "string.h", - "sys/types.h", - "unistd.h", - ]) - preload_headers = set() - funcs = self._parse_gir("click/tests/preload.gir") - for name, func in preloads.items(): - info = funcs[name] - rpreloads.append([info, func]) - headers = info["headers"] - if headers is not None: - preload_headers.update(headers.split(",")) - if "GIMOCK_SUBPROCESS" in os.environ: - return None, rpreloads - self._gimock_temp_dir = tempfile.mkdtemp(prefix="gimock") - self.addCleanup(shutil.rmtree, self._gimock_temp_dir) - preloads_dir = os.path.join(self._gimock_temp_dir, "_preloads") - os.makedirs(preloads_dir) - c_path = os.path.join(preloads_dir, "gimockpreload.c") - with open(c_path, "w") as c: - print("#define _GNU_SOURCE", file=c) - for header in sorted(std_headers | preload_headers): - print("#include <%s>" % header, file=c) - print(file=c) - for info, _ in rpreloads: - conv = {} - conv["name"] = info["name"] - argtypes = [p["type"]["c"] for p in info["params"]] - argnames = [p["name"] for p in info["params"]] - conv["ret"] = info["rv"]["c"] - conv["bareproto"] = ", ".join(argtypes) - conv["proto"] = ", ".join( - "%s %s" % pair for pair in zip(argtypes, argnames)) - conv["args"] = ", ".join(argnames) - if conv["ret"] == "gchar*": - conv["need_strdup"] = "strdup" - else: - conv["need_strdup"] = "" - # The delegation scheme used here is needed because trying - # to pass pointers back and forward through ctypes is a - # recipe for having them truncated to 32 bits at the drop of - # a hat. This approach is less obvious but much safer. - print(dedent("""\ - typedef %(ret)s preloadtype_%(name)s (%(bareproto)s); - preloadtype_%(name)s *ctypes_%(name)s = (void *) 0; - preloadtype_%(name)s *real_%(name)s = (void *) 0; - static volatile int delegate_%(name)s = 0; - - extern void _gimock_init_%(name)s (preloadtype_%(name)s *f) - { - ctypes_%(name)s = f; - if (! real_%(name)s) { - /* Retry lookup in case the symbol wasn't - * resolvable until the program under test was - * loaded. - */ - char *err; - dlerror (); - real_%(name)s = dlsym (RTLD_NEXT, "%(name)s"); - if ((err = dlerror ()) != NULL) { - fprintf (stderr, - "Error getting address of symbol " - "'%(name)s': %%s\\n", err); - fflush (stderr); - _exit (1); - } - } - } - """) % conv, file=c) - if conv["ret"] == "void": - print(dedent("""\ - void %(name)s (%(proto)s) - { - if (ctypes_%(name)s) { - delegate_%(name)s = 0; - (*ctypes_%(name)s) (%(args)s); - if (! delegate_%(name)s) - return; - } - if (!real_%(name)s) - _gimock_init_%(name)s (ctypes_%(name)s); - (*real_%(name)s) (%(args)s); - } - """) % conv, file=c) - else: - print(dedent("""\ - %(ret)s %(name)s (%(proto)s) - { - if (ctypes_%(name)s) { - %(ret)s ret; - delegate_%(name)s = 0; - ret = (*ctypes_%(name)s) (%(args)s); - if (! delegate_%(name)s) - return %(need_strdup)s(ret); - } - if (!real_%(name)s) - _gimock_init_%(name)s (ctypes_%(name)s); - return (*real_%(name)s) (%(args)s); - } - """) % conv, file=c) - print(dedent("""\ - extern void _gimock_delegate_%(name)s (void) - { - delegate_%(name)s = 1; - } - """) % conv, file=c) - print(dedent("""\ - static void __attribute__ ((constructor)) - gimockpreload_init (void) - { - char *err; - dlerror (); - """), file=c) - print("\n".join(" " + line for line in (dedent("""\ - real_%(name)s = dlsym (RTLD_NEXT, "%(name)s"); - if ((err = dlerror ()) != NULL) { - fprintf (stderr, - "Error getting address of symbol " - "'%(name)s': %%s\\n", err); - fflush (stderr); - _exit (1); - } - """) % {"name": name}).split("\n")), file=c) - print("}", file=c) - if "GIMOCK_PRELOAD_DEBUG" in os.environ: - with open(c_path) as c: - print(c.read()) - # TODO: Use libtool or similar rather than hardcoding gcc invocation. - lib_path = os.path.join(preloads_dir, "libgimockpreload.so") - libraries = ["glib-2.0", "gee-0.8", "json-glib-1.0"] - cflags = subprocess.check_output( - ["pkg-config", "--cflags"] + libraries, - universal_newlines=True).rstrip("\n").split() - libs = subprocess.check_output( - ["pkg-config", "--libs"] + libraries, - universal_newlines=True).rstrip("\n").split() - compile_cmd = [ - "gcc", "-O0", "-g", "-shared", "-fPIC", "-DPIC", - "-I", "lib/click", - "-L", - os.path.join(config.abs_top_builddir, "lib", "click", ".libs"), - ] - compile_cmd.extend(cflags) - compile_cmd.extend(["-Wl,-soname", "-Wl,libgimockpreload.so"]) - compile_cmd.append("-Wl,--no-as-needed") - compile_cmd.append(c_path) - compile_cmd.append("-lclick-0.4") - compile_cmd.extend(libs) - compile_cmd.append("-ldl") - compile_cmd.extend(["-o", lib_path]) - subprocess.check_call(compile_cmd) - return lib_path, rpreloads - - # Use as: - # with self.run_in_subprocess("func", ...) as (enter, preloads): - # enter() - # # test case body; preloads["func"] will be a mock.MagicMock - # # instance - @contextlib.contextmanager - def run_in_subprocess(self, *patches): - preloads = {} - for patch in patches: - preloads[patch] = mock.MagicMock() - if preloads: - lib_path, rpreloads = self.make_preloads(preloads) - else: - lib_path, rpreloads = None, None - - class ParentProcess(Exception): - pass - - def helper(lib_path, rpreloads): - if "GIMOCK_SUBPROCESS" in os.environ: - del os.environ["LD_PRELOAD"] - preload_lib = ctypes.cdll.LoadLibrary(lib_path) - delegate_cfunctype = ctypes.CFUNCTYPE(None) - for info, func in rpreloads: - signature = [info["rv"]] + [ - p["type"] for p in info["params"]] - signature = [self._ctypes_type(t) for t in signature] - cfunctype = ctypes.CFUNCTYPE(*signature) - init = getattr( - preload_lib, "_gimock_init_%s" % info["name"]) - cfunc = cfunctype(func) - self._preload_func_refs.append(cfunc) - init(cfunc) - delegate = getattr( - preload_lib, "_gimock_delegate_%s" % info["name"]) - self._delegate_funcs[info["name"]] = delegate_cfunctype( - delegate) - return - rfd, wfd = os.pipe() - # It would be cleaner to use subprocess.Popen(pass_fds=[wfd]), but - # that isn't available in Python 2.7. - if hasattr(os, "set_inheritable"): - os.set_inheritable(wfd, True) - else: - fcntl.fcntl(rfd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) - args = get_executable() + [ - "-m", "unittest", - "%s.%s.%s" % ( - self.__class__.__module__, self.__class__.__name__, - self._testMethodName)] - env = os.environ.copy() - env["GIMOCK_SUBPROCESS"] = str(wfd) - if lib_path is not None: - env["LD_PRELOAD"] = lib_path - subp = subprocess.Popen(args, close_fds=False, env=env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - os.close(wfd) - reader = os.fdopen(rfd, "rb") - stdout, stderr = subp.communicate() - exctype = pickle.load(reader) - # "normal" exit - if exctype is not None and issubclass(exctype, SystemExit): - pass - elif exctype is not None and issubclass(exctype, AssertionError): - print(stdout, file=sys.stdout) - print(stderr, file=sys.stderr) - raise AssertionError("Subprocess failed a test!") - elif exctype is not None or subp.returncode != 0: - print(stdout, file=sys.stdout) - print(stderr, file=sys.stderr) - raise Exception("Subprocess returned an error! (%s, %s)" % ( - exctype, subp.returncode)) - reader.close() - raise ParentProcess() - - try: - yield partial(helper, lib_path, rpreloads), preloads - if "GIMOCK_SUBPROCESS" in os.environ: - wfd = int(os.environ["GIMOCK_SUBPROCESS"]) - writer = os.fdopen(wfd, "wb") - # a sys.ext will generate a SystemExit exception, so we - # push it into the pipe so that the parent knows whats going on - pickle.dump(SystemExit, writer) - writer.flush() - sys.exit(0) - except ParentProcess: - pass - except Exception as e: - if "GIMOCK_SUBPROCESS" in os.environ: - wfd = int(os.environ["GIMOCK_SUBPROCESS"]) - writer = os.fdopen(wfd, "wb") - # It would be better to use tblib to pickle the traceback so - # that we can re-raise it properly from the parent process. - # Until that's packaged and available to us, just print the - # traceback and send the exception type. - print() - traceback.print_exc() - pickle.dump(type(e), writer) - writer.flush() - os._exit(1) - else: - raise - - def make_pointer(self, composite): - # Store a reference to a composite type and return a pointer to it, - # working around http://bugs.python.org/issue5710. - self._composite_refs.append(composite) - return ctypes.addressof(composite) - - def make_string(self, s): - # As make_pointer, but for a string. - copied = ctypes.create_string_buffer(s.encode()) - self._composite_refs.append(copied) - return ctypes.addressof(copied) - - def convert_pointer(self, composite_type, address): - # Return a ctypes composite type instance at a given address. - return composite_type.from_address(address) - - def convert_stat_pointer(self, name, address): - # As convert_pointer, but for a "struct stat *" or "struct stat64 *" - # depending on the wrapped function name. - stat_type = {"__xstat": Stat, "__xstat64": Stat64} - return self.convert_pointer(stat_type[name], address) - - def delegate_to_original(self, name): - # Cause the wrapper function to delegate to the original version - # after the callback returns. (Note that the callback still needs - # to return something type-compatible with the declared result type, - # although the return value will otherwise be ignored.) - self._delegate_funcs[name]() diff -Nru click-0.4.45.1+16.10.20160916/click/tests/gimock_types.py click-0.4.46+16.10.20170607.3/click/tests/gimock_types.py --- click-0.4.45.1+16.10.20160916/click/tests/gimock_types.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/gimock_types.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,89 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""A collection of variously hacky ctypes definitions for use with gimock.""" - -import ctypes - -from click.tests.config import ( - STAT_OFFSET_GID, - STAT_OFFSET_UID, - STAT64_OFFSET_GID, - STAT64_OFFSET_UID, - ) - - -class Passwd(ctypes.Structure): - _fields_ = [ - ("pw_name", ctypes.c_char_p), - ("pw_passwd", ctypes.c_char_p), - ("pw_uid", ctypes.c_uint32), - ("pw_gid", ctypes.c_uint32), - ("pw_gecos", ctypes.c_char_p), - ("pw_dir", ctypes.c_char_p), - ("pw_shell", ctypes.c_char_p), - ] - - -# TODO: This is pretty awful. The layout of "struct stat" is complicated -# enough that we have to use offsetof() in configure to pick out the fields -# we care about. Fortunately, we only care about a couple of fields, and -# since this is an output parameter it doesn't matter if our structure is -# too short (if we cared about this then we could use AC_CHECK_SIZEOF to -# figure it out). -class Stat(ctypes.Structure): - _pack_ = 1 - _fields_ = [] - _fields_.append( - ("pad0", ctypes.c_ubyte * min(STAT_OFFSET_UID, STAT_OFFSET_GID))) - if STAT_OFFSET_UID < STAT_OFFSET_GID: - _fields_.append(("st_uid", ctypes.c_uint32)) - pad = (STAT_OFFSET_GID - STAT_OFFSET_UID - - ctypes.sizeof(ctypes.c_uint32)) - assert pad >= 0 - if pad > 0: - _fields_.append(("pad1", ctypes.c_ubyte * pad)) - _fields_.append(("st_gid", ctypes.c_uint32)) - else: - _fields_.append(("st_gid", ctypes.c_uint32)) - pad = (STAT_OFFSET_UID - STAT_OFFSET_GID - - ctypes.sizeof(ctypes.c_uint32)) - assert pad >= 0 - if pad > 0: - _fields_.append(("pad1", ctypes.c_ubyte * pad)) - _fields_.append(("st_uid", ctypes.c_uint32)) - - -class Stat64(ctypes.Structure): - _pack_ = 1 - _fields_ = [] - _fields_.append( - ("pad0", ctypes.c_ubyte * min(STAT64_OFFSET_UID, STAT64_OFFSET_GID))) - if STAT64_OFFSET_UID < STAT64_OFFSET_GID: - _fields_.append(("st_uid", ctypes.c_uint32)) - pad = (STAT64_OFFSET_GID - STAT64_OFFSET_UID - - ctypes.sizeof(ctypes.c_uint32)) - assert pad >= 0 - if pad > 0: - _fields_.append(("pad1", ctypes.c_ubyte * pad)) - _fields_.append(("st_gid", ctypes.c_uint32)) - else: - _fields_.append(("st_gid", ctypes.c_uint32)) - pad = (STAT64_OFFSET_UID - STAT64_OFFSET_GID - - ctypes.sizeof(ctypes.c_uint32)) - assert pad >= 0 - if pad > 0: - _fields_.append(("pad1", ctypes.c_ubyte * pad)) - _fields_.append(("st_uid", ctypes.c_uint32)) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/helpers.py click-0.4.46+16.10.20170607.3/click/tests/helpers.py --- click-0.4.45.1+16.10.20160916/click/tests/helpers.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/helpers.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,357 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# This file contains code from Python 3.3, released under the Python license -# (http://docs.python.org/3/license.html). - -"""Testing helpers.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestCase', - 'mkfile', - 'touch', - ] - - -import contextlib -from functools import wraps -import json -import os -import re -import shutil -import sys -import tempfile -import unittest -try: - from unittest import mock -except ImportError: - import mock - -from gi.repository import Click, GLib - -from click.tests import gimock - - -def disable_logging(func): - """Decorator to disable logging e.g. during a test""" - @wraps(func) - def wrapper(*args, **kwargs): - import logging - logging.disable(logging.CRITICAL) - try: - return func(*args, **kwargs) - finally: - logging.disable(logging.NOTSET) - return wrapper - - -def make_installed_click(db, db_dir, package="test-1", version="1.0", - json_data={}, make_current=True, user="@all"): - """Create a fake installed click package for the given db/db_dir""" - json_data["name"] = package - json_data["version"] = version - with mkfile_utf8(os.path.join( - db_dir, package, version, ".click", "info", - "%s.manifest" % package)) as f: - json.dump(json_data, f, ensure_ascii=False) - if make_current: - os.symlink( - version, os.path.join(db_dir, package, "current")) - if user == "@all": - registry = Click.User.for_all_users(db) - else: - registry = Click.User.for_user(db, user) - registry.set_version(package, version) - return os.path.join(db_dir, package, version) - - -class TestCase(gimock.GIMockTestCase): - def setUp(self): - super(TestCase, self).setUp() - self.temp_dir = None - self.save_env = dict(os.environ) - self.maxDiff = None - - def tearDown(self): - for key in set(os.environ) - set(self.save_env): - del os.environ[key] - for key, value in os.environ.items(): - if value != self.save_env[key]: - os.environ[key] = self.save_env[key] - for key in set(self.save_env) - set(os.environ): - os.environ[key] = self.save_env[key] - - def use_temp_dir(self): - if self.temp_dir is not None: - return self.temp_dir - self.temp_dir = tempfile.mkdtemp(prefix="click") - self.addCleanup(shutil.rmtree, self.temp_dir) - return self.temp_dir - - # Monkey-patch for Python 2/3 compatibility. - if not hasattr(unittest.TestCase, 'assertCountEqual'): - assertCountEqual = unittest.TestCase.assertItemsEqual - if not hasattr(unittest.TestCase, 'assertRegex'): - assertRegex = unittest.TestCase.assertRegexpMatches - # Renamed in Python 3.2 to omit the trailing 'p'. - if not hasattr(unittest.TestCase, 'assertRaisesRegex'): - assertRaisesRegex = unittest.TestCase.assertRaisesRegexp - - def assertRaisesGError(self, domain_name, code, callableObj, - *args, **kwargs): - with self.assertRaises(GLib.GError) as cm: - callableObj(*args, **kwargs) - self.assertEqual(domain_name, cm.exception.domain) - self.assertEqual(code, cm.exception.code) - - def assertRaisesFileError(self, code, callableObj, *args, **kwargs): - self.assertRaisesGError( - "g-file-error-quark", code, callableObj, *args, **kwargs) - - def assertRaisesDatabaseError(self, code, callableObj, *args, **kwargs): - self.assertRaisesGError( - "click_database_error-quark", code, callableObj, *args, **kwargs) - - def assertRaisesFrameworkError(self, code, callableObj, *args, **kwargs): - self.assertRaisesGError( - "click_framework_error-quark", code, callableObj, *args, **kwargs) - - def assertRaisesHooksError(self, code, callableObj, *args, **kwargs): - self.assertRaisesGError( - "click_hooks_error-quark", code, callableObj, *args, **kwargs) - - def assertRaisesQueryError(self, code, callableObj, *args, **kwargs): - self.assertRaisesGError( - "click_query_error-quark", code, callableObj, *args, **kwargs) - - def assertRaisesUserError(self, code, callableObj, *args, **kwargs): - self.assertRaisesGError( - "click_user_error-quark", code, callableObj, *args, **kwargs) - - def _setup_frameworks(self, preloads, frameworks_dir=None, frameworks=[]): - frameworks_dir = self._create_mock_framework_dir(frameworks_dir) - shutil.rmtree(frameworks_dir, ignore_errors=True) - for framework in frameworks: - self._create_mock_framework_file(framework) - preloads["click_get_frameworks_dir"].side_effect = ( - lambda: self.make_string(frameworks_dir)) - - def _create_mock_framework_dir(self, frameworks_dir=None): - if frameworks_dir is None: - frameworks_dir = os.path.join(self.temp_dir, "frameworks") - patcher = mock.patch('click.paths.frameworks_dir', frameworks_dir) - patcher.start() - self.addCleanup(patcher.stop) - Click.ensuredir(frameworks_dir) - return frameworks_dir - - def _create_mock_framework_file(self, framework_name): - self.use_temp_dir() - self._create_mock_framework_dir() - r = r'(?P[a-z]+-sdk)-(?P[0-9.]+)(-[a-z0-9-]+)?' - match = re.match(r, framework_name) - if match is None: - name = "unknown" - ver = "1.0" - else: - name = match.group("name") - ver = match.group("ver") - framework_filename = os.path.join( - self.temp_dir, "frameworks", - "{0}.framework".format(framework_name)) - with open(framework_filename, "w") as f: - f.write("Base-Name: {0}\n".format(name)) - f.write("Base-Version: {0}\n".format(ver)) - - -if not hasattr(mock, "call"): - # mock 0.7.2, the version in Ubuntu 12.04 LTS, lacks mock.ANY and - # mock.call. Since it's so convenient, monkey-patch a partial backport - # (from Python 3.3 unittest.mock) into place here. - class _ANY(object): - "A helper object that compares equal to everything." - - def __eq__(self, other): - return True - - def __ne__(self, other): - return False - - def __repr__(self): - return '' - - mock.ANY = _ANY() - - class _Call(tuple): - """ - A tuple for holding the results of a call to a mock, either in the form - `(args, kwargs)` or `(name, args, kwargs)`. - - If args or kwargs are empty then a call tuple will compare equal to - a tuple without those values. This makes comparisons less verbose:: - - _Call(('name', (), {})) == ('name',) - _Call(('name', (1,), {})) == ('name', (1,)) - _Call(((), {'a': 'b'})) == ({'a': 'b'},) - - The `_Call` object provides a useful shortcut for comparing with call:: - - _Call(((1, 2), {'a': 3})) == call(1, 2, a=3) - _Call(('foo', (1, 2), {'a': 3})) == call.foo(1, 2, a=3) - - If the _Call has no name then it will match any name. - """ - def __new__(cls, value=(), name=None, parent=None, two=False, - from_kall=True): - name = '' - args = () - kwargs = {} - _len = len(value) - if _len == 3: - name, args, kwargs = value - elif _len == 2: - first, second = value - if isinstance(first, str): - name = first - if isinstance(second, tuple): - args = second - else: - kwargs = second - else: - args, kwargs = first, second - elif _len == 1: - value, = value - if isinstance(value, str): - name = value - elif isinstance(value, tuple): - args = value - else: - kwargs = value - - if two: - return tuple.__new__(cls, (args, kwargs)) - - return tuple.__new__(cls, (name, args, kwargs)) - - def __init__(self, value=(), name=None, parent=None, two=False, - from_kall=True): - self.name = name - self.parent = parent - self.from_kall = from_kall - - def __eq__(self, other): - if other is mock.ANY: - return True - try: - len_other = len(other) - except TypeError: - return False - - self_name = '' - if len(self) == 2: - self_args, self_kwargs = self - else: - self_name, self_args, self_kwargs = self - - other_name = '' - if len_other == 0: - other_args, other_kwargs = (), {} - elif len_other == 3: - other_name, other_args, other_kwargs = other - elif len_other == 1: - value, = other - if isinstance(value, tuple): - other_args = value - other_kwargs = {} - elif isinstance(value, str): - other_name = value - other_args, other_kwargs = (), {} - else: - other_args = () - other_kwargs = value - else: - # len 2 - # could be (name, args) or (name, kwargs) or (args, kwargs) - first, second = other - if isinstance(first, str): - other_name = first - if isinstance(second, tuple): - other_args, other_kwargs = second, {} - else: - other_args, other_kwargs = (), second - else: - other_args, other_kwargs = first, second - - if self_name and other_name != self_name: - return False - - # this order is important for ANY to work! - return (other_args, other_kwargs) == (self_args, self_kwargs) - - def __ne__(self, other): - return not self.__eq__(other) - - def __call__(self, *args, **kwargs): - if self.name is None: - return _Call(('', args, kwargs), name='()') - - name = self.name + '()' - return _Call((self.name, args, kwargs), name=name, parent=self) - - def __getattr__(self, attr): - if self.name is None: - return _Call(name=attr, from_kall=False) - name = '%s.%s' % (self.name, attr) - return _Call(name=name, parent=self, from_kall=False) - - mock.call = _Call(from_kall=False) - - -@contextlib.contextmanager -def mkfile(path, mode="w"): - Click.ensuredir(os.path.dirname(path)) - with open(path, mode) as f: - yield f - - -@contextlib.contextmanager -def mkfile_utf8(path, mode="w"): - Click.ensuredir(os.path.dirname(path)) - if sys.version < "3": - import codecs - with codecs.open(path, mode, "UTF-8") as f: - yield f - else: - # io.open is available from Python 2.6, but we only use it with - # Python 3 because it raises exceptions when passed bytes. - import io - with io.open(path, mode, encoding="UTF-8") as f: - yield f - - -def touch(path): - with mkfile(path, mode="a"): - pass - - -def make_file_with_content(filename, content, mode=0o644): - """Create a file with the given content and mode""" - Click.ensuredir(os.path.dirname(filename)) - with open(filename, "w") as f: - f.write(content) - os.chmod(filename, mode) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/__init__.py click-0.4.46+16.10.20170607.3/click/tests/__init__.py --- click-0.4.45.1+16.10.20160916/click/tests/__init__.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -from __future__ import print_function - -import os -import sys - -from click.tests import config - - -def _append_env_path(envname, value): - if envname in os.environ: - if value in os.environ[envname].split(":"): - return False - os.environ[envname] = "%s:%s" % (os.environ[envname], value) - else: - os.environ[envname] = value - return True - - -def get_executable(): - """Get python executable (respecting if python-coverage was used)""" - coverage_executable = sys.executable+"-coverage" - if "coverage" in sys.modules and os.path.isfile(coverage_executable): - return [coverage_executable, "run", "-p"] - return [sys.executable] - - -# Don't do any of this in interactive mode. -if not hasattr(sys, "ps1"): - _lib_click_dir = os.path.join(config.abs_top_builddir, "lib", "click") - changed = False - if _append_env_path( - "LD_LIBRARY_PATH", os.path.join(_lib_click_dir, ".libs")): - changed = True - if _append_env_path("GI_TYPELIB_PATH", _lib_click_dir): - changed = True - if changed: - coverage_executable = get_executable() - # We have to re-exec ourselves to get the dynamic loader to pick up - # the new value of LD_LIBRARY_PATH. - if "-m unittest" in sys.argv[0]: - # unittest does horrible things to sys.argv in the name of - # "usefulness", making the re-exec more painful than it needs to - # be. - os.execvp( - coverage_executable[0], coverage_executable + ["-m", "unittest"] + sys.argv[1:]) - else: - os.execvp(coverage_executable[0], coverage_executable + sys.argv) - os._exit(1) Binary files /tmp/tmpXhNPZ8/_HuUQal5dm/click-0.4.45.1+16.10.20160916/click/tests/integration/data/evil-keyring/pubring.gpg and /tmp/tmpXhNPZ8/x6Bc0lgh6E/click-0.4.46+16.10.20170607.3/click/tests/integration/data/evil-keyring/pubring.gpg differ Binary files /tmp/tmpXhNPZ8/_HuUQal5dm/click-0.4.45.1+16.10.20160916/click/tests/integration/data/evil-keyring/secring.gpg and /tmp/tmpXhNPZ8/x6Bc0lgh6E/click-0.4.46+16.10.20170607.3/click/tests/integration/data/evil-keyring/secring.gpg differ Binary files /tmp/tmpXhNPZ8/_HuUQal5dm/click-0.4.45.1+16.10.20160916/click/tests/integration/data/evil-keyring/trustdb.gpg and /tmp/tmpXhNPZ8/x6Bc0lgh6E/click-0.4.46+16.10.20170607.3/click/tests/integration/data/evil-keyring/trustdb.gpg differ Binary files /tmp/tmpXhNPZ8/_HuUQal5dm/click-0.4.45.1+16.10.20160916/click/tests/integration/data/origin-keyring/pubring.gpg and /tmp/tmpXhNPZ8/x6Bc0lgh6E/click-0.4.46+16.10.20170607.3/click/tests/integration/data/origin-keyring/pubring.gpg differ Binary files /tmp/tmpXhNPZ8/_HuUQal5dm/click-0.4.45.1+16.10.20160916/click/tests/integration/data/origin-keyring/secring.gpg and /tmp/tmpXhNPZ8/x6Bc0lgh6E/click-0.4.46+16.10.20170607.3/click/tests/integration/data/origin-keyring/secring.gpg differ Binary files /tmp/tmpXhNPZ8/_HuUQal5dm/click-0.4.45.1+16.10.20160916/click/tests/integration/data/origin-keyring/trustdb.gpg and /tmp/tmpXhNPZ8/x6Bc0lgh6E/click-0.4.46+16.10.20170607.3/click/tests/integration/data/origin-keyring/trustdb.gpg differ diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/helpers.py click-0.4.46+16.10.20170607.3/click/tests/integration/helpers.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/helpers.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/helpers.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,124 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests helper for the click CLI interface.""" - -import contextlib -import glob -import json -import os -import random -import shutil -import string -import subprocess -import tempfile -import unittest - - -def require_root(): - if os.getuid() != 0: - raise unittest.SkipTest("This test needs to run as root") - - -def require_network(): - try: - if subprocess.call(["ping", "-c1", "archive.ubuntu.com"]) != 0: - raise unittest.SkipTest("Need network") - except Exception: - pass - - -@contextlib.contextmanager -def chdir(target): - curdir = os.getcwd() - os.chdir(target) - try: - yield - finally: - os.chdir(curdir) - - -def cmdline_for_user(username): - """Helper to get the click commandline for the given username""" - if username == "@all": - user = "--all-users" - else: - user = "--user=%s" % username - return user - - -class ClickTestCase(unittest.TestCase): - - @classmethod - def setUpClass(cls): - if "TEST_INTEGRATION" not in os.environ: - raise unittest.SkipTest("Skipping integration tests") - cls.click_binary = os.environ.get("CLICK_BINARY", "/usr/bin/click") - - def setUp(self): - super(ClickTestCase, self).setUp() - self.temp_dir = tempfile.mkdtemp() - - def tearDown(self): - super(ClickTestCase, self).tearDown() - # we force the cleanup before removing the tempdir so that stuff - # in temp_dir is still available - self.doCleanups() - shutil.rmtree(self.temp_dir) - - def click_install(self, path_to_click, click_name, username, - allow_unauthenticated=True): - cmd = [self.click_binary, "install", cmdline_for_user(username)] - if allow_unauthenticated: - cmd.append("--allow-unauthenticated") - cmd.append(path_to_click) - subprocess.check_call(cmd) - self.addCleanup(self.click_unregister, click_name, username) - - def click_unregister(self, click_name, username): - subprocess.check_call( - [self.click_binary, "unregister", cmdline_for_user(username), - click_name]) - - def _create_manifest(self, target, name, version, framework, hooks={}): - with open(target, "w") as f: - json.dump({ - 'name': name, - 'version': str(version), - 'maintainer': 'Foo Bar ', - 'title': 'test title', - 'framework': framework, - 'hooks': hooks, - }, f) - - def _make_click(self, name=None, version=1.0, - framework="ubuntu-sdk-13.10", hooks={}): - if name is None: - name = "com.example.%s" % "".join( - random.choice(string.ascii_lowercase) for i in range(10)) - tmpdir = tempfile.mkdtemp() - self.addCleanup(lambda: shutil.rmtree(tmpdir)) - clickdir = os.path.join(tmpdir, name) - os.makedirs(clickdir) - self._create_manifest(os.path.join(clickdir, "manifest.json"), - name, version, framework, hooks) - with open(os.path.join(clickdir, "README"), "w") as f: - f.write("hello world!") - with chdir(tmpdir), open(os.devnull, "w") as devnull: - subprocess.call( - [self.click_binary, "build", clickdir], stdout=devnull) - generated_clicks = glob.glob(os.path.join(tmpdir, "*.click")) - self.assertEqual(len(generated_clicks), 1) - return generated_clicks[0] diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/test_build_core_apps.py click-0.4.46+16.10.20170607.3/click/tests/integration/test_build_core_apps.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/test_build_core_apps.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/test_build_core_apps.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,131 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click chroot feature.""" - -from glob import glob -import json -import os -import shutil -import subprocess - -from six import with_metaclass - -from .helpers import ( - chdir, - require_network, - require_root, - ClickTestCase, -) - -# the branches we want to testbuild -TEST_BUILD_BRANCHES = [ - "lp:camera-app", - "lp:notes-app", -] - -# command to "configure" -CORE_APP_CONFIGURE_CMD = [ - "cmake", "..", "-DCLICK_MODE=on", "-DINSTALL_TESTS=off"] - -# command to make install -CLICK_TARGET_DIR = "click-package" -CORE_APP_MAKE_CMD = [ - "make", "DESTDIR=%s" % CLICK_TARGET_DIR, "install"] - -# architectures with native- or cross-compiling support for armhf -ALLOW_ARCHITECTURES = ["amd64", "arm64", "armhf", "i386"] - - -def find_manifest(start_dir): - """Find a click manifest.json{,.in} under the given directory""" - for path, dirs, files in os.walk(start_dir): - for needle in ["manifest.json", "manifest.json.in"]: - if needle in files: - return os.path.join(path, needle) - return None - - -class AddBranchTestFunctions(type): - """Metaclass that creates one test for each branch""" - def __new__(cls, name, bases, dct): - for branch in TEST_BUILD_BRANCHES: - name = "test_build_%s" % branch.split(":")[1].replace("-", "_") - dct[name] = lambda self: self._testbuild_branch(branch) - return type.__new__(cls, name, bases, dct) - - -class TestBuildCoreApps(with_metaclass(AddBranchTestFunctions, ClickTestCase)): - - @classmethod - def setUpClass(cls): - super(TestBuildCoreApps, cls).setUpClass() - require_root() - require_network() - - def _run_in_chroot(self, cmd): - """Run the given cmd in a click chroot""" - return subprocess.check_call(self.chroot_cmd + ["run"] + cmd) - - def _set_arch_and_framework_from_manifest(self, manifest): - with open(manifest) as f: - data = json.load(f) - self.arch = data["architecture"] - self.framework = data["framework"] - - @property - def chroot_cmd(self): - return [ - self.click_binary, "chroot", "-a", self.arch, "-f", self.framework] - - def _ensure_click_chroot(self): - if subprocess.call(self.chroot_cmd + ["exists"]) != 0: - subprocess.check_call(self.chroot_cmd + ["create"]) - - def configure(self): - self._run_in_chroot(CORE_APP_CONFIGURE_CMD) - - def make(self): - self._run_in_chroot(CORE_APP_MAKE_CMD) - - def create_click(self): - subprocess.check_call( - [self.click_binary, "build", CLICK_TARGET_DIR]) - # we expect exactly one click - self.assertEqual(len(glob("*.click")), 1) - - def _testbuild_branch(self, branch): - system_arch = subprocess.check_output( - ["dpkg", "--print-architecture"], universal_newlines=True).strip() - if system_arch not in ALLOW_ARCHITECTURES: - self.skipTest("%s has no armhf build support" % system_arch) - # get and parse - branch_dir = branch[len("lp:"):] - build_dir = os.path.join(branch_dir, "build-tree") - if os.path.exists(branch_dir): - subprocess.check_call(["bzr", "pull"], cwd=branch_dir) - else: - subprocess.check_call(["bzr", "branch", branch]) - manifest = find_manifest(branch_dir) - # build it - self._set_arch_and_framework_from_manifest(manifest) - if os.path.exists(build_dir): - shutil.rmtree(build_dir) - os.makedirs(build_dir) - with chdir(build_dir): - self._ensure_click_chroot() - self.configure() - self.make() - self.create_click() diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/test_build.py click-0.4.46+16.10.20170607.3/click/tests/integration/test_build.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/test_build.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/test_build.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI build command.""" - -import os - -from .helpers import ClickTestCase - - -class TestBuild(ClickTestCase): - def test_build(self): - path_to_click = self._make_click() - self.assertTrue(os.path.exists(path_to_click)) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/test_buildsource.py click-0.4.46+16.10.20170607.3/click/tests/integration/test_buildsource.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/test_buildsource.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/test_buildsource.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI buildsource command.""" - -import os -import shutil -import subprocess -import tarfile -import tempfile - -from .helpers import ( - chdir, - ClickTestCase, -) - - -class TestBuildSource(ClickTestCase): - - def test_buildsource(self): - temp_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, temp_dir) - with chdir(temp_dir): - with open(os.path.join(temp_dir, "README"), "w") as f: - f.write("I'm a source package") - os.mkdir(os.path.join(temp_dir, ".git")) - os.mkdir(os.path.join(temp_dir, ".bzr")) - os.mkdir(os.path.join(temp_dir, ".normal")) - self._create_manifest(os.path.join(temp_dir, "manifest.json"), - "srcfoo", "1.2", "ubuntu-sdk-13.10") - subprocess.check_call( - [self.click_binary, "buildsource", temp_dir], - universal_newlines=True) - # ensure we have the content we expect - source_file = "srcfoo_1.2.tar.gz" - tar = tarfile.open(source_file) - self.assertEqual( - sorted(tar.getnames()), - sorted([".", "./.normal", "./manifest.json", "./README"])) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/test_chroot.py click-0.4.46+16.10.20170607.3/click/tests/integration/test_chroot.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/test_chroot.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/test_chroot.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,100 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click chroot feature.""" - -import subprocess -import unittest - -from .helpers import ( - require_network, - require_root, - ClickTestCase, -) - -# architectures present in 14.04 (current default framework) -ALLOW_ARCHITECTURES = [ - "amd64", "arm64", "armhf", "i386", "powerpc", "ppc64el"] - - -def skipUnlessAllowedArchitecture(): - system_arch = subprocess.check_output( - ["dpkg", "--print-architecture"], universal_newlines=True).strip() - if system_arch in ALLOW_ARCHITECTURES: - return lambda func: func - else: - return unittest.skip("%s does not exist in 14.04") - - -@skipUnlessAllowedArchitecture() -class TestChroot(ClickTestCase): - - @classmethod - def command(cls, arch, *args): - return [cls.click_binary, "chroot", "-a", arch] + list(args) - - @classmethod - def setUpClass(cls): - super(TestChroot, cls).setUpClass() - require_root() - require_network() - cls.arch = subprocess.check_output( - ["dpkg", "--print-architecture"], universal_newlines=True).strip() - subprocess.check_call(cls.command(cls.arch, "create")) - - @classmethod - def tearDownClass(cls): - subprocess.check_call(cls.command(cls.arch, "destroy")) - - def test_upgrade(self): - subprocess.check_call(self.command(self.arch, "upgrade")) - - def test_install(self): - subprocess.check_call(self.command(self.arch, "install", "apt-utils")) - - def test_run(self): - output = subprocess.check_output( - self.command(self.arch, "run", "echo", "hello world"), - universal_newlines=True) - self.assertEqual(output, "hello world\n") - - def test_maint(self): - output = subprocess.check_output( - self.command(self.arch, "maint", "id"), - universal_newlines=True) - self.assertEqual(output, "uid=0(root) gid=0(root) groups=0(root)\n") - - def test_exists_ok(self): - subprocess.check_call(self.command(self.arch, "exists")) - - def test_exists_no(self): - with self.assertRaises(subprocess.CalledProcessError): - subprocess.check_call( - self.command("arch-that-does-not-exist", "exists")) - - -class TestChrootName(TestChroot): - """Run the chroot tests again with a different --name.""" - - @classmethod - def command(cls, arch, *args): - return super(TestChrootName, cls).command( - arch, "-n", "testname", *args) - - def test_exists_different_name_fails(self): - # "click chroot exists" fails for a non-existent name. - with self.assertRaises(subprocess.CalledProcessError): - subprocess.check_call(super(TestChrootName, self).command( - self.arch, "-n", "testname2", "exists")) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/test_contents.py click-0.4.46+16.10.20170607.3/click/tests/integration/test_contents.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/test_contents.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/test_contents.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,33 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI contents command.""" - -import re -import subprocess - -from .helpers import ClickTestCase - - -class TestContents(ClickTestCase): - def test_contents(self): - name = "com.example.contents" - path_to_click = self._make_click(name) - output = subprocess.check_output([ - self.click_binary, "contents", path_to_click], - universal_newlines=True) - self.assertTrue(re.search( - r'-rw-r[-w]-r-- root/root\s+[0-9]+\s+[0-9-]+ [0-9:]+ ./README', - output)) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/test_frameworks.py click-0.4.46+16.10.20170607.3/click/tests/integration/test_frameworks.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/test_frameworks.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/test_frameworks.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI frameworks command.""" - -import os -import subprocess - -from .helpers import ClickTestCase - - -class TestFrameworks(ClickTestCase): - def setUp(self): - super(TestFrameworks, self).setUp() - if (not os.path.exists("/usr/share/click/frameworks") or - not os.listdir("/usr/share/click/frameworks")): - self.skipTest("Please install ubuntu-sdk-libs") - - def test_framework_list(self): - output = subprocess.check_output([ - self.click_binary, "framework", "list"], universal_newlines=True) - self.assertTrue("ubuntu-sdk-" in output) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/test_hook.py click-0.4.46+16.10.20170607.3/click/tests/integration/test_hook.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/test_hook.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/test_hook.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click hook feature.""" - -import os -import subprocess -from textwrap import dedent - -from .helpers import ( - ClickTestCase, - require_root, -) - - -class TestHook(ClickTestCase): - - @classmethod - def setUpClass(cls): - super(TestHook, cls).setUpClass() - require_root() - - def _make_hook(self, name): - hook_fname = "/usr/share/click/hooks/%s.hook" % name - canary_fname = os.path.join(self.temp_dir, "canary.sh") - canary_log = os.path.join(self.temp_dir, "canary.log") - with open(hook_fname, "w") as f: - f.write(dedent("""\ - Pattern: ${home}/${id}.test-hook - User-Level: yes - Exec: %s - Hook-Name: %s - """ % (canary_fname, name))) - with open(canary_fname, "w") as f: - f.write(dedent("""\ - #!/bin/sh - echo "i-hook-you-up" >> %s - """ % canary_log)) - os.chmod(canary_fname, 0o755) - return hook_fname, canary_log - - def test_hook_install_user(self): - # build/install the hook - hook_name = "clicktesthook" - hook_file, hook_log = self._make_hook(hook_name) - self.addCleanup(os.unlink, hook_file) - subprocess.check_call( - [self.click_binary, "hook", "install", hook_name]) - self.addCleanup( - subprocess.check_call, [self.click_binary, "hook", "remove", - hook_name]) - # make click that uses the hook - hooks = {'app1': {hook_name: 'README'}} - click_pkg_name = "com.example.hook-1" - click_pkg = self._make_click( - click_pkg_name, framework="", hooks=hooks) - user = os.environ.get("USER", "root") - self.click_install(click_pkg, click_pkg_name, user) - # ensure we have the hook - generated_hook_file = os.path.expanduser( - "~/com.example.hook-1_app1_1.0.test-hook") - self.assertTrue(os.path.exists(generated_hook_file)) - self.assertEqual( - os.path.realpath(generated_hook_file), - "/opt/click.ubuntu.com/com.example.hook-1/1.0/README") - with open(hook_log) as f: - hook_log_content = f.read().strip() - self.assertEqual("i-hook-you-up", hook_log_content) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/test_info.py click-0.4.46+16.10.20170607.3/click/tests/integration/test_info.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/test_info.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/test_info.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI info command.""" - -import json -import os -import subprocess - -from .helpers import ClickTestCase - - -class TestInfo(ClickTestCase): - def test_info_from_path(self): - name = "com.example.foo" - path_to_click = self._make_click(name) - output = subprocess.check_output([ - self.click_binary, "info", path_to_click], universal_newlines=True) - self.assertEqual(name, json.loads(output)["name"]) - - def test_info_installed_click(self): - name = "com.example.foo" - user = os.environ.get("USER", "root") - path_to_click = self._make_click(name, framework="") - self.click_install(path_to_click, name, user) - output = subprocess.check_output([ - self.click_binary, "info", name], universal_newlines=True) - self.assertEqual(json.loads(output)["name"], name) - - def test_info_file_in_package(self): - name = "org.example.info" - version = "1.0" - click_pkg = self._make_click(name=name, version=version, framework="") - subprocess.check_call( - [self.click_binary, "install", "--allow-unauthenticated", - "--all-users", click_pkg]) - self.addCleanup( - subprocess.check_call, - [self.click_binary, "unregister", "--all-users", name]) - output = subprocess.check_output( - [self.click_binary, "info", - "/opt/click.ubuntu.com/%s/%s/README" % (name, version)], - universal_newlines=True) - self.assertEqual(name, json.loads(output)["name"]) - - def test_info_different_extension(self): - name = "org.example.info" - raw_path = self._make_click(name) - path = "%s.extra" % raw_path - os.rename(raw_path, path) - output = subprocess.check_output([ - self.click_binary, "info", path], universal_newlines=True) - self.assertEqual(name, json.loads(output)["name"]) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/test_install.py click-0.4.46+16.10.20170607.3/click/tests/integration/test_install.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/test_install.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/test_install.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,118 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click install feature.""" - -import subprocess - -from .helpers import ( - require_root, - ClickTestCase, -) - - -def add_user(name): - subprocess.check_call(["useradd", name]) - return name - - -def del_user(name): - subprocess.check_call(["userdel", "-r", name]) - - -class TestClickInstall(ClickTestCase): - - @classmethod - def setUpClass(cls): - super(TestClickInstall, cls).setUpClass() - require_root() - cls.USER_1 = add_user("click-test-user-1") - cls.USER_2 = add_user("click-test-user-2") - - @classmethod - def tearDownClass(cls): - super(TestClickInstall, cls).tearDownClass() - del_user(cls.USER_1) - del_user(cls.USER_2) - - def test_install_for_single_user(self): - name = "foo-1" - click_pkg = self._make_click(name=name, framework="") - # install it - self.click_install(click_pkg, name, self.USER_1) - # ensure that user-1 has it - output = subprocess.check_output([ - "sudo", "-u", self.USER_1, - self.click_binary, "list"], universal_newlines=True) - self.assertEqual(output, "%s\t1.0\n" % name) - # but not user-2 - output = subprocess.check_output([ - "sudo", "-u", self.USER_2, - self.click_binary, "list"], universal_newlines=True) - self.assertEqual(output, "") - # and that we can see it with the --user option - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.USER_1], - universal_newlines=True) - self.assertEqual(output, "%s\t1.0\n" % name) - - def test_install_for_single_user_and_register(self): - name = "foo-1" - click_pkg = self._make_click(name=name, framework="") - self.click_install(click_pkg, name, self.USER_1) - # not available for user2 - output = subprocess.check_output([ - "sudo", "-u", self.USER_2, - self.click_binary, "list"], universal_newlines=True) - self.assertEqual(output, "") - # register it - subprocess.check_call( - [self.click_binary, "register", "--user=%s" % self.USER_2, - name, "1.0", ]) - self.addCleanup(self.click_unregister, name, self.USER_2) - # and ensure its available for user2 - output = subprocess.check_output([ - "sudo", "-u", self.USER_2, - self.click_binary, "list"], universal_newlines=True) - self.assertEqual(output, "%s\t1.0\n" % name) - - def test_install_for_all_users(self): - name = "foo-2" - click_pkg = self._make_click(name=name, framework="") - self.click_install(click_pkg, name, "@all") - # ensure all users see it - for user in (self.USER_1, self.USER_2): - output = subprocess.check_output( - ["sudo", "-u", user, self.click_binary, "list"], - universal_newlines=True) - self.assertEqual(output, "%s\t1.0\n" % name) - - def test_pkgdir_after_install(self): - name = "foo-3" - click_pkg = self._make_click(name=name, version="1.2", framework="") - self.click_install(click_pkg, name, "@all") - # from the path - output = subprocess.check_output( - [self.click_binary, "pkgdir", - "/opt/click.ubuntu.com/%s/1.2/README" % name], - universal_newlines=True).strip() - self.assertEqual(output, "/opt/click.ubuntu.com/%s/1.2" % name) - # now test from the click package name - output = subprocess.check_output( - [self.click_binary, "pkgdir", name], - universal_newlines=True).strip() - # note that this is different from above - self.assertEqual( - output, "/opt/click.ubuntu.com/.click/users/@all/%s" % name) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/test_list.py click-0.4.46+16.10.20170607.3/click/tests/integration/test_list.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/test_list.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/test_list.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,33 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI list command.""" - -import os -import subprocess - -from .helpers import ClickTestCase - - -class TestList(ClickTestCase): - def test_list_simple(self): - name = "com.ubuntu.verify-ok" - path_to_click = self._make_click(name, framework="") - user = os.environ.get("USER", "root") - self.click_install(path_to_click, name, user) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % user], - universal_newlines=True) - self.assertIn(name, output) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/test_signatures.py click-0.4.46+16.10.20170607.3/click/tests/integration/test_signatures.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/test_signatures.py 2016-09-16 17:46:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/test_signatures.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,389 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click signature checking.""" - -import copy -import os -import shutil -import subprocess -import tarfile -from textwrap import dedent - -import apt - -from click import osextras -from .helpers import ( - require_root, - ClickTestCase, -) - - -def makedirs(path): - try: - os.makedirs(path) - except OSError: - pass - - -def get_keyid_from_gpghome(gpg_home): - """Return the public keyid of a given gpg home dir""" - output = subprocess.check_output( - ["gpg", "--home", gpg_home, "--list-keys", "--with-colons"], - universal_newlines=True) - for line in output.splitlines(): - if not line.startswith("pub:"): - continue - return line.split(":")[4] - raise ValueError("Cannot find public key in output: '%s'" % output) - - -class Debsigs: - """Tiny wrapper around the debsigs CLI""" - def __init__(self, gpghome, keyid): - self.keyid = keyid - self.gpghome = gpghome - self.policy = "/etc/debsig/policies/%s/generic.pol" % self.keyid - - def sign(self, filepath, signature_type="origin"): - """Sign the click at filepath""" - env = copy.copy(os.environ) - env["GNUPGHOME"] = os.path.abspath(self.gpghome) - try: - subprocess.check_call( - ["debsigs", - "--sign=%s" % signature_type, - "--default-key=%s" % self.keyid, - filepath], env=env) - finally: - if osextras.find_on_path("gpgconf"): - subprocess.call(["gpgconf", "--kill", "gpg-agent"]) - - def install_signature_policy(self): - """Install/update the system-wide signature policy""" - if apt.Cache()["debsig-verify"].installed >= "0.15": - debsig_xmlns = "https://www.debian.org/debsig/1.0/" - else: - debsig_xmlns = "http://www.debian.org/debsig/1.0/" - xmls = dedent("""\ - - - - - - - - - - - - - - """.format( - debsig_xmlns=debsig_xmlns, keyid=self.keyid, - filename="origin.pub")) - makedirs(os.path.dirname(self.policy)) - with open(self.policy, "w") as f: - f.write(xmls) - self.pubkey_path = ( - "/usr/share/debsig/keyrings/%s/origin.pub" % self.keyid) - makedirs(os.path.dirname(self.pubkey_path)) - shutil.copy( - os.path.join(self.gpghome, "pubring.gpg"), self.pubkey_path) - - def uninstall_signature_policy(self): - # FIXME: update debsig-verify so that it can work from a different - # root than "/" so that the tests do not have to use the - # system root - os.remove(self.policy) - os.remove(self.pubkey_path) - - -class ClickSignaturesTestCase(ClickTestCase): - - @classmethod - def setUpClass(cls): - super(ClickSignaturesTestCase, cls).setUpClass() - require_root() - - def assertClickNoSignatureError(self, cmd_args): - with self.assertRaises(subprocess.CalledProcessError) as cm: - output = subprocess.check_output( - [self.click_binary] + cmd_args, - stderr=subprocess.STDOUT, universal_newlines=True) - output = cm.exception.output - expected_error_message = ("debsig: Origin Signature check failed. " - "This deb might not be signed.") - self.assertIn(expected_error_message, output) - - def assertClickInvalidSignatureError(self, cmd_args): - with self.assertRaises(subprocess.CalledProcessError) as cm: - output = subprocess.check_output( - [self.click_binary] + cmd_args, - stderr=subprocess.STDOUT, universal_newlines=True) - print(output) - - output = cm.exception.output - expected_error_message = "Signature verification error: " - self.assertIn(expected_error_message, output) - - -class TestSignatureVerificationNoSignature(ClickSignaturesTestCase): - - @classmethod - def setUpClass(cls): - super(TestSignatureVerificationNoSignature, cls).setUpClass() - require_root() - - def test_debsig_verify_no_sig(self): - name = "org.example.debsig-no-sig" - path_to_click = self._make_click(name, framework="") - self.assertClickNoSignatureError(["verify", path_to_click]) - - def test_debsig_install_no_sig(self): - name = "org.example.debsig-no-sig" - path_to_click = self._make_click(name, framework="") - self.assertClickNoSignatureError(["install", path_to_click]) - - def test_debsig_install_can_install_with_sig_override(self): - name = "org.example.debsig-no-sig" - path_to_click = self._make_click(name, framework="") - user = os.environ.get("USER", "root") - subprocess.check_call( - [self.click_binary, "install", - "--allow-unauthenticated", "--user=%s" % user, - path_to_click]) - self.addCleanup( - subprocess.call, [self.click_binary, "unregister", - "--user=%s" % user, name]) - - -class TestSignatureVerification(ClickSignaturesTestCase): - - @classmethod - def setUpClass(cls): - super(TestSignatureVerification, cls).setUpClass() - require_root() - - def setUp(self): - super(TestSignatureVerification, self).setUp() - self.user = os.environ.get("USER", "root") - # the valid origin keyring - self.datadir = os.path.join(os.path.dirname(__file__), "data") - origin_keyring_dir = os.path.abspath( - os.path.join(self.datadir, "origin-keyring")) - gpghome = self.make_gpghome(origin_keyring_dir) - keyid = get_keyid_from_gpghome(gpghome) - self.debsigs = Debsigs(gpghome, keyid) - self.debsigs.install_signature_policy() - - def tearDown(self): - self.debsigs.uninstall_signature_policy() - - def make_gpghome(self, source): - gpghome = os.path.join(self.temp_dir, "gnupg") - if os.path.exists(gpghome): - shutil.rmtree(gpghome) - shutil.copytree(source, gpghome) - os.chmod(gpghome, 0o700) - return gpghome - - def test_debsig_install_valid_signature(self): - name = "org.example.debsig-valid-sig" - path_to_click = self._make_click(name, framework="") - self.debsigs.sign(path_to_click) - subprocess.call(["cp", path_to_click, os.path.join("/home/cjwatson/src/ubuntu/click/click", os.path.basename(path_to_click))]) - subprocess.check_call( - [self.click_binary, "install", - "--user=%s" % self.user, - path_to_click]) - self.addCleanup( - subprocess.call, [self.click_binary, "unregister", - "--user=%s" % self.user, name]) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertIn(name, output) - - def test_debsig_install_signature_not_in_keyring(self): - name = "org.example.debsig-no-keyring-sig" - path_to_click = self._make_click(name, framework="") - evil_keyring_dir = os.path.join(self.datadir, "evil-keyring") - gpghome = self.make_gpghome(evil_keyring_dir) - keyid = get_keyid_from_gpghome(gpghome) - debsig_bad = Debsigs(gpghome, keyid) - debsig_bad.sign(path_to_click) - # and ensure its really not there - self.assertClickInvalidSignatureError(["install", path_to_click]) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertNotIn(name, output) - - def test_debsig_install_not_a_signature(self): - name = "org.example.debsig-invalid-sig" - path_to_click = self._make_click(name, framework="") - invalid_sig = os.path.join(self.temp_dir, "_gpgorigin") - with open(invalid_sig, "w") as f: - f.write("no-valid-signature") - # add a invalid sig - subprocess.check_call(["ar", "-r", path_to_click, invalid_sig]) - self.assertClickInvalidSignatureError(["install", path_to_click]) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertNotIn(name, output) - - def test_debsig_install_signature_altered_click(self): - def modify_ar_member(member): - subprocess.check_call( - ["ar", "-x", path_to_click, "control.tar.gz"], - cwd=self.temp_dir) - altered_member = os.path.join(self.temp_dir, member) - with open(altered_member, "ba") as f: - f.write(b"\0") - subprocess.check_call(["ar", "-r", path_to_click, altered_member]) - - # ensure that all members we care about are checked by debsig-verify - for member in ["control.tar.gz", "data.tar.gz", "debian-binary"]: - name = "org.example.debsig-altered-click" - path_to_click = self._make_click(name, framework="") - self.debsigs.sign(path_to_click) - modify_ar_member(member) - self.assertClickInvalidSignatureError(["install", path_to_click]) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertNotIn(name, output) - - def make_nasty_data_tar(self, compression): - new_data_tar = os.path.join(self.temp_dir, "data.tar." + compression) - evilfile = os.path.join(self.temp_dir, "README.evil") - with open(evilfile, "w") as f: - f.write("I am a nasty README") - with tarfile.open(new_data_tar, "w:"+compression) as tar: - tar.add(evilfile) - return new_data_tar - - def test_debsig_install_signature_injected_data_tar(self): - name = "org.example.debsig-injected-data-click" - path_to_click = self._make_click(name, framework="") - self.debsigs.sign(path_to_click) - new_data = self.make_nasty_data_tar("bz2") - # insert before the real data.tar.gz and ensure this is caught - # NOTE: that right now this will not be caught by debsig-verify - # but later in audit() by debian.debfile.DebFile() - subprocess.check_call(["ar", - "-r", - "-b", "data.tar.gz", - path_to_click, - new_data]) - output = subprocess.check_output( - ["ar", "-t", path_to_click], universal_newlines=True) - self.assertEqual(output.splitlines(), - ["debian-binary", - "_click-binary", - "control.tar.gz", - "data.tar.bz2", - "data.tar.gz", - "_gpgorigin"]) - with self.assertRaises(subprocess.CalledProcessError): - output = subprocess.check_output( - [self.click_binary, "install", path_to_click], - stderr=subprocess.STDOUT, universal_newlines=True) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertNotIn(name, output) - - def test_debsig_install_signature_replaced_data_tar(self): - name = "org.example.debsig-replaced-data-click" - path_to_click = self._make_click(name, framework="") - self.debsigs.sign(path_to_click) - new_data = self.make_nasty_data_tar("bz2") - # replace data.tar.gz with data.tar.bz2 and ensure this is caught - subprocess.check_call(["ar", - "-d", - path_to_click, - "data.tar.gz", - ]) - subprocess.check_call(["ar", - "-r", - path_to_click, - new_data]) - output = subprocess.check_output( - ["ar", "-t", path_to_click], universal_newlines=True) - self.assertEqual(output.splitlines(), - ["debian-binary", - "_click-binary", - "control.tar.gz", - "_gpgorigin", - "data.tar.bz2", - ]) - with self.assertRaises(subprocess.CalledProcessError) as cm: - output = subprocess.check_output( - [self.click_binary, "install", path_to_click], - stderr=subprocess.STDOUT, universal_newlines=True) - self.assertIn("Signature verification error", cm.exception.output) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertNotIn(name, output) - - def test_debsig_install_signature_prepend_sig(self): - # this test is probably not really needed, it tries to trick - # the system by prepending a valid signature that is not - # in the keyring. But given that debsig-verify only reads - # the first packet of any given _gpg$foo signature it's - # equivalent to test_debsig_install_signature_not_in_keyring test - name = "org.example.debsig-replaced-data-prepend-sig-click" - path_to_click = self._make_click(name, framework="") - self.debsigs.sign(path_to_click) - new_data = self.make_nasty_data_tar("gz") - # replace data.tar.gz - subprocess.check_call(["ar", - "-r", - path_to_click, - new_data, - ]) - # get previous good _gpgorigin for the old data - subprocess.check_call( - ["ar", "-x", path_to_click, "_gpgorigin"], cwd=self.temp_dir) - with open(os.path.join(self.temp_dir, "_gpgorigin"), "br") as f: - good_gpg_origin = f.read() - # and append a valid signature from a non-keyring key - evil_keyring_dir = os.path.join(self.datadir, "evil-keyring") - gpghome = self.make_gpghome(evil_keyring_dir) - debsig_bad = Debsigs(gpghome, "18B38B9AC1B67A0D") - debsig_bad.sign(path_to_click) - subprocess.check_call( - ["ar", "-x", path_to_click, "_gpgorigin"], cwd=self.temp_dir) - with open(os.path.join(self.temp_dir, "_gpgorigin"), "br") as f: - evil_gpg_origin = f.read() - with open(os.path.join(self.temp_dir, "_gpgorigin"), "wb") as f: - f.write(evil_gpg_origin) - f.write(good_gpg_origin) - subprocess.check_call( - ["ar", "-r", path_to_click, "_gpgorigin"], cwd=self.temp_dir) - # now ensure that the verification fails as well - with self.assertRaises(subprocess.CalledProcessError) as cm: - output = subprocess.check_output( - [self.click_binary, "install", path_to_click], - stderr=subprocess.STDOUT, universal_newlines=True) - self.assertIn("Signature verification error", cm.exception.output) - output = subprocess.check_output( - [self.click_binary, "list", "--user=%s" % self.user], - universal_newlines=True) - self.assertNotIn(name, output) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/integration/test_verify.py click-0.4.46+16.10.20170607.3/click/tests/integration/test_verify.py --- click-0.4.45.1+16.10.20160916/click/tests/integration/test_verify.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/integration/test_verify.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Integration tests for the click CLI verify command.""" - -import os -import subprocess - -from .helpers import ClickTestCase - - -class TestVerify(ClickTestCase): - def test_verify_force_missing_framework_ok(self): - name = "com.example.verify-missing-framework" - path_to_click = self._make_click(name) - output = subprocess.check_output([ - self.click_binary, "verify", - "--force-missing-framework", - "--allow-unauthenticated", - path_to_click], universal_newlines=True) - self.assertEqual(output, "") - - def test_verify_force_ok(self): - name = "com.example.verify-ok" - path_to_click = self._make_click(name, framework="") - output = subprocess.check_output([ - self.click_binary, "verify", "--allow-unauthenticated", - path_to_click], universal_newlines=True) - self.assertEqual(output, "") - - def test_verify_missing_framework(self): - name = "com.example.verify-really-missing-framework" - path_to_click = self._make_click(name, framework="missing") - with self.assertRaises(subprocess.CalledProcessError) as cm: - subprocess.check_output( - [self.click_binary, "verify", - "--allow-unauthenticated", - path_to_click], - universal_newlines=True, stderr=subprocess.STDOUT) - expected_error = ( - 'click.framework.ClickFrameworkInvalid: Framework ' - '"missing" not present on system (use ' - '--force-missing-framework option to override)') - self.assertIn(expected_error, cm.exception.output) - - def test_verify_no_click_but_invalid(self): - name = "com.example.verify-no-click" - path_to_click = os.path.join(self.temp_dir, name+".click") - with open(path_to_click, "w") as f: - f.write("something-that-is-not-a-click") - with self.assertRaises(subprocess.CalledProcessError) as cm: - subprocess.check_output( - [self.click_binary, "verify", "--allow-unauthenticated", - path_to_click], - universal_newlines=True, stderr=subprocess.STDOUT) - expected_error = ( - 'click.install.DebsigVerifyError: Signature verification error: ' - 'debsig: %s does not appear to be a deb format package' - ) % path_to_click - self.assertIn(expected_error, cm.exception.output) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/Makefile.am click-0.4.46+16.10.20170607.3/click/tests/Makefile.am --- click-0.4.45.1+16.10.20160916/click/tests/Makefile.am 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/Makefile.am 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ -noinst_DATA = preload.gir -CLEANFILES = $(noinst_DATA) - -preload.gir: preload.h - PKG_CONFIG_PATH=$(top_builddir)/lib/click g-ir-scanner \ - -n preload --nsversion 0 -l c \ - --pkg glib-2.0 --pkg gee-0.8 --pkg json-glib-1.0 \ - --pkg click-0.4 \ - -I$(top_builddir)/lib/click -L$(top_builddir)/lib/click \ - --accept-unprefixed --warn-all \ - --libtool "$(LIBTOOL)" \ - $< --output $@ - -noinst_SCRIPTS = test_paths.py -CLEANFILES += $(noinst_SCRIPTS) - -do_subst = sed \ - -e 's,[@]sysconfdir[@],$(sysconfdir),g' \ - -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' - -test_paths.py: test_paths.py.in Makefile - $(do_subst) < $(srcdir)/test_paths.py.in > $@ diff -Nru click-0.4.45.1+16.10.20160916/click/tests/preload.h click-0.4.46+16.10.20170607.3/click/tests/preload.h --- click-0.4.45.1+16.10.20160916/click/tests/preload.h 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/preload.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,99 +0,0 @@ -#include -#include - -#include - -#include "click.h" - -/** - * chown: (attributes headers=unistd.h) - */ -extern int chown (const char *file, uid_t owner, gid_t group); - -/** - * geteuid: (attributes headers=sys/types.h,unistd.h) - */ -extern uid_t geteuid (void); - -/* Workaround for g-ir-scanner not picking up the type properly: mode_t is - * uint32_t on all glibc platforms. - */ -/** - * mkdir: (attributes headers=sys/stat.h,sys/types.h) - * @mode: (type guint32) - */ -extern int mkdir (const char *pathname, mode_t mode); - -/** - * getpwnam: (attributes headers=sys/types.h,pwd.h) - * - * Returns: (transfer none): - */ -extern struct passwd *getpwnam (const char *name); - -/** - * under_under_xstat: (attributes headers=sys/types.h,sys/stat.h,unistd.h) - */ -extern int under_under_xstat (int ver, const char *pathname, struct stat *buf); - -/** - * under_under_xstat64: (Attributes headers=sys/types.h,sys/stat.h,unistd.h) - */ -extern int under_under_xstat64 (int ver, const char *pathname, struct stat64 *buf); - -const gchar *g_get_user_name (void); - -/** - * g_spawn_sync: (attributes headers=glib.h) - * @argv: (array zero-terminated=1): - * @envp: (array zero-terminated=1): - * @flags: (type gint) - * @child_setup: (type gpointer) - * @standard_output: (out) (array zero-terminated=1) (element-type guint8): - * @standard_error: (out) (array zero-terminated=1) (element-type guint8): - * @exit_status: (out): - */ -gboolean g_spawn_sync (const gchar *working_directory, - gchar **argv, - gchar **envp, - GSpawnFlags flags, - GSpawnChildSetupFunc child_setup, - gpointer user_data, - gchar **standard_output, - gchar **standard_error, - gint *exit_status, - GError **error); - -/** - * click_find_on_path: (attributes headers=glib.h) - */ -gboolean click_find_on_path (const gchar *command); - -/** - * click_get_db_dir: (attributes headers=glib.h) - */ -gchar *click_get_db_dir (void); - -/** - * click_get_frameworks_dir: (attributes headers=glib.h) - */ -gchar *click_get_frameworks_dir (void); - -/** - * click_get_hooks_dir: (attributes headers=glib.h) - */ -gchar *click_get_hooks_dir (void); - -/** - * click_get_user_home: (attributes headers=glib.h) - */ -gchar *click_get_user_home (const gchar *user_name); - -/** - * click_package_install_hooks: (attributes headers=glib.h,click.h) - * @db: (type gpointer) - */ -void click_package_install_hooks (ClickDB *db, const gchar *package, - const gchar *old_version, - const gchar *new_version, - const gchar *user_name, GError **error); diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_arfile.py click-0.4.46+16.10.20170607.3/click/tests/test_arfile.py --- click-0.4.45.1+16.10.20160916/click/tests/test_arfile.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_arfile.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,86 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.arfile.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestArFile', - ] - - -import os -import subprocess - -from click.arfile import ArFile -from click.tests.helpers import TestCase, touch - - -class TestArFile(TestCase): - def setUp(self): - super(TestArFile, self).setUp() - self.use_temp_dir() - - def test_init_rejects_mode_r(self): - self.assertRaises(ValueError, ArFile, mode="r") - - def test_init_name(self): - path = os.path.join(self.temp_dir, "foo.a") - with ArFile(name=path, mode="w") as arfile: - self.assertEqual("w", arfile.mode) - self.assertEqual("wb", arfile.real_mode) - self.assertEqual(path, arfile.name) - self.assertEqual(path, arfile.fileobj.name) - self.assertTrue(arfile.opened_fileobj) - self.assertFalse(arfile.closed) - - def test_init_rejects_readonly_fileobj(self): - path = os.path.join(self.temp_dir, "foo.a") - touch(path) - with open(path, "rb") as fileobj: - self.assertRaises(ValueError, ArFile, fileobj=fileobj) - - def test_init_fileobj(self): - path = os.path.join(self.temp_dir, "foo.a") - with open(path, "wb") as fileobj: - arfile = ArFile(fileobj=fileobj) - self.assertEqual("w", arfile.mode) - self.assertEqual("wb", arfile.real_mode) - self.assertEqual(path, arfile.name) - self.assertEqual(fileobj, arfile.fileobj) - self.assertFalse(arfile.opened_fileobj) - self.assertFalse(arfile.closed) - - def test_writes_valid_ar_file(self): - member_path = os.path.join(self.temp_dir, "member") - with open(member_path, "wb") as member: - member.write(b"\x00\x01\x02\x03\x04\x05\x06\x07") - path = os.path.join(self.temp_dir, "foo.a") - with ArFile(name=path, mode="w") as arfile: - arfile.add_magic() - arfile.add_data("data-member", b"some data") - arfile.add_file("file-member", member_path) - extract_path = os.path.join(self.temp_dir, "extract") - os.mkdir(extract_path) - subprocess.call(["ar", "x", path], cwd=extract_path) - self.assertCountEqual( - ["data-member", "file-member"], os.listdir(extract_path)) - with open(os.path.join(extract_path, "data-member"), "rb") as member: - self.assertEqual(b"some data", member.read()) - with open(os.path.join(extract_path, "file-member"), "rb") as member: - self.assertEqual( - b"\x00\x01\x02\x03\x04\x05\x06\x07", member.read()) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_build.py click-0.4.46+16.10.20170607.3/click/tests/test_build.py --- click-0.4.45.1+16.10.20160916/click/tests/test_build.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_build.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,345 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.build.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestClickBuilder', - 'TestClickSourceBuilder', - ] - - -import json -import os -import stat -import subprocess -import tarfile -from textwrap import dedent - -from click.build import ClickBuildError, ClickBuilder, ClickSourceBuilder -from click.preinst import static_preinst -from click.tests.helpers import ( - disable_logging, - mkfile, - TestCase, - touch, -) - - -# BAW 2013-04-15: Some tests require umask 022. Use this decorator to -# temporarily tweak the process's umask. The test -- or system -- should -# probably be made more robust instead. -def umask(force_umask): - def decorator(func): - def wrapper(*args, **kws): - old_umask = os.umask(force_umask) - try: - return func(*args, **kws) - finally: - os.umask(old_umask) - return wrapper - return decorator - - -class TestClickBuilderBaseMixin: - def test_read_manifest(self): - self.use_temp_dir() - manifest_path = os.path.join(self.temp_dir, "manifest.json") - with mkfile(manifest_path) as manifest: - print(dedent("""\ - { - "name": "com.example.test", - "version": "1.0", - "maintainer": "Foo Bar ", - "title": "test title", - "framework": "ubuntu-sdk-13.10" - }"""), file=manifest) - self.builder.read_manifest(manifest_path) - self.assertEqual("com.example.test", self.builder.name) - self.assertEqual("1.0", self.builder.version) - self.assertEqual("Foo Bar ", self.builder.maintainer) - self.assertEqual("test title", self.builder.title) - self.assertEqual("all", self.builder.architecture) - - def test_add_file(self): - self.builder.add_file("/nonexistent", "target") - self.assertEqual({"/nonexistent": "target"}, self.builder.file_map) - - def test_epochless_version(self): - self.use_temp_dir() - manifest_path = os.path.join(self.temp_dir, "manifest.json") - for version, epochless_version in ( - ("1.0", "1.0"), - ("1:1.2.3", "1.2.3"), - ): - with mkfile(manifest_path) as manifest: - print(dedent("""\ - { - "name": "com.example.test", - "version": "%s", - "maintainer": "Foo Bar ", - "title": "test title", - "framework": "ubuntu-sdk-13.10" - }""") % version, file=manifest) - self.builder.read_manifest(manifest_path) - self.assertEqual(epochless_version, self.builder.epochless_version) - - def test_manifest_syntax_error(self): - self.use_temp_dir() - manifest_path = os.path.join(self.temp_dir, "manifest.json") - with mkfile(manifest_path) as manifest: - # The comma after the "name" entry is intentionally missing. - print(dedent("""\ - { - "name": "com.example.test" - "version": "1.0" - }"""), file=manifest) - self.assertRaises( - ClickBuildError, self.builder.read_manifest, manifest_path) - - -class TestClickBuilder(TestCase, TestClickBuilderBaseMixin): - def setUp(self): - super(TestClickBuilder, self).setUp() - self.builder = ClickBuilder() - - def extract_field(self, path, name): - return subprocess.check_output( - ["dpkg-deb", "-f", path, name], - universal_newlines=True).rstrip("\n") - - @disable_logging - @umask(0o22) - def test_build(self): - self.use_temp_dir() - scratch = os.path.join(self.temp_dir, "scratch") - with mkfile(os.path.join(scratch, "bin", "foo")) as f: - f.write("test /bin/foo\n") - os.symlink("foo", os.path.join(scratch, "bin", "bar")) - touch(os.path.join(scratch, ".git", "config")) - with mkfile(os.path.join(scratch, "toplevel")) as f: - f.write("test /toplevel\n") - os.symlink( - "file-does-not-exist", os.path.join(scratch, "broken-symlink")) - with mkfile(os.path.join(scratch, "manifest.json")) as f: - json.dump({ - "name": "com.example.test", - "version": "1.0", - "maintainer": "Foo Bar ", - "title": "test title", - "architecture": "all", - "framework": "ubuntu-sdk-13.10", - }, f) - # build() overrides this back to 0o644 - os.fchmod(f.fileno(), 0o600) - self.builder.add_file(scratch, "/") - path = os.path.join(self.temp_dir, "com.example.test_1.0_all.click") - self.assertEqual(path, self.builder.build(self.temp_dir)) - self.assertTrue(os.path.exists(path)) - for key, value in ( - ("Package", "com.example.test"), - ("Version", "1.0"), - ("Click-Version", "0.4"), - ("Architecture", "all"), - ("Maintainer", "Foo Bar "), - ("Description", "test title"), - ): - self.assertEqual(value, self.extract_field(path, key)) - self.assertNotEqual( - "", self.extract_field(path, "Installed-Size")) - control_path = os.path.join(self.temp_dir, "control") - subprocess.check_call(["dpkg-deb", "-e", path, control_path]) - manifest_path = os.path.join(control_path, "manifest") - self.assertEqual(0o644, stat.S_IMODE(os.stat(manifest_path).st_mode)) - with open(os.path.join(scratch, "manifest.json")) as source, \ - open(manifest_path) as target: - source_json = json.load(source) - target_json = json.load(target) - self.assertNotEqual("", target_json["installed-size"]) - del target_json["installed-size"] - self.assertEqual(source_json, target_json) - with open(os.path.join(control_path, "md5sums")) as md5sums: - self.assertRegex( - md5sums.read(), - r"^" - r"eb774c3ead632b397d6450d1df25e001 bin/bar\n" - r"eb774c3ead632b397d6450d1df25e001 bin/foo\n" - r"49327ce6306df8a87522456b14a179e0 toplevel\n" - r"$") - with open(os.path.join(control_path, "preinst")) as preinst: - self.assertEqual(static_preinst, preinst.read()) - contents = subprocess.check_output( - ["dpkg-deb", "-c", path], universal_newlines=True) - self.assertRegex(contents, r"^drwxr-xr-x root/root 0 .* \./\n") - self.assertRegex( - contents, - "\nlrwxrwxrwx root/root 0 .* \./bin/bar -> foo\n") - self.assertRegex( - contents, "\n-rw-r--r-- root/root 14 .* \./bin/foo\n") - self.assertRegex( - contents, "\n-rw-r--r-- root/root 15 .* \./toplevel\n") - extract_path = os.path.join(self.temp_dir, "extract") - subprocess.check_call(["dpkg-deb", "-x", path, extract_path]) - for rel_path in ( - os.path.join("bin", "foo"), - "toplevel", - ): - with open(os.path.join(scratch, rel_path)) as source, \ - open(os.path.join(extract_path, rel_path)) as target: - self.assertEqual(source.read(), target.read()) - self.assertTrue( - os.path.islink(os.path.join(extract_path, "bin", "bar"))) - self.assertEqual( - "foo", os.readlink(os.path.join(extract_path, "bin", "bar"))) - - def _make_scratch_dir(self, manifest_override={}): - self.use_temp_dir() - scratch = os.path.join(self.temp_dir, "scratch") - manifest = { - "name": "com.example.test", - "version": "1.0", - "maintainer": "Foo Bar ", - "title": "test title", - "architecture": "all", - "framework": "ubuntu-sdk-13.10", - } - manifest.update(manifest_override) - with mkfile(os.path.join(scratch, "manifest.json")) as f: - json.dump(manifest, f) - self.builder.add_file(scratch, "/") - return scratch - - @disable_logging - def test_build_excludes_dot_click(self): - scratch = self._make_scratch_dir() - touch(os.path.join(scratch, ".click", "evil-file")) - path = self.builder.build(self.temp_dir) - extract_path = os.path.join(self.temp_dir, "extract") - subprocess.check_call(["dpkg-deb", "-x", path, extract_path]) - self.assertEqual([], os.listdir(extract_path)) - - def test_build_ignore_pattern(self): - scratch = self._make_scratch_dir() - touch(os.path.join(scratch, "build", "foo.o")) - self.builder.add_file(scratch, "/") - self.builder.add_ignore_pattern("build") - path = self.builder.build(self.temp_dir) - extract_path = os.path.join(self.temp_dir, "extract") - subprocess.check_call(["dpkg-deb", "-x", path, extract_path]) - self.assertEqual([], os.listdir(extract_path)) - - @disable_logging - def test_build_multiple_architectures(self): - scratch = self._make_scratch_dir(manifest_override={ - "architecture": ["armhf", "i386"], - }) - path = os.path.join(self.temp_dir, "com.example.test_1.0_multi.click") - self.assertEqual(path, self.builder.build(self.temp_dir)) - self.assertTrue(os.path.exists(path)) - self.assertEqual("multi", self.extract_field(path, "Architecture")) - control_path = os.path.join(self.temp_dir, "control") - subprocess.check_call(["dpkg-deb", "-e", path, control_path]) - manifest_path = os.path.join(control_path, "manifest") - with open(os.path.join(scratch, "manifest.json")) as source, \ - open(manifest_path) as target: - source_json = json.load(source) - target_json = json.load(target) - del target_json["installed-size"] - self.assertEqual(source_json, target_json) - - @disable_logging - def test_build_multiple_frameworks(self): - scratch = self._make_scratch_dir(manifest_override={ - "framework": - "ubuntu-sdk-14.04-basic, ubuntu-sdk-14.04-webapps", - }) - path = self.builder.build(self.temp_dir) - control_path = os.path.join(self.temp_dir, "control") - subprocess.check_call(["dpkg-deb", "-e", path, control_path]) - manifest_path = os.path.join(control_path, "manifest") - with open(os.path.join(scratch, "manifest.json")) as source, \ - open(manifest_path) as target: - source_json = json.load(source) - target_json = json.load(target) - del target_json["installed-size"] - self.assertEqual(source_json, target_json) - - -class TestClickFrameworkValidation(TestCase): - def setUp(self): - super(TestClickFrameworkValidation, self).setUp() - self.builder = ClickBuilder() - for framework_name in ("ubuntu-sdk-13.10", - "ubuntu-sdk-14.04-papi", - "ubuntu-sdk-14.04-html", - "docker-sdk-1.3"): - self._create_mock_framework_file(framework_name) - - def test_validate_framework_good(self): - valid_framework_values = ( - "ubuntu-sdk-13.10", - "ubuntu-sdk-14.04-papi, ubuntu-sdk-14.04-html", - "ubuntu-sdk-13.10, docker-sdk-1.3", - ) - for framework in valid_framework_values: - self.builder._validate_framework(framework) - - def test_validate_framework_bad(self): - invalid_framework_values = ( - "ubuntu-sdk-13.10, ubuntu-sdk-14.04-papi", - "ubuntu-sdk-13.10 (>= 13.10)", - "ubuntu-sdk-13.10 | ubuntu-sdk-14.04", - ) - for framework in invalid_framework_values: - with self.assertRaises(ClickBuildError): - self.builder._validate_framework(framework) - - -class TestClickSourceBuilder(TestCase, TestClickBuilderBaseMixin): - def setUp(self): - super(TestClickSourceBuilder, self).setUp() - self.builder = ClickSourceBuilder() - - @umask(0o22) - def test_build(self): - self.use_temp_dir() - scratch = os.path.join(self.temp_dir, "scratch") - touch(os.path.join(scratch, "bin", "foo")) - touch(os.path.join(scratch, ".git", "config")) - touch(os.path.join(scratch, "foo.so")) - touch(os.path.join(scratch, "build", "meep.goah")) - with mkfile(os.path.join(scratch, "manifest.json")) as f: - json.dump({ - "name": "com.example.test", - "version": "1.0", - "maintainer": "Foo Bar ", - "title": "test title", - "architecture": "all", - "framework": "ubuntu-sdk-13.10", - }, f) - # build() overrides this back to 0o644 - os.fchmod(f.fileno(), 0o600) - self.builder.add_file(scratch, "./") - self.builder.add_ignore_pattern("build") - path = os.path.join(self.temp_dir, "com.example.test_1.0.tar.gz") - self.assertEqual(path, self.builder.build(self.temp_dir)) - self.assertTrue(os.path.exists(path)) - with tarfile.open(path, mode="r:gz") as tar: - self.assertCountEqual( - [".", "./bin", "./bin/foo", "./manifest.json"], tar.getnames()) - self.assertTrue(tar.getmember("./bin/foo").isfile()) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_chroot.py click-0.4.46+16.10.20170607.3/click/tests/test_chroot.py --- click-0.4.45.1+16.10.20160916/click/tests/test_chroot.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_chroot.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,356 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Michael Vogt - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.chroot.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestClickChroot', - ] - -import os -import re -from textwrap import dedent - -from click.chroot import ( - ClickChroot, - generate_sources, - strip_dev_series_from_framework, -) -from click.tests.helpers import TestCase, mock - - -class FakeClickChroot(ClickChroot): - - def __init__(self, *args, **kwargs): - self.temp_dir = kwargs.pop("temp_dir") - super(FakeClickChroot, self).__init__(*args, **kwargs) - self._exists = False - - def exists(self): - return self._exists - - def maint(self, *args, **kwargs): - self._maint_args = args - self._maint_kwargs = kwargs - return 0 - - def _debootstrap(self, components, mount, archive_mirror, ports_mirror): - os.makedirs(os.path.join(mount, "etc", "apt")) - os.makedirs(os.path.join(mount, "usr", "sbin")) - os.makedirs(os.path.join(mount, "sbin")) - with open(os.path.join(mount, "sbin", "initctl"), "w"): - pass - self._exists = True - - @property - def chroot_config(self): - p = self.temp_dir + super(FakeClickChroot, self).chroot_config - if not os.path.exists(os.path.dirname(p)): - os.makedirs(os.path.dirname(p)) - return p - - -class TestClickChroot(TestCase): - def set_dpkg_native_architecture(self, arch): - """Fool dpkg-architecture into selecting a given native arch.""" - self.use_temp_dir() - dpkg_script_path = os.path.join(self.temp_dir, "dpkg") - with open(dpkg_script_path, "w") as dpkg_script: - print(dedent("""\ - #! /bin/sh - echo %s - """) % arch, file=dpkg_script) - os.chmod(dpkg_script_path, 0o755) - os.environ["PATH"] = "%s:%s" % (self.temp_dir, os.environ["PATH"]) - - def test_get_native_arch_amd64_to_amd64(self): - chroot = ClickChroot("amd64", "ubuntu-sdk-14.04", series="trusty") - self.assertEqual("amd64", chroot._get_native_arch("amd64", "amd64")) - - def test_get_native_arch_amd64_to_armhf(self): - chroot = ClickChroot("armhf", "ubuntu-sdk-14.04", series="trusty") - self.assertEqual("amd64", chroot._get_native_arch("amd64", "armhf")) - - def test_get_native_arch_amd64_to_i386(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04", series="trusty") - self.assertEqual("i386", chroot._get_native_arch("amd64", "i386")) - - def test_dpkg_architecture_amd64_to_armhf(self): - self.set_dpkg_native_architecture("amd64") - chroot = ClickChroot("armhf", "ubuntu-sdk-14.04", series="trusty") - self.assertEqual("amd64", chroot.dpkg_architecture["DEB_BUILD_ARCH"]) - self.assertEqual("armhf", chroot.dpkg_architecture["DEB_HOST_ARCH"]) - - def test_dpkg_architecture_i386_to_armhf(self): - self.set_dpkg_native_architecture("i386") - chroot = ClickChroot("armhf", "ubuntu-sdk-14.04", series="trusty") - self.assertEqual("i386", chroot.dpkg_architecture["DEB_BUILD_ARCH"]) - self.assertEqual("armhf", chroot.dpkg_architecture["DEB_HOST_ARCH"]) - - def test_dpkg_architecture_amd64_to_i386(self): - self.set_dpkg_native_architecture("amd64") - chroot = ClickChroot("i386", "ubuntu-sdk-14.04", series="trusty") - self.assertEqual("i386", chroot.dpkg_architecture["DEB_BUILD_ARCH"]) - self.assertEqual("i386", chroot.dpkg_architecture["DEB_HOST_ARCH"]) - - def test_gen_sources_archive_only(self): - chroot = ClickChroot("amd64", "ubuntu-sdk-13.10", series="trusty") - chroot.native_arch = "i386" - sources = generate_sources( - chroot.series, chroot.native_arch, chroot.target_arch, - "http://archive.ubuntu.com/ubuntu", - "http://ports.ubuntu.com/ubuntu-ports", - "main") - self.assertEqual([ - 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu trusty main', - 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu trusty-security main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-security main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-security main', - ], sources) - - def test_gen_sources_mixed_archive_ports(self): - chroot = ClickChroot("armhf", "ubuntu-sdk-13.10", series="trusty") - chroot.native_arch = "i386" - sources = generate_sources( - chroot.series, chroot.native_arch, chroot.target_arch, - "http://archive.ubuntu.com/ubuntu", - "http://ports.ubuntu.com/ubuntu-ports", - "main") - self.assertEqual([ - 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty main', - 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty-updates main', - 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty-security main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-security main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-security main', - ], sources) - - def test_gen_sources_ports_only(self): - chroot = ClickChroot("armhf", "ubuntu-sdk-13.10", series="trusty") - chroot.native_arch = "armel" - sources = generate_sources( - chroot.series, chroot.native_arch, chroot.target_arch, - "http://archive.ubuntu.com/ubuntu", - "http://ports.ubuntu.com/ubuntu-ports", - "main") - self.assertEqual([ - 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty main', - 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty-updates main', - 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty-security main', - 'deb [arch=armel] http://ports.ubuntu.com/ubuntu-ports trusty main', - 'deb [arch=armel] http://ports.ubuntu.com/ubuntu-ports trusty-updates main', - 'deb [arch=armel] http://ports.ubuntu.com/ubuntu-ports trusty-security main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-security main', - ], sources) - - def test_gen_sources_native(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04", series="trusty") - chroot.native_arch = "i386" - sources = generate_sources( - chroot.series, chroot.native_arch, chroot.target_arch, - "http://archive.ubuntu.com/ubuntu", - "http://ports.ubuntu.com/ubuntu-ports", - "main") - self.assertEqual([ - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-security main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-updates main', - 'deb-src http://archive.ubuntu.com/ubuntu trusty-security main', - ], sources) - - def test_make_cross_package_native(self): - chroot = ClickChroot("amd64", "ubuntu-sdk-14.04", series="trusty") - chroot.native_arch = "amd64" - self.assertEqual("g++", chroot._make_cross_package("g++")) - - def test_make_cross_package_cross(self): - chroot = ClickChroot("armhf", "ubuntu-sdk-14.04", series="trusty") - chroot.native_arch = "amd64" - self.assertEqual( - "g++-arm-linux-gnueabihf", chroot._make_cross_package("g++")) - - def test_framework_base_base(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04-papi") - self.assertEqual(chroot.framework_base, "ubuntu-sdk-14.04") - - def test_framework_base_series(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - self.assertEqual(chroot.framework_base, "ubuntu-sdk-14.04") - - def test_chroot_series(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - self.assertEqual(chroot.series, "trusty") - - def test_chroot_full_name(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - self.assertEqual(chroot.full_name, "click-ubuntu-sdk-14.04-i386") - - def test_chroot_generate_daemon_config(self): - self.use_temp_dir() - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - os.makedirs(os.path.join(self.temp_dir, "usr", "sbin")) - daemon_policy = chroot._generate_daemon_policy(self.temp_dir) - with open(daemon_policy) as f: - self.assertEqual(f.read(), chroot.DAEMON_POLICY) - - def test_chroot_generate_finish_script(self): - self.use_temp_dir() - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - finish_script = chroot._generate_finish_script( - self.temp_dir, - ["build-pkg-1", "build-pkg-2"]) - with open(finish_script) as f: - self.assertEqual(f.read(), dedent("""\ - #!/bin/bash - set -e - # Configure target arch - dpkg --add-architecture i386 - # Reload package lists - apt-get update || true - # Pull down signature requirements - apt-get -y --force-yes install gnupg ubuntu-keyring - # Reload package lists - apt-get update || true - # Disable debconf questions - # so that automated builds won't prompt - echo set debconf/frontend Noninteractive | debconf-communicate - echo set debconf/priority critical | debconf-communicate - apt-get -y --force-yes dist-upgrade - # Install basic build tool set to match buildd - apt-get -y --force-yes install build-pkg-1 build-pkg-2 - # Set up expected /dev entries - if [ ! -r /dev/stdin ]; then - ln -s /proc/self/fd/0 /dev/stdin - fi - if [ ! -r /dev/stdout ]; then - ln -s /proc/self/fd/1 /dev/stdout - fi - if [ ! -r /dev/stderr ]; then - ln -s /proc/self/fd/2 /dev/stderr - fi - # Clean up - rm /finish.sh - apt-get clean - """)) - - def test_chroot_generate_apt_conf_d_empty(self): - self.use_temp_dir() - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - apt_conf_f = chroot._generate_apt_proxy_file(self.temp_dir, "") - self.assertFalse(os.path.exists(apt_conf_f)) - - def test_chroot_generate_apt_conf_d(self): - self.use_temp_dir() - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - apt_conf_f = chroot._generate_apt_proxy_file( - self.temp_dir, "http://proxy.example.com") - with open(apt_conf_f) as f: - self.assertEqual( - re.sub(r'\s+', ' ', f.read()), - '// proxy settings copied by click chroot ' - 'Acquire { HTTP { Proxy "http://proxy.example.com"; }; }; ') - - def test_chroot_generate_chroot_config(self): - self.use_temp_dir() - chroot = FakeClickChroot( - "i386", "ubuntu-sdk-14.04", temp_dir=self.temp_dir) - with mock.patch.object(chroot, "user", new="meep"): - chroot._generate_chroot_config(self.temp_dir) - with open(chroot.chroot_config) as f: - content = f.read() - self.assertEqual( - content, dedent("""\ - [click-ubuntu-sdk-14.04-i386] - description=Build chroot for click packages on i386 - users=root,{user} - root-users=root,{user} - source-root-users=root,{user} - type=directory - profile=default - setup.fstab=click/fstab - # Not protocols or services see - # debian bug 557730 - setup.nssdatabases=sbuild/nssdatabases - union-type=overlayfs - directory={temp_dir} - """).format(user="meep", temp_dir=self.temp_dir)) - - def test_chroot_create_mocked(self): - self.use_temp_dir() - os.environ["http_proxy"] = "http://proxy.example.com/" - target = "ubuntu-sdk-14.04" - chroot = FakeClickChroot( - "i386", target, chroots_dir=self.temp_dir, temp_dir=self.temp_dir) - with mock.patch.object(chroot, "maint") as mock_maint: - mock_maint.return_value = 0 - chroot.create() - mock_maint.assert_called_with("/finish.sh") - # ensure the following files where created inside the chroot - for in_chroot in ["etc/localtime", - "etc/timezone", - "etc/apt/sources.list", - "usr/sbin/policy-rc.d"]: - full_path = os.path.join( - self.temp_dir, chroot.full_name, in_chroot) - self.assertTrue(os.path.exists(full_path)) - # ensure the schroot/chroot.d file was created and looks valid - schroot_d = os.path.join( - self.temp_dir, "etc", "schroot", "chroot.d", chroot.full_name) - self.assertTrue(os.path.exists(schroot_d)) - - def test_chroot_maint(self): - chroot = ClickChroot("i386", "ubuntu-sdk-14.04") - with mock.patch("subprocess.call") as mock_call: - mock_call.return_value = 0 - chroot.maint("foo", "bar") - mock_call.assert_called_with([ - "schroot", "-u", "root", - "-c", "source:"+chroot.full_name, - "--", - "foo", "bar"]) - - def test_chroot_destroy(self): - self.use_temp_dir() - chroot = FakeClickChroot( - "i386", "ubuntu-sdk-14.04", - chroots_dir=self.temp_dir, temp_dir=self.temp_dir) - chroot.create() - chroot_path = os.path.join(self.temp_dir, chroot.full_name) - self.assertTrue(os.path.exists(chroot_path)) - chroot.destroy() - self.assertFalse(os.path.exists(chroot_path)) - - def test_strip_dev_series_from_framework(self): - for have, want in ( - ("ubuntu-sdk-14.10-html-dev1", "ubuntu-sdk-14.10-html"), - ("ubuntu-sdk-14.10-html", "ubuntu-sdk-14.10-html"), - ("ubuntu-sdk-14.04-dev99", "ubuntu-sdk-14.04"), - ): - self.assertEqual(strip_dev_series_from_framework(have), want) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_database.py click-0.4.46+16.10.20170607.3/click/tests/test_database.py --- click-0.4.45.1+16.10.20160916/click/tests/test_database.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_database.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,936 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.database.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - "TestClickDB", - "TestClickInstalledPackage", - "TestClickSingleDB", - ] - - -from functools import partial -from itertools import takewhile -import json -import os -import unittest - -from gi.repository import Click, GLib -from six import integer_types - -from click.json_helpers import json_array_to_python, json_object_to_python -from click.tests.gimock_types import Passwd -from click.tests.helpers import TestCase, mkfile, touch - - -class TestClickInstalledPackage(TestCase): - def setUp(self): - super(TestClickInstalledPackage, self).setUp() - self.foo = Click.InstalledPackage.new( - "foo", "1.0", "/path/to/foo/1.0", False) - self.foo_clone = Click.InstalledPackage.new( - "foo", "1.0", "/path/to/foo/1.0", False) - self.foo_different_version = Click.InstalledPackage.new( - "foo", "2.0", "/path/to/foo/1.0", False) - self.foo_different_path = Click.InstalledPackage.new( - "foo", "1.0", "/path/to/foo/2.0", False) - self.foo_different_writeable = Click.InstalledPackage.new( - "foo", "1.0", "/path/to/foo/1.0", True) - self.bar = Click.InstalledPackage.new( - "bar", "1.0", "/path/to/foo/1.0", False) - - def test_hash(self): - self.assertIsInstance(self.foo.hash(), integer_types) - self.assertEqual(self.foo.hash(), self.foo_clone.hash()) - self.assertNotEqual(self.foo.hash(), self.foo_different_version.hash()) - self.assertNotEqual(self.foo.hash(), self.foo_different_path.hash()) - self.assertNotEqual( - self.foo.hash(), self.foo_different_writeable.hash()) - self.assertNotEqual(self.foo.hash(), self.bar.hash()) - - # GLib doesn't allow passing an InstalledPackage as an argument here. - @unittest.expectedFailure - def test_equal_to(self): - self.assertTrue(self.foo.equal_to(self.foo_clone)) - self.assertFalse(self.foo.equal_to(self.foo_different_version)) - self.assertFalse(self.foo.equal_to(self.foo_different_path)) - self.assertFalse(self.foo.equal_to(self.foo_different_writeable)) - self.assertFalse(self.foo.equal_to(self.bar)) - - -class TestClickSingleDB(TestCase): - def setUp(self): - super(TestClickSingleDB, self).setUp() - self.use_temp_dir() - self.master_db = Click.DB() - self.master_db.add(self.temp_dir) - self.db = self.master_db.get(self.master_db.props.size - 1) - self.spawn_calls = [] - - def g_spawn_sync_side_effect(self, status_map, working_directory, argv, - envp, flags, child_setup, user_data, - standard_output, standard_error, exit_status, - error): - self.spawn_calls.append(list(takewhile(lambda x: x is not None, argv))) - if argv[0] in status_map: - exit_status[0] = status_map[argv[0]] - else: - self.delegate_to_original("g_spawn_sync") - return 0 - - def _installed_packages_tuplify(self, ip): - return [(p.props.package, p.props.version, p.props.path) for p in ip] - - def test_path(self): - path = os.path.join(self.temp_dir, "a", "1.0") - os.makedirs(path) - self.assertEqual(path, self.db.get_path("a", "1.0")) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, self.db.get_path, "a", "1.1") - - def test_has_package_version(self): - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - self.assertTrue(self.db.has_package_version("a", "1.0")) - self.assertFalse(self.db.has_package_version("a", "1.1")) - - def test_packages_current(self): - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - os.makedirs(os.path.join(self.temp_dir, "a", "1.1")) - a_current = os.path.join(self.temp_dir, "a", "current") - os.symlink("1.1", a_current) - os.makedirs(os.path.join(self.temp_dir, "b", "0.1")) - b_current = os.path.join(self.temp_dir, "b", "current") - os.symlink("0.1", b_current) - os.makedirs(os.path.join(self.temp_dir, "c", "2.0")) - self.assertEqual([ - ("a", "1.1", a_current), - ("b", "0.1", b_current), - ], self._installed_packages_tuplify( - self.db.get_packages(all_versions=False))) - - def test_packages_all(self): - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - os.makedirs(os.path.join(self.temp_dir, "a", "1.1")) - os.symlink("1.1", os.path.join(self.temp_dir, "a", "current")) - os.makedirs(os.path.join(self.temp_dir, "b", "0.1")) - os.symlink("0.1", os.path.join(self.temp_dir, "b", "current")) - os.makedirs(os.path.join(self.temp_dir, "c", "2.0")) - self.assertEqual([ - ("a", "1.0", os.path.join(self.temp_dir, "a", "1.0")), - ("a", "1.1", os.path.join(self.temp_dir, "a", "1.1")), - ("b", "0.1", os.path.join(self.temp_dir, "b", "0.1")), - ("c", "2.0", os.path.join(self.temp_dir, "c", "2.0")), - ], self._installed_packages_tuplify( - self.db.get_packages(all_versions=True))) - - def test_packages_all_ignores_non_directory(self): - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - touch(os.path.join(self.temp_dir, "file")) - self.assertEqual([ - ("a", "1.0", os.path.join(self.temp_dir, "a", "1.0")), - ], self._installed_packages_tuplify( - self.db.get_packages(all_versions=True))) - - def test_manifest(self): - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - manifest_obj = { - "name": "a", "version": "1.0", "hooks": {"a-app": {}}, - "_should_be_removed": "", - } - with mkfile(manifest_path) as manifest: - json.dump(manifest_obj, manifest) - del manifest_obj["_should_be_removed"] - manifest_obj["_directory"] = os.path.join(self.temp_dir, "a", "1.0") - self.assertEqual( - manifest_obj, - json_object_to_python(self.db.get_manifest("a", "1.0"))) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, - self.db.get_manifest, "a", "1.1") - self.assertEqual( - manifest_obj, - json.loads(self.db.get_manifest_as_string("a", "1.0"))) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, - self.db.get_manifest_as_string, "a", "1.1") - - def test_manifest_bad(self): - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - print("{bad syntax", file=manifest) - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, self.db.get_manifest, "a", "1.0") - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, - self.db.get_manifest_as_string, "a", "1.0") - manifest_path = os.path.join( - self.temp_dir, "a", "1.1", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - print("[0]", file=manifest) - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, self.db.get_manifest, "a", "1.1") - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, - self.db.get_manifest_as_string, "a", "1.1") - - def test_app_running(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - preloads["click_find_on_path"].return_value = True - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) - self.assertTrue(self.db.app_running("foo", "bar", "1.0")) - self.assertEqual( - [[b"ubuntu-app-pid", b"foo_bar_1.0"]], self.spawn_calls) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8}) - self.assertFalse(self.db.app_running("foo", "bar", "1.0")) - - def test_any_app_running_ubuntu_app_pid(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - preloads["click_find_on_path"].side_effect = ( - lambda command: command == b"ubuntu-app-pid") - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) - self.assertTrue(self.db.any_app_running("a", "1.0")) - self.assertEqual( - [[b"ubuntu-app-pid", b"a_a-app_1.0"]], self.spawn_calls) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8}) - self.assertFalse(self.db.any_app_running("a", "1.0")) - - def test_any_app_running_upstart_app_pid(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - preloads["click_find_on_path"].side_effect = ( - lambda command: command == b"upstart-app-pid") - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"upstart-app-pid": 0}) - self.assertTrue(self.db.any_app_running("a", "1.0")) - self.assertEqual( - [[b"upstart-app-pid", b"a_a-app_1.0"]], self.spawn_calls) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"upstart-app-pid": 1 << 8}) - self.assertFalse(self.db.any_app_running("a", "1.0")) - - def test_any_app_running_no_app_pid_command(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - preloads["click_find_on_path"].return_value = False - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) - self.assertFalse(self.db.any_app_running("a", "1.0")) - - def test_any_app_running_missing_app(self): - with self.run_in_subprocess("click_find_on_path") as (enter, preloads): - enter() - preloads["click_find_on_path"].side_effect = ( - lambda command: command == b"ubuntu-app-pid") - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, - self.db.any_app_running, "a", "1.0") - - def test_any_app_running_bad_manifest(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - print("{bad syntax", file=manifest) - preloads["click_find_on_path"].side_effect = ( - lambda command: command == b"ubuntu-app-pid") - self.assertFalse(self.db.any_app_running("a", "1.0")) - self.assertFalse(preloads["g_spawn_sync"].called) - - def test_any_app_running_no_hooks(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({}, manifest) - preloads["click_find_on_path"].side_effect = ( - lambda command: command == b"ubuntu-app-pid") - self.assertFalse(self.db.any_app_running("a", "1.0")) - self.assertFalse(preloads["g_spawn_sync"].called) - - def test_maybe_remove_registered(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - version_path = os.path.join(self.temp_dir, "a", "1.0") - manifest_path = os.path.join( - version_path, ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - user_path = os.path.join( - self.temp_dir, ".click", "users", "test-user", "a") - os.makedirs(os.path.dirname(user_path)) - os.symlink(version_path, user_path) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) - preloads["click_find_on_path"].return_value = True - self.db.maybe_remove("a", "1.0") - self.assertTrue(os.path.exists(version_path)) - self.assertTrue(os.path.exists(user_path)) - - def test_maybe_remove_running(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - version_path = os.path.join(self.temp_dir, "a", "1.0") - manifest_path = os.path.join( - version_path, ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) - preloads["click_find_on_path"].return_value = True - self.db.maybe_remove("a", "1.0") - self.assertTrue(os.path.exists(version_path)) - - def test_maybe_remove_not_running(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", - ) as (enter, preloads): - enter() - os.environ["TEST_QUIET"] = "1" - version_path = os.path.join(self.temp_dir, "a", "1.0") - manifest_path = os.path.join( - version_path, ".click", "info", "a.manifest") - with mkfile(manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - current_path = os.path.join(self.temp_dir, "a", "current") - os.symlink("1.0", current_path) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8}) - preloads["click_find_on_path"].return_value = True - self.db.maybe_remove("a", "1.0") - self.assertFalse(os.path.exists(os.path.join(self.temp_dir, "a"))) - - def test_gc(self): - with self.run_in_subprocess( - "click_find_on_path", "g_spawn_sync", "getpwnam" - ) as (enter, preloads): - enter() - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) - os.environ["TEST_QUIET"] = "1" - a_path = os.path.join(self.temp_dir, "a", "1.0") - a_manifest_path = os.path.join( - a_path, ".click", "info", "a.manifest") - with mkfile(a_manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - b_path = os.path.join(self.temp_dir, "b", "1.0") - b_manifest_path = os.path.join( - b_path, ".click", "info", "b.manifest") - with mkfile(b_manifest_path) as manifest: - json.dump({"hooks": {"b-app": {}}}, manifest) - c_path = os.path.join(self.temp_dir, "c", "1.0") - c_manifest_path = os.path.join( - c_path, ".click", "info", "c.manifest") - with mkfile(c_manifest_path) as manifest: - json.dump({"hooks": {"c-app": {}}}, manifest) - a_user_path = os.path.join( - self.temp_dir, ".click", "users", "test-user", "a") - os.makedirs(os.path.dirname(a_user_path)) - os.symlink(a_path, a_user_path) - b_gcinuse_path = os.path.join( - self.temp_dir, ".click", "users", "@gcinuse", "b") - os.makedirs(os.path.dirname(b_gcinuse_path)) - os.symlink(b_path, b_gcinuse_path) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8}) - preloads["click_find_on_path"].return_value = True - self.db.gc() - self.assertTrue(os.path.exists(a_path)) - self.assertFalse(os.path.exists(b_gcinuse_path)) - self.assertFalse(os.path.exists(b_path)) - self.assertFalse(os.path.exists(c_path)) - - def test_gc_ignores_non_directory(self): - with self.run_in_subprocess( - "getpwnam" - ) as (enter, preloads): - enter() - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) - a_path = os.path.join(self.temp_dir, "a", "1.0") - a_manifest_path = os.path.join( - a_path, ".click", "info", "a.manifest") - with mkfile(a_manifest_path) as manifest: - json.dump({"hooks": {"a-app": {}}}, manifest) - a_user_path = os.path.join( - self.temp_dir, ".click", "users", "test-user", "a") - os.makedirs(os.path.dirname(a_user_path)) - os.symlink(a_path, a_user_path) - touch(os.path.join(self.temp_dir, "file")) - self.db.gc() - self.assertTrue(os.path.exists(a_path)) - - # Test that bug #1479001 is fixed. Uses the following scenario: - # - # - Two databases: db1 and db2. - # - One package, "test-package": - # - Versions 1 and 3 installed in db1 - # - Version 2 installed in db2 - # - User has a registration in db2 for version 2, where the registration - # timestamp precedes the installation of version 3. - # - # In this case, bug #1479001 expects that the user's registration would - # be updated to 3, since it was installed after the user registered for - # 2, which implies that the user would like the update to 3. - def test_gc_fixes_old_user_registrations(self): - with self.run_in_subprocess("getpwnam") as (enter, preloads): - enter() - - # Setup the system hook - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_dir=b"/foo"))) - - # Setup both databases - db1 = os.path.join(self.temp_dir, "db1") - db2 = os.path.join(self.temp_dir, "db2") - db = Click.DB() - db.add(db1) - db.add(db2) - - # Prepare common manifest for the packages - manifest = {"hooks": {"test-app": {"test": "foo"}}} - - # Setup versions 1.0 and 3.0 of package in db1 - version1 = os.path.join(db1, "test-package", "1.0") - with mkfile(os.path.join(version1, ".click", "info", - "test-package.manifest")) as f: - json.dump(manifest, f) - - version3 = os.path.join(db1, "test-package", "3.0") - with mkfile(os.path.join(version3, ".click", "info", - "test-package.manifest")) as f: - json.dump(manifest, f) - - # Setup version 0.2 of package in db2 - version2 = os.path.join(db2, "test-package", "2.0") - with mkfile(os.path.join(version2, ".click", "info", - "test-package.manifest")) as f: - json.dump(manifest, f) - - # Setup the user registration for 2.0 in db2. - registrationPath = os.path.join( - db2, ".click", "users", "foo", "test-package") - os.makedirs(os.path.dirname(registrationPath)) - os.symlink(version2, registrationPath) - - # Run the garbage collection to update the registrations. - db.gc() - - # Verify that the user still has a registration for the package, - # and that it's now registered for version 3.0. - self.assertTrue(os.path.lexists(registrationPath)) - self.assertEqual(version3, os.readlink(registrationPath)) - - user_db = Click.User.for_user(db, "foo") - try: - version = user_db.get_version("test-package") - self.assertEqual("3.0", version) - except: - self.fail("No user registration for 'test-package'") - - def _make_ownership_test(self): - path = os.path.join(self.temp_dir, "a", "1.0") - touch(os.path.join(path, ".click", "info", "a.manifest")) - os.symlink("1.0", os.path.join(self.temp_dir, "a", "current")) - user_path = os.path.join( - self.temp_dir, ".click", "users", "test-user", "a") - os.makedirs(os.path.dirname(user_path)) - os.symlink(path, user_path) - touch(os.path.join(self.temp_dir, ".click", "log")) - - def _set_stat_side_effect(self, preloads, side_effect, limit): - limit = limit.encode() - preloads["__xstat"].side_effect = ( - lambda ver, path, buf: side_effect( - "__xstat", limit, ver, path, buf)) - preloads["__xstat64"].side_effect = ( - lambda ver, path, buf: side_effect( - "__xstat64", limit, ver, path, buf)) - - def test_ensure_ownership_quick_if_correct(self): - def stat_side_effect(name, limit, ver, path, buf): - st = self.convert_stat_pointer(name, buf) - if path == limit: - st.st_uid = 1 - st.st_gid = 1 - return 0 - else: - self.delegate_to_original(name) - return -1 - - with self.run_in_subprocess( - "chown", "getpwnam", "__xstat", "__xstat64", - ) as (enter, preloads): - enter() - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) - self._set_stat_side_effect( - preloads, stat_side_effect, self.db.props.root) - - self._make_ownership_test() - self.db.ensure_ownership() - self.assertFalse(preloads["chown"].called) - - def test_ensure_ownership(self): - def stat_side_effect(name, limit, ver, path, buf): - st = self.convert_stat_pointer(name, buf) - if path == limit: - st.st_uid = 2 - st.st_gid = 2 - return 0 - else: - self.delegate_to_original(name) - return -1 - - with self.run_in_subprocess( - "chown", "getpwnam", "__xstat", "__xstat64", - ) as (enter, preloads): - enter() - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) - self._set_stat_side_effect( - preloads, stat_side_effect, self.db.props.root) - - self._make_ownership_test() - self.db.ensure_ownership() - expected_paths = [ - self.temp_dir, - os.path.join(self.temp_dir, ".click"), - os.path.join(self.temp_dir, ".click", "log"), - os.path.join(self.temp_dir, ".click", "users"), - os.path.join(self.temp_dir, "a"), - os.path.join(self.temp_dir, "a", "1.0"), - os.path.join(self.temp_dir, "a", "1.0", ".click"), - os.path.join(self.temp_dir, "a", "1.0", ".click", "info"), - os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest"), - os.path.join(self.temp_dir, "a", "current"), - ] - self.assertCountEqual( - [path.encode() for path in expected_paths], - [args[0][0] for args in preloads["chown"].call_args_list]) - self.assertCountEqual( - [(1, 1)], - set(args[0][1:] for args in preloads["chown"].call_args_list)) - - def test_ensure_ownership_missing_clickpkg_user(self): - with self.run_in_subprocess("getpwnam") as (enter, preloads): - enter() - preloads["getpwnam"].return_value = None - self.assertRaisesDatabaseError( - Click.DatabaseError.ENSURE_OWNERSHIP, self.db.ensure_ownership) - - def test_ensure_ownership_failed_chown(self): - def stat_side_effect(name, limit, ver, path, buf): - st = self.convert_stat_pointer(name, buf) - if path == limit: - st.st_uid = 2 - st.st_gid = 2 - return 0 - else: - self.delegate_to_original(name) - return -1 - - with self.run_in_subprocess( - "chown", "getpwnam", "__xstat", "__xstat64", - ) as (enter, preloads): - enter() - preloads["chown"].return_value = -1 - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) - self._set_stat_side_effect( - preloads, stat_side_effect, self.db.props.root) - - self._make_ownership_test() - self.assertRaisesDatabaseError( - Click.DatabaseError.ENSURE_OWNERSHIP, self.db.ensure_ownership) - - -class TestClickDB(TestCase): - def setUp(self): - super(TestClickDB, self).setUp() - self.use_temp_dir() - - def _installed_packages_tuplify(self, ip): - return [ - (p.props.package, p.props.version, p.props.path, p.props.writeable) - for p in ip] - - def test_read_configuration(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = /a", file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = /b", file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - db.add("/c") - self.assertEqual(3, db.props.size) - self.assertEqual( - ["/a", "/b", "/c"], - [db.get(i).props.root for i in range(db.props.size)]) - - def test_no_read(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = /a", file=a) - db = Click.DB() - self.assertEqual(0, db.props.size) - - def test_no_db_conf_errors(self): - db = Click.DB() - self.assertRaisesDatabaseError( - Click.DatabaseError.INVALID, db.get, 0) - self.assertEqual(db.props.overlay, "") - self.assertRaisesDatabaseError( - Click.DatabaseError.INVALID, db.maybe_remove, "something", "1.0") - self.assertRaisesDatabaseError( - Click.DatabaseError.INVALID, db.gc) - self.assertRaisesDatabaseError( - Click.DatabaseError.INVALID, db.ensure_ownership) - - def test_read_nonexistent(self): - db = Click.DB() - db.read(db_dir=os.path.join(self.temp_dir, "nonexistent")) - self.assertEqual(0, db.props.size) - - def test_read_not_directory(self): - path = os.path.join(self.temp_dir, "file") - touch(path) - db = Click.DB() - self.assertRaisesFileError(GLib.FileError.NOTDIR, db.read, db_dir=path) - - def test_add(self): - db = Click.DB() - self.assertEqual(0, db.props.size) - db.add("/new/root") - self.assertEqual(1, db.props.size) - self.assertEqual("/new/root", db.get(0).props.root) - - def test_overlay(self): - with open(os.path.join(self.temp_dir, "00_custom.conf"), "w") as f: - print("[Click Database]", file=f) - print("root = /custom", file=f) - with open(os.path.join(self.temp_dir, "99_default.conf"), "w") as f: - print("[Click Database]", file=f) - print("root = /opt/click.ubuntu.com", file=f) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertEqual("/opt/click.ubuntu.com", db.props.overlay) - - def test_path(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, db.get_path, "pkg", "1.0") - os.makedirs(os.path.join(self.temp_dir, "a", "pkg", "1.0")) - self.assertEqual( - os.path.join(self.temp_dir, "a", "pkg", "1.0"), - db.get_path("pkg", "1.0")) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, db.get_path, "pkg", "1.1") - os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.0")) - # The deepest copy of the same package/version is still preferred. - self.assertEqual( - os.path.join(self.temp_dir, "a", "pkg", "1.0"), - db.get_path("pkg", "1.0")) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.1")) - self.assertEqual( - os.path.join(self.temp_dir, "b", "pkg", "1.1"), - db.get_path("pkg", "1.1")) - - def test_has_package_version(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertFalse(db.has_package_version("pkg", "1.0")) - os.makedirs(os.path.join(self.temp_dir, "a", "pkg", "1.0")) - self.assertTrue(db.has_package_version("pkg", "1.0")) - self.assertFalse(db.has_package_version("pkg", "1.1")) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.0")) - self.assertTrue(db.has_package_version("pkg", "1.0")) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.1")) - self.assertTrue(db.has_package_version("pkg", "1.1")) - - def test_packages_current(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertEqual([], list(db.get_packages(all_versions=False))) - os.makedirs(os.path.join(self.temp_dir, "a", "pkg1", "1.0")) - os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg1", "1.1")) - pkg1_current = os.path.join(self.temp_dir, "b", "pkg1", "current") - os.symlink("1.1", pkg1_current) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg2", "0.1")) - pkg2_current = os.path.join(self.temp_dir, "b", "pkg2", "current") - os.symlink("0.1", pkg2_current) - self.assertEqual([ - ("pkg1", "1.1", pkg1_current, True), - ("pkg2", "0.1", pkg2_current, True), - ], self._installed_packages_tuplify( - db.get_packages(all_versions=False))) - - def test_packages_all(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertEqual([], list(db.get_packages(all_versions=True))) - os.makedirs(os.path.join(self.temp_dir, "a", "pkg1", "1.0")) - os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg1", "1.1")) - os.symlink("1.1", os.path.join(self.temp_dir, "b", "pkg1", "current")) - os.makedirs(os.path.join(self.temp_dir, "b", "pkg2", "0.1")) - os.symlink("0.1", os.path.join(self.temp_dir, "b", "pkg2", "current")) - self.assertEqual([ - ("pkg1", "1.1", os.path.join(self.temp_dir, "b", "pkg1", "1.1"), - True), - ("pkg2", "0.1", os.path.join(self.temp_dir, "b", "pkg2", "0.1"), - True), - ("pkg1", "1.0", os.path.join(self.temp_dir, "a", "pkg1", "1.0"), - False), - ], self._installed_packages_tuplify( - db.get_packages(all_versions=True))) - - def test_manifest(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, db.get_manifest, "pkg", "1.0") - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, - db.get_manifest_as_string, "pkg", "1.0") - a_manifest_path = os.path.join( - self.temp_dir, "a", "pkg", "1.0", ".click", "info", "pkg.manifest") - a_manifest_obj = {"name": "pkg", "version": "1.0"} - with mkfile(a_manifest_path) as a_manifest: - json.dump(a_manifest_obj, a_manifest) - a_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "a", "pkg", "1.0") - self.assertEqual( - a_manifest_obj, - json_object_to_python(db.get_manifest("pkg", "1.0"))) - self.assertEqual( - a_manifest_obj, - json.loads(db.get_manifest_as_string("pkg", "1.0"))) - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, db.get_manifest, "pkg", "1.1") - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, - db.get_manifest_as_string, "pkg", "1.1") - b_manifest_path = os.path.join( - self.temp_dir, "b", "pkg", "1.1", ".click", "info", "pkg.manifest") - b_manifest_obj = {"name": "pkg", "version": "1.1"} - with mkfile(b_manifest_path) as b_manifest: - json.dump(b_manifest_obj, b_manifest) - b_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "b", "pkg", "1.1") - self.assertEqual( - b_manifest_obj, - json_object_to_python(db.get_manifest("pkg", "1.1"))) - self.assertEqual( - b_manifest_obj, - json.loads(db.get_manifest_as_string("pkg", "1.1"))) - - def test_manifest_bad(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - db = Click.DB() - db.read(db_dir=self.temp_dir) - manifest_path = os.path.join( - self.temp_dir, "a", "pkg", "1.0", ".click", "info", "pkg.manifest") - with mkfile(manifest_path) as manifest: - print("{bad syntax", file=manifest) - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, db.get_manifest, "pkg", "1.0") - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, - db.get_manifest_as_string, "pkg", "1.0") - manifest_path = os.path.join( - self.temp_dir, "a", "pkg", "1.1", ".click", "info", "pkg.manifest") - with mkfile(manifest_path) as manifest: - print("[0]", file=manifest) - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, db.get_manifest, "pkg", "1.0") - self.assertRaisesDatabaseError( - Click.DatabaseError.BAD_MANIFEST, - db.get_manifest_as_string, "pkg", "1.0") - - def test_manifests_current(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertEqual( - [], json_array_to_python(db.get_manifests(all_versions=False))) - self.assertEqual( - [], json.loads(db.get_manifests_as_string(all_versions=False))) - a_pkg1_manifest_path = os.path.join( - self.temp_dir, "a", "pkg1", "1.0", - ".click", "info", "pkg1.manifest") - a_pkg1_manifest_obj = {"name": "pkg1", "version": "1.0"} - with mkfile(a_pkg1_manifest_path) as a_pkg1_manifest: - json.dump(a_pkg1_manifest_obj, a_pkg1_manifest) - os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) - b_pkg1_manifest_path = os.path.join( - self.temp_dir, "b", "pkg1", "1.1", - ".click", "info", "pkg1.manifest") - b_pkg1_manifest_obj = {"name": "pkg1", "version": "1.1"} - with mkfile(b_pkg1_manifest_path) as b_pkg1_manifest: - json.dump(b_pkg1_manifest_obj, b_pkg1_manifest) - os.symlink("1.1", os.path.join(self.temp_dir, "b", "pkg1", "current")) - b_pkg2_manifest_path = os.path.join( - self.temp_dir, "b", "pkg2", "0.1", - ".click", "info", "pkg2.manifest") - b_pkg2_manifest_obj = {"name": "pkg2", "version": "0.1"} - with mkfile(b_pkg2_manifest_path) as b_pkg2_manifest: - json.dump(b_pkg2_manifest_obj, b_pkg2_manifest) - os.symlink("0.1", os.path.join(self.temp_dir, "b", "pkg2", "current")) - b_pkg1_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "b", "pkg1", "1.1") - b_pkg1_manifest_obj["_removable"] = 1 - b_pkg2_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "b", "pkg2", "0.1") - b_pkg2_manifest_obj["_removable"] = 1 - self.assertEqual( - [b_pkg1_manifest_obj, b_pkg2_manifest_obj], - json_array_to_python(db.get_manifests(all_versions=False))) - self.assertEqual( - [b_pkg1_manifest_obj, b_pkg2_manifest_obj], - json.loads(db.get_manifests_as_string(all_versions=False))) - - def test_manifests_all(self): - with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: - print("[Click Database]", file=a) - print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) - with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: - print("[Click Database]", file=b) - print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) - db = Click.DB() - db.read(db_dir=self.temp_dir) - self.assertEqual( - [], json_array_to_python(db.get_manifests(all_versions=True))) - self.assertEqual( - [], json.loads(db.get_manifests_as_string(all_versions=True))) - a_pkg1_manifest_path = os.path.join( - self.temp_dir, "a", "pkg1", "1.0", - ".click", "info", "pkg1.manifest") - a_pkg1_manifest_obj = {"name": "pkg1", "version": "1.0"} - with mkfile(a_pkg1_manifest_path) as a_pkg1_manifest: - json.dump(a_pkg1_manifest_obj, a_pkg1_manifest) - os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) - b_pkg1_manifest_path = os.path.join( - self.temp_dir, "b", "pkg1", "1.1", - ".click", "info", "pkg1.manifest") - b_pkg1_manifest_obj = {"name": "pkg1", "version": "1.1"} - with mkfile(b_pkg1_manifest_path) as b_pkg1_manifest: - json.dump(b_pkg1_manifest_obj, b_pkg1_manifest) - os.symlink("1.1", os.path.join(self.temp_dir, "b", "pkg1", "current")) - b_pkg2_manifest_path = os.path.join( - self.temp_dir, "b", "pkg2", "0.1", - ".click", "info", "pkg2.manifest") - b_pkg2_manifest_obj = {"name": "pkg2", "version": "0.1"} - with mkfile(b_pkg2_manifest_path) as b_pkg2_manifest: - json.dump(b_pkg2_manifest_obj, b_pkg2_manifest) - os.symlink("0.1", os.path.join(self.temp_dir, "b", "pkg2", "current")) - a_pkg1_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "a", "pkg1", "1.0") - a_pkg1_manifest_obj["_removable"] = 0 - b_pkg1_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "b", "pkg1", "1.1") - b_pkg1_manifest_obj["_removable"] = 1 - b_pkg2_manifest_obj["_directory"] = os.path.join( - self.temp_dir, "b", "pkg2", "0.1") - b_pkg2_manifest_obj["_removable"] = 1 - self.assertEqual( - [b_pkg1_manifest_obj, b_pkg2_manifest_obj, a_pkg1_manifest_obj], - json_array_to_python(db.get_manifests(all_versions=True))) - self.assertEqual( - [b_pkg1_manifest_obj, b_pkg2_manifest_obj, a_pkg1_manifest_obj], - json.loads(db.get_manifests_as_string(all_versions=True))) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_framework.py click-0.4.46+16.10.20170607.3/click/tests/test_framework.py --- click-0.4.45.1+16.10.20160916/click/tests/test_framework.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_framework.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,142 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.framework.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestClickFramework', - ] - - -import os - -from gi.repository import Click - -from click.tests.helpers import TestCase, touch - - -class TestClickFramework(TestCase): - def setUp(self): - super(TestClickFramework, self).setUp() - self.use_temp_dir() - - def _setup_frameworks(self, preloads, frameworks_dir=None, frameworks={}): - if frameworks_dir is None: - frameworks_dir = os.path.join(self.temp_dir, "frameworks") - Click.ensuredir(frameworks_dir) - for framework_name in frameworks: - framework_path = os.path.join( - frameworks_dir, "%s.framework" % framework_name) - with open(framework_path, "w") as framework: - for key, value in frameworks[framework_name].items(): - print("%s: %s" % (key, value), file=framework) - preloads["click_get_frameworks_dir"].side_effect = ( - lambda: self.make_string(frameworks_dir)) - - def test_open(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - self._setup_frameworks(preloads, frameworks={"framework-1": {}}) - Click.Framework.open("framework-1") - self.assertRaisesFrameworkError( - Click.FrameworkError.NO_SUCH_FRAMEWORK, - Click.Framework.open, "framework-2") - - def test_has_framework(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - self._setup_frameworks(preloads, frameworks={"framework-1": {}}) - self.assertTrue(Click.Framework.has_framework("framework-1")) - self.assertFalse(Click.Framework.has_framework("framework-2")) - - def test_get_frameworks(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - self._setup_frameworks( - preloads, - frameworks={"ubuntu-sdk-13.10": {}, "ubuntu-sdk-14.04": {}, - "ubuntu-sdk-14.10": {}}) - self.assertEqual( - ["ubuntu-sdk-13.10", "ubuntu-sdk-14.04", "ubuntu-sdk-14.10"], - sorted(f.props.name for f in Click.Framework.get_frameworks())) - - def test_get_frameworks_nonexistent(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - frameworks_dir = os.path.join(self.temp_dir, "nonexistent") - preloads["click_get_frameworks_dir"].side_effect = ( - lambda: self.make_string(frameworks_dir)) - self.assertEqual([], Click.Framework.get_frameworks()) - - def test_get_frameworks_not_directory(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = os.path.join(self.temp_dir, "file") - touch(path) - preloads["click_get_frameworks_dir"].side_effect = ( - lambda: self.make_string(path)) - self.assertEqual([], Click.Framework.get_frameworks()) - - def test_get_frameworks_ignores_other_files(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - frameworks_dir = os.path.join(self.temp_dir, "frameworks") - Click.ensuredir(frameworks_dir) - touch(os.path.join(frameworks_dir, "file")) - preloads["click_get_frameworks_dir"].side_effect = ( - lambda: self.make_string(frameworks_dir)) - self.assertEqual([], Click.Framework.get_frameworks()) - - def test_get_frameworks_ignores_unopenable_files(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - frameworks_dir = os.path.join(self.temp_dir, "frameworks") - Click.ensuredir(frameworks_dir) - os.symlink( - "nonexistent", os.path.join(frameworks_dir, "foo.framework")) - preloads["click_get_frameworks_dir"].side_effect = ( - lambda: self.make_string(frameworks_dir)) - self.assertEqual([], Click.Framework.get_frameworks()) - - def test_fields(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - self._setup_frameworks( - preloads, - frameworks={ - "ubuntu-sdk-14.04-qml": { - "base-name": "ubuntu-sdk", "base-version": "14.04", - }}) - framework = Click.Framework.open("ubuntu-sdk-14.04-qml") - self.assertCountEqual( - ["base-name", "base-version"], framework.get_fields()) - self.assertEqual("ubuntu-sdk", framework.get_field("base-name")) - self.assertEqual("14.04", framework.get_field("base-version")) - self.assertRaisesFrameworkError( - Click.FrameworkError.MISSING_FIELD, - framework.get_field, "nonexistent") - self.assertEqual("ubuntu-sdk", framework.get_base_name()) - self.assertEqual("14.04", framework.get_base_version()) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_hooks.py click-0.4.46+16.10.20170607.3/click/tests/test_hooks.py --- click-0.4.45.1+16.10.20160916/click/tests/test_hooks.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_hooks.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,1232 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.hooks.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - "TestClickHookSystemLevel", - "TestClickHookUserLevel", - "TestClickPatternFormatter", - "TestPackageInstallHooks", - "TestPackageRemoveHooks", - ] - - -from functools import partial -from itertools import takewhile -import json -import os -from textwrap import dedent - -from gi.repository import Click, GLib - -from click.tests.gimock_types import Passwd -from click.tests.helpers import TestCase, mkfile, mkfile_utf8 - - -class TestClickPatternFormatter(TestCase): - def _make_variant(self, **kwargs): - # pygobject's Variant creator can't handle maybe types, so we have - # to do this by hand. - builder = GLib.VariantBuilder.new(GLib.VariantType.new("a{sms}")) - for key, value in kwargs.items(): - entry = GLib.VariantBuilder.new(GLib.VariantType.new("{sms}")) - entry.add_value(GLib.Variant.new_string(key)) - entry.add_value(GLib.Variant.new_maybe( - GLib.VariantType.new("s"), - None if value is None else GLib.Variant.new_string(value))) - builder.add_value(entry.end()) - return builder.end() - - def test_expands_provided_keys(self): - self.assertEqual( - "foo.bar", - Click.pattern_format("foo.${key}", self._make_variant(key="bar"))) - self.assertEqual( - "foo.barbaz", - Click.pattern_format( - "foo.${key1}${key2}", - self._make_variant(key1="bar", key2="baz"))) - - def test_expands_missing_keys_to_empty_string(self): - self.assertEqual( - "xy", Click.pattern_format("x${key}y", self._make_variant())) - - def test_preserves_unmatched_dollar(self): - self.assertEqual("$", Click.pattern_format("$", self._make_variant())) - self.assertEqual( - "$ {foo}", Click.pattern_format("$ {foo}", self._make_variant())) - self.assertEqual( - "x${y", - Click.pattern_format("${key}${y", self._make_variant(key="x"))) - - def test_double_dollar(self): - self.assertEqual("$", Click.pattern_format("$$", self._make_variant())) - self.assertEqual( - "${foo}", Click.pattern_format("$${foo}", self._make_variant())) - self.assertEqual( - "x$y", - Click.pattern_format("x$$${key}", self._make_variant(key="y"))) - - def test_possible_expansion(self): - self.assertEqual( - {"id": "abc"}, - Click.pattern_possible_expansion( - "x_abc_1", "x_${id}_${num}", - self._make_variant(num="1")).unpack()) - self.assertIsNone( - Click.pattern_possible_expansion( - "x_abc_1", "x_${id}_${num}", self._make_variant(num="2"))) - - -class TestClickHookBase(TestCase): - - TEST_USER = "test-user" - - def setUp(self): - super(TestClickHookBase, self).setUp() - self.use_temp_dir() - self.db = Click.DB() - self.db.add(self.temp_dir) - self.spawn_calls = [] - - def _make_installed_click(self, package="test-1", version="1.0", - json_data={}, - make_current=True, - all_users=False): - with mkfile_utf8(os.path.join( - self.temp_dir, package, version, ".click", "info", - "%s.manifest" % package)) as f: - json.dump(json_data, f, ensure_ascii=False) - if make_current: - os.symlink( - version, os.path.join(self.temp_dir, package, "current")) - if all_users: - db = Click.User.for_all_users(self.db) - else: - db = Click.User.for_user(self.db, self.TEST_USER) - db.set_version(package, version) - - def _make_hook_file(self, content, hookname="test"): - hook_file = os.path.join(self.hooks_dir, "%s.hook" % hookname) - with mkfile(hook_file) as f: - print(content, file=f) - - def _setup_hooks_dir(self, preloads, hooks_dir=None): - if hooks_dir is None: - hooks_dir = self.temp_dir - preloads["click_get_hooks_dir"].side_effect = ( - lambda: self.make_string(hooks_dir)) - self.hooks_dir = hooks_dir - - def g_spawn_sync_side_effect(self, status_map, working_directory, argv, - envp, flags, child_setup, user_data, - standard_output, standard_error, exit_status, - error): - self.spawn_calls.append(list(takewhile(lambda x: x is not None, argv))) - if argv[0] in status_map: - exit_status[0] = status_map[argv[0]] - else: - self.delegate_to_original("g_spawn_sync") - return 0 - - -class TestClickHookSystemLevel(TestClickHookBase): - def test_open(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - Pattern: /usr/share/test/${id}.test - # Comment - Exec: test-update - User: root - """)) - hook = Click.Hook.open(self.db, "test") - self.assertCountEqual( - ["pattern", "exec", "user"], hook.get_fields()) - self.assertEqual( - "/usr/share/test/${id}.test", hook.get_field("pattern")) - self.assertEqual("test-update", hook.get_field("exec")) - self.assertRaisesHooksError( - Click.HooksError.MISSING_FIELD, hook.get_field, "nonexistent") - self.assertFalse(hook.props.is_user_level) - - def test_open_unopenable_file(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - os.symlink("nonexistent", os.path.join(self.hooks_dir, "foo.hook")) - self.assertRaisesHooksError( - Click.HooksError.NO_SUCH_HOOK, Click.Hook.open, self.db, "foo") - - def test_hook_name_absent(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: /usr/share/test/${id}.test") - hook = Click.Hook.open(self.db, "test") - self.assertEqual("test", hook.get_hook_name()) - - def test_hook_name_present(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - Pattern: /usr/share/test/${id}.test - Hook-Name: other""")) - hook = Click.Hook.open(self.db, "test") - self.assertEqual("other", hook.get_hook_name()) - - def test_invalid_app_id(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - Pattern: /usr/share/test/${id}.test - # Comment - Exec: test-update - User: root - """)) - hook = Click.Hook.open(self.db, "test") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_app_id, - "package", "0.1", "app_name") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_app_id, - "package", "0.1", "app/name") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_pattern, - "package", "0.1", "app_name") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_pattern, - "package", "0.1", "app/name") - - def test_short_id_invalid(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: /usr/share/test/${short-id}.test") - hook = Click.Hook.open(self.db, "test") - # It would perhaps be better if unrecognised $-expansions raised - # KeyError, but they don't right now. - self.assertEqual( - "/usr/share/test/.test", - hook.get_pattern("package", "0.1", "app-name", user_name=None)) - - def test_short_id_valid_with_single_version(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - Pattern: /usr/share/test/${short-id}.test - Single-Version: yes""")) - hook = Click.Hook.open(self.db, "test") - self.assertEqual( - "/usr/share/test/package_app-name.test", - hook.get_pattern("package", "0.1", "app-name", user_name=None)) - - def test_run_commands(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"/bin/sh": 0}) - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: - print("Exec: test-update", file=f) - print("User: root", file=f) - hook = Click.Hook.open(self.db, "test") - self.assertEqual( - "root", hook.get_run_commands_user(user_name=None)) - hook.run_commands(user_name=None) - self.assertEqual( - [[b"/bin/sh", b"-c", b"test-update"]], self.spawn_calls) - - def test_run_commands_fail(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"/bin/sh": 1}) - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: - print("Exec: test-update", file=f) - print("User: root", file=f) - hook = Click.Hook.open(self.db, "test") - self.assertRaisesHooksError( - Click.HooksError.COMMAND_FAILED, hook.run_commands, - user_name=None) - - def test_install_package(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: %s/${id}.test" % self.temp_dir) - os.makedirs( - os.path.join(self.temp_dir, "org.example.package", "1.0")) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo/bar", - user_name=None) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - target_path = os.path.join( - self.temp_dir, "org.example.package", "1.0", "foo", "bar") - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_install_package_trailing_slash(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: %s/${id}/" % self.temp_dir) - os.makedirs( - os.path.join(self.temp_dir, "org.example.package", "1.0")) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo", - user_name=None) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0") - target_path = os.path.join( - self.temp_dir, "org.example.package", "1.0", "foo") - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_install_package_uses_deepest_copy(self): - # If the same version of a package is unpacked in multiple - # databases, then we make sure the link points to the deepest copy, - # even if it already points somewhere else. It is important to be - # consistent about this since system hooks may only have a single - # target for any given application ID. - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: %s/${id}.test" % self.temp_dir) - underlay = os.path.join(self.temp_dir, "underlay") - overlay = os.path.join(self.temp_dir, "overlay") - db = Click.DB() - db.add(underlay) - db.add(overlay) - os.makedirs(os.path.join(underlay, "org.example.package", "1.0")) - os.makedirs(os.path.join(overlay, "org.example.package", "1.0")) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - underlay_target_path = os.path.join( - underlay, "org.example.package", "1.0", "foo") - overlay_target_path = os.path.join( - overlay, "org.example.package", "1.0", "foo") - os.symlink(overlay_target_path, symlink_path) - hook = Click.Hook.open(db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo", - user_name=None) - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(underlay_target_path, os.readlink(symlink_path)) - - def test_upgrade(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: %s/${id}.test" % self.temp_dir) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - os.symlink("old-target", symlink_path) - os.makedirs( - os.path.join(self.temp_dir, "org.example.package", "1.0")) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo/bar", - user_name=None) - target_path = os.path.join( - self.temp_dir, "org.example.package", "1.0", "foo", "bar") - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_remove_package(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file( - "Pattern: %s/${id}.test" % self.temp_dir) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - os.symlink("old-target", symlink_path) - hook = Click.Hook.open(self.db, "test") - hook.remove_package( - "org.example.package", "1.0", "test-app", user_name=None) - self.assertFalse(os.path.exists(symlink_path)) - - def test_install(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - self._make_hook_file( - "Pattern: %s/${id}.new" % self.temp_dir, - hookname="new") - self._make_installed_click("test-1", "1.0", json_data={ - "maintainer": - b"Unic\xc3\xb3de ".decode( - "UTF-8"), - "hooks": {"test1-app": {"new": "target-1"}}}) - self._make_installed_click("test-2", "2.0", json_data={ - "maintainer": - b"Unic\xc3\xb3de ".decode( - "UTF-8"), - "hooks": {"test1-app": {"new": "target-2"}}, - }) - hook = Click.Hook.open(self.db, "new") - hook.install(user_name=None) - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.new") - self.assertTrue(os.path.lexists(path_1)) - self.assertEqual( - os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), - os.readlink(path_1)) - path_2 = os.path.join(self.temp_dir, "test-2_test1-app_2.0.new") - self.assertTrue(os.path.lexists(path_2)) - self.assertEqual( - os.path.join(self.temp_dir, "test-2", "2.0", "target-2"), - os.readlink(path_2)) - - def test_remove(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - self._make_hook_file( - "Pattern: %s/${id}.old" % self.temp_dir, - hookname="old") - self._make_installed_click("test-1", "1.0", json_data={ - "hooks": {"test1-app": {"old": "target-1"}}}) - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.old") - os.symlink( - os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), - path_1) - self._make_installed_click("test-2", "2.0", json_data={ - "hooks": {"test2-app": {"old": "target-2"}}}) - path_2 = os.path.join(self.temp_dir, "test-2_test2-app_2.0.old") - os.symlink( - os.path.join(self.temp_dir, "test-2", "2.0", "target-2"), - path_2) - hook = Click.Hook.open(self.db, "old") - hook.remove(user_name=None) - self.assertFalse(os.path.exists(path_1)) - self.assertFalse(os.path.exists(path_2)) - - def test_sync(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - self._make_hook_file( - "Pattern: %s/${id}.test" % self.temp_dir) - self._make_installed_click("test-1", "1.0", json_data={ - "hooks": {"test1-app": {"test": "target-1"}}}) - self._make_installed_click( - "test-2", "1.0", make_current=False, - json_data={"hooks": {"test2-app": {"test": "target-2"}}}) - self._make_installed_click("test-2", "1.1", json_data={ - "hooks": {"test2-app": {"test": "target-2"}}}) - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.test") - os.symlink( - os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), - path_1) - path_2_1_0 = os.path.join( - self.temp_dir, "test-2_test2-app_1.0.test") - path_2_1_1 = os.path.join( - self.temp_dir, "test-2_test2-app_1.1.test") - path_3 = os.path.join(self.temp_dir, "test-3_test3-app_1.0.test") - os.symlink( - os.path.join(self.temp_dir, "test-3", "1.0", "target-3"), - path_3) - hook = Click.Hook.open(self.db, "test") - hook.sync(user_name=None) - self.assertTrue(os.path.lexists(path_1)) - self.assertEqual( - os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), - os.readlink(path_1)) - self.assertTrue(os.path.lexists(path_2_1_0)) - self.assertEqual( - os.path.join(self.temp_dir, "test-2", "1.0", "target-2"), - os.readlink(path_2_1_0)) - self.assertTrue(os.path.lexists(path_2_1_1)) - self.assertEqual( - os.path.join(self.temp_dir, "test-2", "1.1", "target-2"), - os.readlink(path_2_1_1)) - self.assertFalse(os.path.lexists(path_3)) - - -class TestClickHookUserLevel(TestClickHookBase): - def test_open(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: ${home}/.local/share/test/${id}.test - # Comment - Exec: test-update - """)) - hook = Click.Hook.open(self.db, "test") - self.assertCountEqual( - ["user-level", "pattern", "exec"], hook.get_fields()) - self.assertEqual( - "${home}/.local/share/test/${id}.test", - hook.get_field("pattern")) - self.assertEqual("test-update", hook.get_field("exec")) - self.assertRaisesHooksError( - Click.HooksError.MISSING_FIELD, hook.get_field, "nonexistent") - self.assertTrue(hook.props.is_user_level) - - def test_hook_name_absent(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: ${home}/.local/share/test/${id}.test""")) - hook = Click.Hook.open(self.db, "test") - self.assertEqual("test", hook.get_hook_name()) - - def test_hook_name_present(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: ${home}/.local/share/test/${id}.test - Hook-Name: other""")) - hook = Click.Hook.open(self.db, "test") - self.assertEqual("other", hook.get_hook_name()) - - def test_invalid_app_id(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: ${home}/.local/share/test/${id}.test - # Comment - Exec: test-update""")) - hook = Click.Hook.open(self.db, "test") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_app_id, - "package", "0.1", "app_name") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_app_id, - "package", "0.1", "app/name") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_pattern, - "package", "0.1", "app_name") - self.assertRaisesHooksError( - Click.HooksError.BAD_APP_NAME, hook.get_pattern, - "package", "0.1", "app/name") - - def test_short_id_valid(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "getpwnam") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_dir=b"/mock"))) - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: ${home}/.local/share/test/${short-id}.test - """)) - hook = Click.Hook.open(self.db, "test") - self.assertEqual( - "/mock/.local/share/test/package_app-name.test", - hook.get_pattern( - "package", "0.1", "app-name", user_name="mock")) - - def test_run_commands(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"/bin/sh": 0}) - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: - print("User-Level: yes", file=f) - print("Exec: test-update", file=f) - hook = Click.Hook.open(self.db, "test") - self.assertEqual( - self.TEST_USER, - hook.get_run_commands_user(user_name=self.TEST_USER)) - hook.run_commands(user_name=self.TEST_USER) - self.assertEqual( - [[b"/bin/sh", b"-c", b"test-update"]], self.spawn_calls) - - def test_run_commands_fail(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["g_spawn_sync"].side_effect = partial( - self.g_spawn_sync_side_effect, {b"/bin/sh": 1}) - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: - print("User-Level: yes", file=f) - print("Exec: test-update", file=f) - hook = Click.Hook.open(self.db, "test") - self.assertRaisesHooksError( - Click.HooksError.COMMAND_FAILED, hook.run_commands, - user_name=self.TEST_USER) - - def test_install_package(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = b"/home/test-user" - os.makedirs(os.path.join( - self.temp_dir, "org.example.package", "1.0")) - user_db = Click.User.for_user(self.db, self.TEST_USER) - user_db.set_version("org.example.package", "1.0") - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: %s/${id}.test""") % self.temp_dir) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo/bar", - user_name=self.TEST_USER) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - target_path = os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, - "org.example.package", "foo", "bar") - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_install_package_trailing_slash(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = b"/home/test-user" - os.makedirs(os.path.join( - self.temp_dir, "org.example.package", "1.0")) - user_db = Click.User.for_user(self.db, self.TEST_USER) - user_db.set_version("org.example.package", "1.0") - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: %s/${id}/""") % self.temp_dir) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo", - user_name=self.TEST_USER) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0") - target_path = os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, - "org.example.package", "foo") - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_install_package_removes_previous(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = b"/home/test-user" - os.makedirs(os.path.join( - self.temp_dir, "org.example.package", "1.0")) - os.makedirs(os.path.join( - self.temp_dir, "org.example.package", "1.1")) - user_db = Click.User.for_user(self.db, self.TEST_USER) - user_db.set_version("org.example.package", "1.0") - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: %s/${id}.test""") % self.temp_dir) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo/bar", - user_name=self.TEST_USER) - hook.install_package( - "org.example.package", "1.1", "test-app", "foo/bar", - user_name=self.TEST_USER) - old_symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.1.test") - self.assertFalse(os.path.islink(old_symlink_path)) - self.assertTrue(os.path.islink(symlink_path)) - target_path = os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, - "org.example.package", "foo", "bar") - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_upgrade(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = b"/home/test-user" - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - os.symlink("old-target", symlink_path) - os.makedirs(os.path.join( - self.temp_dir, "org.example.package", "1.0")) - user_db = Click.User.for_user(self.db, self.TEST_USER) - user_db.set_version("org.example.package", "1.0") - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: %s/${id}.test""") % self.temp_dir) - hook = Click.Hook.open(self.db, "test") - hook.install_package( - "org.example.package", "1.0", "test-app", "foo/bar", - user_name=self.TEST_USER) - target_path = os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, - "org.example.package", "foo", "bar") - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(target_path, os.readlink(symlink_path)) - - def test_remove_package(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = b"/home/test-user" - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: %s/${id}.test""") % self.temp_dir) - symlink_path = os.path.join( - self.temp_dir, "org.example.package_test-app_1.0.test") - os.symlink("old-target", symlink_path) - hook = Click.Hook.open(self.db, "test") - hook.remove_package( - "org.example.package", "1.0", "test-app", - user_name=self.TEST_USER) - self.assertFalse(os.path.exists(symlink_path)) - - def test_install(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", "getpwnam" - ) as (enter, preloads): - enter() - # Don't tell click about the hooks directory yet. - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = b"/home/test-user" - preloads["getpwnam"].side_effect = ( - lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) - with mkfile(os.path.join(self.temp_dir, "hooks", "new.hook")) as f: - print("User-Level: yes", file=f) - print("Pattern: %s/${id}.new" % self.temp_dir, file=f) - self._make_installed_click("test-1", "1.0", json_data={ - "maintainer": - b"Unic\xc3\xb3de ".decode( - "UTF-8"), - "hooks": {"test1-app": {"new": "target-1"}}, - }) - self._make_installed_click("test-2", "2.0", json_data={ - "maintainer": - b"Unic\xc3\xb3de ".decode( - "UTF-8"), - "hooks": {"test1-app": {"new": "target-2"}}, - }) - # Now tell click about the hooks directory and make sure it - # catches up correctly. - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - hook = Click.Hook.open(self.db, "new") - hook.install(user_name=None) - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.new") - self.assertTrue(os.path.lexists(path_1)) - self.assertEqual( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-1", - "target-1"), - os.readlink(path_1)) - path_2 = os.path.join(self.temp_dir, "test-2_test1-app_2.0.new") - self.assertTrue(os.path.lexists(path_2)) - self.assertEqual( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-2", - "target-2"), - os.readlink(path_2)) - - os.unlink(path_1) - os.unlink(path_2) - hook.install(user_name="another-user") - self.assertFalse(os.path.lexists(path_1)) - self.assertFalse(os.path.lexists(path_2)) - - hook.install(user_name=self.TEST_USER) - self.assertTrue(os.path.lexists(path_1)) - self.assertEqual( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-1", - "target-1"), - os.readlink(path_1)) - self.assertTrue(os.path.lexists(path_2)) - self.assertEqual( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-2", - "target-2"), - os.readlink(path_2)) - - def test_remove(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - # Don't tell click about the hooks directory yet. - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = b"/home/test-user" - with mkfile(os.path.join(self.temp_dir, "hooks", "old.hook")) as f: - print("User-Level: yes", file=f) - print("Pattern: %s/${id}.old" % self.temp_dir, file=f) - user_db = Click.User.for_user(self.db, self.TEST_USER) - self._make_installed_click("test-1", "1.0", json_data={ - "hooks": {"test1-app": {"old": "target-1"}}}) - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.old") - os.symlink( - os.path.join(user_db.get_path("test-1"), "target-1"), path_1) - self._make_installed_click("test-2", "2.0", json_data={ - "hooks": {"test2-app": {"old": "target-2"}}}) - path_2 = os.path.join(self.temp_dir, "test-2_test2-app_2.0.old") - os.symlink( - os.path.join(user_db.get_path("test-2"), "target-2"), path_2) - # Now tell click about the hooks directory and make sure it - # catches up correctly. - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - hook = Click.Hook.open(self.db, "old") - hook.remove(user_name=None) - self.assertFalse(os.path.exists(path_1)) - self.assertFalse(os.path.exists(path_2)) - - def test_sync(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - preloads["click_get_user_home"].return_value = b"/home/test-user" - self._setup_hooks_dir(preloads) - with mkfile( - os.path.join(self.temp_dir, "hooks", "test.hook")) as f: - print("User-Level: yes", file=f) - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) - self._make_installed_click("test-1", "1.0", json_data={ - "hooks": {"test1-app": {"test": "target-1"}}}) - self._make_installed_click("test-2", "1.1", json_data={ - "hooks": {"test2-app": {"test": "target-2"}}}) - path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.test") - os.symlink( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-1", - "target-1"), - path_1) - path_2 = os.path.join(self.temp_dir, "test-2_test2-app_1.1.test") - path_3 = os.path.join(self.temp_dir, "test-3_test3-app_1.0.test") - os.symlink( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-3", - "target-3"), - path_3) - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - hook = Click.Hook.open(self.db, "test") - hook.sync(user_name=self.TEST_USER) - self.assertTrue(os.path.lexists(path_1)) - self.assertEqual( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-1", - "target-1"), - os.readlink(path_1)) - self.assertTrue(os.path.lexists(path_2)) - self.assertEqual( - os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, "test-2", - "target-2"), - os.readlink(path_2)) - self.assertFalse(os.path.lexists(path_3)) - - def test_sync_without_user_db(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - preloads["click_get_user_home"].return_value = b"/home/test-user" - self._setup_hooks_dir(preloads) - with mkfile( - os.path.join(self.temp_dir, "hooks", "test.hook")) as f: - print("User-Level: yes", file=f) - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) - self._make_installed_click( - "test-package", "1.0", all_users=True, json_data={ - "hooks": {"test-app": {"test": "target"}}}) - self._setup_hooks_dir( - preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) - hook = Click.Hook.open(self.db, "test") - hook.sync(user_name=self.TEST_USER) - self.assertFalse(os.path.exists(os.path.join( - self.temp_dir, ".click", "users", self.TEST_USER, - "test-package"))) - - def test_sync_uses_deepest_copy(self): - # If the same version of a package is unpacked in multiple - # databases, then we make sure the user link points to the deepest - # copy, even if it already points somewhere else. It is important - # to be consistent about this since system hooks may only have a - # single target for any given application ID, and user links must - # match system hooks so that (for example) the version of an - # application run by a user has a matching system AppArmor profile. - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - ) as (enter, preloads): - enter() - self._setup_hooks_dir(preloads) - preloads["click_get_user_home"].return_value = b"/home/test-user" - with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: - print("User-Level: yes", file=f) - print("Pattern: %s/${id}.test" % self.temp_dir, file=f) - underlay = os.path.join(self.temp_dir, "underlay") - overlay = os.path.join(self.temp_dir, "overlay") - db = Click.DB() - db.add(underlay) - db.add(overlay) - underlay_unpacked = os.path.join(underlay, "test-package", "1.0") - overlay_unpacked = os.path.join(overlay, "test-package", "1.0") - os.makedirs(underlay_unpacked) - os.makedirs(overlay_unpacked) - manifest = {"hooks": {"test-app": {"test": "foo"}}} - with mkfile(os.path.join( - underlay_unpacked, ".click", "info", - "test-package.manifest")) as f: - json.dump(manifest, f) - with mkfile(os.path.join( - overlay_unpacked, ".click", "info", - "test-package.manifest")) as f: - json.dump(manifest, f) - underlay_user_link = os.path.join( - underlay, ".click", "users", "@all", "test-package") - overlay_user_link = os.path.join( - overlay, ".click", "users", self.TEST_USER, "test-package") - Click.ensuredir(os.path.dirname(underlay_user_link)) - os.symlink(underlay_unpacked, underlay_user_link) - Click.ensuredir(os.path.dirname(overlay_user_link)) - os.symlink(overlay_unpacked, overlay_user_link) - symlink_path = os.path.join( - self.temp_dir, "test-package_test-app_1.0.test") - underlay_target_path = os.path.join(underlay_user_link, "foo") - overlay_target_path = os.path.join(overlay_user_link, "foo") - os.symlink(overlay_target_path, symlink_path) - hook = Click.Hook.open(db, "test") - hook.sync(user_name=self.TEST_USER) - self.assertTrue(os.path.islink(underlay_user_link)) - self.assertEqual( - underlay_unpacked, os.readlink(underlay_user_link)) - self.assertFalse(os.path.islink(overlay_user_link)) - self.assertTrue(os.path.islink(symlink_path)) - self.assertEqual(underlay_target_path, os.readlink(symlink_path)) - - -class TestPackageInstallHooks(TestClickHookBase): - def test_removes_old_hooks(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - hooks_dir = os.path.join(self.temp_dir, "hooks") - self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) - with mkfile(os.path.join(hooks_dir, "unity.hook")) as f: - print("Pattern: %s/unity/${id}.scope" % self.temp_dir, file=f) - print("Single-Version: yes", file=f) - with mkfile(os.path.join(hooks_dir, "yelp-docs.hook")) as f: - print("Pattern: %s/yelp/docs-${id}.txt" % self.temp_dir, - file=f) - print("Single-Version: yes", file=f) - print("Hook-Name: yelp", file=f) - with mkfile(os.path.join(hooks_dir, "yelp-other.hook")) as f: - print("Pattern: %s/yelp/other-${id}.txt" % self.temp_dir, - file=f) - print("Single-Version: yes", file=f) - print("Hook-Name: yelp", file=f) - os.mkdir(os.path.join(self.temp_dir, "unity")) - unity_path = os.path.join( - self.temp_dir, "unity", "test_app_1.0.scope") - os.symlink("dummy", unity_path) - os.mkdir(os.path.join(self.temp_dir, "yelp")) - yelp_docs_path = os.path.join( - self.temp_dir, "yelp", "docs-test_app_1.0.txt") - os.symlink("dummy", yelp_docs_path) - yelp_other_path = os.path.join( - self.temp_dir, "yelp", "other-test_app_1.0.txt") - os.symlink("dummy", yelp_other_path) - self._make_installed_click("test", "1.0", make_current=False, json_data={ - "hooks": {"app": {"yelp": "foo.txt", "unity": "foo.scope"}}}) - self._make_installed_click("test", "1.1", json_data={}) - Click.package_install_hooks( - self.db, "test", "1.0", "1.1", user_name=None) - self.assertFalse(os.path.lexists(unity_path)) - self.assertFalse(os.path.lexists(yelp_docs_path)) - self.assertFalse(os.path.lexists(yelp_other_path)) - - def test_installs_new_hooks(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - hooks_dir = os.path.join(self.temp_dir, "hooks") - self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) - with mkfile(os.path.join(hooks_dir, "a.hook")) as f: - print("Pattern: %s/a/${id}.a" % self.temp_dir, file=f) - with mkfile(os.path.join(hooks_dir, "b-1.hook")) as f: - print("Pattern: %s/b/1-${id}.b" % self.temp_dir, file=f) - print("Hook-Name: b", file=f) - with mkfile(os.path.join(hooks_dir, "b-2.hook")) as f: - print("Pattern: %s/b/2-${id}.b" % self.temp_dir, file=f) - print("Hook-Name: b", file=f) - os.mkdir(os.path.join(self.temp_dir, "a")) - os.mkdir(os.path.join(self.temp_dir, "b")) - self._make_installed_click( - "test", "1.0", make_current=False, json_data={"hooks": {}}) - self._make_installed_click("test", "1.1", json_data={ - "hooks": {"app": {"a": "foo.a", "b": "foo.b"}}}) - Click.package_install_hooks( - self.db, "test", "1.0", "1.1", user_name=None) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "a", "test_app_1.1.a"))) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "b", "1-test_app_1.1.b"))) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "b", "2-test_app_1.1.b"))) - - def test_upgrades_existing_hooks(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - hooks_dir = os.path.join(self.temp_dir, "hooks") - self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) - with mkfile(os.path.join(hooks_dir, "a.hook")) as f: - print("Pattern: %s/a/${id}.a" % self.temp_dir, file=f) - print("Single-Version: yes", file=f) - with mkfile(os.path.join(hooks_dir, "b-1.hook")) as f: - print("Pattern: %s/b/1-${id}.b" % self.temp_dir, file=f) - print("Single-Version: yes", file=f) - print("Hook-Name: b", file=f) - with mkfile(os.path.join(hooks_dir, "b-2.hook")) as f: - print("Pattern: %s/b/2-${id}.b" % self.temp_dir, file=f) - print("Single-Version: yes", file=f) - print("Hook-Name: b", file=f) - with mkfile(os.path.join(hooks_dir, "c.hook")) as f: - print("Pattern: %s/c/${id}.c" % self.temp_dir, file=f) - print("Single-Version: yes", file=f) - os.mkdir(os.path.join(self.temp_dir, "a")) - a_path = os.path.join(self.temp_dir, "a", "test_app_1.0.a") - os.symlink("dummy", a_path) - os.mkdir(os.path.join(self.temp_dir, "b")) - b_irrelevant_path = os.path.join( - self.temp_dir, "b", "1-test_other-app_1.0.b") - os.symlink("dummy", b_irrelevant_path) - b_1_path = os.path.join(self.temp_dir, "b", "1-test_app_1.0.b") - os.symlink("dummy", b_1_path) - b_2_path = os.path.join(self.temp_dir, "b", "2-test_app_1.0.b") - os.symlink("dummy", b_2_path) - os.mkdir(os.path.join(self.temp_dir, "c")) - package_dir = os.path.join(self.temp_dir, "test") - with mkfile(os.path.join( - package_dir, "1.0", ".click", "info", - "test.manifest")) as f: - json.dump({"hooks": {"app": {"a": "foo.a", "b": "foo.b"}}}, f) - with mkfile(os.path.join( - package_dir, "1.1", ".click", "info", - "test.manifest")) as f: - json.dump( - {"hooks": { - "app": {"a": "foo.a", "b": "foo.b", "c": "foo.c"}} - }, f) - Click.package_install_hooks( - self.db, "test", "1.0", "1.1", user_name=None) - self.assertFalse(os.path.lexists(a_path)) - self.assertTrue(os.path.lexists(b_irrelevant_path)) - self.assertFalse(os.path.lexists(b_1_path)) - self.assertFalse(os.path.lexists(b_2_path)) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "a", "test_app_1.1.a"))) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "b", "1-test_app_1.1.b"))) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "b", "2-test_app_1.1.b"))) - self.assertTrue(os.path.lexists( - os.path.join(self.temp_dir, "c", "test_app_1.1.c"))) - - -class TestPackageRemoveHooks(TestClickHookBase): - def test_removes_hooks(self): - with self.run_in_subprocess( - "click_get_hooks_dir") as (enter, preloads): - enter() - hooks_dir = os.path.join(self.temp_dir, "hooks") - self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) - with mkfile(os.path.join(hooks_dir, "unity.hook")) as f: - print("Pattern: %s/unity/${id}.scope" % self.temp_dir, file=f) - with mkfile(os.path.join(hooks_dir, "yelp-docs.hook")) as f: - print("Pattern: %s/yelp/docs-${id}.txt" % self.temp_dir, - file=f) - print("Hook-Name: yelp", file=f) - with mkfile(os.path.join(hooks_dir, "yelp-other.hook")) as f: - print("Pattern: %s/yelp/other-${id}.txt" % self.temp_dir, - file=f) - print("Hook-Name: yelp", file=f) - os.mkdir(os.path.join(self.temp_dir, "unity")) - unity_path = os.path.join( - self.temp_dir, "unity", "test_app_1.0.scope") - os.symlink("dummy", unity_path) - os.mkdir(os.path.join(self.temp_dir, "yelp")) - yelp_docs_path = os.path.join( - self.temp_dir, "yelp", "docs-test_app_1.0.txt") - os.symlink("dummy", yelp_docs_path) - yelp_other_path = os.path.join( - self.temp_dir, "yelp", "other-test_app_1.0.txt") - os.symlink("dummy", yelp_other_path) - package_dir = os.path.join(self.temp_dir, "test") - with mkfile(os.path.join( - package_dir, "1.0", ".click", "info", - "test.manifest")) as f: - json.dump( - {"hooks": { - "app": {"yelp": "foo.txt", "unity": "foo.scope"}} - }, f) - Click.package_remove_hooks(self.db, "test", "1.0", user_name=None) - self.assertFalse(os.path.lexists(unity_path)) - self.assertFalse(os.path.lexists(yelp_docs_path)) - self.assertFalse(os.path.lexists(yelp_other_path)) - - -class TestPackageHooksValidateFramework(TestClickHookBase): - - def _setup_test_env(self, preloads): - preloads["click_get_user_home"].return_value = b"/home/test-user" - self._setup_hooks_dir( - preloads, os.path.join(self.temp_dir, "hooks")) - self._make_hook_file(dedent("""\ - User-Level: yes - Pattern: %s/${id}.test - """) % self.temp_dir) - self.hook_symlink_path = os.path.join( - self.temp_dir, "test-1_test1-app_1.0.test") - - def test_links_are_kept_on_validate_framework(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - "click_get_frameworks_dir", - ) as (enter, preloads): - enter() - self._setup_frameworks( - preloads, frameworks=["ubuntu-sdk-13.10"]) - self._setup_test_env(preloads) - self._make_installed_click(json_data={ - "framework": "ubuntu-sdk-13.10", - "hooks": { - "test1-app": {"test": "target-1"} - }, - }) - self.assertTrue(os.path.lexists(self.hook_symlink_path)) - # run the hooks - Click.run_user_hooks(self.db, user_name=self.TEST_USER) - self.assertTrue(os.path.lexists(self.hook_symlink_path)) - - def test_links_are_kept_multiple_frameworks(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - "click_get_frameworks_dir", - ) as (enter, preloads): - enter() - self._setup_frameworks( - preloads, frameworks=["ubuntu-sdk-14.04", "ubuntu-sdk-13.10"]) - self._setup_test_env(preloads) - self._make_installed_click(json_data={ - "framework": "ubuntu-sdk-13.10", - "hooks": { - "test1-app": {"test": "target-1"} - }, - }) - self.assertTrue(os.path.lexists(self.hook_symlink_path)) - # run the hooks - Click.run_user_hooks(self.db, user_name=self.TEST_USER) - self.assertTrue(os.path.lexists(self.hook_symlink_path)) - - def test_links_are_removed_on_missing_framework(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - "click_get_frameworks_dir", - ) as (enter, preloads): - enter() - self._setup_frameworks(preloads, frameworks=["missing"]) - self._setup_test_env(preloads) - self._make_installed_click(json_data={ - "framework": "ubuntu-sdk-13.10", - "hooks": { - "test1-app": {"test": "target-1"} - }, - }) - self.assertTrue(os.path.lexists(self.hook_symlink_path)) - # run the hooks - Click.run_user_hooks(self.db, user_name=self.TEST_USER) - self.assertFalse(os.path.lexists(self.hook_symlink_path)) - - def test_links_are_removed_on_missing_multiple_framework(self): - with self.run_in_subprocess( - "click_get_hooks_dir", "click_get_user_home", - "click_get_frameworks_dir", - ) as (enter, preloads): - enter() - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - self._setup_test_env(preloads) - self._make_installed_click(json_data={ - "framework": "ubuntu-sdk-13.10, ubuntu-sdk-13.10-html", - "hooks": { - "test1-app": {"test": "target-1"} - }, - }) - self.assertTrue(os.path.lexists(self.hook_symlink_path)) - # run the hooks - Click.run_user_hooks(self.db, user_name=self.TEST_USER) - self.assertFalse(os.path.lexists(self.hook_symlink_path)) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_install.py click-0.4.46+16.10.20170607.3/click/tests/test_install.py --- click-0.4.45.1+16.10.20160916/click/tests/test_install.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_install.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,771 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.install.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestClickInstaller', - ] - - -from contextlib import ( - closing, - contextmanager, - ) -import hashlib -import json -import os -import shutil -import stat -import subprocess -import tarfile - -from unittest import skipUnless - -from debian.deb822 import Deb822 -from gi.repository import Click - -from click.arfile import ArFile -from click.build import ClickBuilder -from click.install import ( - ClickInstaller, - ClickInstallerAuditError, - ClickInstallerPermissionDenied, -) -from click.preinst import static_preinst -from click.tests.helpers import ( - disable_logging, - mkfile, - mock, - TestCase, - touch, -) -from click.versions import spec_version - - -@contextmanager -def mock_quiet_subprocess_call(): - original_call = subprocess.call - - def side_effect(*args, **kwargs): - if "TEST_VERBOSE" in os.environ: - return original_call(*args, **kwargs) - else: - with open("/dev/null", "w") as devnull: - return original_call( - *args, stdout=devnull, stderr=devnull, **kwargs) - - with mock.patch("subprocess.call") as mock_call: - mock_call.side_effect = side_effect - yield mock_call - - -class TestClickInstaller(TestCase): - def setUp(self): - super(TestClickInstaller, self).setUp() - self.use_temp_dir() - self.db = Click.DB() - self.db.add(self.temp_dir) - # mock signature checks during the tests - self.debsig_patcher = mock.patch("click.install.DebsigVerify") - self.debsig_patcher.start() - - def tearDown(self): - self.debsig_patcher.stop() - - def make_fake_package(self, control_fields=None, manifest=None, - control_scripts=None, data_files=None): - """Build a fake package with given contents.""" - control_fields = {} if control_fields is None else control_fields - control_scripts = {} if control_scripts is None else control_scripts - data_files = {} if data_files is None else data_files - - data_dir = os.path.join(self.temp_dir, "fake-package") - control_dir = os.path.join(self.temp_dir, "DEBIAN") - with mkfile(os.path.join(control_dir, "control")) as control: - for key, value in control_fields.items(): - print('%s: %s' % (key.title(), value), file=control) - print(file=control) - if manifest is not None: - with mkfile(os.path.join(control_dir, "manifest")) as f: - json.dump(manifest, f) - print(file=f) - for name, contents in control_scripts.items(): - with mkfile(os.path.join(control_dir, name)) as script: - script.write(contents) - Click.ensuredir(data_dir) - for name, path in data_files.items(): - Click.ensuredir(os.path.dirname(os.path.join(data_dir, name))) - if path is None: - touch(os.path.join(data_dir, name)) - elif os.path.isdir(path): - shutil.copytree(path, os.path.join(data_dir, name)) - else: - shutil.copy2(path, os.path.join(data_dir, name)) - package_path = '%s.click' % data_dir - ClickBuilder()._pack( - self.temp_dir, control_dir, data_dir, package_path) - return package_path - - def test_audit_no_click_version(self): - path = self.make_fake_package() - self.assertRaisesRegex( - ClickInstallerAuditError, "No Click-Version field", - ClickInstaller(self.db).audit, path) - - def test_audit_bad_click_version(self): - path = self.make_fake_package(control_fields={"Click-Version": "|"}) - self.assertRaises(ValueError, ClickInstaller(self.db).audit, path) - - def test_audit_new_click_version(self): - path = self.make_fake_package(control_fields={"Click-Version": "999"}) - self.assertRaisesRegex( - ClickInstallerAuditError, - "Click-Version: 999 newer than maximum supported version .*", - ClickInstaller(self.db).audit, path) - - def test_audit_forbids_depends(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={ - "Click-Version": "0.2", - "Depends": "libc6", - }) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - self.assertRaisesRegex( - ClickInstallerAuditError, - "Depends field is forbidden in Click packages", - ClickInstaller(self.db).audit, path) - - def test_audit_forbids_maintscript(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - control_scripts={ - "preinst": "#! /bin/sh\n", - "postinst": "#! /bin/sh\n", - }) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - self.assertRaisesRegex( - ClickInstallerAuditError, - r"Maintainer scripts are forbidden in Click packages " - r"\(found: postinst preinst\)", - ClickInstaller(self.db).audit, path) - - def test_audit_requires_manifest(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - control_scripts={"preinst": static_preinst}) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - self.assertRaisesRegex( - ClickInstallerAuditError, "Package has no manifest", - ClickInstaller(self.db).audit, path) - - def test_audit_invalid_manifest_json(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - control_scripts={"manifest": "{", "preinst": static_preinst}) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - self.assertRaises(ValueError, ClickInstaller(self.db).audit, path) - - def test_audit_no_name(self): - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={}) - self.assertRaisesRegex( - ClickInstallerAuditError, 'No "name" entry in manifest', - ClickInstaller(self.db).audit, path) - - def test_audit_name_bad_character(self): - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={"name": "../evil"}) - self.assertRaisesRegex( - ClickInstallerAuditError, - 'Invalid character "/" in "name" entry: ../evil', - ClickInstaller(self.db).audit, path) - - def test_audit_no_version(self): - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={"name": "test-package"}) - self.assertRaisesRegex( - ClickInstallerAuditError, 'No "version" entry in manifest', - ClickInstaller(self.db).audit, path) - - def test_audit_no_framework(self): - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={"name": "test-package", "version": "1.0"}, - control_scripts={"preinst": static_preinst}) - self.assertRaisesRegex( - ClickInstallerAuditError, 'No "framework" entry in manifest', - ClickInstaller(self.db).audit, path) - - def test_audit_missing_framework(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "missing", - }, - control_scripts={"preinst": static_preinst}) - self._setup_frameworks(preloads, frameworks=["present"]) - self.assertRaisesRegex( - ClickInstallerAuditError, - 'Framework "missing" not present on system.*', - ClickInstaller(self.db).audit, path) - - # FIXME: we really want a unit test with a valid signature too - def test_audit_no_signature(self): - if not Click.find_on_path("debsig-verify"): - self.skipTest("this test needs debsig-verify") - path = self.make_fake_package( - control_fields={"Click-Version": "0.4"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "", - }) - self.debsig_patcher.stop() - self.assertRaisesRegex( - ClickInstallerAuditError, "Signature verification error", - ClickInstaller(self.db).audit, path) - self.debsig_patcher.start() - - @disable_logging - def test_audit_missing_framework_force(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "missing", - }) - self._setup_frameworks(preloads, frameworks=["present"]) - ClickInstaller(self.db, True).audit(path) - - def test_audit_passes_correct_package(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - installer = ClickInstaller(self.db) - self.assertEqual(("test-package", "1.0"), installer.audit(path)) - - def test_audit_multiple_frameworks(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.4"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": - "ubuntu-sdk-14.04-basic, ubuntu-sdk-14.04-webapps", - }, - control_scripts={"preinst": static_preinst}) - installer = ClickInstaller(self.db) - self._setup_frameworks(preloads, frameworks=["dummy"]) - self.assertRaisesRegex( - ClickInstallerAuditError, - 'Frameworks "ubuntu-sdk-14.04-basic", ' - '"ubuntu-sdk-14.04-webapps" not present on system.*', - installer.audit, path) - self._setup_frameworks( - preloads, frameworks=["dummy", "ubuntu-sdk-14.04-basic"]) - self.assertRaisesRegex( - ClickInstallerAuditError, - 'Framework "ubuntu-sdk-14.04-webapps" not present on ' - 'system.*', - installer.audit, path) - self._setup_frameworks( - preloads, frameworks=[ - "dummy", "ubuntu-sdk-14.04-basic", - "ubuntu-sdk-14.04-webapps", - ]) - self.assertEqual(("test-package", "1.0"), installer.audit(path)) - - def test_audit_missing_dot_slash(self): - # Manually construct a package with data paths that do not start - # with "./", which could be used to bypass path filtering. - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}, - data_files={".click/tmp.ci/manifest": None}) - # Repack without the leading "./". - data_dir = os.path.join(self.temp_dir, "fake-package") - data_tar_path = os.path.join(self.temp_dir, "data.tar.gz") - control_tar_path = os.path.join(self.temp_dir, "control.tar.gz") - package_path = '%s.click' % data_dir - with closing(tarfile.TarFile.open( - name=data_tar_path, mode="w:gz", format=tarfile.GNU_FORMAT - )) as data_tar: - data_tar.add( - os.path.join(data_dir, ".click"), arcname=".click") - with ArFile(name=package_path, mode="w") as package: - package.add_magic() - package.add_data("debian-binary", b"2.0\n") - package.add_data( - "_click-binary", ("%s\n" % spec_version).encode("UTF-8")) - package.add_file("control.tar.gz", control_tar_path) - package.add_file("data.tar.gz", data_tar_path) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer = ClickInstaller(self.db) - self.assertRaisesRegex( - ClickInstallerAuditError, - 'File name ".click" in package does not start with "./"', - installer.audit, path) - - def test_audit_broken_md5sums(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={ - "preinst": static_preinst, - "md5sums": "%s foo" % ("0" * 32), - }, - data_files={"foo": None}) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer = ClickInstaller(self.db) - self.assertRaises( - subprocess.CalledProcessError, installer.audit, - path, slow=True) - - def test_audit_matching_md5sums(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - data_path = os.path.join(self.temp_dir, "foo") - with mkfile(data_path) as data: - print("test", file=data) - with open(data_path, "rb") as data: - data_md5sum = hashlib.md5(data.read()).hexdigest() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={ - "preinst": static_preinst, - "md5sums": "%s foo" % data_md5sum, - }, - data_files={"foo": data_path}) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer = ClickInstaller(self.db) - self.assertEqual( - ("test-package", "1.0"), installer.audit(path, slow=True)) - - def test_no_write_permission(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={"Click-Version": "0.2"}, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}) - write_mask = ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - installer = ClickInstaller(self.db) - temp_dir_mode = os.stat(self.temp_dir).st_mode - try: - os.chmod(self.temp_dir, temp_dir_mode & write_mask) - self.assertRaises( - ClickInstallerPermissionDenied, installer.install, path) - finally: - os.chmod(self.temp_dir, temp_dir_mode) - - @skipUnless( - os.path.exists(ClickInstaller(None)._preload_path()), - "preload bits not built; installing packages will fail") - @mock.patch("gi.repository.Click.package_install_hooks") - def test_install(self, mock_package_install_hooks): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.0", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}, - data_files={"foo": None}) - root = os.path.join(self.temp_dir, "root") - db = Click.DB() - db.add(root) - installer = ClickInstaller(db) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer.install(path) - self.assertCountEqual([".click", "test-package"], os.listdir(root)) - package_dir = os.path.join(root, "test-package") - self.assertCountEqual(["1.0", "current"], os.listdir(package_dir)) - inst_dir = os.path.join(package_dir, "current") - self.assertTrue(os.path.islink(inst_dir)) - self.assertEqual("1.0", os.readlink(inst_dir)) - self.assertCountEqual([".click", "foo"], os.listdir(inst_dir)) - status_path = os.path.join(inst_dir, ".click", "status") - with open(status_path) as status_file: - # .readlines() avoids the need for a python-apt backport to - # Ubuntu 12.04 LTS. - status = list(Deb822.iter_paragraphs(status_file.readlines())) - self.assertEqual(1, len(status)) - self.assertEqual({ - "Package": "test-package", - "Status": "install ok installed", - "Version": "1.0", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, status[0]) - mock_package_install_hooks.assert_called_once_with( - db, "test-package", None, "1.0", user_name=None) - - @skipUnless( - os.path.exists(ClickInstaller(None)._preload_path()), - "preload bits not built; installing packages will fail") - def test_sandbox(self): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - original_call = subprocess.check_output - - def call_side_effect(*args, **kwargs): - return original_call( - ["touch", os.path.join(self.temp_dir, "sentinel")], - **kwargs) - - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.0", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, - manifest={ - "name": "test-package", - "version": "1.0", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}, - data_files={"foo": None}) - root = os.path.join(self.temp_dir, "root") - db = Click.DB() - db.add(root) - installer = ClickInstaller(db) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock.patch("subprocess.check_output") as mock_call: - mock_call.side_effect = call_side_effect - self.assertRaises( - subprocess.CalledProcessError, installer.install, path) - self.assertFalse( - os.path.exists(os.path.join(self.temp_dir, "sentinel"))) - - @skipUnless( - os.path.exists(ClickInstaller(None)._preload_path()), - "preload bits not built; installing packages will fail") - @mock.patch("gi.repository.Click.package_install_hooks") - def test_upgrade(self, mock_package_install_hooks): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - os.environ["TEST_QUIET"] = "1" - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.1", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, - manifest={ - "name": "test-package", - "version": "1.1", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}, - data_files={"foo": None}) - root = os.path.join(self.temp_dir, "root") - package_dir = os.path.join(root, "test-package") - inst_dir = os.path.join(package_dir, "current") - os.makedirs(os.path.join(package_dir, "1.0")) - os.symlink("1.0", inst_dir) - db = Click.DB() - db.add(root) - installer = ClickInstaller(db) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer.install(path) - self.assertCountEqual([".click", "test-package"], os.listdir(root)) - self.assertCountEqual(["1.1", "current"], os.listdir(package_dir)) - self.assertTrue(os.path.islink(inst_dir)) - self.assertEqual("1.1", os.readlink(inst_dir)) - self.assertCountEqual([".click", "foo"], os.listdir(inst_dir)) - status_path = os.path.join(inst_dir, ".click", "status") - with open(status_path) as status_file: - # .readlines() avoids the need for a python-apt backport to - # Ubuntu 12.04 LTS. - status = list(Deb822.iter_paragraphs(status_file.readlines())) - self.assertEqual(1, len(status)) - self.assertEqual({ - "Package": "test-package", - "Status": "install ok installed", - "Version": "1.1", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, status[0]) - mock_package_install_hooks.assert_called_once_with( - db, "test-package", "1.0", "1.1", user_name=None) - - def _get_mode(self, path): - return stat.S_IMODE(os.stat(path).st_mode) - - @skipUnless( - os.path.exists(ClickInstaller(None)._preload_path()), - "preload bits not built; installing packages will fail") - @mock.patch("gi.repository.Click.package_install_hooks") - def test_world_readable(self, mock_package_install_hooks): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - owner_only_file = os.path.join(self.temp_dir, "owner-only-file") - touch(owner_only_file) - os.chmod(owner_only_file, stat.S_IRUSR | stat.S_IWUSR) - owner_only_dir = os.path.join(self.temp_dir, "owner-only-dir") - os.mkdir(owner_only_dir, stat.S_IRWXU) - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.1", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, - manifest={ - "name": "test-package", - "version": "1.1", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}, - data_files={ - "world-readable-file": owner_only_file, - "world-readable-dir": owner_only_dir, - }) - root = os.path.join(self.temp_dir, "root") - db = Click.DB() - db.add(root) - installer = ClickInstaller(db) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer.install(path) - inst_dir = os.path.join(root, "test-package", "current") - self.assertEqual( - stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH, - self._get_mode(os.path.join(inst_dir, "world-readable-file"))) - self.assertEqual( - stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | - stat.S_IROTH | stat.S_IXOTH, - self._get_mode(os.path.join(inst_dir, "world-readable-dir"))) - - @skipUnless( - os.path.exists(ClickInstaller(None)._preload_path()), - "preload bits not built; installing packages will fail") - @mock.patch("gi.repository.Click.package_install_hooks") - @mock.patch("click.install.ClickInstaller._dpkg_architecture") - def test_single_architecture(self, mock_dpkg_architecture, - mock_package_install_hooks): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - mock_dpkg_architecture.return_value = "armhf" - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.1", - "Architecture": "armhf", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, - manifest={ - "name": "test-package", - "version": "1.1", - "framework": "ubuntu-sdk-13.10", - "architecture": "armhf", - }, - control_scripts={"preinst": static_preinst}) - root = os.path.join(self.temp_dir, "root") - db = Click.DB() - db.add(root) - installer = ClickInstaller(db) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer.install(path) - self.assertTrue( - os.path.exists(os.path.join(root, "test-package", "current"))) - - @skipUnless( - os.path.exists(ClickInstaller(None)._preload_path()), - "preload bits not built; installing packages will fail") - @mock.patch("gi.repository.Click.package_install_hooks") - @mock.patch("click.install.ClickInstaller._dpkg_architecture") - def test_multiple_architectures(self, mock_dpkg_architecture, - mock_package_install_hooks): - with self.run_in_subprocess( - "click_get_frameworks_dir") as (enter, preloads): - enter() - mock_dpkg_architecture.return_value = "armhf" - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.1", - "Architecture": "multi", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.2", - }, - manifest={ - "name": "test-package", - "version": "1.1", - "framework": "ubuntu-sdk-13.10", - "architecture": ["armhf", "i386"], - }, - control_scripts={"preinst": static_preinst}) - root = os.path.join(self.temp_dir, "root") - db = Click.DB() - db.add(root) - installer = ClickInstaller(db) - self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) - with mock_quiet_subprocess_call(): - installer.install(path) - self.assertTrue( - os.path.exists(os.path.join(root, "test-package", "current"))) - - @disable_logging - def test_reinstall_preinstalled(self): - # Attempting to reinstall a preinstalled version shouldn't actually - # reinstall it in an overlay database (which would cause - # irreconcilable confusion about the correct target for system hook - # symlinks), but should instead simply update the user registration. - path = self.make_fake_package( - control_fields={ - "Package": "test-package", - "Version": "1.1", - "Architecture": "all", - "Maintainer": "Foo Bar ", - "Description": "test", - "Click-Version": "0.4", - }, - manifest={ - "name": "test-package", - "version": "1.1", - "framework": "ubuntu-sdk-13.10", - }, - control_scripts={"preinst": static_preinst}) - underlay = os.path.join(self.temp_dir, "underlay") - overlay = os.path.join(self.temp_dir, "overlay") - db = Click.DB() - db.add(underlay) - installer = ClickInstaller(db, True) - with mock_quiet_subprocess_call(): - installer.install(path, all_users=True) - underlay_unpacked = os.path.join(underlay, "test-package", "1.1") - self.assertTrue(os.path.exists(underlay_unpacked)) - all_link = os.path.join( - underlay, ".click", "users", "@all", "test-package") - self.assertTrue(os.path.islink(all_link)) - self.assertEqual(underlay_unpacked, os.readlink(all_link)) - db.add(overlay) - registry = Click.User.for_user(db, "test-user") - registry.remove("test-package") - user_link = os.path.join( - overlay, ".click", "users", "test-user", "test-package") - self.assertTrue(os.path.islink(user_link)) - self.assertEqual("@hidden", os.readlink(user_link)) - installer = ClickInstaller(db, True) - with mock_quiet_subprocess_call(): - installer.install(path, user="test-user") - overlay_unpacked = os.path.join(overlay, "test-package", "1.1") - self.assertFalse(os.path.exists(overlay_unpacked)) - self.assertEqual("1.1", registry.get_version("test-package")) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_osextras.py click-0.4.46+16.10.20170607.3/click/tests/test_osextras.py --- click-0.4.45.1+16.10.20160916/click/tests/test_osextras.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_osextras.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,193 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.osextras.""" - -from __future__ import print_function -__all__ = [ - 'TestOSExtrasNative', - 'TestOSExtrasPython', - ] - - -import os - -from gi.repository import Click, GLib - -from click import osextras -from click.tests.helpers import TestCase, mock, touch - - -class TestOSExtrasBaseMixin: - def test_ensuredir_previously_missing(self): - new_dir = os.path.join(self.temp_dir, "dir") - self.mod.ensuredir(new_dir) - self.assertTrue(os.path.isdir(new_dir)) - - def test_ensuredir_previously_present(self): - new_dir = os.path.join(self.temp_dir, "dir") - os.mkdir(new_dir) - self.mod.ensuredir(new_dir) - self.assertTrue(os.path.isdir(new_dir)) - - def test_find_on_path_missing_environment(self): - os.environ.pop("PATH", None) - self.assertFalse(self.mod.find_on_path("ls")) - - def test_find_on_path_present_executable(self): - bin_dir = os.path.join(self.temp_dir, "bin") - program = os.path.join(bin_dir, "program") - touch(program) - os.chmod(program, 0o755) - os.environ["PATH"] = bin_dir - self.assertTrue(self.mod.find_on_path("program")) - - def test_find_on_path_present_not_executable(self): - bin_dir = os.path.join(self.temp_dir, "bin") - touch(os.path.join(bin_dir, "program")) - os.environ["PATH"] = bin_dir - self.assertFalse(self.mod.find_on_path("program")) - - def test_find_on_path_requires_regular_file(self): - bin_dir = os.path.join(self.temp_dir, "bin") - self.mod.ensuredir(os.path.join(bin_dir, "subdir")) - os.environ["PATH"] = bin_dir - self.assertFalse(self.mod.find_on_path("subdir")) - - def test_unlink_file_present(self): - path = os.path.join(self.temp_dir, "file") - touch(path) - self.mod.unlink_force(path) - self.assertFalse(os.path.exists(path)) - - def test_unlink_file_missing(self): - path = os.path.join(self.temp_dir, "file") - self.mod.unlink_force(path) - self.assertFalse(os.path.exists(path)) - - def test_symlink_file_present(self): - path = os.path.join(self.temp_dir, "link") - touch(path) - self.mod.symlink_force("source", path) - self.assertTrue(os.path.islink(path)) - self.assertEqual("source", os.readlink(path)) - - def test_symlink_link_present(self): - path = os.path.join(self.temp_dir, "link") - os.symlink("old", path) - self.mod.symlink_force("source", path) - self.assertTrue(os.path.islink(path)) - self.assertEqual("source", os.readlink(path)) - - def test_symlink_missing(self): - path = os.path.join(self.temp_dir, "link") - self.mod.symlink_force("source", path) - self.assertTrue(os.path.islink(path)) - self.assertEqual("source", os.readlink(path)) - - def test_umask(self): - old_mask = os.umask(0o040) - try: - self.assertEqual(0o040, self.mod.get_umask()) - os.umask(0o002) - self.assertEqual(0o002, self.mod.get_umask()) - finally: - os.umask(old_mask) - - -class TestOSExtrasNative(TestCase, TestOSExtrasBaseMixin): - def setUp(self): - super(TestOSExtrasNative, self).setUp() - self.use_temp_dir() - self.mod = Click - - def test_ensuredir_error(self): - path = os.path.join(self.temp_dir, "file") - touch(path) - self.assertRaisesFileError(mock.ANY, self.mod.ensuredir, path) - - def test_dir_read_name_directory_present(self): - new_dir = os.path.join(self.temp_dir, "dir") - touch(os.path.join(new_dir, "file")) - d = Click.Dir.open(new_dir, 0) - self.assertEqual("file", d.read_name()) - self.assertIsNone(d.read_name()) - - def test_dir_read_name_directory_missing(self): - new_dir = os.path.join(self.temp_dir, "dir") - d = Click.Dir.open(new_dir, 0) - self.assertIsNone(d.read_name()) - - def test_dir_open_error(self): - not_dir = os.path.join(self.temp_dir, "file") - touch(not_dir) - self.assertRaisesFileError( - GLib.FileError.NOTDIR, Click.Dir.open, not_dir, 0) - - def test_unlink_error(self): - path = os.path.join(self.temp_dir, "dir") - os.mkdir(path) - self.assertRaisesFileError(mock.ANY, self.mod.unlink_force, path) - - def test_symlink_unlink_error(self): - path = os.path.join(self.temp_dir, "dir") - os.mkdir(path) - self.assertRaisesFileError( - mock.ANY, self.mod.symlink_force, "source", path) - - def test_symlink_error(self): - path = os.path.join(self.temp_dir, "dir", "file") - self.assertRaisesFileError( - mock.ANY, self.mod.symlink_force, "source", path) - - -class TestOSExtrasPython(TestCase, TestOSExtrasBaseMixin): - def setUp(self): - super(TestOSExtrasPython, self).setUp() - self.use_temp_dir() - self.mod = osextras - - def test_ensuredir_oserror(self): - path = os.path.join(self.temp_dir, "file") - touch(path) - self.assertRaises(OSError, self.mod.ensuredir, path) - - def test_listdir_directory_present(self): - new_dir = os.path.join(self.temp_dir, "dir") - touch(os.path.join(new_dir, "file")) - self.assertEqual(["file"], osextras.listdir_force(new_dir)) - - def test_listdir_directory_missing(self): - new_dir = os.path.join(self.temp_dir, "dir") - self.assertEqual([], osextras.listdir_force(new_dir)) - - def test_listdir_oserror(self): - not_dir = os.path.join(self.temp_dir, "file") - touch(not_dir) - self.assertRaises(OSError, osextras.listdir_force, not_dir) - - def test_unlink_oserror(self): - path = os.path.join(self.temp_dir, "dir") - os.mkdir(path) - self.assertRaises(OSError, self.mod.unlink_force, path) - - def test_symlink_unlink_oserror(self): - path = os.path.join(self.temp_dir, "dir") - os.mkdir(path) - self.assertRaises(OSError, self.mod.symlink_force, "source", path) - - def test_symlink_oserror(self): - path = os.path.join(self.temp_dir, "dir", "file") - self.assertRaises(OSError, self.mod.symlink_force, "source", path) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_paths.py.in click-0.4.46+16.10.20170607.3/click/tests/test_paths.py.in --- click-0.4.45.1+16.10.20160916/click/tests/test_paths.py.in 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_paths.py.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.paths. - -This is mostly just to reduce noise in the coverage report. -""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestClickPaths', - ] - - -from gi.repository import Click - -from click.tests.helpers import TestCase - - -class TestClickPaths(TestCase): - def test_get_hooks_dir(self): - self.assertEqual("@pkgdatadir@/hooks", Click.get_hooks_dir()) - - def test_get_db_dir(self): - self.assertEqual("@sysconfdir@/click/databases", Click.get_db_dir()) - - def test_get_frameworks_dir(self): - self.assertEqual("@pkgdatadir@/frameworks", Click.get_frameworks_dir()) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_query.py click-0.4.46+16.10.20170607.3/click/tests/test_query.py --- click-0.4.45.1+16.10.20160916/click/tests/test_query.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_query.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -# Copyright (C) 2014 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.query.""" - -from __future__ import print_function -__all__ = [ - 'TestQuery', - ] - - -import os - -from gi.repository import Click - -from click.tests.helpers import TestCase, touch - - -class TestQuery(TestCase): - def setUp(self): - super(TestQuery, self).setUp() - self.use_temp_dir() - - def test_find_package_directory_missing(self): - path = os.path.join(self.temp_dir, "nonexistent") - self.assertRaisesQueryError( - Click.QueryError.PATH, Click.find_package_directory, path) - - def test_find_package_directory(self): - info = os.path.join(self.temp_dir, ".click", "info") - path = os.path.join(self.temp_dir, "file") - Click.ensuredir(info) - touch(path) - pkgdir = Click.find_package_directory(path) - self.assertEqual(self.temp_dir, pkgdir) - - def test_find_package_directory_outside(self): - self.assertRaisesQueryError( - Click.QueryError.NO_PACKAGE_DIR, Click.find_package_directory, - "/bin") diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_scripts.py click-0.4.46+16.10.20170607.3/click/tests/test_scripts.py --- click-0.4.45.1+16.10.20160916/click/tests/test_scripts.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_scripts.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,49 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Test that all top-level scripts work.""" - -__metaclass__ = type -__all__ = [ - 'TestScripts', - ] - -import os -import subprocess -from unittest import skipIf - -from click.tests.helpers import TestCase - - -class TestScripts(TestCase): - @skipIf('SKIP_SLOW_TESTS' in os.environ, 'Skipping slow tests') - def test_scripts(self): - self.longMessage = True - paths = [] - for dirpath, _, filenames in os.walk("bin"): - filenames = [ - n for n in filenames - if not n.startswith(".") and not n.endswith("~")] - for filename in filenames: - paths.append(os.path.join(dirpath, filename)) - for path in paths: - subp = subprocess.Popen( - [path, "--help"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) - err = subp.communicate()[1] - self.assertEqual("", err, "%s --help produced error output" % path) - self.assertEqual( - 0, subp.returncode, "%s --help exited non-zero" % path) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_static.py click-0.4.46+16.10.20170607.3/click/tests/test_static.py --- click-0.4.45.1+16.10.20160916/click/tests/test_static.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_static.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Test compliance with various static analysis tools.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestStatic', - ] - - -import os -import sys -from unittest import skipIf - -from pkg_resources import resource_filename - -try: - import pep8 -except ImportError: - pep8 = None -try: - import pyflakes - import pyflakes.api - import pyflakes.reporter -except ImportError: - pyflakes = None - - -from click.tests.helpers import TestCase - - -class TestStatic(TestCase): - def all_paths(self): - paths = [] - start_dir = os.path.dirname(resource_filename('click', '__init__.py')) - for dirpath, dirnames, filenames in os.walk(start_dir): - for ignore in ('doc', ".bzr", "__pycache__"): - if ignore in dirnames: - dirnames.remove(ignore) - filenames = [ - n for n in filenames - if not n.startswith(".") and not n.endswith("~")] - if dirpath.split(os.sep)[-1] == "bin": - for filename in filenames: - paths.append(os.path.join(dirpath, filename)) - else: - for filename in filenames: - if filename.endswith(".py"): - paths.append(os.path.join(dirpath, filename)) - return paths - - @skipIf('SKIP_SLOW_TESTS' in os.environ, 'Skipping slow tests') - @skipIf(pep8 is None, 'No pep8 package available') - def test_pep8_clean(self): - # https://github.com/jcrocholl/pep8/issues/103 - pep8_style = pep8.StyleGuide(ignore='E123') - result = pep8_style.check_files(self.all_paths()) - self.assertEqual(result.total_errors, 0) - - @skipIf('SKIP_SLOW_TESTS' in os.environ, 'Skipping slow tests') - @skipIf(pyflakes is None, 'No pyflakes package available') - def test_pyflakes_clean(self): - reporter = pyflakes.reporter.Reporter(sys.stdout, sys.stderr) - warnings = pyflakes.api.checkRecursive(self.all_paths(), reporter) - self.assertEqual(0, warnings) diff -Nru click-0.4.45.1+16.10.20160916/click/tests/test_user.py click-0.4.46+16.10.20170607.3/click/tests/test_user.py --- click-0.4.45.1+16.10.20160916/click/tests/test_user.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/tests/test_user.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,591 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Unit tests for click.user.""" - -from __future__ import print_function - -__metaclass__ = type -__all__ = [ - 'TestClickUser', - ] - - -import json -import os -import shutil -from textwrap import dedent - -from gi.repository import Click, GLib - -from click.json_helpers import json_array_to_python, json_object_to_python -from click.tests.gimock_types import Passwd -from click.tests.helpers import ( - TestCase, - make_installed_click, - mkfile, - make_file_with_content, - touch, -) - - -class TestClickUser(TestCase): - def setUp(self): - super(TestClickUser, self).setUp() - self.use_temp_dir() - self.db = Click.DB() - self.db.add(self.temp_dir) - - def _setUpMultiDB(self): - self.multi_db = Click.DB() - self.multi_db.add(os.path.join(self.temp_dir, "custom")) - self.multi_db.add(os.path.join(self.temp_dir, "click")) - user_dbs = [ - os.path.join( - self.multi_db.get(i).props.root, ".click", "users", "user") - for i in range(self.multi_db.props.size) - ] - a_1_0 = os.path.join(self.temp_dir, "custom", "a", "1.0") - os.makedirs(a_1_0) - with mkfile(os.path.join(a_1_0, ".click", "info", "a.manifest")) as m: - json.dump({"name": "a", "version": "1.0", - "hooks": {"a-app": {}}}, m) - b_2_0 = os.path.join(self.temp_dir, "custom", "b", "2.0") - os.makedirs(b_2_0) - with mkfile(os.path.join(b_2_0, ".click", "info", "b.manifest")) as m: - json.dump({"name": "b", "version": "2.0"}, m) - a_1_1 = os.path.join(self.temp_dir, "click", "a", "1.1") - os.makedirs(a_1_1) - with mkfile(os.path.join(a_1_1, ".click", "info", "a.manifest")) as m: - json.dump({"name": "a", "version": "1.1"}, m) - c_0_1 = os.path.join(self.temp_dir, "click", "c", "0.1") - os.makedirs(c_0_1) - with mkfile(os.path.join(c_0_1, ".click", "info", "c.manifest")) as m: - json.dump({"name": "c", "version": "0.1"}, m) - os.makedirs(user_dbs[0]) - os.symlink(a_1_0, os.path.join(user_dbs[0], "a")) - os.symlink(b_2_0, os.path.join(user_dbs[0], "b")) - os.makedirs(user_dbs[1]) - os.symlink(a_1_1, os.path.join(user_dbs[1], "a")) - os.symlink(c_0_1, os.path.join(user_dbs[1], "c")) - return user_dbs, Click.User.for_user(self.multi_db, "user") - - def test_new_no_db(self): - with self.run_in_subprocess( - "click_get_db_dir", "g_get_user_name") as (enter, preloads): - enter() - preloads["click_get_db_dir"].side_effect = ( - lambda: self.make_string(self.temp_dir)) - preloads["g_get_user_name"].side_effect = ( - lambda: self.make_string("test-user")) - db_root = os.path.join(self.temp_dir, "db") - os.makedirs(db_root) - with open(os.path.join(self.temp_dir, "db.conf"), "w") as f: - print("[Click Database]", file=f) - print("root = %s" % db_root, file=f) - registry = Click.User.for_user(None, None) - self.assertEqual( - os.path.join(db_root, ".click", "users", "test-user"), - registry.get_overlay_db()) - - def test_new_db_not_directory(self): - with self.run_in_subprocess( - "click_get_db_dir", "g_get_user_name") as (enter, preloads): - enter() - path = os.path.join(self.temp_dir, "file") - touch(path) - preloads["click_get_db_dir"].side_effect = ( - lambda: self.make_string(path)) - self.assertRaisesFileError( - GLib.FileError.NOTDIR, Click.User.for_user, None, None) - - def test_get_overlay_db(self): - self.assertEqual( - os.path.join(self.temp_dir, ".click", "users", "user"), - Click.User.for_user(self.db, "user").get_overlay_db()) - - def test_ensure_db_ownership(self): - # getpwnam results are cached properly, in a way that doesn't fail - # due to confusion with getpwnam returning a pointer to a static - # buffer. - with self.run_in_subprocess( - "chown", "geteuid", "getpwnam") as (enter, preloads): - enter() - preloads["geteuid"].return_value = 0 - getpwnam_result = Passwd() - - def getpwnam_side_effect(name): - if name == b"clickpkg": - getpwnam_result.pw_uid = 1 - getpwnam_result.pw_gid = 1 - else: - getpwnam_result.pw_uid = 2 - getpwnam_result.pw_gid = 2 - return self.make_pointer(getpwnam_result) - - preloads["getpwnam"].side_effect = getpwnam_side_effect - registry = Click.User.for_user(self.db, "user") - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - click_dir = os.path.join(self.temp_dir, ".click") - - registry.set_version("a", "1.0") - self.assertEqual(3, preloads["chown"].call_count) - preloads["chown"].assert_any_call(click_dir.encode(), 1, 1) - preloads["chown"].assert_any_call( - os.path.join(click_dir, "users").encode(), 1, 1) - preloads["chown"].assert_any_call( - os.path.join(click_dir, "users", "user").encode(), 2, 2) - - # Try again, now that both password file entries should be - # cached. - shutil.rmtree(os.path.join(self.temp_dir, ".click")) - preloads["chown"].reset_mock() - registry.set_version("a", "1.0") - self.assertEqual(3, preloads["chown"].call_count) - preloads["chown"].assert_any_call(click_dir.encode(), 1, 1) - preloads["chown"].assert_any_call( - os.path.join(click_dir, "users").encode(), 1, 1) - preloads["chown"].assert_any_call( - os.path.join(click_dir, "users", "user").encode(), 2, 2) - - def test_ensure_db_mkdir_fails(self): - with self.run_in_subprocess("mkdir") as (enter, preloads): - enter() - preloads["mkdir"].return_value = -1 - registry = Click.User.for_user(self.db, "user") - self.assertRaisesUserError( - Click.UserError.CREATE_DB, registry.set_version, "a", "1.0") - - def test_ensure_db_chown_fails(self): - with self.run_in_subprocess( - "chown", "geteuid", "getpwnam") as (enter, preloads): - enter() - preloads["geteuid"].return_value = 0 - getpwnam_result = Passwd() - - def getpwnam_side_effect(name): - if name == b"clickpkg": - getpwnam_result.pw_uid = 1 - getpwnam_result.pw_gid = 1 - else: - getpwnam_result.pw_uid = 2 - getpwnam_result.pw_gid = 2 - return self.make_pointer(getpwnam_result) - - preloads["getpwnam"].side_effect = getpwnam_side_effect - preloads["chown"].return_value = -1 - registry = Click.User.for_user(self.db, "user") - self.assertRaisesUserError( - Click.UserError.CHOWN_DB, registry.set_version, "a", "1.0") - - def test_ensure_db_getpwnam_fails(self): - with self.run_in_subprocess( - "geteuid", "getpwnam") as (enter, preloads): - enter() - preloads["geteuid"].return_value = 0 - preloads["getpwnam"].return_value = None - registry = Click.User.for_user(self.db, "user") - self.assertRaisesUserError( - Click.UserError.GETPWNAM, registry.set_version, "a", "1.0") - - def test_get_package_names_missing(self): - db = Click.DB() - db.add(os.path.join(self.temp_dir, "nonexistent")) - registry = Click.User.for_user(db, None) - self.assertEqual([], list(registry.get_package_names())) - - def test_get_package_names(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(registry.get_overlay_db()) - os.symlink("/1.0", os.path.join(registry.get_overlay_db(), "a")) - os.symlink("/1.1", os.path.join(registry.get_overlay_db(), "b")) - self.assertCountEqual(["a", "b"], list(registry.get_package_names())) - - def test_get_package_names_multiple_root(self): - _, registry = self._setUpMultiDB() - self.assertCountEqual( - ["a", "b", "c"], list(registry.get_package_names())) - - def test_get_version_missing(self): - registry = Click.User.for_user(self.db, "user") - self.assertRaisesUserError( - Click.UserError.NO_SUCH_PACKAGE, registry.get_version, "a") - self.assertFalse(registry.has_package_name("a")) - - def test_get_version(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(registry.get_overlay_db()) - os.symlink("/1.0", os.path.join(registry.get_overlay_db(), "a")) - self.assertEqual("1.0", registry.get_version("a")) - self.assertTrue(registry.has_package_name("a")) - - def test_get_version_multiple_root(self): - _, registry = self._setUpMultiDB() - self.assertEqual("1.1", registry.get_version("a")) - self.assertEqual("2.0", registry.get_version("b")) - self.assertEqual("0.1", registry.get_version("c")) - self.assertTrue(registry.has_package_name("a")) - self.assertTrue(registry.has_package_name("b")) - self.assertTrue(registry.has_package_name("c")) - - def test_set_version_missing_target(self): - registry = Click.User.for_user(self.db, "user") - self.assertRaisesDatabaseError( - Click.DatabaseError.DOES_NOT_EXIST, - registry.set_version, "a", "1.0") - - def test_set_version_missing(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - registry.set_version("a", "1.0") - path = os.path.join(registry.get_overlay_db(), "a") - self.assertTrue(os.path.islink(path)) - self.assertEqual( - os.path.join(self.temp_dir, "a", "1.0"), os.readlink(path)) - - def test_set_version_changed(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(registry.get_overlay_db()) - path = os.path.join(registry.get_overlay_db(), "a") - os.symlink("/1.0", path) - os.makedirs(os.path.join(self.temp_dir, "a", "1.1")) - registry.set_version("a", "1.1") - self.assertTrue(os.path.islink(path)) - self.assertEqual( - os.path.join(self.temp_dir, "a", "1.1"), os.readlink(path)) - - def test_set_version_multiple_root(self): - user_dbs, registry = self._setUpMultiDB() - - os.makedirs(os.path.join(self.multi_db.get(1).props.root, "a", "1.2")) - registry.set_version("a", "1.2") - a_underlay = os.path.join(user_dbs[0], "a") - a_overlay = os.path.join(user_dbs[1], "a") - self.assertTrue(os.path.islink(a_underlay)) - self.assertEqual( - os.path.join(self.multi_db.get(0).props.root, "a", "1.0"), - os.readlink(a_underlay)) - self.assertTrue(os.path.islink(a_overlay)) - self.assertEqual( - os.path.join(self.multi_db.get(1).props.root, "a", "1.2"), - os.readlink(a_overlay)) - - os.makedirs(os.path.join(self.multi_db.get(1).props.root, "b", "2.1")) - registry.set_version("b", "2.1") - b_underlay = os.path.join(user_dbs[0], "b") - b_overlay = os.path.join(user_dbs[1], "b") - self.assertTrue(os.path.islink(b_underlay)) - self.assertEqual( - os.path.join(self.multi_db.get(0).props.root, "b", "2.0"), - os.readlink(b_underlay)) - self.assertTrue(os.path.islink(b_overlay)) - self.assertEqual( - os.path.join(self.multi_db.get(1).props.root, "b", "2.1"), - os.readlink(b_overlay)) - - os.makedirs(os.path.join(self.multi_db.get(1).props.root, "c", "0.2")) - registry.set_version("c", "0.2") - c_underlay = os.path.join(user_dbs[0], "c") - c_overlay = os.path.join(user_dbs[1], "c") - self.assertFalse(os.path.islink(c_underlay)) - self.assertTrue(os.path.islink(c_overlay)) - self.assertEqual( - os.path.join(self.multi_db.get(1).props.root, "c", "0.2"), - os.readlink(c_overlay)) - - os.makedirs(os.path.join(self.multi_db.get(1).props.root, "d", "3.0")) - registry.set_version("d", "3.0") - d_underlay = os.path.join(user_dbs[0], "d") - d_overlay = os.path.join(user_dbs[1], "d") - self.assertFalse(os.path.islink(d_underlay)) - self.assertTrue(os.path.islink(d_overlay)) - self.assertEqual( - os.path.join(self.multi_db.get(1).props.root, "d", "3.0"), - os.readlink(d_overlay)) - - def test_set_version_restore_to_underlay(self): - user_dbs, registry = self._setUpMultiDB() - a_underlay = os.path.join(user_dbs[0], "a") - a_overlay = os.path.join(user_dbs[1], "a") - - # Initial state: 1.0 in underlay, 1.1 in overlay. - self.assertTrue(os.path.islink(a_underlay)) - self.assertEqual( - os.path.join(self.multi_db.get(0).props.root, "a", "1.0"), - os.readlink(a_underlay)) - self.assertTrue(os.path.islink(a_overlay)) - self.assertEqual( - os.path.join(self.multi_db.get(1).props.root, "a", "1.1"), - os.readlink(a_overlay)) - - # Setting to 1.0 (version in underlay) removes overlay link. - registry.set_version("a", "1.0") - self.assertTrue(os.path.islink(a_underlay)) - self.assertEqual( - os.path.join(self.multi_db.get(0).props.root, "a", "1.0"), - os.readlink(a_underlay)) - self.assertFalse(os.path.islink(a_overlay)) - - def test_remove_missing(self): - registry = Click.User.for_user(self.db, "user") - self.assertRaisesUserError( - Click.UserError.NO_SUCH_PACKAGE, registry.remove, "a") - - def test_remove(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(registry.get_overlay_db()) - path = os.path.join(registry.get_overlay_db(), "a") - os.symlink("/1.0", path) - registry.remove("a") - self.assertFalse(os.path.exists(path)) - - def test_remove_multiple_root(self): - user_dbs, registry = self._setUpMultiDB() - registry.remove("a") - self.assertFalse(os.path.exists(os.path.join(user_dbs[1], "a"))) - # Exposed underlay. - self.assertEqual("1.0", registry.get_version("a")) - registry.remove("b") - # Hidden. - self.assertEqual( - "@hidden", os.readlink(os.path.join(user_dbs[1], "b"))) - self.assertFalse(registry.has_package_name("b")) - registry.remove("c") - self.assertFalse(os.path.exists(os.path.join(user_dbs[1], "c"))) - self.assertFalse(registry.has_package_name("c")) - self.assertRaisesUserError( - Click.UserError.NO_SUCH_PACKAGE, registry.remove, "d") - - def test_remove_multiple_root_creates_overlay_directory(self): - multi_db = Click.DB() - multi_db.add(os.path.join(self.temp_dir, "preinstalled")) - multi_db.add(os.path.join(self.temp_dir, "click")) - user_dbs = [ - os.path.join(multi_db.get(i).props.root, ".click", "users", "user") - for i in range(multi_db.props.size) - ] - a_1_0 = os.path.join(self.temp_dir, "preinstalled", "a", "1.0") - os.makedirs(a_1_0) - os.makedirs(user_dbs[0]) - os.symlink(a_1_0, os.path.join(user_dbs[0], "a")) - self.assertFalse(os.path.exists(user_dbs[1])) - registry = Click.User.for_user(multi_db, "user") - self.assertEqual("1.0", registry.get_version("a")) - registry.remove("a") - self.assertFalse(registry.has_package_name("a")) - self.assertEqual( - "@hidden", os.readlink(os.path.join(user_dbs[1], "a"))) - - def test_get_path(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - registry.set_version("a", "1.0") - self.assertEqual( - os.path.join(registry.get_overlay_db(), "a"), - registry.get_path("a")) - - def test_get_path_multiple_root(self): - user_dbs, registry = self._setUpMultiDB() - self.assertEqual( - os.path.join(user_dbs[1], "a"), registry.get_path("a")) - self.assertEqual( - os.path.join(user_dbs[0], "b"), registry.get_path("b")) - self.assertEqual( - os.path.join(user_dbs[1], "c"), registry.get_path("c")) - self.assertRaisesUserError( - Click.UserError.NO_SUCH_PACKAGE, registry.get_path, "d") - - def test_get_manifest(self): - registry = Click.User.for_user(self.db, "user") - manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - manifest_obj = {"name": "a", "version": "1.0"} - with mkfile(manifest_path) as manifest: - json.dump(manifest_obj, manifest) - manifest_obj["_directory"] = os.path.join( - registry.get_overlay_db(), "a") - manifest_obj["_removable"] = 1 - registry.set_version("a", "1.0") - self.assertEqual( - manifest_obj, json_object_to_python(registry.get_manifest("a"))) - self.assertEqual( - manifest_obj, json.loads(registry.get_manifest_as_string("a"))) - - def test_get_manifest_multiple_root(self): - user_dbs, registry = self._setUpMultiDB() - expected_a = { - "name": "a", - "version": "1.1", - "_directory": os.path.join(user_dbs[1], "a"), - "_removable": 1, - } - self.assertEqual( - expected_a, json_object_to_python(registry.get_manifest("a"))) - self.assertEqual( - expected_a, json.loads(registry.get_manifest_as_string("a"))) - expected_b = { - "name": "b", - "version": "2.0", - "_directory": os.path.join(user_dbs[0], "b"), - "_removable": 1, - } - self.assertEqual( - expected_b, json_object_to_python(registry.get_manifest("b"))) - self.assertEqual( - expected_b, json.loads(registry.get_manifest_as_string("b"))) - expected_c = { - "name": "c", - "version": "0.1", - "_directory": os.path.join(user_dbs[1], "c"), - "_removable": 1, - } - self.assertEqual( - expected_c, json_object_to_python(registry.get_manifest("c"))) - self.assertEqual( - expected_c, json.loads(registry.get_manifest_as_string("c"))) - self.assertRaisesUserError( - Click.UserError.NO_SUCH_PACKAGE, registry.get_path, "d") - - def test_get_manifests(self): - registry = Click.User.for_user(self.db, "user") - a_manifest_path = os.path.join( - self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") - a_manifest_obj = {"name": "a", "version": "1.0"} - with mkfile(a_manifest_path) as a_manifest: - json.dump(a_manifest_obj, a_manifest) - registry.set_version("a", "1.0") - b_manifest_path = os.path.join( - self.temp_dir, "b", "2.0", ".click", "info", "b.manifest") - b_manifest_obj = {"name": "b", "version": "2.0"} - with mkfile(b_manifest_path) as b_manifest: - json.dump(b_manifest_obj, b_manifest) - registry.set_version("b", "2.0") - a_manifest_obj["_directory"] = os.path.join( - registry.get_overlay_db(), "a") - a_manifest_obj["_removable"] = 1 - b_manifest_obj["_directory"] = os.path.join( - registry.get_overlay_db(), "b") - b_manifest_obj["_removable"] = 1 - self.assertEqual( - [a_manifest_obj, b_manifest_obj], - json_array_to_python(registry.get_manifests())) - self.assertEqual( - [a_manifest_obj, b_manifest_obj], - json.loads(registry.get_manifests_as_string())) - - def test_get_manifests_multiple_root(self): - user_dbs, registry = self._setUpMultiDB() - a_manifest_obj = { - "name": "a", - "version": "1.1", - "_directory": os.path.join(user_dbs[1], "a"), - "_removable": 1, - } - b_manifest_obj = { - "name": "b", - "version": "2.0", - "_directory": os.path.join(user_dbs[0], "b"), - "_removable": 1, - } - c_manifest_obj = { - "name": "c", - "version": "0.1", - "_directory": os.path.join(user_dbs[1], "c"), - "_removable": 1, - } - self.assertEqual( - [a_manifest_obj, c_manifest_obj, b_manifest_obj], - json_array_to_python(registry.get_manifests())) - self.assertEqual( - [a_manifest_obj, c_manifest_obj, b_manifest_obj], - json.loads(registry.get_manifests_as_string())) - registry.remove("b") - self.assertEqual( - "@hidden", os.readlink(os.path.join(user_dbs[1], "b"))) - self.assertEqual( - [a_manifest_obj, c_manifest_obj], - json_array_to_python(registry.get_manifests())) - self.assertEqual( - [a_manifest_obj, c_manifest_obj], - json.loads(registry.get_manifests_as_string())) - - def test_is_removable(self): - registry = Click.User.for_user(self.db, "user") - os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) - registry.set_version("a", "1.0") - self.assertTrue(registry.is_removable("a")) - - def test_is_removable_multiple_root(self): - user_dbs, registry = self._setUpMultiDB() - self.assertTrue(registry.is_removable("a")) - self.assertTrue(registry.is_removable("b")) - self.assertTrue(registry.is_removable("c")) - self.assertFalse(registry.is_removable("d")) - - def test_hidden(self): - user_dbs, registry = self._setUpMultiDB() - b_overlay = os.path.join(user_dbs[1], "b") - - registry.remove("b") - self.assertFalse(registry.has_package_name("b")) - self.assertTrue(os.path.islink(b_overlay)) - self.assertEqual("@hidden", os.readlink(b_overlay)) - self.assertRaisesUserError( - Click.UserError.HIDDEN_PACKAGE, registry.get_version, "b") - self.assertRaisesUserError( - Click.UserError.HIDDEN_PACKAGE, registry.get_path, "b") - self.assertFalse(registry.is_removable("b")) - - registry.set_version("b", "2.0") - self.assertTrue(registry.has_package_name("b")) - self.assertTrue(os.path.islink(b_overlay)) - self.assertEqual( - os.path.join(self.multi_db.get(0).props.root, "b", "2.0"), - os.readlink(b_overlay)) - self.assertEqual("2.0", registry.get_version("b")) - self.assertEqual(b_overlay, registry.get_path("b")) - self.assertTrue(registry.is_removable("b")) - - -class StopAppTestCase(TestCase): - - def setUp(self): - super(StopAppTestCase, self).setUp() - self.use_temp_dir() - self.db = Click.DB() - self.db.add(self.temp_dir) - - # setup fake app_stop - fake_app_stop = os.path.join(self.temp_dir, "bin", "ubuntu-app-stop") - self.fake_app_stop_output = os.path.join( - self.temp_dir, "fake-app-stop.out") - fake_app_stop_content = dedent("""\ - #!/bin/sh - echo "$@" >> %s - """ % self.fake_app_stop_output) - make_file_with_content(fake_app_stop, fake_app_stop_content, 0o755) - # its ok to modify env here, click.helpers.TestCase will take care - # of it - os.environ["PATH"] = "%s:%s" % ( - os.path.dirname(fake_app_stop), os.environ["PATH"]) - - def test_app_stops_on_remove(self): - make_installed_click(self.db, self.temp_dir, "meep", "2.0", - {"hooks": {"a-app": {}}}) - registry = Click.User.for_user(self.db, "user") - registry.remove("meep") - # ensure that stop was called with the right app - with open(self.fake_app_stop_output) as f: - self.assertEqual("meep_a-app_2.0", f.read().strip()) diff -Nru click-0.4.45.1+16.10.20160916/click/versions.py click-0.4.46+16.10.20170607.3/click/versions.py --- click-0.4.45.1+16.10.20160916/click/versions.py 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click/versions.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ -# Copyright (C) 2013 Canonical Ltd. -# Author: Colin Watson - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 3 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Click package versioning.""" - -spec_version = "0.4" diff -Nru click-0.4.45.1+16.10.20160916/click_package/arfile.py click-0.4.46+16.10.20170607.3/click_package/arfile.py --- click-0.4.45.1+16.10.20160916/click_package/arfile.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/arfile.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,102 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Basic support for writing ar archive files. + +We do things this way so that Click packages can be created with minimal +dependencies (e.g. on non-Ubuntu systems). No read support is needed, since +Click packages are always installed on systems that have dpkg. + +Some method names and general approach come from the tarfile module in +Python's standard library; details of the format come from dpkg. +""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'ArFile', + ] + +import os +import shutil +import time + + +class ArFile: + def __init__(self, name=None, mode="w", fileobj=None): + if mode != "w": + raise ValueError("only mode 'w' is supported") + self.mode = mode + self.real_mode = "wb" + + if fileobj: + if name is None and hasattr(fileobj, "name"): + name = fileobj.name + if hasattr(fileobj, "mode"): + if fileobj.mode != "wb": + raise ValueError("fileobj must be opened with mode='wb'") + self._mode = fileobj.mode + self.opened_fileobj = False + else: + fileobj = open(name, self.real_mode) + self.opened_fileobj = True + self.name = name + self.fileobj = fileobj + self.closed = False + + def close(self): + if self.opened_fileobj: + self.fileobj.close() + self.closed = True + + def _check(self): + if self.closed: + raise IOError("ArFile %s is closed" % self.name) + + def __enter__(self): + self._check() + return self + + def __exit__(self, *args): + self.close() + + def add_magic(self): + self.fileobj.write(b"!\n") + + def add_header(self, name, size): + if len(name) > 15: + raise ValueError("ar member name '%s' length too long" % name) + if size > 9999999999: + raise ValueError("ar member size %d too large" % size) + header = ("%-16s%-12u0 0 100644 %-10d`\n" % ( + name, int(time.time()), size)).encode() + assert len(header) == 60 # sizeof(struct ar_hdr) + self.fileobj.write(header) + + def add_data(self, name, data): + size = len(data) + self.add_header(name, size) + self.fileobj.write(data) + if size & 1: + self.fileobj.write(b"\n") # padding + + def add_file(self, name, path): + with open(path, "rb") as fobj: + size = os.fstat(fobj.fileno()).st_size + self.add_header(name, size) + shutil.copyfileobj(fobj, self.fileobj) + if size & 1: + self.fileobj.write(b"\n") # padding diff -Nru click-0.4.45.1+16.10.20160916/click_package/build.py click-0.4.46+16.10.20170607.3/click_package/build.py --- click-0.4.45.1+16.10.20160916/click_package/build.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/build.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,341 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Building Click packages.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'ClickBuildError', + 'ClickBuilder', + 'ClickSourceBuilder', + ] + + +import contextlib +import hashlib +import io +import json +import os +import re +import shutil +import subprocess +import sys +import tarfile +import tempfile +from textwrap import dedent + +try: + import apt_pkg + apt_pkg.init_system() +except ImportError: + # "click build" is required to work with only the Python standard library. + pass + +from click_package import osextras +from click_package.arfile import ArFile +from click_package.preinst import static_preinst +from click_package.versions import spec_version + +from click_package.framework import ( + validate_framework, + ClickFrameworkInvalid, +) + + +@contextlib.contextmanager +def make_temp_dir(): + temp_dir = tempfile.mkdtemp(prefix="click") + try: + os.chmod(temp_dir, 0o755) + yield temp_dir + finally: + shutil.rmtree(temp_dir) + + +class FakerootTarFile(tarfile.TarFile): + """A version of TarFile which pretends all files are owned by root:root.""" + + def gettarinfo(self, *args, **kwargs): + tarinfo = super(FakerootTarFile, self).gettarinfo(*args, **kwargs) + tarinfo.uid = tarinfo.gid = 0 + tarinfo.uname = tarinfo.gname = "root" + return tarinfo + + +class ClickBuildError(Exception): + pass + + +class ClickBuilderBase: + def __init__(self): + self.file_map = {} + # From @Dpkg::Source::Package::tar_ignore_default_pattern. + # (more in ClickSourceBuilder) + self._ignore_patterns = [ + "*.click", + ".*.sw?", + "*~", + ",,*", + ".[#~]*", + ".arch-ids", + ".arch-inventory", + ".bzr", + ".bzr-builddeb", + ".bzr.backup", + ".bzr.tags", + ".bzrignore", + ".cvsignore", + ".git", + ".gitattributes", + ".gitignore", + ".gitmodules", + ".hg", + ".hgignore", + ".hgsigs", + ".hgtags", + ".shelf", + ".svn", + "CVS", + "DEADJOE", + "RCS", + "_MTN", + "_darcs", + "{arch}", + ] + + def add_ignore_pattern(self, pattern): + self._ignore_patterns.append(pattern) + + def add_file(self, source_path, dest_path): + self.file_map[source_path] = dest_path + + def read_manifest(self, manifest_path): + with io.open(manifest_path, encoding="UTF-8") as manifest: + try: + self.manifest = json.load(manifest) + except Exception as e: + raise ClickBuildError( + "Error reading manifest from %s: %s" % (manifest_path, e)) + keys = sorted(self.manifest) + for key in keys: + if key.startswith("_"): + print( + "Ignoring reserved dynamic key '%s'." % key, + file=sys.stderr) + del self.manifest[key] + + @property + def name(self): + return self.manifest["name"] + + @property + def version(self): + return self.manifest["version"] + + @property + def epochless_version(self): + return re.sub(r"^\d+:", "", self.version) + + @property + def maintainer(self): + return self.manifest["maintainer"] + + @property + def title(self): + return self.manifest["title"] + + @property + def architecture(self): + manifest_arch = self.manifest.get("architecture", "all") + if isinstance(manifest_arch, list): + return "multi" + else: + return manifest_arch + + +class ClickBuilder(ClickBuilderBase): + + def list_files(self, root_path): + for dirpath, _, filenames in os.walk(root_path): + rel_dirpath = os.path.relpath(dirpath, root_path) + if rel_dirpath == ".": + rel_dirpath = "" + for filename in filenames: + yield os.path.join(rel_dirpath, filename) + + def _filter_dot_click(self, tarinfo): + """Filter out attempts to include .click at the top level.""" + if tarinfo.name == './.click' or tarinfo.name.startswith('./.click/'): + return None + return tarinfo + + def _pack(self, temp_dir, control_dir, data_dir, package_path): + data_tar_path = os.path.join(temp_dir, "data.tar.gz") + with contextlib.closing(FakerootTarFile.open( + name=data_tar_path, mode="w:gz", format=tarfile.GNU_FORMAT + )) as data_tar: + data_tar.add(data_dir, arcname="./", filter=self._filter_dot_click) + + control_tar_path = os.path.join(temp_dir, "control.tar.gz") + control_tar = tarfile.open( + name=control_tar_path, mode="w:gz", format=tarfile.GNU_FORMAT) + control_tar.add(control_dir, arcname="./") + control_tar.close() + + with ArFile(name=package_path, mode="w") as package: + package.add_magic() + package.add_data("debian-binary", b"2.0\n") + package.add_data( + "_click-binary", ("%s\n" % spec_version).encode("UTF-8")) + package.add_file("control.tar.gz", control_tar_path) + package.add_file("data.tar.gz", data_tar_path) + + def _validate_framework(self, framework_string): + """Apply policy checks to framework declarations.""" + try: + validate_framework( + framework_string, ignore_missing_frameworks=True) + except ClickFrameworkInvalid as e: + raise ClickBuildError(str(e)) + + def build(self, dest_dir, manifest_path="manifest.json"): + with make_temp_dir() as temp_dir: + # Prepare data area. + root_path = os.path.join(temp_dir, "data") + + for source_path, dest_path in self.file_map.items(): + if dest_path.startswith("/"): + dest_path = dest_path[1:] + real_dest_path = os.path.join(root_path, dest_path) + shutil.copytree( + source_path, real_dest_path, symlinks=True, + ignore=shutil.ignore_patterns(*self._ignore_patterns)) + + # Prepare control area. + control_dir = os.path.join(temp_dir, "DEBIAN") + osextras.ensuredir(control_dir) + + if os.path.isabs(manifest_path): + full_manifest_path = manifest_path + else: + full_manifest_path = os.path.join(root_path, manifest_path) + self.read_manifest(full_manifest_path) + if "framework" in self.manifest: + self._validate_framework(self.manifest["framework"]) + + du_output = subprocess.check_output( + ["du", "-k", "-s", "--apparent-size", "."], + cwd=temp_dir, universal_newlines=True).rstrip("\n") + match = re.match(r"^(\d+)\s+\.$", du_output) + if not match: + raise Exception("du gave unexpected output '%s'" % du_output) + installed_size = match.group(1) + self.manifest["installed-size"] = installed_size + control_path = os.path.join(control_dir, "control") + osextras.ensuredir(os.path.dirname(control_path)) + with io.open(control_path, "w", encoding="UTF-8") as control: + print(dedent("""\ + Package: %s + Version: %s + Click-Version: %s + Architecture: %s + Maintainer: %s + Installed-Size: %s + Description: %s""" % ( + self.name, self.version, spec_version, self.architecture, + self.maintainer, installed_size, self.title)), + file=control) + + # Control file names must not contain a dot, hence "manifest" + # rather than "manifest.json" in the control area. + real_manifest_path = os.path.join(control_dir, "manifest") + with io.open( + real_manifest_path, "w", encoding="UTF-8") as manifest: + print( + json.dumps( + self.manifest, ensure_ascii=False, sort_keys=True, + indent=4, separators=(",", ": ")), + file=manifest) + os.unlink(full_manifest_path) + os.chmod(real_manifest_path, 0o644) + + md5sums_path = os.path.join(control_dir, "md5sums") + with open(md5sums_path, "w") as md5sums: + for path in sorted(self.list_files(root_path)): + md5 = hashlib.md5() + p = os.path.join(root_path, path) + if not os.path.exists(p): + continue + with open(p, "rb") as f: + while True: + buf = f.read(16384) + if not buf: + break + md5.update(buf) + print("%s %s" % (md5.hexdigest(), path), file=md5sums) + + preinst_path = os.path.join(control_dir, "preinst") + with open(preinst_path, "w") as preinst: + preinst.write(static_preinst) + + # Pack everything up. + package_name = "%s_%s_%s.click" % ( + self.name, self.epochless_version, self.architecture) + package_path = os.path.join(dest_dir, package_name) + self._pack(temp_dir, control_dir, root_path, package_path) + return package_path + + +class ClickSourceBuilder(ClickBuilderBase): + + def __init__(self): + super(ClickSourceBuilder, self).__init__() + # From @Dpkg::Source::Package::tar_ignore_default_pattern. + # (more in ClickBuilderBase) + self._ignore_patterns += [ + "*.a", + ".be", + ".deps", + "*.la", + "*.o", + "*.so", + ] + + def build(self, dest_dir, manifest_path=None): + with make_temp_dir() as temp_dir: + root_path = os.path.join(temp_dir, "source") + for source_path, dest_path in self.file_map.items(): + if dest_path.startswith("/"): + dest_path = dest_path[1:] + real_dest_path = os.path.join(root_path, dest_path) + shutil.copytree( + source_path, real_dest_path, symlinks=True, + ignore=shutil.ignore_patterns(*self._ignore_patterns)) + + real_manifest_path = os.path.join(root_path, "manifest.json") + if manifest_path is not None: + shutil.copy2(manifest_path, real_manifest_path) + os.chmod(real_manifest_path, 0o644) + self.read_manifest(real_manifest_path) + + package_name = "%s_%s.tar.gz" % (self.name, self.epochless_version) + package_path = os.path.join(dest_dir, package_name) + with contextlib.closing(FakerootTarFile.open( + name=package_path, mode="w:gz", format=tarfile.GNU_FORMAT + )) as tar: + tar.add(root_path, arcname="./") + return package_path diff -Nru click-0.4.45.1+16.10.20160916/click_package/chroot.py click-0.4.46+16.10.20170607.3/click_package/chroot.py --- click-0.4.45.1+16.10.20160916/click_package/chroot.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/chroot.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,721 @@ +# Copyright (C) 2013 Canonical Ltd. +# Authors: Colin Watson , +# Brian Murray +# Michael Vogt +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Chroot management for building Click packages.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + "ClickChroot", + "ClickChrootException", + "ClickChrootAlreadyExistsException", + "ClickChrootDoesNotExistException", + ] + +try: + from urllib.error import URLError + from urllib.request import urlopen +except ImportError: + from urllib2 import URLError, urlopen +import os +import pwd +import fileinput +import re +import shutil +import stat +import subprocess +import sys +from textwrap import dedent +from xml.etree import ElementTree + + +framework_base = { + "ubuntu-sdk-13.10": "ubuntu-sdk-13.10", + # 14.04 + "ubuntu-sdk-14.04-html": "ubuntu-sdk-14.04", + "ubuntu-sdk-14.04-papi": "ubuntu-sdk-14.04", + "ubuntu-sdk-14.04-qml": "ubuntu-sdk-14.04", + # 14.10 + "ubuntu-sdk-14.10-html": "ubuntu-sdk-14.10", + "ubuntu-sdk-14.10-papi": "ubuntu-sdk-14.10", + "ubuntu-sdk-14.10-qml": "ubuntu-sdk-14.10", + # 15.04 + "ubuntu-sdk-15.04-html": "ubuntu-sdk-15.04", + "ubuntu-sdk-15.04-papi": "ubuntu-sdk-15.04", + "ubuntu-sdk-15.04-qml": "ubuntu-sdk-15.04", + # 15.10 + "ubuntu-sdk-15.10-html-dev1": "ubuntu-sdk-15.10-dev1", + "ubuntu-sdk-15.10-papi-dev1": "ubuntu-sdk-15.10-dev1", + "ubuntu-sdk-15.10-qml-dev1": "ubuntu-sdk-15.10-dev1", + } + + +framework_series = { + "ubuntu-sdk-13.10": "saucy", + "ubuntu-sdk-14.04": "trusty", + "ubuntu-sdk-14.10": "utopic", + "ubuntu-sdk-15.04": "vivid", + "ubuntu-sdk-15.10": "wily", + } + + +# Please keep the lists of package names sorted. +extra_packages = { + "ubuntu-sdk-13.10": [ + "libqt5opengl5-dev:{TARGET}", + "libqt5svg5-dev:{TARGET}", + "libqt5v8-5-dev:{TARGET}", + "libqt5webkit5-dev:{TARGET}", + "libqt5xmlpatterns5-dev:{TARGET}", + "qmlscene:{TARGET}", + "qt3d5-dev:{TARGET}", + "qt5-default:{TARGET}", + "qt5-qmake:{TARGET}", + "qtbase5-dev:{TARGET}", + "qtdeclarative5-dev:{TARGET}", + "qtmultimedia5-dev:{TARGET}", + "qtquick1-5-dev:{TARGET}", + "qtscript5-dev:{TARGET}", + "qtsensors5-dev:{TARGET}", + "qttools5-dev:{TARGET}", + "ubuntu-ui-toolkit-doc", + ], + "ubuntu-sdk-14.04": [ + "cmake", + "google-mock:{TARGET}", + "intltool", + "libboost1.54-dev:{TARGET}", + "libjsoncpp-dev:{TARGET}", + "libprocess-cpp-dev:{TARGET}", + "libproperties-cpp-dev:{TARGET}", + "libqt5svg5-dev:{TARGET}", + "libqt5webkit5-dev:{TARGET}", + "libqt5xmlpatterns5-dev:{TARGET}", + "libunity-scopes-dev:{TARGET}", + # bug #1316930, needed for autopilot + "python3", + "qmlscene:{TARGET}", + "qt3d5-dev:{TARGET}", + "qt5-default:{TARGET}", + "qtbase5-dev:{TARGET}", + "qtdeclarative5-dev:{TARGET}", + "qtdeclarative5-dev-tools", + "qtlocation5-dev:{TARGET}", + "qtmultimedia5-dev:{TARGET}", + "qtscript5-dev:{TARGET}", + "qtsensors5-dev:{TARGET}", + "qttools5-dev:{TARGET}", + "qttools5-dev-tools:{TARGET}", + "ubuntu-ui-toolkit-doc", + ], + "ubuntu-sdk-14.10": [ + "cmake", + "cmake-extras", + "google-mock:{TARGET}", + "intltool", + "libboost1.55-dev:{TARGET}", + "libcontent-hub-dev:{TARGET}", + "libjsoncpp-dev:{TARGET}", + "libnet-cpp-dev:{TARGET}", + "libprocess-cpp-dev:{TARGET}", + "libproperties-cpp-dev:{TARGET}", + "libqt5keychain0:{TARGET}", + "libqt5sensors5-dev:{TARGET}", + "libqt5svg5-dev:{TARGET}", + "libqt5webkit5-dev:{TARGET}", + "libqt5xmlpatterns5-dev:{TARGET}", + "libunity-scopes-dev:{TARGET}", + # bug #1316930, needed for autopilot + "python3", + "qml-module-qt-labs-settings:{TARGET}", + "qml-module-qtmultimedia:{TARGET}", + "qml-module-qtquick-layouts:{TARGET}", + "qml-module-qtsensors:{TARGET}", + "qml-module-qtwebkit:{TARGET}", + "qmlscene:{TARGET}", + "qt3d5-dev:{TARGET}", + "qt5-default:{TARGET}", + "qtdeclarative5-accounts-plugin:{TARGET}", + "qtdeclarative5-dev-tools", + "qtdeclarative5-folderlistmodel-plugin:{TARGET}", + "qtdeclarative5-localstorage-plugin:{TARGET}", + "qtdeclarative5-online-accounts-client0.1:{TARGET}", + "qtdeclarative5-particles-plugin:{TARGET}", + "qtdeclarative5-poppler1.0:{TARGET}", + "qtdeclarative5-qtlocation-plugin:{TARGET}", + "qtdeclarative5-qtorganizer-plugin:{TARGET}", + "qtdeclarative5-qtpositioning-plugin:{TARGET}", + "qtdeclarative5-u1db1.0:{TARGET}", + "qtdeclarative5-ubuntu-content0.1:{TARGET}", + "qtdeclarative5-ubuntu-download-manager0.1:{TARGET}", + "qtdeclarative5-ubuntu-mediascanner0.1:{TARGET}", + "qtdeclarative5-ubuntu-syncmonitor0.1:{TARGET}", + "qtdeclarative5-ubuntu-telephony-phonenumber0.1:{TARGET}", + "qtdeclarative5-ubuntu-ui-toolkit-plugin:{TARGET}", + "qtdeclarative5-usermetrics0.1:{TARGET}", + "qtdeclarative5-xmllistmodel-plugin:{TARGET}", + "qtlocation5-dev:{TARGET}", + "qtmultimedia5-dev:{TARGET}", + "qtscript5-dev:{TARGET}", + "qttools5-dev:{TARGET}", + "qttools5-dev-tools:{TARGET}", + "ubuntu-html5-theme:{TARGET}", + "ubuntu-ui-toolkit-doc", + ], + "ubuntu-sdk-15.04": [ + # the sdk libs + "ubuntu-sdk-libs:{TARGET}", + "ubuntu-sdk-libs-dev:{TARGET}", + # the native build tools + "ubuntu-sdk-libs-tools", + # FIXME: see + # http://pad.lv/~mvo/oxide/crossbuild-friendly/+merge/234093 + # we help the apt resolver here until the + # oxideqt-codecs/oxidec-codecs-extras is sorted + "oxideqt-codecs-extra", + ], + "ubuntu-sdk-15.10-dev1": [ + # the sdk libs + "ubuntu-sdk-libs:{TARGET}", + "ubuntu-sdk-libs-dev:{TARGET}", + # the native build tools + "ubuntu-sdk-libs-tools", + # FIXME: see + # http://pad.lv/~mvo/oxide/crossbuild-friendly/+merge/234093 + # we help the apt resolver here until the + # oxideqt-codecs/oxidec-codecs-extras is sorted + "oxideqt-codecs-extra", + ], + } + + +primary_arches = ["amd64", "i386"] + + +non_meta_re = re.compile(r'^[a-zA-Z0-9+,./:=@_-]+$') + + +GEOIP_SERVER = "http://geoip.ubuntu.com/lookup" + +overlay_ppa = "ci-train-ppa-service/stable-phone-overlay" + + +def get_geoip_country_code_prefix(): + click_no_local_mirror = os.environ.get('CLICK_NO_LOCAL_MIRROR', 'auto') + if click_no_local_mirror == '1': + return "" + try: + with urlopen(GEOIP_SERVER) as f: + xml_data = f.read() + et = ElementTree.fromstring(xml_data) + cc = et.find("CountryCode") + if not cc: + return "" + return cc.text.lower()+"." + except (ElementTree.ParseError, URLError): + pass + return "" + + +def generate_sources(series, native_arch, target_arch, + archive_mirror, ports_mirror, components): + """Generate a list of strings for apts sources.list. + Arguments: + series -- the distro series (e.g. vivid) + native_arch -- the native architecture (e.g. amd64) + target_arch -- the target architecture (e.g. armhf) + archive_mirror -- main mirror, e.g. http://archive.ubuntu.com/ubuntu + ports_mirror -- ports mirror, e.g. http://ports.ubuntu.com/ubuntu-ports + components -- the components as string, e.g. "main restricted universe" + """ + pockets = ['%s' % series] + for pocket in ['updates', 'security']: + pockets.append('%s-%s' % (series, pocket)) + sources = [] + # write binary lines + arches = [target_arch] + if native_arch != target_arch: + arches.append(native_arch) + for arch in arches: + if arch not in primary_arches: + mirror = ports_mirror + else: + mirror = archive_mirror + for pocket in pockets: + sources.append("deb [arch=%s] %s %s %s" % + (arch, mirror, pocket, components)) + # write source lines + for pocket in pockets: + sources.append("deb-src %s %s %s" % + (archive_mirror, pocket, components)) + return sources + + +def shell_escape(command): + escaped = [] + for arg in command: + if non_meta_re.match(arg): + escaped.append(arg) + else: + escaped.append("'%s'" % arg.replace("'", "'\\''")) + return " ".join(escaped) + + +def strip_dev_series_from_framework(framework): + """Remove trailing -dev[0-9]+ from a framework name""" + return re.sub(r'^(.*)-dev[0-9]+$', r'\1', framework) + + +class ClickChrootException(Exception): + """A generic issue with the chroot""" + pass + + +class ClickChrootAlreadyExistsException(ClickChrootException): + """The chroot already exists""" + pass + + +class ClickChrootDoesNotExistException(ClickChrootException): + """A chroot with that name does not exist yet""" + pass + + +class ClickChroot: + + DAEMON_POLICY = dedent("""\ + #!/bin/sh + while true; do + case "$1" in + -*) shift ;; + makedev) exit 0;; + x11-common) exit 0;; + *) exit 101;; + esac + done + """) + + def __init__(self, target_arch, framework, name=None, series=None, + session=None, chroots_dir=None): + self.target_arch = target_arch + self.framework = strip_dev_series_from_framework(framework) + if name is None: + name = "click" + self.name = name + if series is None: + series = framework_series[self.framework_base] + self.series = series + self.session = session + system_arch = subprocess.check_output( + ["dpkg", "--print-architecture"], + universal_newlines=True).strip() + self.native_arch = self._get_native_arch(system_arch, self.target_arch) + if chroots_dir is None: + chroots_dir = "/var/lib/schroot/chroots" + self.chroots_dir = chroots_dir + + if "SUDO_USER" in os.environ: + self.user = os.environ["SUDO_USER"] + elif "PKEXEC_UID" in os.environ: + self.user = pwd.getpwuid(int(os.environ["PKEXEC_UID"])).pw_name + else: + self.user = pwd.getpwuid(os.getuid()).pw_name + self.dpkg_architecture = self._dpkg_architecture() + + def _get_native_arch(self, system_arch, target_arch): + """Determine the proper native architecture for a chroot. + + Some combinations of system and target architecture do not require + cross-building, so in these cases we just create a chroot suitable + for native building. + """ + if (system_arch, target_arch) in ( + ("amd64", "i386"), + # This will only work if the system is running a 64-bit + # kernel; but there's no alternative since no i386-to-amd64 + # cross-compiler is available in the Ubuntu archive. + ("i386", "amd64"), + ): + return target_arch + else: + return system_arch + + def _dpkg_architecture(self): + dpkg_architecture = {} + command = ["dpkg-architecture", "-a%s" % self.target_arch] + env = dict(os.environ) + env["CC"] = "true" + # Force dpkg-architecture to recalculate everything rather than + # picking up values from the environment, which will be present when + # running the test suite under dpkg-buildpackage. + for key in list(env): + if key.startswith("DEB_BUILD_") or key.startswith("DEB_HOST_"): + del env[key] + lines = subprocess.check_output( + command, env=env, universal_newlines=True).splitlines() + for line in lines: + try: + key, value = line.split("=", 1) + except ValueError: + continue + dpkg_architecture[key] = value + if self.native_arch == self.target_arch: + # We may have overridden the native architecture (see + # _get_native_arch above), so we need to force DEB_BUILD_* to + # match. + for key in list(dpkg_architecture): + if key.startswith("DEB_HOST_"): + new_key = "DEB_BUILD_" + key[len("DEB_HOST_"):] + dpkg_architecture[new_key] = dpkg_architecture[key] + return dpkg_architecture + + def _get_overlayfs_name(self): + for line in fileinput.input("/proc/filesystems"): + if line.strip() == "nodev\toverlay": + fileinput.close() + return "overlay" + return "overlayfs" + + def _generate_chroot_config(self, mount): + admin_user = "root" + users = [] + for key in ("users", "root-users", "source-root-users"): + users.append("%s=%s,%s" % (key, admin_user, self.user)) + with open(self.chroot_config, "w") as target: + target.write(dedent("""\ + [{full_name}] + description=Build chroot for click packages on {target_arch} + {users} + type=directory + profile=default + setup.fstab=click/fstab + # Not protocols or services see + # debian bug 557730 + setup.nssdatabases=sbuild/nssdatabases + union-type={overlayfs_name} + directory={mount} + """).format(full_name=self.full_name, + target_arch=self.target_arch, + users="\n".join(users), + mount=mount, + overlayfs_name=self._get_overlayfs_name())) + + def _generate_daemon_policy(self, mount): + daemon_policy = "%s/usr/sbin/policy-rc.d" % mount + with open(daemon_policy, "w") as policy: + policy.write(self.DAEMON_POLICY) + return daemon_policy + + def _generate_apt_proxy_file(self, mount, proxy): + apt_conf_d = os.path.join(mount, "etc", "apt", "apt.conf.d") + if not os.path.exists(apt_conf_d): + os.makedirs(apt_conf_d) + apt_conf_f = os.path.join(apt_conf_d, "99-click-chroot-proxy") + if proxy: + with open(apt_conf_f, "w") as f: + f.write(dedent("""\ + // proxy settings copied by click chroot + Acquire { + HTTP { + Proxy "%s"; + }; + }; + """) % proxy) + return apt_conf_f + + def _generate_finish_script(self, mount, build_pkgs): + finish_script = "%s/finish.sh" % mount + with open(finish_script, 'w') as finish: + finish.write(dedent("""\ + #!/bin/bash + set -e + # Configure target arch + dpkg --add-architecture {target_arch} + # Reload package lists + apt-get update || true + # Pull down signature requirements + apt-get -y --force-yes install gnupg ubuntu-keyring + """).format(target_arch=self.target_arch)) + if self.series == "vivid": + finish.write(dedent("""\ + apt-get -y --force-yes install software-properties-common + add-apt-repository -y ppa:{ppa} + echo "Package: *" \ + > /etc/apt/preferences.d/stable-phone-overlay.pref + echo \ + "Pin: release o=LP-PPA-{pin_ppa}" \ + >> /etc/apt/preferences.d/stable-phone-overlay.pref + echo "Pin-Priority: 1001" \ + >> /etc/apt/preferences.d/stable-phone-overlay.pref + """).format(ppa=overlay_ppa, + pin_ppa=re.sub('/', '-', overlay_ppa))) + finish.write(dedent("""\ + # Reload package lists + apt-get update || true + # Disable debconf questions + # so that automated builds won't prompt + echo set debconf/frontend Noninteractive | debconf-communicate + echo set debconf/priority critical | debconf-communicate + apt-get -y --force-yes dist-upgrade + # Install basic build tool set to match buildd + apt-get -y --force-yes install {build_pkgs} + # Set up expected /dev entries + if [ ! -r /dev/stdin ]; then + ln -s /proc/self/fd/0 /dev/stdin + fi + if [ ! -r /dev/stdout ]; then + ln -s /proc/self/fd/1 /dev/stdout + fi + if [ ! -r /dev/stderr ]; then + ln -s /proc/self/fd/2 /dev/stderr + fi + # Clean up + rm /finish.sh + apt-get clean + """).format(build_pkgs=' '.join(build_pkgs))) + return finish_script + + def _debootstrap(self, components, mount, archive_mirror, ports_mirror): + if self.native_arch in primary_arches: + mirror = archive_mirror + else: + mirror = ports_mirror + subprocess.check_call([ + "debootstrap", + "--arch", self.native_arch, + "--variant=buildd", + "--components=%s" % ','.join(components), + self.series, + mount, + mirror, + ]) + + @property + def framework_base(self): + if self.framework in framework_base: + return framework_base[self.framework] + else: + return self.framework + + @property + def full_name(self): + return "%s-%s-%s" % (self.name, self.framework_base, self.target_arch) + + @property + def full_session_name(self): + return "%s-%s" % (self.full_name, self.session) + + @property + def chroot_config(self): + return "/etc/schroot/chroot.d/%s" % self.full_name + + def exists(self): + command = ["schroot", "-c", self.full_name, "-i"] + with open("/dev/null", "w") as devnull: + return subprocess.call( + command, stdout=devnull, stderr=devnull) == 0 + + def _make_executable(self, path): + mode = stat.S_IMODE(os.stat(path).st_mode) + os.chmod(path, mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + + def _make_cross_package(self, prefix): + if self.native_arch == self.target_arch: + return prefix + else: + target_tuple = self.dpkg_architecture["DEB_HOST_GNU_TYPE"] + return "%s-%s" % (prefix, target_tuple) + + def create(self, keep_broken_chroot_on_fail=False): + if self.exists(): + raise ClickChrootAlreadyExistsException( + "Chroot %s already exists" % self.full_name) + components = ["main", "restricted", "universe", "multiverse"] + mount = "%s/%s" % (self.chroots_dir, self.full_name) + proxy = None + if not proxy and "http_proxy" in os.environ: + proxy = os.environ["http_proxy"] + if not proxy: + proxy = subprocess.check_output( + 'unset x; eval "$(apt-config shell x Acquire::HTTP::Proxy)"; \ + echo "$x"', + shell=True, universal_newlines=True).strip() + build_pkgs = [ + # sort alphabetically + "apt-utils", + "build-essential", + "cmake", + "dpkg-cross", + "fakeroot", + "libc-dev:%s" % self.target_arch, + # build pkg names dynamically + self._make_cross_package("g++"), + self._make_cross_package("pkg-config"), + ] + for package in extra_packages.get(self.framework_base, []): + package = package.format(TARGET=self.target_arch) + build_pkgs.append(package) + os.makedirs(mount) + + country_code = get_geoip_country_code_prefix() + archive_mirror = "http://%sarchive.ubuntu.com/ubuntu" % country_code + ports_mirror = "http://%sports.ubuntu.com/ubuntu-ports" % country_code + # this doesn't work because we are running this under sudo + if 'DEBOOTSTRAP_MIRROR' in os.environ: + archive_mirror = os.environ['DEBOOTSTRAP_MIRROR'] + self._debootstrap(components, mount, archive_mirror, ports_mirror) + sources = generate_sources(self.series, self.native_arch, + self.target_arch, + archive_mirror, ports_mirror, + ' '.join(components)) + with open("%s/etc/apt/sources.list" % mount, "w") as sources_list: + for line in sources: + print(line, file=sources_list) + shutil.copy2("/etc/localtime", "%s/etc/" % mount) + shutil.copy2("/etc/timezone", "%s/etc/" % mount) + self._generate_chroot_config(mount) + daemon_policy = self._generate_daemon_policy(mount) + self._make_executable(daemon_policy) + initctl = "%s/sbin/initctl" % mount + if os.path.exists(initctl): + os.remove(initctl) + os.symlink("%s/bin/true" % mount, initctl) + self._generate_apt_proxy_file(mount, proxy) + finish_script = self._generate_finish_script(mount, build_pkgs) + self._make_executable(finish_script) + command = ["/finish.sh"] + ret_code = self.maint(*command) + if ret_code != 0 and not keep_broken_chroot_on_fail: + # cleanup on failure + self.destroy() + raise ClickChrootException( + "Failed to create chroot '{}' (exit status {})".format( + self.full_name, ret_code)) + return ret_code + + def run(self, *args): + if not self.exists(): + raise ClickChrootDoesNotExistException( + "Chroot %s does not exist" % self.full_name) + command = ["schroot", "-c"] + if self.session: + command.extend([self.full_session_name, "--run-session"]) + else: + command.append(self.full_name) + command.extend(["--", "env"]) + for key, value in self.dpkg_architecture.items(): + command.append("%s=%s" % (key, value)) + command.extend(args) + ret = subprocess.call(command) + if ret == 0: + return 0 + else: + print("Command returned %d: %s" % (ret, shell_escape(command)), + file=sys.stderr) + return ret + + def maint(self, *args): + command = ["schroot", "-u", "root", "-c"] + if self.session: + command.extend([self.full_session_name, "--run-session"]) + else: + command.append("source:%s" % self.full_name) + command.append("--") + command.extend(args) + ret = subprocess.call(command) + if ret == 0: + return 0 + else: + print("Command returned %d: %s" % (ret, shell_escape(command)), + file=sys.stderr) + return ret + + def install(self, *pkgs): + if not self.exists(): + raise ClickChrootDoesNotExistException( + "Chroot %s does not exist" % self.full_name) + ret = self.update() + if ret != 0: + return ret + command = ["apt-get", "install", "--yes"] + command.extend(pkgs) + ret = self.maint(*command) + if ret != 0: + return ret + return self.clean() + + def clean(self): + command = ["apt-get", "clean"] + return self.maint(*command) + + def update(self): + command = ["apt-get", "update", "--yes"] + return self.maint(*command) + + def upgrade(self): + if not self.exists(): + raise ClickChrootDoesNotExistException( + "Chroot %s does not exist" % self.full_name) + ret = self.update() + if ret != 0: + return ret + command = ["apt-get", "dist-upgrade", "--yes"] + ret = self.maint(*command) + if ret != 0: + return ret + return self.clean() + + def destroy(self): + # remove config + if os.path.exists(self.chroot_config): + os.remove(self.chroot_config) + # find all schroot mount points, this is actually quite complicated + mount_dir = os.path.abspath( + os.path.join(self.chroots_dir, "..", "mount")) + needle = os.path.join(mount_dir, self.full_name) + all_mounts = [] + with open("/proc/mounts") as f: + for line in f.readlines(): + mp = line.split()[1] + if mp.startswith(needle): + all_mounts.append(mp) + # reverse order is important in case of submounts + for mp in sorted(all_mounts, key=len, reverse=True): + subprocess.call(["umount", mp]) + # now remove the rest + chroot_dir = "%s/%s" % (self.chroots_dir, self.full_name) + if os.path.exists(chroot_dir): + shutil.rmtree(chroot_dir) + return 0 + + def begin_session(self): + if not self.exists(): + raise ClickChrootDoesNotExistException( + "Chroot %s does not exist" % self.full_name) + command = ["schroot", "-c", self.full_name, "--begin-session", + "--session-name", self.full_session_name] + subprocess.check_call(command) + return 0 + + def end_session(self): + if not self.exists(): + raise ClickChrootDoesNotExistException( + "Chroot %s does not exist" % self.full_name) + command = ["schroot", "-c", self.full_session_name, "--end-session"] + subprocess.check_call(command) + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/build.py click-0.4.46+16.10.20170607.3/click_package/commands/build.py --- click-0.4.45.1+16.10.20160916/click_package/commands/build.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/build.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,77 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Build a Click package.""" + +from __future__ import print_function + +from optparse import OptionParser +import os +import sys +import subprocess + +from gi.repository import Click +from click_package.build import ClickBuildError, ClickBuilder + + +def run(argv): + parser = OptionParser("%prog build [options] DIRECTORY") + parser.add_option( + "-m", "--manifest", metavar="PATH", default="manifest.json", + help="read package manifest from PATH (default: manifest.json)") + parser.add_option( + "--no-validate", action="store_false", default=True, dest="validate", + help="Don't run click-reviewers-tools check on resulting .click") + parser.add_option( + "-I", "--ignore", metavar="file-pattern", action='append', default=[], + help="Ignore the given pattern when building the package") + options, args = parser.parse_args(argv) + if len(args) < 1: + parser.error("need directory") + directory = args[0] + if not os.path.isdir(directory): + parser.error('directory "%s" does not exist' % directory) + if os.path.isdir(os.path.join(directory, options.manifest)): + options.manifest = os.path.join(options.manifest, "manifest.json") + if not os.path.exists(os.path.join(directory, options.manifest)): + parser.error( + 'directory "%s" does not contain manifest file "%s"' % + (directory, options.manifest)) + builder = ClickBuilder() + builder.add_file(directory, "./") + for ignore in options.ignore: + builder.add_ignore_pattern(ignore) + try: + path = builder.build(".", manifest_path=options.manifest) + except ClickBuildError as e: + print(e, file=sys.stderr) + return 1 + if options.validate and Click.find_on_path('click-review'): + print("Now executing: click-review %s" % path) + try: + subprocess.check_call(['click-review', path]) + except subprocess.CalledProcessError: + # qtcreator-plugin-ubuntu relies on return code 0 + # to establish if a .click package has been built + # at all. + # + # If we want to distinguish between + # - click build failed + # - click build succeeded, but validation failed + # both tools will have to learn this at the same + # time. + pass + print("Successfully built package in '%s'." % path) + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/buildsource.py click-0.4.46+16.10.20170607.3/click_package/commands/buildsource.py --- click-0.4.45.1+16.10.20160916/click_package/commands/buildsource.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/buildsource.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,60 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Build a Click source package.""" + +from __future__ import print_function + +from optparse import OptionParser +import os +import sys + +from click_package.build import ClickBuildError, ClickSourceBuilder + + +def run(argv): + parser = OptionParser("%prog buildsource [options] DIRECTORY") + parser.add_option( + "-m", "--manifest", metavar="PATH", + help="read package manifest from PATH") + parser.add_option( + "-I", "--ignore", metavar="file-pattern", action='append', + default=[], + help="Ignore the given pattern when building the package") + options, args = parser.parse_args(argv) + if len(args) < 1: + parser.error("need directory") + directory = args[0] + if not os.path.isdir(directory): + parser.error('directory "%s" does not exist' % directory) + if not options.manifest: + options.manifest = os.path.join(directory, "manifest.json") + if os.path.isdir(os.path.join(directory, options.manifest)): + options.manifest = os.path.join(options.manifest, "manifest.json") + if not os.path.exists(os.path.join(directory, options.manifest)): + parser.error( + 'directory "%s" does not contain manifest file "%s"' % + (directory, options.manifest)) + builder = ClickSourceBuilder() + builder.add_file(directory, "./") + for ignore in options.ignore: + builder.add_ignore_pattern(ignore) + try: + path = builder.build(".", manifest_path=options.manifest) + except ClickBuildError as e: + print(e, file=sys.stderr) + return 1 + print("Successfully built source package in '%s'." % path) + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/chroot.py click-0.4.46+16.10.20170607.3/click_package/commands/chroot.py --- click-0.4.45.1+16.10.20160916/click_package/commands/chroot.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/chroot.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,266 @@ +#! /usr/bin/python3 + +# Copyright (C) 2013 Canonical Ltd. +# Author: Brian Murray + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Use and manage a Click chroot.""" + +from __future__ import print_function + +from argparse import ArgumentParser, REMAINDER +from contextlib import contextmanager +import os + +from click_package.chroot import ( + ClickChroot, + ClickChrootAlreadyExistsException, + ClickChrootDoesNotExistException, +) +from click_package import osextras + + +def requires_root(parser): + if os.getuid() != 0: + parser.error("must be run as root; try sudo") + + +@contextmanager +def message_on_error(exc, msg): + """ + Context Manager that prints the error message 'msg' on exception 'exc' + """ + try: + yield + except exc: + print(msg) + + +# FIXME: i18n(?) +class ErrorMessages: + EXISTS = """A chroot for that name and architecture already exists. +Please see the man-page how to use it.""" + NOT_EXISTS = """A chroot for that name and architecture does not exist. +Please use 'create' to create it.""" + + +def create(parser, args): + if not osextras.find_on_path("debootstrap"): + parser.error( + "debootstrap not installed and configured; install click-dev and " + "debootstrap") + requires_root(parser) + chroot = ClickChroot( + args.architecture, args.framework, name=args.name, series=args.series) + with message_on_error( + ClickChrootAlreadyExistsException, ErrorMessages.EXISTS): + return chroot.create(args.keep_broken_chroot) + # if we reach this point there was a error so return exit_status 1 + return 1 + + +def install(parser, args): + packages = args.packages + chroot = ClickChroot( + args.architecture, args.framework, name=args.name, + session=args.session) + with message_on_error( + ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): + return chroot.install(*packages) + # if we reach this point there was a error so return exit_status 1 + return 1 + + +def destroy(parser, args): + requires_root(parser) + # ask for confirmation? + chroot = ClickChroot(args.architecture, args.framework, name=args.name) + with message_on_error( + ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): + return chroot.destroy() + # if we reach this point there was a error so return exit_status 1 + return 1 + + +def execute(parser, args): + program = args.program + if not program: + program = ["/bin/bash"] + chroot = ClickChroot( + args.architecture, args.framework, name=args.name, + session=args.session) + with message_on_error( + ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): + return chroot.run(*program) + # if we reach this point there was a error so return exit_status 1 + return 1 + + +def maint(parser, args): + program = args.program + if not program: + program = ["/bin/bash"] + chroot = ClickChroot( + args.architecture, args.framework, name=args.name, + session=args.session) + with message_on_error( + ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): + return chroot.maint(*program) + # if we reach this point there was a error so return exit_status 1 + return 1 + + +def upgrade(parser, args): + chroot = ClickChroot( + args.architecture, args.framework, name=args.name, + session=args.session) + with message_on_error( + ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): + return chroot.upgrade() + # if we reach this point there was a error so return exit_status 1 + return 1 + + +def begin_session(parser, args): + chroot = ClickChroot( + args.architecture, args.framework, name=args.name, + session=args.session) + with message_on_error( + ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): + return chroot.begin_session() + # if we reach this point there was a error so return exit_status 1 + return 1 + + +def end_session(parser, args): + chroot = ClickChroot( + args.architecture, args.framework, name=args.name, + session=args.session) + with message_on_error( + ClickChrootDoesNotExistException, ErrorMessages.NOT_EXISTS): + return chroot.end_session() + # if we reach this point there was a error so return exit_status 1 + return 1 + + +def exists(parser, args): + chroot = ClickChroot(args.architecture, args.framework, name=args.name) + # return shell exit codes 0 on success, 1 on failure + if chroot.exists(): + return 0 + else: + return 1 + + +def run(argv): + parser = ArgumentParser("click chroot") + subparsers = parser.add_subparsers( + description="management subcommands", + help="valid commands") + parser.add_argument( + "-a", "--architecture", required=True, + help="architecture for the chroot") + parser.add_argument( + "-f", "--framework", default="ubuntu-sdk-14.04", + help="framework for the chroot (default: ubuntu-sdk-14.04)") + parser.add_argument( + "-s", "--series", + help="series to use for a newly-created chroot (defaults to a series " + "appropriate for the framework)") + parser.add_argument( + "-n", "--name", default="click", + help=( + "name of the chroot (default: click; the framework and " + "architecture will be appended)")) + create_parser = subparsers.add_parser( + "create", + help="create a chroot of the provided architecture") + create_parser.add_argument( + "-k", "--keep-broken-chroot", default=False, action="store_true", + help="Keep the chroot even if creating it fails (default is to delete " + "it)") + create_parser.set_defaults(func=create) + destroy_parser = subparsers.add_parser( + "destroy", + help="destroy the chroot") + destroy_parser.set_defaults(func=destroy) + upgrade_parser = subparsers.add_parser( + "upgrade", + help="upgrade the chroot") + upgrade_parser.add_argument( + "-n", "--session-name", + dest='session', + help="persistent chroot session name to upgrade") + upgrade_parser.set_defaults(func=upgrade) + install_parser = subparsers.add_parser( + "install", + help="install packages in the chroot") + install_parser.add_argument( + "-n", "--session-name", + dest='session', + help="persistent chroot session name to install packages in") + install_parser.add_argument( + "packages", nargs="+", + help="packages to install") + install_parser.set_defaults(func=install) + execute_parser = subparsers.add_parser( + "run", + help="run a program in the chroot") + execute_parser.add_argument( + "-n", "--session-name", + dest='session', + help="persistent chroot session name to run a program in") + execute_parser.add_argument( + "program", nargs=REMAINDER, + help="program to run with arguments") + execute_parser.set_defaults(func=execute) + maint_parser = subparsers.add_parser( + "maint", + help="run a maintenance command in the chroot") + maint_parser.add_argument( + "-n", "--session-name", + dest='session', + help="persistent chroot session name to run a maintenance command in") + maint_parser.add_argument( + "program", nargs=REMAINDER, + help="program to run with arguments") + maint_parser.set_defaults(func=maint) + begin_parser = subparsers.add_parser( + "begin-session", + help="begin a persistent chroot session") + begin_parser.add_argument( + "session", + help="new session name") + begin_parser.set_defaults(func=begin_session) + end_parser = subparsers.add_parser( + "end-session", + help="end a persistent chroot session") + end_parser.add_argument( + "session", + help="session name to end") + end_parser.set_defaults(func=end_session) + exists_parser = subparsers.add_parser( + "exists", + help="test if the given chroot exists") + exists_parser.set_defaults(func=exists) + args = parser.parse_args(argv) + if not hasattr(args, "func"): + parser.print_help() + return 1 + if (not osextras.find_on_path("schroot") or + not os.path.exists("/etc/schroot/click/fstab")): + parser.error( + "schroot not installed and configured; install click-dev and " + "schroot") + return args.func(parser, args) diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/contents.py click-0.4.46+16.10.20170607.3/click_package/commands/contents.py --- click-0.4.45.1+16.10.20160916/click_package/commands/contents.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/contents.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,31 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Show the file-list contents of a Click package file.""" + +from __future__ import print_function + +from optparse import OptionParser +import subprocess + + +def run(argv): + parser = OptionParser("%prog contents [options] PATH") + _, args = parser.parse_args(argv) + if len(args) < 1: + parser.error("need file name") + path = args[0] + subprocess.check_call(["dpkg-deb", "-c", path]) + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/desktophook.py click-0.4.46+16.10.20170607.3/click_package/commands/desktophook.py --- click-0.4.45.1+16.10.20160916/click_package/commands/desktophook.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/desktophook.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,179 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Click desktop hook. (Temporary; do not rely on this.)""" + +from __future__ import print_function + +import errno +import io +import json +from optparse import OptionParser +import os + +from gi.repository import Click + +from click_package import osextras + + +COMMENT = \ + '# Generated by "click desktophook"; changes here will be overwritten.' + + +def desktop_entries(directory, only_ours=False): + for entry in osextras.listdir_force(directory): + if not entry.endswith(".desktop"): + continue + path = os.path.join(directory, entry) + if only_ours: + try: + with io.open(path, encoding="UTF-8") as f: + if COMMENT not in f.read(): + continue + except Exception: + continue + yield entry + + +def split_entry(entry): + entry = entry[:-8] # strip .desktop + return entry.split("_", 2) + + +def older(source_path, target_path): + """Return True iff source_path is older than target_path. + + It's also OK for target_path to be missing. + """ + try: + source_mtime = os.stat(source_path).st_mtime + except OSError as e: + if e.errno == errno.ENOENT: + return False + try: + target_mtime = os.stat(target_path).st_mtime + except OSError as e: + if e.errno == errno.ENOENT: + return True + return source_mtime < target_mtime + + +def read_hooks_for(path, package, app_name): + try: + directory = Click.find_package_directory(path) + manifest_path = os.path.join( + directory, ".click", "info", "%s.manifest" % package) + with io.open(manifest_path, encoding="UTF-8") as manifest: + return json.load(manifest).get("hooks", {}).get(app_name, {}) + except Exception: + return {} + + +def quote_for_desktop_exec(s): + """Quote a string for Exec in a .desktop file. + + The rules are fairly awful. See: + http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html + """ + for c in s: + if c in " \t\n\"'\\><~|&;$*?#()`%": + break + else: + return s + quoted = [] + for c in s: + if c in "\"`$\\": + quoted.append("\\" + c) + elif c == "%": + quoted.append("%%") + else: + quoted.append(c) + escaped = [] + for c in "".join(quoted): + if c == "\\": + escaped.append("\\\\") + else: + escaped.append(c) + return '"%s"' % "".join(escaped) + + +# TODO: This is a very crude .desktop file mangler; we should instead +# implement proper (de)serialisation. +def write_desktop_file(target_path, source_path, profile): + Click.ensuredir(os.path.dirname(target_path)) + with io.open(source_path, encoding="UTF-8") as source, \ + io.open(target_path, "w", encoding="UTF-8") as target: + source_dir = Click.find_package_directory(source_path) + written_comment = False + seen_path = False + for line in source: + if not line.rstrip("\n") or line.startswith("#"): + # Comment + target.write(line) + elif line.startswith("["): + # Group header + target.write(line) + if not written_comment: + print(COMMENT, file=target) + elif "=" not in line: + # Who knows? + target.write(line) + else: + key, value = line.split("=", 1) + key = key.strip() + value = value.strip() + if key == "Exec": + target.write( + "%s=aa-exec-click -p %s -- %s\n" % + (key, quote_for_desktop_exec(profile), value)) + elif key == "Path": + target.write("%s=%s\n" % (key, source_dir)) + seen_path = True + elif key == "Icon": + icon_path = os.path.join(source_dir, value) + if os.path.exists(icon_path): + target.write("%s=%s\n" % (key, icon_path)) + else: + target.write("%s=%s\n" % (key, value)) + else: + target.write("%s=%s\n" % (key, value)) + if not seen_path: + target.write("Path=%s\n" % source_dir) + + +def run(argv): + parser = OptionParser("%prog desktophook [options]") + parser.parse_args(argv) + source_dir = os.path.expanduser("~/.local/share/click/hooks/desktop") + target_dir = os.path.expanduser("~/.local/share/applications") + source_entries = set(desktop_entries(source_dir)) + target_entries = set(desktop_entries(target_dir, only_ours=True)) + + for new_entry in source_entries: + package, app_name, version = split_entry(new_entry) + source_path = os.path.join(source_dir, new_entry) + target_path = os.path.join(target_dir, new_entry) + if older(source_path, target_path): + hooks = read_hooks_for(source_path, package, app_name) + if "apparmor" in hooks: + profile = "%s_%s_%s" % (package, app_name, version) + else: + profile = "unconfined" + write_desktop_file(target_path, source_path, profile) + + for remove_entry in target_entries - source_entries: + os.unlink(os.path.join(target_dir, remove_entry)) + + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/framework.py click-0.4.46+16.10.20170607.3/click_package/commands/framework.py --- click-0.4.45.1+16.10.20160916/click_package/commands/framework.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/framework.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,71 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""List available frameworks.""" + +from __future__ import print_function + +from argparse import ArgumentParser + +from gi.repository import Click + + +def list(parser, args): + for framework in Click.Framework.get_frameworks(): + print("%s" % framework.props.name) + return 0 + + +def info(parser, args): + framework = Click.Framework.open(args.framework_name) + for field in sorted(framework.get_fields()): + print("%s: %s" % (field, framework.get_field(field))) + + +def get_field(parser, args): + framework = Click.Framework.open(args.framework_name) + print(framework.get_field(args.field_name)) + + +def run(argv): + parser = ArgumentParser("click framework") + subparsers = parser.add_subparsers() + list_parser = subparsers.add_parser( + "list", + help="list available frameworks") + list_parser.set_defaults(func=list) + info_parser = subparsers.add_parser( + "info", + help="show info about a specific framework") + info_parser.add_argument( + "framework_name", + help="framework name with the information") + info_parser.set_defaults(func=info) + get_field_parser = subparsers.add_parser( + "get-field", + help="get a field from a given framework") + get_field_parser.add_argument( + "framework_name", + help="framework name with the information") + get_field_parser.add_argument( + "field_name", + help="the field name (e.g. base-version)") + get_field_parser.set_defaults(func=get_field) + + args = parser.parse_args(argv) + if not hasattr(args, "func"): + parser.print_help() + return 1 + return args.func(parser, args) diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/hook.py click-0.4.46+16.10.20170607.3/click_package/commands/hook.py --- click-0.4.45.1+16.10.20160916/click_package/commands/hook.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/hook.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,94 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Install or remove a Click system hook.""" + +from __future__ import print_function + +from optparse import OptionParser +import sys +from textwrap import dedent + +from gi.repository import Click, GLib + + +per_hook_subcommands = { + "install": "install", + "remove": "remove", + } + + +def run(argv): + parser = OptionParser(dedent("""\ + %prog hook [options] SUBCOMMAND [...] + + Subcommands are as follows: + + install HOOK + remove HOOK + run-system + run-user [--user=USER]""")) + parser.add_option( + "--root", metavar="PATH", help="look for additional packages in PATH") + parser.add_option( + "--user", metavar="USER", + help=( + "run user-level hooks for USER (default: current user; only " + "applicable to run-user)")) + options, args = parser.parse_args(argv) + if len(args) < 1: + parser.error("need subcommand (install, remove, run-system, run-user)") + subcommand = args[0] + if subcommand in per_hook_subcommands: + if len(args) < 2: + parser.error("need hook name") + db = Click.DB() + db.read(db_dir=None) + if options.root is not None: + db.add(options.root) + name = args[1] + hook = Click.Hook.open(db, name) + getattr(hook, per_hook_subcommands[subcommand])(user_name=None) + elif subcommand == "run-system": + db = Click.DB() + db.read(db_dir=None) + if options.root is not None: + db.add(options.root) + try: + Click.run_system_hooks(db) + except GLib.GError as e: + if e.domain == "click_hooks_error-quark": + print(e.message, file=sys.stderr) + return 1 + else: + raise + elif subcommand == "run-user": + db = Click.DB() + db.read(db_dir=None) + if options.root is not None: + db.add(options.root) + try: + Click.run_user_hooks(db, user_name=options.user) + except GLib.GError as e: + if e.domain == "click_hooks_error-quark": + print(e.message, file=sys.stderr) + return 1 + else: + raise + else: + parser.error( + "unknown subcommand '%s' (known: install, remove, run-system," + "run-user)" % subcommand) + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/info.py click-0.4.46+16.10.20170607.3/click_package/commands/info.py --- click-0.4.45.1+16.10.20160916/click_package/commands/info.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/info.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,88 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Show manifest information for a Click package.""" + +from __future__ import print_function + +from contextlib import closing +import glob +import json +from optparse import OptionParser +import os +import sys + +from gi.repository import Click + +from click_package.install import DebFile +from click_package.json_helpers import json_object_to_python + + +def _load_manifest(manifest_file): + manifest = json.load(manifest_file) + keys = list(manifest) + for key in keys: + if key.startswith("_"): + del manifest[key] + return manifest + + +def get_manifest(options, arg): + if "/" not in arg: + db = Click.DB() + db.read(db_dir=None) + if options.root is not None: + db.add(options.root) + registry = Click.User.for_user(db, name=options.user) + if registry.has_package_name(arg): + return json_object_to_python(registry.get_manifest(arg)) + + try: + with closing(DebFile(filename=arg)) as package: + with package.control.get_file( + "manifest", encoding="UTF-8") as manifest_file: + return _load_manifest(manifest_file) + except Exception: + pkgdir = Click.find_package_directory(arg) + manifest_path = glob.glob( + os.path.join(pkgdir, ".click", "info", "*.manifest")) + if len(manifest_path) > 1: + raise Exception("Multiple manifest files found in '%s'" % ( + manifest_path)) + with open(manifest_path[0]) as f: + return _load_manifest(f) + + +def run(argv): + parser = OptionParser("%prog info [options] PATH") + parser.add_option( + "--root", metavar="PATH", help="look for additional packages in PATH") + parser.add_option( + "--user", metavar="USER", + help="look up PACKAGE-NAME for USER (if you have permission; " + "default: current user)") + options, args = parser.parse_args(argv) + if len(args) < 1: + parser.error("need file name") + try: + manifest = get_manifest(options, args[0]) + except Exception as e: + print(e, file=sys.stderr) + return 1 + json.dump( + manifest, sys.stdout, ensure_ascii=False, sort_keys=True, indent=4, + separators=(",", ": ")) + print() + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/__init__.py click-0.4.46+16.10.20170607.3/click_package/commands/__init__.py --- click-0.4.45.1+16.10.20160916/click_package/commands/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/__init__.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,55 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""click commands.""" + +import importlib + + +all_commands = ( + "build", + "buildsource", + "chroot", + "contents", + "desktophook", + "framework", + "hook", + "info", + "install", + "list", + "pkgdir", + "register", + "unregister", + "verify", + ) + + +hidden_commands = ( + "desktophook", + ) + + +def load_command(command): + return importlib.import_module("click_package.commands.%s" % command) + + +def help_text(): + lines = [] + for command in all_commands: + if command in hidden_commands: + continue + mod = load_command(command) + lines.append(" %-21s %s" % (command, mod.__doc__.splitlines()[0])) + return "\n".join(lines) diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/install.py click-0.4.46+16.10.20170607.3/click_package/commands/install.py --- click-0.4.45.1+16.10.20160916/click_package/commands/install.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/install.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,70 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Install a Click package (low-level; consider pkcon instead).""" + +from __future__ import print_function + +from optparse import OptionParser +import sys +from textwrap import dedent + +from gi.repository import Click + +from click_package.install import ClickInstaller, ClickInstallerError + + +def run(argv): + parser = OptionParser(dedent("""\ + %prog install [options] PACKAGE-FILE + + This is a low-level tool; to install a package as an ordinary user + you should generally use "pkcon install-local PACKAGE-FILE" + instead.""")) + parser.add_option( + "--root", metavar="PATH", help="install packages underneath PATH") + parser.add_option( + "--force-missing-framework", action="store_true", default=False, + help="install despite missing system framework") + parser.add_option( + "--user", metavar="USER", help="register package for USER") + parser.add_option( + "--all-users", default=False, action="store_true", + help="register package for all users") + parser.add_option( + "--allow-unauthenticated", default=False, action="store_true", + help="allow installing packages with no signatures") + parser.add_option( + "--verbose", default=False, action="store_true", + help="be more verbose on install") + options, args = parser.parse_args(argv) + if len(args) < 1: + parser.error("need package file name") + db = Click.DB() + db.read(db_dir=None) + if options.root is not None: + db.add(options.root) + package_path = args[0] + installer = ClickInstaller( + db=db, force_missing_framework=options.force_missing_framework, + allow_unauthenticated=options.allow_unauthenticated) + try: + installer.install( + package_path, user=options.user, all_users=options.all_users, + quiet=not options.verbose) + except ClickInstallerError as e: + print("Cannot install %s: %s" % (package_path, e), file=sys.stderr) + return 1 + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/list.py click-0.4.46+16.10.20170607.3/click_package/commands/list.py --- click-0.4.45.1+16.10.20160916/click_package/commands/list.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/list.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,64 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""List installed Click packages.""" + +from __future__ import print_function + +import json +from optparse import OptionParser +import sys + +from gi.repository import Click + +from click_package.json_helpers import json_array_to_python + + +def list_packages(options): + db = Click.DB() + db.read(db_dir=None) + if options.root is not None: + db.add(options.root) + if options.all: + return json_array_to_python(db.get_manifests(all_versions=True)) + else: + registry = Click.User.for_user(db, name=options.user) + return json_array_to_python(registry.get_manifests()) + + +def run(argv): + parser = OptionParser("%prog list [options]") + parser.add_option( + "--root", metavar="PATH", help="look for additional packages in PATH") + parser.add_option( + "--all", default=False, action="store_true", + help="list all installed packages") + parser.add_option( + "--user", metavar="USER", + help="list packages registered by USER (if you have permission)") + parser.add_option( + "--manifest", default=False, action="store_true", + help="format output as a JSON array of manifests") + options, _ = parser.parse_args(argv) + json_output = list_packages(options) + if options.manifest: + json.dump( + json_output, sys.stdout, ensure_ascii=False, sort_keys=True, + indent=4, separators=(",", ": ")) + print() + else: + for manifest in json_output: + print("%s\t%s" % (manifest["name"], manifest["version"])) + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/pkgdir.py click-0.4.46+16.10.20170607.3/click_package/commands/pkgdir.py --- click-0.4.45.1+16.10.20160916/click_package/commands/pkgdir.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/pkgdir.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,52 @@ +#! /usr/bin/python3 + +# Copyright (C) 2013 Canonical Ltd. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Print the directory where a Click package is unpacked.""" + +from __future__ import print_function + +from optparse import OptionParser +import sys + +from gi.repository import Click + + +def run(argv): + parser = OptionParser("%prog pkgdir [options] {PACKAGE-NAME|PATH}") + parser.add_option( + "--root", metavar="PATH", help="look for additional packages in PATH") + parser.add_option( + "--user", metavar="USER", + help="look up PACKAGE-NAME for USER (if you have permission; " + "default: current user)") + options, args = parser.parse_args(argv) + if len(args) < 1: + parser.error("need package name") + try: + if "/" in args[0]: + print(Click.find_package_directory(args[0])) + else: + db = Click.DB() + db.read(db_dir=None) + if options.root is not None: + db.add(options.root) + package_name = args[0] + registry = Click.User.for_user(db, name=options.user) + print(registry.get_path(package_name)) + except Exception as e: + print(e, file=sys.stderr) + return 1 + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/register.py click-0.4.46+16.10.20170607.3/click_package/commands/register.py --- click-0.4.45.1+16.10.20160916/click_package/commands/register.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/register.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,57 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Register an installed Click package for a user.""" + +from __future__ import print_function + +from optparse import OptionParser + +from gi.repository import Click, GLib + + +def run(argv): + parser = OptionParser("%prog register [options] PACKAGE-NAME VERSION") + parser.add_option( + "--root", metavar="PATH", help="look for additional packages in PATH") + parser.add_option( + "--user", metavar="USER", + help="register package for USER (default: current user)") + parser.add_option( + "--all-users", default=False, action="store_true", + help="register package for all users") + options, args = parser.parse_args(argv) + if len(args) < 1: + parser.error("need package name") + if len(args) < 2: + parser.error("need version") + db = Click.DB() + db.read(db_dir=None) + if options.root is not None: + db.add(options.root) + package = args[0] + version = args[1] + if options.all_users: + registry = Click.User.for_all_users(db) + else: + registry = Click.User.for_user(db, name=options.user) + try: + old_version = registry.get_version(package) + except GLib.GError: + old_version = None + registry.set_version(package, version) + if old_version is not None: + db.maybe_remove(package, old_version) + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/unregister.py click-0.4.46+16.10.20170607.3/click_package/commands/unregister.py --- click-0.4.45.1+16.10.20160916/click_package/commands/unregister.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/unregister.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,65 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Unregister an installed Click package for a user.""" + +from __future__ import print_function + +from optparse import OptionParser +import os +import sys + +from gi.repository import Click + + +def run(argv): + parser = OptionParser("%prog unregister [options] PACKAGE-NAME [VERSION]") + parser.add_option( + "--root", metavar="PATH", help="look for additional packages in PATH") + parser.add_option( + "--user", metavar="USER", + help="unregister package for USER (default: $SUDO_USER, if known)") + parser.add_option( + "--all-users", default=False, action="store_true", + help="unregister package that was previously registered for all users") + options, args = parser.parse_args(argv) + if len(args) < 1: + parser.error("need package name") + if os.geteuid() != 0: + parser.error( + "click unregister must be started as root, since it may need to " + "remove packages from disk") + if options.user is None and "SUDO_USER" in os.environ: + options.user = os.environ["SUDO_USER"] + db = Click.DB() + db.read(db_dir=None) + if options.root is not None: + db.add(options.root) + package = args[0] + if options.all_users: + registry = Click.User.for_all_users(db) + else: + registry = Click.User.for_user(db, name=options.user) + old_version = registry.get_version(package) + if len(args) >= 2 and old_version != args[1]: + print( + "Not removing %s %s; expected version %s" % + (package, old_version, args[1]), + file=sys.stderr) + sys.exit(1) + registry.remove(package) + db.maybe_remove(package, old_version) + # TODO: remove data + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/commands/verify.py click-0.4.46+16.10.20170607.3/click_package/commands/verify.py --- click-0.4.45.1+16.10.20160916/click_package/commands/verify.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/commands/verify.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,43 @@ +#! /usr/bin/python3 + +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Verify a Click package.""" + +from __future__ import print_function + +from optparse import OptionParser + +from click_package.install import ClickInstaller + + +def run(argv): + parser = OptionParser("%prog verify [options] PACKAGE-FILE") + parser.add_option( + "--force-missing-framework", action="store_true", default=False, + help="ignore missing system framework") + parser.add_option( + "--allow-unauthenticated", action="store_true", default=False, + help="allow installing packages with no sigantures") + options, args = parser.parse_args(argv) + if len(args) < 1: + parser.error("need package file name") + package_path = args[0] + installer = ClickInstaller( + db=None, force_missing_framework=options.force_missing_framework, + allow_unauthenticated=options.allow_unauthenticated) + installer.audit(package_path, slow=True) + return 0 diff -Nru click-0.4.45.1+16.10.20160916/click_package/framework.py click-0.4.46+16.10.20170607.3/click_package/framework.py --- click-0.4.45.1+16.10.20160916/click_package/framework.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/framework.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,145 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Pure python click framework handling support.""" + +import logging +import os +import re + +try: + import apt_pkg +except: + pass + +import click_package.paths + + +class ClickFrameworkInvalid(Exception): + pass + + +# python version of the vala parse_deb822_file() +def parse_deb822_file(filename): + data = {} + with open(filename) as f: + for line in f: + line = line.strip() + # from deb822.vala + field_re_posix = ( + r'^([^:[:space:]]+)[[:space:]]*:[[:space:]]' + '([^[:space:]].*?)[[:space:]]*$') + # python does not do posix char classes + field_re = field_re_posix.replace("[:space:]", "\s") + blank_re_posix = r'^[[:space:]]*$' + blank_re = blank_re_posix.replace("[:space:]", "\s") + if re.match(blank_re, line): + break + match = re.match(field_re, line) + if match and match.group(1) and match.group(2): + data[match.group(1).lower()] = match.group(2) + return data + + +# python version of vala get_frameworks_dir +def get_frameworks_dir(): + return click_package.paths.frameworks_dir + + +def get_framework_path(framework_name): + framework_path = os.path.join( + get_frameworks_dir(), framework_name+".framework") + return framework_path + + +# python version of the vala click_framework_get_base_version() +def click_framework_get_base_version(framework_name): + deb822 = parse_deb822_file(get_framework_path(framework_name)) + return deb822.get("base-version", None) + + +# python version of the vala click_framework_get_base_version() +def click_framework_get_base_name(framework_name): + deb822 = parse_deb822_file(get_framework_path(framework_name)) + return deb822.get("base-name", None) + + +# python version of the vala click_framework_has_framework +def click_framework_has_framework(framework_name): + return os.path.exists(get_framework_path(framework_name)) + + +def validate_framework(framework_string, ignore_missing_frameworks=False): + try: + apt_pkg + except NameError: + logging.warning("No apt_pkg module, skipping validate_framework") + return + + try: + parsed_framework = apt_pkg.parse_depends(framework_string) + except ValueError: + raise ClickFrameworkInvalid( + 'Could not parse framework "%s"' % framework_string) + + base_name_versions = {} + missing_frameworks = [] + for or_dep in parsed_framework: + if len(or_dep) > 1: + raise ClickFrameworkInvalid( + 'Alternative dependencies in framework "%s" not yet ' + 'allowed' % framework_string) + if or_dep[0][1] or or_dep[0][2]: + raise ClickFrameworkInvalid( + 'Version relationship in framework "%s" not yet allowed' % + framework_string) + # now verify that different base versions are not mixed + framework_name = or_dep[0][0] + if not click_framework_has_framework(framework_name): + missing_frameworks.append(framework_name) + continue + # ensure we do not use different base versions for the same base-name + framework_base_name = click_framework_get_base_name( + framework_name) + framework_base_version = click_framework_get_base_version( + framework_name) + prev = base_name_versions.get(framework_base_name, None) + if prev and prev != framework_base_version: + raise ClickFrameworkInvalid( + 'Multiple frameworks with different base versions are not ' + 'allowed. Found: {} ({} != {})'.format( + framework_base_name, + framework_base_version, + base_name_versions[framework_base_name])) + base_name_versions[framework_base_name] = framework_base_version + + if not ignore_missing_frameworks: + if len(missing_frameworks) > 1: + raise ClickFrameworkInvalid( + 'Frameworks %s not present on system (use ' + '--force-missing-framework option to override)' % + ", ".join('"%s"' % f for f in missing_frameworks)) + elif missing_frameworks: + raise ClickFrameworkInvalid( + 'Framework "%s" not present on system (use ' + '--force-missing-framework option to override)' % + missing_frameworks[0]) + else: + if len(missing_frameworks) > 1: + logging.warning("Ignoring missing frameworks %s" % ( + ", ".join('"%s"' % f for f in missing_frameworks))) + elif missing_frameworks: + logging.warning('Ignoring missing framework "%s"' % ( + missing_frameworks[0])) diff -Nru click-0.4.45.1+16.10.20160916/click_package/__init__.py click-0.4.46+16.10.20170607.3/click_package/__init__.py --- click-0.4.45.1+16.10.20160916/click_package/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/__init__.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,3 @@ +# Marker to help resolve unfortunate name clash between this package and +# https://pypi.python.org/pypi/click. +_CLICK_IS_A_PACKAGING_FORMAT_ = 1 diff -Nru click-0.4.45.1+16.10.20160916/click_package/install.py click-0.4.46+16.10.20170607.3/click_package/install.py --- click-0.4.45.1+16.10.20160916/click_package/install.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/install.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,479 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Installing Click packages.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'ClickInstaller', + 'ClickInstallerAuditError', + 'ClickInstallerError', + 'ClickInstallerPermissionDenied', + ] + + +from functools import partial +import grp +import inspect +import json +import logging +import os +import pwd +import shutil +import stat +import subprocess +import sys +import tempfile +from textwrap import dedent + +from contextlib import closing + +from debian.debfile import DebFile as _DebFile +from debian.debian_support import Version +from gi.repository import Click + +from click_package.paths import preload_path +from click_package.preinst import static_preinst_matches +from click_package.versions import spec_version + +from click_package.framework import ( + validate_framework, + ClickFrameworkInvalid, +) + + +try: + _DebFile.close + DebFile = _DebFile +except AttributeError: + # Yay! The Ubuntu 13.04 version of python-debian 0.1.21 + # debian.debfile.DebFile has a .close() method but the PyPI version of + # 0.1.21 does not. It's worse than that because DebFile.close() really + # delegates to DebPart.close() and *that's* missing in the PyPI version. + # To get that working, we have to reach inside the object and name mangle + # the attribute. + class DebFile(_DebFile): + def close(self): + self.control._DebPart__member.close() + self.data._DebPart__member.close() + + +class DebsigVerifyError(Exception): + pass + + +class DebsigVerify: + """Tiny wrapper around the debsig-verify commandline""" + # from debsig-verify-0.9/debsigs.h + DS_SUCCESS = 0 + DS_FAIL_NOSIGS = 10 + DS_FAIL_UNKNOWN_ORIGIN = 11 + DS_FAIL_NOPOLICIES = 12 + DS_FAIL_BADSIG = 13 + DS_FAIL_INTERNAL = 14 + + # should be a property, but python does not support support + # class properties easily + @classmethod + def available(cls): + return Click.find_on_path("debsig-verify") + + @classmethod + def verify(cls, path, allow_unauthenticated): + command = ["debsig-verify"] + [path] + try: + subprocess.check_output(command, universal_newlines=True) + except subprocess.CalledProcessError as e: + if (allow_unauthenticated and + e.returncode in (DebsigVerify.DS_FAIL_NOSIGS, + DebsigVerify.DS_FAIL_UNKNOWN_ORIGIN, + DebsigVerify.DS_FAIL_NOPOLICIES)): + logging.warning( + "Signature check failed, but installing anyway " + "as requested") + else: + raise DebsigVerifyError( + "Signature verification error: %s" % e.output) + return True + + +class ClickInstallerError(Exception): + pass + + +class ClickInstallerPermissionDenied(ClickInstallerError): + pass + + +class ClickInstallerAuditError(ClickInstallerError): + pass + + +class ClickInstaller: + def __init__(self, db, force_missing_framework=False, + allow_unauthenticated=False): + self.db = db + self.force_missing_framework = force_missing_framework + self.allow_unauthenticated = allow_unauthenticated + + def _preload_path(self): + if "CLICK_PACKAGE_PRELOAD" in os.environ: + return os.environ["CLICK_PACKAGE_PRELOAD"] + my_path = inspect.getsourcefile(ClickInstaller) + preload = os.path.join( + os.path.dirname(my_path), os.pardir, "preload", ".libs", + "libclickpreload.so") + if os.path.exists(preload): + return os.path.abspath(preload) + return preload_path + + def _dpkg_architecture(self): + return subprocess.check_output( + ["dpkg", "--print-architecture"], + universal_newlines=True).rstrip("\n") + + def extract(self, path, target): + command = ["dpkg-deb", "-R", path, target] + with open(path, "rb") as fd: + env = dict(os.environ) + preloads = [self._preload_path()] + if "LD_PRELOAD" in env: + preloads.append(env["LD_PRELOAD"]) + env["LD_PRELOAD"] = " ".join(preloads) + env["CLICK_BASE_DIR"] = target + env["CLICK_PACKAGE_PATH"] = path + env["CLICK_PACKAGE_FD"] = str(fd.fileno()) + env.pop("HOME", None) + kwargs = {} + if sys.version >= "3.2": + kwargs["pass_fds"] = (fd.fileno(),) + subprocess.check_call(command, env=env, **kwargs) + + def audit(self, path, slow=False, check_arch=False): + # always do the signature check first + if DebsigVerify.available(): + try: + DebsigVerify.verify(path, self.allow_unauthenticated) + except DebsigVerifyError as e: + raise ClickInstallerAuditError(str(e)) + else: + logging.warning( + "debsig-verify not available; cannot check signatures") + + # fail early if the file cannot be opened + try: + with closing(DebFile(filename=path)) as package: + pass + except Exception as e: + raise ClickInstallerError("Failed to read %s: %s" % ( + path, str(e))) + + # then perform the audit + with closing(DebFile(filename=path)) as package: + control_fields = package.control.debcontrol() + + try: + click_version = Version(control_fields["Click-Version"]) + except KeyError: + raise ClickInstallerAuditError("No Click-Version field") + if click_version > spec_version: + raise ClickInstallerAuditError( + "Click-Version: %s newer than maximum supported version " + "%s" % (click_version, spec_version)) + + for field in ( + "Pre-Depends", "Depends", "Recommends", "Suggests", "Enhances", + "Conflicts", "Breaks", + "Provides", + ): + if field in control_fields: + raise ClickInstallerAuditError( + "%s field is forbidden in Click packages" % field) + + scripts = package.control.scripts() + if ("preinst" in scripts and + static_preinst_matches(scripts["preinst"])): + scripts.pop("preinst", None) + if scripts: + raise ClickInstallerAuditError( + "Maintainer scripts are forbidden in Click packages " + "(found: %s)" % + " ".join(sorted(scripts))) + + if not package.control.has_file("manifest"): + raise ClickInstallerAuditError("Package has no manifest") + with package.control.get_file("manifest", encoding="UTF-8") as f: + manifest = json.load(f) + + try: + package_name = manifest["name"] + except KeyError: + raise ClickInstallerAuditError('No "name" entry in manifest') + # TODO: perhaps just do full name validation? + if "/" in package_name: + raise ClickInstallerAuditError( + 'Invalid character "/" in "name" entry: %s' % package_name) + if "_" in package_name: + raise ClickInstallerAuditError( + 'Invalid character "_" in "name" entry: %s' % package_name) + + try: + package_version = manifest["version"] + except KeyError: + raise ClickInstallerAuditError( + 'No "version" entry in manifest') + # TODO: perhaps just do full version validation? + if "/" in package_version: + raise ClickInstallerAuditError( + 'Invalid character "/" in "version" entry: %s' % + package_version) + if "_" in package_version: + raise ClickInstallerAuditError( + 'Invalid character "_" in "version" entry: %s' % + package_version) + + try: + framework = manifest["framework"] + except KeyError: + raise ClickInstallerAuditError( + 'No "framework" entry in manifest') + try: + validate_framework(framework, self.force_missing_framework) + except ClickFrameworkInvalid as e: + raise ClickInstallerAuditError(str(e)) + + if check_arch: + architecture = manifest.get("architecture", "all") + if architecture != "all": + dpkg_architecture = self._dpkg_architecture() + if isinstance(architecture, list): + if dpkg_architecture not in architecture: + raise ClickInstallerAuditError( + 'Package architectures "%s" not compatible ' + 'with system architecture "%s"' % + (" ".join(architecture), dpkg_architecture)) + elif architecture != dpkg_architecture: + raise ClickInstallerAuditError( + 'Package architecture "%s" not compatible ' + 'with system architecture "%s"' % + (architecture, dpkg_architecture)) + + # This isn't ideally quick, since it has to decompress the data + # part of the package, but dpkg's path filtering code assumes + # that all paths start with "./" so we must check it before + # passing the package to dpkg. + for data_name in package.data: + if data_name != "." and not data_name.startswith("./"): + raise ClickInstallerAuditError( + 'File name "%s" in package does not start with "./"' % + data_name) + + if slow: + temp_dir = tempfile.mkdtemp(prefix="click") + try: + self.extract(path, temp_dir) + command = [ + "md5sum", "-c", "--quiet", + os.path.join("DEBIAN", "md5sums"), + ] + subprocess.check_call(command, cwd=temp_dir) + finally: + shutil.rmtree(temp_dir) + + return package_name, package_version + + def _drop_privileges(self, username): + if os.geteuid() != 0: + return + pw = pwd.getpwnam(username) + os.setgroups( + [g.gr_gid for g in grp.getgrall() if username in g.gr_mem]) + # Portability note: this assumes that we have [gs]etres[gu]id, which + # is true on Linux but not necessarily elsewhere. If you need to + # support something else, there are reasonably standard alternatives + # involving other similar calls; see e.g. gnulib/lib/idpriv-drop.c. + os.setresgid(pw.pw_gid, pw.pw_gid, pw.pw_gid) + os.setresuid(pw.pw_uid, pw.pw_uid, pw.pw_uid) + assert os.getresuid() == (pw.pw_uid, pw.pw_uid, pw.pw_uid) + assert os.getresgid() == (pw.pw_gid, pw.pw_gid, pw.pw_gid) + os.umask(0o022) + + def _euid_access(self, username, path, mode): + """Like os.access, but for the effective UID.""" + # TODO: Dropping privileges and calling + # os.access(effective_ids=True) ought to work, but for some reason + # appears not to return False when it should. It seems that we need + # a subprocess to check this reliably. At least we don't have to + # exec anything. + pid = os.fork() + if pid == 0: # child + self._drop_privileges(username) + os._exit(0 if os.access(path, mode) else 1) + else: # parent + _, status = os.waitpid(pid, 0) + return status == 0 + + def _check_write_permissions(self, path): + while True: + if os.path.exists(path): + break + path = os.path.dirname(path) + if path == "/": + break + if not self._euid_access("clickpkg", path, os.W_OK): + raise ClickInstallerPermissionDenied( + 'Cannot acquire permission to write to %s; either run as root ' + 'with --user, or use "pkcon install-local" instead' % path) + + def _install_preexec(self, inst_dir): + self._drop_privileges("clickpkg") + + admin_dir = os.path.join(inst_dir, ".click") + if not os.path.exists(admin_dir): + os.makedirs(admin_dir) + with open(os.path.join(admin_dir, "available"), "w"): + pass + with open(os.path.join(admin_dir, "status"), "w"): + pass + os.mkdir(os.path.join(admin_dir, "info")) + os.mkdir(os.path.join(admin_dir, "updates")) + os.mkdir(os.path.join(admin_dir, "triggers")) + + def _unpack(self, path, user=None, all_users=False, quiet=True): + package_name, package_version = self.audit(path, check_arch=True) + + # Is this package already unpacked in an underlay (non-topmost) + # database? + if self.db.has_package_version(package_name, package_version): + overlay = self.db.get(self.db.props.size - 1) + if not overlay.has_package_version(package_name, package_version): + return package_name, package_version, None + + package_dir = os.path.join(self.db.props.overlay, package_name) + inst_dir = os.path.join(package_dir, package_version) + assert ( + os.path.dirname(os.path.dirname(inst_dir)) == + self.db.props.overlay) + + self._check_write_permissions(self.db.props.overlay) + root_click = os.path.join(self.db.props.overlay, ".click") + if not os.path.exists(root_click): + os.makedirs(root_click) + if os.geteuid() == 0: + pw = pwd.getpwnam("clickpkg") + os.chown(root_click, pw.pw_uid, pw.pw_gid) + + # TODO: sandbox so that this can only write to the unpack directory + command = [ + "dpkg", + # We normally run dpkg as non-root. + "--force-not-root", + # /sbin and /usr/sbin may not necessarily be on $PATH; we don't + # use the tools dpkg gets from there. + "--force-bad-path", + # We check the package architecture ourselves in audit(). + "--force-architecture", + "--instdir", inst_dir, + "--admindir", os.path.join(inst_dir, ".click"), + "--path-exclude", "*/.click/*", + "--log", os.path.join(root_click, "log"), + "--no-triggers", + "--install", path, + ] + with open(path, "rb") as fd: + env = dict(os.environ) + preloads = [self._preload_path()] + if "LD_PRELOAD" in env: + preloads.append(env["LD_PRELOAD"]) + env["LD_PRELOAD"] = " ".join(preloads) + env["CLICK_BASE_DIR"] = self.db.props.overlay + env["CLICK_PACKAGE_PATH"] = path + env["CLICK_PACKAGE_FD"] = str(fd.fileno()) + env.pop("HOME", None) + kwargs = {} + if sys.version >= "3.2": + kwargs["pass_fds"] = (fd.fileno(),) + if quiet: + fn = subprocess.check_output + kwargs["stderr"] = subprocess.STDOUT + else: + fn = subprocess.check_call + try: + fn(command, + preexec_fn=partial(self._install_preexec, inst_dir), + env=env, universal_newlines=True, + **kwargs) + except subprocess.CalledProcessError as e: + logging.error("%s failed with exit_code %s:\n%s" % ( + command, e.returncode, e.output)) + raise + for dirpath, dirnames, filenames in os.walk(inst_dir): + for entry in dirnames + filenames: + entry_path = os.path.join(dirpath, entry) + entry_mode = os.lstat(entry_path).st_mode + new_entry_mode = entry_mode | stat.S_IRGRP | stat.S_IROTH + if entry_mode & stat.S_IXUSR: + new_entry_mode |= stat.S_IXGRP | stat.S_IXOTH + if new_entry_mode != entry_mode: + try: + os.chmod(entry_path, new_entry_mode) + except OSError: + pass + + current_path = os.path.join(package_dir, "current") + + if os.path.islink(current_path): + old_version = os.readlink(current_path) + if "/" in old_version: + old_version = None + else: + old_version = None + Click.package_install_hooks( + self.db, package_name, old_version, package_version, + user_name=None) + + new_path = os.path.join(package_dir, "current.new") + Click.symlink_force(package_version, new_path) + if os.geteuid() == 0: + # shutil.chown would be more convenient, but it doesn't support + # follow_symlinks=False in Python 3.3. + # http://bugs.python.org/issue18108 + pw = pwd.getpwnam("clickpkg") + os.chown(new_path, pw.pw_uid, pw.pw_gid, follow_symlinks=False) + os.rename(new_path, current_path) + + return package_name, package_version, old_version + + def install(self, path, user=None, all_users=False, quiet=True): + package_name, package_version, old_version = self._unpack( + path, user=user, all_users=all_users, quiet=quiet) + + if user is not None or all_users: + if all_users: + registry = Click.User.for_all_users(self.db) + else: + registry = Click.User.for_user(self.db, name=user) + registry.set_version(package_name, package_version) + else: + print(dedent("""\ + %s %s has not been registered for any users. + It may be garbage-collected the next time the system starts. + To avoid this, use "click register". + """) % (package_name, package_version)) + + if old_version is not None: + self.db.maybe_remove(package_name, old_version) diff -Nru click-0.4.45.1+16.10.20160916/click_package/json_helpers.py click-0.4.46+16.10.20170607.3/click_package/json_helpers.py --- click-0.4.45.1+16.10.20160916/click_package/json_helpers.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/json_helpers.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,59 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Helper functions to turn json-glib objects into Python objects.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'ClickJsonError', + 'json_array_to_python', + 'json_node_to_python', + 'json_object_to_python', + ] + + +from gi.repository import Json + + +class ClickJsonError(Exception): + pass + + +def json_array_to_python(array): + return [json_node_to_python(element) for element in array.get_elements()] + + +def json_object_to_python(obj): + ret = {} + for name in obj.get_members(): + ret[name] = json_node_to_python(obj.get_member(name)) + return ret + + +def json_node_to_python(node): + node_type = node.get_node_type() + if node_type == Json.NodeType.ARRAY: + return json_array_to_python(node.get_array()) + elif node_type == Json.NodeType.OBJECT: + return json_object_to_python(node.get_object()) + elif node_type == Json.NodeType.NULL: + return None + elif node_type == Json.NodeType.VALUE: + return node.get_value() + else: + raise ClickJsonError( + "Unknown JSON node type \"%s\"" % node_type.value_nick) diff -Nru click-0.4.45.1+16.10.20160916/click_package/Makefile.am click-0.4.46+16.10.20170607.3/click_package/Makefile.am --- click-0.4.45.1+16.10.20160916/click_package/Makefile.am 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/Makefile.am 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,13 @@ +SUBDIRS = tests + +noinst_SCRIPTS = paths.py +CLEANFILES = $(noinst_SCRIPTS) + +do_subst = sed \ + -e 's,[@]sysconfdir[@],$(sysconfdir),g' \ + -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \ + -e 's,[@]pkglibdir[@],$(pkglibdir),g' \ + -e 's,[@]DEFAULT_ROOT[@],$(DEFAULT_ROOT),g' + +paths.py: paths.py.in Makefile + $(do_subst) < $(srcdir)/paths.py.in > $@ diff -Nru click-0.4.45.1+16.10.20160916/click_package/osextras.py click-0.4.46+16.10.20170607.3/click_package/osextras.py --- click-0.4.45.1+16.10.20160916/click_package/osextras.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/osextras.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,92 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Extra OS-level utility functions. + +Usually we can instead use the functions exported from +lib/click/osextras.vala via GObject Introspection. These pure-Python +versions are preserved so that they can be used from code that needs to be +maximally portable: for example, click.build is intended to be usable even +on systems that lack GObject, as long as they have a reasonably recent +version of Python. +""" + +__all__ = [ + 'ensuredir', + 'find_on_path', + 'unlink_force', + ] + + +import errno +import os + +try: + # Python 3.3 + from shutil import which + + def find_on_path(command): + # http://bugs.python.org/issue17012 + path = os.environ.get('PATH', os.pathsep) + return which(command, path=os.environ.get('PATH', path)) is not None +except ImportError: + # Python 2 + def find_on_path(command): + """Is command on the executable search path?""" + if 'PATH' not in os.environ: + return False + path = os.environ['PATH'] + for element in path.split(os.pathsep): + if not element: + continue + filename = os.path.join(element, command) + if os.path.isfile(filename) and os.access(filename, os.X_OK): + return True + return False + + +def ensuredir(directory): + if not os.path.isdir(directory): + os.makedirs(directory) + + +def listdir_force(directory): + try: + return os.listdir(directory) + except OSError as e: + if e.errno == errno.ENOENT: + return [] + raise + + +def unlink_force(path): + """Unlink path, without worrying about whether it exists.""" + try: + os.unlink(path) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + +def symlink_force(source, link_name): + """Create symlink link_name -> source, even if link_name exists.""" + unlink_force(link_name) + os.symlink(source, link_name) + + +def get_umask(): + mask = os.umask(0) + os.umask(mask) + return mask diff -Nru click-0.4.45.1+16.10.20160916/click_package/paths.py.in click-0.4.46+16.10.20170607.3/click_package/paths.py.in --- click-0.4.45.1+16.10.20160916/click_package/paths.py.in 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/paths.py.in 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,19 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Click paths.""" + +preload_path = "@pkglibdir@/libclickpreload.so" +frameworks_dir = "@pkgdatadir@/frameworks" diff -Nru click-0.4.45.1+16.10.20160916/click_package/preinst.py click-0.4.46+16.10.20170607.3/click_package/preinst.py --- click-0.4.45.1+16.10.20160916/click_package/preinst.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/preinst.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,66 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Preinst for Click packages. + +In general there is a rule that Click packages may not have maintainer +scripts. However, there is one exception: a static preinst used to cause +dpkg to fail if people attempt to install Click packages directly using dpkg +rather than via "click install". This avoids accidents, since Click +packages use a different root of their filesystem tarball. +""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'static_preinst', + 'static_preinst_matches', + ] + + +_older_static_preinst = """\ +#! /bin/sh +echo "Click packages may not be installed directly using dpkg." +echo "Use click-install instead." +exit 1 +""" + + +_old_static_preinst = """\ +#! /bin/sh +echo "Click packages may not be installed directly using dpkg." +echo "Use 'click-package install' instead." +exit 1 +""" + + +static_preinst = """\ +#! /bin/sh +echo "Click packages may not be installed directly using dpkg." +echo "Use 'click install' instead." +exit 1 +""" + + +def static_preinst_matches(preinst): + for allow_preinst in ( + _older_static_preinst, + _old_static_preinst, + static_preinst, + ): + if preinst == allow_preinst.encode(): + return True + return False diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/config.py.in click-0.4.46+16.10.20170607.3/click_package/tests/config.py.in --- click-0.4.45.1+16.10.20160916/click_package/tests/config.py.in 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/config.py.in 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,20 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +abs_top_builddir = "@abs_top_builddir@" +STAT_OFFSET_UID = @STAT_OFFSET_UID@ +STAT_OFFSET_GID = @STAT_OFFSET_GID@ +STAT64_OFFSET_UID = @STAT64_OFFSET_UID@ +STAT64_OFFSET_GID = @STAT64_OFFSET_GID@ diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/gimock.py click-0.4.46+16.10.20170607.3/click_package/tests/gimock.py --- click-0.4.45.1+16.10.20160916/click_package/tests/gimock.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/gimock.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,551 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Mock function support based on GObject Introspection. + +(Note to reviewers: I expect to rewrite this from scratch on my own time as +a more generalised set of Python modules for unit testing of C code, +although using similar core ideas. This is a first draft for the purpose of +getting Click's test suite to work expediently, rather than an interface I'm +prepared to commit to long-term.) + +Python is a versatile and concise language for writing tests, and GObject +Introspection (GI) makes it straightforward (often trivial) to bind native +code into Python. However, writing tests for native code quickly runs into +the problem of how to build mock functions. You might reasonably have code +that calls chown(), for instance, and want to test how it's called rather +than worrying about setting up a fakeroot-type environment where chown() +will work. The obvious solution is to use `LD_PRELOAD` wrappers, but there +are various problems to overcome in practice: + + * You can only set up a new `LD_PRELOAD` by going through the run-time + linker; you can't just set it for a single in-process test case. + * Generating the preloaded wrapper involves a fair bit of boilerplate code. + * Having to write per-test mock code in C is inconvenient, and makes it + difficult to get information back out of the mock (such as "how often was + this function called, and with what arguments?"). + +The first problem can be solved by a decorator that knows how to run +individual tests in a subprocess. This is made somewhat more inconvenient +by the fact that there is no way for a context manager's `__enter__` method +to avoid executing the context-managed block other than by throwing an +exception, which makes it hard to silently avoid executing the test case in +the parent process, but we can work around this at the cost of an extra line +of code per invocation. + +For the rest, a combination of GI itself and ctypes can help. We can use GI +to keep track of argument and return types of the mocked C functions in a +reasonably sane way, by parsing header files. We're operating in the other +direction from how GI is normally used, so PyGObject can't deal with +bridging the two calling conventions for us. ctypes can: but we still need +to be careful! We have to construct the callback functions in the child +process, ensure that we keep references to them, and inject function +pointers into the preloaded library via specially-named helper functions; +until those function pointers are set up we must make sure to call the libc +functions instead (since some of them might be called during Python +startup). + +The combination of all of this allows us to bridge C functions somewhat +transparently into Python. This lets you supply a Python function or method +as the mock replacement for a C library function, making it much simpler to +record state. + +It's still not perfect: + + * We're using GI in an upside-down kind of way, and we specifically need + GIR files rather than typelibs so that we can extract the original C + type, so some fiddling is required for each new function you want to + mock. + + * The subprocess arrangements are unavoidably slow and it's possible that + they may cause problems with some test runners. + + * Some C functions (such as `stat`) tend to have multiple underlying entry + points in the C library which must be preloaded independently. + + * You have to be careful about how your libraries are linked, because `ld + -Wl,-Bsymbolic-functions` prevents `LD_PRELOAD` working for intra-library + calls. + + * `ctypes should return composite types from callbacks + `_. The least awful approach for now + seems to be to construct the composite type in question, stash a + reference to it forever, and then return a pointer to it as a void *; we + can only get away with this because tests are by nature relatively + short-lived. + + * The ctypes module's handling of 64-bit pointers is basically just awful. + The right answer is probably to use a different callback-generation + framework entirely (maybe extending PyGObject so that we can get at the + pieces we need), but I've hacked around it for now. + + * It doesn't appear to be possible to install mock replacements for + functions that are called directly from Python code using their GI + wrappers. You can work around this by simply patching the GI wrapper + instead, using `mock.patch`. + +I think the benefits, in terms of local clarity of tests, are worth the +downsides. +""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = ['GIMockTestCase'] + + +import contextlib +import ctypes +import fcntl +from functools import partial +import os +import pickle +import shutil +import subprocess +import sys +import tempfile +from textwrap import dedent +import traceback +import unittest +try: + from unittest import mock +except ImportError: + import mock +try: + import xml.etree.cElementTree as etree +except ImportError: + import xml.etree.ElementTree as etree + +from click_package.tests.gimock_types import Stat, Stat64 +from click_package.tests import config, get_executable + +# Borrowed from giscanner.girparser. +CORE_NS = "http://www.gtk.org/introspection/core/1.0" +C_NS = "http://www.gtk.org/introspection/c/1.0" +GLIB_NS = "http://www.gtk.org/introspection/glib/1.0" + + +def _corens(tag): + return '{%s}%s' % (CORE_NS, tag) + + +def _glibns(tag): + return '{%s}%s' % (GLIB_NS, tag) + + +def _cns(tag): + return '{%s}%s' % (C_NS, tag) + + +# Override some c:type annotations that g-ir-scanner gets a bit wrong. +_c_type_override = { + "passwd*": "struct passwd*", + "stat*": "struct stat*", + "stat64*": "struct stat64*", + } + + +# Mapping of GI type name -> ctypes type. +_typemap = { + "GError**": ctypes.c_void_p, + "gboolean": ctypes.c_int, + "gint": ctypes.c_int, + "gint*": ctypes.POINTER(ctypes.c_int), + "gint32": ctypes.c_int32, + "gpointer": ctypes.c_void_p, + "guint": ctypes.c_uint, + "guint8**": ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8)), + "guint32": ctypes.c_uint32, + "none": None, + "utf8": ctypes.c_char_p, + "utf8*": ctypes.POINTER(ctypes.c_char_p), + } + + +class GIMockTestCase(unittest.TestCase): + def setUp(self): + super(GIMockTestCase, self).setUp() + self._preload_func_refs = [] + self._composite_refs = [] + self._delegate_funcs = {} + + def tearDown(self): + self._preload_func_refs = [] + self._composite_refs = [] + self._delegate_funcs = {} + + def doCleanups(self): + # we do not want to run the cleanups twice, just run it in the parent + if "GIMOCK_SUBPROCESS" not in os.environ: + return super(GIMockTestCase, self).doCleanups() + + def _gir_get_type(self, obj): + ret = {} + arrayinfo = obj.find(_corens("array")) + if arrayinfo is not None: + typeinfo = arrayinfo.find(_corens("type")) + raw_ctype = arrayinfo.get(_cns("type")) + else: + typeinfo = obj.find(_corens("type")) + raw_ctype = typeinfo.get(_cns("type")) + gi_type = typeinfo.get("name") + if obj.get("direction", "in") == "out": + gi_type += "*" + if arrayinfo is not None: + gi_type += "*" + ret["gi"] = gi_type + ret["c"] = _c_type_override.get(raw_ctype, raw_ctype) + return ret + + def _parse_gir(self, path): + # A very, very crude GIR parser. We might have used + # giscanner.girparser, but it's not importable in Python 3 at the + # moment. + tree = etree.parse(path) + root = tree.getroot() + assert root.tag == _corens("repository") + assert root.get("version") == "1.2" + ns = root.find(_corens("namespace")) + assert ns is not None + funcs = {} + for func in ns.findall(_corens("function")): + name = func.get(_cns("identifier")) + # g-ir-scanner skips identifiers starting with "__", which we + # need in order to mock stat effectively. Work around this. + name = name.replace("under_under_", "__") + headers = None + for attr in func.findall(_corens("attribute")): + if attr.get("name") == "headers": + headers = attr.get("value") + break + rv = func.find(_corens("return-value")) + assert rv is not None + params = [] + paramnode = func.find(_corens("parameters")) + if paramnode is not None: + for param in paramnode.findall(_corens("parameter")): + params.append({ + "name": param.get("name"), + "type": self._gir_get_type(param), + }) + if func.get("throws", "0") == "1": + params.append({ + "name": "error", + "type": {"gi": "GError**", "c": "GError**"}, + }) + funcs[name] = { + "name": name, + "headers": headers, + "rv": self._gir_get_type(rv), + "params": params, + } + return funcs + + def _ctypes_type(self, gi_type): + return _typemap[gi_type["gi"]] + + def make_preloads(self, preloads): + rpreloads = [] + std_headers = set([ + "dlfcn.h", + # Not strictly needed, but convenient for ad-hoc debugging. + "stdio.h", + "stdint.h", + "stdlib.h", + "string.h", + "sys/types.h", + "unistd.h", + ]) + preload_headers = set() + funcs = self._parse_gir("click_package/tests/preload.gir") + for name, func in preloads.items(): + info = funcs[name] + rpreloads.append([info, func]) + headers = info["headers"] + if headers is not None: + preload_headers.update(headers.split(",")) + if "GIMOCK_SUBPROCESS" in os.environ: + return None, rpreloads + self._gimock_temp_dir = tempfile.mkdtemp(prefix="gimock") + self.addCleanup(shutil.rmtree, self._gimock_temp_dir) + preloads_dir = os.path.join(self._gimock_temp_dir, "_preloads") + os.makedirs(preloads_dir) + c_path = os.path.join(preloads_dir, "gimockpreload.c") + with open(c_path, "w") as c: + print("#define _GNU_SOURCE", file=c) + for header in sorted(std_headers | preload_headers): + print("#include <%s>" % header, file=c) + print(file=c) + for info, _ in rpreloads: + conv = {} + conv["name"] = info["name"] + argtypes = [p["type"]["c"] for p in info["params"]] + argnames = [p["name"] for p in info["params"]] + conv["ret"] = info["rv"]["c"] + conv["bareproto"] = ", ".join(argtypes) + conv["proto"] = ", ".join( + "%s %s" % pair for pair in zip(argtypes, argnames)) + conv["args"] = ", ".join(argnames) + if conv["ret"] == "gchar*": + conv["need_strdup"] = "strdup" + else: + conv["need_strdup"] = "" + # The delegation scheme used here is needed because trying + # to pass pointers back and forward through ctypes is a + # recipe for having them truncated to 32 bits at the drop of + # a hat. This approach is less obvious but much safer. + print(dedent("""\ + typedef %(ret)s preloadtype_%(name)s (%(bareproto)s); + preloadtype_%(name)s *ctypes_%(name)s = (void *) 0; + preloadtype_%(name)s *real_%(name)s = (void *) 0; + static volatile int delegate_%(name)s = 0; + + extern void _gimock_init_%(name)s (preloadtype_%(name)s *f) + { + ctypes_%(name)s = f; + if (! real_%(name)s) { + /* Retry lookup in case the symbol wasn't + * resolvable until the program under test was + * loaded. + */ + char *err; + dlerror (); + real_%(name)s = dlsym (RTLD_NEXT, "%(name)s"); + if ((err = dlerror ()) != NULL) { + fprintf (stderr, + "Error getting address of symbol " + "'%(name)s': %%s\\n", err); + fflush (stderr); + _exit (1); + } + } + } + """) % conv, file=c) + if conv["ret"] == "void": + print(dedent("""\ + void %(name)s (%(proto)s) + { + if (ctypes_%(name)s) { + delegate_%(name)s = 0; + (*ctypes_%(name)s) (%(args)s); + if (! delegate_%(name)s) + return; + } + if (!real_%(name)s) + _gimock_init_%(name)s (ctypes_%(name)s); + (*real_%(name)s) (%(args)s); + } + """) % conv, file=c) + else: + print(dedent("""\ + %(ret)s %(name)s (%(proto)s) + { + if (ctypes_%(name)s) { + %(ret)s ret; + delegate_%(name)s = 0; + ret = (*ctypes_%(name)s) (%(args)s); + if (! delegate_%(name)s) + return %(need_strdup)s(ret); + } + if (!real_%(name)s) + _gimock_init_%(name)s (ctypes_%(name)s); + return (*real_%(name)s) (%(args)s); + } + """) % conv, file=c) + print(dedent("""\ + extern void _gimock_delegate_%(name)s (void) + { + delegate_%(name)s = 1; + } + """) % conv, file=c) + print(dedent("""\ + static void __attribute__ ((constructor)) + gimockpreload_init (void) + { + char *err; + dlerror (); + """), file=c) + print("\n".join(" " + line for line in (dedent("""\ + real_%(name)s = dlsym (RTLD_NEXT, "%(name)s"); + if ((err = dlerror ()) != NULL) { + fprintf (stderr, + "Error getting address of symbol " + "'%(name)s': %%s\\n", err); + fflush (stderr); + _exit (1); + } + """) % {"name": name}).split("\n")), file=c) + print("}", file=c) + if "GIMOCK_PRELOAD_DEBUG" in os.environ: + with open(c_path) as c: + print(c.read()) + # TODO: Use libtool or similar rather than hardcoding gcc invocation. + lib_path = os.path.join(preloads_dir, "libgimockpreload.so") + libraries = ["glib-2.0", "gee-0.8", "json-glib-1.0"] + cflags = subprocess.check_output( + ["pkg-config", "--cflags"] + libraries, + universal_newlines=True).rstrip("\n").split() + libs = subprocess.check_output( + ["pkg-config", "--libs"] + libraries, + universal_newlines=True).rstrip("\n").split() + compile_cmd = [ + "gcc", "-O0", "-g", "-shared", "-fPIC", "-DPIC", + "-I", "lib/click", + "-L", + os.path.join(config.abs_top_builddir, "lib", "click", ".libs"), + ] + compile_cmd.extend(cflags) + compile_cmd.extend(["-Wl,-soname", "-Wl,libgimockpreload.so"]) + compile_cmd.append("-Wl,--no-as-needed") + compile_cmd.append(c_path) + compile_cmd.append("-lclick-0.4") + compile_cmd.extend(libs) + compile_cmd.append("-ldl") + compile_cmd.extend(["-o", lib_path]) + subprocess.check_call(compile_cmd) + return lib_path, rpreloads + + # Use as: + # with self.run_in_subprocess("func", ...) as (enter, preloads): + # enter() + # # test case body; preloads["func"] will be a mock.MagicMock + # # instance + @contextlib.contextmanager + def run_in_subprocess(self, *patches): + preloads = {} + for patch in patches: + preloads[patch] = mock.MagicMock() + if preloads: + lib_path, rpreloads = self.make_preloads(preloads) + else: + lib_path, rpreloads = None, None + + class ParentProcess(Exception): + pass + + def helper(lib_path, rpreloads): + if "GIMOCK_SUBPROCESS" in os.environ: + del os.environ["LD_PRELOAD"] + preload_lib = ctypes.cdll.LoadLibrary(lib_path) + delegate_cfunctype = ctypes.CFUNCTYPE(None) + for info, func in rpreloads: + signature = [info["rv"]] + [ + p["type"] for p in info["params"]] + signature = [self._ctypes_type(t) for t in signature] + cfunctype = ctypes.CFUNCTYPE(*signature) + init = getattr( + preload_lib, "_gimock_init_%s" % info["name"]) + cfunc = cfunctype(func) + self._preload_func_refs.append(cfunc) + init(cfunc) + delegate = getattr( + preload_lib, "_gimock_delegate_%s" % info["name"]) + self._delegate_funcs[info["name"]] = delegate_cfunctype( + delegate) + return + rfd, wfd = os.pipe() + # It would be cleaner to use subprocess.Popen(pass_fds=[wfd]), but + # that isn't available in Python 2.7. + if hasattr(os, "set_inheritable"): + os.set_inheritable(wfd, True) + else: + fcntl.fcntl(rfd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) + args = get_executable() + [ + "-m", "unittest", + "%s.%s.%s" % ( + self.__class__.__module__, self.__class__.__name__, + self._testMethodName)] + env = os.environ.copy() + env["GIMOCK_SUBPROCESS"] = str(wfd) + if lib_path is not None: + env["LD_PRELOAD"] = lib_path + subp = subprocess.Popen(args, close_fds=False, env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + os.close(wfd) + reader = os.fdopen(rfd, "rb") + stdout, stderr = subp.communicate() + exctype = pickle.load(reader) + # "normal" exit + if exctype is not None and issubclass(exctype, SystemExit): + pass + elif exctype is not None and issubclass(exctype, AssertionError): + print(stdout, file=sys.stdout) + print(stderr, file=sys.stderr) + raise AssertionError("Subprocess failed a test!") + elif exctype is not None or subp.returncode != 0: + print(stdout, file=sys.stdout) + print(stderr, file=sys.stderr) + raise Exception("Subprocess returned an error! (%s, %s)" % ( + exctype, subp.returncode)) + reader.close() + raise ParentProcess() + + try: + yield partial(helper, lib_path, rpreloads), preloads + if "GIMOCK_SUBPROCESS" in os.environ: + wfd = int(os.environ["GIMOCK_SUBPROCESS"]) + writer = os.fdopen(wfd, "wb") + # a sys.ext will generate a SystemExit exception, so we + # push it into the pipe so that the parent knows whats going on + pickle.dump(SystemExit, writer) + writer.flush() + sys.exit(0) + except ParentProcess: + pass + except Exception as e: + if "GIMOCK_SUBPROCESS" in os.environ: + wfd = int(os.environ["GIMOCK_SUBPROCESS"]) + writer = os.fdopen(wfd, "wb") + # It would be better to use tblib to pickle the traceback so + # that we can re-raise it properly from the parent process. + # Until that's packaged and available to us, just print the + # traceback and send the exception type. + print() + traceback.print_exc() + pickle.dump(type(e), writer) + writer.flush() + os._exit(1) + else: + raise + + def make_pointer(self, composite): + # Store a reference to a composite type and return a pointer to it, + # working around http://bugs.python.org/issue5710. + self._composite_refs.append(composite) + return ctypes.addressof(composite) + + def make_string(self, s): + # As make_pointer, but for a string. + copied = ctypes.create_string_buffer(s.encode()) + self._composite_refs.append(copied) + return ctypes.addressof(copied) + + def convert_pointer(self, composite_type, address): + # Return a ctypes composite type instance at a given address. + return composite_type.from_address(address) + + def convert_stat_pointer(self, name, address): + # As convert_pointer, but for a "struct stat *" or "struct stat64 *" + # depending on the wrapped function name. + stat_type = {"__xstat": Stat, "__xstat64": Stat64} + return self.convert_pointer(stat_type[name], address) + + def delegate_to_original(self, name): + # Cause the wrapper function to delegate to the original version + # after the callback returns. (Note that the callback still needs + # to return something type-compatible with the declared result type, + # although the return value will otherwise be ignored.) + self._delegate_funcs[name]() diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/gimock_types.py click-0.4.46+16.10.20170607.3/click_package/tests/gimock_types.py --- click-0.4.45.1+16.10.20160916/click_package/tests/gimock_types.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/gimock_types.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,89 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""A collection of variously hacky ctypes definitions for use with gimock.""" + +import ctypes + +from click_package.tests.config import ( + STAT_OFFSET_GID, + STAT_OFFSET_UID, + STAT64_OFFSET_GID, + STAT64_OFFSET_UID, + ) + + +class Passwd(ctypes.Structure): + _fields_ = [ + ("pw_name", ctypes.c_char_p), + ("pw_passwd", ctypes.c_char_p), + ("pw_uid", ctypes.c_uint32), + ("pw_gid", ctypes.c_uint32), + ("pw_gecos", ctypes.c_char_p), + ("pw_dir", ctypes.c_char_p), + ("pw_shell", ctypes.c_char_p), + ] + + +# TODO: This is pretty awful. The layout of "struct stat" is complicated +# enough that we have to use offsetof() in configure to pick out the fields +# we care about. Fortunately, we only care about a couple of fields, and +# since this is an output parameter it doesn't matter if our structure is +# too short (if we cared about this then we could use AC_CHECK_SIZEOF to +# figure it out). +class Stat(ctypes.Structure): + _pack_ = 1 + _fields_ = [] + _fields_.append( + ("pad0", ctypes.c_ubyte * min(STAT_OFFSET_UID, STAT_OFFSET_GID))) + if STAT_OFFSET_UID < STAT_OFFSET_GID: + _fields_.append(("st_uid", ctypes.c_uint32)) + pad = (STAT_OFFSET_GID - STAT_OFFSET_UID - + ctypes.sizeof(ctypes.c_uint32)) + assert pad >= 0 + if pad > 0: + _fields_.append(("pad1", ctypes.c_ubyte * pad)) + _fields_.append(("st_gid", ctypes.c_uint32)) + else: + _fields_.append(("st_gid", ctypes.c_uint32)) + pad = (STAT_OFFSET_UID - STAT_OFFSET_GID - + ctypes.sizeof(ctypes.c_uint32)) + assert pad >= 0 + if pad > 0: + _fields_.append(("pad1", ctypes.c_ubyte * pad)) + _fields_.append(("st_uid", ctypes.c_uint32)) + + +class Stat64(ctypes.Structure): + _pack_ = 1 + _fields_ = [] + _fields_.append( + ("pad0", ctypes.c_ubyte * min(STAT64_OFFSET_UID, STAT64_OFFSET_GID))) + if STAT64_OFFSET_UID < STAT64_OFFSET_GID: + _fields_.append(("st_uid", ctypes.c_uint32)) + pad = (STAT64_OFFSET_GID - STAT64_OFFSET_UID - + ctypes.sizeof(ctypes.c_uint32)) + assert pad >= 0 + if pad > 0: + _fields_.append(("pad1", ctypes.c_ubyte * pad)) + _fields_.append(("st_gid", ctypes.c_uint32)) + else: + _fields_.append(("st_gid", ctypes.c_uint32)) + pad = (STAT64_OFFSET_UID - STAT64_OFFSET_GID - + ctypes.sizeof(ctypes.c_uint32)) + assert pad >= 0 + if pad > 0: + _fields_.append(("pad1", ctypes.c_ubyte * pad)) + _fields_.append(("st_uid", ctypes.c_uint32)) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/helpers.py click-0.4.46+16.10.20170607.3/click_package/tests/helpers.py --- click-0.4.45.1+16.10.20160916/click_package/tests/helpers.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/helpers.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,357 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# This file contains code from Python 3.3, released under the Python license +# (http://docs.python.org/3/license.html). + +"""Testing helpers.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'TestCase', + 'mkfile', + 'touch', + ] + + +import contextlib +from functools import wraps +import json +import os +import re +import shutil +import sys +import tempfile +import unittest +try: + from unittest import mock +except ImportError: + import mock + +from gi.repository import Click, GLib + +from click_package.tests import gimock + + +def disable_logging(func): + """Decorator to disable logging e.g. during a test""" + @wraps(func) + def wrapper(*args, **kwargs): + import logging + logging.disable(logging.CRITICAL) + try: + return func(*args, **kwargs) + finally: + logging.disable(logging.NOTSET) + return wrapper + + +def make_installed_click(db, db_dir, package="test-1", version="1.0", + json_data={}, make_current=True, user="@all"): + """Create a fake installed click package for the given db/db_dir""" + json_data["name"] = package + json_data["version"] = version + with mkfile_utf8(os.path.join( + db_dir, package, version, ".click", "info", + "%s.manifest" % package)) as f: + json.dump(json_data, f, ensure_ascii=False) + if make_current: + os.symlink( + version, os.path.join(db_dir, package, "current")) + if user == "@all": + registry = Click.User.for_all_users(db) + else: + registry = Click.User.for_user(db, user) + registry.set_version(package, version) + return os.path.join(db_dir, package, version) + + +class TestCase(gimock.GIMockTestCase): + def setUp(self): + super(TestCase, self).setUp() + self.temp_dir = None + self.save_env = dict(os.environ) + self.maxDiff = None + + def tearDown(self): + for key in set(os.environ) - set(self.save_env): + del os.environ[key] + for key, value in os.environ.items(): + if value != self.save_env[key]: + os.environ[key] = self.save_env[key] + for key in set(self.save_env) - set(os.environ): + os.environ[key] = self.save_env[key] + + def use_temp_dir(self): + if self.temp_dir is not None: + return self.temp_dir + self.temp_dir = tempfile.mkdtemp(prefix="click") + self.addCleanup(shutil.rmtree, self.temp_dir) + return self.temp_dir + + # Monkey-patch for Python 2/3 compatibility. + if not hasattr(unittest.TestCase, 'assertCountEqual'): + assertCountEqual = unittest.TestCase.assertItemsEqual + if not hasattr(unittest.TestCase, 'assertRegex'): + assertRegex = unittest.TestCase.assertRegexpMatches + # Renamed in Python 3.2 to omit the trailing 'p'. + if not hasattr(unittest.TestCase, 'assertRaisesRegex'): + assertRaisesRegex = unittest.TestCase.assertRaisesRegexp + + def assertRaisesGError(self, domain_name, code, callableObj, + *args, **kwargs): + with self.assertRaises(GLib.GError) as cm: + callableObj(*args, **kwargs) + self.assertEqual(domain_name, cm.exception.domain) + self.assertEqual(code, cm.exception.code) + + def assertRaisesFileError(self, code, callableObj, *args, **kwargs): + self.assertRaisesGError( + "g-file-error-quark", code, callableObj, *args, **kwargs) + + def assertRaisesDatabaseError(self, code, callableObj, *args, **kwargs): + self.assertRaisesGError( + "click_database_error-quark", code, callableObj, *args, **kwargs) + + def assertRaisesFrameworkError(self, code, callableObj, *args, **kwargs): + self.assertRaisesGError( + "click_framework_error-quark", code, callableObj, *args, **kwargs) + + def assertRaisesHooksError(self, code, callableObj, *args, **kwargs): + self.assertRaisesGError( + "click_hooks_error-quark", code, callableObj, *args, **kwargs) + + def assertRaisesQueryError(self, code, callableObj, *args, **kwargs): + self.assertRaisesGError( + "click_query_error-quark", code, callableObj, *args, **kwargs) + + def assertRaisesUserError(self, code, callableObj, *args, **kwargs): + self.assertRaisesGError( + "click_user_error-quark", code, callableObj, *args, **kwargs) + + def _setup_frameworks(self, preloads, frameworks_dir=None, frameworks=[]): + frameworks_dir = self._create_mock_framework_dir(frameworks_dir) + shutil.rmtree(frameworks_dir, ignore_errors=True) + for framework in frameworks: + self._create_mock_framework_file(framework) + preloads["click_get_frameworks_dir"].side_effect = ( + lambda: self.make_string(frameworks_dir)) + + def _create_mock_framework_dir(self, frameworks_dir=None): + if frameworks_dir is None: + frameworks_dir = os.path.join(self.temp_dir, "frameworks") + patcher = mock.patch('click_package.paths.frameworks_dir', frameworks_dir) + patcher.start() + self.addCleanup(patcher.stop) + Click.ensuredir(frameworks_dir) + return frameworks_dir + + def _create_mock_framework_file(self, framework_name): + self.use_temp_dir() + self._create_mock_framework_dir() + r = r'(?P[a-z]+-sdk)-(?P[0-9.]+)(-[a-z0-9-]+)?' + match = re.match(r, framework_name) + if match is None: + name = "unknown" + ver = "1.0" + else: + name = match.group("name") + ver = match.group("ver") + framework_filename = os.path.join( + self.temp_dir, "frameworks", + "{0}.framework".format(framework_name)) + with open(framework_filename, "w") as f: + f.write("Base-Name: {0}\n".format(name)) + f.write("Base-Version: {0}\n".format(ver)) + + +if not hasattr(mock, "call"): + # mock 0.7.2, the version in Ubuntu 12.04 LTS, lacks mock.ANY and + # mock.call. Since it's so convenient, monkey-patch a partial backport + # (from Python 3.3 unittest.mock) into place here. + class _ANY(object): + "A helper object that compares equal to everything." + + def __eq__(self, other): + return True + + def __ne__(self, other): + return False + + def __repr__(self): + return '' + + mock.ANY = _ANY() + + class _Call(tuple): + """ + A tuple for holding the results of a call to a mock, either in the form + `(args, kwargs)` or `(name, args, kwargs)`. + + If args or kwargs are empty then a call tuple will compare equal to + a tuple without those values. This makes comparisons less verbose:: + + _Call(('name', (), {})) == ('name',) + _Call(('name', (1,), {})) == ('name', (1,)) + _Call(((), {'a': 'b'})) == ({'a': 'b'},) + + The `_Call` object provides a useful shortcut for comparing with call:: + + _Call(((1, 2), {'a': 3})) == call(1, 2, a=3) + _Call(('foo', (1, 2), {'a': 3})) == call.foo(1, 2, a=3) + + If the _Call has no name then it will match any name. + """ + def __new__(cls, value=(), name=None, parent=None, two=False, + from_kall=True): + name = '' + args = () + kwargs = {} + _len = len(value) + if _len == 3: + name, args, kwargs = value + elif _len == 2: + first, second = value + if isinstance(first, str): + name = first + if isinstance(second, tuple): + args = second + else: + kwargs = second + else: + args, kwargs = first, second + elif _len == 1: + value, = value + if isinstance(value, str): + name = value + elif isinstance(value, tuple): + args = value + else: + kwargs = value + + if two: + return tuple.__new__(cls, (args, kwargs)) + + return tuple.__new__(cls, (name, args, kwargs)) + + def __init__(self, value=(), name=None, parent=None, two=False, + from_kall=True): + self.name = name + self.parent = parent + self.from_kall = from_kall + + def __eq__(self, other): + if other is mock.ANY: + return True + try: + len_other = len(other) + except TypeError: + return False + + self_name = '' + if len(self) == 2: + self_args, self_kwargs = self + else: + self_name, self_args, self_kwargs = self + + other_name = '' + if len_other == 0: + other_args, other_kwargs = (), {} + elif len_other == 3: + other_name, other_args, other_kwargs = other + elif len_other == 1: + value, = other + if isinstance(value, tuple): + other_args = value + other_kwargs = {} + elif isinstance(value, str): + other_name = value + other_args, other_kwargs = (), {} + else: + other_args = () + other_kwargs = value + else: + # len 2 + # could be (name, args) or (name, kwargs) or (args, kwargs) + first, second = other + if isinstance(first, str): + other_name = first + if isinstance(second, tuple): + other_args, other_kwargs = second, {} + else: + other_args, other_kwargs = (), second + else: + other_args, other_kwargs = first, second + + if self_name and other_name != self_name: + return False + + # this order is important for ANY to work! + return (other_args, other_kwargs) == (self_args, self_kwargs) + + def __ne__(self, other): + return not self.__eq__(other) + + def __call__(self, *args, **kwargs): + if self.name is None: + return _Call(('', args, kwargs), name='()') + + name = self.name + '()' + return _Call((self.name, args, kwargs), name=name, parent=self) + + def __getattr__(self, attr): + if self.name is None: + return _Call(name=attr, from_kall=False) + name = '%s.%s' % (self.name, attr) + return _Call(name=name, parent=self, from_kall=False) + + mock.call = _Call(from_kall=False) + + +@contextlib.contextmanager +def mkfile(path, mode="w"): + Click.ensuredir(os.path.dirname(path)) + with open(path, mode) as f: + yield f + + +@contextlib.contextmanager +def mkfile_utf8(path, mode="w"): + Click.ensuredir(os.path.dirname(path)) + if sys.version < "3": + import codecs + with codecs.open(path, mode, "UTF-8") as f: + yield f + else: + # io.open is available from Python 2.6, but we only use it with + # Python 3 because it raises exceptions when passed bytes. + import io + with io.open(path, mode, encoding="UTF-8") as f: + yield f + + +def touch(path): + with mkfile(path, mode="a"): + pass + + +def make_file_with_content(filename, content, mode=0o644): + """Create a file with the given content and mode""" + Click.ensuredir(os.path.dirname(filename)) + with open(filename, "w") as f: + f.write(content) + os.chmod(filename, mode) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/__init__.py click-0.4.46+16.10.20170607.3/click_package/tests/__init__.py --- click-0.4.45.1+16.10.20160916/click_package/tests/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/__init__.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,48 @@ +from __future__ import print_function + +import os +import sys + +from click_package.tests import config + + +def _append_env_path(envname, value): + if envname in os.environ: + if value in os.environ[envname].split(":"): + return False + os.environ[envname] = "%s:%s" % (os.environ[envname], value) + else: + os.environ[envname] = value + return True + + +def get_executable(): + """Get python executable (respecting if python-coverage was used)""" + coverage_executable = sys.executable+"-coverage" + if "coverage" in sys.modules and os.path.isfile(coverage_executable): + return [coverage_executable, "run", "-p"] + return [sys.executable] + + +# Don't do any of this in interactive mode. +if not hasattr(sys, "ps1"): + _lib_click_dir = os.path.join(config.abs_top_builddir, "lib", "click") + changed = False + if _append_env_path( + "LD_LIBRARY_PATH", os.path.join(_lib_click_dir, ".libs")): + changed = True + if _append_env_path("GI_TYPELIB_PATH", _lib_click_dir): + changed = True + if changed: + coverage_executable = get_executable() + # We have to re-exec ourselves to get the dynamic loader to pick up + # the new value of LD_LIBRARY_PATH. + if "-m unittest" in sys.argv[0]: + # unittest does horrible things to sys.argv in the name of + # "usefulness", making the re-exec more painful than it needs to + # be. + os.execvp( + coverage_executable[0], coverage_executable + ["-m", "unittest"] + sys.argv[1:]) + else: + os.execvp(coverage_executable[0], coverage_executable + sys.argv) + os._exit(1) Binary files /tmp/tmpXhNPZ8/_HuUQal5dm/click-0.4.45.1+16.10.20160916/click_package/tests/integration/data/evil-keyring/pubring.gpg and /tmp/tmpXhNPZ8/x6Bc0lgh6E/click-0.4.46+16.10.20170607.3/click_package/tests/integration/data/evil-keyring/pubring.gpg differ Binary files /tmp/tmpXhNPZ8/_HuUQal5dm/click-0.4.45.1+16.10.20160916/click_package/tests/integration/data/evil-keyring/secring.gpg and /tmp/tmpXhNPZ8/x6Bc0lgh6E/click-0.4.46+16.10.20170607.3/click_package/tests/integration/data/evil-keyring/secring.gpg differ Binary files /tmp/tmpXhNPZ8/_HuUQal5dm/click-0.4.45.1+16.10.20160916/click_package/tests/integration/data/evil-keyring/trustdb.gpg and /tmp/tmpXhNPZ8/x6Bc0lgh6E/click-0.4.46+16.10.20170607.3/click_package/tests/integration/data/evil-keyring/trustdb.gpg differ Binary files /tmp/tmpXhNPZ8/_HuUQal5dm/click-0.4.45.1+16.10.20160916/click_package/tests/integration/data/origin-keyring/pubring.gpg and /tmp/tmpXhNPZ8/x6Bc0lgh6E/click-0.4.46+16.10.20170607.3/click_package/tests/integration/data/origin-keyring/pubring.gpg differ Binary files /tmp/tmpXhNPZ8/_HuUQal5dm/click-0.4.45.1+16.10.20160916/click_package/tests/integration/data/origin-keyring/secring.gpg and /tmp/tmpXhNPZ8/x6Bc0lgh6E/click-0.4.46+16.10.20170607.3/click_package/tests/integration/data/origin-keyring/secring.gpg differ Binary files /tmp/tmpXhNPZ8/_HuUQal5dm/click-0.4.45.1+16.10.20160916/click_package/tests/integration/data/origin-keyring/trustdb.gpg and /tmp/tmpXhNPZ8/x6Bc0lgh6E/click-0.4.46+16.10.20170607.3/click_package/tests/integration/data/origin-keyring/trustdb.gpg differ diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/helpers.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/helpers.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/helpers.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/helpers.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,131 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests helper for the click CLI interface.""" + +import contextlib +import glob +import json +import os +import random +import shutil +import string +import subprocess +import tempfile +import unittest + + +def require_root(): + if os.getuid() != 0: + raise unittest.SkipTest("This test needs to run as root") + + +def require_network(): + try: + if subprocess.call(["ping", "-c1", "archive.ubuntu.com"]) != 0: + raise unittest.SkipTest("Need network") + except Exception: + pass + + +def require_overlay(): + try: + subprocess.check_call(["/sbin/modprobe", "overlay"]) + except subprocess.CalledProcessError: + raise unittest.SkipTest("Requires overlay fs support") + + +@contextlib.contextmanager +def chdir(target): + curdir = os.getcwd() + os.chdir(target) + try: + yield + finally: + os.chdir(curdir) + + +def cmdline_for_user(username): + """Helper to get the click commandline for the given username""" + if username == "@all": + user = "--all-users" + else: + user = "--user=%s" % username + return user + + +class ClickTestCase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + if "TEST_INTEGRATION" not in os.environ: + raise unittest.SkipTest("Skipping integration tests") + cls.click_binary = os.environ.get("CLICK_BINARY", "/usr/bin/click") + + def setUp(self): + super(ClickTestCase, self).setUp() + self.temp_dir = tempfile.mkdtemp() + + def tearDown(self): + super(ClickTestCase, self).tearDown() + # we force the cleanup before removing the tempdir so that stuff + # in temp_dir is still available + self.doCleanups() + shutil.rmtree(self.temp_dir) + + def click_install(self, path_to_click, click_name, username, + allow_unauthenticated=True): + cmd = [self.click_binary, "install", cmdline_for_user(username)] + if allow_unauthenticated: + cmd.append("--allow-unauthenticated") + cmd.append(path_to_click) + subprocess.check_call(cmd) + self.addCleanup(self.click_unregister, click_name, username) + + def click_unregister(self, click_name, username): + subprocess.check_call( + [self.click_binary, "unregister", cmdline_for_user(username), + click_name]) + + def _create_manifest(self, target, name, version, framework, hooks={}): + with open(target, "w") as f: + json.dump({ + 'name': name, + 'version': str(version), + 'maintainer': 'Foo Bar ', + 'title': 'test title', + 'framework': framework, + 'hooks': hooks, + }, f) + + def _make_click(self, name=None, version=1.0, + framework="ubuntu-sdk-13.10", hooks={}): + if name is None: + name = "com.example.%s" % "".join( + random.choice(string.ascii_lowercase) for i in range(10)) + tmpdir = tempfile.mkdtemp() + self.addCleanup(lambda: shutil.rmtree(tmpdir)) + clickdir = os.path.join(tmpdir, name) + os.makedirs(clickdir) + self._create_manifest(os.path.join(clickdir, "manifest.json"), + name, version, framework, hooks) + with open(os.path.join(clickdir, "README"), "w") as f: + f.write("hello world!") + with chdir(tmpdir), open(os.devnull, "w") as devnull: + subprocess.call( + [self.click_binary, "build", clickdir], stdout=devnull) + generated_clicks = glob.glob(os.path.join(tmpdir, "*.click")) + self.assertEqual(len(generated_clicks), 1) + return generated_clicks[0] diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_build_core_apps.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_build_core_apps.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_build_core_apps.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_build_core_apps.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,133 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests for the click chroot feature.""" + +from glob import glob +import json +import os +import shutil +import subprocess + +from six import with_metaclass + +from .helpers import ( + chdir, + require_network, + require_overlay, + require_root, + ClickTestCase, +) + +# the branches we want to testbuild +TEST_BUILD_BRANCHES = [ + "lp:camera-app", + "lp:notes-app", +] + +# command to "configure" +CORE_APP_CONFIGURE_CMD = [ + "cmake", "..", "-DCLICK_MODE=on", "-DINSTALL_TESTS=off"] + +# command to make install +CLICK_TARGET_DIR = "click-package" +CORE_APP_MAKE_CMD = [ + "make", "DESTDIR=%s" % CLICK_TARGET_DIR, "install"] + +# architectures with native- or cross-compiling support for armhf +ALLOW_ARCHITECTURES = ["amd64", "arm64", "armhf", "i386"] + + +def find_manifest(start_dir): + """Find a click manifest.json{,.in} under the given directory""" + for path, dirs, files in os.walk(start_dir): + for needle in ["manifest.json", "manifest.json.in"]: + if needle in files: + return os.path.join(path, needle) + return None + + +class AddBranchTestFunctions(type): + """Metaclass that creates one test for each branch""" + def __new__(cls, name, bases, dct): + for branch in TEST_BUILD_BRANCHES: + name = "test_build_%s" % branch.split(":")[1].replace("-", "_") + dct[name] = lambda self: self._testbuild_branch(branch) + return type.__new__(cls, name, bases, dct) + + +class TestBuildCoreApps(with_metaclass(AddBranchTestFunctions, ClickTestCase)): + + @classmethod + def setUpClass(cls): + super(TestBuildCoreApps, cls).setUpClass() + require_root() + require_network() + require_overlay() + + def _run_in_chroot(self, cmd): + """Run the given cmd in a click chroot""" + return subprocess.check_call(self.chroot_cmd + ["run"] + cmd) + + def _set_arch_and_framework_from_manifest(self, manifest): + with open(manifest) as f: + data = json.load(f) + self.arch = data["architecture"] + self.framework = data["framework"] + + @property + def chroot_cmd(self): + return [ + self.click_binary, "chroot", "-a", self.arch, "-f", self.framework] + + def _ensure_click_chroot(self): + if subprocess.call(self.chroot_cmd + ["exists"]) != 0: + subprocess.check_call(self.chroot_cmd + ["create"]) + + def configure(self): + self._run_in_chroot(CORE_APP_CONFIGURE_CMD) + + def make(self): + self._run_in_chroot(CORE_APP_MAKE_CMD) + + def create_click(self): + subprocess.check_call( + [self.click_binary, "build", CLICK_TARGET_DIR]) + # we expect exactly one click + self.assertEqual(len(glob("*.click")), 1) + + def _testbuild_branch(self, branch): + system_arch = subprocess.check_output( + ["dpkg", "--print-architecture"], universal_newlines=True).strip() + if system_arch not in ALLOW_ARCHITECTURES: + self.skipTest("%s has no armhf build support" % system_arch) + # get and parse + branch_dir = branch[len("lp:"):] + build_dir = os.path.join(branch_dir, "build-tree") + if os.path.exists(branch_dir): + subprocess.check_call(["bzr", "pull"], cwd=branch_dir) + else: + subprocess.check_call(["bzr", "branch", branch]) + manifest = find_manifest(branch_dir) + # build it + self._set_arch_and_framework_from_manifest(manifest) + if os.path.exists(build_dir): + shutil.rmtree(build_dir) + os.makedirs(build_dir) + with chdir(build_dir): + self._ensure_click_chroot() + self.configure() + self.make() + self.create_click() diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_build.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_build.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_build.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_build.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,26 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests for the click CLI build command.""" + +import os + +from .helpers import ClickTestCase + + +class TestBuild(ClickTestCase): + def test_build(self): + path_to_click = self._make_click() + self.assertTrue(os.path.exists(path_to_click)) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_buildsource.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_buildsource.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_buildsource.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_buildsource.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,51 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests for the click CLI buildsource command.""" + +import os +import shutil +import subprocess +import tarfile +import tempfile + +from .helpers import ( + chdir, + ClickTestCase, +) + + +class TestBuildSource(ClickTestCase): + + def test_buildsource(self): + temp_dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, temp_dir) + with chdir(temp_dir): + with open(os.path.join(temp_dir, "README"), "w") as f: + f.write("I'm a source package") + os.mkdir(os.path.join(temp_dir, ".git")) + os.mkdir(os.path.join(temp_dir, ".bzr")) + os.mkdir(os.path.join(temp_dir, ".normal")) + self._create_manifest(os.path.join(temp_dir, "manifest.json"), + "srcfoo", "1.2", "ubuntu-sdk-13.10") + subprocess.check_call( + [self.click_binary, "buildsource", temp_dir], + universal_newlines=True) + # ensure we have the content we expect + source_file = "srcfoo_1.2.tar.gz" + tar = tarfile.open(source_file) + self.assertEqual( + sorted(tar.getnames()), + sorted([".", "./.normal", "./manifest.json", "./README"])) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_chroot.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_chroot.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_chroot.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_chroot.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,102 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests for the click chroot feature.""" + +import subprocess +import unittest + +from .helpers import ( + require_network, + require_overlay, + require_root, + ClickTestCase, +) + +# architectures present in 14.04 (current default framework) +ALLOW_ARCHITECTURES = [ + "amd64", "arm64", "armhf", "i386", "powerpc", "ppc64el"] + + +def skipUnlessAllowedArchitecture(): + system_arch = subprocess.check_output( + ["dpkg", "--print-architecture"], universal_newlines=True).strip() + if system_arch in ALLOW_ARCHITECTURES: + return lambda func: func + else: + return unittest.skip("%s does not exist in 14.04") + + +@skipUnlessAllowedArchitecture() +class TestChroot(ClickTestCase): + + @classmethod + def command(cls, arch, *args): + return [cls.click_binary, "chroot", "-a", arch] + list(args) + + @classmethod + def setUpClass(cls): + super(TestChroot, cls).setUpClass() + require_root() + require_network() + require_overlay() + cls.arch = subprocess.check_output( + ["dpkg", "--print-architecture"], universal_newlines=True).strip() + subprocess.check_call(cls.command(cls.arch, "create")) + + @classmethod + def tearDownClass(cls): + subprocess.check_call(cls.command(cls.arch, "destroy")) + + def test_upgrade(self): + subprocess.check_call(self.command(self.arch, "upgrade")) + + def test_install(self): + subprocess.check_call(self.command(self.arch, "install", "apt-utils")) + + def test_run(self): + output = subprocess.check_output( + self.command(self.arch, "run", "echo", "hello world"), + universal_newlines=True) + self.assertEqual(output, "hello world\n") + + def test_maint(self): + output = subprocess.check_output( + self.command(self.arch, "maint", "id"), + universal_newlines=True) + self.assertEqual(output, "uid=0(root) gid=0(root) groups=0(root)\n") + + def test_exists_ok(self): + subprocess.check_call(self.command(self.arch, "exists")) + + def test_exists_no(self): + with self.assertRaises(subprocess.CalledProcessError): + subprocess.check_call( + self.command("arch-that-does-not-exist", "exists")) + + +class TestChrootName(TestChroot): + """Run the chroot tests again with a different --name.""" + + @classmethod + def command(cls, arch, *args): + return super(TestChrootName, cls).command( + arch, "-n", "testname", *args) + + def test_exists_different_name_fails(self): + # "click chroot exists" fails for a non-existent name. + with self.assertRaises(subprocess.CalledProcessError): + subprocess.check_call(super(TestChrootName, self).command( + self.arch, "-n", "testname2", "exists")) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_contents.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_contents.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_contents.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_contents.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,33 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests for the click CLI contents command.""" + +import re +import subprocess + +from .helpers import ClickTestCase + + +class TestContents(ClickTestCase): + def test_contents(self): + name = "com.example.contents" + path_to_click = self._make_click(name) + output = subprocess.check_output([ + self.click_binary, "contents", path_to_click], + universal_newlines=True) + self.assertTrue(re.search( + r'-rw-r[-w]-r-- root/root\s+[0-9]+\s+[0-9-]+ [0-9:]+ ./README', + output)) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_frameworks.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_frameworks.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_frameworks.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_frameworks.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,34 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests for the click CLI frameworks command.""" + +import os +import subprocess + +from .helpers import ClickTestCase + + +class TestFrameworks(ClickTestCase): + def setUp(self): + super(TestFrameworks, self).setUp() + if (not os.path.exists("/usr/share/click/frameworks") or + not os.listdir("/usr/share/click/frameworks")): + self.skipTest("Please install ubuntu-sdk-libs") + + def test_framework_list(self): + output = subprocess.check_output([ + self.click_binary, "framework", "list"], universal_newlines=True) + self.assertTrue("ubuntu-sdk-" in output) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_hook.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_hook.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_hook.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_hook.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,80 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests for the click hook feature.""" + +import os +import subprocess +from textwrap import dedent + +from .helpers import ( + ClickTestCase, + require_root, +) + + +class TestHook(ClickTestCase): + + @classmethod + def setUpClass(cls): + super(TestHook, cls).setUpClass() + require_root() + + def _make_hook(self, name): + hook_fname = "/usr/share/click/hooks/%s.hook" % name + canary_fname = os.path.join(self.temp_dir, "canary.sh") + canary_log = os.path.join(self.temp_dir, "canary.log") + with open(hook_fname, "w") as f: + f.write(dedent("""\ + Pattern: ${home}/${id}.test-hook + User-Level: yes + Exec: %s + Hook-Name: %s + """ % (canary_fname, name))) + with open(canary_fname, "w") as f: + f.write(dedent("""\ + #!/bin/sh + echo "i-hook-you-up" >> %s + """ % canary_log)) + os.chmod(canary_fname, 0o755) + return hook_fname, canary_log + + def test_hook_install_user(self): + # build/install the hook + hook_name = "clicktesthook" + hook_file, hook_log = self._make_hook(hook_name) + self.addCleanup(os.unlink, hook_file) + subprocess.check_call( + [self.click_binary, "hook", "install", hook_name]) + self.addCleanup( + subprocess.check_call, [self.click_binary, "hook", "remove", + hook_name]) + # make click that uses the hook + hooks = {'app1': {hook_name: 'README'}} + click_pkg_name = "com.example.hook-1" + click_pkg = self._make_click( + click_pkg_name, framework="", hooks=hooks) + user = os.environ.get("USER", "root") + self.click_install(click_pkg, click_pkg_name, user) + # ensure we have the hook + generated_hook_file = os.path.expanduser( + "~/com.example.hook-1_app1_1.0.test-hook") + self.assertTrue(os.path.exists(generated_hook_file)) + self.assertEqual( + os.path.realpath(generated_hook_file), + "/opt/click.ubuntu.com/com.example.hook-1/1.0/README") + with open(hook_log) as f: + hook_log_content = f.read().strip() + self.assertEqual("i-hook-you-up", hook_log_content) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_info.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_info.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_info.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_info.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,65 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests for the click CLI info command.""" + +import json +import os +import subprocess + +from .helpers import ClickTestCase + + +class TestInfo(ClickTestCase): + def test_info_from_path(self): + name = "com.example.foo" + path_to_click = self._make_click(name) + output = subprocess.check_output([ + self.click_binary, "info", path_to_click], universal_newlines=True) + self.assertEqual(name, json.loads(output)["name"]) + + def test_info_installed_click(self): + name = "com.example.foo" + user = os.environ.get("USER", "root") + path_to_click = self._make_click(name, framework="") + self.click_install(path_to_click, name, user) + output = subprocess.check_output([ + self.click_binary, "info", name], universal_newlines=True) + self.assertEqual(json.loads(output)["name"], name) + + def test_info_file_in_package(self): + name = "org.example.info" + version = "1.0" + click_pkg = self._make_click(name=name, version=version, framework="") + subprocess.check_call( + [self.click_binary, "install", "--allow-unauthenticated", + "--all-users", click_pkg]) + self.addCleanup( + subprocess.check_call, + [self.click_binary, "unregister", "--all-users", name]) + output = subprocess.check_output( + [self.click_binary, "info", + "/opt/click.ubuntu.com/%s/%s/README" % (name, version)], + universal_newlines=True) + self.assertEqual(name, json.loads(output)["name"]) + + def test_info_different_extension(self): + name = "org.example.info" + raw_path = self._make_click(name) + path = "%s.extra" % raw_path + os.rename(raw_path, path) + output = subprocess.check_output([ + self.click_binary, "info", path], universal_newlines=True) + self.assertEqual(name, json.loads(output)["name"]) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_install.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_install.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_install.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_install.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,118 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests for the click install feature.""" + +import subprocess + +from .helpers import ( + require_root, + ClickTestCase, +) + + +def add_user(name): + subprocess.check_call(["useradd", name]) + return name + + +def del_user(name): + subprocess.check_call(["userdel", "-r", name]) + + +class TestClickInstall(ClickTestCase): + + @classmethod + def setUpClass(cls): + super(TestClickInstall, cls).setUpClass() + require_root() + cls.USER_1 = add_user("click-test-user-1") + cls.USER_2 = add_user("click-test-user-2") + + @classmethod + def tearDownClass(cls): + super(TestClickInstall, cls).tearDownClass() + del_user(cls.USER_1) + del_user(cls.USER_2) + + def test_install_for_single_user(self): + name = "foo-1" + click_pkg = self._make_click(name=name, framework="") + # install it + self.click_install(click_pkg, name, self.USER_1) + # ensure that user-1 has it + output = subprocess.check_output([ + "sudo", "-u", self.USER_1, + self.click_binary, "list"], universal_newlines=True) + self.assertEqual(output, "%s\t1.0\n" % name) + # but not user-2 + output = subprocess.check_output([ + "sudo", "-u", self.USER_2, + self.click_binary, "list"], universal_newlines=True) + self.assertEqual(output, "") + # and that we can see it with the --user option + output = subprocess.check_output( + [self.click_binary, "list", "--user=%s" % self.USER_1], + universal_newlines=True) + self.assertEqual(output, "%s\t1.0\n" % name) + + def test_install_for_single_user_and_register(self): + name = "foo-1" + click_pkg = self._make_click(name=name, framework="") + self.click_install(click_pkg, name, self.USER_1) + # not available for user2 + output = subprocess.check_output([ + "sudo", "-u", self.USER_2, + self.click_binary, "list"], universal_newlines=True) + self.assertEqual(output, "") + # register it + subprocess.check_call( + [self.click_binary, "register", "--user=%s" % self.USER_2, + name, "1.0", ]) + self.addCleanup(self.click_unregister, name, self.USER_2) + # and ensure its available for user2 + output = subprocess.check_output([ + "sudo", "-u", self.USER_2, + self.click_binary, "list"], universal_newlines=True) + self.assertEqual(output, "%s\t1.0\n" % name) + + def test_install_for_all_users(self): + name = "foo-2" + click_pkg = self._make_click(name=name, framework="") + self.click_install(click_pkg, name, "@all") + # ensure all users see it + for user in (self.USER_1, self.USER_2): + output = subprocess.check_output( + ["sudo", "-u", user, self.click_binary, "list"], + universal_newlines=True) + self.assertEqual(output, "%s\t1.0\n" % name) + + def test_pkgdir_after_install(self): + name = "foo-3" + click_pkg = self._make_click(name=name, version="1.2", framework="") + self.click_install(click_pkg, name, "@all") + # from the path + output = subprocess.check_output( + [self.click_binary, "pkgdir", + "/opt/click.ubuntu.com/%s/1.2/README" % name], + universal_newlines=True).strip() + self.assertEqual(output, "/opt/click.ubuntu.com/%s/1.2" % name) + # now test from the click package name + output = subprocess.check_output( + [self.click_binary, "pkgdir", name], + universal_newlines=True).strip() + # note that this is different from above + self.assertEqual( + output, "/opt/click.ubuntu.com/.click/users/@all/%s" % name) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_list.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_list.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_list.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_list.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,33 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests for the click CLI list command.""" + +import os +import subprocess + +from .helpers import ClickTestCase + + +class TestList(ClickTestCase): + def test_list_simple(self): + name = "com.ubuntu.verify-ok" + path_to_click = self._make_click(name, framework="") + user = os.environ.get("USER", "root") + self.click_install(path_to_click, name, user) + output = subprocess.check_output( + [self.click_binary, "list", "--user=%s" % user], + universal_newlines=True) + self.assertIn(name, output) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_signatures.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_signatures.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_signatures.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_signatures.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,389 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests for the click signature checking.""" + +import copy +import os +import shutil +import subprocess +import tarfile +from textwrap import dedent + +import apt + +from click_package import osextras +from .helpers import ( + require_root, + ClickTestCase, +) + + +def makedirs(path): + try: + os.makedirs(path) + except OSError: + pass + + +def get_keyid_from_gpghome(gpg_home): + """Return the public keyid of a given gpg home dir""" + output = subprocess.check_output( + ["gpg", "--home", gpg_home, "--list-keys", "--with-colons"], + universal_newlines=True) + for line in output.splitlines(): + if not line.startswith("pub:"): + continue + return line.split(":")[4] + raise ValueError("Cannot find public key in output: '%s'" % output) + + +class Debsigs: + """Tiny wrapper around the debsigs CLI""" + def __init__(self, gpghome, keyid): + self.keyid = keyid + self.gpghome = gpghome + self.policy = "/etc/debsig/policies/%s/generic.pol" % self.keyid + + def sign(self, filepath, signature_type="origin"): + """Sign the click at filepath""" + env = copy.copy(os.environ) + env["GNUPGHOME"] = os.path.abspath(self.gpghome) + try: + subprocess.check_call( + ["debsigs", + "--sign=%s" % signature_type, + "--default-key=%s" % self.keyid, + filepath], env=env) + finally: + if osextras.find_on_path("gpgconf"): + subprocess.call(["gpgconf", "--kill", "gpg-agent"]) + + def install_signature_policy(self): + """Install/update the system-wide signature policy""" + if apt.Cache()["debsig-verify"].installed >= "0.15": + debsig_xmlns = "https://www.debian.org/debsig/1.0/" + else: + debsig_xmlns = "http://www.debian.org/debsig/1.0/" + xmls = dedent("""\ + + + + + + + + + + + + + + """.format( + debsig_xmlns=debsig_xmlns, keyid=self.keyid, + filename="origin.pub")) + makedirs(os.path.dirname(self.policy)) + with open(self.policy, "w") as f: + f.write(xmls) + self.pubkey_path = ( + "/usr/share/debsig/keyrings/%s/origin.pub" % self.keyid) + makedirs(os.path.dirname(self.pubkey_path)) + shutil.copy( + os.path.join(self.gpghome, "pubring.gpg"), self.pubkey_path) + + def uninstall_signature_policy(self): + # FIXME: update debsig-verify so that it can work from a different + # root than "/" so that the tests do not have to use the + # system root + os.remove(self.policy) + os.remove(self.pubkey_path) + + +class ClickSignaturesTestCase(ClickTestCase): + + @classmethod + def setUpClass(cls): + super(ClickSignaturesTestCase, cls).setUpClass() + require_root() + + def assertClickNoSignatureError(self, cmd_args): + with self.assertRaises(subprocess.CalledProcessError) as cm: + output = subprocess.check_output( + [self.click_binary] + cmd_args, + stderr=subprocess.STDOUT, universal_newlines=True) + output = cm.exception.output + expected_error_message = ("debsig: Origin Signature check failed. " + "This deb might not be signed.") + self.assertIn(expected_error_message, output) + + def assertClickInvalidSignatureError(self, cmd_args): + with self.assertRaises(subprocess.CalledProcessError) as cm: + output = subprocess.check_output( + [self.click_binary] + cmd_args, + stderr=subprocess.STDOUT, universal_newlines=True) + print(output) + + output = cm.exception.output + expected_error_message = "Signature verification error: " + self.assertIn(expected_error_message, output) + + +class TestSignatureVerificationNoSignature(ClickSignaturesTestCase): + + @classmethod + def setUpClass(cls): + super(TestSignatureVerificationNoSignature, cls).setUpClass() + require_root() + + def test_debsig_verify_no_sig(self): + name = "org.example.debsig-no-sig" + path_to_click = self._make_click(name, framework="") + self.assertClickNoSignatureError(["verify", path_to_click]) + + def test_debsig_install_no_sig(self): + name = "org.example.debsig-no-sig" + path_to_click = self._make_click(name, framework="") + self.assertClickNoSignatureError(["install", path_to_click]) + + def test_debsig_install_can_install_with_sig_override(self): + name = "org.example.debsig-no-sig" + path_to_click = self._make_click(name, framework="") + user = os.environ.get("USER", "root") + subprocess.check_call( + [self.click_binary, "install", + "--allow-unauthenticated", "--user=%s" % user, + path_to_click]) + self.addCleanup( + subprocess.call, [self.click_binary, "unregister", + "--user=%s" % user, name]) + + +class TestSignatureVerification(ClickSignaturesTestCase): + + @classmethod + def setUpClass(cls): + super(TestSignatureVerification, cls).setUpClass() + require_root() + + def setUp(self): + super(TestSignatureVerification, self).setUp() + self.user = os.environ.get("USER", "root") + # the valid origin keyring + self.datadir = os.path.join(os.path.dirname(__file__), "data") + origin_keyring_dir = os.path.abspath( + os.path.join(self.datadir, "origin-keyring")) + gpghome = self.make_gpghome(origin_keyring_dir) + keyid = get_keyid_from_gpghome(gpghome) + self.debsigs = Debsigs(gpghome, keyid) + self.debsigs.install_signature_policy() + + def tearDown(self): + self.debsigs.uninstall_signature_policy() + + def make_gpghome(self, source): + gpghome = os.path.join(self.temp_dir, "gnupg") + if os.path.exists(gpghome): + shutil.rmtree(gpghome) + shutil.copytree(source, gpghome) + os.chmod(gpghome, 0o700) + return gpghome + + def test_debsig_install_valid_signature(self): + name = "org.example.debsig-valid-sig" + path_to_click = self._make_click(name, framework="") + self.debsigs.sign(path_to_click) + subprocess.call(["cp", path_to_click, os.path.join("/home/cjwatson/src/ubuntu/click/click", os.path.basename(path_to_click))]) + subprocess.check_call( + [self.click_binary, "install", + "--user=%s" % self.user, + path_to_click]) + self.addCleanup( + subprocess.call, [self.click_binary, "unregister", + "--user=%s" % self.user, name]) + output = subprocess.check_output( + [self.click_binary, "list", "--user=%s" % self.user], + universal_newlines=True) + self.assertIn(name, output) + + def test_debsig_install_signature_not_in_keyring(self): + name = "org.example.debsig-no-keyring-sig" + path_to_click = self._make_click(name, framework="") + evil_keyring_dir = os.path.join(self.datadir, "evil-keyring") + gpghome = self.make_gpghome(evil_keyring_dir) + keyid = get_keyid_from_gpghome(gpghome) + debsig_bad = Debsigs(gpghome, keyid) + debsig_bad.sign(path_to_click) + # and ensure its really not there + self.assertClickInvalidSignatureError(["install", path_to_click]) + output = subprocess.check_output( + [self.click_binary, "list", "--user=%s" % self.user], + universal_newlines=True) + self.assertNotIn(name, output) + + def test_debsig_install_not_a_signature(self): + name = "org.example.debsig-invalid-sig" + path_to_click = self._make_click(name, framework="") + invalid_sig = os.path.join(self.temp_dir, "_gpgorigin") + with open(invalid_sig, "w") as f: + f.write("no-valid-signature") + # add a invalid sig + subprocess.check_call(["ar", "-r", path_to_click, invalid_sig]) + self.assertClickInvalidSignatureError(["install", path_to_click]) + output = subprocess.check_output( + [self.click_binary, "list", "--user=%s" % self.user], + universal_newlines=True) + self.assertNotIn(name, output) + + def test_debsig_install_signature_altered_click(self): + def modify_ar_member(member): + subprocess.check_call( + ["ar", "-x", path_to_click, "control.tar.gz"], + cwd=self.temp_dir) + altered_member = os.path.join(self.temp_dir, member) + with open(altered_member, "ba") as f: + f.write(b"\0") + subprocess.check_call(["ar", "-r", path_to_click, altered_member]) + + # ensure that all members we care about are checked by debsig-verify + for member in ["control.tar.gz", "data.tar.gz", "debian-binary"]: + name = "org.example.debsig-altered-click" + path_to_click = self._make_click(name, framework="") + self.debsigs.sign(path_to_click) + modify_ar_member(member) + self.assertClickInvalidSignatureError(["install", path_to_click]) + output = subprocess.check_output( + [self.click_binary, "list", "--user=%s" % self.user], + universal_newlines=True) + self.assertNotIn(name, output) + + def make_nasty_data_tar(self, compression): + new_data_tar = os.path.join(self.temp_dir, "data.tar." + compression) + evilfile = os.path.join(self.temp_dir, "README.evil") + with open(evilfile, "w") as f: + f.write("I am a nasty README") + with tarfile.open(new_data_tar, "w:"+compression) as tar: + tar.add(evilfile) + return new_data_tar + + def test_debsig_install_signature_injected_data_tar(self): + name = "org.example.debsig-injected-data-click" + path_to_click = self._make_click(name, framework="") + self.debsigs.sign(path_to_click) + new_data = self.make_nasty_data_tar("bz2") + # insert before the real data.tar.gz and ensure this is caught + # NOTE: that right now this will not be caught by debsig-verify + # but later in audit() by debian.debfile.DebFile() + subprocess.check_call(["ar", + "-r", + "-b", "data.tar.gz", + path_to_click, + new_data]) + output = subprocess.check_output( + ["ar", "-t", path_to_click], universal_newlines=True) + self.assertEqual(output.splitlines(), + ["debian-binary", + "_click-binary", + "control.tar.gz", + "data.tar.bz2", + "data.tar.gz", + "_gpgorigin"]) + with self.assertRaises(subprocess.CalledProcessError): + output = subprocess.check_output( + [self.click_binary, "install", path_to_click], + stderr=subprocess.STDOUT, universal_newlines=True) + output = subprocess.check_output( + [self.click_binary, "list", "--user=%s" % self.user], + universal_newlines=True) + self.assertNotIn(name, output) + + def test_debsig_install_signature_replaced_data_tar(self): + name = "org.example.debsig-replaced-data-click" + path_to_click = self._make_click(name, framework="") + self.debsigs.sign(path_to_click) + new_data = self.make_nasty_data_tar("bz2") + # replace data.tar.gz with data.tar.bz2 and ensure this is caught + subprocess.check_call(["ar", + "-d", + path_to_click, + "data.tar.gz", + ]) + subprocess.check_call(["ar", + "-r", + path_to_click, + new_data]) + output = subprocess.check_output( + ["ar", "-t", path_to_click], universal_newlines=True) + self.assertEqual(output.splitlines(), + ["debian-binary", + "_click-binary", + "control.tar.gz", + "_gpgorigin", + "data.tar.bz2", + ]) + with self.assertRaises(subprocess.CalledProcessError) as cm: + output = subprocess.check_output( + [self.click_binary, "install", path_to_click], + stderr=subprocess.STDOUT, universal_newlines=True) + self.assertIn("Signature verification error", cm.exception.output) + output = subprocess.check_output( + [self.click_binary, "list", "--user=%s" % self.user], + universal_newlines=True) + self.assertNotIn(name, output) + + def test_debsig_install_signature_prepend_sig(self): + # this test is probably not really needed, it tries to trick + # the system by prepending a valid signature that is not + # in the keyring. But given that debsig-verify only reads + # the first packet of any given _gpg$foo signature it's + # equivalent to test_debsig_install_signature_not_in_keyring test + name = "org.example.debsig-replaced-data-prepend-sig-click" + path_to_click = self._make_click(name, framework="") + self.debsigs.sign(path_to_click) + new_data = self.make_nasty_data_tar("gz") + # replace data.tar.gz + subprocess.check_call(["ar", + "-r", + path_to_click, + new_data, + ]) + # get previous good _gpgorigin for the old data + subprocess.check_call( + ["ar", "-x", path_to_click, "_gpgorigin"], cwd=self.temp_dir) + with open(os.path.join(self.temp_dir, "_gpgorigin"), "br") as f: + good_gpg_origin = f.read() + # and append a valid signature from a non-keyring key + evil_keyring_dir = os.path.join(self.datadir, "evil-keyring") + gpghome = self.make_gpghome(evil_keyring_dir) + debsig_bad = Debsigs(gpghome, "18B38B9AC1B67A0D") + debsig_bad.sign(path_to_click) + subprocess.check_call( + ["ar", "-x", path_to_click, "_gpgorigin"], cwd=self.temp_dir) + with open(os.path.join(self.temp_dir, "_gpgorigin"), "br") as f: + evil_gpg_origin = f.read() + with open(os.path.join(self.temp_dir, "_gpgorigin"), "wb") as f: + f.write(evil_gpg_origin) + f.write(good_gpg_origin) + subprocess.check_call( + ["ar", "-r", path_to_click, "_gpgorigin"], cwd=self.temp_dir) + # now ensure that the verification fails as well + with self.assertRaises(subprocess.CalledProcessError) as cm: + output = subprocess.check_output( + [self.click_binary, "install", path_to_click], + stderr=subprocess.STDOUT, universal_newlines=True) + self.assertIn("Signature verification error", cm.exception.output) + output = subprocess.check_output( + [self.click_binary, "list", "--user=%s" % self.user], + universal_newlines=True) + self.assertNotIn(name, output) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_verify.py click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_verify.py --- click-0.4.45.1+16.10.20160916/click_package/tests/integration/test_verify.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/integration/test_verify.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,73 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Integration tests for the click CLI verify command.""" + +import os +import subprocess + +from .helpers import ClickTestCase + + +class TestVerify(ClickTestCase): + def test_verify_force_missing_framework_ok(self): + name = "com.example.verify-missing-framework" + path_to_click = self._make_click(name) + output = subprocess.check_output([ + self.click_binary, "verify", + "--force-missing-framework", + "--allow-unauthenticated", + path_to_click], universal_newlines=True) + self.assertEqual(output, "") + + def test_verify_force_ok(self): + name = "com.example.verify-ok" + path_to_click = self._make_click(name, framework="") + output = subprocess.check_output([ + self.click_binary, "verify", "--allow-unauthenticated", + path_to_click], universal_newlines=True) + self.assertEqual(output, "") + + def test_verify_missing_framework(self): + name = "com.example.verify-really-missing-framework" + path_to_click = self._make_click(name, framework="missing") + with self.assertRaises(subprocess.CalledProcessError) as cm: + subprocess.check_output( + [self.click_binary, "verify", + "--allow-unauthenticated", + path_to_click], + universal_newlines=True, stderr=subprocess.STDOUT) + expected_error = ( + 'click_package.framework.ClickFrameworkInvalid: Framework ' + '"missing" not present on system (use ' + '--force-missing-framework option to override)') + self.assertIn(expected_error, cm.exception.output) + + def test_verify_no_click_but_invalid(self): + name = "com.example.verify-no-click" + path_to_click = os.path.join(self.temp_dir, name+".click") + with open(path_to_click, "w") as f: + f.write("something-that-is-not-a-click") + with self.assertRaises(subprocess.CalledProcessError) as cm: + subprocess.check_output( + [self.click_binary, "verify", "--allow-unauthenticated", + path_to_click], + universal_newlines=True, stderr=subprocess.STDOUT) + expected_error = ( + 'click_package.install.DebsigVerifyError: ' + 'Signature verification error: ' + 'debsig: %s does not appear to be a deb format package' + ) % path_to_click + self.assertIn(expected_error, cm.exception.output) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/Makefile.am click-0.4.46+16.10.20170607.3/click_package/tests/Makefile.am --- click-0.4.45.1+16.10.20160916/click_package/tests/Makefile.am 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/Makefile.am 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,22 @@ +noinst_DATA = preload.gir +CLEANFILES = $(noinst_DATA) + +preload.gir: preload.h + PKG_CONFIG_PATH=$(top_builddir)/lib/click g-ir-scanner \ + -n preload --nsversion 0 -l c \ + --pkg glib-2.0 --pkg gee-0.8 --pkg json-glib-1.0 \ + --pkg click-0.4 \ + -I$(top_builddir)/lib/click -L$(top_builddir)/lib/click \ + --accept-unprefixed --warn-all \ + --libtool "$(LIBTOOL)" \ + $< --output $@ + +noinst_SCRIPTS = test_paths.py +CLEANFILES += $(noinst_SCRIPTS) + +do_subst = sed \ + -e 's,[@]sysconfdir[@],$(sysconfdir),g' \ + -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' + +test_paths.py: test_paths.py.in Makefile + $(do_subst) < $(srcdir)/test_paths.py.in > $@ diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/preload.h click-0.4.46+16.10.20170607.3/click_package/tests/preload.h --- click-0.4.45.1+16.10.20160916/click_package/tests/preload.h 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/preload.h 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,99 @@ +#include +#include + +#include + +#include "click.h" + +/** + * chown: (attributes headers=unistd.h) + */ +extern int chown (const char *file, uid_t owner, gid_t group); + +/** + * geteuid: (attributes headers=sys/types.h,unistd.h) + */ +extern uid_t geteuid (void); + +/* Workaround for g-ir-scanner not picking up the type properly: mode_t is + * uint32_t on all glibc platforms. + */ +/** + * mkdir: (attributes headers=sys/stat.h,sys/types.h) + * @mode: (type guint32) + */ +extern int mkdir (const char *pathname, mode_t mode); + +/** + * getpwnam: (attributes headers=sys/types.h,pwd.h) + * + * Returns: (transfer none): + */ +extern struct passwd *getpwnam (const char *name); + +/** + * under_under_xstat: (attributes headers=sys/types.h,sys/stat.h,unistd.h) + */ +extern int under_under_xstat (int ver, const char *pathname, struct stat *buf); + +/** + * under_under_xstat64: (Attributes headers=sys/types.h,sys/stat.h,unistd.h) + */ +extern int under_under_xstat64 (int ver, const char *pathname, struct stat64 *buf); + +const gchar *g_get_user_name (void); + +/** + * g_spawn_sync: (attributes headers=glib.h) + * @argv: (array zero-terminated=1): + * @envp: (array zero-terminated=1): + * @flags: (type gint) + * @child_setup: (type gpointer) + * @standard_output: (out) (array zero-terminated=1) (element-type guint8): + * @standard_error: (out) (array zero-terminated=1) (element-type guint8): + * @exit_status: (out): + */ +gboolean g_spawn_sync (const gchar *working_directory, + gchar **argv, + gchar **envp, + GSpawnFlags flags, + GSpawnChildSetupFunc child_setup, + gpointer user_data, + gchar **standard_output, + gchar **standard_error, + gint *exit_status, + GError **error); + +/** + * click_find_on_path: (attributes headers=glib.h) + */ +gboolean click_find_on_path (const gchar *command); + +/** + * click_get_db_dir: (attributes headers=glib.h) + */ +gchar *click_get_db_dir (void); + +/** + * click_get_frameworks_dir: (attributes headers=glib.h) + */ +gchar *click_get_frameworks_dir (void); + +/** + * click_get_hooks_dir: (attributes headers=glib.h) + */ +gchar *click_get_hooks_dir (void); + +/** + * click_get_user_home: (attributes headers=glib.h) + */ +gchar *click_get_user_home (const gchar *user_name); + +/** + * click_package_install_hooks: (attributes headers=glib.h,click.h) + * @db: (type gpointer) + */ +void click_package_install_hooks (ClickDB *db, const gchar *package, + const gchar *old_version, + const gchar *new_version, + const gchar *user_name, GError **error); diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_arfile.py click-0.4.46+16.10.20170607.3/click_package/tests/test_arfile.py --- click-0.4.45.1+16.10.20160916/click_package/tests/test_arfile.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_arfile.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,86 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Unit tests for click_package.arfile.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'TestArFile', + ] + + +import os +import subprocess + +from click_package.arfile import ArFile +from click_package.tests.helpers import TestCase, touch + + +class TestArFile(TestCase): + def setUp(self): + super(TestArFile, self).setUp() + self.use_temp_dir() + + def test_init_rejects_mode_r(self): + self.assertRaises(ValueError, ArFile, mode="r") + + def test_init_name(self): + path = os.path.join(self.temp_dir, "foo.a") + with ArFile(name=path, mode="w") as arfile: + self.assertEqual("w", arfile.mode) + self.assertEqual("wb", arfile.real_mode) + self.assertEqual(path, arfile.name) + self.assertEqual(path, arfile.fileobj.name) + self.assertTrue(arfile.opened_fileobj) + self.assertFalse(arfile.closed) + + def test_init_rejects_readonly_fileobj(self): + path = os.path.join(self.temp_dir, "foo.a") + touch(path) + with open(path, "rb") as fileobj: + self.assertRaises(ValueError, ArFile, fileobj=fileobj) + + def test_init_fileobj(self): + path = os.path.join(self.temp_dir, "foo.a") + with open(path, "wb") as fileobj: + arfile = ArFile(fileobj=fileobj) + self.assertEqual("w", arfile.mode) + self.assertEqual("wb", arfile.real_mode) + self.assertEqual(path, arfile.name) + self.assertEqual(fileobj, arfile.fileobj) + self.assertFalse(arfile.opened_fileobj) + self.assertFalse(arfile.closed) + + def test_writes_valid_ar_file(self): + member_path = os.path.join(self.temp_dir, "member") + with open(member_path, "wb") as member: + member.write(b"\x00\x01\x02\x03\x04\x05\x06\x07") + path = os.path.join(self.temp_dir, "foo.a") + with ArFile(name=path, mode="w") as arfile: + arfile.add_magic() + arfile.add_data("data-member", b"some data") + arfile.add_file("file-member", member_path) + extract_path = os.path.join(self.temp_dir, "extract") + os.mkdir(extract_path) + subprocess.call(["ar", "x", path], cwd=extract_path) + self.assertCountEqual( + ["data-member", "file-member"], os.listdir(extract_path)) + with open(os.path.join(extract_path, "data-member"), "rb") as member: + self.assertEqual(b"some data", member.read()) + with open(os.path.join(extract_path, "file-member"), "rb") as member: + self.assertEqual( + b"\x00\x01\x02\x03\x04\x05\x06\x07", member.read()) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_build.py click-0.4.46+16.10.20170607.3/click_package/tests/test_build.py --- click-0.4.45.1+16.10.20160916/click_package/tests/test_build.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_build.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,345 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Unit tests for click_package.build.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'TestClickBuilder', + 'TestClickSourceBuilder', + ] + + +import json +import os +import stat +import subprocess +import tarfile +from textwrap import dedent + +from click_package.build import ClickBuildError, ClickBuilder, ClickSourceBuilder +from click_package.preinst import static_preinst +from click_package.tests.helpers import ( + disable_logging, + mkfile, + TestCase, + touch, +) + + +# BAW 2013-04-15: Some tests require umask 022. Use this decorator to +# temporarily tweak the process's umask. The test -- or system -- should +# probably be made more robust instead. +def umask(force_umask): + def decorator(func): + def wrapper(*args, **kws): + old_umask = os.umask(force_umask) + try: + return func(*args, **kws) + finally: + os.umask(old_umask) + return wrapper + return decorator + + +class TestClickBuilderBaseMixin: + def test_read_manifest(self): + self.use_temp_dir() + manifest_path = os.path.join(self.temp_dir, "manifest.json") + with mkfile(manifest_path) as manifest: + print(dedent("""\ + { + "name": "com.example.test", + "version": "1.0", + "maintainer": "Foo Bar ", + "title": "test title", + "framework": "ubuntu-sdk-13.10" + }"""), file=manifest) + self.builder.read_manifest(manifest_path) + self.assertEqual("com.example.test", self.builder.name) + self.assertEqual("1.0", self.builder.version) + self.assertEqual("Foo Bar ", self.builder.maintainer) + self.assertEqual("test title", self.builder.title) + self.assertEqual("all", self.builder.architecture) + + def test_add_file(self): + self.builder.add_file("/nonexistent", "target") + self.assertEqual({"/nonexistent": "target"}, self.builder.file_map) + + def test_epochless_version(self): + self.use_temp_dir() + manifest_path = os.path.join(self.temp_dir, "manifest.json") + for version, epochless_version in ( + ("1.0", "1.0"), + ("1:1.2.3", "1.2.3"), + ): + with mkfile(manifest_path) as manifest: + print(dedent("""\ + { + "name": "com.example.test", + "version": "%s", + "maintainer": "Foo Bar ", + "title": "test title", + "framework": "ubuntu-sdk-13.10" + }""") % version, file=manifest) + self.builder.read_manifest(manifest_path) + self.assertEqual(epochless_version, self.builder.epochless_version) + + def test_manifest_syntax_error(self): + self.use_temp_dir() + manifest_path = os.path.join(self.temp_dir, "manifest.json") + with mkfile(manifest_path) as manifest: + # The comma after the "name" entry is intentionally missing. + print(dedent("""\ + { + "name": "com.example.test" + "version": "1.0" + }"""), file=manifest) + self.assertRaises( + ClickBuildError, self.builder.read_manifest, manifest_path) + + +class TestClickBuilder(TestCase, TestClickBuilderBaseMixin): + def setUp(self): + super(TestClickBuilder, self).setUp() + self.builder = ClickBuilder() + + def extract_field(self, path, name): + return subprocess.check_output( + ["dpkg-deb", "-f", path, name], + universal_newlines=True).rstrip("\n") + + @disable_logging + @umask(0o22) + def test_build(self): + self.use_temp_dir() + scratch = os.path.join(self.temp_dir, "scratch") + with mkfile(os.path.join(scratch, "bin", "foo")) as f: + f.write("test /bin/foo\n") + os.symlink("foo", os.path.join(scratch, "bin", "bar")) + touch(os.path.join(scratch, ".git", "config")) + with mkfile(os.path.join(scratch, "toplevel")) as f: + f.write("test /toplevel\n") + os.symlink( + "file-does-not-exist", os.path.join(scratch, "broken-symlink")) + with mkfile(os.path.join(scratch, "manifest.json")) as f: + json.dump({ + "name": "com.example.test", + "version": "1.0", + "maintainer": "Foo Bar ", + "title": "test title", + "architecture": "all", + "framework": "ubuntu-sdk-13.10", + }, f) + # build() overrides this back to 0o644 + os.fchmod(f.fileno(), 0o600) + self.builder.add_file(scratch, "/") + path = os.path.join(self.temp_dir, "com.example.test_1.0_all.click") + self.assertEqual(path, self.builder.build(self.temp_dir)) + self.assertTrue(os.path.exists(path)) + for key, value in ( + ("Package", "com.example.test"), + ("Version", "1.0"), + ("Click-Version", "0.4"), + ("Architecture", "all"), + ("Maintainer", "Foo Bar "), + ("Description", "test title"), + ): + self.assertEqual(value, self.extract_field(path, key)) + self.assertNotEqual( + "", self.extract_field(path, "Installed-Size")) + control_path = os.path.join(self.temp_dir, "control") + subprocess.check_call(["dpkg-deb", "-e", path, control_path]) + manifest_path = os.path.join(control_path, "manifest") + self.assertEqual(0o644, stat.S_IMODE(os.stat(manifest_path).st_mode)) + with open(os.path.join(scratch, "manifest.json")) as source, \ + open(manifest_path) as target: + source_json = json.load(source) + target_json = json.load(target) + self.assertNotEqual("", target_json["installed-size"]) + del target_json["installed-size"] + self.assertEqual(source_json, target_json) + with open(os.path.join(control_path, "md5sums")) as md5sums: + self.assertRegex( + md5sums.read(), + r"^" + r"eb774c3ead632b397d6450d1df25e001 bin/bar\n" + r"eb774c3ead632b397d6450d1df25e001 bin/foo\n" + r"49327ce6306df8a87522456b14a179e0 toplevel\n" + r"$") + with open(os.path.join(control_path, "preinst")) as preinst: + self.assertEqual(static_preinst, preinst.read()) + contents = subprocess.check_output( + ["dpkg-deb", "-c", path], universal_newlines=True) + self.assertRegex(contents, r"^drwxr-xr-x root/root 0 .* \./\n") + self.assertRegex( + contents, + "\nlrwxrwxrwx root/root 0 .* \./bin/bar -> foo\n") + self.assertRegex( + contents, "\n-rw-r--r-- root/root 14 .* \./bin/foo\n") + self.assertRegex( + contents, "\n-rw-r--r-- root/root 15 .* \./toplevel\n") + extract_path = os.path.join(self.temp_dir, "extract") + subprocess.check_call(["dpkg-deb", "-x", path, extract_path]) + for rel_path in ( + os.path.join("bin", "foo"), + "toplevel", + ): + with open(os.path.join(scratch, rel_path)) as source, \ + open(os.path.join(extract_path, rel_path)) as target: + self.assertEqual(source.read(), target.read()) + self.assertTrue( + os.path.islink(os.path.join(extract_path, "bin", "bar"))) + self.assertEqual( + "foo", os.readlink(os.path.join(extract_path, "bin", "bar"))) + + def _make_scratch_dir(self, manifest_override={}): + self.use_temp_dir() + scratch = os.path.join(self.temp_dir, "scratch") + manifest = { + "name": "com.example.test", + "version": "1.0", + "maintainer": "Foo Bar ", + "title": "test title", + "architecture": "all", + "framework": "ubuntu-sdk-13.10", + } + manifest.update(manifest_override) + with mkfile(os.path.join(scratch, "manifest.json")) as f: + json.dump(manifest, f) + self.builder.add_file(scratch, "/") + return scratch + + @disable_logging + def test_build_excludes_dot_click(self): + scratch = self._make_scratch_dir() + touch(os.path.join(scratch, ".click", "evil-file")) + path = self.builder.build(self.temp_dir) + extract_path = os.path.join(self.temp_dir, "extract") + subprocess.check_call(["dpkg-deb", "-x", path, extract_path]) + self.assertEqual([], os.listdir(extract_path)) + + def test_build_ignore_pattern(self): + scratch = self._make_scratch_dir() + touch(os.path.join(scratch, "build", "foo.o")) + self.builder.add_file(scratch, "/") + self.builder.add_ignore_pattern("build") + path = self.builder.build(self.temp_dir) + extract_path = os.path.join(self.temp_dir, "extract") + subprocess.check_call(["dpkg-deb", "-x", path, extract_path]) + self.assertEqual([], os.listdir(extract_path)) + + @disable_logging + def test_build_multiple_architectures(self): + scratch = self._make_scratch_dir(manifest_override={ + "architecture": ["armhf", "i386"], + }) + path = os.path.join(self.temp_dir, "com.example.test_1.0_multi.click") + self.assertEqual(path, self.builder.build(self.temp_dir)) + self.assertTrue(os.path.exists(path)) + self.assertEqual("multi", self.extract_field(path, "Architecture")) + control_path = os.path.join(self.temp_dir, "control") + subprocess.check_call(["dpkg-deb", "-e", path, control_path]) + manifest_path = os.path.join(control_path, "manifest") + with open(os.path.join(scratch, "manifest.json")) as source, \ + open(manifest_path) as target: + source_json = json.load(source) + target_json = json.load(target) + del target_json["installed-size"] + self.assertEqual(source_json, target_json) + + @disable_logging + def test_build_multiple_frameworks(self): + scratch = self._make_scratch_dir(manifest_override={ + "framework": + "ubuntu-sdk-14.04-basic, ubuntu-sdk-14.04-webapps", + }) + path = self.builder.build(self.temp_dir) + control_path = os.path.join(self.temp_dir, "control") + subprocess.check_call(["dpkg-deb", "-e", path, control_path]) + manifest_path = os.path.join(control_path, "manifest") + with open(os.path.join(scratch, "manifest.json")) as source, \ + open(manifest_path) as target: + source_json = json.load(source) + target_json = json.load(target) + del target_json["installed-size"] + self.assertEqual(source_json, target_json) + + +class TestClickFrameworkValidation(TestCase): + def setUp(self): + super(TestClickFrameworkValidation, self).setUp() + self.builder = ClickBuilder() + for framework_name in ("ubuntu-sdk-13.10", + "ubuntu-sdk-14.04-papi", + "ubuntu-sdk-14.04-html", + "docker-sdk-1.3"): + self._create_mock_framework_file(framework_name) + + def test_validate_framework_good(self): + valid_framework_values = ( + "ubuntu-sdk-13.10", + "ubuntu-sdk-14.04-papi, ubuntu-sdk-14.04-html", + "ubuntu-sdk-13.10, docker-sdk-1.3", + ) + for framework in valid_framework_values: + self.builder._validate_framework(framework) + + def test_validate_framework_bad(self): + invalid_framework_values = ( + "ubuntu-sdk-13.10, ubuntu-sdk-14.04-papi", + "ubuntu-sdk-13.10 (>= 13.10)", + "ubuntu-sdk-13.10 | ubuntu-sdk-14.04", + ) + for framework in invalid_framework_values: + with self.assertRaises(ClickBuildError): + self.builder._validate_framework(framework) + + +class TestClickSourceBuilder(TestCase, TestClickBuilderBaseMixin): + def setUp(self): + super(TestClickSourceBuilder, self).setUp() + self.builder = ClickSourceBuilder() + + @umask(0o22) + def test_build(self): + self.use_temp_dir() + scratch = os.path.join(self.temp_dir, "scratch") + touch(os.path.join(scratch, "bin", "foo")) + touch(os.path.join(scratch, ".git", "config")) + touch(os.path.join(scratch, "foo.so")) + touch(os.path.join(scratch, "build", "meep.goah")) + with mkfile(os.path.join(scratch, "manifest.json")) as f: + json.dump({ + "name": "com.example.test", + "version": "1.0", + "maintainer": "Foo Bar ", + "title": "test title", + "architecture": "all", + "framework": "ubuntu-sdk-13.10", + }, f) + # build() overrides this back to 0o644 + os.fchmod(f.fileno(), 0o600) + self.builder.add_file(scratch, "./") + self.builder.add_ignore_pattern("build") + path = os.path.join(self.temp_dir, "com.example.test_1.0.tar.gz") + self.assertEqual(path, self.builder.build(self.temp_dir)) + self.assertTrue(os.path.exists(path)) + with tarfile.open(path, mode="r:gz") as tar: + self.assertCountEqual( + [".", "./bin", "./bin/foo", "./manifest.json"], tar.getnames()) + self.assertTrue(tar.getmember("./bin/foo").isfile()) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_chroot.py click-0.4.46+16.10.20170607.3/click_package/tests/test_chroot.py --- click-0.4.45.1+16.10.20160916/click_package/tests/test_chroot.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_chroot.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,357 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Michael Vogt + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Unit tests for click_package.chroot.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'TestClickChroot', + ] + +import os +import re +from textwrap import dedent + +from click_package.chroot import ( + ClickChroot, + generate_sources, + strip_dev_series_from_framework, +) +from click_package.tests.helpers import TestCase, mock + + +class FakeClickChroot(ClickChroot): + + def __init__(self, *args, **kwargs): + self.temp_dir = kwargs.pop("temp_dir") + super(FakeClickChroot, self).__init__(*args, **kwargs) + self._exists = False + + def exists(self): + return self._exists + + def maint(self, *args, **kwargs): + self._maint_args = args + self._maint_kwargs = kwargs + return 0 + + def _debootstrap(self, components, mount, archive_mirror, ports_mirror): + os.makedirs(os.path.join(mount, "etc", "apt")) + os.makedirs(os.path.join(mount, "usr", "sbin")) + os.makedirs(os.path.join(mount, "sbin")) + with open(os.path.join(mount, "sbin", "initctl"), "w"): + pass + self._exists = True + + @property + def chroot_config(self): + p = self.temp_dir + super(FakeClickChroot, self).chroot_config + if not os.path.exists(os.path.dirname(p)): + os.makedirs(os.path.dirname(p)) + return p + + +class TestClickChroot(TestCase): + def set_dpkg_native_architecture(self, arch): + """Fool dpkg-architecture into selecting a given native arch.""" + self.use_temp_dir() + dpkg_script_path = os.path.join(self.temp_dir, "dpkg") + with open(dpkg_script_path, "w") as dpkg_script: + print(dedent("""\ + #! /bin/sh + echo %s + """) % arch, file=dpkg_script) + os.chmod(dpkg_script_path, 0o755) + os.environ["PATH"] = "%s:%s" % (self.temp_dir, os.environ["PATH"]) + + def test_get_native_arch_amd64_to_amd64(self): + chroot = ClickChroot("amd64", "ubuntu-sdk-14.04", series="trusty") + self.assertEqual("amd64", chroot._get_native_arch("amd64", "amd64")) + + def test_get_native_arch_amd64_to_armhf(self): + chroot = ClickChroot("armhf", "ubuntu-sdk-14.04", series="trusty") + self.assertEqual("amd64", chroot._get_native_arch("amd64", "armhf")) + + def test_get_native_arch_amd64_to_i386(self): + chroot = ClickChroot("i386", "ubuntu-sdk-14.04", series="trusty") + self.assertEqual("i386", chroot._get_native_arch("amd64", "i386")) + + def test_dpkg_architecture_amd64_to_armhf(self): + self.set_dpkg_native_architecture("amd64") + chroot = ClickChroot("armhf", "ubuntu-sdk-14.04", series="trusty") + self.assertEqual("amd64", chroot.dpkg_architecture["DEB_BUILD_ARCH"]) + self.assertEqual("armhf", chroot.dpkg_architecture["DEB_HOST_ARCH"]) + + def test_dpkg_architecture_i386_to_armhf(self): + self.set_dpkg_native_architecture("i386") + chroot = ClickChroot("armhf", "ubuntu-sdk-14.04", series="trusty") + self.assertEqual("i386", chroot.dpkg_architecture["DEB_BUILD_ARCH"]) + self.assertEqual("armhf", chroot.dpkg_architecture["DEB_HOST_ARCH"]) + + def test_dpkg_architecture_amd64_to_i386(self): + self.set_dpkg_native_architecture("amd64") + chroot = ClickChroot("i386", "ubuntu-sdk-14.04", series="trusty") + self.assertEqual("i386", chroot.dpkg_architecture["DEB_BUILD_ARCH"]) + self.assertEqual("i386", chroot.dpkg_architecture["DEB_HOST_ARCH"]) + + def test_gen_sources_archive_only(self): + chroot = ClickChroot("amd64", "ubuntu-sdk-13.10", series="trusty") + chroot.native_arch = "i386" + sources = generate_sources( + chroot.series, chroot.native_arch, chroot.target_arch, + "http://archive.ubuntu.com/ubuntu", + "http://ports.ubuntu.com/ubuntu-ports", + "main") + self.assertEqual([ + 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu trusty main', + 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu trusty-updates main', + 'deb [arch=amd64] http://archive.ubuntu.com/ubuntu trusty-security main', + 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty main', + 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-updates main', + 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-security main', + 'deb-src http://archive.ubuntu.com/ubuntu trusty main', + 'deb-src http://archive.ubuntu.com/ubuntu trusty-updates main', + 'deb-src http://archive.ubuntu.com/ubuntu trusty-security main', + ], sources) + + def test_gen_sources_mixed_archive_ports(self): + chroot = ClickChroot("armhf", "ubuntu-sdk-13.10", series="trusty") + chroot.native_arch = "i386" + sources = generate_sources( + chroot.series, chroot.native_arch, chroot.target_arch, + "http://archive.ubuntu.com/ubuntu", + "http://ports.ubuntu.com/ubuntu-ports", + "main") + self.assertEqual([ + 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty main', + 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty-updates main', + 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty-security main', + 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty main', + 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-updates main', + 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-security main', + 'deb-src http://archive.ubuntu.com/ubuntu trusty main', + 'deb-src http://archive.ubuntu.com/ubuntu trusty-updates main', + 'deb-src http://archive.ubuntu.com/ubuntu trusty-security main', + ], sources) + + def test_gen_sources_ports_only(self): + chroot = ClickChroot("armhf", "ubuntu-sdk-13.10", series="trusty") + chroot.native_arch = "armel" + sources = generate_sources( + chroot.series, chroot.native_arch, chroot.target_arch, + "http://archive.ubuntu.com/ubuntu", + "http://ports.ubuntu.com/ubuntu-ports", + "main") + self.assertEqual([ + 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty main', + 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty-updates main', + 'deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports trusty-security main', + 'deb [arch=armel] http://ports.ubuntu.com/ubuntu-ports trusty main', + 'deb [arch=armel] http://ports.ubuntu.com/ubuntu-ports trusty-updates main', + 'deb [arch=armel] http://ports.ubuntu.com/ubuntu-ports trusty-security main', + 'deb-src http://archive.ubuntu.com/ubuntu trusty main', + 'deb-src http://archive.ubuntu.com/ubuntu trusty-updates main', + 'deb-src http://archive.ubuntu.com/ubuntu trusty-security main', + ], sources) + + def test_gen_sources_native(self): + chroot = ClickChroot("i386", "ubuntu-sdk-14.04", series="trusty") + chroot.native_arch = "i386" + sources = generate_sources( + chroot.series, chroot.native_arch, chroot.target_arch, + "http://archive.ubuntu.com/ubuntu", + "http://ports.ubuntu.com/ubuntu-ports", + "main") + self.assertEqual([ + 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty main', + 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-updates main', + 'deb [arch=i386] http://archive.ubuntu.com/ubuntu trusty-security main', + 'deb-src http://archive.ubuntu.com/ubuntu trusty main', + 'deb-src http://archive.ubuntu.com/ubuntu trusty-updates main', + 'deb-src http://archive.ubuntu.com/ubuntu trusty-security main', + ], sources) + + def test_make_cross_package_native(self): + chroot = ClickChroot("amd64", "ubuntu-sdk-14.04", series="trusty") + chroot.native_arch = "amd64" + self.assertEqual("g++", chroot._make_cross_package("g++")) + + def test_make_cross_package_cross(self): + chroot = ClickChroot("armhf", "ubuntu-sdk-14.04", series="trusty") + chroot.native_arch = "amd64" + self.assertEqual( + "g++-arm-linux-gnueabihf", chroot._make_cross_package("g++")) + + def test_framework_base_base(self): + chroot = ClickChroot("i386", "ubuntu-sdk-14.04-papi") + self.assertEqual(chroot.framework_base, "ubuntu-sdk-14.04") + + def test_framework_base_series(self): + chroot = ClickChroot("i386", "ubuntu-sdk-14.04") + self.assertEqual(chroot.framework_base, "ubuntu-sdk-14.04") + + def test_chroot_series(self): + chroot = ClickChroot("i386", "ubuntu-sdk-14.04") + self.assertEqual(chroot.series, "trusty") + + def test_chroot_full_name(self): + chroot = ClickChroot("i386", "ubuntu-sdk-14.04") + self.assertEqual(chroot.full_name, "click-ubuntu-sdk-14.04-i386") + + def test_chroot_generate_daemon_config(self): + self.use_temp_dir() + chroot = ClickChroot("i386", "ubuntu-sdk-14.04") + os.makedirs(os.path.join(self.temp_dir, "usr", "sbin")) + daemon_policy = chroot._generate_daemon_policy(self.temp_dir) + with open(daemon_policy) as f: + self.assertEqual(f.read(), chroot.DAEMON_POLICY) + + def test_chroot_generate_finish_script(self): + self.use_temp_dir() + chroot = ClickChroot("i386", "ubuntu-sdk-14.04") + finish_script = chroot._generate_finish_script( + self.temp_dir, + ["build-pkg-1", "build-pkg-2"]) + with open(finish_script) as f: + self.assertEqual(f.read(), dedent("""\ + #!/bin/bash + set -e + # Configure target arch + dpkg --add-architecture i386 + # Reload package lists + apt-get update || true + # Pull down signature requirements + apt-get -y --force-yes install gnupg ubuntu-keyring + # Reload package lists + apt-get update || true + # Disable debconf questions + # so that automated builds won't prompt + echo set debconf/frontend Noninteractive | debconf-communicate + echo set debconf/priority critical | debconf-communicate + apt-get -y --force-yes dist-upgrade + # Install basic build tool set to match buildd + apt-get -y --force-yes install build-pkg-1 build-pkg-2 + # Set up expected /dev entries + if [ ! -r /dev/stdin ]; then + ln -s /proc/self/fd/0 /dev/stdin + fi + if [ ! -r /dev/stdout ]; then + ln -s /proc/self/fd/1 /dev/stdout + fi + if [ ! -r /dev/stderr ]; then + ln -s /proc/self/fd/2 /dev/stderr + fi + # Clean up + rm /finish.sh + apt-get clean + """)) + + def test_chroot_generate_apt_conf_d_empty(self): + self.use_temp_dir() + chroot = ClickChroot("i386", "ubuntu-sdk-14.04") + apt_conf_f = chroot._generate_apt_proxy_file(self.temp_dir, "") + self.assertFalse(os.path.exists(apt_conf_f)) + + def test_chroot_generate_apt_conf_d(self): + self.use_temp_dir() + chroot = ClickChroot("i386", "ubuntu-sdk-14.04") + apt_conf_f = chroot._generate_apt_proxy_file( + self.temp_dir, "http://proxy.example.com") + with open(apt_conf_f) as f: + self.assertEqual( + re.sub(r'\s+', ' ', f.read()), + '// proxy settings copied by click chroot ' + 'Acquire { HTTP { Proxy "http://proxy.example.com"; }; }; ') + + def test_chroot_generate_chroot_config(self): + self.use_temp_dir() + chroot = FakeClickChroot( + "i386", "ubuntu-sdk-14.04", temp_dir=self.temp_dir) + with mock.patch.object(chroot, "user", new="meep"): + chroot._generate_chroot_config(self.temp_dir) + with open(chroot.chroot_config) as f: + content = f.read() + self.assertEqual( + content, dedent("""\ + [click-ubuntu-sdk-14.04-i386] + description=Build chroot for click packages on i386 + users=root,{user} + root-users=root,{user} + source-root-users=root,{user} + type=directory + profile=default + setup.fstab=click/fstab + # Not protocols or services see + # debian bug 557730 + setup.nssdatabases=sbuild/nssdatabases + union-type={overlayfs} + directory={temp_dir} + """).format(user="meep", temp_dir=self.temp_dir, + overlayfs=chroot._get_overlayfs_name())) + + def test_chroot_create_mocked(self): + self.use_temp_dir() + os.environ["http_proxy"] = "http://proxy.example.com/" + target = "ubuntu-sdk-14.04" + chroot = FakeClickChroot( + "i386", target, chroots_dir=self.temp_dir, temp_dir=self.temp_dir) + with mock.patch.object(chroot, "maint") as mock_maint: + mock_maint.return_value = 0 + chroot.create() + mock_maint.assert_called_with("/finish.sh") + # ensure the following files where created inside the chroot + for in_chroot in ["etc/localtime", + "etc/timezone", + "etc/apt/sources.list", + "usr/sbin/policy-rc.d"]: + full_path = os.path.join( + self.temp_dir, chroot.full_name, in_chroot) + self.assertTrue(os.path.exists(full_path)) + # ensure the schroot/chroot.d file was created and looks valid + schroot_d = os.path.join( + self.temp_dir, "etc", "schroot", "chroot.d", chroot.full_name) + self.assertTrue(os.path.exists(schroot_d)) + + def test_chroot_maint(self): + chroot = ClickChroot("i386", "ubuntu-sdk-14.04") + with mock.patch("subprocess.call") as mock_call: + mock_call.return_value = 0 + chroot.maint("foo", "bar") + mock_call.assert_called_with([ + "schroot", "-u", "root", + "-c", "source:"+chroot.full_name, + "--", + "foo", "bar"]) + + def test_chroot_destroy(self): + self.use_temp_dir() + chroot = FakeClickChroot( + "i386", "ubuntu-sdk-14.04", + chroots_dir=self.temp_dir, temp_dir=self.temp_dir) + chroot.create() + chroot_path = os.path.join(self.temp_dir, chroot.full_name) + self.assertTrue(os.path.exists(chroot_path)) + chroot.destroy() + self.assertFalse(os.path.exists(chroot_path)) + + def test_strip_dev_series_from_framework(self): + for have, want in ( + ("ubuntu-sdk-14.10-html-dev1", "ubuntu-sdk-14.10-html"), + ("ubuntu-sdk-14.10-html", "ubuntu-sdk-14.10-html"), + ("ubuntu-sdk-14.04-dev99", "ubuntu-sdk-14.04"), + ): + self.assertEqual(strip_dev_series_from_framework(have), want) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_database.py click-0.4.46+16.10.20170607.3/click_package/tests/test_database.py --- click-0.4.45.1+16.10.20160916/click_package/tests/test_database.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_database.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,936 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Unit tests for click_package.database.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + "TestClickDB", + "TestClickInstalledPackage", + "TestClickSingleDB", + ] + + +from functools import partial +from itertools import takewhile +import json +import os +import unittest + +from gi.repository import Click, GLib +from six import integer_types + +from click_package.json_helpers import json_array_to_python, json_object_to_python +from click_package.tests.gimock_types import Passwd +from click_package.tests.helpers import TestCase, mkfile, touch + + +class TestClickInstalledPackage(TestCase): + def setUp(self): + super(TestClickInstalledPackage, self).setUp() + self.foo = Click.InstalledPackage.new( + "foo", "1.0", "/path/to/foo/1.0", False) + self.foo_clone = Click.InstalledPackage.new( + "foo", "1.0", "/path/to/foo/1.0", False) + self.foo_different_version = Click.InstalledPackage.new( + "foo", "2.0", "/path/to/foo/1.0", False) + self.foo_different_path = Click.InstalledPackage.new( + "foo", "1.0", "/path/to/foo/2.0", False) + self.foo_different_writeable = Click.InstalledPackage.new( + "foo", "1.0", "/path/to/foo/1.0", True) + self.bar = Click.InstalledPackage.new( + "bar", "1.0", "/path/to/foo/1.0", False) + + def test_hash(self): + self.assertIsInstance(self.foo.hash(), integer_types) + self.assertEqual(self.foo.hash(), self.foo_clone.hash()) + self.assertNotEqual(self.foo.hash(), self.foo_different_version.hash()) + self.assertNotEqual(self.foo.hash(), self.foo_different_path.hash()) + self.assertNotEqual( + self.foo.hash(), self.foo_different_writeable.hash()) + self.assertNotEqual(self.foo.hash(), self.bar.hash()) + + # GLib doesn't allow passing an InstalledPackage as an argument here. + @unittest.expectedFailure + def test_equal_to(self): + self.assertTrue(self.foo.equal_to(self.foo_clone)) + self.assertFalse(self.foo.equal_to(self.foo_different_version)) + self.assertFalse(self.foo.equal_to(self.foo_different_path)) + self.assertFalse(self.foo.equal_to(self.foo_different_writeable)) + self.assertFalse(self.foo.equal_to(self.bar)) + + +class TestClickSingleDB(TestCase): + def setUp(self): + super(TestClickSingleDB, self).setUp() + self.use_temp_dir() + self.master_db = Click.DB() + self.master_db.add(self.temp_dir) + self.db = self.master_db.get(self.master_db.props.size - 1) + self.spawn_calls = [] + + def g_spawn_sync_side_effect(self, status_map, working_directory, argv, + envp, flags, child_setup, user_data, + standard_output, standard_error, exit_status, + error): + self.spawn_calls.append(list(takewhile(lambda x: x is not None, argv))) + if argv[0] in status_map: + exit_status[0] = status_map[argv[0]] + else: + self.delegate_to_original("g_spawn_sync") + return 0 + + def _installed_packages_tuplify(self, ip): + return [(p.props.package, p.props.version, p.props.path) for p in ip] + + def test_path(self): + path = os.path.join(self.temp_dir, "a", "1.0") + os.makedirs(path) + self.assertEqual(path, self.db.get_path("a", "1.0")) + self.assertRaisesDatabaseError( + Click.DatabaseError.DOES_NOT_EXIST, self.db.get_path, "a", "1.1") + + def test_has_package_version(self): + os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) + self.assertTrue(self.db.has_package_version("a", "1.0")) + self.assertFalse(self.db.has_package_version("a", "1.1")) + + def test_packages_current(self): + os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) + os.makedirs(os.path.join(self.temp_dir, "a", "1.1")) + a_current = os.path.join(self.temp_dir, "a", "current") + os.symlink("1.1", a_current) + os.makedirs(os.path.join(self.temp_dir, "b", "0.1")) + b_current = os.path.join(self.temp_dir, "b", "current") + os.symlink("0.1", b_current) + os.makedirs(os.path.join(self.temp_dir, "c", "2.0")) + self.assertEqual([ + ("a", "1.1", a_current), + ("b", "0.1", b_current), + ], self._installed_packages_tuplify( + self.db.get_packages(all_versions=False))) + + def test_packages_all(self): + os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) + os.makedirs(os.path.join(self.temp_dir, "a", "1.1")) + os.symlink("1.1", os.path.join(self.temp_dir, "a", "current")) + os.makedirs(os.path.join(self.temp_dir, "b", "0.1")) + os.symlink("0.1", os.path.join(self.temp_dir, "b", "current")) + os.makedirs(os.path.join(self.temp_dir, "c", "2.0")) + self.assertEqual([ + ("a", "1.0", os.path.join(self.temp_dir, "a", "1.0")), + ("a", "1.1", os.path.join(self.temp_dir, "a", "1.1")), + ("b", "0.1", os.path.join(self.temp_dir, "b", "0.1")), + ("c", "2.0", os.path.join(self.temp_dir, "c", "2.0")), + ], self._installed_packages_tuplify( + self.db.get_packages(all_versions=True))) + + def test_packages_all_ignores_non_directory(self): + os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) + touch(os.path.join(self.temp_dir, "file")) + self.assertEqual([ + ("a", "1.0", os.path.join(self.temp_dir, "a", "1.0")), + ], self._installed_packages_tuplify( + self.db.get_packages(all_versions=True))) + + def test_manifest(self): + manifest_path = os.path.join( + self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") + manifest_obj = { + "name": "a", "version": "1.0", "hooks": {"a-app": {}}, + "_should_be_removed": "", + } + with mkfile(manifest_path) as manifest: + json.dump(manifest_obj, manifest) + del manifest_obj["_should_be_removed"] + manifest_obj["_directory"] = os.path.join(self.temp_dir, "a", "1.0") + self.assertEqual( + manifest_obj, + json_object_to_python(self.db.get_manifest("a", "1.0"))) + self.assertRaisesDatabaseError( + Click.DatabaseError.DOES_NOT_EXIST, + self.db.get_manifest, "a", "1.1") + self.assertEqual( + manifest_obj, + json.loads(self.db.get_manifest_as_string("a", "1.0"))) + self.assertRaisesDatabaseError( + Click.DatabaseError.DOES_NOT_EXIST, + self.db.get_manifest_as_string, "a", "1.1") + + def test_manifest_bad(self): + manifest_path = os.path.join( + self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") + with mkfile(manifest_path) as manifest: + print("{bad syntax", file=manifest) + self.assertRaisesDatabaseError( + Click.DatabaseError.BAD_MANIFEST, self.db.get_manifest, "a", "1.0") + self.assertRaisesDatabaseError( + Click.DatabaseError.BAD_MANIFEST, + self.db.get_manifest_as_string, "a", "1.0") + manifest_path = os.path.join( + self.temp_dir, "a", "1.1", ".click", "info", "a.manifest") + with mkfile(manifest_path) as manifest: + print("[0]", file=manifest) + self.assertRaisesDatabaseError( + Click.DatabaseError.BAD_MANIFEST, self.db.get_manifest, "a", "1.1") + self.assertRaisesDatabaseError( + Click.DatabaseError.BAD_MANIFEST, + self.db.get_manifest_as_string, "a", "1.1") + + def test_app_running(self): + with self.run_in_subprocess( + "click_find_on_path", "g_spawn_sync", + ) as (enter, preloads): + enter() + preloads["click_find_on_path"].return_value = True + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) + self.assertTrue(self.db.app_running("foo", "bar", "1.0")) + self.assertEqual( + [[b"ubuntu-app-pid", b"foo_bar_1.0"]], self.spawn_calls) + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8}) + self.assertFalse(self.db.app_running("foo", "bar", "1.0")) + + def test_any_app_running_ubuntu_app_pid(self): + with self.run_in_subprocess( + "click_find_on_path", "g_spawn_sync", + ) as (enter, preloads): + enter() + manifest_path = os.path.join( + self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") + with mkfile(manifest_path) as manifest: + json.dump({"hooks": {"a-app": {}}}, manifest) + preloads["click_find_on_path"].side_effect = ( + lambda command: command == b"ubuntu-app-pid") + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) + self.assertTrue(self.db.any_app_running("a", "1.0")) + self.assertEqual( + [[b"ubuntu-app-pid", b"a_a-app_1.0"]], self.spawn_calls) + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8}) + self.assertFalse(self.db.any_app_running("a", "1.0")) + + def test_any_app_running_upstart_app_pid(self): + with self.run_in_subprocess( + "click_find_on_path", "g_spawn_sync", + ) as (enter, preloads): + enter() + manifest_path = os.path.join( + self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") + with mkfile(manifest_path) as manifest: + json.dump({"hooks": {"a-app": {}}}, manifest) + preloads["click_find_on_path"].side_effect = ( + lambda command: command == b"upstart-app-pid") + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"upstart-app-pid": 0}) + self.assertTrue(self.db.any_app_running("a", "1.0")) + self.assertEqual( + [[b"upstart-app-pid", b"a_a-app_1.0"]], self.spawn_calls) + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"upstart-app-pid": 1 << 8}) + self.assertFalse(self.db.any_app_running("a", "1.0")) + + def test_any_app_running_no_app_pid_command(self): + with self.run_in_subprocess( + "click_find_on_path", "g_spawn_sync", + ) as (enter, preloads): + enter() + manifest_path = os.path.join( + self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") + with mkfile(manifest_path) as manifest: + json.dump({"hooks": {"a-app": {}}}, manifest) + preloads["click_find_on_path"].return_value = False + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) + self.assertFalse(self.db.any_app_running("a", "1.0")) + + def test_any_app_running_missing_app(self): + with self.run_in_subprocess("click_find_on_path") as (enter, preloads): + enter() + preloads["click_find_on_path"].side_effect = ( + lambda command: command == b"ubuntu-app-pid") + self.assertRaisesDatabaseError( + Click.DatabaseError.DOES_NOT_EXIST, + self.db.any_app_running, "a", "1.0") + + def test_any_app_running_bad_manifest(self): + with self.run_in_subprocess( + "click_find_on_path", "g_spawn_sync", + ) as (enter, preloads): + enter() + manifest_path = os.path.join( + self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") + with mkfile(manifest_path) as manifest: + print("{bad syntax", file=manifest) + preloads["click_find_on_path"].side_effect = ( + lambda command: command == b"ubuntu-app-pid") + self.assertFalse(self.db.any_app_running("a", "1.0")) + self.assertFalse(preloads["g_spawn_sync"].called) + + def test_any_app_running_no_hooks(self): + with self.run_in_subprocess( + "click_find_on_path", "g_spawn_sync", + ) as (enter, preloads): + enter() + manifest_path = os.path.join( + self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") + with mkfile(manifest_path) as manifest: + json.dump({}, manifest) + preloads["click_find_on_path"].side_effect = ( + lambda command: command == b"ubuntu-app-pid") + self.assertFalse(self.db.any_app_running("a", "1.0")) + self.assertFalse(preloads["g_spawn_sync"].called) + + def test_maybe_remove_registered(self): + with self.run_in_subprocess( + "click_find_on_path", "g_spawn_sync", + ) as (enter, preloads): + enter() + version_path = os.path.join(self.temp_dir, "a", "1.0") + manifest_path = os.path.join( + version_path, ".click", "info", "a.manifest") + with mkfile(manifest_path) as manifest: + json.dump({"hooks": {"a-app": {}}}, manifest) + user_path = os.path.join( + self.temp_dir, ".click", "users", "test-user", "a") + os.makedirs(os.path.dirname(user_path)) + os.symlink(version_path, user_path) + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) + preloads["click_find_on_path"].return_value = True + self.db.maybe_remove("a", "1.0") + self.assertTrue(os.path.exists(version_path)) + self.assertTrue(os.path.exists(user_path)) + + def test_maybe_remove_running(self): + with self.run_in_subprocess( + "click_find_on_path", "g_spawn_sync", + ) as (enter, preloads): + enter() + version_path = os.path.join(self.temp_dir, "a", "1.0") + manifest_path = os.path.join( + version_path, ".click", "info", "a.manifest") + with mkfile(manifest_path) as manifest: + json.dump({"hooks": {"a-app": {}}}, manifest) + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 0}) + preloads["click_find_on_path"].return_value = True + self.db.maybe_remove("a", "1.0") + self.assertTrue(os.path.exists(version_path)) + + def test_maybe_remove_not_running(self): + with self.run_in_subprocess( + "click_find_on_path", "g_spawn_sync", + ) as (enter, preloads): + enter() + os.environ["TEST_QUIET"] = "1" + version_path = os.path.join(self.temp_dir, "a", "1.0") + manifest_path = os.path.join( + version_path, ".click", "info", "a.manifest") + with mkfile(manifest_path) as manifest: + json.dump({"hooks": {"a-app": {}}}, manifest) + current_path = os.path.join(self.temp_dir, "a", "current") + os.symlink("1.0", current_path) + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8}) + preloads["click_find_on_path"].return_value = True + self.db.maybe_remove("a", "1.0") + self.assertFalse(os.path.exists(os.path.join(self.temp_dir, "a"))) + + def test_gc(self): + with self.run_in_subprocess( + "click_find_on_path", "g_spawn_sync", "getpwnam" + ) as (enter, preloads): + enter() + preloads["getpwnam"].side_effect = ( + lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) + os.environ["TEST_QUIET"] = "1" + a_path = os.path.join(self.temp_dir, "a", "1.0") + a_manifest_path = os.path.join( + a_path, ".click", "info", "a.manifest") + with mkfile(a_manifest_path) as manifest: + json.dump({"hooks": {"a-app": {}}}, manifest) + b_path = os.path.join(self.temp_dir, "b", "1.0") + b_manifest_path = os.path.join( + b_path, ".click", "info", "b.manifest") + with mkfile(b_manifest_path) as manifest: + json.dump({"hooks": {"b-app": {}}}, manifest) + c_path = os.path.join(self.temp_dir, "c", "1.0") + c_manifest_path = os.path.join( + c_path, ".click", "info", "c.manifest") + with mkfile(c_manifest_path) as manifest: + json.dump({"hooks": {"c-app": {}}}, manifest) + a_user_path = os.path.join( + self.temp_dir, ".click", "users", "test-user", "a") + os.makedirs(os.path.dirname(a_user_path)) + os.symlink(a_path, a_user_path) + b_gcinuse_path = os.path.join( + self.temp_dir, ".click", "users", "@gcinuse", "b") + os.makedirs(os.path.dirname(b_gcinuse_path)) + os.symlink(b_path, b_gcinuse_path) + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"ubuntu-app-pid": 1 << 8}) + preloads["click_find_on_path"].return_value = True + self.db.gc() + self.assertTrue(os.path.exists(a_path)) + self.assertFalse(os.path.exists(b_gcinuse_path)) + self.assertFalse(os.path.exists(b_path)) + self.assertFalse(os.path.exists(c_path)) + + def test_gc_ignores_non_directory(self): + with self.run_in_subprocess( + "getpwnam" + ) as (enter, preloads): + enter() + preloads["getpwnam"].side_effect = ( + lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) + a_path = os.path.join(self.temp_dir, "a", "1.0") + a_manifest_path = os.path.join( + a_path, ".click", "info", "a.manifest") + with mkfile(a_manifest_path) as manifest: + json.dump({"hooks": {"a-app": {}}}, manifest) + a_user_path = os.path.join( + self.temp_dir, ".click", "users", "test-user", "a") + os.makedirs(os.path.dirname(a_user_path)) + os.symlink(a_path, a_user_path) + touch(os.path.join(self.temp_dir, "file")) + self.db.gc() + self.assertTrue(os.path.exists(a_path)) + + # Test that bug #1479001 is fixed. Uses the following scenario: + # + # - Two databases: db1 and db2. + # - One package, "test-package": + # - Versions 1 and 3 installed in db1 + # - Version 2 installed in db2 + # - User has a registration in db2 for version 2, where the registration + # timestamp precedes the installation of version 3. + # + # In this case, bug #1479001 expects that the user's registration would + # be updated to 3, since it was installed after the user registered for + # 2, which implies that the user would like the update to 3. + def test_gc_fixes_old_user_registrations(self): + with self.run_in_subprocess("getpwnam") as (enter, preloads): + enter() + + # Setup the system hook + preloads["getpwnam"].side_effect = ( + lambda name: self.make_pointer(Passwd(pw_dir=b"/foo"))) + + # Setup both databases + db1 = os.path.join(self.temp_dir, "db1") + db2 = os.path.join(self.temp_dir, "db2") + db = Click.DB() + db.add(db1) + db.add(db2) + + # Prepare common manifest for the packages + manifest = {"hooks": {"test-app": {"test": "foo"}}} + + # Setup versions 1.0 and 3.0 of package in db1 + version1 = os.path.join(db1, "test-package", "1.0") + with mkfile(os.path.join(version1, ".click", "info", + "test-package.manifest")) as f: + json.dump(manifest, f) + + version3 = os.path.join(db1, "test-package", "3.0") + with mkfile(os.path.join(version3, ".click", "info", + "test-package.manifest")) as f: + json.dump(manifest, f) + + # Setup version 0.2 of package in db2 + version2 = os.path.join(db2, "test-package", "2.0") + with mkfile(os.path.join(version2, ".click", "info", + "test-package.manifest")) as f: + json.dump(manifest, f) + + # Setup the user registration for 2.0 in db2. + registrationPath = os.path.join( + db2, ".click", "users", "foo", "test-package") + os.makedirs(os.path.dirname(registrationPath)) + os.symlink(version2, registrationPath) + + # Run the garbage collection to update the registrations. + db.gc() + + # Verify that the user still has a registration for the package, + # and that it's now registered for version 3.0. + self.assertTrue(os.path.lexists(registrationPath)) + self.assertEqual(version3, os.readlink(registrationPath)) + + user_db = Click.User.for_user(db, "foo") + try: + version = user_db.get_version("test-package") + self.assertEqual("3.0", version) + except: + self.fail("No user registration for 'test-package'") + + def _make_ownership_test(self): + path = os.path.join(self.temp_dir, "a", "1.0") + touch(os.path.join(path, ".click", "info", "a.manifest")) + os.symlink("1.0", os.path.join(self.temp_dir, "a", "current")) + user_path = os.path.join( + self.temp_dir, ".click", "users", "test-user", "a") + os.makedirs(os.path.dirname(user_path)) + os.symlink(path, user_path) + touch(os.path.join(self.temp_dir, ".click", "log")) + + def _set_stat_side_effect(self, preloads, side_effect, limit): + limit = limit.encode() + preloads["__xstat"].side_effect = ( + lambda ver, path, buf: side_effect( + "__xstat", limit, ver, path, buf)) + preloads["__xstat64"].side_effect = ( + lambda ver, path, buf: side_effect( + "__xstat64", limit, ver, path, buf)) + + def test_ensure_ownership_quick_if_correct(self): + def stat_side_effect(name, limit, ver, path, buf): + st = self.convert_stat_pointer(name, buf) + if path == limit: + st.st_uid = 1 + st.st_gid = 1 + return 0 + else: + self.delegate_to_original(name) + return -1 + + with self.run_in_subprocess( + "chown", "getpwnam", "__xstat", "__xstat64", + ) as (enter, preloads): + enter() + preloads["getpwnam"].side_effect = ( + lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) + self._set_stat_side_effect( + preloads, stat_side_effect, self.db.props.root) + + self._make_ownership_test() + self.db.ensure_ownership() + self.assertFalse(preloads["chown"].called) + + def test_ensure_ownership(self): + def stat_side_effect(name, limit, ver, path, buf): + st = self.convert_stat_pointer(name, buf) + if path == limit: + st.st_uid = 2 + st.st_gid = 2 + return 0 + else: + self.delegate_to_original(name) + return -1 + + with self.run_in_subprocess( + "chown", "getpwnam", "__xstat", "__xstat64", + ) as (enter, preloads): + enter() + preloads["getpwnam"].side_effect = ( + lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) + self._set_stat_side_effect( + preloads, stat_side_effect, self.db.props.root) + + self._make_ownership_test() + self.db.ensure_ownership() + expected_paths = [ + self.temp_dir, + os.path.join(self.temp_dir, ".click"), + os.path.join(self.temp_dir, ".click", "log"), + os.path.join(self.temp_dir, ".click", "users"), + os.path.join(self.temp_dir, "a"), + os.path.join(self.temp_dir, "a", "1.0"), + os.path.join(self.temp_dir, "a", "1.0", ".click"), + os.path.join(self.temp_dir, "a", "1.0", ".click", "info"), + os.path.join( + self.temp_dir, "a", "1.0", ".click", "info", "a.manifest"), + os.path.join(self.temp_dir, "a", "current"), + ] + self.assertCountEqual( + [path.encode() for path in expected_paths], + [args[0][0] for args in preloads["chown"].call_args_list]) + self.assertCountEqual( + [(1, 1)], + set(args[0][1:] for args in preloads["chown"].call_args_list)) + + def test_ensure_ownership_missing_clickpkg_user(self): + with self.run_in_subprocess("getpwnam") as (enter, preloads): + enter() + preloads["getpwnam"].return_value = None + self.assertRaisesDatabaseError( + Click.DatabaseError.ENSURE_OWNERSHIP, self.db.ensure_ownership) + + def test_ensure_ownership_failed_chown(self): + def stat_side_effect(name, limit, ver, path, buf): + st = self.convert_stat_pointer(name, buf) + if path == limit: + st.st_uid = 2 + st.st_gid = 2 + return 0 + else: + self.delegate_to_original(name) + return -1 + + with self.run_in_subprocess( + "chown", "getpwnam", "__xstat", "__xstat64", + ) as (enter, preloads): + enter() + preloads["chown"].return_value = -1 + preloads["getpwnam"].side_effect = ( + lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) + self._set_stat_side_effect( + preloads, stat_side_effect, self.db.props.root) + + self._make_ownership_test() + self.assertRaisesDatabaseError( + Click.DatabaseError.ENSURE_OWNERSHIP, self.db.ensure_ownership) + + +class TestClickDB(TestCase): + def setUp(self): + super(TestClickDB, self).setUp() + self.use_temp_dir() + + def _installed_packages_tuplify(self, ip): + return [ + (p.props.package, p.props.version, p.props.path, p.props.writeable) + for p in ip] + + def test_read_configuration(self): + with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: + print("[Click Database]", file=a) + print("root = /a", file=a) + with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: + print("[Click Database]", file=b) + print("root = /b", file=b) + db = Click.DB() + db.read(db_dir=self.temp_dir) + db.add("/c") + self.assertEqual(3, db.props.size) + self.assertEqual( + ["/a", "/b", "/c"], + [db.get(i).props.root for i in range(db.props.size)]) + + def test_no_read(self): + with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: + print("[Click Database]", file=a) + print("root = /a", file=a) + db = Click.DB() + self.assertEqual(0, db.props.size) + + def test_no_db_conf_errors(self): + db = Click.DB() + self.assertRaisesDatabaseError( + Click.DatabaseError.INVALID, db.get, 0) + self.assertEqual(db.props.overlay, "") + self.assertRaisesDatabaseError( + Click.DatabaseError.INVALID, db.maybe_remove, "something", "1.0") + self.assertRaisesDatabaseError( + Click.DatabaseError.INVALID, db.gc) + self.assertRaisesDatabaseError( + Click.DatabaseError.INVALID, db.ensure_ownership) + + def test_read_nonexistent(self): + db = Click.DB() + db.read(db_dir=os.path.join(self.temp_dir, "nonexistent")) + self.assertEqual(0, db.props.size) + + def test_read_not_directory(self): + path = os.path.join(self.temp_dir, "file") + touch(path) + db = Click.DB() + self.assertRaisesFileError(GLib.FileError.NOTDIR, db.read, db_dir=path) + + def test_add(self): + db = Click.DB() + self.assertEqual(0, db.props.size) + db.add("/new/root") + self.assertEqual(1, db.props.size) + self.assertEqual("/new/root", db.get(0).props.root) + + def test_overlay(self): + with open(os.path.join(self.temp_dir, "00_custom.conf"), "w") as f: + print("[Click Database]", file=f) + print("root = /custom", file=f) + with open(os.path.join(self.temp_dir, "99_default.conf"), "w") as f: + print("[Click Database]", file=f) + print("root = /opt/click.ubuntu.com", file=f) + db = Click.DB() + db.read(db_dir=self.temp_dir) + self.assertEqual("/opt/click.ubuntu.com", db.props.overlay) + + def test_path(self): + with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: + print("[Click Database]", file=a) + print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) + with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: + print("[Click Database]", file=b) + print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) + db = Click.DB() + db.read(db_dir=self.temp_dir) + self.assertRaisesDatabaseError( + Click.DatabaseError.DOES_NOT_EXIST, db.get_path, "pkg", "1.0") + os.makedirs(os.path.join(self.temp_dir, "a", "pkg", "1.0")) + self.assertEqual( + os.path.join(self.temp_dir, "a", "pkg", "1.0"), + db.get_path("pkg", "1.0")) + self.assertRaisesDatabaseError( + Click.DatabaseError.DOES_NOT_EXIST, db.get_path, "pkg", "1.1") + os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.0")) + # The deepest copy of the same package/version is still preferred. + self.assertEqual( + os.path.join(self.temp_dir, "a", "pkg", "1.0"), + db.get_path("pkg", "1.0")) + os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.1")) + self.assertEqual( + os.path.join(self.temp_dir, "b", "pkg", "1.1"), + db.get_path("pkg", "1.1")) + + def test_has_package_version(self): + with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: + print("[Click Database]", file=a) + print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) + with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: + print("[Click Database]", file=b) + print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) + db = Click.DB() + db.read(db_dir=self.temp_dir) + self.assertFalse(db.has_package_version("pkg", "1.0")) + os.makedirs(os.path.join(self.temp_dir, "a", "pkg", "1.0")) + self.assertTrue(db.has_package_version("pkg", "1.0")) + self.assertFalse(db.has_package_version("pkg", "1.1")) + os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.0")) + self.assertTrue(db.has_package_version("pkg", "1.0")) + os.makedirs(os.path.join(self.temp_dir, "b", "pkg", "1.1")) + self.assertTrue(db.has_package_version("pkg", "1.1")) + + def test_packages_current(self): + with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: + print("[Click Database]", file=a) + print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) + with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: + print("[Click Database]", file=b) + print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) + db = Click.DB() + db.read(db_dir=self.temp_dir) + self.assertEqual([], list(db.get_packages(all_versions=False))) + os.makedirs(os.path.join(self.temp_dir, "a", "pkg1", "1.0")) + os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) + os.makedirs(os.path.join(self.temp_dir, "b", "pkg1", "1.1")) + pkg1_current = os.path.join(self.temp_dir, "b", "pkg1", "current") + os.symlink("1.1", pkg1_current) + os.makedirs(os.path.join(self.temp_dir, "b", "pkg2", "0.1")) + pkg2_current = os.path.join(self.temp_dir, "b", "pkg2", "current") + os.symlink("0.1", pkg2_current) + self.assertEqual([ + ("pkg1", "1.1", pkg1_current, True), + ("pkg2", "0.1", pkg2_current, True), + ], self._installed_packages_tuplify( + db.get_packages(all_versions=False))) + + def test_packages_all(self): + with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: + print("[Click Database]", file=a) + print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) + with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: + print("[Click Database]", file=b) + print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) + db = Click.DB() + db.read(db_dir=self.temp_dir) + self.assertEqual([], list(db.get_packages(all_versions=True))) + os.makedirs(os.path.join(self.temp_dir, "a", "pkg1", "1.0")) + os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) + os.makedirs(os.path.join(self.temp_dir, "b", "pkg1", "1.1")) + os.symlink("1.1", os.path.join(self.temp_dir, "b", "pkg1", "current")) + os.makedirs(os.path.join(self.temp_dir, "b", "pkg2", "0.1")) + os.symlink("0.1", os.path.join(self.temp_dir, "b", "pkg2", "current")) + self.assertEqual([ + ("pkg1", "1.1", os.path.join(self.temp_dir, "b", "pkg1", "1.1"), + True), + ("pkg2", "0.1", os.path.join(self.temp_dir, "b", "pkg2", "0.1"), + True), + ("pkg1", "1.0", os.path.join(self.temp_dir, "a", "pkg1", "1.0"), + False), + ], self._installed_packages_tuplify( + db.get_packages(all_versions=True))) + + def test_manifest(self): + with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: + print("[Click Database]", file=a) + print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) + with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: + print("[Click Database]", file=b) + print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) + db = Click.DB() + db.read(db_dir=self.temp_dir) + self.assertRaisesDatabaseError( + Click.DatabaseError.DOES_NOT_EXIST, db.get_manifest, "pkg", "1.0") + self.assertRaisesDatabaseError( + Click.DatabaseError.DOES_NOT_EXIST, + db.get_manifest_as_string, "pkg", "1.0") + a_manifest_path = os.path.join( + self.temp_dir, "a", "pkg", "1.0", ".click", "info", "pkg.manifest") + a_manifest_obj = {"name": "pkg", "version": "1.0"} + with mkfile(a_manifest_path) as a_manifest: + json.dump(a_manifest_obj, a_manifest) + a_manifest_obj["_directory"] = os.path.join( + self.temp_dir, "a", "pkg", "1.0") + self.assertEqual( + a_manifest_obj, + json_object_to_python(db.get_manifest("pkg", "1.0"))) + self.assertEqual( + a_manifest_obj, + json.loads(db.get_manifest_as_string("pkg", "1.0"))) + self.assertRaisesDatabaseError( + Click.DatabaseError.DOES_NOT_EXIST, db.get_manifest, "pkg", "1.1") + self.assertRaisesDatabaseError( + Click.DatabaseError.DOES_NOT_EXIST, + db.get_manifest_as_string, "pkg", "1.1") + b_manifest_path = os.path.join( + self.temp_dir, "b", "pkg", "1.1", ".click", "info", "pkg.manifest") + b_manifest_obj = {"name": "pkg", "version": "1.1"} + with mkfile(b_manifest_path) as b_manifest: + json.dump(b_manifest_obj, b_manifest) + b_manifest_obj["_directory"] = os.path.join( + self.temp_dir, "b", "pkg", "1.1") + self.assertEqual( + b_manifest_obj, + json_object_to_python(db.get_manifest("pkg", "1.1"))) + self.assertEqual( + b_manifest_obj, + json.loads(db.get_manifest_as_string("pkg", "1.1"))) + + def test_manifest_bad(self): + with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: + print("[Click Database]", file=a) + print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) + db = Click.DB() + db.read(db_dir=self.temp_dir) + manifest_path = os.path.join( + self.temp_dir, "a", "pkg", "1.0", ".click", "info", "pkg.manifest") + with mkfile(manifest_path) as manifest: + print("{bad syntax", file=manifest) + self.assertRaisesDatabaseError( + Click.DatabaseError.BAD_MANIFEST, db.get_manifest, "pkg", "1.0") + self.assertRaisesDatabaseError( + Click.DatabaseError.BAD_MANIFEST, + db.get_manifest_as_string, "pkg", "1.0") + manifest_path = os.path.join( + self.temp_dir, "a", "pkg", "1.1", ".click", "info", "pkg.manifest") + with mkfile(manifest_path) as manifest: + print("[0]", file=manifest) + self.assertRaisesDatabaseError( + Click.DatabaseError.BAD_MANIFEST, db.get_manifest, "pkg", "1.0") + self.assertRaisesDatabaseError( + Click.DatabaseError.BAD_MANIFEST, + db.get_manifest_as_string, "pkg", "1.0") + + def test_manifests_current(self): + with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: + print("[Click Database]", file=a) + print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) + with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: + print("[Click Database]", file=b) + print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) + db = Click.DB() + db.read(db_dir=self.temp_dir) + self.assertEqual( + [], json_array_to_python(db.get_manifests(all_versions=False))) + self.assertEqual( + [], json.loads(db.get_manifests_as_string(all_versions=False))) + a_pkg1_manifest_path = os.path.join( + self.temp_dir, "a", "pkg1", "1.0", + ".click", "info", "pkg1.manifest") + a_pkg1_manifest_obj = {"name": "pkg1", "version": "1.0"} + with mkfile(a_pkg1_manifest_path) as a_pkg1_manifest: + json.dump(a_pkg1_manifest_obj, a_pkg1_manifest) + os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) + b_pkg1_manifest_path = os.path.join( + self.temp_dir, "b", "pkg1", "1.1", + ".click", "info", "pkg1.manifest") + b_pkg1_manifest_obj = {"name": "pkg1", "version": "1.1"} + with mkfile(b_pkg1_manifest_path) as b_pkg1_manifest: + json.dump(b_pkg1_manifest_obj, b_pkg1_manifest) + os.symlink("1.1", os.path.join(self.temp_dir, "b", "pkg1", "current")) + b_pkg2_manifest_path = os.path.join( + self.temp_dir, "b", "pkg2", "0.1", + ".click", "info", "pkg2.manifest") + b_pkg2_manifest_obj = {"name": "pkg2", "version": "0.1"} + with mkfile(b_pkg2_manifest_path) as b_pkg2_manifest: + json.dump(b_pkg2_manifest_obj, b_pkg2_manifest) + os.symlink("0.1", os.path.join(self.temp_dir, "b", "pkg2", "current")) + b_pkg1_manifest_obj["_directory"] = os.path.join( + self.temp_dir, "b", "pkg1", "1.1") + b_pkg1_manifest_obj["_removable"] = 1 + b_pkg2_manifest_obj["_directory"] = os.path.join( + self.temp_dir, "b", "pkg2", "0.1") + b_pkg2_manifest_obj["_removable"] = 1 + self.assertEqual( + [b_pkg1_manifest_obj, b_pkg2_manifest_obj], + json_array_to_python(db.get_manifests(all_versions=False))) + self.assertEqual( + [b_pkg1_manifest_obj, b_pkg2_manifest_obj], + json.loads(db.get_manifests_as_string(all_versions=False))) + + def test_manifests_all(self): + with open(os.path.join(self.temp_dir, "a.conf"), "w") as a: + print("[Click Database]", file=a) + print("root = %s" % os.path.join(self.temp_dir, "a"), file=a) + with open(os.path.join(self.temp_dir, "b.conf"), "w") as b: + print("[Click Database]", file=b) + print("root = %s" % os.path.join(self.temp_dir, "b"), file=b) + db = Click.DB() + db.read(db_dir=self.temp_dir) + self.assertEqual( + [], json_array_to_python(db.get_manifests(all_versions=True))) + self.assertEqual( + [], json.loads(db.get_manifests_as_string(all_versions=True))) + a_pkg1_manifest_path = os.path.join( + self.temp_dir, "a", "pkg1", "1.0", + ".click", "info", "pkg1.manifest") + a_pkg1_manifest_obj = {"name": "pkg1", "version": "1.0"} + with mkfile(a_pkg1_manifest_path) as a_pkg1_manifest: + json.dump(a_pkg1_manifest_obj, a_pkg1_manifest) + os.symlink("1.0", os.path.join(self.temp_dir, "a", "pkg1", "current")) + b_pkg1_manifest_path = os.path.join( + self.temp_dir, "b", "pkg1", "1.1", + ".click", "info", "pkg1.manifest") + b_pkg1_manifest_obj = {"name": "pkg1", "version": "1.1"} + with mkfile(b_pkg1_manifest_path) as b_pkg1_manifest: + json.dump(b_pkg1_manifest_obj, b_pkg1_manifest) + os.symlink("1.1", os.path.join(self.temp_dir, "b", "pkg1", "current")) + b_pkg2_manifest_path = os.path.join( + self.temp_dir, "b", "pkg2", "0.1", + ".click", "info", "pkg2.manifest") + b_pkg2_manifest_obj = {"name": "pkg2", "version": "0.1"} + with mkfile(b_pkg2_manifest_path) as b_pkg2_manifest: + json.dump(b_pkg2_manifest_obj, b_pkg2_manifest) + os.symlink("0.1", os.path.join(self.temp_dir, "b", "pkg2", "current")) + a_pkg1_manifest_obj["_directory"] = os.path.join( + self.temp_dir, "a", "pkg1", "1.0") + a_pkg1_manifest_obj["_removable"] = 0 + b_pkg1_manifest_obj["_directory"] = os.path.join( + self.temp_dir, "b", "pkg1", "1.1") + b_pkg1_manifest_obj["_removable"] = 1 + b_pkg2_manifest_obj["_directory"] = os.path.join( + self.temp_dir, "b", "pkg2", "0.1") + b_pkg2_manifest_obj["_removable"] = 1 + self.assertEqual( + [b_pkg1_manifest_obj, b_pkg2_manifest_obj, a_pkg1_manifest_obj], + json_array_to_python(db.get_manifests(all_versions=True))) + self.assertEqual( + [b_pkg1_manifest_obj, b_pkg2_manifest_obj, a_pkg1_manifest_obj], + json.loads(db.get_manifests_as_string(all_versions=True))) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_framework.py click-0.4.46+16.10.20170607.3/click_package/tests/test_framework.py --- click-0.4.45.1+16.10.20160916/click_package/tests/test_framework.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_framework.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,142 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Unit tests for click_package.framework.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'TestClickFramework', + ] + + +import os + +from gi.repository import Click + +from click_package.tests.helpers import TestCase, touch + + +class TestClickFramework(TestCase): + def setUp(self): + super(TestClickFramework, self).setUp() + self.use_temp_dir() + + def _setup_frameworks(self, preloads, frameworks_dir=None, frameworks={}): + if frameworks_dir is None: + frameworks_dir = os.path.join(self.temp_dir, "frameworks") + Click.ensuredir(frameworks_dir) + for framework_name in frameworks: + framework_path = os.path.join( + frameworks_dir, "%s.framework" % framework_name) + with open(framework_path, "w") as framework: + for key, value in frameworks[framework_name].items(): + print("%s: %s" % (key, value), file=framework) + preloads["click_get_frameworks_dir"].side_effect = ( + lambda: self.make_string(frameworks_dir)) + + def test_open(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + self._setup_frameworks(preloads, frameworks={"framework-1": {}}) + Click.Framework.open("framework-1") + self.assertRaisesFrameworkError( + Click.FrameworkError.NO_SUCH_FRAMEWORK, + Click.Framework.open, "framework-2") + + def test_has_framework(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + self._setup_frameworks(preloads, frameworks={"framework-1": {}}) + self.assertTrue(Click.Framework.has_framework("framework-1")) + self.assertFalse(Click.Framework.has_framework("framework-2")) + + def test_get_frameworks(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + self._setup_frameworks( + preloads, + frameworks={"ubuntu-sdk-13.10": {}, "ubuntu-sdk-14.04": {}, + "ubuntu-sdk-14.10": {}}) + self.assertEqual( + ["ubuntu-sdk-13.10", "ubuntu-sdk-14.04", "ubuntu-sdk-14.10"], + sorted(f.props.name for f in Click.Framework.get_frameworks())) + + def test_get_frameworks_nonexistent(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + frameworks_dir = os.path.join(self.temp_dir, "nonexistent") + preloads["click_get_frameworks_dir"].side_effect = ( + lambda: self.make_string(frameworks_dir)) + self.assertEqual([], Click.Framework.get_frameworks()) + + def test_get_frameworks_not_directory(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = os.path.join(self.temp_dir, "file") + touch(path) + preloads["click_get_frameworks_dir"].side_effect = ( + lambda: self.make_string(path)) + self.assertEqual([], Click.Framework.get_frameworks()) + + def test_get_frameworks_ignores_other_files(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + frameworks_dir = os.path.join(self.temp_dir, "frameworks") + Click.ensuredir(frameworks_dir) + touch(os.path.join(frameworks_dir, "file")) + preloads["click_get_frameworks_dir"].side_effect = ( + lambda: self.make_string(frameworks_dir)) + self.assertEqual([], Click.Framework.get_frameworks()) + + def test_get_frameworks_ignores_unopenable_files(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + frameworks_dir = os.path.join(self.temp_dir, "frameworks") + Click.ensuredir(frameworks_dir) + os.symlink( + "nonexistent", os.path.join(frameworks_dir, "foo.framework")) + preloads["click_get_frameworks_dir"].side_effect = ( + lambda: self.make_string(frameworks_dir)) + self.assertEqual([], Click.Framework.get_frameworks()) + + def test_fields(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + self._setup_frameworks( + preloads, + frameworks={ + "ubuntu-sdk-14.04-qml": { + "base-name": "ubuntu-sdk", "base-version": "14.04", + }}) + framework = Click.Framework.open("ubuntu-sdk-14.04-qml") + self.assertCountEqual( + ["base-name", "base-version"], framework.get_fields()) + self.assertEqual("ubuntu-sdk", framework.get_field("base-name")) + self.assertEqual("14.04", framework.get_field("base-version")) + self.assertRaisesFrameworkError( + Click.FrameworkError.MISSING_FIELD, + framework.get_field, "nonexistent") + self.assertEqual("ubuntu-sdk", framework.get_base_name()) + self.assertEqual("14.04", framework.get_base_version()) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_hooks.py click-0.4.46+16.10.20170607.3/click_package/tests/test_hooks.py --- click-0.4.45.1+16.10.20160916/click_package/tests/test_hooks.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_hooks.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,1232 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Unit tests for click_package.hooks.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + "TestClickHookSystemLevel", + "TestClickHookUserLevel", + "TestClickPatternFormatter", + "TestPackageInstallHooks", + "TestPackageRemoveHooks", + ] + + +from functools import partial +from itertools import takewhile +import json +import os +from textwrap import dedent + +from gi.repository import Click, GLib + +from click_package.tests.gimock_types import Passwd +from click_package.tests.helpers import TestCase, mkfile, mkfile_utf8 + + +class TestClickPatternFormatter(TestCase): + def _make_variant(self, **kwargs): + # pygobject's Variant creator can't handle maybe types, so we have + # to do this by hand. + builder = GLib.VariantBuilder.new(GLib.VariantType.new("a{sms}")) + for key, value in kwargs.items(): + entry = GLib.VariantBuilder.new(GLib.VariantType.new("{sms}")) + entry.add_value(GLib.Variant.new_string(key)) + entry.add_value(GLib.Variant.new_maybe( + GLib.VariantType.new("s"), + None if value is None else GLib.Variant.new_string(value))) + builder.add_value(entry.end()) + return builder.end() + + def test_expands_provided_keys(self): + self.assertEqual( + "foo.bar", + Click.pattern_format("foo.${key}", self._make_variant(key="bar"))) + self.assertEqual( + "foo.barbaz", + Click.pattern_format( + "foo.${key1}${key2}", + self._make_variant(key1="bar", key2="baz"))) + + def test_expands_missing_keys_to_empty_string(self): + self.assertEqual( + "xy", Click.pattern_format("x${key}y", self._make_variant())) + + def test_preserves_unmatched_dollar(self): + self.assertEqual("$", Click.pattern_format("$", self._make_variant())) + self.assertEqual( + "$ {foo}", Click.pattern_format("$ {foo}", self._make_variant())) + self.assertEqual( + "x${y", + Click.pattern_format("${key}${y", self._make_variant(key="x"))) + + def test_double_dollar(self): + self.assertEqual("$", Click.pattern_format("$$", self._make_variant())) + self.assertEqual( + "${foo}", Click.pattern_format("$${foo}", self._make_variant())) + self.assertEqual( + "x$y", + Click.pattern_format("x$$${key}", self._make_variant(key="y"))) + + def test_possible_expansion(self): + self.assertEqual( + {"id": "abc"}, + Click.pattern_possible_expansion( + "x_abc_1", "x_${id}_${num}", + self._make_variant(num="1")).unpack()) + self.assertIsNone( + Click.pattern_possible_expansion( + "x_abc_1", "x_${id}_${num}", self._make_variant(num="2"))) + + +class TestClickHookBase(TestCase): + + TEST_USER = "test-user" + + def setUp(self): + super(TestClickHookBase, self).setUp() + self.use_temp_dir() + self.db = Click.DB() + self.db.add(self.temp_dir) + self.spawn_calls = [] + + def _make_installed_click(self, package="test-1", version="1.0", + json_data={}, + make_current=True, + all_users=False): + with mkfile_utf8(os.path.join( + self.temp_dir, package, version, ".click", "info", + "%s.manifest" % package)) as f: + json.dump(json_data, f, ensure_ascii=False) + if make_current: + os.symlink( + version, os.path.join(self.temp_dir, package, "current")) + if all_users: + db = Click.User.for_all_users(self.db) + else: + db = Click.User.for_user(self.db, self.TEST_USER) + db.set_version(package, version) + + def _make_hook_file(self, content, hookname="test"): + hook_file = os.path.join(self.hooks_dir, "%s.hook" % hookname) + with mkfile(hook_file) as f: + print(content, file=f) + + def _setup_hooks_dir(self, preloads, hooks_dir=None): + if hooks_dir is None: + hooks_dir = self.temp_dir + preloads["click_get_hooks_dir"].side_effect = ( + lambda: self.make_string(hooks_dir)) + self.hooks_dir = hooks_dir + + def g_spawn_sync_side_effect(self, status_map, working_directory, argv, + envp, flags, child_setup, user_data, + standard_output, standard_error, exit_status, + error): + self.spawn_calls.append(list(takewhile(lambda x: x is not None, argv))) + if argv[0] in status_map: + exit_status[0] = status_map[argv[0]] + else: + self.delegate_to_original("g_spawn_sync") + return 0 + + +class TestClickHookSystemLevel(TestClickHookBase): + def test_open(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file(dedent("""\ + Pattern: /usr/share/test/${id}.test + # Comment + Exec: test-update + User: root + """)) + hook = Click.Hook.open(self.db, "test") + self.assertCountEqual( + ["pattern", "exec", "user"], hook.get_fields()) + self.assertEqual( + "/usr/share/test/${id}.test", hook.get_field("pattern")) + self.assertEqual("test-update", hook.get_field("exec")) + self.assertRaisesHooksError( + Click.HooksError.MISSING_FIELD, hook.get_field, "nonexistent") + self.assertFalse(hook.props.is_user_level) + + def test_open_unopenable_file(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + os.symlink("nonexistent", os.path.join(self.hooks_dir, "foo.hook")) + self.assertRaisesHooksError( + Click.HooksError.NO_SUCH_HOOK, Click.Hook.open, self.db, "foo") + + def test_hook_name_absent(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file( + "Pattern: /usr/share/test/${id}.test") + hook = Click.Hook.open(self.db, "test") + self.assertEqual("test", hook.get_hook_name()) + + def test_hook_name_present(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file(dedent("""\ + Pattern: /usr/share/test/${id}.test + Hook-Name: other""")) + hook = Click.Hook.open(self.db, "test") + self.assertEqual("other", hook.get_hook_name()) + + def test_invalid_app_id(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file(dedent("""\ + Pattern: /usr/share/test/${id}.test + # Comment + Exec: test-update + User: root + """)) + hook = Click.Hook.open(self.db, "test") + self.assertRaisesHooksError( + Click.HooksError.BAD_APP_NAME, hook.get_app_id, + "package", "0.1", "app_name") + self.assertRaisesHooksError( + Click.HooksError.BAD_APP_NAME, hook.get_app_id, + "package", "0.1", "app/name") + self.assertRaisesHooksError( + Click.HooksError.BAD_APP_NAME, hook.get_pattern, + "package", "0.1", "app_name") + self.assertRaisesHooksError( + Click.HooksError.BAD_APP_NAME, hook.get_pattern, + "package", "0.1", "app/name") + + def test_short_id_invalid(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file( + "Pattern: /usr/share/test/${short-id}.test") + hook = Click.Hook.open(self.db, "test") + # It would perhaps be better if unrecognised $-expansions raised + # KeyError, but they don't right now. + self.assertEqual( + "/usr/share/test/.test", + hook.get_pattern("package", "0.1", "app-name", user_name=None)) + + def test_short_id_valid_with_single_version(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file(dedent("""\ + Pattern: /usr/share/test/${short-id}.test + Single-Version: yes""")) + hook = Click.Hook.open(self.db, "test") + self.assertEqual( + "/usr/share/test/package_app-name.test", + hook.get_pattern("package", "0.1", "app-name", user_name=None)) + + def test_run_commands(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"/bin/sh": 0}) + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: + print("Exec: test-update", file=f) + print("User: root", file=f) + hook = Click.Hook.open(self.db, "test") + self.assertEqual( + "root", hook.get_run_commands_user(user_name=None)) + hook.run_commands(user_name=None) + self.assertEqual( + [[b"/bin/sh", b"-c", b"test-update"]], self.spawn_calls) + + def test_run_commands_fail(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"/bin/sh": 1}) + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: + print("Exec: test-update", file=f) + print("User: root", file=f) + hook = Click.Hook.open(self.db, "test") + self.assertRaisesHooksError( + Click.HooksError.COMMAND_FAILED, hook.run_commands, + user_name=None) + + def test_install_package(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file( + "Pattern: %s/${id}.test" % self.temp_dir) + os.makedirs( + os.path.join(self.temp_dir, "org.example.package", "1.0")) + hook = Click.Hook.open(self.db, "test") + hook.install_package( + "org.example.package", "1.0", "test-app", "foo/bar", + user_name=None) + symlink_path = os.path.join( + self.temp_dir, "org.example.package_test-app_1.0.test") + target_path = os.path.join( + self.temp_dir, "org.example.package", "1.0", "foo", "bar") + self.assertTrue(os.path.islink(symlink_path)) + self.assertEqual(target_path, os.readlink(symlink_path)) + + def test_install_package_trailing_slash(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file( + "Pattern: %s/${id}/" % self.temp_dir) + os.makedirs( + os.path.join(self.temp_dir, "org.example.package", "1.0")) + hook = Click.Hook.open(self.db, "test") + hook.install_package( + "org.example.package", "1.0", "test-app", "foo", + user_name=None) + symlink_path = os.path.join( + self.temp_dir, "org.example.package_test-app_1.0") + target_path = os.path.join( + self.temp_dir, "org.example.package", "1.0", "foo") + self.assertTrue(os.path.islink(symlink_path)) + self.assertEqual(target_path, os.readlink(symlink_path)) + + def test_install_package_uses_deepest_copy(self): + # If the same version of a package is unpacked in multiple + # databases, then we make sure the link points to the deepest copy, + # even if it already points somewhere else. It is important to be + # consistent about this since system hooks may only have a single + # target for any given application ID. + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file( + "Pattern: %s/${id}.test" % self.temp_dir) + underlay = os.path.join(self.temp_dir, "underlay") + overlay = os.path.join(self.temp_dir, "overlay") + db = Click.DB() + db.add(underlay) + db.add(overlay) + os.makedirs(os.path.join(underlay, "org.example.package", "1.0")) + os.makedirs(os.path.join(overlay, "org.example.package", "1.0")) + symlink_path = os.path.join( + self.temp_dir, "org.example.package_test-app_1.0.test") + underlay_target_path = os.path.join( + underlay, "org.example.package", "1.0", "foo") + overlay_target_path = os.path.join( + overlay, "org.example.package", "1.0", "foo") + os.symlink(overlay_target_path, symlink_path) + hook = Click.Hook.open(db, "test") + hook.install_package( + "org.example.package", "1.0", "test-app", "foo", + user_name=None) + self.assertTrue(os.path.islink(symlink_path)) + self.assertEqual(underlay_target_path, os.readlink(symlink_path)) + + def test_upgrade(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file( + "Pattern: %s/${id}.test" % self.temp_dir) + symlink_path = os.path.join( + self.temp_dir, "org.example.package_test-app_1.0.test") + os.symlink("old-target", symlink_path) + os.makedirs( + os.path.join(self.temp_dir, "org.example.package", "1.0")) + hook = Click.Hook.open(self.db, "test") + hook.install_package( + "org.example.package", "1.0", "test-app", "foo/bar", + user_name=None) + target_path = os.path.join( + self.temp_dir, "org.example.package", "1.0", "foo", "bar") + self.assertTrue(os.path.islink(symlink_path)) + self.assertEqual(target_path, os.readlink(symlink_path)) + + def test_remove_package(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file( + "Pattern: %s/${id}.test" % self.temp_dir) + symlink_path = os.path.join( + self.temp_dir, "org.example.package_test-app_1.0.test") + os.symlink("old-target", symlink_path) + hook = Click.Hook.open(self.db, "test") + hook.remove_package( + "org.example.package", "1.0", "test-app", user_name=None) + self.assertFalse(os.path.exists(symlink_path)) + + def test_install(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir( + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) + self._make_hook_file( + "Pattern: %s/${id}.new" % self.temp_dir, + hookname="new") + self._make_installed_click("test-1", "1.0", json_data={ + "maintainer": + b"Unic\xc3\xb3de ".decode( + "UTF-8"), + "hooks": {"test1-app": {"new": "target-1"}}}) + self._make_installed_click("test-2", "2.0", json_data={ + "maintainer": + b"Unic\xc3\xb3de ".decode( + "UTF-8"), + "hooks": {"test1-app": {"new": "target-2"}}, + }) + hook = Click.Hook.open(self.db, "new") + hook.install(user_name=None) + path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.new") + self.assertTrue(os.path.lexists(path_1)) + self.assertEqual( + os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), + os.readlink(path_1)) + path_2 = os.path.join(self.temp_dir, "test-2_test1-app_2.0.new") + self.assertTrue(os.path.lexists(path_2)) + self.assertEqual( + os.path.join(self.temp_dir, "test-2", "2.0", "target-2"), + os.readlink(path_2)) + + def test_remove(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir( + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) + self._make_hook_file( + "Pattern: %s/${id}.old" % self.temp_dir, + hookname="old") + self._make_installed_click("test-1", "1.0", json_data={ + "hooks": {"test1-app": {"old": "target-1"}}}) + path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.old") + os.symlink( + os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), + path_1) + self._make_installed_click("test-2", "2.0", json_data={ + "hooks": {"test2-app": {"old": "target-2"}}}) + path_2 = os.path.join(self.temp_dir, "test-2_test2-app_2.0.old") + os.symlink( + os.path.join(self.temp_dir, "test-2", "2.0", "target-2"), + path_2) + hook = Click.Hook.open(self.db, "old") + hook.remove(user_name=None) + self.assertFalse(os.path.exists(path_1)) + self.assertFalse(os.path.exists(path_2)) + + def test_sync(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir( + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) + self._make_hook_file( + "Pattern: %s/${id}.test" % self.temp_dir) + self._make_installed_click("test-1", "1.0", json_data={ + "hooks": {"test1-app": {"test": "target-1"}}}) + self._make_installed_click( + "test-2", "1.0", make_current=False, + json_data={"hooks": {"test2-app": {"test": "target-2"}}}) + self._make_installed_click("test-2", "1.1", json_data={ + "hooks": {"test2-app": {"test": "target-2"}}}) + path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.test") + os.symlink( + os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), + path_1) + path_2_1_0 = os.path.join( + self.temp_dir, "test-2_test2-app_1.0.test") + path_2_1_1 = os.path.join( + self.temp_dir, "test-2_test2-app_1.1.test") + path_3 = os.path.join(self.temp_dir, "test-3_test3-app_1.0.test") + os.symlink( + os.path.join(self.temp_dir, "test-3", "1.0", "target-3"), + path_3) + hook = Click.Hook.open(self.db, "test") + hook.sync(user_name=None) + self.assertTrue(os.path.lexists(path_1)) + self.assertEqual( + os.path.join(self.temp_dir, "test-1", "1.0", "target-1"), + os.readlink(path_1)) + self.assertTrue(os.path.lexists(path_2_1_0)) + self.assertEqual( + os.path.join(self.temp_dir, "test-2", "1.0", "target-2"), + os.readlink(path_2_1_0)) + self.assertTrue(os.path.lexists(path_2_1_1)) + self.assertEqual( + os.path.join(self.temp_dir, "test-2", "1.1", "target-2"), + os.readlink(path_2_1_1)) + self.assertFalse(os.path.lexists(path_3)) + + +class TestClickHookUserLevel(TestClickHookBase): + def test_open(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file(dedent("""\ + User-Level: yes + Pattern: ${home}/.local/share/test/${id}.test + # Comment + Exec: test-update + """)) + hook = Click.Hook.open(self.db, "test") + self.assertCountEqual( + ["user-level", "pattern", "exec"], hook.get_fields()) + self.assertEqual( + "${home}/.local/share/test/${id}.test", + hook.get_field("pattern")) + self.assertEqual("test-update", hook.get_field("exec")) + self.assertRaisesHooksError( + Click.HooksError.MISSING_FIELD, hook.get_field, "nonexistent") + self.assertTrue(hook.props.is_user_level) + + def test_hook_name_absent(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file(dedent("""\ + User-Level: yes + Pattern: ${home}/.local/share/test/${id}.test""")) + hook = Click.Hook.open(self.db, "test") + self.assertEqual("test", hook.get_hook_name()) + + def test_hook_name_present(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file(dedent("""\ + User-Level: yes + Pattern: ${home}/.local/share/test/${id}.test + Hook-Name: other""")) + hook = Click.Hook.open(self.db, "test") + self.assertEqual("other", hook.get_hook_name()) + + def test_invalid_app_id(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + self._make_hook_file(dedent("""\ + User-Level: yes + Pattern: ${home}/.local/share/test/${id}.test + # Comment + Exec: test-update""")) + hook = Click.Hook.open(self.db, "test") + self.assertRaisesHooksError( + Click.HooksError.BAD_APP_NAME, hook.get_app_id, + "package", "0.1", "app_name") + self.assertRaisesHooksError( + Click.HooksError.BAD_APP_NAME, hook.get_app_id, + "package", "0.1", "app/name") + self.assertRaisesHooksError( + Click.HooksError.BAD_APP_NAME, hook.get_pattern, + "package", "0.1", "app_name") + self.assertRaisesHooksError( + Click.HooksError.BAD_APP_NAME, hook.get_pattern, + "package", "0.1", "app/name") + + def test_short_id_valid(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "getpwnam") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + preloads["getpwnam"].side_effect = ( + lambda name: self.make_pointer(Passwd(pw_dir=b"/mock"))) + self._make_hook_file(dedent("""\ + User-Level: yes + Pattern: ${home}/.local/share/test/${short-id}.test + """)) + hook = Click.Hook.open(self.db, "test") + self.assertEqual( + "/mock/.local/share/test/package_app-name.test", + hook.get_pattern( + "package", "0.1", "app-name", user_name="mock")) + + def test_run_commands(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"/bin/sh": 0}) + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: + print("User-Level: yes", file=f) + print("Exec: test-update", file=f) + hook = Click.Hook.open(self.db, "test") + self.assertEqual( + self.TEST_USER, + hook.get_run_commands_user(user_name=self.TEST_USER)) + hook.run_commands(user_name=self.TEST_USER) + self.assertEqual( + [[b"/bin/sh", b"-c", b"test-update"]], self.spawn_calls) + + def test_run_commands_fail(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "g_spawn_sync") as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + preloads["g_spawn_sync"].side_effect = partial( + self.g_spawn_sync_side_effect, {b"/bin/sh": 1}) + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: + print("User-Level: yes", file=f) + print("Exec: test-update", file=f) + hook = Click.Hook.open(self.db, "test") + self.assertRaisesHooksError( + Click.HooksError.COMMAND_FAILED, hook.run_commands, + user_name=self.TEST_USER) + + def test_install_package(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + ) as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + preloads["click_get_user_home"].return_value = b"/home/test-user" + os.makedirs(os.path.join( + self.temp_dir, "org.example.package", "1.0")) + user_db = Click.User.for_user(self.db, self.TEST_USER) + user_db.set_version("org.example.package", "1.0") + self._make_hook_file(dedent("""\ + User-Level: yes + Pattern: %s/${id}.test""") % self.temp_dir) + hook = Click.Hook.open(self.db, "test") + hook.install_package( + "org.example.package", "1.0", "test-app", "foo/bar", + user_name=self.TEST_USER) + symlink_path = os.path.join( + self.temp_dir, "org.example.package_test-app_1.0.test") + target_path = os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, + "org.example.package", "foo", "bar") + self.assertTrue(os.path.islink(symlink_path)) + self.assertEqual(target_path, os.readlink(symlink_path)) + + def test_install_package_trailing_slash(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + ) as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + preloads["click_get_user_home"].return_value = b"/home/test-user" + os.makedirs(os.path.join( + self.temp_dir, "org.example.package", "1.0")) + user_db = Click.User.for_user(self.db, self.TEST_USER) + user_db.set_version("org.example.package", "1.0") + self._make_hook_file(dedent("""\ + User-Level: yes + Pattern: %s/${id}/""") % self.temp_dir) + hook = Click.Hook.open(self.db, "test") + hook.install_package( + "org.example.package", "1.0", "test-app", "foo", + user_name=self.TEST_USER) + symlink_path = os.path.join( + self.temp_dir, "org.example.package_test-app_1.0") + target_path = os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, + "org.example.package", "foo") + self.assertTrue(os.path.islink(symlink_path)) + self.assertEqual(target_path, os.readlink(symlink_path)) + + def test_install_package_removes_previous(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + ) as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + preloads["click_get_user_home"].return_value = b"/home/test-user" + os.makedirs(os.path.join( + self.temp_dir, "org.example.package", "1.0")) + os.makedirs(os.path.join( + self.temp_dir, "org.example.package", "1.1")) + user_db = Click.User.for_user(self.db, self.TEST_USER) + user_db.set_version("org.example.package", "1.0") + self._make_hook_file(dedent("""\ + User-Level: yes + Pattern: %s/${id}.test""") % self.temp_dir) + hook = Click.Hook.open(self.db, "test") + hook.install_package( + "org.example.package", "1.0", "test-app", "foo/bar", + user_name=self.TEST_USER) + hook.install_package( + "org.example.package", "1.1", "test-app", "foo/bar", + user_name=self.TEST_USER) + old_symlink_path = os.path.join( + self.temp_dir, "org.example.package_test-app_1.0.test") + symlink_path = os.path.join( + self.temp_dir, "org.example.package_test-app_1.1.test") + self.assertFalse(os.path.islink(old_symlink_path)) + self.assertTrue(os.path.islink(symlink_path)) + target_path = os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, + "org.example.package", "foo", "bar") + self.assertEqual(target_path, os.readlink(symlink_path)) + + def test_upgrade(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + ) as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + preloads["click_get_user_home"].return_value = b"/home/test-user" + symlink_path = os.path.join( + self.temp_dir, "org.example.package_test-app_1.0.test") + os.symlink("old-target", symlink_path) + os.makedirs(os.path.join( + self.temp_dir, "org.example.package", "1.0")) + user_db = Click.User.for_user(self.db, self.TEST_USER) + user_db.set_version("org.example.package", "1.0") + self._make_hook_file(dedent("""\ + User-Level: yes + Pattern: %s/${id}.test""") % self.temp_dir) + hook = Click.Hook.open(self.db, "test") + hook.install_package( + "org.example.package", "1.0", "test-app", "foo/bar", + user_name=self.TEST_USER) + target_path = os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, + "org.example.package", "foo", "bar") + self.assertTrue(os.path.islink(symlink_path)) + self.assertEqual(target_path, os.readlink(symlink_path)) + + def test_remove_package(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + ) as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + preloads["click_get_user_home"].return_value = b"/home/test-user" + self._make_hook_file(dedent("""\ + User-Level: yes + Pattern: %s/${id}.test""") % self.temp_dir) + symlink_path = os.path.join( + self.temp_dir, "org.example.package_test-app_1.0.test") + os.symlink("old-target", symlink_path) + hook = Click.Hook.open(self.db, "test") + hook.remove_package( + "org.example.package", "1.0", "test-app", + user_name=self.TEST_USER) + self.assertFalse(os.path.exists(symlink_path)) + + def test_install(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", "getpwnam" + ) as (enter, preloads): + enter() + # Don't tell click about the hooks directory yet. + self._setup_hooks_dir(preloads) + preloads["click_get_user_home"].return_value = b"/home/test-user" + preloads["getpwnam"].side_effect = ( + lambda name: self.make_pointer(Passwd(pw_uid=1, pw_gid=1))) + with mkfile(os.path.join(self.temp_dir, "hooks", "new.hook")) as f: + print("User-Level: yes", file=f) + print("Pattern: %s/${id}.new" % self.temp_dir, file=f) + self._make_installed_click("test-1", "1.0", json_data={ + "maintainer": + b"Unic\xc3\xb3de ".decode( + "UTF-8"), + "hooks": {"test1-app": {"new": "target-1"}}, + }) + self._make_installed_click("test-2", "2.0", json_data={ + "maintainer": + b"Unic\xc3\xb3de ".decode( + "UTF-8"), + "hooks": {"test1-app": {"new": "target-2"}}, + }) + # Now tell click about the hooks directory and make sure it + # catches up correctly. + self._setup_hooks_dir( + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) + hook = Click.Hook.open(self.db, "new") + hook.install(user_name=None) + path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.new") + self.assertTrue(os.path.lexists(path_1)) + self.assertEqual( + os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, "test-1", + "target-1"), + os.readlink(path_1)) + path_2 = os.path.join(self.temp_dir, "test-2_test1-app_2.0.new") + self.assertTrue(os.path.lexists(path_2)) + self.assertEqual( + os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, "test-2", + "target-2"), + os.readlink(path_2)) + + os.unlink(path_1) + os.unlink(path_2) + hook.install(user_name="another-user") + self.assertFalse(os.path.lexists(path_1)) + self.assertFalse(os.path.lexists(path_2)) + + hook.install(user_name=self.TEST_USER) + self.assertTrue(os.path.lexists(path_1)) + self.assertEqual( + os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, "test-1", + "target-1"), + os.readlink(path_1)) + self.assertTrue(os.path.lexists(path_2)) + self.assertEqual( + os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, "test-2", + "target-2"), + os.readlink(path_2)) + + def test_remove(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + ) as (enter, preloads): + enter() + # Don't tell click about the hooks directory yet. + self._setup_hooks_dir(preloads) + preloads["click_get_user_home"].return_value = b"/home/test-user" + with mkfile(os.path.join(self.temp_dir, "hooks", "old.hook")) as f: + print("User-Level: yes", file=f) + print("Pattern: %s/${id}.old" % self.temp_dir, file=f) + user_db = Click.User.for_user(self.db, self.TEST_USER) + self._make_installed_click("test-1", "1.0", json_data={ + "hooks": {"test1-app": {"old": "target-1"}}}) + path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.old") + os.symlink( + os.path.join(user_db.get_path("test-1"), "target-1"), path_1) + self._make_installed_click("test-2", "2.0", json_data={ + "hooks": {"test2-app": {"old": "target-2"}}}) + path_2 = os.path.join(self.temp_dir, "test-2_test2-app_2.0.old") + os.symlink( + os.path.join(user_db.get_path("test-2"), "target-2"), path_2) + # Now tell click about the hooks directory and make sure it + # catches up correctly. + self._setup_hooks_dir( + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) + hook = Click.Hook.open(self.db, "old") + hook.remove(user_name=None) + self.assertFalse(os.path.exists(path_1)) + self.assertFalse(os.path.exists(path_2)) + + def test_sync(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + ) as (enter, preloads): + enter() + preloads["click_get_user_home"].return_value = b"/home/test-user" + self._setup_hooks_dir(preloads) + with mkfile( + os.path.join(self.temp_dir, "hooks", "test.hook")) as f: + print("User-Level: yes", file=f) + print("Pattern: %s/${id}.test" % self.temp_dir, file=f) + self._make_installed_click("test-1", "1.0", json_data={ + "hooks": {"test1-app": {"test": "target-1"}}}) + self._make_installed_click("test-2", "1.1", json_data={ + "hooks": {"test2-app": {"test": "target-2"}}}) + path_1 = os.path.join(self.temp_dir, "test-1_test1-app_1.0.test") + os.symlink( + os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, "test-1", + "target-1"), + path_1) + path_2 = os.path.join(self.temp_dir, "test-2_test2-app_1.1.test") + path_3 = os.path.join(self.temp_dir, "test-3_test3-app_1.0.test") + os.symlink( + os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, "test-3", + "target-3"), + path_3) + self._setup_hooks_dir( + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) + hook = Click.Hook.open(self.db, "test") + hook.sync(user_name=self.TEST_USER) + self.assertTrue(os.path.lexists(path_1)) + self.assertEqual( + os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, "test-1", + "target-1"), + os.readlink(path_1)) + self.assertTrue(os.path.lexists(path_2)) + self.assertEqual( + os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, "test-2", + "target-2"), + os.readlink(path_2)) + self.assertFalse(os.path.lexists(path_3)) + + def test_sync_without_user_db(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + ) as (enter, preloads): + enter() + preloads["click_get_user_home"].return_value = b"/home/test-user" + self._setup_hooks_dir(preloads) + with mkfile( + os.path.join(self.temp_dir, "hooks", "test.hook")) as f: + print("User-Level: yes", file=f) + print("Pattern: %s/${id}.test" % self.temp_dir, file=f) + self._make_installed_click( + "test-package", "1.0", all_users=True, json_data={ + "hooks": {"test-app": {"test": "target"}}}) + self._setup_hooks_dir( + preloads, hooks_dir=os.path.join(self.temp_dir, "hooks")) + hook = Click.Hook.open(self.db, "test") + hook.sync(user_name=self.TEST_USER) + self.assertFalse(os.path.exists(os.path.join( + self.temp_dir, ".click", "users", self.TEST_USER, + "test-package"))) + + def test_sync_uses_deepest_copy(self): + # If the same version of a package is unpacked in multiple + # databases, then we make sure the user link points to the deepest + # copy, even if it already points somewhere else. It is important + # to be consistent about this since system hooks may only have a + # single target for any given application ID, and user links must + # match system hooks so that (for example) the version of an + # application run by a user has a matching system AppArmor profile. + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + ) as (enter, preloads): + enter() + self._setup_hooks_dir(preloads) + preloads["click_get_user_home"].return_value = b"/home/test-user" + with mkfile(os.path.join(self.temp_dir, "test.hook")) as f: + print("User-Level: yes", file=f) + print("Pattern: %s/${id}.test" % self.temp_dir, file=f) + underlay = os.path.join(self.temp_dir, "underlay") + overlay = os.path.join(self.temp_dir, "overlay") + db = Click.DB() + db.add(underlay) + db.add(overlay) + underlay_unpacked = os.path.join(underlay, "test-package", "1.0") + overlay_unpacked = os.path.join(overlay, "test-package", "1.0") + os.makedirs(underlay_unpacked) + os.makedirs(overlay_unpacked) + manifest = {"hooks": {"test-app": {"test": "foo"}}} + with mkfile(os.path.join( + underlay_unpacked, ".click", "info", + "test-package.manifest")) as f: + json.dump(manifest, f) + with mkfile(os.path.join( + overlay_unpacked, ".click", "info", + "test-package.manifest")) as f: + json.dump(manifest, f) + underlay_user_link = os.path.join( + underlay, ".click", "users", "@all", "test-package") + overlay_user_link = os.path.join( + overlay, ".click", "users", self.TEST_USER, "test-package") + Click.ensuredir(os.path.dirname(underlay_user_link)) + os.symlink(underlay_unpacked, underlay_user_link) + Click.ensuredir(os.path.dirname(overlay_user_link)) + os.symlink(overlay_unpacked, overlay_user_link) + symlink_path = os.path.join( + self.temp_dir, "test-package_test-app_1.0.test") + underlay_target_path = os.path.join(underlay_user_link, "foo") + overlay_target_path = os.path.join(overlay_user_link, "foo") + os.symlink(overlay_target_path, symlink_path) + hook = Click.Hook.open(db, "test") + hook.sync(user_name=self.TEST_USER) + self.assertTrue(os.path.islink(underlay_user_link)) + self.assertEqual( + underlay_unpacked, os.readlink(underlay_user_link)) + self.assertFalse(os.path.islink(overlay_user_link)) + self.assertTrue(os.path.islink(symlink_path)) + self.assertEqual(underlay_target_path, os.readlink(symlink_path)) + + +class TestPackageInstallHooks(TestClickHookBase): + def test_removes_old_hooks(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + hooks_dir = os.path.join(self.temp_dir, "hooks") + self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) + with mkfile(os.path.join(hooks_dir, "unity.hook")) as f: + print("Pattern: %s/unity/${id}.scope" % self.temp_dir, file=f) + print("Single-Version: yes", file=f) + with mkfile(os.path.join(hooks_dir, "yelp-docs.hook")) as f: + print("Pattern: %s/yelp/docs-${id}.txt" % self.temp_dir, + file=f) + print("Single-Version: yes", file=f) + print("Hook-Name: yelp", file=f) + with mkfile(os.path.join(hooks_dir, "yelp-other.hook")) as f: + print("Pattern: %s/yelp/other-${id}.txt" % self.temp_dir, + file=f) + print("Single-Version: yes", file=f) + print("Hook-Name: yelp", file=f) + os.mkdir(os.path.join(self.temp_dir, "unity")) + unity_path = os.path.join( + self.temp_dir, "unity", "test_app_1.0.scope") + os.symlink("dummy", unity_path) + os.mkdir(os.path.join(self.temp_dir, "yelp")) + yelp_docs_path = os.path.join( + self.temp_dir, "yelp", "docs-test_app_1.0.txt") + os.symlink("dummy", yelp_docs_path) + yelp_other_path = os.path.join( + self.temp_dir, "yelp", "other-test_app_1.0.txt") + os.symlink("dummy", yelp_other_path) + self._make_installed_click("test", "1.0", make_current=False, json_data={ + "hooks": {"app": {"yelp": "foo.txt", "unity": "foo.scope"}}}) + self._make_installed_click("test", "1.1", json_data={}) + Click.package_install_hooks( + self.db, "test", "1.0", "1.1", user_name=None) + self.assertFalse(os.path.lexists(unity_path)) + self.assertFalse(os.path.lexists(yelp_docs_path)) + self.assertFalse(os.path.lexists(yelp_other_path)) + + def test_installs_new_hooks(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + hooks_dir = os.path.join(self.temp_dir, "hooks") + self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) + with mkfile(os.path.join(hooks_dir, "a.hook")) as f: + print("Pattern: %s/a/${id}.a" % self.temp_dir, file=f) + with mkfile(os.path.join(hooks_dir, "b-1.hook")) as f: + print("Pattern: %s/b/1-${id}.b" % self.temp_dir, file=f) + print("Hook-Name: b", file=f) + with mkfile(os.path.join(hooks_dir, "b-2.hook")) as f: + print("Pattern: %s/b/2-${id}.b" % self.temp_dir, file=f) + print("Hook-Name: b", file=f) + os.mkdir(os.path.join(self.temp_dir, "a")) + os.mkdir(os.path.join(self.temp_dir, "b")) + self._make_installed_click( + "test", "1.0", make_current=False, json_data={"hooks": {}}) + self._make_installed_click("test", "1.1", json_data={ + "hooks": {"app": {"a": "foo.a", "b": "foo.b"}}}) + Click.package_install_hooks( + self.db, "test", "1.0", "1.1", user_name=None) + self.assertTrue(os.path.lexists( + os.path.join(self.temp_dir, "a", "test_app_1.1.a"))) + self.assertTrue(os.path.lexists( + os.path.join(self.temp_dir, "b", "1-test_app_1.1.b"))) + self.assertTrue(os.path.lexists( + os.path.join(self.temp_dir, "b", "2-test_app_1.1.b"))) + + def test_upgrades_existing_hooks(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + hooks_dir = os.path.join(self.temp_dir, "hooks") + self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) + with mkfile(os.path.join(hooks_dir, "a.hook")) as f: + print("Pattern: %s/a/${id}.a" % self.temp_dir, file=f) + print("Single-Version: yes", file=f) + with mkfile(os.path.join(hooks_dir, "b-1.hook")) as f: + print("Pattern: %s/b/1-${id}.b" % self.temp_dir, file=f) + print("Single-Version: yes", file=f) + print("Hook-Name: b", file=f) + with mkfile(os.path.join(hooks_dir, "b-2.hook")) as f: + print("Pattern: %s/b/2-${id}.b" % self.temp_dir, file=f) + print("Single-Version: yes", file=f) + print("Hook-Name: b", file=f) + with mkfile(os.path.join(hooks_dir, "c.hook")) as f: + print("Pattern: %s/c/${id}.c" % self.temp_dir, file=f) + print("Single-Version: yes", file=f) + os.mkdir(os.path.join(self.temp_dir, "a")) + a_path = os.path.join(self.temp_dir, "a", "test_app_1.0.a") + os.symlink("dummy", a_path) + os.mkdir(os.path.join(self.temp_dir, "b")) + b_irrelevant_path = os.path.join( + self.temp_dir, "b", "1-test_other-app_1.0.b") + os.symlink("dummy", b_irrelevant_path) + b_1_path = os.path.join(self.temp_dir, "b", "1-test_app_1.0.b") + os.symlink("dummy", b_1_path) + b_2_path = os.path.join(self.temp_dir, "b", "2-test_app_1.0.b") + os.symlink("dummy", b_2_path) + os.mkdir(os.path.join(self.temp_dir, "c")) + package_dir = os.path.join(self.temp_dir, "test") + with mkfile(os.path.join( + package_dir, "1.0", ".click", "info", + "test.manifest")) as f: + json.dump({"hooks": {"app": {"a": "foo.a", "b": "foo.b"}}}, f) + with mkfile(os.path.join( + package_dir, "1.1", ".click", "info", + "test.manifest")) as f: + json.dump( + {"hooks": { + "app": {"a": "foo.a", "b": "foo.b", "c": "foo.c"}} + }, f) + Click.package_install_hooks( + self.db, "test", "1.0", "1.1", user_name=None) + self.assertFalse(os.path.lexists(a_path)) + self.assertTrue(os.path.lexists(b_irrelevant_path)) + self.assertFalse(os.path.lexists(b_1_path)) + self.assertFalse(os.path.lexists(b_2_path)) + self.assertTrue(os.path.lexists( + os.path.join(self.temp_dir, "a", "test_app_1.1.a"))) + self.assertTrue(os.path.lexists( + os.path.join(self.temp_dir, "b", "1-test_app_1.1.b"))) + self.assertTrue(os.path.lexists( + os.path.join(self.temp_dir, "b", "2-test_app_1.1.b"))) + self.assertTrue(os.path.lexists( + os.path.join(self.temp_dir, "c", "test_app_1.1.c"))) + + +class TestPackageRemoveHooks(TestClickHookBase): + def test_removes_hooks(self): + with self.run_in_subprocess( + "click_get_hooks_dir") as (enter, preloads): + enter() + hooks_dir = os.path.join(self.temp_dir, "hooks") + self._setup_hooks_dir(preloads, hooks_dir=hooks_dir) + with mkfile(os.path.join(hooks_dir, "unity.hook")) as f: + print("Pattern: %s/unity/${id}.scope" % self.temp_dir, file=f) + with mkfile(os.path.join(hooks_dir, "yelp-docs.hook")) as f: + print("Pattern: %s/yelp/docs-${id}.txt" % self.temp_dir, + file=f) + print("Hook-Name: yelp", file=f) + with mkfile(os.path.join(hooks_dir, "yelp-other.hook")) as f: + print("Pattern: %s/yelp/other-${id}.txt" % self.temp_dir, + file=f) + print("Hook-Name: yelp", file=f) + os.mkdir(os.path.join(self.temp_dir, "unity")) + unity_path = os.path.join( + self.temp_dir, "unity", "test_app_1.0.scope") + os.symlink("dummy", unity_path) + os.mkdir(os.path.join(self.temp_dir, "yelp")) + yelp_docs_path = os.path.join( + self.temp_dir, "yelp", "docs-test_app_1.0.txt") + os.symlink("dummy", yelp_docs_path) + yelp_other_path = os.path.join( + self.temp_dir, "yelp", "other-test_app_1.0.txt") + os.symlink("dummy", yelp_other_path) + package_dir = os.path.join(self.temp_dir, "test") + with mkfile(os.path.join( + package_dir, "1.0", ".click", "info", + "test.manifest")) as f: + json.dump( + {"hooks": { + "app": {"yelp": "foo.txt", "unity": "foo.scope"}} + }, f) + Click.package_remove_hooks(self.db, "test", "1.0", user_name=None) + self.assertFalse(os.path.lexists(unity_path)) + self.assertFalse(os.path.lexists(yelp_docs_path)) + self.assertFalse(os.path.lexists(yelp_other_path)) + + +class TestPackageHooksValidateFramework(TestClickHookBase): + + def _setup_test_env(self, preloads): + preloads["click_get_user_home"].return_value = b"/home/test-user" + self._setup_hooks_dir( + preloads, os.path.join(self.temp_dir, "hooks")) + self._make_hook_file(dedent("""\ + User-Level: yes + Pattern: %s/${id}.test + """) % self.temp_dir) + self.hook_symlink_path = os.path.join( + self.temp_dir, "test-1_test1-app_1.0.test") + + def test_links_are_kept_on_validate_framework(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + "click_get_frameworks_dir", + ) as (enter, preloads): + enter() + self._setup_frameworks( + preloads, frameworks=["ubuntu-sdk-13.10"]) + self._setup_test_env(preloads) + self._make_installed_click(json_data={ + "framework": "ubuntu-sdk-13.10", + "hooks": { + "test1-app": {"test": "target-1"} + }, + }) + self.assertTrue(os.path.lexists(self.hook_symlink_path)) + # run the hooks + Click.run_user_hooks(self.db, user_name=self.TEST_USER) + self.assertTrue(os.path.lexists(self.hook_symlink_path)) + + def test_links_are_kept_multiple_frameworks(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + "click_get_frameworks_dir", + ) as (enter, preloads): + enter() + self._setup_frameworks( + preloads, frameworks=["ubuntu-sdk-14.04", "ubuntu-sdk-13.10"]) + self._setup_test_env(preloads) + self._make_installed_click(json_data={ + "framework": "ubuntu-sdk-13.10", + "hooks": { + "test1-app": {"test": "target-1"} + }, + }) + self.assertTrue(os.path.lexists(self.hook_symlink_path)) + # run the hooks + Click.run_user_hooks(self.db, user_name=self.TEST_USER) + self.assertTrue(os.path.lexists(self.hook_symlink_path)) + + def test_links_are_removed_on_missing_framework(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + "click_get_frameworks_dir", + ) as (enter, preloads): + enter() + self._setup_frameworks(preloads, frameworks=["missing"]) + self._setup_test_env(preloads) + self._make_installed_click(json_data={ + "framework": "ubuntu-sdk-13.10", + "hooks": { + "test1-app": {"test": "target-1"} + }, + }) + self.assertTrue(os.path.lexists(self.hook_symlink_path)) + # run the hooks + Click.run_user_hooks(self.db, user_name=self.TEST_USER) + self.assertFalse(os.path.lexists(self.hook_symlink_path)) + + def test_links_are_removed_on_missing_multiple_framework(self): + with self.run_in_subprocess( + "click_get_hooks_dir", "click_get_user_home", + "click_get_frameworks_dir", + ) as (enter, preloads): + enter() + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + self._setup_test_env(preloads) + self._make_installed_click(json_data={ + "framework": "ubuntu-sdk-13.10, ubuntu-sdk-13.10-html", + "hooks": { + "test1-app": {"test": "target-1"} + }, + }) + self.assertTrue(os.path.lexists(self.hook_symlink_path)) + # run the hooks + Click.run_user_hooks(self.db, user_name=self.TEST_USER) + self.assertFalse(os.path.lexists(self.hook_symlink_path)) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_install.py click-0.4.46+16.10.20170607.3/click_package/tests/test_install.py --- click-0.4.45.1+16.10.20160916/click_package/tests/test_install.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_install.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,771 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Unit tests for click_package.install.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'TestClickInstaller', + ] + + +from contextlib import ( + closing, + contextmanager, + ) +import hashlib +import json +import os +import shutil +import stat +import subprocess +import tarfile + +from unittest import skipUnless + +from debian.deb822 import Deb822 +from gi.repository import Click + +from click_package.arfile import ArFile +from click_package.build import ClickBuilder +from click_package.install import ( + ClickInstaller, + ClickInstallerAuditError, + ClickInstallerPermissionDenied, +) +from click_package.preinst import static_preinst +from click_package.tests.helpers import ( + disable_logging, + mkfile, + mock, + TestCase, + touch, +) +from click_package.versions import spec_version + + +@contextmanager +def mock_quiet_subprocess_call(): + original_call = subprocess.call + + def side_effect(*args, **kwargs): + if "TEST_VERBOSE" in os.environ: + return original_call(*args, **kwargs) + else: + with open("/dev/null", "w") as devnull: + return original_call( + *args, stdout=devnull, stderr=devnull, **kwargs) + + with mock.patch("subprocess.call") as mock_call: + mock_call.side_effect = side_effect + yield mock_call + + +class TestClickInstaller(TestCase): + def setUp(self): + super(TestClickInstaller, self).setUp() + self.use_temp_dir() + self.db = Click.DB() + self.db.add(self.temp_dir) + # mock signature checks during the tests + self.debsig_patcher = mock.patch("click_package.install.DebsigVerify") + self.debsig_patcher.start() + + def tearDown(self): + self.debsig_patcher.stop() + + def make_fake_package(self, control_fields=None, manifest=None, + control_scripts=None, data_files=None): + """Build a fake package with given contents.""" + control_fields = {} if control_fields is None else control_fields + control_scripts = {} if control_scripts is None else control_scripts + data_files = {} if data_files is None else data_files + + data_dir = os.path.join(self.temp_dir, "fake-package") + control_dir = os.path.join(self.temp_dir, "DEBIAN") + with mkfile(os.path.join(control_dir, "control")) as control: + for key, value in control_fields.items(): + print('%s: %s' % (key.title(), value), file=control) + print(file=control) + if manifest is not None: + with mkfile(os.path.join(control_dir, "manifest")) as f: + json.dump(manifest, f) + print(file=f) + for name, contents in control_scripts.items(): + with mkfile(os.path.join(control_dir, name)) as script: + script.write(contents) + Click.ensuredir(data_dir) + for name, path in data_files.items(): + Click.ensuredir(os.path.dirname(os.path.join(data_dir, name))) + if path is None: + touch(os.path.join(data_dir, name)) + elif os.path.isdir(path): + shutil.copytree(path, os.path.join(data_dir, name)) + else: + shutil.copy2(path, os.path.join(data_dir, name)) + package_path = '%s.click' % data_dir + ClickBuilder()._pack( + self.temp_dir, control_dir, data_dir, package_path) + return package_path + + def test_audit_no_click_version(self): + path = self.make_fake_package() + self.assertRaisesRegex( + ClickInstallerAuditError, "No Click-Version field", + ClickInstaller(self.db).audit, path) + + def test_audit_bad_click_version(self): + path = self.make_fake_package(control_fields={"Click-Version": "|"}) + self.assertRaises(ValueError, ClickInstaller(self.db).audit, path) + + def test_audit_new_click_version(self): + path = self.make_fake_package(control_fields={"Click-Version": "999"}) + self.assertRaisesRegex( + ClickInstallerAuditError, + "Click-Version: 999 newer than maximum supported version .*", + ClickInstaller(self.db).audit, path) + + def test_audit_forbids_depends(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = self.make_fake_package( + control_fields={ + "Click-Version": "0.2", + "Depends": "libc6", + }) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + self.assertRaisesRegex( + ClickInstallerAuditError, + "Depends field is forbidden in Click packages", + ClickInstaller(self.db).audit, path) + + def test_audit_forbids_maintscript(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + control_scripts={ + "preinst": "#! /bin/sh\n", + "postinst": "#! /bin/sh\n", + }) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + self.assertRaisesRegex( + ClickInstallerAuditError, + r"Maintainer scripts are forbidden in Click packages " + r"\(found: postinst preinst\)", + ClickInstaller(self.db).audit, path) + + def test_audit_requires_manifest(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + control_scripts={"preinst": static_preinst}) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + self.assertRaisesRegex( + ClickInstallerAuditError, "Package has no manifest", + ClickInstaller(self.db).audit, path) + + def test_audit_invalid_manifest_json(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + control_scripts={"manifest": "{", "preinst": static_preinst}) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + self.assertRaises(ValueError, ClickInstaller(self.db).audit, path) + + def test_audit_no_name(self): + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + manifest={}) + self.assertRaisesRegex( + ClickInstallerAuditError, 'No "name" entry in manifest', + ClickInstaller(self.db).audit, path) + + def test_audit_name_bad_character(self): + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + manifest={"name": "../evil"}) + self.assertRaisesRegex( + ClickInstallerAuditError, + 'Invalid character "/" in "name" entry: ../evil', + ClickInstaller(self.db).audit, path) + + def test_audit_no_version(self): + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + manifest={"name": "test-package"}) + self.assertRaisesRegex( + ClickInstallerAuditError, 'No "version" entry in manifest', + ClickInstaller(self.db).audit, path) + + def test_audit_no_framework(self): + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + manifest={"name": "test-package", "version": "1.0"}, + control_scripts={"preinst": static_preinst}) + self.assertRaisesRegex( + ClickInstallerAuditError, 'No "framework" entry in manifest', + ClickInstaller(self.db).audit, path) + + def test_audit_missing_framework(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + manifest={ + "name": "test-package", + "version": "1.0", + "framework": "missing", + }, + control_scripts={"preinst": static_preinst}) + self._setup_frameworks(preloads, frameworks=["present"]) + self.assertRaisesRegex( + ClickInstallerAuditError, + 'Framework "missing" not present on system.*', + ClickInstaller(self.db).audit, path) + + # FIXME: we really want a unit test with a valid signature too + def test_audit_no_signature(self): + if not Click.find_on_path("debsig-verify"): + self.skipTest("this test needs debsig-verify") + path = self.make_fake_package( + control_fields={"Click-Version": "0.4"}, + manifest={ + "name": "test-package", + "version": "1.0", + "framework": "", + }) + self.debsig_patcher.stop() + self.assertRaisesRegex( + ClickInstallerAuditError, "Signature verification error", + ClickInstaller(self.db).audit, path) + self.debsig_patcher.start() + + @disable_logging + def test_audit_missing_framework_force(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + manifest={ + "name": "test-package", + "version": "1.0", + "framework": "missing", + }) + self._setup_frameworks(preloads, frameworks=["present"]) + ClickInstaller(self.db, True).audit(path) + + def test_audit_passes_correct_package(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + manifest={ + "name": "test-package", + "version": "1.0", + "framework": "ubuntu-sdk-13.10", + }, + control_scripts={"preinst": static_preinst}) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + installer = ClickInstaller(self.db) + self.assertEqual(("test-package", "1.0"), installer.audit(path)) + + def test_audit_multiple_frameworks(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = self.make_fake_package( + control_fields={"Click-Version": "0.4"}, + manifest={ + "name": "test-package", + "version": "1.0", + "framework": + "ubuntu-sdk-14.04-basic, ubuntu-sdk-14.04-webapps", + }, + control_scripts={"preinst": static_preinst}) + installer = ClickInstaller(self.db) + self._setup_frameworks(preloads, frameworks=["dummy"]) + self.assertRaisesRegex( + ClickInstallerAuditError, + 'Frameworks "ubuntu-sdk-14.04-basic", ' + '"ubuntu-sdk-14.04-webapps" not present on system.*', + installer.audit, path) + self._setup_frameworks( + preloads, frameworks=["dummy", "ubuntu-sdk-14.04-basic"]) + self.assertRaisesRegex( + ClickInstallerAuditError, + 'Framework "ubuntu-sdk-14.04-webapps" not present on ' + 'system.*', + installer.audit, path) + self._setup_frameworks( + preloads, frameworks=[ + "dummy", "ubuntu-sdk-14.04-basic", + "ubuntu-sdk-14.04-webapps", + ]) + self.assertEqual(("test-package", "1.0"), installer.audit(path)) + + def test_audit_missing_dot_slash(self): + # Manually construct a package with data paths that do not start + # with "./", which could be used to bypass path filtering. + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + manifest={ + "name": "test-package", + "version": "1.0", + "framework": "ubuntu-sdk-13.10", + }, + control_scripts={"preinst": static_preinst}, + data_files={".click/tmp.ci/manifest": None}) + # Repack without the leading "./". + data_dir = os.path.join(self.temp_dir, "fake-package") + data_tar_path = os.path.join(self.temp_dir, "data.tar.gz") + control_tar_path = os.path.join(self.temp_dir, "control.tar.gz") + package_path = '%s.click' % data_dir + with closing(tarfile.TarFile.open( + name=data_tar_path, mode="w:gz", format=tarfile.GNU_FORMAT + )) as data_tar: + data_tar.add( + os.path.join(data_dir, ".click"), arcname=".click") + with ArFile(name=package_path, mode="w") as package: + package.add_magic() + package.add_data("debian-binary", b"2.0\n") + package.add_data( + "_click-binary", ("%s\n" % spec_version).encode("UTF-8")) + package.add_file("control.tar.gz", control_tar_path) + package.add_file("data.tar.gz", data_tar_path) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + with mock_quiet_subprocess_call(): + installer = ClickInstaller(self.db) + self.assertRaisesRegex( + ClickInstallerAuditError, + 'File name ".click" in package does not start with "./"', + installer.audit, path) + + def test_audit_broken_md5sums(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + manifest={ + "name": "test-package", + "version": "1.0", + "framework": "ubuntu-sdk-13.10", + }, + control_scripts={ + "preinst": static_preinst, + "md5sums": "%s foo" % ("0" * 32), + }, + data_files={"foo": None}) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + with mock_quiet_subprocess_call(): + installer = ClickInstaller(self.db) + self.assertRaises( + subprocess.CalledProcessError, installer.audit, + path, slow=True) + + def test_audit_matching_md5sums(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + data_path = os.path.join(self.temp_dir, "foo") + with mkfile(data_path) as data: + print("test", file=data) + with open(data_path, "rb") as data: + data_md5sum = hashlib.md5(data.read()).hexdigest() + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + manifest={ + "name": "test-package", + "version": "1.0", + "framework": "ubuntu-sdk-13.10", + }, + control_scripts={ + "preinst": static_preinst, + "md5sums": "%s foo" % data_md5sum, + }, + data_files={"foo": data_path}) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + with mock_quiet_subprocess_call(): + installer = ClickInstaller(self.db) + self.assertEqual( + ("test-package", "1.0"), installer.audit(path, slow=True)) + + def test_no_write_permission(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = self.make_fake_package( + control_fields={"Click-Version": "0.2"}, + manifest={ + "name": "test-package", + "version": "1.0", + "framework": "ubuntu-sdk-13.10", + }, + control_scripts={"preinst": static_preinst}) + write_mask = ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + installer = ClickInstaller(self.db) + temp_dir_mode = os.stat(self.temp_dir).st_mode + try: + os.chmod(self.temp_dir, temp_dir_mode & write_mask) + self.assertRaises( + ClickInstallerPermissionDenied, installer.install, path) + finally: + os.chmod(self.temp_dir, temp_dir_mode) + + @skipUnless( + os.path.exists(ClickInstaller(None)._preload_path()), + "preload bits not built; installing packages will fail") + @mock.patch("gi.repository.Click.package_install_hooks") + def test_install(self, mock_package_install_hooks): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + path = self.make_fake_package( + control_fields={ + "Package": "test-package", + "Version": "1.0", + "Architecture": "all", + "Maintainer": "Foo Bar ", + "Description": "test", + "Click-Version": "0.2", + }, + manifest={ + "name": "test-package", + "version": "1.0", + "framework": "ubuntu-sdk-13.10", + }, + control_scripts={"preinst": static_preinst}, + data_files={"foo": None}) + root = os.path.join(self.temp_dir, "root") + db = Click.DB() + db.add(root) + installer = ClickInstaller(db) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + with mock_quiet_subprocess_call(): + installer.install(path) + self.assertCountEqual([".click", "test-package"], os.listdir(root)) + package_dir = os.path.join(root, "test-package") + self.assertCountEqual(["1.0", "current"], os.listdir(package_dir)) + inst_dir = os.path.join(package_dir, "current") + self.assertTrue(os.path.islink(inst_dir)) + self.assertEqual("1.0", os.readlink(inst_dir)) + self.assertCountEqual([".click", "foo"], os.listdir(inst_dir)) + status_path = os.path.join(inst_dir, ".click", "status") + with open(status_path) as status_file: + # .readlines() avoids the need for a python-apt backport to + # Ubuntu 12.04 LTS. + status = list(Deb822.iter_paragraphs(status_file.readlines())) + self.assertEqual(1, len(status)) + self.assertEqual({ + "Package": "test-package", + "Status": "install ok installed", + "Version": "1.0", + "Architecture": "all", + "Maintainer": "Foo Bar ", + "Description": "test", + "Click-Version": "0.2", + }, status[0]) + mock_package_install_hooks.assert_called_once_with( + db, "test-package", None, "1.0", user_name=None) + + @skipUnless( + os.path.exists(ClickInstaller(None)._preload_path()), + "preload bits not built; installing packages will fail") + def test_sandbox(self): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + original_call = subprocess.check_output + + def call_side_effect(*args, **kwargs): + return original_call( + ["touch", os.path.join(self.temp_dir, "sentinel")], + **kwargs) + + path = self.make_fake_package( + control_fields={ + "Package": "test-package", + "Version": "1.0", + "Architecture": "all", + "Maintainer": "Foo Bar ", + "Description": "test", + "Click-Version": "0.2", + }, + manifest={ + "name": "test-package", + "version": "1.0", + "framework": "ubuntu-sdk-13.10", + }, + control_scripts={"preinst": static_preinst}, + data_files={"foo": None}) + root = os.path.join(self.temp_dir, "root") + db = Click.DB() + db.add(root) + installer = ClickInstaller(db) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + with mock.patch("subprocess.check_output") as mock_call: + mock_call.side_effect = call_side_effect + self.assertRaises( + subprocess.CalledProcessError, installer.install, path) + self.assertFalse( + os.path.exists(os.path.join(self.temp_dir, "sentinel"))) + + @skipUnless( + os.path.exists(ClickInstaller(None)._preload_path()), + "preload bits not built; installing packages will fail") + @mock.patch("gi.repository.Click.package_install_hooks") + def test_upgrade(self, mock_package_install_hooks): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + os.environ["TEST_QUIET"] = "1" + path = self.make_fake_package( + control_fields={ + "Package": "test-package", + "Version": "1.1", + "Architecture": "all", + "Maintainer": "Foo Bar ", + "Description": "test", + "Click-Version": "0.2", + }, + manifest={ + "name": "test-package", + "version": "1.1", + "framework": "ubuntu-sdk-13.10", + }, + control_scripts={"preinst": static_preinst}, + data_files={"foo": None}) + root = os.path.join(self.temp_dir, "root") + package_dir = os.path.join(root, "test-package") + inst_dir = os.path.join(package_dir, "current") + os.makedirs(os.path.join(package_dir, "1.0")) + os.symlink("1.0", inst_dir) + db = Click.DB() + db.add(root) + installer = ClickInstaller(db) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + with mock_quiet_subprocess_call(): + installer.install(path) + self.assertCountEqual([".click", "test-package"], os.listdir(root)) + self.assertCountEqual(["1.1", "current"], os.listdir(package_dir)) + self.assertTrue(os.path.islink(inst_dir)) + self.assertEqual("1.1", os.readlink(inst_dir)) + self.assertCountEqual([".click", "foo"], os.listdir(inst_dir)) + status_path = os.path.join(inst_dir, ".click", "status") + with open(status_path) as status_file: + # .readlines() avoids the need for a python-apt backport to + # Ubuntu 12.04 LTS. + status = list(Deb822.iter_paragraphs(status_file.readlines())) + self.assertEqual(1, len(status)) + self.assertEqual({ + "Package": "test-package", + "Status": "install ok installed", + "Version": "1.1", + "Architecture": "all", + "Maintainer": "Foo Bar ", + "Description": "test", + "Click-Version": "0.2", + }, status[0]) + mock_package_install_hooks.assert_called_once_with( + db, "test-package", "1.0", "1.1", user_name=None) + + def _get_mode(self, path): + return stat.S_IMODE(os.stat(path).st_mode) + + @skipUnless( + os.path.exists(ClickInstaller(None)._preload_path()), + "preload bits not built; installing packages will fail") + @mock.patch("gi.repository.Click.package_install_hooks") + def test_world_readable(self, mock_package_install_hooks): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + owner_only_file = os.path.join(self.temp_dir, "owner-only-file") + touch(owner_only_file) + os.chmod(owner_only_file, stat.S_IRUSR | stat.S_IWUSR) + owner_only_dir = os.path.join(self.temp_dir, "owner-only-dir") + os.mkdir(owner_only_dir, stat.S_IRWXU) + path = self.make_fake_package( + control_fields={ + "Package": "test-package", + "Version": "1.1", + "Architecture": "all", + "Maintainer": "Foo Bar ", + "Description": "test", + "Click-Version": "0.2", + }, + manifest={ + "name": "test-package", + "version": "1.1", + "framework": "ubuntu-sdk-13.10", + }, + control_scripts={"preinst": static_preinst}, + data_files={ + "world-readable-file": owner_only_file, + "world-readable-dir": owner_only_dir, + }) + root = os.path.join(self.temp_dir, "root") + db = Click.DB() + db.add(root) + installer = ClickInstaller(db) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + with mock_quiet_subprocess_call(): + installer.install(path) + inst_dir = os.path.join(root, "test-package", "current") + self.assertEqual( + stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH, + self._get_mode(os.path.join(inst_dir, "world-readable-file"))) + self.assertEqual( + stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH, + self._get_mode(os.path.join(inst_dir, "world-readable-dir"))) + + @skipUnless( + os.path.exists(ClickInstaller(None)._preload_path()), + "preload bits not built; installing packages will fail") + @mock.patch("gi.repository.Click.package_install_hooks") + @mock.patch("click_package.install.ClickInstaller._dpkg_architecture") + def test_single_architecture(self, mock_dpkg_architecture, + mock_package_install_hooks): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + mock_dpkg_architecture.return_value = "armhf" + path = self.make_fake_package( + control_fields={ + "Package": "test-package", + "Version": "1.1", + "Architecture": "armhf", + "Maintainer": "Foo Bar ", + "Description": "test", + "Click-Version": "0.2", + }, + manifest={ + "name": "test-package", + "version": "1.1", + "framework": "ubuntu-sdk-13.10", + "architecture": "armhf", + }, + control_scripts={"preinst": static_preinst}) + root = os.path.join(self.temp_dir, "root") + db = Click.DB() + db.add(root) + installer = ClickInstaller(db) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + with mock_quiet_subprocess_call(): + installer.install(path) + self.assertTrue( + os.path.exists(os.path.join(root, "test-package", "current"))) + + @skipUnless( + os.path.exists(ClickInstaller(None)._preload_path()), + "preload bits not built; installing packages will fail") + @mock.patch("gi.repository.Click.package_install_hooks") + @mock.patch("click_package.install.ClickInstaller._dpkg_architecture") + def test_multiple_architectures(self, mock_dpkg_architecture, + mock_package_install_hooks): + with self.run_in_subprocess( + "click_get_frameworks_dir") as (enter, preloads): + enter() + mock_dpkg_architecture.return_value = "armhf" + path = self.make_fake_package( + control_fields={ + "Package": "test-package", + "Version": "1.1", + "Architecture": "multi", + "Maintainer": "Foo Bar ", + "Description": "test", + "Click-Version": "0.2", + }, + manifest={ + "name": "test-package", + "version": "1.1", + "framework": "ubuntu-sdk-13.10", + "architecture": ["armhf", "i386"], + }, + control_scripts={"preinst": static_preinst}) + root = os.path.join(self.temp_dir, "root") + db = Click.DB() + db.add(root) + installer = ClickInstaller(db) + self._setup_frameworks(preloads, frameworks=["ubuntu-sdk-13.10"]) + with mock_quiet_subprocess_call(): + installer.install(path) + self.assertTrue( + os.path.exists(os.path.join(root, "test-package", "current"))) + + @disable_logging + def test_reinstall_preinstalled(self): + # Attempting to reinstall a preinstalled version shouldn't actually + # reinstall it in an overlay database (which would cause + # irreconcilable confusion about the correct target for system hook + # symlinks), but should instead simply update the user registration. + path = self.make_fake_package( + control_fields={ + "Package": "test-package", + "Version": "1.1", + "Architecture": "all", + "Maintainer": "Foo Bar ", + "Description": "test", + "Click-Version": "0.4", + }, + manifest={ + "name": "test-package", + "version": "1.1", + "framework": "ubuntu-sdk-13.10", + }, + control_scripts={"preinst": static_preinst}) + underlay = os.path.join(self.temp_dir, "underlay") + overlay = os.path.join(self.temp_dir, "overlay") + db = Click.DB() + db.add(underlay) + installer = ClickInstaller(db, True) + with mock_quiet_subprocess_call(): + installer.install(path, all_users=True) + underlay_unpacked = os.path.join(underlay, "test-package", "1.1") + self.assertTrue(os.path.exists(underlay_unpacked)) + all_link = os.path.join( + underlay, ".click", "users", "@all", "test-package") + self.assertTrue(os.path.islink(all_link)) + self.assertEqual(underlay_unpacked, os.readlink(all_link)) + db.add(overlay) + registry = Click.User.for_user(db, "test-user") + registry.remove("test-package") + user_link = os.path.join( + overlay, ".click", "users", "test-user", "test-package") + self.assertTrue(os.path.islink(user_link)) + self.assertEqual("@hidden", os.readlink(user_link)) + installer = ClickInstaller(db, True) + with mock_quiet_subprocess_call(): + installer.install(path, user="test-user") + overlay_unpacked = os.path.join(overlay, "test-package", "1.1") + self.assertFalse(os.path.exists(overlay_unpacked)) + self.assertEqual("1.1", registry.get_version("test-package")) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_osextras.py click-0.4.46+16.10.20170607.3/click_package/tests/test_osextras.py --- click-0.4.45.1+16.10.20160916/click_package/tests/test_osextras.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_osextras.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,193 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Unit tests for click_package.osextras.""" + +from __future__ import print_function +__all__ = [ + 'TestOSExtrasNative', + 'TestOSExtrasPython', + ] + + +import os + +from gi.repository import Click, GLib + +from click_package import osextras +from click_package.tests.helpers import TestCase, mock, touch + + +class TestOSExtrasBaseMixin: + def test_ensuredir_previously_missing(self): + new_dir = os.path.join(self.temp_dir, "dir") + self.mod.ensuredir(new_dir) + self.assertTrue(os.path.isdir(new_dir)) + + def test_ensuredir_previously_present(self): + new_dir = os.path.join(self.temp_dir, "dir") + os.mkdir(new_dir) + self.mod.ensuredir(new_dir) + self.assertTrue(os.path.isdir(new_dir)) + + def test_find_on_path_missing_environment(self): + os.environ.pop("PATH", None) + self.assertFalse(self.mod.find_on_path("ls")) + + def test_find_on_path_present_executable(self): + bin_dir = os.path.join(self.temp_dir, "bin") + program = os.path.join(bin_dir, "program") + touch(program) + os.chmod(program, 0o755) + os.environ["PATH"] = bin_dir + self.assertTrue(self.mod.find_on_path("program")) + + def test_find_on_path_present_not_executable(self): + bin_dir = os.path.join(self.temp_dir, "bin") + touch(os.path.join(bin_dir, "program")) + os.environ["PATH"] = bin_dir + self.assertFalse(self.mod.find_on_path("program")) + + def test_find_on_path_requires_regular_file(self): + bin_dir = os.path.join(self.temp_dir, "bin") + self.mod.ensuredir(os.path.join(bin_dir, "subdir")) + os.environ["PATH"] = bin_dir + self.assertFalse(self.mod.find_on_path("subdir")) + + def test_unlink_file_present(self): + path = os.path.join(self.temp_dir, "file") + touch(path) + self.mod.unlink_force(path) + self.assertFalse(os.path.exists(path)) + + def test_unlink_file_missing(self): + path = os.path.join(self.temp_dir, "file") + self.mod.unlink_force(path) + self.assertFalse(os.path.exists(path)) + + def test_symlink_file_present(self): + path = os.path.join(self.temp_dir, "link") + touch(path) + self.mod.symlink_force("source", path) + self.assertTrue(os.path.islink(path)) + self.assertEqual("source", os.readlink(path)) + + def test_symlink_link_present(self): + path = os.path.join(self.temp_dir, "link") + os.symlink("old", path) + self.mod.symlink_force("source", path) + self.assertTrue(os.path.islink(path)) + self.assertEqual("source", os.readlink(path)) + + def test_symlink_missing(self): + path = os.path.join(self.temp_dir, "link") + self.mod.symlink_force("source", path) + self.assertTrue(os.path.islink(path)) + self.assertEqual("source", os.readlink(path)) + + def test_umask(self): + old_mask = os.umask(0o040) + try: + self.assertEqual(0o040, self.mod.get_umask()) + os.umask(0o002) + self.assertEqual(0o002, self.mod.get_umask()) + finally: + os.umask(old_mask) + + +class TestOSExtrasNative(TestCase, TestOSExtrasBaseMixin): + def setUp(self): + super(TestOSExtrasNative, self).setUp() + self.use_temp_dir() + self.mod = Click + + def test_ensuredir_error(self): + path = os.path.join(self.temp_dir, "file") + touch(path) + self.assertRaisesFileError(mock.ANY, self.mod.ensuredir, path) + + def test_dir_read_name_directory_present(self): + new_dir = os.path.join(self.temp_dir, "dir") + touch(os.path.join(new_dir, "file")) + d = Click.Dir.open(new_dir, 0) + self.assertEqual("file", d.read_name()) + self.assertIsNone(d.read_name()) + + def test_dir_read_name_directory_missing(self): + new_dir = os.path.join(self.temp_dir, "dir") + d = Click.Dir.open(new_dir, 0) + self.assertIsNone(d.read_name()) + + def test_dir_open_error(self): + not_dir = os.path.join(self.temp_dir, "file") + touch(not_dir) + self.assertRaisesFileError( + GLib.FileError.NOTDIR, Click.Dir.open, not_dir, 0) + + def test_unlink_error(self): + path = os.path.join(self.temp_dir, "dir") + os.mkdir(path) + self.assertRaisesFileError(mock.ANY, self.mod.unlink_force, path) + + def test_symlink_unlink_error(self): + path = os.path.join(self.temp_dir, "dir") + os.mkdir(path) + self.assertRaisesFileError( + mock.ANY, self.mod.symlink_force, "source", path) + + def test_symlink_error(self): + path = os.path.join(self.temp_dir, "dir", "file") + self.assertRaisesFileError( + mock.ANY, self.mod.symlink_force, "source", path) + + +class TestOSExtrasPython(TestCase, TestOSExtrasBaseMixin): + def setUp(self): + super(TestOSExtrasPython, self).setUp() + self.use_temp_dir() + self.mod = osextras + + def test_ensuredir_oserror(self): + path = os.path.join(self.temp_dir, "file") + touch(path) + self.assertRaises(OSError, self.mod.ensuredir, path) + + def test_listdir_directory_present(self): + new_dir = os.path.join(self.temp_dir, "dir") + touch(os.path.join(new_dir, "file")) + self.assertEqual(["file"], osextras.listdir_force(new_dir)) + + def test_listdir_directory_missing(self): + new_dir = os.path.join(self.temp_dir, "dir") + self.assertEqual([], osextras.listdir_force(new_dir)) + + def test_listdir_oserror(self): + not_dir = os.path.join(self.temp_dir, "file") + touch(not_dir) + self.assertRaises(OSError, osextras.listdir_force, not_dir) + + def test_unlink_oserror(self): + path = os.path.join(self.temp_dir, "dir") + os.mkdir(path) + self.assertRaises(OSError, self.mod.unlink_force, path) + + def test_symlink_unlink_oserror(self): + path = os.path.join(self.temp_dir, "dir") + os.mkdir(path) + self.assertRaises(OSError, self.mod.symlink_force, "source", path) + + def test_symlink_oserror(self): + path = os.path.join(self.temp_dir, "dir", "file") + self.assertRaises(OSError, self.mod.symlink_force, "source", path) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_paths.py.in click-0.4.46+16.10.20170607.3/click_package/tests/test_paths.py.in --- click-0.4.45.1+16.10.20160916/click_package/tests/test_paths.py.in 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_paths.py.in 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,42 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Unit tests for click.paths. + +This is mostly just to reduce noise in the coverage report. +""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'TestClickPaths', + ] + + +from gi.repository import Click + +from click_package.tests.helpers import TestCase + + +class TestClickPaths(TestCase): + def test_get_hooks_dir(self): + self.assertEqual("@pkgdatadir@/hooks", Click.get_hooks_dir()) + + def test_get_db_dir(self): + self.assertEqual("@sysconfdir@/click/databases", Click.get_db_dir()) + + def test_get_frameworks_dir(self): + self.assertEqual("@pkgdatadir@/frameworks", Click.get_frameworks_dir()) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_query.py click-0.4.46+16.10.20170607.3/click_package/tests/test_query.py --- click-0.4.45.1+16.10.20160916/click_package/tests/test_query.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_query.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,52 @@ +# Copyright (C) 2014 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Unit tests for click_package.query.""" + +from __future__ import print_function +__all__ = [ + 'TestQuery', + ] + + +import os + +from gi.repository import Click + +from click_package.tests.helpers import TestCase, touch + + +class TestQuery(TestCase): + def setUp(self): + super(TestQuery, self).setUp() + self.use_temp_dir() + + def test_find_package_directory_missing(self): + path = os.path.join(self.temp_dir, "nonexistent") + self.assertRaisesQueryError( + Click.QueryError.PATH, Click.find_package_directory, path) + + def test_find_package_directory(self): + info = os.path.join(self.temp_dir, ".click", "info") + path = os.path.join(self.temp_dir, "file") + Click.ensuredir(info) + touch(path) + pkgdir = Click.find_package_directory(path) + self.assertEqual(self.temp_dir, pkgdir) + + def test_find_package_directory_outside(self): + self.assertRaisesQueryError( + Click.QueryError.NO_PACKAGE_DIR, Click.find_package_directory, + "/bin") diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_scripts.py click-0.4.46+16.10.20170607.3/click_package/tests/test_scripts.py --- click-0.4.45.1+16.10.20160916/click_package/tests/test_scripts.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_scripts.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,49 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Test that all top-level scripts work.""" + +__metaclass__ = type +__all__ = [ + 'TestScripts', + ] + +import os +import subprocess +from unittest import skipIf + +from click_package.tests.helpers import TestCase + + +class TestScripts(TestCase): + @skipIf('SKIP_SLOW_TESTS' in os.environ, 'Skipping slow tests') + def test_scripts(self): + self.longMessage = True + paths = [] + for dirpath, _, filenames in os.walk("bin"): + filenames = [ + n for n in filenames + if not n.startswith(".") and not n.endswith("~")] + for filename in filenames: + paths.append(os.path.join(dirpath, filename)) + for path in paths: + subp = subprocess.Popen( + [path, "--help"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True) + err = subp.communicate()[1] + self.assertEqual("", err, "%s --help produced error output" % path) + self.assertEqual( + 0, subp.returncode, "%s --help exited non-zero" % path) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_static.py click-0.4.46+16.10.20170607.3/click_package/tests/test_static.py --- click-0.4.45.1+16.10.20160916/click_package/tests/test_static.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_static.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,80 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Test compliance with various static analysis tools.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'TestStatic', + ] + + +import os +import sys +from unittest import skipIf + +from pkg_resources import resource_filename + +try: + import pep8 +except ImportError: + pep8 = None +try: + import pyflakes + import pyflakes.api + import pyflakes.reporter +except ImportError: + pyflakes = None + + +from click_package.tests.helpers import TestCase + + +class TestStatic(TestCase): + def all_paths(self): + paths = [] + start_dir = os.path.dirname(resource_filename('click_package', '__init__.py')) + for dirpath, dirnames, filenames in os.walk(start_dir): + for ignore in ('doc', ".bzr", "__pycache__"): + if ignore in dirnames: + dirnames.remove(ignore) + filenames = [ + n for n in filenames + if not n.startswith(".") and not n.endswith("~")] + if dirpath.split(os.sep)[-1] == "bin": + for filename in filenames: + paths.append(os.path.join(dirpath, filename)) + else: + for filename in filenames: + if filename.endswith(".py"): + paths.append(os.path.join(dirpath, filename)) + return paths + + @skipIf('SKIP_SLOW_TESTS' in os.environ, 'Skipping slow tests') + @skipIf(pep8 is None, 'No pep8 package available') + def test_pep8_clean(self): + # https://github.com/jcrocholl/pep8/issues/103 + pep8_style = pep8.StyleGuide(ignore='E123') + result = pep8_style.check_files(self.all_paths()) + self.assertEqual(result.total_errors, 0) + + @skipIf('SKIP_SLOW_TESTS' in os.environ, 'Skipping slow tests') + @skipIf(pyflakes is None, 'No pyflakes package available') + def test_pyflakes_clean(self): + reporter = pyflakes.reporter.Reporter(sys.stdout, sys.stderr) + warnings = pyflakes.api.checkRecursive(self.all_paths(), reporter) + self.assertEqual(0, warnings) diff -Nru click-0.4.45.1+16.10.20160916/click_package/tests/test_user.py click-0.4.46+16.10.20170607.3/click_package/tests/test_user.py --- click-0.4.45.1+16.10.20160916/click_package/tests/test_user.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/tests/test_user.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,591 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Unit tests for click_package.user.""" + +from __future__ import print_function + +__metaclass__ = type +__all__ = [ + 'TestClickUser', + ] + + +import json +import os +import shutil +from textwrap import dedent + +from gi.repository import Click, GLib + +from click_package.json_helpers import json_array_to_python, json_object_to_python +from click_package.tests.gimock_types import Passwd +from click_package.tests.helpers import ( + TestCase, + make_installed_click, + mkfile, + make_file_with_content, + touch, +) + + +class TestClickUser(TestCase): + def setUp(self): + super(TestClickUser, self).setUp() + self.use_temp_dir() + self.db = Click.DB() + self.db.add(self.temp_dir) + + def _setUpMultiDB(self): + self.multi_db = Click.DB() + self.multi_db.add(os.path.join(self.temp_dir, "custom")) + self.multi_db.add(os.path.join(self.temp_dir, "click")) + user_dbs = [ + os.path.join( + self.multi_db.get(i).props.root, ".click", "users", "user") + for i in range(self.multi_db.props.size) + ] + a_1_0 = os.path.join(self.temp_dir, "custom", "a", "1.0") + os.makedirs(a_1_0) + with mkfile(os.path.join(a_1_0, ".click", "info", "a.manifest")) as m: + json.dump({"name": "a", "version": "1.0", + "hooks": {"a-app": {}}}, m) + b_2_0 = os.path.join(self.temp_dir, "custom", "b", "2.0") + os.makedirs(b_2_0) + with mkfile(os.path.join(b_2_0, ".click", "info", "b.manifest")) as m: + json.dump({"name": "b", "version": "2.0"}, m) + a_1_1 = os.path.join(self.temp_dir, "click", "a", "1.1") + os.makedirs(a_1_1) + with mkfile(os.path.join(a_1_1, ".click", "info", "a.manifest")) as m: + json.dump({"name": "a", "version": "1.1"}, m) + c_0_1 = os.path.join(self.temp_dir, "click", "c", "0.1") + os.makedirs(c_0_1) + with mkfile(os.path.join(c_0_1, ".click", "info", "c.manifest")) as m: + json.dump({"name": "c", "version": "0.1"}, m) + os.makedirs(user_dbs[0]) + os.symlink(a_1_0, os.path.join(user_dbs[0], "a")) + os.symlink(b_2_0, os.path.join(user_dbs[0], "b")) + os.makedirs(user_dbs[1]) + os.symlink(a_1_1, os.path.join(user_dbs[1], "a")) + os.symlink(c_0_1, os.path.join(user_dbs[1], "c")) + return user_dbs, Click.User.for_user(self.multi_db, "user") + + def test_new_no_db(self): + with self.run_in_subprocess( + "click_get_db_dir", "g_get_user_name") as (enter, preloads): + enter() + preloads["click_get_db_dir"].side_effect = ( + lambda: self.make_string(self.temp_dir)) + preloads["g_get_user_name"].side_effect = ( + lambda: self.make_string("test-user")) + db_root = os.path.join(self.temp_dir, "db") + os.makedirs(db_root) + with open(os.path.join(self.temp_dir, "db.conf"), "w") as f: + print("[Click Database]", file=f) + print("root = %s" % db_root, file=f) + registry = Click.User.for_user(None, None) + self.assertEqual( + os.path.join(db_root, ".click", "users", "test-user"), + registry.get_overlay_db()) + + def test_new_db_not_directory(self): + with self.run_in_subprocess( + "click_get_db_dir", "g_get_user_name") as (enter, preloads): + enter() + path = os.path.join(self.temp_dir, "file") + touch(path) + preloads["click_get_db_dir"].side_effect = ( + lambda: self.make_string(path)) + self.assertRaisesFileError( + GLib.FileError.NOTDIR, Click.User.for_user, None, None) + + def test_get_overlay_db(self): + self.assertEqual( + os.path.join(self.temp_dir, ".click", "users", "user"), + Click.User.for_user(self.db, "user").get_overlay_db()) + + def test_ensure_db_ownership(self): + # getpwnam results are cached properly, in a way that doesn't fail + # due to confusion with getpwnam returning a pointer to a static + # buffer. + with self.run_in_subprocess( + "chown", "geteuid", "getpwnam") as (enter, preloads): + enter() + preloads["geteuid"].return_value = 0 + getpwnam_result = Passwd() + + def getpwnam_side_effect(name): + if name == b"clickpkg": + getpwnam_result.pw_uid = 1 + getpwnam_result.pw_gid = 1 + else: + getpwnam_result.pw_uid = 2 + getpwnam_result.pw_gid = 2 + return self.make_pointer(getpwnam_result) + + preloads["getpwnam"].side_effect = getpwnam_side_effect + registry = Click.User.for_user(self.db, "user") + os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) + click_dir = os.path.join(self.temp_dir, ".click") + + registry.set_version("a", "1.0") + self.assertEqual(3, preloads["chown"].call_count) + preloads["chown"].assert_any_call(click_dir.encode(), 1, 1) + preloads["chown"].assert_any_call( + os.path.join(click_dir, "users").encode(), 1, 1) + preloads["chown"].assert_any_call( + os.path.join(click_dir, "users", "user").encode(), 2, 2) + + # Try again, now that both password file entries should be + # cached. + shutil.rmtree(os.path.join(self.temp_dir, ".click")) + preloads["chown"].reset_mock() + registry.set_version("a", "1.0") + self.assertEqual(3, preloads["chown"].call_count) + preloads["chown"].assert_any_call(click_dir.encode(), 1, 1) + preloads["chown"].assert_any_call( + os.path.join(click_dir, "users").encode(), 1, 1) + preloads["chown"].assert_any_call( + os.path.join(click_dir, "users", "user").encode(), 2, 2) + + def test_ensure_db_mkdir_fails(self): + with self.run_in_subprocess("mkdir") as (enter, preloads): + enter() + preloads["mkdir"].return_value = -1 + registry = Click.User.for_user(self.db, "user") + self.assertRaisesUserError( + Click.UserError.CREATE_DB, registry.set_version, "a", "1.0") + + def test_ensure_db_chown_fails(self): + with self.run_in_subprocess( + "chown", "geteuid", "getpwnam") as (enter, preloads): + enter() + preloads["geteuid"].return_value = 0 + getpwnam_result = Passwd() + + def getpwnam_side_effect(name): + if name == b"clickpkg": + getpwnam_result.pw_uid = 1 + getpwnam_result.pw_gid = 1 + else: + getpwnam_result.pw_uid = 2 + getpwnam_result.pw_gid = 2 + return self.make_pointer(getpwnam_result) + + preloads["getpwnam"].side_effect = getpwnam_side_effect + preloads["chown"].return_value = -1 + registry = Click.User.for_user(self.db, "user") + self.assertRaisesUserError( + Click.UserError.CHOWN_DB, registry.set_version, "a", "1.0") + + def test_ensure_db_getpwnam_fails(self): + with self.run_in_subprocess( + "geteuid", "getpwnam") as (enter, preloads): + enter() + preloads["geteuid"].return_value = 0 + preloads["getpwnam"].return_value = None + registry = Click.User.for_user(self.db, "user") + self.assertRaisesUserError( + Click.UserError.GETPWNAM, registry.set_version, "a", "1.0") + + def test_get_package_names_missing(self): + db = Click.DB() + db.add(os.path.join(self.temp_dir, "nonexistent")) + registry = Click.User.for_user(db, None) + self.assertEqual([], list(registry.get_package_names())) + + def test_get_package_names(self): + registry = Click.User.for_user(self.db, "user") + os.makedirs(registry.get_overlay_db()) + os.symlink("/1.0", os.path.join(registry.get_overlay_db(), "a")) + os.symlink("/1.1", os.path.join(registry.get_overlay_db(), "b")) + self.assertCountEqual(["a", "b"], list(registry.get_package_names())) + + def test_get_package_names_multiple_root(self): + _, registry = self._setUpMultiDB() + self.assertCountEqual( + ["a", "b", "c"], list(registry.get_package_names())) + + def test_get_version_missing(self): + registry = Click.User.for_user(self.db, "user") + self.assertRaisesUserError( + Click.UserError.NO_SUCH_PACKAGE, registry.get_version, "a") + self.assertFalse(registry.has_package_name("a")) + + def test_get_version(self): + registry = Click.User.for_user(self.db, "user") + os.makedirs(registry.get_overlay_db()) + os.symlink("/1.0", os.path.join(registry.get_overlay_db(), "a")) + self.assertEqual("1.0", registry.get_version("a")) + self.assertTrue(registry.has_package_name("a")) + + def test_get_version_multiple_root(self): + _, registry = self._setUpMultiDB() + self.assertEqual("1.1", registry.get_version("a")) + self.assertEqual("2.0", registry.get_version("b")) + self.assertEqual("0.1", registry.get_version("c")) + self.assertTrue(registry.has_package_name("a")) + self.assertTrue(registry.has_package_name("b")) + self.assertTrue(registry.has_package_name("c")) + + def test_set_version_missing_target(self): + registry = Click.User.for_user(self.db, "user") + self.assertRaisesDatabaseError( + Click.DatabaseError.DOES_NOT_EXIST, + registry.set_version, "a", "1.0") + + def test_set_version_missing(self): + registry = Click.User.for_user(self.db, "user") + os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) + registry.set_version("a", "1.0") + path = os.path.join(registry.get_overlay_db(), "a") + self.assertTrue(os.path.islink(path)) + self.assertEqual( + os.path.join(self.temp_dir, "a", "1.0"), os.readlink(path)) + + def test_set_version_changed(self): + registry = Click.User.for_user(self.db, "user") + os.makedirs(registry.get_overlay_db()) + path = os.path.join(registry.get_overlay_db(), "a") + os.symlink("/1.0", path) + os.makedirs(os.path.join(self.temp_dir, "a", "1.1")) + registry.set_version("a", "1.1") + self.assertTrue(os.path.islink(path)) + self.assertEqual( + os.path.join(self.temp_dir, "a", "1.1"), os.readlink(path)) + + def test_set_version_multiple_root(self): + user_dbs, registry = self._setUpMultiDB() + + os.makedirs(os.path.join(self.multi_db.get(1).props.root, "a", "1.2")) + registry.set_version("a", "1.2") + a_underlay = os.path.join(user_dbs[0], "a") + a_overlay = os.path.join(user_dbs[1], "a") + self.assertTrue(os.path.islink(a_underlay)) + self.assertEqual( + os.path.join(self.multi_db.get(0).props.root, "a", "1.0"), + os.readlink(a_underlay)) + self.assertTrue(os.path.islink(a_overlay)) + self.assertEqual( + os.path.join(self.multi_db.get(1).props.root, "a", "1.2"), + os.readlink(a_overlay)) + + os.makedirs(os.path.join(self.multi_db.get(1).props.root, "b", "2.1")) + registry.set_version("b", "2.1") + b_underlay = os.path.join(user_dbs[0], "b") + b_overlay = os.path.join(user_dbs[1], "b") + self.assertTrue(os.path.islink(b_underlay)) + self.assertEqual( + os.path.join(self.multi_db.get(0).props.root, "b", "2.0"), + os.readlink(b_underlay)) + self.assertTrue(os.path.islink(b_overlay)) + self.assertEqual( + os.path.join(self.multi_db.get(1).props.root, "b", "2.1"), + os.readlink(b_overlay)) + + os.makedirs(os.path.join(self.multi_db.get(1).props.root, "c", "0.2")) + registry.set_version("c", "0.2") + c_underlay = os.path.join(user_dbs[0], "c") + c_overlay = os.path.join(user_dbs[1], "c") + self.assertFalse(os.path.islink(c_underlay)) + self.assertTrue(os.path.islink(c_overlay)) + self.assertEqual( + os.path.join(self.multi_db.get(1).props.root, "c", "0.2"), + os.readlink(c_overlay)) + + os.makedirs(os.path.join(self.multi_db.get(1).props.root, "d", "3.0")) + registry.set_version("d", "3.0") + d_underlay = os.path.join(user_dbs[0], "d") + d_overlay = os.path.join(user_dbs[1], "d") + self.assertFalse(os.path.islink(d_underlay)) + self.assertTrue(os.path.islink(d_overlay)) + self.assertEqual( + os.path.join(self.multi_db.get(1).props.root, "d", "3.0"), + os.readlink(d_overlay)) + + def test_set_version_restore_to_underlay(self): + user_dbs, registry = self._setUpMultiDB() + a_underlay = os.path.join(user_dbs[0], "a") + a_overlay = os.path.join(user_dbs[1], "a") + + # Initial state: 1.0 in underlay, 1.1 in overlay. + self.assertTrue(os.path.islink(a_underlay)) + self.assertEqual( + os.path.join(self.multi_db.get(0).props.root, "a", "1.0"), + os.readlink(a_underlay)) + self.assertTrue(os.path.islink(a_overlay)) + self.assertEqual( + os.path.join(self.multi_db.get(1).props.root, "a", "1.1"), + os.readlink(a_overlay)) + + # Setting to 1.0 (version in underlay) removes overlay link. + registry.set_version("a", "1.0") + self.assertTrue(os.path.islink(a_underlay)) + self.assertEqual( + os.path.join(self.multi_db.get(0).props.root, "a", "1.0"), + os.readlink(a_underlay)) + self.assertFalse(os.path.islink(a_overlay)) + + def test_remove_missing(self): + registry = Click.User.for_user(self.db, "user") + self.assertRaisesUserError( + Click.UserError.NO_SUCH_PACKAGE, registry.remove, "a") + + def test_remove(self): + registry = Click.User.for_user(self.db, "user") + os.makedirs(registry.get_overlay_db()) + path = os.path.join(registry.get_overlay_db(), "a") + os.symlink("/1.0", path) + registry.remove("a") + self.assertFalse(os.path.exists(path)) + + def test_remove_multiple_root(self): + user_dbs, registry = self._setUpMultiDB() + registry.remove("a") + self.assertFalse(os.path.exists(os.path.join(user_dbs[1], "a"))) + # Exposed underlay. + self.assertEqual("1.0", registry.get_version("a")) + registry.remove("b") + # Hidden. + self.assertEqual( + "@hidden", os.readlink(os.path.join(user_dbs[1], "b"))) + self.assertFalse(registry.has_package_name("b")) + registry.remove("c") + self.assertFalse(os.path.exists(os.path.join(user_dbs[1], "c"))) + self.assertFalse(registry.has_package_name("c")) + self.assertRaisesUserError( + Click.UserError.NO_SUCH_PACKAGE, registry.remove, "d") + + def test_remove_multiple_root_creates_overlay_directory(self): + multi_db = Click.DB() + multi_db.add(os.path.join(self.temp_dir, "preinstalled")) + multi_db.add(os.path.join(self.temp_dir, "click")) + user_dbs = [ + os.path.join(multi_db.get(i).props.root, ".click", "users", "user") + for i in range(multi_db.props.size) + ] + a_1_0 = os.path.join(self.temp_dir, "preinstalled", "a", "1.0") + os.makedirs(a_1_0) + os.makedirs(user_dbs[0]) + os.symlink(a_1_0, os.path.join(user_dbs[0], "a")) + self.assertFalse(os.path.exists(user_dbs[1])) + registry = Click.User.for_user(multi_db, "user") + self.assertEqual("1.0", registry.get_version("a")) + registry.remove("a") + self.assertFalse(registry.has_package_name("a")) + self.assertEqual( + "@hidden", os.readlink(os.path.join(user_dbs[1], "a"))) + + def test_get_path(self): + registry = Click.User.for_user(self.db, "user") + os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) + registry.set_version("a", "1.0") + self.assertEqual( + os.path.join(registry.get_overlay_db(), "a"), + registry.get_path("a")) + + def test_get_path_multiple_root(self): + user_dbs, registry = self._setUpMultiDB() + self.assertEqual( + os.path.join(user_dbs[1], "a"), registry.get_path("a")) + self.assertEqual( + os.path.join(user_dbs[0], "b"), registry.get_path("b")) + self.assertEqual( + os.path.join(user_dbs[1], "c"), registry.get_path("c")) + self.assertRaisesUserError( + Click.UserError.NO_SUCH_PACKAGE, registry.get_path, "d") + + def test_get_manifest(self): + registry = Click.User.for_user(self.db, "user") + manifest_path = os.path.join( + self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") + manifest_obj = {"name": "a", "version": "1.0"} + with mkfile(manifest_path) as manifest: + json.dump(manifest_obj, manifest) + manifest_obj["_directory"] = os.path.join( + registry.get_overlay_db(), "a") + manifest_obj["_removable"] = 1 + registry.set_version("a", "1.0") + self.assertEqual( + manifest_obj, json_object_to_python(registry.get_manifest("a"))) + self.assertEqual( + manifest_obj, json.loads(registry.get_manifest_as_string("a"))) + + def test_get_manifest_multiple_root(self): + user_dbs, registry = self._setUpMultiDB() + expected_a = { + "name": "a", + "version": "1.1", + "_directory": os.path.join(user_dbs[1], "a"), + "_removable": 1, + } + self.assertEqual( + expected_a, json_object_to_python(registry.get_manifest("a"))) + self.assertEqual( + expected_a, json.loads(registry.get_manifest_as_string("a"))) + expected_b = { + "name": "b", + "version": "2.0", + "_directory": os.path.join(user_dbs[0], "b"), + "_removable": 1, + } + self.assertEqual( + expected_b, json_object_to_python(registry.get_manifest("b"))) + self.assertEqual( + expected_b, json.loads(registry.get_manifest_as_string("b"))) + expected_c = { + "name": "c", + "version": "0.1", + "_directory": os.path.join(user_dbs[1], "c"), + "_removable": 1, + } + self.assertEqual( + expected_c, json_object_to_python(registry.get_manifest("c"))) + self.assertEqual( + expected_c, json.loads(registry.get_manifest_as_string("c"))) + self.assertRaisesUserError( + Click.UserError.NO_SUCH_PACKAGE, registry.get_path, "d") + + def test_get_manifests(self): + registry = Click.User.for_user(self.db, "user") + a_manifest_path = os.path.join( + self.temp_dir, "a", "1.0", ".click", "info", "a.manifest") + a_manifest_obj = {"name": "a", "version": "1.0"} + with mkfile(a_manifest_path) as a_manifest: + json.dump(a_manifest_obj, a_manifest) + registry.set_version("a", "1.0") + b_manifest_path = os.path.join( + self.temp_dir, "b", "2.0", ".click", "info", "b.manifest") + b_manifest_obj = {"name": "b", "version": "2.0"} + with mkfile(b_manifest_path) as b_manifest: + json.dump(b_manifest_obj, b_manifest) + registry.set_version("b", "2.0") + a_manifest_obj["_directory"] = os.path.join( + registry.get_overlay_db(), "a") + a_manifest_obj["_removable"] = 1 + b_manifest_obj["_directory"] = os.path.join( + registry.get_overlay_db(), "b") + b_manifest_obj["_removable"] = 1 + self.assertEqual( + [a_manifest_obj, b_manifest_obj], + json_array_to_python(registry.get_manifests())) + self.assertEqual( + [a_manifest_obj, b_manifest_obj], + json.loads(registry.get_manifests_as_string())) + + def test_get_manifests_multiple_root(self): + user_dbs, registry = self._setUpMultiDB() + a_manifest_obj = { + "name": "a", + "version": "1.1", + "_directory": os.path.join(user_dbs[1], "a"), + "_removable": 1, + } + b_manifest_obj = { + "name": "b", + "version": "2.0", + "_directory": os.path.join(user_dbs[0], "b"), + "_removable": 1, + } + c_manifest_obj = { + "name": "c", + "version": "0.1", + "_directory": os.path.join(user_dbs[1], "c"), + "_removable": 1, + } + self.assertEqual( + [a_manifest_obj, c_manifest_obj, b_manifest_obj], + json_array_to_python(registry.get_manifests())) + self.assertEqual( + [a_manifest_obj, c_manifest_obj, b_manifest_obj], + json.loads(registry.get_manifests_as_string())) + registry.remove("b") + self.assertEqual( + "@hidden", os.readlink(os.path.join(user_dbs[1], "b"))) + self.assertEqual( + [a_manifest_obj, c_manifest_obj], + json_array_to_python(registry.get_manifests())) + self.assertEqual( + [a_manifest_obj, c_manifest_obj], + json.loads(registry.get_manifests_as_string())) + + def test_is_removable(self): + registry = Click.User.for_user(self.db, "user") + os.makedirs(os.path.join(self.temp_dir, "a", "1.0")) + registry.set_version("a", "1.0") + self.assertTrue(registry.is_removable("a")) + + def test_is_removable_multiple_root(self): + user_dbs, registry = self._setUpMultiDB() + self.assertTrue(registry.is_removable("a")) + self.assertTrue(registry.is_removable("b")) + self.assertTrue(registry.is_removable("c")) + self.assertFalse(registry.is_removable("d")) + + def test_hidden(self): + user_dbs, registry = self._setUpMultiDB() + b_overlay = os.path.join(user_dbs[1], "b") + + registry.remove("b") + self.assertFalse(registry.has_package_name("b")) + self.assertTrue(os.path.islink(b_overlay)) + self.assertEqual("@hidden", os.readlink(b_overlay)) + self.assertRaisesUserError( + Click.UserError.HIDDEN_PACKAGE, registry.get_version, "b") + self.assertRaisesUserError( + Click.UserError.HIDDEN_PACKAGE, registry.get_path, "b") + self.assertFalse(registry.is_removable("b")) + + registry.set_version("b", "2.0") + self.assertTrue(registry.has_package_name("b")) + self.assertTrue(os.path.islink(b_overlay)) + self.assertEqual( + os.path.join(self.multi_db.get(0).props.root, "b", "2.0"), + os.readlink(b_overlay)) + self.assertEqual("2.0", registry.get_version("b")) + self.assertEqual(b_overlay, registry.get_path("b")) + self.assertTrue(registry.is_removable("b")) + + +class StopAppTestCase(TestCase): + + def setUp(self): + super(StopAppTestCase, self).setUp() + self.use_temp_dir() + self.db = Click.DB() + self.db.add(self.temp_dir) + + # setup fake app_stop + fake_app_stop = os.path.join(self.temp_dir, "bin", "ubuntu-app-stop") + self.fake_app_stop_output = os.path.join( + self.temp_dir, "fake-app-stop.out") + fake_app_stop_content = dedent("""\ + #!/bin/sh + echo "$@" >> %s + """ % self.fake_app_stop_output) + make_file_with_content(fake_app_stop, fake_app_stop_content, 0o755) + # its ok to modify env here, click.helpers.TestCase will take care + # of it + os.environ["PATH"] = "%s:%s" % ( + os.path.dirname(fake_app_stop), os.environ["PATH"]) + + def test_app_stops_on_remove(self): + make_installed_click(self.db, self.temp_dir, "meep", "2.0", + {"hooks": {"a-app": {}}}) + registry = Click.User.for_user(self.db, "user") + registry.remove("meep") + # ensure that stop was called with the right app + with open(self.fake_app_stop_output) as f: + self.assertEqual("meep_a-app_2.0", f.read().strip()) diff -Nru click-0.4.45.1+16.10.20160916/click_package/versions.py click-0.4.46+16.10.20170607.3/click_package/versions.py --- click-0.4.45.1+16.10.20160916/click_package/versions.py 1970-01-01 00:00:00.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/click_package/versions.py 2017-06-08 01:24:46.000000000 +0000 @@ -0,0 +1,18 @@ +# Copyright (C) 2013 Canonical Ltd. +# Author: Colin Watson + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Click package versioning.""" + +spec_version = "0.4" diff -Nru click-0.4.45.1+16.10.20160916/configure.ac click-0.4.46+16.10.20170607.3/configure.ac --- click-0.4.45.1+16.10.20160916/configure.ac 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/configure.ac 2017-06-08 01:24:46.000000000 +0000 @@ -131,9 +131,9 @@ AC_CONFIG_FILES([ Makefile - click/Makefile - click/tests/Makefile - click/tests/config.py + click_package/Makefile + click_package/tests/Makefile + click_package/tests/config.py conf/Makefile conf/databases/Makefile conf/databases/99_default.conf diff -Nru click-0.4.45.1+16.10.20160916/debian/changelog click-0.4.46+16.10.20170607.3/debian/changelog --- click-0.4.45.1+16.10.20160916/debian/changelog 2016-09-16 17:46:25.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/debian/changelog 2017-06-07 18:53:16.000000000 +0000 @@ -1,3 +1,12 @@ +click (0.4.46+16.10.20170607.3-0ubuntu1) yakkety; urgency=medium + + * Rename the python package this installs from click to click_package. + (LP: #1693226) + * Use the correct overlayfs module name for the chroot configuration. + (LP: #1696402) + + -- Sergio Schvezov Wed, 07 Jun 2017 18:53:16 +0000 + click (0.4.45.1+16.10.20160916-0ubuntu1) yakkety; urgency=medium * Kill gpg-agent (if possible) after running debsigs in integration tests. diff -Nru click-0.4.45.1+16.10.20160916/debian/control click-0.4.46+16.10.20170607.3/debian/control --- click-0.4.45.1+16.10.20160916/debian/control 2016-09-16 17:46:25.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/debian/control 2017-06-07 18:53:16.000000000 +0000 @@ -40,7 +40,6 @@ Section: python Architecture: any Depends: ${misc:Depends}, ${python3:Depends}, gir1.2-click-0.4 (= ${binary:Version}), gir1.2-glib-2.0, python3-apt, python3-debian, python3-gi -Conflicts: python3-click, python3-click-cli Replaces: python3-click (<< 0.4.43) Description: Click packages (Python 3 interface) Click is a simplified packaging format that installs in a separate part of diff -Nru click-0.4.45.1+16.10.20160916/debian/tests/run-tests.sh click-0.4.46+16.10.20170607.3/debian/tests/run-tests.sh --- click-0.4.45.1+16.10.20160916/debian/tests/run-tests.sh 2016-09-16 17:46:25.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/debian/tests/run-tests.sh 2017-06-07 18:53:16.000000000 +0000 @@ -10,4 +10,4 @@ --with-systemduserunitdir=/usr/lib/systemd/user \ --disable-packagekit -TEST_INTEGRATION=1 python3 -m unittest discover -vv click.tests.integration +TEST_INTEGRATION=1 python3 -m unittest discover -vv click_package.tests.integration diff -Nru click-0.4.45.1+16.10.20160916/doc/index.rst click-0.4.46+16.10.20170607.3/doc/index.rst --- click-0.4.45.1+16.10.20160916/doc/index.rst 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/doc/index.rst 2017-06-08 01:24:46.000000000 +0000 @@ -84,11 +84,11 @@ To run a specific testcase, use the standard python unittest syntax like:: - $ python3 -m unittest click.tests.test_install + $ python3 -m unittest click_package.tests.test_install or:: - $ python2 -m unittest click.tests.test_build.TestClickBuilder.test_build + $ python2 -m unittest click_package.tests.test_build.TestClickBuilder.test_build Test coverage ------------- @@ -130,7 +130,7 @@ GI_TYPELIB_PATH=$(pwd)/lib/click \ CLICK_BINARY=$(pwd)/bin/click \ TEST_INTEGRATION=1 \ - python3 -m unittest discover click.tests.integration + python3 -m unittest discover click_package.tests.integration to run against the build tree. diff -Nru click-0.4.45.1+16.10.20160916/Makefile.am click-0.4.46+16.10.20170607.3/Makefile.am --- click-0.4.45.1+16.10.20160916/Makefile.am 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/Makefile.am 2017-06-08 01:24:46.000000000 +0000 @@ -1,4 +1,4 @@ -SUBDIRS = lib preload click conf debhelper init po schroot +SUBDIRS = lib preload click_package conf debhelper init po schroot if PACKAGEKIT SUBDIRS += pk-plugin endif diff -Nru click-0.4.45.1+16.10.20160916/setup.py.in click-0.4.46+16.10.20170607.3/setup.py.in --- click-0.4.45.1+16.10.20160916/setup.py.in 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/setup.py.in 2017-06-08 01:24:46.000000000 +0000 @@ -29,7 +29,7 @@ coverage_executable = "python3-coverage" self.spawn([ coverage_executable, "run", "-m", "unittest", - "discover", "-vv", "click.tests"]) + "discover", "-vv", "click_package.tests"]) self.spawn([coverage_executable, "combine"]) self.spawn([ coverage_executable, "xml", "-o", "coverage-python.xml"]) @@ -49,5 +49,5 @@ scripts=['bin/click'], install_requires=requirements, cmdclass={"test": test_extra}, - test_suite="click.tests", + test_suite="click_package.tests", ) diff -Nru click-0.4.45.1+16.10.20160916/tox.ini click-0.4.46+16.10.20170607.3/tox.ini --- click-0.4.45.1+16.10.20160916/tox.ini 2016-09-16 17:45:45.000000000 +0000 +++ click-0.4.46+16.10.20170607.3/tox.ini 2017-06-08 01:24:46.000000000 +0000 @@ -1,6 +1,6 @@ [tox] -envlist = py27,py32,py33,py34 +envlist = py27,py32,py33,py34,py35,py36 [testenv] -commands = python -m unittest discover -vv click.tests +commands = python -m unittest discover -vv click_package.tests sitepackages = True