diff -Nru click-reviewers-tools-0.21/bin/clickreviews/cr_bin_path.py click-reviewers-tools-0.24/bin/clickreviews/cr_bin_path.py --- click-reviewers-tools-0.21/bin/clickreviews/cr_bin_path.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/cr_bin_path.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ '''cr_bin_path.py: click bin-path''' # -# Copyright (C) 2014 Canonical Ltd. +# Copyright (C) 2014-2015 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 @@ -26,7 +26,7 @@ peer_hooks = dict() my_hook = 'bin-path' peer_hooks[my_hook] = dict() - peer_hooks[my_hook]['required'] = ["snappy-systemd", "apparmor"] + peer_hooks[my_hook]['required'] = ["apparmor"] peer_hooks[my_hook]['allowed'] = peer_hooks[my_hook]['required'] ClickReview.__init__(self, fn, "bin-path", peer_hooks=peer_hooks) @@ -67,3 +67,28 @@ s = "'%s' is not executable" % \ (self.manifest['hooks'][app]['bin-path']) self._add_result(t, n, s) + + def check_binary_description(self): + '''Check package.yaml binary description''' + if not self.is_snap or 'binaries' not in self.pkg_yaml: + return + + my_dict = self._create_dict(self.pkg_yaml['binaries']) + + for app in sorted(my_dict): + t = 'info' + n = 'package_yaml_description_present_%s' % (app) + s = 'OK' + if 'description' not in my_dict[app]: + s = 'OK (skip missing)' + self._add_result('info', n, s) + return + self._add_result(t, n, s) + + t = 'info' + n = 'package_yaml_description_empty_%s' % (app) + s = 'OK' + if len(my_dict[app]['description']) == 0: + t = 'error' + s = "description is empty" + self._add_result(t, n, s) diff -Nru click-reviewers-tools-0.21/bin/clickreviews/cr_common.py click-reviewers-tools-0.24/bin/clickreviews/cr_common.py --- click-reviewers-tools-0.21/bin/clickreviews/cr_common.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/cr_common.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ '''common.py: common classes and functions''' # -# Copyright (C) 2013-2014 Canonical Ltd. +# Copyright (C) 2013-2015 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 @@ -23,11 +23,13 @@ import magic import os import pprint +import re import shutil import subprocess import sys import tempfile import types +import yaml DEBUGGING = False UNPACK_DIR = None @@ -75,10 +77,24 @@ # FIXME: when apparmor-policy is implemented, use this # framework_allowed_peer_hooks = ["apparmor-policy"] framework_allowed_peer_hooks = [] - service_allowed_peer_hooks = ["bin-path", + service_allowed_peer_hooks = ["apparmor", + "bin-path", "snappy-systemd", ] + snappy_required = ["name", "version"] + # optional snappy fields here (may be required by appstore) + snappy_optional = ["architecture", + "binaries", + "frameworks", + "icon", + "integration", + "services", + "source", + "type", + "vendor", # replaces maintainer + ] + def __init__(self, fn, review_type, peer_hooks=None): self.click_package = fn self._check_path_exists() @@ -105,7 +121,6 @@ self.unpack_dir = UNPACK_DIR # Get some basic information from the control file - control_file = self._extract_control_file() tmp = list(Deb822.iter_paragraphs(control_file)) if len(tmp) != 1: @@ -123,6 +138,17 @@ error("Could not load manifest file. Is it properly formatted?") self._verify_manifest_structure() + # Parse and store the package.yaml + pkg_yaml = self._extract_package_yaml() + self.is_snap = False + if pkg_yaml is not None: + try: + self.pkg_yaml = yaml.safe_load(pkg_yaml) + except Exception as e: + error("Could not load package.yaml. Is it properly formatted?") + self._verify_package_yaml_structure() + self.is_snap = True + # Get a list of all unpacked files, except DEBIAN/ self.pkg_files = [] self._list_all_files() @@ -160,6 +186,13 @@ error("Could not find manifest file") return open_file_read(m) + def _extract_package_yaml(self): + '''Extract and read the snappy package.yaml''' + y = os.path.join(self.unpack_dir, "meta/package.yaml") + if not os.path.isfile(y): + return None # snappy packaging is still optional + return open_file_read(y) + def _check_path_exists(self): '''Check that the provided path exists''' if not os.path.exists(self.click_package): @@ -244,6 +277,40 @@ error("manifest malformed: unsupported field '%s':\n%s" % (k, mp)) + def _verify_package_yaml_structure(self): + '''Verify package.yaml has the expected structure''' + # https://developer.ubuntu.com/en/snappy/guides/packaging-format-apps/ + # lp:click doc/file-format.rst + yp = yaml.dump(self.pkg_yaml, default_flow_style=False, indent=4) + if not isinstance(self.pkg_yaml, dict): + error("package yaml malformed:\n%s" % self.pkg_yaml) + + for f in self.snappy_required: + if f not in self.pkg_yaml: + error("could not find required '%s' in package.yaml:\n%s" % + (f, yp)) + elif f in ['name', 'version']: + # make sure this is a string for other tests since + # yaml.safe_load may make it an int, float or str + self.pkg_yaml[f] = str(self.pkg_yaml[f]) + + for f in self.snappy_optional: + if f in self.pkg_yaml: + if f in ["architecture", "frameworks"] and not \ + (isinstance(self.pkg_yaml[f], str) or + isinstance(self.pkg_yaml[f], list)): + error("yaml malformed: '%s' is not str or list:\n%s" % + (f, mp)) + elif f in ["binaries", "services"] and not \ + isinstance(self.pkg_yaml[f], list): + error("yaml malformed: '%s' is not list:\n%s" % (f, yp)) + elif f == "integration" and not isinstance(self.pkg_yaml[f], + dict): + error("yaml malformed: '%s' is not dict:\n%s" % (f, yp)) + elif f in ["icon", "source", "type", "vendor"] and not \ + isinstance(self.pkg_yaml[f], str): + error("yaml malformed: '%s' is not str:\n%s" % (f, yp)) + def _verify_peer_hooks(self, my_hook): '''Compare manifest for required and allowed hooks''' d = dict() @@ -257,6 +324,11 @@ if h == my_hook: continue if h not in self.manifest["hooks"][app]: + # Treat these as equivalent for satisfying peer hooks + if h == 'apparmor' and \ + 'apparmor-profile' in self.manifest["hooks"][app]: + continue + if 'missing' not in d: d['missing'] = dict() if app not in d['missing']: @@ -266,6 +338,13 @@ if h == my_hook: continue if h not in self.peer_hooks[my_hook]['allowed']: + # 'apparmor-profile' is allowed when 'apparmor' is, but + # they may not be used together + if h == 'apparmor-profile': + if 'apparmor' in self.peer_hooks[my_hook]['allowed'] \ + and 'apparmor' not in self.manifest["hooks"][app]: + continue + if 'disallowed' not in d: d['disallowed'] = dict() if app not in d['disallowed']: @@ -274,6 +353,44 @@ return d + def _verify_pkgname(self, n): + '''Verify package name''' + if re.search(r'^[a-z0-9][a-z0-9+.-]+$', n): + return True + return False + + def _verify_pkgversion(self, v): + '''Verify package name''' + re_valid_version = re.compile(r'^((\d+):)?' # epoch + '([A-Za-z0-9.+:~-]+?)' # upstream + '(-([A-Za-z0-9+.~]+))?$') # debian + if re_valid_version.match(v): + return True + return False + + def _verify_maintainer(self, m): + '''Verify maintainer email''' + # Simple regex as used by python3-debian. If we wanted to be more + # thorough we could use email_re from django.core.validators + if re.search(r"^(.*)\s+<(.*@.*)>$", m): + return True + return False + + def _create_dict(self, lst, topkey='name'): + '''Converts list of dicts into dict[topkey][]. Useful for + conversions from yaml list to json dict''' + d = dict() + for entry in lst: + if topkey not in entry: + error("required field '%s' not present: %s" % (topkey, entry)) + name = entry[topkey] + d[name] = dict() + for key in entry: + if key == topkey: + continue + d[name][key] = entry[key] + return d + def check_peer_hooks(self, hooks_sublist=[]): '''Check if peer hooks are valid''' # Nothing to verify diff -Nru click-reviewers-tools-0.21/bin/clickreviews/cr_desktop.py click-reviewers-tools-0.24/bin/clickreviews/cr_desktop.py --- click-reviewers-tools-0.21/bin/clickreviews/cr_desktop.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/cr_desktop.py 2015-03-10 15:16:29.000000000 +0000 @@ -340,11 +340,7 @@ "--webappModelSearchPath or --webapp= when " + \ "running local application" elif not is_launching_local_app: - if found_url_patterns and found_model_search_path: - t = 'error' - s = "should not specify --webappUrlPatterns when using " + \ - "--webappModelSearchPath" - elif not found_url_patterns and not found_model_search_path: + if not found_url_patterns and not found_model_search_path: t = 'error' s = "must specify one of --webappUrlPatterns or " + \ "--webappModelSearchPath" diff -Nru click-reviewers-tools-0.21/bin/clickreviews/cr_lint.py click-reviewers-tools-0.24/bin/clickreviews/cr_lint.py --- click-reviewers-tools-0.21/bin/clickreviews/cr_lint.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/cr_lint.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ -'''cr_lint.py: click lint checks''' +'''cr_lint.py: lint checks''' # -# Copyright (C) 2013-2014 Canonical Ltd. +# Copyright (C) 2013-2015 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 @@ -60,8 +60,10 @@ self.is_core_app = (self.click_pkgname.startswith('com.ubuntu.') and not self.click_pkgname.startswith( 'com.ubuntu.developer.') - and self.email == - 'ubuntu-touch-coreapps@lists.launchpad.net') + and (self.email == + 'ubuntu-touch-coreapps@lists.launchpad.net' or + self.email == + 'ubuntu-devel-discuss@lists.ubuntu.com')) # "core scope" is not necessarily a word we use right now, but # we want to special case scopes which are written through our # vetted development process. @@ -87,6 +89,7 @@ 'account-qml-plugin', 'account-service', 'apparmor', + 'apparmor-profile', 'bin-path', 'content-hub', 'desktop', @@ -99,7 +102,22 @@ self.redflagged_hooks = ['framework', 'pay-ui', + 'apparmor-profile', ] + + # Valid values for 'type' in packaging yaml + # - app + # - framework + # - oem + self.snappy_valid_types = ['app', + 'framework', + # 'framework-policy', # TBI + # 'oem', # TBI + ] + self.snappy_redflagged_types = ['framework', + # 'oem', # TBI + ] + if overrides is None: overrides = {} self.overrides = overrides @@ -372,10 +390,16 @@ if f not in self.manifest['hooks'][app]: # TODO: when have apparmor policy for account-provider and # account-qml-plugin, remove this conditional - if f != 'apparmor' or \ - len(self.manifest['hooks'][app]) != 2 or \ - 'account-provider' not in self.manifest['hooks'][app] or \ - 'account-qml-plugin' not in self.manifest['hooks'][app]: + if f == 'apparmor' and \ + 'apparmor-profile' in self.manifest['hooks'][app]: + # Don't require apparmor if have apparmor-profile + pass + elif f != 'apparmor' or \ + len(self.manifest['hooks'][app]) != 2 or \ + 'account-provider' not in \ + self.manifest['hooks'][app] or \ + 'account-qml-plugin' not in \ + self.manifest['hooks'][app]: t = 'error' s = "'%s' hook not found for '%s'" % (f, app) self._add_result(t, n, s) @@ -465,7 +489,7 @@ t = 'info' n = 'pkgname_valid' s = "OK" - if not re.search(r'^[a-z0-9][a-z0-9+.-]+$', p): + if not self._verify_pkgname(p): t = 'error' s = "'%s' not properly formatted" % p self._add_result(t, n, s) @@ -525,7 +549,7 @@ return if len(self.pkg_bin_files) == 0: - t = 'error' + t = 'warn' s = "Could not find compiled binaries for architecture '%s'" % \ self.click_arch self._add_result(t, n, s) @@ -551,7 +575,7 @@ 'like "Joe Bloggs ")', 'http://askubuntu.com/questions/417351/what-does-lint-maintainer-format-mean/417352') return - elif not re.search(r"^(.*)\s+<(.*@.*)>$", self.manifest['maintainer']): + elif not self._verify_maintainer(self.manifest['maintainer']): self._add_result('error', n, 'invalid format for maintainer: %s (should be ' 'like "Joe Bloggs ")' % @@ -597,7 +621,13 @@ pkgname_base = self.click_pkgname.split('.')[-1] if len(self.manifest['description']) < len(pkgname_base): t = 'warn' - s = "'%s' is too short" % self.manifest['description'] + if self.is_snap and (self.manifest['description'] == '\n' or + self.manifest['description'] == ''): + s = "manifest description is empty. Is meta/readme.md " + \ + "formatted correctly?" + else: + s = "'%s' is too short" % self.manifest['description'] + self._add_result(t, n, s) def check_framework(self): @@ -658,17 +688,18 @@ def check_package_filename(self): '''Check filename of package''' tmp = os.path.basename(self.click_package).split('_') + click_package_bn = os.path.basename(self.click_package) t = 'info' n = 'package_filename_format' s = 'OK' if len(tmp) != 3: t = 'warn' - s = "'%s' not of form $pkgname_$version_$arch.click" % \ - os.path.basename(self.click_package) + s = "'%s' not of form $pkgname_$version_$arch.[click|snap]" % \ + click_package_bn self._add_result(t, n, s) # handle $pkgname.click or $pkgname.snap - if tmp[0].endswith('.snap'): + if self.click_package.endswith('.snap'): pkgname = tmp[0].partition('.snap')[0] else: pkgname = tmp[0].partition('.click')[0] @@ -677,7 +708,8 @@ s = 'OK' l = None if pkgname != self.click_pkgname: - if pkgname.startswith('com.ubuntu.snappy.'): + if pkgname.startswith('com.ubuntu.snappy.') or \ + click_package_bn.startswith('com.ubuntu.snappy.'): s = "OK (store snappy workaround)" else: t = 'error' @@ -705,7 +737,7 @@ l = None if len(tmp) >= 2: # handle $pkgname_$version.click or $pkgname_$version.snap - if tmp[0].endswith('.snap'): + if self.click_package.endswith('.snap'): version = tmp[1].partition('.snap')[0] else: version = tmp[1].partition('.click')[0] @@ -724,7 +756,7 @@ n = 'package_filename_arch_valid' s = 'OK' if len(tmp) >= 3: - if tmp[0].endswith('.snap'): + if self.click_package.endswith('.snap'): arch = tmp[2].partition('.snap')[0] else: arch = tmp[2].partition('.click')[0] @@ -748,7 +780,7 @@ n = 'package_filename_arch_match' s = 'OK' if len(tmp) >= 3: - if tmp[0].endswith('.snap'): + if self.click_package.endswith('.snap'): arch = tmp[2].partition('.snap')[0] else: arch = tmp[2].partition('.click')[0] @@ -816,28 +848,27 @@ pass self._add_result(t, n, s) - def check_manifest_architecture(self): - '''Check package architecture in manifest is valid''' + def _verify_architecture(self, my_dict, test_str): t = 'info' - n = 'manifest_architecture_valid' + n = '%s_architecture_valid' % test_str s = 'OK' - if 'architecture' not in self.manifest: + if 'architecture' not in my_dict: s = 'OK (architecture not specified)' self._add_result(t, n, s) return - manifest_archs_list = list(self.valid_control_architectures) - manifest_archs_list.remove("multi") + archs_list = list(self.valid_control_architectures) + archs_list.remove("multi") - if isinstance(self.manifest['architecture'], str) and \ - self.manifest['architecture'] not in manifest_archs_list: + if isinstance(my_dict['architecture'], str) and \ + my_dict['architecture'] not in archs_list: t = 'error' - s = "not a valid architecture: %s" % self.manifest['architecture'] - elif isinstance(self.manifest['architecture'], list): - manifest_archs_list.remove("all") + s = "not a valid architecture: %s" % my_dict['architecture'] + elif isinstance(my_dict['architecture'], list): + archs_list.remove("all") bad_archs = [] - for a in self.manifest['architecture']: - if a not in manifest_archs_list: + for a in my_dict['architecture']: + if a not in archs_list: bad_archs.append(a) if len(bad_archs) > 0: t = 'error' @@ -845,31 +876,188 @@ ",".join(bad_archs) self._add_result(t, n, s) - def check_icon(self): - '''Check icon()''' + def check_manifest_architecture(self): + '''Check package architecture in manifest is valid''' + self._verify_architecture(self.manifest, "manifest") + + def _verify_icon(self, my_dict, test_str): t = 'info' - n = 'icon_present' + n = '%s_icon_present' % test_str s = 'OK' - if 'icon' not in self.manifest: + if 'icon' not in my_dict: s = 'Skipped, optional icon not present' self._add_result(t, n, s) return self._add_result(t, n, s) t = 'info' - n = 'icon_empty' + n = '%s_icon_empty' % test_str + s = 'OK' + if len(my_dict['icon']) == 0: + t = 'error' + s = "icon entry is empty" + return + self._add_result(t, n, s) + + t = 'info' + n = '%s_icon_absolute_path' % test_str + s = 'OK' + if my_dict['icon'].startswith('/'): + t = 'error' + s = "icon entry '%s' should not specify absolute path" % \ + my_dict['icon'] + self._add_result(t, n, s) + + def check_icon(self): + '''Check icon()''' + self._verify_icon(self.manifest, "manifest") + + def check_snappy_name(self): + '''Check package name''' + if not self.is_snap: + return + + t = 'info' + n = 'snappy_name_valid' + s = 'OK' + if 'name' not in self.pkg_yaml: + t = 'error' + s = "could not find 'name' in yaml" + elif not self._verify_pkgname(self.pkg_yaml['name']): + t = 'error' + s = "malformed 'name': '%s'" % self.pkg_yaml['name'] + self._add_result(t, n, s) + + def check_snappy_version(self): + '''Check package version''' + if not self.is_snap: + return + + t = 'info' + n = 'snappy_version_valid' + s = 'OK' + if 'version' not in self.pkg_yaml: + t = 'error' + s = "could not find 'version' in yaml" + elif not self._verify_pkgversion(self.pkg_yaml['version']): + t = 'error' + s = "malformed 'version': '%s'" % self.pkg_yaml['version'] + self._add_result(t, n, s) + + def check_snappy_type(self): + '''Check type''' + if not self.is_snap: + return + + t = 'info' + n = 'snappy_type_valid' + s = 'OK' + if 'type' not in self.pkg_yaml: + s = 'OK (skip missing)' + elif self.pkg_yaml['type'] not in self.snappy_valid_types: + t = 'error' + s = "unknown 'type': '%s'" % self.pkg_yaml['type'] + self._add_result(t, n, s) + + def check_snappy_type_redflagged(self): + '''Check if snappy type is redflagged''' + if not self.is_snap: + return + + t = 'info' + n = 'snappy_type_redflag' + s = "OK" + manual_review = False + if 'type' not in self.pkg_yaml: + s = 'OK (skip missing)' + elif self.pkg_yaml['type'] in self.snappy_redflagged_types: + t = 'error' + s = "(MANUAL REVIEW) type '%s' not allowed" % self.pkg_yaml['type'] + manual_review = True + self._add_result(t, n, s, manual_review=manual_review) + + def check_snappy_vendor(self): + '''Check package vendor''' + if not self.is_snap: + return + + t = 'info' + n = 'snappy_vendor_valid' s = 'OK' - if len(self.manifest['icon']) == 0: + if 'vendor' not in self.pkg_yaml: + s = "OK (skip missing)" + elif not self._verify_maintainer(self.pkg_yaml['vendor']): t = 'error' - s = "icon manifest entry is empty" + s = "malformed 'vendor': '%s'" % self.pkg_yaml['vendor'] + self._add_result(t, n, s) + + def check_snappy_icon(self): + '''Check icon()''' + if not self.is_snap: return + + self._verify_icon(self.pkg_yaml, "package_yaml") + + def check_snappy_architecture(self): + '''Check package architecture in package.yaml is valid''' + if not self.is_snap: + return + + self._verify_architecture(self.pkg_yaml, "package yaml") + + def check_snappy_unknown_entries(self): + '''Check for any unknown fields''' + if not self.is_snap: + return + + t = 'info' + n = 'snappy_unknown' + s = 'OK' + unknown = [] + for f in self.pkg_yaml: + if f not in self.snappy_required + self.snappy_optional: + unknown.append(f) + if len(unknown) > 0: + t = 'warn' + s = "unknown entries in package.yaml: '%s'" % (",".join(unknown)) + obsoleted = ['maintainer', 'ports'] + tmp = list(set(unknown) & set(obsoleted)) + if len(tmp) > 0: + t = 'error' + s += " (%s obsoleted)" % ",".join(tmp) self._add_result(t, n, s) + def _extract_readme_md(self): + '''Extract meta/readme.md''' + contents = None + readme = os.path.join(self.unpack_dir, "meta/readme.md") + if os.path.exists(readme): + fh = open_file_read(readme) + contents = fh.read() + return contents + + def check_snappy_readme_md(self): + '''Check package architecture in package.yaml is valid''' + if not self.is_snap: + return + + contents = self._extract_readme_md() + t = 'info' - n = 'icon_absolute_path' + n = 'snappy_readme.md' s = 'OK' - if self.manifest['icon'].startswith('/'): + if contents is None: t = 'error' - s = "icon manifest entry '%s' should not specify absolute path" % \ - self.manifest['icon'] + s = 'meta/readme.md does not exist' + self._add_result(t, n, s) + return + self._add_result(t, n, s) + + t = 'info' + n = 'snappy_readme.md_length' + s = 'OK' + pkgname_base = self.pkg_yaml['name'].split('.')[0] + if len(contents) < len(pkgname_base): + t = 'warn' + s = "meta/readme.md is too short" self._add_result(t, n, s) diff -Nru click-reviewers-tools-0.21/bin/clickreviews/cr_security.py click-reviewers-tools-0.24/bin/clickreviews/cr_security.py --- click-reviewers-tools-0.21/bin/clickreviews/cr_security.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/cr_security.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ '''cr_security.py: click security checks''' # -# Copyright (C) 2013-2014 Canonical Ltd. +# Copyright (C) 2013-2015 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 @@ -16,7 +16,7 @@ from __future__ import print_function -from clickreviews.cr_common import ClickReview, error +from clickreviews.cr_common import ClickReview, error, open_file_read import clickreviews.cr_common as cr_common import clickreviews.apparmor_policy as apparmor_policy import json @@ -36,6 +36,13 @@ ['pay-ui'] peer_hooks[my_hook]['required'] = [] + my_hook2 = 'apparmor-profile' + peer_hooks[my_hook2] = dict() + # Basically, everything except frameworks + peer_hooks[my_hook2]['allowed'] = \ + ClickReview.service_allowed_peer_hooks + peer_hooks[my_hook2]['required'] = [] + ClickReview.__init__(self, fn, "security", peer_hooks=peer_hooks) local_copy = os.path.join(os.path.dirname(__file__), @@ -99,6 +106,10 @@ 'ubuntu-sdk-14.10': { 'policy_version': 1.2, }, + 'ubuntu-core-15.04': { + 'policy_vendor': 'ubuntu-snappy', + 'policy_version': 1.3, + }, } if overrides is None: overrides = {} @@ -118,6 +129,21 @@ self._extract_security_manifest(app) self.security_apps.append(app) + self.security_profiles = dict() + self.security_apps_profiles = [] + for app in self.manifest['hooks']: + if 'apparmor-profile' not in self.manifest['hooks'][app]: + # msg("Skipped missing apparmor hook for '%s'" % app) + continue + if not isinstance(self.manifest['hooks'][app]['apparmor-profile'], + str): + error("manifest malformed: hooks/%s/apparmor-profile is not " + "str" % app) + rel_fn = self.manifest['hooks'][app]['apparmor-profile'] + self.security_profiles[rel_fn] = \ + self._extract_security_profile(app) + self.security_apps_profiles.append(app) + def _override_framework_policies(self, overrides): # override major framework policies self.major_framework_policy.update(overrides) @@ -187,6 +213,38 @@ m = self.security_manifests[f] return (f, m) + def _extract_security_profile(self, app): + '''Extract security profile''' + d = self.manifest['hooks'][app]['apparmor-profile'] + fn = os.path.join(self.unpack_dir, d) + rel_fn = self.manifest['hooks'][app]['apparmor-profile'] + + if not os.path.exists(fn): + error("Could not find '%s'" % rel_fn) + + fh = open_file_read(fn) + contents = "" + for line in fh.readlines(): + contents += line + fh.close() + + # We could try to run this through apparmor_parser, but that is going + # to be system dependent (eg, a profile may reference features on a + # new parser and fail here on the local parser) + + return contents + + def _get_security_profile(self, app): + '''Get the security profile for app''' + if app not in self.manifest['hooks']: + error("Could not find '%s' in click manifest" % app) + elif 'apparmor-profile' not in self.manifest['hooks'][app]: + error("Could not find apparmor-profile hook for '%s' in click " + "manifest" % app) + f = self.manifest['hooks'][app]['apparmor-profile'] + p = self.security_profiles[f] + return (f, p) + def _get_policy_versions(self, vendor): '''Get the supported AppArmor policy versions''' if vendor not in self.aa_policy: @@ -267,6 +325,29 @@ s = "policy_vendor '%s' not found" % m['policy_vendor'] self._add_result(t, n, s) + t = 'info' + n = 'policy_vendor_matches_framework (%s)' % (f) + s = "OK" + if 'policy_vendor' in m: # policy_vendor is optional + found_major = False + for name, data in self.major_framework_policy.items(): + # TODO: use libclick when it is available + if not self.manifest['framework'].startswith(name): + continue + elif 'policy_vendor' not in data: + # when not specified, default to 'ubuntu' + data['policy_vendor'] = "ubuntu" + found_major = True + if m['policy_vendor'] != data['policy_vendor']: + t = 'error' + s = '%s != %s (%s)' % (str(m['policy_vendor']), + data['policy_vendor'], + self.manifest['framework']) + if not found_major: + t = 'error' + s = "Invalid framework '%s'" % self.manifest['framework'] + self._add_result(t, n, s) + def check_policy_version(self): '''Check policy version''' for app in sorted(self.security_apps): @@ -635,3 +716,23 @@ t = 'error' s = "missing required fields: %s" % ", ".join(not_found) self._add_result(t, n, s) + + def check_apparmor_profile(self): + '''Check apparmor-profile''' + for app in sorted(self.security_apps_profiles): + (f, p) = self._get_security_profile(app) + + for v in ['###VAR###', + '###PROFILEATTACH###', + '@{CLICK_DIR}', + '@{APP_PKGNAME}', + '@{APP_VERSION}', + ]: + t = 'info' + n = 'apparmor_profile_%s (%s)' % (v, f) + s = "OK" + if v not in p: + self._add_result('warn', n, + "could not find '%s' in profile" % v) + continue + self._add_result(t, n, s) diff -Nru click-reviewers-tools-0.21/bin/clickreviews/cr_systemd.py click-reviewers-tools-0.24/bin/clickreviews/cr_systemd.py --- click-reviewers-tools-0.21/bin/clickreviews/cr_systemd.py 1970-01-01 00:00:00.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/cr_systemd.py 2015-03-10 15:16:29.000000000 +0000 @@ -0,0 +1,305 @@ +'''cr_systemd.py: click systemd''' +# +# Copyright (C) 2015 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 . + +from __future__ import print_function + +from clickreviews.cr_common import ClickReview, error, open_file_read, msg +import yaml +import os + + +class ClickReviewSystemd(ClickReview): + '''This class represents click lint reviews''' + def __init__(self, fn): + peer_hooks = dict() + my_hook = 'snappy-systemd' + peer_hooks[my_hook] = dict() + peer_hooks[my_hook]['required'] = ["apparmor"] + peer_hooks[my_hook]['allowed'] = peer_hooks[my_hook]['required'] + + ClickReview.__init__(self, fn, "snappy-systemd", peer_hooks=peer_hooks) + + # snappy-systemd currently only allows specifying: + # - start (required) + # - description (required) + # - stop + # - poststop + # - stop-timeout + self.required_keys = ['start', 'description'] + self.optional_keys = ['stop', 'poststop', 'stop-timeout'] + + self.systemd_files = dict() # click-show-files and tests + self.systemd = dict() + for app in self.manifest['hooks']: + if 'snappy-systemd' not in self.manifest['hooks'][app]: + # msg("Skipped missing systemd hook for '%s'" % app) + continue + if not isinstance(self.manifest['hooks'][app]['snappy-systemd'], + str): + error("manifest malformed: hooks/%s/snappy-systemd is not str" + % app) + (full_fn, jd) = self._extract_systemd(app) + self.systemd_files[app] = full_fn + self.systemd[app] = jd + + def _extract_systemd(self, app): + '''Get systemd yaml''' + u = self.manifest['hooks'][app]['snappy-systemd'] + fn = os.path.join(self.unpack_dir, u) + + bn = os.path.basename(fn) + if not os.path.exists(fn): + error("Could not find '%s'" % bn) + + fh = open_file_read(fn) + contents = "" + for line in fh.readlines(): + contents += line + fh.close() + + try: + yd = yaml.safe_load(contents) + except Exception as e: + error("snappy-systemd yaml unparseable: %s (%s):\n%s" % (bn, + str(e), contents)) + + if not isinstance(yd, dict): + error("snappy-systemd yaml is malformed: %s:\n%s" % (bn, contents)) + + return (fn, yd) + + def _verify_required(self, my_dict, test_str): + for app in sorted(my_dict): + f = os.path.basename(self.systemd_files[app]) + for r in self.required_keys: + found = False + t = 'info' + n = '%s_required_key_%s_%s' % (test_str, r, f) + s = "OK" + if r in my_dict[app]: + if not isinstance(my_dict[app][r], str): + t = 'error' + s = "'%s' is not a string" % r + elif my_dict[app][r] == "": + t = 'error' + s = "'%s' is empty" % r + else: + found = True + if not found and t != 'error': + t = 'error' + s = "Missing required field '%s'" % r + self._add_result(t, n, s) + + def check_required(self): + '''Check snappy-systemd required fields''' + self._verify_required(self.systemd, 'hook') + + def check_snappy_required(self): + '''Check for package.yaml required fields''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_required(self._create_dict(self.pkg_yaml['services']), + 'package_yaml') + + def _verify_optional(self, my_dict, test_str): + for app in sorted(my_dict): + f = os.path.basename(self.systemd_files[app]) + for o in self.optional_keys: + found = False + t = 'info' + n = '%s_optional_key_%s_%s' % (test_str, o, f) + s = "OK" + if o in my_dict[app]: + if o == 'stop-timeout' and \ + not isinstance(my_dict[app][o], int): + t = 'error' + s = "'%s' is not an integer" % o + elif not isinstance(my_dict[app][o], str): + t = 'error' + s = "'%s' is not a string" % o + elif my_dict[app][o] == "": + t = 'error' + s = "'%s' is empty" % o + else: + found = True + if not found and t != 'error': + s = "OK (skip missing)" + self._add_result(t, n, s) + + def check_optional(self): + '''Check snappy-systemd optional fields''' + self._verify_optional(self.systemd, 'hook') + + def check_snappy_optional(self): + '''Check snappy packate.yaml optional fields''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_optional(self._create_dict(self.pkg_yaml['services']), + 'package_yaml') + + def _verify_unknown(self, my_dict, test_str): + for app in sorted(my_dict): + f = os.path.basename(self.systemd_files[app]) + unknown = [] + t = 'info' + n = '%s_unknown_key_%s' % (test_str, f) + s = "OK" + + for f in my_dict[app].keys(): + if f not in self.required_keys and \ + f not in self.optional_keys: + unknown.append(f) + + if len(unknown) == 1: + t = 'warn' + s = "Unknown field '%s'" % unknown[0] + elif len(unknown) > 1: + t = 'warn' + s = "Unknown fields '%s'" % ", ".join(unknown) + self._add_result(t, n, s) + + def check_unknown(self): + '''Check snappy-systemd unknown fields''' + self._verify_unknown(self.systemd, 'hook') + + def check_snappy_unknown(self): + '''Check snappy package.yaml unknown fields''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_unknown(self._create_dict(self.pkg_yaml['services']), + 'package_yaml') + + def _verify_service_description(self, my_dict, test_str): + '''Check snappy-systemd description''' + for app in sorted(my_dict): + f = os.path.basename(self.systemd_files[app]) + t = 'info' + n = '%s_description_present_%s' % (test_str, f) + s = 'OK' + if 'description' not in my_dict[app]: + s = 'required description field not specified' + self._add_result('error', n, s) + return + self._add_result(t, n, s) + + t = 'info' + n = '%s_description_empty_%s' % (test_str, f) + s = 'OK' + if len(my_dict[app]['description']) == 0: + t = 'error' + s = "description is empty" + self._add_result(t, n, s) + + def check_service_description(self): + '''Check snappy-systemd description''' + self._verify_service_description(self.systemd, 'hook') + + def check_snappy_service_description(self): + '''Check snappy package.yaml description''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_service_description(self._create_dict( + self.pkg_yaml['services']), + 'package_yaml') + + def _verify_entry(self, my_dict, d, test_str): + for app in sorted(my_dict): + if d not in my_dict[app]: + continue + f = os.path.basename(self.systemd_files[app]) + + t = 'info' + n = '%s_%s_empty_%s' % (test_str, d, f) + s = 'OK' + if len(my_dict[app][d]) == 0: + t = 'error' + s = "%s entry is empty" % d + self._add_result(t, n, s) + continue + self._add_result(t, n, s) + + t = 'info' + n = '%s_%s_absolute_path_%s' % (test_str, d, f) + s = 'OK' + if my_dict[app][d].startswith('/'): + t = 'error' + s = "'%s' should not specify absolute path" % my_dict[app][d] + self._add_result(t, n, s) + + def check_service_start(self): + '''Check snappy-systemd start''' + self._verify_entry(self.systemd, 'start', 'hook') + + def check_snappy_service_start(self): + '''Check snappy package.yaml start''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_entry(self._create_dict(self.pkg_yaml['services']), + 'start', 'package_yaml') + + def check_service_stop(self): + '''Check snappy-systemd stop''' + self._verify_entry(self.systemd, 'stop', 'hook') + + def check_snappy_service_stop(self): + '''Check snappy package.yaml stop''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_entry(self._create_dict(self.pkg_yaml['services']), + 'stop', 'package_yaml') + + def check_service_poststop(self): + '''Check snappy-systemd poststop''' + self._verify_entry(self.systemd, 'poststop', 'hook') + + def check_snappy_service_poststop(self): + '''Check snappy package.yaml poststop''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_entry(self._create_dict(self.pkg_yaml['services']), + 'poststop', 'package_yaml') + + def _verify_service_stop_timeout(self, my_dict, test_str): + for app in sorted(my_dict): + f = os.path.basename(self.systemd_files[app]) + t = 'info' + n = '%s_stop_timeout_%s' % (test_str, f) + s = "OK" + + if 'stop-timeout' not in my_dict[app]: + s = "OK (skip missing)" + elif not isinstance(my_dict[app]['stop-timeout'], int): + t = 'error' + s = 'stop-timeout is not an integer' + elif my_dict[app]['stop-timeout'] < 0 or \ + my_dict[app]['stop-timeout'] > 60: + t = 'error' + s = "stop-timeout '%d' out of range (0-60)" % \ + my_dict[app]['stop-timeout'] + + self._add_result(t, n, s) + + def check_service_stop_timeout(self): + '''Check snappy-systemd''' + self._verify_service_stop_timeout(self.systemd, 'hook') + + def check_snappy_service_stop_timeout(self): + '''Check snappy package.yaml top-timeout''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_service_stop_timeout(self._create_dict( + self.pkg_yaml['services']), + 'package_yaml') diff -Nru click-reviewers-tools-0.21/bin/clickreviews/cr_tests.py click-reviewers-tools-0.24/bin/clickreviews/cr_tests.py --- click-reviewers-tools-0.21/bin/clickreviews/cr_tests.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/cr_tests.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ '''cr_tests.py: common setup and tests for test modules''' # -# Copyright (C) 2013-2014 Canonical Ltd. +# Copyright (C) 2013-2015 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 @@ -19,6 +19,7 @@ import os import tempfile from xdg.DesktopEntry import DesktopEntry +import yaml from unittest.mock import patch from unittest import TestCase @@ -29,7 +30,10 @@ # These should be set in the test cases TEST_CONTROL = "" TEST_MANIFEST = "" +TEST_PKG_YAML = "" +TEST_README_MD = "" TEST_SECURITY = dict() +TEST_SECURITY_PROFILES = dict() TEST_DESKTOP = dict() TEST_WEBAPP_MANIFESTS = dict() TEST_URLS = dict() @@ -63,6 +67,16 @@ return io.StringIO(TEST_MANIFEST) +def _extract_package_yaml(self): + '''Pretend we read the package.yaml file''' + return io.StringIO(TEST_PKG_YAML) + + +def _extract_readme_md(self): + '''Pretend we read the meta/readme.md file''' + return TEST_README_MD + + def _extract_click_frameworks(self): '''Pretend we enumerated the click frameworks''' return ["ubuntu-sdk-13.10", @@ -95,6 +109,16 @@ return ("%s.apparmor" % app, json.loads(TEST_SECURITY[app])) +def _extract_security_profile(self, app): + '''Pretend we read the security profile''' + return io.StringIO(TEST_SECURITY_PROFILES[app]) + + +def _get_security_profile(self, app): + '''Pretend we read the security profile''' + return ("%s.profile" % app, TEST_SECURITY_PROFILES[app]) + + def _get_security_supported_policy_versions(self): '''Pretend we read the contens of /usr/share/apparmor/easyprof''' return [1.0, 1.1, 1.2, 1.3] @@ -176,7 +200,7 @@ def _extract_systemd(self, app): '''Pretend we found the systemd file''' - return ("%s.click-service" % app, TEST_SNAPPY_SYSTEMD[app]) + return ("%s.snappy-systemd" % app, TEST_SNAPPY_SYSTEMD[app]) # http://docs.python.org/3.4/library/unittest.mock-examples.html @@ -201,6 +225,9 @@ 'clickreviews.cr_common.ClickReview._extract_manifest_file', _extract_manifest_file)) patches.append(patch( + 'clickreviews.cr_common.ClickReview._extract_package_yaml', + _extract_package_yaml)) +patches.append(patch( 'clickreviews.cr_common.ClickReview._extract_click_frameworks', _extract_click_frameworks)) patches.append(patch('clickreviews.cr_common.unpack_click', _mock_func)) @@ -219,6 +246,9 @@ patches.append(patch( 'clickreviews.cr_lint.ClickReview._list_all_compiled_binaries', _mock_func)) +patches.append(patch( + 'clickreviews.cr_lint.ClickReviewLint._extract_readme_md', + _extract_readme_md)) # security overrides patches.append(patch( @@ -227,6 +257,12 @@ patches.append(patch( 'clickreviews.cr_security.ClickReviewSecurity._get_security_manifest', _get_security_manifest)) +patches.append(patch( + 'clickreviews.cr_security.ClickReviewSecurity._extract_security_profile', + _extract_security_profile)) +patches.append(patch( + 'clickreviews.cr_security.ClickReviewSecurity._get_security_profile', + _get_security_profile)) # desktop overrides patches.append(patch( @@ -339,8 +375,19 @@ "%s.url-dispatcher" % self.default_appname self._update_test_manifest() + self.test_pkg_yaml = dict() + self.set_test_pkg_yaml("name", self.test_control['Package']) + self.set_test_pkg_yaml("version", self.test_control['Version']) + self.set_test_pkg_yaml("architecture", + self.test_control['Architecture']) + self._update_test_pkg_yaml() + + self.test_readme_md = self.test_control['Description'] + self._update_test_readme_md() + # hooks self.test_security_manifests = dict() + self.test_security_profiles = dict() self.test_desktop_files = dict() self.test_url_dispatcher = dict() self.test_scopes = dict() @@ -400,7 +447,11 @@ # Reset to no systemd entries in manifest self.set_test_systemd(app, None, None) + # Reset to no security profiles + self.set_test_security_profile(app, None) + self._update_test_security_manifests() + self._update_test_security_profiles() self._update_test_desktop_files() self._update_test_url_dispatcher() self._update_test_scopes() @@ -431,12 +482,31 @@ global TEST_MANIFEST TEST_MANIFEST = json.dumps(self.test_manifest) + def _update_test_pkg_yaml(self): + global TEST_PKG_YAML + TEST_PKG_YAML = yaml.dump(self.test_pkg_yaml, + default_flow_style=False, + indent=4) + + def _update_test_readme_md(self): + global TEST_README_MD + TEST_README_MD = self.test_readme_md + def _update_test_security_manifests(self): global TEST_SECURITY TEST_SECURITY = dict() for app in self.test_security_manifests.keys(): TEST_SECURITY[app] = json.dumps(self.test_security_manifests[app]) + def _update_test_security_profiles(self): + global TEST_SECURITY_PROFILES + TEST_SECURITY_PROFILES = dict() + for app in self.test_security_profiles.keys(): + TEST_SECURITY_PROFILES[app] = self.test_security_profiles[app] + self.test_manifest["hooks"][app]["apparmor-profile"] = \ + "%s.profile" % app + self._update_test_manifest() + def _update_test_desktop_files(self): global TEST_DESKTOP TEST_DESKTOP = dict() @@ -552,6 +622,9 @@ TEST_SNAPPY_SYSTEMD = dict() for app in self.test_systemd.keys(): TEST_SNAPPY_SYSTEMD[app] = self.test_systemd[app] + self.test_manifest["hooks"][app]["snappy-systemd"] = \ + "%s.snappy-systemd" % app + self._update_test_manifest() def _update_test_name(self): self.test_name = "%s_%s_%s.click" % (self.test_control['Package'], @@ -619,6 +692,24 @@ self.test_manifest[key] = value self._update_test_manifest() + def set_test_pkg_yaml(self, key, value): + '''Set key in meta/package.yaml to value. If value is None, remove + key''' + if value is None: + if key in self.test_pkg_yaml: + self.test_pkg_yaml.pop(key, None) + else: + self.test_pkg_yaml[key] = value + self._update_test_pkg_yaml() + + def set_test_readme_md(self, contents): + '''Set contents of meta/readme.md''' + if contents is None: + self.test_readme_md = None + else: + self.test_readme_md = contents + self._update_test_readme_md() + def set_test_security_manifest(self, app, key, value): '''Set key in security manifest to value. If value is None, remove key''' @@ -632,6 +723,17 @@ self.test_security_manifests[app][key] = value self._update_test_security_manifests() + def set_test_security_profile(self, app, policy): + '''Set policy in security profile''' + if policy is None: + if app in self.test_security_profiles: + self.test_security_profiles.pop(app) + else: + if app not in self.test_security_profiles: + self.test_security_profiles[app] = dict() + self.test_security_profiles[app] = policy + self._update_test_security_profiles() + def set_test_desktop(self, app, key, value, no_update=False): '''Set key in desktop file to value. If value is None, remove key''' if app not in self.test_desktop_files: @@ -774,17 +876,21 @@ self.test_framework[app][key] = value self._update_test_framework() - def set_test_systemd(self, app, key, value, append=False): - '''Set systemd entries. If value is None, remove''' - if app not in self.test_systemd: - self.test_systemd[app] = [] - - if value is None: - self.test_systemd[app] = [] + def set_test_systemd(self, app, key, value): + '''Set systemd entries. If key is None, remove hook, if value is None, + remove key''' + if key is None: + if app in self.test_systemd: + self.test_systemd.pop(app) else: - if not append: - self.test_systemd[app] = [] - self.test_systemd[app].append({key: value}) + if app not in self.test_systemd: + self.test_systemd[app] = {} + + if value is None: + if key in self.test_systemd[app]: + del(self.test_systemd[app][key]) + else: + self.test_systemd[app][key] = value self._update_test_systemd() def setUp(self): @@ -799,8 +905,14 @@ TEST_CONTROL = "" global TEST_MANIFEST TEST_MANIFEST = "" + global TEST_PKG_YAML + TEST_PKG_YAML = "" + global TEST_README_MD + TEST_README_MD = "" global TEST_SECURITY TEST_SECURITY = dict() + global TEST_SECURITY_PROFILES + TEST_SECURITY_PROFILES = dict() global TEST_DESKTOP TEST_DESKTOP = dict() global TEST_URLS diff -Nru click-reviewers-tools-0.21/bin/clickreviews/tests/test_cr_bin_path.py click-reviewers-tools-0.24/bin/clickreviews/tests/test_cr_bin_path.py --- click-reviewers-tools-0.21/bin/clickreviews/tests/test_cr_bin_path.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/tests/test_cr_bin_path.py 2015-03-10 15:16:29.000000000 +0000 @@ -26,6 +26,15 @@ cr_tests.mock_patch() super() + def _set_binary(self, key, value, name=None): + d = dict() + if name is None: + d['name'] = 'foo' + else: + d['name'] = name + d[key] = value + self.set_test_pkg_yaml("binaries", [d]) + def test_check_path(self): '''Test check_path()''' self.set_test_bin_path(self.default_appname, "bin/foo.exe") @@ -57,7 +66,6 @@ tmp["bin-path"] = "usr/bin/foo" # add any required peer hooks - tmp["snappy-systemd"] = "foo.systemd" tmp["apparmor"] = "foo.apparmor" c.manifest["hooks"][self.default_appname] = tmp @@ -116,3 +124,30 @@ r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) + + def test_check_binary_description(self): + '''Test check_binary_description()''' + self._set_binary("description", "some description") + c = ClickReviewBinPath(self.test_name) + c.check_binary_description() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_binary_description_unspecified(self): + '''Test check_binary_description() - unspecified''' + self._set_binary("name", "foo") + c = ClickReviewBinPath(self.test_name) + c.check_binary_description() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_binary_description_empty(self): + '''Test check_binary_description() - empty''' + self._set_binary("description", "") + c = ClickReviewBinPath(self.test_name) + c.check_binary_description() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) diff -Nru click-reviewers-tools-0.21/bin/clickreviews/tests/test_cr_desktop.py click-reviewers-tools-0.24/bin/clickreviews/tests/test_cr_desktop.py --- click-reviewers-tools-0.21/bin/clickreviews/tests/test_cr_desktop.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/tests/test_cr_desktop.py 2015-03-10 15:16:29.000000000 +0000 @@ -224,7 +224,7 @@ ex) c.check_desktop_exec_webapp_args() r = c.click_report - expected_counts = {'info': None, 'warn': 0, 'error': 1} + expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_missing_exec(self): diff -Nru click-reviewers-tools-0.21/bin/clickreviews/tests/test_cr_lint.py click-reviewers-tools-0.24/bin/clickreviews/tests/test_cr_lint.py --- click-reviewers-tools-0.21/bin/clickreviews/tests/test_cr_lint.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/tests/test_cr_lint.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ '''test_cr_lint.py: tests for the cr_lint module''' # -# Copyright (C) 2013-2014 Canonical Ltd. +# Copyright (C) 2013-2015 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 @@ -172,7 +172,7 @@ c.pkg_bin_files = [] c.check_architecture_specified_needed() r = c.click_report - expected_counts = {'info': None, 'warn': 0, 'error': 1} + expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_specified_needed2(self): @@ -780,6 +780,17 @@ expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) + def test_check_hooks_missing_apparmor_with_apparmor_profile(self): + '''Test check_hooks() - missing apparmor with apparmor-profile''' + self.set_test_manifest("framework", "ubuntu-sdk-13.10") + c = ClickReviewLint(self.test_name) + del c.manifest['hooks'][self.default_appname]['apparmor'] + c.manifest['hooks'][self.default_appname]['apparmor-profile'] = 'foo' + c.check_hooks() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + def test_check_hooks_has_desktop_and_scope(self): '''Test check_hooks() - desktop with scope''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") @@ -809,8 +820,8 @@ expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) - def test_check_hooks_redflagged(self): - '''Test check_hooks_redflagged()''' + def test_check_hooks_redflagged_payui(self): + '''Test check_hooks_redflagged() - pay-ui''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.manifest['hooks'][self.default_appname]["pay-ui"] = "foo" @@ -819,3 +830,499 @@ expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) self.check_manual_review(r, 'lint_hooks_redflag_test-app') + + def test_check_hooks_redflagged_apparmor_profile(self): + '''Test check_hooks_redflagged() - apparmor-profile''' + self.set_test_manifest("framework", "ubuntu-sdk-13.10") + c = ClickReviewLint(self.test_name) + c.manifest['hooks'][self.default_appname]["apparmor-profile"] = "foo" + c.check_hooks_redflagged() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + self.check_manual_review(r, 'lint_hooks_redflag_test-app') + + def test_snappy_name1(self): + '''Test check_snappy_name - toplevel''' + self.set_test_pkg_yaml("name", "foo") + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_name2(self): + '''Test check_snappy_name - flat''' + self.set_test_pkg_yaml("name", "foo.bar") + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_name3(self): + '''Test check_snappy_name - reverse domain''' + self.set_test_pkg_yaml("name", "com.ubuntu.develeper.baz.foo") + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_name_bad(self): + '''Test check_snappy_name - bad''' + self.set_test_pkg_yaml("name", "foo?bar") + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_name_bad2(self): + '''Test check_snappy_name - empty''' + self.set_test_pkg_yaml("name", "") + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_name_bad3(self): + '''Test check_snappy_name - list''' + self.set_test_pkg_yaml("name", []) + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_name_bad4(self): + '''Test check_snappy_name - dict''' + self.set_test_pkg_yaml("name", {}) + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_version1(self): + '''Test check_snappy_version - integer''' + self.set_test_pkg_yaml("version", 1) + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_version2(self): + '''Test check_snappy_version - float''' + self.set_test_pkg_yaml("version", 1.0) + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_version3(self): + '''Test check_snappy_version - MAJOR.MINOR.MICRO''' + self.set_test_pkg_yaml("version", "1.0.1") + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_version4(self): + '''Test check_snappy_version - str''' + self.set_test_pkg_yaml("version", "1.0a") + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_version5(self): + '''Test check_snappy_version - alpha''' + self.set_test_pkg_yaml("version", "a.b") + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_version_bad(self): + '''Test check_snappy_version - bad''' + self.set_test_pkg_yaml("version", "foo?bar") + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_version_bad2(self): + '''Test check_snappy_version - empty''' + self.set_test_pkg_yaml("version", "") + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_version_bad3(self): + '''Test check_snappy_version - list''' + self.set_test_pkg_yaml("version", []) + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_version_bad4(self): + '''Test check_snappy_version - dict''' + self.set_test_pkg_yaml("version", {}) + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_type(self): + '''Test check_snappy_type - unspecified''' + self.set_test_pkg_yaml("type", None) + c = ClickReviewLint(self.test_name) + c.check_snappy_type() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_type_app(self): + '''Test check_snappy_type - app''' + self.set_test_pkg_yaml("type", "app") + c = ClickReviewLint(self.test_name) + c.check_snappy_type() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_type_framework(self): + '''Test check_snappy_type - framework''' + self.set_test_pkg_yaml("type", "framework") + c = ClickReviewLint(self.test_name) + c.check_snappy_type() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_type_framework_policy(self): + '''Test check_snappy_type - framework-policy''' + self.set_test_pkg_yaml("type", "framework-policy") + c = ClickReviewLint(self.test_name) + c.check_snappy_type() + r = c.click_report + # TODO: update this when 'framework-policy' is implemented + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_type_oem(self): + '''Test check_snappy_type - oem''' + self.set_test_pkg_yaml("type", "oem") + c = ClickReviewLint(self.test_name) + c.check_snappy_type() + r = c.click_report + # TODO: update this when 'oem' is implemented + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_type_redflagged(self): + '''Test check_snappy_type_redflagged - unspecified''' + self.set_test_pkg_yaml("type", None) + c = ClickReviewLint(self.test_name) + c.check_snappy_type_redflagged() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_type_redflagged_app(self): + '''Test check_snappy_type_redflagged - app''' + self.set_test_pkg_yaml("type", "app") + c = ClickReviewLint(self.test_name) + c.check_snappy_type_redflagged() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_type_redflagged_framework(self): + '''Test check_snappy_type_redflagged - framework''' + self.set_test_pkg_yaml("type", "framework") + c = ClickReviewLint(self.test_name) + c.check_snappy_type_redflagged() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_vendor(self): + '''Test check_snappy_vendor''' + self.set_test_pkg_yaml("vendor", "Foo Bar ") + c = ClickReviewLint(self.test_name) + c.check_snappy_vendor() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_vendor_missing(self): + '''Test check_snappy_vendor - missing''' + self.set_test_pkg_yaml("vendor", None) + c = ClickReviewLint(self.test_name) + c.check_snappy_vendor() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_vendor_empty(self): + '''Test check_snappy_vendor - empty''' + self.set_test_pkg_yaml("vendor", "") + c = ClickReviewLint(self.test_name) + c.check_snappy_vendor() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_vendor_bad(self): + '''Test check_snappy_vendor - bad''' + self.set_test_pkg_yaml("vendor", "Foo Bar") + c = ClickReviewLint(self.test_name) + c.check_snappy_vendor() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_icon(self): + '''Test check_snappy_icon()''' + self.set_test_pkg_yaml("icon", "someicon") + c = ClickReviewLint(self.test_name) + c.check_snappy_icon() + r = c.click_report + expected_counts = {'info': 3, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_icon_unspecified(self): + '''Test check_snappy_icon() - unspecified''' + self.set_test_pkg_yaml("icon", None) + c = ClickReviewLint(self.test_name) + c.check_snappy_icon() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_icon_empty(self): + '''Test check_snappy_icon() - empty''' + self.set_test_pkg_yaml("icon", "") + c = ClickReviewLint(self.test_name) + c.check_snappy_icon() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_icon_absolute_path(self): + '''Test check_snappy_icon() - absolute path''' + self.set_test_pkg_yaml("icon", "/foo/bar/someicon") + c = ClickReviewLint(self.test_name) + c.check_snappy_icon() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_missing_arch(self): + '''Test check_snappy_architecture() (missing)''' + self.set_test_pkg_yaml("architecture", None) + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_arch_all(self): + '''Test check_snappy_architecture() (all)''' + self.set_test_pkg_yaml("architecture", "all") + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_arch_single_armhf(self): + '''Test check_snappy_architecture() (single arch, armhf)''' + self.set_test_pkg_yaml("architecture", "armhf") + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_arch_single_i386(self): + '''Test check_snappy_architecture() (single arch, i386)''' + self.set_test_pkg_yaml("architecture", "i386") + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_arch_single_amd64(self): + '''Test check_snappy_architecture() (single arch, amd64)''' + self.set_test_pkg_yaml("architecture", "amd64") + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_arch_single_nonexistent(self): + '''Test check_snappy_architecture() (single nonexistent arch)''' + self.set_test_pkg_yaml("architecture", "nonexistent") + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_arch_single_multi(self): + '''Test check_snappy_architecture() (single arch: invalid multi)''' + self.set_test_pkg_yaml("architecture", "multi") + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_valid_arch_multi(self): + '''Test check_snappy_architecture() (valid multi)''' + arch = "multi" + self.set_test_pkg_yaml("architecture", ["armhf"]) + self.set_test_control("Architecture", arch) + test_name = "%s_%s_%s.snap" % (self.test_control['Package'], + self.test_control['Version'], + arch) + c = ClickReviewLint(test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_valid_arch_multi2(self): + '''Test check_snappy_architecture() (valid multi2)''' + arch = "multi" + self.set_test_pkg_yaml("architecture", ["armhf", "i386"]) + self.set_test_control("Architecture", arch) + test_name = "%s_%s_%s.snap" % (self.test_control['Package'], + self.test_control['Version'], + arch) + c = ClickReviewLint(test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_invalid_arch_multi_nonexistent(self): + '''Test check_snappy_architecture() (invalid multi)''' + arch = "multi" + self.set_test_pkg_yaml("architecture", ["armhf", "nonexistent"]) + self.set_test_control("Architecture", arch) + test_name = "%s_%s_%s.snap" % (self.test_control['Package'], + self.test_control['Version'], + arch) + c = ClickReviewLint(test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_invalid_arch_multi_all(self): + '''Test check_snappy_architecture() (invalid all)''' + arch = "multi" + self.set_test_pkg_yaml("architecture", ["armhf", "all"]) + self.set_test_control("Architecture", arch) + test_name = "%s_%s_%s.snap" % (self.test_control['Package'], + self.test_control['Version'], + arch) + c = ClickReviewLint(test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_invalid_arch_multi_multi(self): + '''Test check_snappy_architecture() (invalid multi)''' + arch = "multi" + self.set_test_pkg_yaml("architecture", ["multi", "armhf"]) + self.set_test_control("Architecture", arch) + test_name = "%s_%s_%s.snap" % (self.test_control['Package'], + self.test_control['Version'], + arch) + c = ClickReviewLint(test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_unknown_entries(self): + '''Test check_snappy_unknown_entries - none''' + self.set_test_pkg_yaml("name", "foo") + c = ClickReviewLint(self.test_name) + c.check_snappy_unknown_entries() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_unknown_entries2(self): + '''Test check_snappy_unknown_entries - one''' + self.set_test_pkg_yaml("nonexistent", "bar") + c = ClickReviewLint(self.test_name) + c.check_snappy_unknown_entries() + r = c.click_report + expected_counts = {'info': 0, 'warn': 1, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_unknown_obsoleted(self): + '''Test check_snappy_unknown_entries - obsoleted''' + self.set_test_pkg_yaml("maintainer", "bar") + c = ClickReviewLint(self.test_name) + c.check_snappy_unknown_entries() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + # Lets check that the right warning is triggering + m = r['error']['lint_snappy_unknown']['text'] + self.assertIn("unknown entries in package.yaml: 'maintainer' " + "(maintainer obsoleted)", m) + + def test_check_snappy_readme_md(self): + '''Test check_snappy_readme_md()''' + self.set_test_pkg_yaml("name", self.test_name.split('_')[0]) + self.set_test_readme_md("%s - some description" % + self.test_name.split('_')[0]) + c = ClickReviewLint(self.test_name) + c.check_snappy_readme_md() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_readme_md_bad(self): + '''Test check_snappy_readme_md() - short''' + self.set_test_pkg_yaml("name", "prettylong.name") + self.set_test_readme_md("abc") + c = ClickReviewLint(self.test_name) + c.check_snappy_readme_md() + r = c.click_report + expected_counts = {'info': 1, 'warn': 1, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_readme_md_bad2(self): + '''Test check_snappy_readme_md() - missing''' + self.set_test_pkg_yaml("name", self.test_name.split('_')[0]) + self.set_test_readme_md(None) + c = ClickReviewLint(self.test_name) + c.check_snappy_readme_md() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) diff -Nru click-reviewers-tools-0.21/bin/clickreviews/tests/test_cr_security.py click-reviewers-tools-0.24/bin/clickreviews/tests/test_cr_security.py --- click-reviewers-tools-0.21/bin/clickreviews/tests/test_cr_security.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/tests/test_cr_security.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ '''test_cr_security.py: tests for the cr_security module''' # -# Copyright (C) 2013-2014 Canonical Ltd. +# Copyright (C) 2013-2015 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 @@ -244,7 +244,7 @@ c = ClickReviewSecurity(self.test_name) c.check_policy_vendor() report = c.click_report - expected_counts = {'info': 1, 'warn': 0, 'error': 0} + expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_ubuntu(self): @@ -254,11 +254,12 @@ "policy_vendor", "ubuntu") c.check_policy_vendor() report = c.click_report - expected_counts = {'info': 1, 'warn': 0, 'error': 0} + expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_ubuntu_snappy(self): '''Test check_policy_vendor() - ubuntu-snappy''' + self.set_test_manifest("framework", "ubuntu-core-15.04") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu-snappy") @@ -266,17 +267,112 @@ "policy_version", 1.3) c.check_policy_vendor() report = c.click_report - expected_counts = {'info': 1, 'warn': 0, 'error': 0} + expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_nonexistent(self): '''Test check_policy_vendor() - nonexistent''' + self.set_test_manifest("framework", "nonexistent") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, - "policy_vendor", "nonexistent") + "policy_vendor", "ubuntu") c.check_policy_vendor() report = c.click_report - expected_counts = {'info': 0, 'warn': 0, 'error': 1} + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(report, expected_counts) + + def test_check_policy_vendor_framework(self): + '''Test check_policy_vendor() - matching framework''' + tmp = ClickReviewSecurity(self.test_name) + # for each installed framework on the system, verify that the policy + # matches the framework + for f in tmp.valid_frameworks: + self.set_test_manifest("framework", f) + policy_vendor = "ubuntu" + for k in tmp.major_framework_policy.keys(): + if f.startswith(k): + if 'policy_vendor' not in tmp.major_framework_policy[k]: + policy_vendor = 'ubuntu' + else: + policy_vendor = tmp.major_framework_policy[k]['policy_vendor'] + self.set_test_security_manifest(self.default_appname, + "policy_vendor", + policy_vendor) + c = ClickReviewSecurity(self.test_name) + c.check_policy_vendor() + report = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(report, expected_counts) + + def test_check_policy_vendor_framework_unmatch1(self): + '''Test check_policy_vendor() - unmatching framework''' + self.set_test_security_manifest(self.default_appname, + "policy_vendor", "ubuntu-snappy") + c = ClickReviewSecurity(self.test_name) + c.check_policy_vendor() + report = c.click_report + + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(report, expected_counts) + + expected = dict() + expected['info'] = dict() + expected['warn'] = dict() + expected['error'] = dict() + expected['error']["security_policy_vendor_matches_framework (%s)" % + self.default_security_json] = \ + {"text": "ubuntu-snappy != ubuntu (ubuntu-sdk-13.10)"} + self.check_results(report, expected=expected) + + def test_check_policy_vendor_framework_unmatch2(self): + '''Test check_policy_vendor() - unmatching framework - nonexistent''' + self.set_test_manifest("framework", "nonexistent") + self.set_test_security_manifest(self.default_appname, + "policy_vendor", "ubuntu") + c = ClickReviewSecurity(self.test_name) + c.check_policy_vendor() + report = c.click_report + + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(report, expected_counts) + + expected = dict() + expected['info'] = dict() + expected['warn'] = dict() + expected['error'] = dict() + expected['error']["security_policy_vendor_matches_framework (%s)" % + self.default_security_json] = \ + {"text": "Invalid framework 'nonexistent'"} + self.check_results(report, expected=expected) + + def test_check_policy_vendor_framework_with_overrides(self): + '''Test check_policy_vendor() - override framework (nonexistent)''' + self.set_test_manifest("framework", "nonexistent") + self.set_test_security_manifest(self.default_appname, + "policy_vendor", "ubuntu") + overrides = {'framework': {'nonexistent': {'state': 'available', + 'policy_vendor': 'ubuntu', + 'policy_version': 1.2}}} + c = ClickReviewSecurity(self.test_name, overrides=overrides) + c.check_policy_vendor() + report = c.click_report + + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(report, expected_counts) + + def test_check_policy_vendor_framework_with_malformed_overrides(self): + '''Test check_policy_vendor() - incorrectly override framework''' + self.set_test_manifest("framework", "nonexistent") + self.set_test_security_manifest(self.default_appname, + "policy_vendor", "ubuntu") + overrides = {'nonexistent': {'state': 'available', + 'policy_vendor': 'ubuntu', + 'policy_version': 1.2}} + c = ClickReviewSecurity(self.test_name, overrides=overrides) + c.check_policy_vendor() + report = c.click_report + + expected_counts = {'info': 1, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_template_unspecified(self): @@ -838,8 +934,8 @@ # do the test c.check_peer_hooks() r = c.click_report - # We should end up with 2 info - expected_counts = {'info': 2, 'warn': 0, 'error': 0} + # We should end up with 4 info + expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_disallowed(self): @@ -864,6 +960,71 @@ expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) + def test_check_peer_hooks_disallowed_apparmor_profile(self): + '''Test check_peer_hooks() - disallowed (apparmor-profile)''' + c = ClickReviewSecurity(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["apparmor"] = "foo.apparmor" + + # add something not allowed + tmp["apparmor-profile"] = "foo.profile" + + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_peer_hooks_aa_profile(self): + '''Test check_peer_hooks() - apparmor-profile''' + c = ClickReviewSecurity(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["apparmor-profile"] = "foo.profile" + + # update the manifest and test_manifest + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + # We should end up with 4 info + expected_counts = {'info': 4, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_peer_hooks_aa_profile_disallowed(self): + '''Test check_peer_hooks() - disallowed - apparmor-profile''' + c = ClickReviewSecurity(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["apparmor-profile"] = "foo.profile" + + # add something not allowed + tmp["framework"] = "foo.framework" + + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + def test_check_redflag_policy_vendor_ubuntu(self): '''Test check_redflag() - policy_vendor - ubuntu''' c = ClickReviewSecurity(self.test_name) @@ -943,3 +1104,36 @@ report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) + + def test_check_apparmor_profile(self): + '''Test check_apparmor_profile()''' + policy = ''' +###VAR### +###PROFILEATTACH### { + #include + # Read-only for the install directory + @{CLICK_DIR}/@{APP_PKGNAME}/@{APP_VERSION}/** mrklix, +} +''' + self.set_test_security_profile(self.default_appname, policy) + c = ClickReviewSecurity(self.test_name) + c.check_apparmor_profile() + report = c.click_report + expected_counts = {'info': 5, 'warn': 0, 'error': 0} + self.check_results(report, expected_counts) + + def test_check_apparmor_profile_missing_var(self): + '''Test check_apparmor_profile() - missing ###VAR###''' + policy = ''' +###PROFILEATTACH### { + #include + # Read-only for the install directory + @{CLICK_DIR}/@{APP_PKGNAME}/@{APP_VERSION}/** mrklix, +} +''' + self.set_test_security_profile(self.default_appname, policy) + c = ClickReviewSecurity(self.test_name) + c.check_apparmor_profile() + report = c.click_report + expected_counts = {'info': None, 'warn': 1, 'error': 0} + self.check_results(report, expected_counts) diff -Nru click-reviewers-tools-0.21/bin/clickreviews/tests/test_cr_systemd.py click-reviewers-tools-0.24/bin/clickreviews/tests/test_cr_systemd.py --- click-reviewers-tools-0.21/bin/clickreviews/tests/test_cr_systemd.py 1970-01-01 00:00:00.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/tests/test_cr_systemd.py 2015-03-10 15:16:29.000000000 +0000 @@ -0,0 +1,789 @@ +'''test_cr_systemd.py: tests for the cr_systemd module''' +# +# Copyright (C) 2015 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 . + +from clickreviews.cr_systemd import ClickReviewSystemd +import clickreviews.cr_tests as cr_tests + + +class TestClickReviewSystemd(cr_tests.TestClickReview): + """Tests for the lint review tool.""" + def setUp(self): + # Monkey patch various file access classes. stop() is handled with + # addCleanup in super() + cr_tests.mock_patch() + super() + + def _set_service(self, key, value, name=None): + d = dict() + if name is None: + d['name'] = 'foo' + else: + d['name'] = name + d[key] = value + self.set_test_pkg_yaml("services", [d]) + + def test_check_required(self): + '''Test check_required() - has start and description''' + self.set_test_systemd(self.default_appname, + key="start", + value="bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + c = ClickReviewSystemd(self.test_name) + c.check_required() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_required_empty_value(self): + '''Test check_required() - empty start''' + self.set_test_systemd(self.default_appname, + key="start", + value="") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + c = ClickReviewSystemd(self.test_name) + c.check_required() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_required_bad_value(self): + '''Test check_required() - bad start''' + self.set_test_systemd(self.default_appname, + key="start", + value=[]) + self.set_test_systemd(self.default_appname, + key="description", + value="something") + c = ClickReviewSystemd(self.test_name) + c.check_required() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_required_multiple(self): + '''Test check_required() - multiple''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + c = ClickReviewSystemd(self.test_name) + c.check_required() + r = c.click_report + expected_counts = {'info': -1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_required_multiple(self): + '''Test check_required() - multiple with nonexistent''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + self.set_test_systemd(self.default_appname, + key="nonexistent", + value="foo") + c = ClickReviewSystemd(self.test_name) + c.check_required() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_optional_none(self): + '''Test check_optional() - start only''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + c = ClickReviewSystemd(self.test_name) + c.check_optional() + r = c.click_report + expected_counts = {'info': 3, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_optional_stop_empty(self): + '''Test check_optional() - with empty stop''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + self.set_test_systemd(self.default_appname, + key="stop", + value="") + c = ClickReviewSystemd(self.test_name) + c.check_optional() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_optional_stop_bad(self): + '''Test check_optional() - with bad stop''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + self.set_test_systemd(self.default_appname, + key="stop", + value=[]) + c = ClickReviewSystemd(self.test_name) + c.check_optional() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_optional_stop_nonexistent(self): + '''Test check_optional() - with stop plus nonexistent''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + self.set_test_systemd(self.default_appname, + key="stop", + value="bin/bar") + self.set_test_systemd(self.default_appname, + key="nonexistent", + value="foo") + c = ClickReviewSystemd(self.test_name) + c.check_optional() + r = c.click_report + expected_counts = {'info': 3, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_optional_stop_without_start(self): + '''Test check_optional() - with stop, no start''' + self.set_test_systemd(self.default_appname, + key="stop", + value="/bin/bar") + c = ClickReviewSystemd(self.test_name) + c.check_optional() + r = c.click_report + expected_counts = {'info': 3, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_optional_stop_without_start2(self): + '''Test check_optional() - with stop, nonexistent, no start''' + self.set_test_systemd(self.default_appname, + key="stop", + value="/bin/bar") + self.set_test_systemd(self.default_appname, + key="nonexistent", + value="example.com") + c = ClickReviewSystemd(self.test_name) + c.check_optional() + r = c.click_report + expected_counts = {'info': 3, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_unknown(self): + '''Test check_unknown()''' + self.set_test_systemd(self.default_appname, + key="nonexistent", + value="foo") + c = ClickReviewSystemd(self.test_name) + c.check_unknown() + r = c.click_report + expected_counts = {'info': 0, 'warn': 1, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_unknown_multiple(self): + '''Test check_unknown() - multiple with nonexistent''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + self.set_test_systemd(self.default_appname, + key="stop", + value="bin/bar") + self.set_test_systemd(self.default_appname, + key="nonexistent", + value="foo") + c = ClickReviewSystemd(self.test_name) + c.check_unknown() + r = c.click_report + expected_counts = {'info': 0, 'warn': 1, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_peer_hooks(self): + '''Test check_peer_hooks()''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + c = ClickReviewSystemd(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["snappy-systemd"] = "meta/foo.snappy-systemd" + + # add required hooks + tmp["apparmor"] = "meta/foo.apparmor" + + # update the manifest and test_manifest + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + # We should end up with 2 info + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_peer_hooks2(self): + '''Test check_peer_hooks() - apparmor-profile''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + c = ClickReviewSystemd(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["snappy-systemd"] = "meta/foo.snappy-systemd" + + # add required hooks + tmp["apparmor-profile"] = "meta/foo.profile" + + # update the manifest and test_manifest + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + # We should end up with 2 info + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_peer_hooks_disallowed(self): + '''Test check_peer_hooks() - disallowed''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + c = ClickReviewSystemd(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["snappy-systemd"] = "meta/foo.snappy-systemd" + + # add required hooks + tmp["apparmor"] = "meta/foo.apparmor" + + # add something not allowed + tmp["bin-path"] = "bin/bar" + + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_peer_hooks_disallowed2(self): + '''Test check_peer_hooks() - disallowed (nonexistent)''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + c = ClickReviewSystemd(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["snappy-systemd"] = "meta/foo.snappy-systemd" + + # add required hooks + tmp["apparmor"] = "meta/foo.apparmor" + + # add something not allowed + tmp["nonexistent"] = "nonexistent-hook" + + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_description(self): + '''Test check_service_description()''' + self.set_test_systemd(self.default_appname, + "description", + "some description") + c = ClickReviewSystemd(self.test_name) + c.check_service_description() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_description_unspecified(self): + '''Test check_service_description() - unspecified''' + self.set_test_systemd(self.default_appname, + "description", + None) + c = ClickReviewSystemd(self.test_name) + c.check_service_description() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_description_empty(self): + '''Test check_service_description() - empty''' + self.set_test_systemd(self.default_appname, + "description", + "") + c = ClickReviewSystemd(self.test_name) + c.check_service_description() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_start(self): + '''Test check_service_start()''' + self.set_test_systemd(self.default_appname, + "start", + "some/start") + c = ClickReviewSystemd(self.test_name) + c.check_service_start() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_start_unspecified(self): + '''Test check_service_start() - unspecified''' + self.set_test_systemd(self.default_appname, + "start", + None) + c = ClickReviewSystemd(self.test_name) + c.check_service_start() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_start_empty(self): + '''Test check_service_start() - empty''' + self.set_test_systemd(self.default_appname, + "start", + "") + c = ClickReviewSystemd(self.test_name) + c.check_service_start() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_start_absolute_path(self): + '''Test check_service_start() - absolute path''' + self.set_test_systemd(self.default_appname, + "start", + "/foo/bar/some/start") + c = ClickReviewSystemd(self.test_name) + c.check_service_start() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_stop(self): + '''Test check_service_stop()''' + self.set_test_systemd(self.default_appname, + "stop", + "some/stop") + c = ClickReviewSystemd(self.test_name) + c.check_service_stop() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_stop_unspecified(self): + '''Test check_service_stop() - unspecified''' + self.set_test_systemd(self.default_appname, + "stop", + None) + c = ClickReviewSystemd(self.test_name) + c.check_service_stop() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_stop_empty(self): + '''Test check_service_stop() - empty''' + self.set_test_systemd(self.default_appname, + "stop", + "") + c = ClickReviewSystemd(self.test_name) + c.check_service_stop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_stop_absolute_path(self): + '''Test check_service_stop() - absolute path''' + self.set_test_systemd(self.default_appname, + "stop", + "/foo/bar/some/stop") + c = ClickReviewSystemd(self.test_name) + c.check_service_stop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_poststop(self): + '''Test check_service_poststop()''' + self.set_test_systemd(self.default_appname, + "poststop", + "some/poststop") + c = ClickReviewSystemd(self.test_name) + c.check_service_poststop() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_poststop_unspecified(self): + '''Test check_service_poststop() - unspecified''' + self.set_test_systemd(self.default_appname, + "poststop", + None) + c = ClickReviewSystemd(self.test_name) + c.check_service_poststop() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_poststop_empty(self): + '''Test check_service_poststop() - empty''' + self.set_test_systemd(self.default_appname, + "poststop", + "") + c = ClickReviewSystemd(self.test_name) + c.check_service_poststop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_poststop_absolute_path(self): + '''Test check_service_poststop() - absolute path''' + self.set_test_systemd(self.default_appname, + "poststop", + "/foo/bar/some/poststop") + c = ClickReviewSystemd(self.test_name) + c.check_service_poststop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_stop_timeout(self): + '''Test check_service_stop_timeout()''' + self.set_test_systemd(self.default_appname, + key="start", + value="bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + self.set_test_systemd(self.default_appname, + key="stop-timeout", + value=30) + c = ClickReviewSystemd(self.test_name) + c.check_service_stop_timeout() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_stop_timeout_empty(self): + '''Test check_service_stop_timeout() - empty''' + self.set_test_systemd(self.default_appname, + key="start", + value="bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + self.set_test_systemd(self.default_appname, + key="stop-timeout", + value="") + c = ClickReviewSystemd(self.test_name) + c.check_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_stop_timeout_bad(self): + '''Test check_service_stop_timeout() - bad''' + self.set_test_systemd(self.default_appname, + key="start", + value="bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + self.set_test_systemd(self.default_appname, + key="stop-timeout", + value="a") + c = ClickReviewSystemd(self.test_name) + c.check_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_stop_timeout_range_low(self): + '''Test check_service_stop_timeout() - out of range (low)''' + self.set_test_systemd(self.default_appname, + key="start", + value="bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + self.set_test_systemd(self.default_appname, + key="stop-timeout", + value=-1) + c = ClickReviewSystemd(self.test_name) + c.check_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_stop_timeout_range_high(self): + '''Test check_service_stop_timeout() - out of range (high)''' + self.set_test_systemd(self.default_appname, + key="start", + value="bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + self.set_test_systemd(self.default_appname, + key="stop-timeout", + value=61) + c = ClickReviewSystemd(self.test_name) + c.check_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_description(self): + '''Test check_snappy_service_description()''' + self._set_service("description", "some description") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_description() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_description_unspecified(self): + '''Test check_snappy_service_description() - unspecified''' + # self._set_service("description", None) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_description() + r = c.click_report + # required check is done elsewhere, so no error + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_description_empty(self): + '''Test check_snappy_service_description() - empty''' + self._set_service("description", "") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_description() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_start(self): + '''Test check_snappy_service_start()''' + self._set_service("start", "some/start") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_start() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_start_unspecified(self): + '''Test check_snappy_service_start() - unspecified''' + # self._set_service("start", None) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_start() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_start_empty(self): + '''Test check_snappy_service_start() - empty''' + self._set_service("start", "") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_start() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_start_absolute_path(self): + '''Test check_snappy_service_start() - absolute path''' + self._set_service("start", "/foo/bar/some/start") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_start() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop(self): + '''Test check_snappy_service_stop()''' + self._set_service("stop", "some/stop") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_unspecified(self): + '''Test check_snappy_service_stop() - unspecified''' + # self._set_service("stop", None) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_empty(self): + '''Test check_snappy_service_stop() - empty''' + self._set_service("stop", "") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_absolute_path(self): + '''Test check_snappy_service_stop() - absolute path''' + self._set_service("stop", "/foo/bar/some/stop") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_poststop(self): + '''Test check_snappy_service_poststop()''' + self._set_service("poststop", "some/poststop") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_poststop() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_poststop_unspecified(self): + '''Test check_snappy_service_poststop() - unspecified''' + # self._set_service("poststop", None) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_poststop() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_poststop_empty(self): + '''Test check_snappy_service_poststop() - empty''' + self._set_service("poststop", "") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_poststop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_poststop_absolute_path(self): + '''Test check_snappy_service_poststop() - absolute path''' + self._set_service("poststop", "/foo/bar/some/poststop") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_poststop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_timeout(self): + '''Test check_snappy_service_stop_timeout()''' + self._set_service(key="start", value="bin/foo") + self._set_service(key="description", value="something") + self._set_service(key="stop-timeout", value=30) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop_timeout() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_timeout_empty(self): + '''Test check_snappy_service_stop_timeout() - empty''' + self._set_service(key="start", value="bin/foo") + self._set_service(key="description", value="something") + self._set_service(key="stop-timeout", value="") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_timeout_bad(self): + '''Test check_snappy_service_stop_timeout() - bad''' + self._set_service(key="start", value="bin/foo") + self._set_service(key="description", value="something") + self._set_service(key="stop-timeout", value="a") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_timeout_range_low(self): + '''Test check_snappy_service_stop_timeout() - out of range (low)''' + self._set_service(key="start", value="bin/foo") + self._set_service(key="description", value="something") + self._set_service(key="stop-timeout", value=-1) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_timeout_range_high(self): + '''Test check_snappy_service_stop_timeout() - out of range (high)''' + self._set_service(key="start", value="bin/foo") + self._set_service(key="description", value="something") + self._set_service(key="stop-timeout", value=61) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) diff -Nru click-reviewers-tools-0.21/bin/clickreviews/tests/test_cr_url_dispatcher.py click-reviewers-tools-0.24/bin/clickreviews/tests/test_cr_url_dispatcher.py --- click-reviewers-tools-0.21/bin/clickreviews/tests/test_cr_url_dispatcher.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/bin/clickreviews/tests/test_cr_url_dispatcher.py 2015-03-10 15:16:29.000000000 +0000 @@ -171,7 +171,7 @@ key="domain-suffix", value="example.com") self.set_test_url_dispatcher(self.default_appname, - key="domain-suffix", + key="nonexistent", value="example.com", append=True) c = ClickReviewUrlDispatcher(self.test_name) @@ -216,13 +216,12 @@ key="protocol", value="some-protocol") c = ClickReviewUrlDispatcher(self.test_name) - print(c.manifest["hooks"]) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook - tmp["url-dispatcher"] = \ + tmp["urls"] = \ self.test_manifest["hooks"][self.default_appname]["urls"] # update the manifest and test_manifest diff -Nru click-reviewers-tools-0.21/bin/click-show-files click-reviewers-tools-0.24/bin/click-show-files --- click-reviewers-tools-0.21/bin/click-show-files 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/bin/click-show-files 2015-03-10 15:16:29.000000000 +0000 @@ -1,7 +1,7 @@ #!/usr/bin/python3 '''check-show-files: show files''' # -# Copyright (C) 2014 Canonical Ltd. +# Copyright (C) 2014-2015 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 diff -Nru click-reviewers-tools-0.21/clickreviews/cr_bin_path.py click-reviewers-tools-0.24/clickreviews/cr_bin_path.py --- click-reviewers-tools-0.21/clickreviews/cr_bin_path.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/cr_bin_path.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ '''cr_bin_path.py: click bin-path''' # -# Copyright (C) 2014 Canonical Ltd. +# Copyright (C) 2014-2015 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 @@ -26,7 +26,7 @@ peer_hooks = dict() my_hook = 'bin-path' peer_hooks[my_hook] = dict() - peer_hooks[my_hook]['required'] = ["snappy-systemd", "apparmor"] + peer_hooks[my_hook]['required'] = ["apparmor"] peer_hooks[my_hook]['allowed'] = peer_hooks[my_hook]['required'] ClickReview.__init__(self, fn, "bin-path", peer_hooks=peer_hooks) @@ -67,3 +67,28 @@ s = "'%s' is not executable" % \ (self.manifest['hooks'][app]['bin-path']) self._add_result(t, n, s) + + def check_binary_description(self): + '''Check package.yaml binary description''' + if not self.is_snap or 'binaries' not in self.pkg_yaml: + return + + my_dict = self._create_dict(self.pkg_yaml['binaries']) + + for app in sorted(my_dict): + t = 'info' + n = 'package_yaml_description_present_%s' % (app) + s = 'OK' + if 'description' not in my_dict[app]: + s = 'OK (skip missing)' + self._add_result('info', n, s) + return + self._add_result(t, n, s) + + t = 'info' + n = 'package_yaml_description_empty_%s' % (app) + s = 'OK' + if len(my_dict[app]['description']) == 0: + t = 'error' + s = "description is empty" + self._add_result(t, n, s) diff -Nru click-reviewers-tools-0.21/clickreviews/cr_common.py click-reviewers-tools-0.24/clickreviews/cr_common.py --- click-reviewers-tools-0.21/clickreviews/cr_common.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/cr_common.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ '''common.py: common classes and functions''' # -# Copyright (C) 2013-2014 Canonical Ltd. +# Copyright (C) 2013-2015 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 @@ -23,11 +23,13 @@ import magic import os import pprint +import re import shutil import subprocess import sys import tempfile import types +import yaml DEBUGGING = False UNPACK_DIR = None @@ -75,10 +77,24 @@ # FIXME: when apparmor-policy is implemented, use this # framework_allowed_peer_hooks = ["apparmor-policy"] framework_allowed_peer_hooks = [] - service_allowed_peer_hooks = ["bin-path", + service_allowed_peer_hooks = ["apparmor", + "bin-path", "snappy-systemd", ] + snappy_required = ["name", "version"] + # optional snappy fields here (may be required by appstore) + snappy_optional = ["architecture", + "binaries", + "frameworks", + "icon", + "integration", + "services", + "source", + "type", + "vendor", # replaces maintainer + ] + def __init__(self, fn, review_type, peer_hooks=None): self.click_package = fn self._check_path_exists() @@ -105,7 +121,6 @@ self.unpack_dir = UNPACK_DIR # Get some basic information from the control file - control_file = self._extract_control_file() tmp = list(Deb822.iter_paragraphs(control_file)) if len(tmp) != 1: @@ -123,6 +138,17 @@ error("Could not load manifest file. Is it properly formatted?") self._verify_manifest_structure() + # Parse and store the package.yaml + pkg_yaml = self._extract_package_yaml() + self.is_snap = False + if pkg_yaml is not None: + try: + self.pkg_yaml = yaml.safe_load(pkg_yaml) + except Exception as e: + error("Could not load package.yaml. Is it properly formatted?") + self._verify_package_yaml_structure() + self.is_snap = True + # Get a list of all unpacked files, except DEBIAN/ self.pkg_files = [] self._list_all_files() @@ -160,6 +186,13 @@ error("Could not find manifest file") return open_file_read(m) + def _extract_package_yaml(self): + '''Extract and read the snappy package.yaml''' + y = os.path.join(self.unpack_dir, "meta/package.yaml") + if not os.path.isfile(y): + return None # snappy packaging is still optional + return open_file_read(y) + def _check_path_exists(self): '''Check that the provided path exists''' if not os.path.exists(self.click_package): @@ -244,6 +277,40 @@ error("manifest malformed: unsupported field '%s':\n%s" % (k, mp)) + def _verify_package_yaml_structure(self): + '''Verify package.yaml has the expected structure''' + # https://developer.ubuntu.com/en/snappy/guides/packaging-format-apps/ + # lp:click doc/file-format.rst + yp = yaml.dump(self.pkg_yaml, default_flow_style=False, indent=4) + if not isinstance(self.pkg_yaml, dict): + error("package yaml malformed:\n%s" % self.pkg_yaml) + + for f in self.snappy_required: + if f not in self.pkg_yaml: + error("could not find required '%s' in package.yaml:\n%s" % + (f, yp)) + elif f in ['name', 'version']: + # make sure this is a string for other tests since + # yaml.safe_load may make it an int, float or str + self.pkg_yaml[f] = str(self.pkg_yaml[f]) + + for f in self.snappy_optional: + if f in self.pkg_yaml: + if f in ["architecture", "frameworks"] and not \ + (isinstance(self.pkg_yaml[f], str) or + isinstance(self.pkg_yaml[f], list)): + error("yaml malformed: '%s' is not str or list:\n%s" % + (f, mp)) + elif f in ["binaries", "services"] and not \ + isinstance(self.pkg_yaml[f], list): + error("yaml malformed: '%s' is not list:\n%s" % (f, yp)) + elif f == "integration" and not isinstance(self.pkg_yaml[f], + dict): + error("yaml malformed: '%s' is not dict:\n%s" % (f, yp)) + elif f in ["icon", "source", "type", "vendor"] and not \ + isinstance(self.pkg_yaml[f], str): + error("yaml malformed: '%s' is not str:\n%s" % (f, yp)) + def _verify_peer_hooks(self, my_hook): '''Compare manifest for required and allowed hooks''' d = dict() @@ -257,6 +324,11 @@ if h == my_hook: continue if h not in self.manifest["hooks"][app]: + # Treat these as equivalent for satisfying peer hooks + if h == 'apparmor' and \ + 'apparmor-profile' in self.manifest["hooks"][app]: + continue + if 'missing' not in d: d['missing'] = dict() if app not in d['missing']: @@ -266,6 +338,13 @@ if h == my_hook: continue if h not in self.peer_hooks[my_hook]['allowed']: + # 'apparmor-profile' is allowed when 'apparmor' is, but + # they may not be used together + if h == 'apparmor-profile': + if 'apparmor' in self.peer_hooks[my_hook]['allowed'] \ + and 'apparmor' not in self.manifest["hooks"][app]: + continue + if 'disallowed' not in d: d['disallowed'] = dict() if app not in d['disallowed']: @@ -274,6 +353,44 @@ return d + def _verify_pkgname(self, n): + '''Verify package name''' + if re.search(r'^[a-z0-9][a-z0-9+.-]+$', n): + return True + return False + + def _verify_pkgversion(self, v): + '''Verify package name''' + re_valid_version = re.compile(r'^((\d+):)?' # epoch + '([A-Za-z0-9.+:~-]+?)' # upstream + '(-([A-Za-z0-9+.~]+))?$') # debian + if re_valid_version.match(v): + return True + return False + + def _verify_maintainer(self, m): + '''Verify maintainer email''' + # Simple regex as used by python3-debian. If we wanted to be more + # thorough we could use email_re from django.core.validators + if re.search(r"^(.*)\s+<(.*@.*)>$", m): + return True + return False + + def _create_dict(self, lst, topkey='name'): + '''Converts list of dicts into dict[topkey][]. Useful for + conversions from yaml list to json dict''' + d = dict() + for entry in lst: + if topkey not in entry: + error("required field '%s' not present: %s" % (topkey, entry)) + name = entry[topkey] + d[name] = dict() + for key in entry: + if key == topkey: + continue + d[name][key] = entry[key] + return d + def check_peer_hooks(self, hooks_sublist=[]): '''Check if peer hooks are valid''' # Nothing to verify diff -Nru click-reviewers-tools-0.21/clickreviews/cr_desktop.py click-reviewers-tools-0.24/clickreviews/cr_desktop.py --- click-reviewers-tools-0.21/clickreviews/cr_desktop.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/cr_desktop.py 2015-03-10 15:16:29.000000000 +0000 @@ -340,11 +340,7 @@ "--webappModelSearchPath or --webapp= when " + \ "running local application" elif not is_launching_local_app: - if found_url_patterns and found_model_search_path: - t = 'error' - s = "should not specify --webappUrlPatterns when using " + \ - "--webappModelSearchPath" - elif not found_url_patterns and not found_model_search_path: + if not found_url_patterns and not found_model_search_path: t = 'error' s = "must specify one of --webappUrlPatterns or " + \ "--webappModelSearchPath" diff -Nru click-reviewers-tools-0.21/clickreviews/cr_lint.py click-reviewers-tools-0.24/clickreviews/cr_lint.py --- click-reviewers-tools-0.21/clickreviews/cr_lint.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/cr_lint.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ -'''cr_lint.py: click lint checks''' +'''cr_lint.py: lint checks''' # -# Copyright (C) 2013-2014 Canonical Ltd. +# Copyright (C) 2013-2015 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 @@ -60,8 +60,10 @@ self.is_core_app = (self.click_pkgname.startswith('com.ubuntu.') and not self.click_pkgname.startswith( 'com.ubuntu.developer.') - and self.email == - 'ubuntu-touch-coreapps@lists.launchpad.net') + and (self.email == + 'ubuntu-touch-coreapps@lists.launchpad.net' or + self.email == + 'ubuntu-devel-discuss@lists.ubuntu.com')) # "core scope" is not necessarily a word we use right now, but # we want to special case scopes which are written through our # vetted development process. @@ -87,6 +89,7 @@ 'account-qml-plugin', 'account-service', 'apparmor', + 'apparmor-profile', 'bin-path', 'content-hub', 'desktop', @@ -99,7 +102,22 @@ self.redflagged_hooks = ['framework', 'pay-ui', + 'apparmor-profile', ] + + # Valid values for 'type' in packaging yaml + # - app + # - framework + # - oem + self.snappy_valid_types = ['app', + 'framework', + # 'framework-policy', # TBI + # 'oem', # TBI + ] + self.snappy_redflagged_types = ['framework', + # 'oem', # TBI + ] + if overrides is None: overrides = {} self.overrides = overrides @@ -372,10 +390,16 @@ if f not in self.manifest['hooks'][app]: # TODO: when have apparmor policy for account-provider and # account-qml-plugin, remove this conditional - if f != 'apparmor' or \ - len(self.manifest['hooks'][app]) != 2 or \ - 'account-provider' not in self.manifest['hooks'][app] or \ - 'account-qml-plugin' not in self.manifest['hooks'][app]: + if f == 'apparmor' and \ + 'apparmor-profile' in self.manifest['hooks'][app]: + # Don't require apparmor if have apparmor-profile + pass + elif f != 'apparmor' or \ + len(self.manifest['hooks'][app]) != 2 or \ + 'account-provider' not in \ + self.manifest['hooks'][app] or \ + 'account-qml-plugin' not in \ + self.manifest['hooks'][app]: t = 'error' s = "'%s' hook not found for '%s'" % (f, app) self._add_result(t, n, s) @@ -465,7 +489,7 @@ t = 'info' n = 'pkgname_valid' s = "OK" - if not re.search(r'^[a-z0-9][a-z0-9+.-]+$', p): + if not self._verify_pkgname(p): t = 'error' s = "'%s' not properly formatted" % p self._add_result(t, n, s) @@ -525,7 +549,7 @@ return if len(self.pkg_bin_files) == 0: - t = 'error' + t = 'warn' s = "Could not find compiled binaries for architecture '%s'" % \ self.click_arch self._add_result(t, n, s) @@ -551,7 +575,7 @@ 'like "Joe Bloggs ")', 'http://askubuntu.com/questions/417351/what-does-lint-maintainer-format-mean/417352') return - elif not re.search(r"^(.*)\s+<(.*@.*)>$", self.manifest['maintainer']): + elif not self._verify_maintainer(self.manifest['maintainer']): self._add_result('error', n, 'invalid format for maintainer: %s (should be ' 'like "Joe Bloggs ")' % @@ -597,7 +621,13 @@ pkgname_base = self.click_pkgname.split('.')[-1] if len(self.manifest['description']) < len(pkgname_base): t = 'warn' - s = "'%s' is too short" % self.manifest['description'] + if self.is_snap and (self.manifest['description'] == '\n' or + self.manifest['description'] == ''): + s = "manifest description is empty. Is meta/readme.md " + \ + "formatted correctly?" + else: + s = "'%s' is too short" % self.manifest['description'] + self._add_result(t, n, s) def check_framework(self): @@ -658,17 +688,18 @@ def check_package_filename(self): '''Check filename of package''' tmp = os.path.basename(self.click_package).split('_') + click_package_bn = os.path.basename(self.click_package) t = 'info' n = 'package_filename_format' s = 'OK' if len(tmp) != 3: t = 'warn' - s = "'%s' not of form $pkgname_$version_$arch.click" % \ - os.path.basename(self.click_package) + s = "'%s' not of form $pkgname_$version_$arch.[click|snap]" % \ + click_package_bn self._add_result(t, n, s) # handle $pkgname.click or $pkgname.snap - if tmp[0].endswith('.snap'): + if self.click_package.endswith('.snap'): pkgname = tmp[0].partition('.snap')[0] else: pkgname = tmp[0].partition('.click')[0] @@ -677,7 +708,8 @@ s = 'OK' l = None if pkgname != self.click_pkgname: - if pkgname.startswith('com.ubuntu.snappy.'): + if pkgname.startswith('com.ubuntu.snappy.') or \ + click_package_bn.startswith('com.ubuntu.snappy.'): s = "OK (store snappy workaround)" else: t = 'error' @@ -705,7 +737,7 @@ l = None if len(tmp) >= 2: # handle $pkgname_$version.click or $pkgname_$version.snap - if tmp[0].endswith('.snap'): + if self.click_package.endswith('.snap'): version = tmp[1].partition('.snap')[0] else: version = tmp[1].partition('.click')[0] @@ -724,7 +756,7 @@ n = 'package_filename_arch_valid' s = 'OK' if len(tmp) >= 3: - if tmp[0].endswith('.snap'): + if self.click_package.endswith('.snap'): arch = tmp[2].partition('.snap')[0] else: arch = tmp[2].partition('.click')[0] @@ -748,7 +780,7 @@ n = 'package_filename_arch_match' s = 'OK' if len(tmp) >= 3: - if tmp[0].endswith('.snap'): + if self.click_package.endswith('.snap'): arch = tmp[2].partition('.snap')[0] else: arch = tmp[2].partition('.click')[0] @@ -816,28 +848,27 @@ pass self._add_result(t, n, s) - def check_manifest_architecture(self): - '''Check package architecture in manifest is valid''' + def _verify_architecture(self, my_dict, test_str): t = 'info' - n = 'manifest_architecture_valid' + n = '%s_architecture_valid' % test_str s = 'OK' - if 'architecture' not in self.manifest: + if 'architecture' not in my_dict: s = 'OK (architecture not specified)' self._add_result(t, n, s) return - manifest_archs_list = list(self.valid_control_architectures) - manifest_archs_list.remove("multi") + archs_list = list(self.valid_control_architectures) + archs_list.remove("multi") - if isinstance(self.manifest['architecture'], str) and \ - self.manifest['architecture'] not in manifest_archs_list: + if isinstance(my_dict['architecture'], str) and \ + my_dict['architecture'] not in archs_list: t = 'error' - s = "not a valid architecture: %s" % self.manifest['architecture'] - elif isinstance(self.manifest['architecture'], list): - manifest_archs_list.remove("all") + s = "not a valid architecture: %s" % my_dict['architecture'] + elif isinstance(my_dict['architecture'], list): + archs_list.remove("all") bad_archs = [] - for a in self.manifest['architecture']: - if a not in manifest_archs_list: + for a in my_dict['architecture']: + if a not in archs_list: bad_archs.append(a) if len(bad_archs) > 0: t = 'error' @@ -845,31 +876,188 @@ ",".join(bad_archs) self._add_result(t, n, s) - def check_icon(self): - '''Check icon()''' + def check_manifest_architecture(self): + '''Check package architecture in manifest is valid''' + self._verify_architecture(self.manifest, "manifest") + + def _verify_icon(self, my_dict, test_str): t = 'info' - n = 'icon_present' + n = '%s_icon_present' % test_str s = 'OK' - if 'icon' not in self.manifest: + if 'icon' not in my_dict: s = 'Skipped, optional icon not present' self._add_result(t, n, s) return self._add_result(t, n, s) t = 'info' - n = 'icon_empty' + n = '%s_icon_empty' % test_str + s = 'OK' + if len(my_dict['icon']) == 0: + t = 'error' + s = "icon entry is empty" + return + self._add_result(t, n, s) + + t = 'info' + n = '%s_icon_absolute_path' % test_str + s = 'OK' + if my_dict['icon'].startswith('/'): + t = 'error' + s = "icon entry '%s' should not specify absolute path" % \ + my_dict['icon'] + self._add_result(t, n, s) + + def check_icon(self): + '''Check icon()''' + self._verify_icon(self.manifest, "manifest") + + def check_snappy_name(self): + '''Check package name''' + if not self.is_snap: + return + + t = 'info' + n = 'snappy_name_valid' + s = 'OK' + if 'name' not in self.pkg_yaml: + t = 'error' + s = "could not find 'name' in yaml" + elif not self._verify_pkgname(self.pkg_yaml['name']): + t = 'error' + s = "malformed 'name': '%s'" % self.pkg_yaml['name'] + self._add_result(t, n, s) + + def check_snappy_version(self): + '''Check package version''' + if not self.is_snap: + return + + t = 'info' + n = 'snappy_version_valid' + s = 'OK' + if 'version' not in self.pkg_yaml: + t = 'error' + s = "could not find 'version' in yaml" + elif not self._verify_pkgversion(self.pkg_yaml['version']): + t = 'error' + s = "malformed 'version': '%s'" % self.pkg_yaml['version'] + self._add_result(t, n, s) + + def check_snappy_type(self): + '''Check type''' + if not self.is_snap: + return + + t = 'info' + n = 'snappy_type_valid' + s = 'OK' + if 'type' not in self.pkg_yaml: + s = 'OK (skip missing)' + elif self.pkg_yaml['type'] not in self.snappy_valid_types: + t = 'error' + s = "unknown 'type': '%s'" % self.pkg_yaml['type'] + self._add_result(t, n, s) + + def check_snappy_type_redflagged(self): + '''Check if snappy type is redflagged''' + if not self.is_snap: + return + + t = 'info' + n = 'snappy_type_redflag' + s = "OK" + manual_review = False + if 'type' not in self.pkg_yaml: + s = 'OK (skip missing)' + elif self.pkg_yaml['type'] in self.snappy_redflagged_types: + t = 'error' + s = "(MANUAL REVIEW) type '%s' not allowed" % self.pkg_yaml['type'] + manual_review = True + self._add_result(t, n, s, manual_review=manual_review) + + def check_snappy_vendor(self): + '''Check package vendor''' + if not self.is_snap: + return + + t = 'info' + n = 'snappy_vendor_valid' s = 'OK' - if len(self.manifest['icon']) == 0: + if 'vendor' not in self.pkg_yaml: + s = "OK (skip missing)" + elif not self._verify_maintainer(self.pkg_yaml['vendor']): t = 'error' - s = "icon manifest entry is empty" + s = "malformed 'vendor': '%s'" % self.pkg_yaml['vendor'] + self._add_result(t, n, s) + + def check_snappy_icon(self): + '''Check icon()''' + if not self.is_snap: return + + self._verify_icon(self.pkg_yaml, "package_yaml") + + def check_snappy_architecture(self): + '''Check package architecture in package.yaml is valid''' + if not self.is_snap: + return + + self._verify_architecture(self.pkg_yaml, "package yaml") + + def check_snappy_unknown_entries(self): + '''Check for any unknown fields''' + if not self.is_snap: + return + + t = 'info' + n = 'snappy_unknown' + s = 'OK' + unknown = [] + for f in self.pkg_yaml: + if f not in self.snappy_required + self.snappy_optional: + unknown.append(f) + if len(unknown) > 0: + t = 'warn' + s = "unknown entries in package.yaml: '%s'" % (",".join(unknown)) + obsoleted = ['maintainer', 'ports'] + tmp = list(set(unknown) & set(obsoleted)) + if len(tmp) > 0: + t = 'error' + s += " (%s obsoleted)" % ",".join(tmp) self._add_result(t, n, s) + def _extract_readme_md(self): + '''Extract meta/readme.md''' + contents = None + readme = os.path.join(self.unpack_dir, "meta/readme.md") + if os.path.exists(readme): + fh = open_file_read(readme) + contents = fh.read() + return contents + + def check_snappy_readme_md(self): + '''Check package architecture in package.yaml is valid''' + if not self.is_snap: + return + + contents = self._extract_readme_md() + t = 'info' - n = 'icon_absolute_path' + n = 'snappy_readme.md' s = 'OK' - if self.manifest['icon'].startswith('/'): + if contents is None: t = 'error' - s = "icon manifest entry '%s' should not specify absolute path" % \ - self.manifest['icon'] + s = 'meta/readme.md does not exist' + self._add_result(t, n, s) + return + self._add_result(t, n, s) + + t = 'info' + n = 'snappy_readme.md_length' + s = 'OK' + pkgname_base = self.pkg_yaml['name'].split('.')[0] + if len(contents) < len(pkgname_base): + t = 'warn' + s = "meta/readme.md is too short" self._add_result(t, n, s) diff -Nru click-reviewers-tools-0.21/clickreviews/cr_security.py click-reviewers-tools-0.24/clickreviews/cr_security.py --- click-reviewers-tools-0.21/clickreviews/cr_security.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/cr_security.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ '''cr_security.py: click security checks''' # -# Copyright (C) 2013-2014 Canonical Ltd. +# Copyright (C) 2013-2015 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 @@ -16,7 +16,7 @@ from __future__ import print_function -from clickreviews.cr_common import ClickReview, error +from clickreviews.cr_common import ClickReview, error, open_file_read import clickreviews.cr_common as cr_common import clickreviews.apparmor_policy as apparmor_policy import json @@ -36,6 +36,13 @@ ['pay-ui'] peer_hooks[my_hook]['required'] = [] + my_hook2 = 'apparmor-profile' + peer_hooks[my_hook2] = dict() + # Basically, everything except frameworks + peer_hooks[my_hook2]['allowed'] = \ + ClickReview.service_allowed_peer_hooks + peer_hooks[my_hook2]['required'] = [] + ClickReview.__init__(self, fn, "security", peer_hooks=peer_hooks) local_copy = os.path.join(os.path.dirname(__file__), @@ -99,6 +106,10 @@ 'ubuntu-sdk-14.10': { 'policy_version': 1.2, }, + 'ubuntu-core-15.04': { + 'policy_vendor': 'ubuntu-snappy', + 'policy_version': 1.3, + }, } if overrides is None: overrides = {} @@ -118,6 +129,21 @@ self._extract_security_manifest(app) self.security_apps.append(app) + self.security_profiles = dict() + self.security_apps_profiles = [] + for app in self.manifest['hooks']: + if 'apparmor-profile' not in self.manifest['hooks'][app]: + # msg("Skipped missing apparmor hook for '%s'" % app) + continue + if not isinstance(self.manifest['hooks'][app]['apparmor-profile'], + str): + error("manifest malformed: hooks/%s/apparmor-profile is not " + "str" % app) + rel_fn = self.manifest['hooks'][app]['apparmor-profile'] + self.security_profiles[rel_fn] = \ + self._extract_security_profile(app) + self.security_apps_profiles.append(app) + def _override_framework_policies(self, overrides): # override major framework policies self.major_framework_policy.update(overrides) @@ -187,6 +213,38 @@ m = self.security_manifests[f] return (f, m) + def _extract_security_profile(self, app): + '''Extract security profile''' + d = self.manifest['hooks'][app]['apparmor-profile'] + fn = os.path.join(self.unpack_dir, d) + rel_fn = self.manifest['hooks'][app]['apparmor-profile'] + + if not os.path.exists(fn): + error("Could not find '%s'" % rel_fn) + + fh = open_file_read(fn) + contents = "" + for line in fh.readlines(): + contents += line + fh.close() + + # We could try to run this through apparmor_parser, but that is going + # to be system dependent (eg, a profile may reference features on a + # new parser and fail here on the local parser) + + return contents + + def _get_security_profile(self, app): + '''Get the security profile for app''' + if app not in self.manifest['hooks']: + error("Could not find '%s' in click manifest" % app) + elif 'apparmor-profile' not in self.manifest['hooks'][app]: + error("Could not find apparmor-profile hook for '%s' in click " + "manifest" % app) + f = self.manifest['hooks'][app]['apparmor-profile'] + p = self.security_profiles[f] + return (f, p) + def _get_policy_versions(self, vendor): '''Get the supported AppArmor policy versions''' if vendor not in self.aa_policy: @@ -267,6 +325,29 @@ s = "policy_vendor '%s' not found" % m['policy_vendor'] self._add_result(t, n, s) + t = 'info' + n = 'policy_vendor_matches_framework (%s)' % (f) + s = "OK" + if 'policy_vendor' in m: # policy_vendor is optional + found_major = False + for name, data in self.major_framework_policy.items(): + # TODO: use libclick when it is available + if not self.manifest['framework'].startswith(name): + continue + elif 'policy_vendor' not in data: + # when not specified, default to 'ubuntu' + data['policy_vendor'] = "ubuntu" + found_major = True + if m['policy_vendor'] != data['policy_vendor']: + t = 'error' + s = '%s != %s (%s)' % (str(m['policy_vendor']), + data['policy_vendor'], + self.manifest['framework']) + if not found_major: + t = 'error' + s = "Invalid framework '%s'" % self.manifest['framework'] + self._add_result(t, n, s) + def check_policy_version(self): '''Check policy version''' for app in sorted(self.security_apps): @@ -635,3 +716,23 @@ t = 'error' s = "missing required fields: %s" % ", ".join(not_found) self._add_result(t, n, s) + + def check_apparmor_profile(self): + '''Check apparmor-profile''' + for app in sorted(self.security_apps_profiles): + (f, p) = self._get_security_profile(app) + + for v in ['###VAR###', + '###PROFILEATTACH###', + '@{CLICK_DIR}', + '@{APP_PKGNAME}', + '@{APP_VERSION}', + ]: + t = 'info' + n = 'apparmor_profile_%s (%s)' % (v, f) + s = "OK" + if v not in p: + self._add_result('warn', n, + "could not find '%s' in profile" % v) + continue + self._add_result(t, n, s) diff -Nru click-reviewers-tools-0.21/clickreviews/cr_systemd.py click-reviewers-tools-0.24/clickreviews/cr_systemd.py --- click-reviewers-tools-0.21/clickreviews/cr_systemd.py 1970-01-01 00:00:00.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/cr_systemd.py 2015-03-10 15:16:29.000000000 +0000 @@ -0,0 +1,305 @@ +'''cr_systemd.py: click systemd''' +# +# Copyright (C) 2015 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 . + +from __future__ import print_function + +from clickreviews.cr_common import ClickReview, error, open_file_read, msg +import yaml +import os + + +class ClickReviewSystemd(ClickReview): + '''This class represents click lint reviews''' + def __init__(self, fn): + peer_hooks = dict() + my_hook = 'snappy-systemd' + peer_hooks[my_hook] = dict() + peer_hooks[my_hook]['required'] = ["apparmor"] + peer_hooks[my_hook]['allowed'] = peer_hooks[my_hook]['required'] + + ClickReview.__init__(self, fn, "snappy-systemd", peer_hooks=peer_hooks) + + # snappy-systemd currently only allows specifying: + # - start (required) + # - description (required) + # - stop + # - poststop + # - stop-timeout + self.required_keys = ['start', 'description'] + self.optional_keys = ['stop', 'poststop', 'stop-timeout'] + + self.systemd_files = dict() # click-show-files and tests + self.systemd = dict() + for app in self.manifest['hooks']: + if 'snappy-systemd' not in self.manifest['hooks'][app]: + # msg("Skipped missing systemd hook for '%s'" % app) + continue + if not isinstance(self.manifest['hooks'][app]['snappy-systemd'], + str): + error("manifest malformed: hooks/%s/snappy-systemd is not str" + % app) + (full_fn, jd) = self._extract_systemd(app) + self.systemd_files[app] = full_fn + self.systemd[app] = jd + + def _extract_systemd(self, app): + '''Get systemd yaml''' + u = self.manifest['hooks'][app]['snappy-systemd'] + fn = os.path.join(self.unpack_dir, u) + + bn = os.path.basename(fn) + if not os.path.exists(fn): + error("Could not find '%s'" % bn) + + fh = open_file_read(fn) + contents = "" + for line in fh.readlines(): + contents += line + fh.close() + + try: + yd = yaml.safe_load(contents) + except Exception as e: + error("snappy-systemd yaml unparseable: %s (%s):\n%s" % (bn, + str(e), contents)) + + if not isinstance(yd, dict): + error("snappy-systemd yaml is malformed: %s:\n%s" % (bn, contents)) + + return (fn, yd) + + def _verify_required(self, my_dict, test_str): + for app in sorted(my_dict): + f = os.path.basename(self.systemd_files[app]) + for r in self.required_keys: + found = False + t = 'info' + n = '%s_required_key_%s_%s' % (test_str, r, f) + s = "OK" + if r in my_dict[app]: + if not isinstance(my_dict[app][r], str): + t = 'error' + s = "'%s' is not a string" % r + elif my_dict[app][r] == "": + t = 'error' + s = "'%s' is empty" % r + else: + found = True + if not found and t != 'error': + t = 'error' + s = "Missing required field '%s'" % r + self._add_result(t, n, s) + + def check_required(self): + '''Check snappy-systemd required fields''' + self._verify_required(self.systemd, 'hook') + + def check_snappy_required(self): + '''Check for package.yaml required fields''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_required(self._create_dict(self.pkg_yaml['services']), + 'package_yaml') + + def _verify_optional(self, my_dict, test_str): + for app in sorted(my_dict): + f = os.path.basename(self.systemd_files[app]) + for o in self.optional_keys: + found = False + t = 'info' + n = '%s_optional_key_%s_%s' % (test_str, o, f) + s = "OK" + if o in my_dict[app]: + if o == 'stop-timeout' and \ + not isinstance(my_dict[app][o], int): + t = 'error' + s = "'%s' is not an integer" % o + elif not isinstance(my_dict[app][o], str): + t = 'error' + s = "'%s' is not a string" % o + elif my_dict[app][o] == "": + t = 'error' + s = "'%s' is empty" % o + else: + found = True + if not found and t != 'error': + s = "OK (skip missing)" + self._add_result(t, n, s) + + def check_optional(self): + '''Check snappy-systemd optional fields''' + self._verify_optional(self.systemd, 'hook') + + def check_snappy_optional(self): + '''Check snappy packate.yaml optional fields''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_optional(self._create_dict(self.pkg_yaml['services']), + 'package_yaml') + + def _verify_unknown(self, my_dict, test_str): + for app in sorted(my_dict): + f = os.path.basename(self.systemd_files[app]) + unknown = [] + t = 'info' + n = '%s_unknown_key_%s' % (test_str, f) + s = "OK" + + for f in my_dict[app].keys(): + if f not in self.required_keys and \ + f not in self.optional_keys: + unknown.append(f) + + if len(unknown) == 1: + t = 'warn' + s = "Unknown field '%s'" % unknown[0] + elif len(unknown) > 1: + t = 'warn' + s = "Unknown fields '%s'" % ", ".join(unknown) + self._add_result(t, n, s) + + def check_unknown(self): + '''Check snappy-systemd unknown fields''' + self._verify_unknown(self.systemd, 'hook') + + def check_snappy_unknown(self): + '''Check snappy package.yaml unknown fields''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_unknown(self._create_dict(self.pkg_yaml['services']), + 'package_yaml') + + def _verify_service_description(self, my_dict, test_str): + '''Check snappy-systemd description''' + for app in sorted(my_dict): + f = os.path.basename(self.systemd_files[app]) + t = 'info' + n = '%s_description_present_%s' % (test_str, f) + s = 'OK' + if 'description' not in my_dict[app]: + s = 'required description field not specified' + self._add_result('error', n, s) + return + self._add_result(t, n, s) + + t = 'info' + n = '%s_description_empty_%s' % (test_str, f) + s = 'OK' + if len(my_dict[app]['description']) == 0: + t = 'error' + s = "description is empty" + self._add_result(t, n, s) + + def check_service_description(self): + '''Check snappy-systemd description''' + self._verify_service_description(self.systemd, 'hook') + + def check_snappy_service_description(self): + '''Check snappy package.yaml description''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_service_description(self._create_dict( + self.pkg_yaml['services']), + 'package_yaml') + + def _verify_entry(self, my_dict, d, test_str): + for app in sorted(my_dict): + if d not in my_dict[app]: + continue + f = os.path.basename(self.systemd_files[app]) + + t = 'info' + n = '%s_%s_empty_%s' % (test_str, d, f) + s = 'OK' + if len(my_dict[app][d]) == 0: + t = 'error' + s = "%s entry is empty" % d + self._add_result(t, n, s) + continue + self._add_result(t, n, s) + + t = 'info' + n = '%s_%s_absolute_path_%s' % (test_str, d, f) + s = 'OK' + if my_dict[app][d].startswith('/'): + t = 'error' + s = "'%s' should not specify absolute path" % my_dict[app][d] + self._add_result(t, n, s) + + def check_service_start(self): + '''Check snappy-systemd start''' + self._verify_entry(self.systemd, 'start', 'hook') + + def check_snappy_service_start(self): + '''Check snappy package.yaml start''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_entry(self._create_dict(self.pkg_yaml['services']), + 'start', 'package_yaml') + + def check_service_stop(self): + '''Check snappy-systemd stop''' + self._verify_entry(self.systemd, 'stop', 'hook') + + def check_snappy_service_stop(self): + '''Check snappy package.yaml stop''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_entry(self._create_dict(self.pkg_yaml['services']), + 'stop', 'package_yaml') + + def check_service_poststop(self): + '''Check snappy-systemd poststop''' + self._verify_entry(self.systemd, 'poststop', 'hook') + + def check_snappy_service_poststop(self): + '''Check snappy package.yaml poststop''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_entry(self._create_dict(self.pkg_yaml['services']), + 'poststop', 'package_yaml') + + def _verify_service_stop_timeout(self, my_dict, test_str): + for app in sorted(my_dict): + f = os.path.basename(self.systemd_files[app]) + t = 'info' + n = '%s_stop_timeout_%s' % (test_str, f) + s = "OK" + + if 'stop-timeout' not in my_dict[app]: + s = "OK (skip missing)" + elif not isinstance(my_dict[app]['stop-timeout'], int): + t = 'error' + s = 'stop-timeout is not an integer' + elif my_dict[app]['stop-timeout'] < 0 or \ + my_dict[app]['stop-timeout'] > 60: + t = 'error' + s = "stop-timeout '%d' out of range (0-60)" % \ + my_dict[app]['stop-timeout'] + + self._add_result(t, n, s) + + def check_service_stop_timeout(self): + '''Check snappy-systemd''' + self._verify_service_stop_timeout(self.systemd, 'hook') + + def check_snappy_service_stop_timeout(self): + '''Check snappy package.yaml top-timeout''' + if not self.is_snap or 'services' not in self.pkg_yaml: + return + self._verify_service_stop_timeout(self._create_dict( + self.pkg_yaml['services']), + 'package_yaml') diff -Nru click-reviewers-tools-0.21/clickreviews/cr_tests.py click-reviewers-tools-0.24/clickreviews/cr_tests.py --- click-reviewers-tools-0.21/clickreviews/cr_tests.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/cr_tests.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ '''cr_tests.py: common setup and tests for test modules''' # -# Copyright (C) 2013-2014 Canonical Ltd. +# Copyright (C) 2013-2015 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 @@ -19,6 +19,7 @@ import os import tempfile from xdg.DesktopEntry import DesktopEntry +import yaml from unittest.mock import patch from unittest import TestCase @@ -29,7 +30,10 @@ # These should be set in the test cases TEST_CONTROL = "" TEST_MANIFEST = "" +TEST_PKG_YAML = "" +TEST_README_MD = "" TEST_SECURITY = dict() +TEST_SECURITY_PROFILES = dict() TEST_DESKTOP = dict() TEST_WEBAPP_MANIFESTS = dict() TEST_URLS = dict() @@ -63,6 +67,16 @@ return io.StringIO(TEST_MANIFEST) +def _extract_package_yaml(self): + '''Pretend we read the package.yaml file''' + return io.StringIO(TEST_PKG_YAML) + + +def _extract_readme_md(self): + '''Pretend we read the meta/readme.md file''' + return TEST_README_MD + + def _extract_click_frameworks(self): '''Pretend we enumerated the click frameworks''' return ["ubuntu-sdk-13.10", @@ -95,6 +109,16 @@ return ("%s.apparmor" % app, json.loads(TEST_SECURITY[app])) +def _extract_security_profile(self, app): + '''Pretend we read the security profile''' + return io.StringIO(TEST_SECURITY_PROFILES[app]) + + +def _get_security_profile(self, app): + '''Pretend we read the security profile''' + return ("%s.profile" % app, TEST_SECURITY_PROFILES[app]) + + def _get_security_supported_policy_versions(self): '''Pretend we read the contens of /usr/share/apparmor/easyprof''' return [1.0, 1.1, 1.2, 1.3] @@ -176,7 +200,7 @@ def _extract_systemd(self, app): '''Pretend we found the systemd file''' - return ("%s.click-service" % app, TEST_SNAPPY_SYSTEMD[app]) + return ("%s.snappy-systemd" % app, TEST_SNAPPY_SYSTEMD[app]) # http://docs.python.org/3.4/library/unittest.mock-examples.html @@ -201,6 +225,9 @@ 'clickreviews.cr_common.ClickReview._extract_manifest_file', _extract_manifest_file)) patches.append(patch( + 'clickreviews.cr_common.ClickReview._extract_package_yaml', + _extract_package_yaml)) +patches.append(patch( 'clickreviews.cr_common.ClickReview._extract_click_frameworks', _extract_click_frameworks)) patches.append(patch('clickreviews.cr_common.unpack_click', _mock_func)) @@ -219,6 +246,9 @@ patches.append(patch( 'clickreviews.cr_lint.ClickReview._list_all_compiled_binaries', _mock_func)) +patches.append(patch( + 'clickreviews.cr_lint.ClickReviewLint._extract_readme_md', + _extract_readme_md)) # security overrides patches.append(patch( @@ -227,6 +257,12 @@ patches.append(patch( 'clickreviews.cr_security.ClickReviewSecurity._get_security_manifest', _get_security_manifest)) +patches.append(patch( + 'clickreviews.cr_security.ClickReviewSecurity._extract_security_profile', + _extract_security_profile)) +patches.append(patch( + 'clickreviews.cr_security.ClickReviewSecurity._get_security_profile', + _get_security_profile)) # desktop overrides patches.append(patch( @@ -339,8 +375,19 @@ "%s.url-dispatcher" % self.default_appname self._update_test_manifest() + self.test_pkg_yaml = dict() + self.set_test_pkg_yaml("name", self.test_control['Package']) + self.set_test_pkg_yaml("version", self.test_control['Version']) + self.set_test_pkg_yaml("architecture", + self.test_control['Architecture']) + self._update_test_pkg_yaml() + + self.test_readme_md = self.test_control['Description'] + self._update_test_readme_md() + # hooks self.test_security_manifests = dict() + self.test_security_profiles = dict() self.test_desktop_files = dict() self.test_url_dispatcher = dict() self.test_scopes = dict() @@ -400,7 +447,11 @@ # Reset to no systemd entries in manifest self.set_test_systemd(app, None, None) + # Reset to no security profiles + self.set_test_security_profile(app, None) + self._update_test_security_manifests() + self._update_test_security_profiles() self._update_test_desktop_files() self._update_test_url_dispatcher() self._update_test_scopes() @@ -431,12 +482,31 @@ global TEST_MANIFEST TEST_MANIFEST = json.dumps(self.test_manifest) + def _update_test_pkg_yaml(self): + global TEST_PKG_YAML + TEST_PKG_YAML = yaml.dump(self.test_pkg_yaml, + default_flow_style=False, + indent=4) + + def _update_test_readme_md(self): + global TEST_README_MD + TEST_README_MD = self.test_readme_md + def _update_test_security_manifests(self): global TEST_SECURITY TEST_SECURITY = dict() for app in self.test_security_manifests.keys(): TEST_SECURITY[app] = json.dumps(self.test_security_manifests[app]) + def _update_test_security_profiles(self): + global TEST_SECURITY_PROFILES + TEST_SECURITY_PROFILES = dict() + for app in self.test_security_profiles.keys(): + TEST_SECURITY_PROFILES[app] = self.test_security_profiles[app] + self.test_manifest["hooks"][app]["apparmor-profile"] = \ + "%s.profile" % app + self._update_test_manifest() + def _update_test_desktop_files(self): global TEST_DESKTOP TEST_DESKTOP = dict() @@ -552,6 +622,9 @@ TEST_SNAPPY_SYSTEMD = dict() for app in self.test_systemd.keys(): TEST_SNAPPY_SYSTEMD[app] = self.test_systemd[app] + self.test_manifest["hooks"][app]["snappy-systemd"] = \ + "%s.snappy-systemd" % app + self._update_test_manifest() def _update_test_name(self): self.test_name = "%s_%s_%s.click" % (self.test_control['Package'], @@ -619,6 +692,24 @@ self.test_manifest[key] = value self._update_test_manifest() + def set_test_pkg_yaml(self, key, value): + '''Set key in meta/package.yaml to value. If value is None, remove + key''' + if value is None: + if key in self.test_pkg_yaml: + self.test_pkg_yaml.pop(key, None) + else: + self.test_pkg_yaml[key] = value + self._update_test_pkg_yaml() + + def set_test_readme_md(self, contents): + '''Set contents of meta/readme.md''' + if contents is None: + self.test_readme_md = None + else: + self.test_readme_md = contents + self._update_test_readme_md() + def set_test_security_manifest(self, app, key, value): '''Set key in security manifest to value. If value is None, remove key''' @@ -632,6 +723,17 @@ self.test_security_manifests[app][key] = value self._update_test_security_manifests() + def set_test_security_profile(self, app, policy): + '''Set policy in security profile''' + if policy is None: + if app in self.test_security_profiles: + self.test_security_profiles.pop(app) + else: + if app not in self.test_security_profiles: + self.test_security_profiles[app] = dict() + self.test_security_profiles[app] = policy + self._update_test_security_profiles() + def set_test_desktop(self, app, key, value, no_update=False): '''Set key in desktop file to value. If value is None, remove key''' if app not in self.test_desktop_files: @@ -774,17 +876,21 @@ self.test_framework[app][key] = value self._update_test_framework() - def set_test_systemd(self, app, key, value, append=False): - '''Set systemd entries. If value is None, remove''' - if app not in self.test_systemd: - self.test_systemd[app] = [] - - if value is None: - self.test_systemd[app] = [] + def set_test_systemd(self, app, key, value): + '''Set systemd entries. If key is None, remove hook, if value is None, + remove key''' + if key is None: + if app in self.test_systemd: + self.test_systemd.pop(app) else: - if not append: - self.test_systemd[app] = [] - self.test_systemd[app].append({key: value}) + if app not in self.test_systemd: + self.test_systemd[app] = {} + + if value is None: + if key in self.test_systemd[app]: + del(self.test_systemd[app][key]) + else: + self.test_systemd[app][key] = value self._update_test_systemd() def setUp(self): @@ -799,8 +905,14 @@ TEST_CONTROL = "" global TEST_MANIFEST TEST_MANIFEST = "" + global TEST_PKG_YAML + TEST_PKG_YAML = "" + global TEST_README_MD + TEST_README_MD = "" global TEST_SECURITY TEST_SECURITY = dict() + global TEST_SECURITY_PROFILES + TEST_SECURITY_PROFILES = dict() global TEST_DESKTOP TEST_DESKTOP = dict() global TEST_URLS diff -Nru click-reviewers-tools-0.21/clickreviews/tests/test_cr_bin_path.py click-reviewers-tools-0.24/clickreviews/tests/test_cr_bin_path.py --- click-reviewers-tools-0.21/clickreviews/tests/test_cr_bin_path.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/tests/test_cr_bin_path.py 2015-03-10 15:16:29.000000000 +0000 @@ -26,6 +26,15 @@ cr_tests.mock_patch() super() + def _set_binary(self, key, value, name=None): + d = dict() + if name is None: + d['name'] = 'foo' + else: + d['name'] = name + d[key] = value + self.set_test_pkg_yaml("binaries", [d]) + def test_check_path(self): '''Test check_path()''' self.set_test_bin_path(self.default_appname, "bin/foo.exe") @@ -57,7 +66,6 @@ tmp["bin-path"] = "usr/bin/foo" # add any required peer hooks - tmp["snappy-systemd"] = "foo.systemd" tmp["apparmor"] = "foo.apparmor" c.manifest["hooks"][self.default_appname] = tmp @@ -116,3 +124,30 @@ r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) + + def test_check_binary_description(self): + '''Test check_binary_description()''' + self._set_binary("description", "some description") + c = ClickReviewBinPath(self.test_name) + c.check_binary_description() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_binary_description_unspecified(self): + '''Test check_binary_description() - unspecified''' + self._set_binary("name", "foo") + c = ClickReviewBinPath(self.test_name) + c.check_binary_description() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_binary_description_empty(self): + '''Test check_binary_description() - empty''' + self._set_binary("description", "") + c = ClickReviewBinPath(self.test_name) + c.check_binary_description() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) diff -Nru click-reviewers-tools-0.21/clickreviews/tests/test_cr_desktop.py click-reviewers-tools-0.24/clickreviews/tests/test_cr_desktop.py --- click-reviewers-tools-0.21/clickreviews/tests/test_cr_desktop.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/tests/test_cr_desktop.py 2015-03-10 15:16:29.000000000 +0000 @@ -224,7 +224,7 @@ ex) c.check_desktop_exec_webapp_args() r = c.click_report - expected_counts = {'info': None, 'warn': 0, 'error': 1} + expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_missing_exec(self): diff -Nru click-reviewers-tools-0.21/clickreviews/tests/test_cr_lint.py click-reviewers-tools-0.24/clickreviews/tests/test_cr_lint.py --- click-reviewers-tools-0.21/clickreviews/tests/test_cr_lint.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/tests/test_cr_lint.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ '''test_cr_lint.py: tests for the cr_lint module''' # -# Copyright (C) 2013-2014 Canonical Ltd. +# Copyright (C) 2013-2015 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 @@ -172,7 +172,7 @@ c.pkg_bin_files = [] c.check_architecture_specified_needed() r = c.click_report - expected_counts = {'info': None, 'warn': 0, 'error': 1} + expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_specified_needed2(self): @@ -780,6 +780,17 @@ expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) + def test_check_hooks_missing_apparmor_with_apparmor_profile(self): + '''Test check_hooks() - missing apparmor with apparmor-profile''' + self.set_test_manifest("framework", "ubuntu-sdk-13.10") + c = ClickReviewLint(self.test_name) + del c.manifest['hooks'][self.default_appname]['apparmor'] + c.manifest['hooks'][self.default_appname]['apparmor-profile'] = 'foo' + c.check_hooks() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + def test_check_hooks_has_desktop_and_scope(self): '''Test check_hooks() - desktop with scope''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") @@ -809,8 +820,8 @@ expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) - def test_check_hooks_redflagged(self): - '''Test check_hooks_redflagged()''' + def test_check_hooks_redflagged_payui(self): + '''Test check_hooks_redflagged() - pay-ui''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.manifest['hooks'][self.default_appname]["pay-ui"] = "foo" @@ -819,3 +830,499 @@ expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) self.check_manual_review(r, 'lint_hooks_redflag_test-app') + + def test_check_hooks_redflagged_apparmor_profile(self): + '''Test check_hooks_redflagged() - apparmor-profile''' + self.set_test_manifest("framework", "ubuntu-sdk-13.10") + c = ClickReviewLint(self.test_name) + c.manifest['hooks'][self.default_appname]["apparmor-profile"] = "foo" + c.check_hooks_redflagged() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + self.check_manual_review(r, 'lint_hooks_redflag_test-app') + + def test_snappy_name1(self): + '''Test check_snappy_name - toplevel''' + self.set_test_pkg_yaml("name", "foo") + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_name2(self): + '''Test check_snappy_name - flat''' + self.set_test_pkg_yaml("name", "foo.bar") + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_name3(self): + '''Test check_snappy_name - reverse domain''' + self.set_test_pkg_yaml("name", "com.ubuntu.develeper.baz.foo") + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_name_bad(self): + '''Test check_snappy_name - bad''' + self.set_test_pkg_yaml("name", "foo?bar") + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_name_bad2(self): + '''Test check_snappy_name - empty''' + self.set_test_pkg_yaml("name", "") + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_name_bad3(self): + '''Test check_snappy_name - list''' + self.set_test_pkg_yaml("name", []) + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_name_bad4(self): + '''Test check_snappy_name - dict''' + self.set_test_pkg_yaml("name", {}) + c = ClickReviewLint(self.test_name) + c.check_snappy_name() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_version1(self): + '''Test check_snappy_version - integer''' + self.set_test_pkg_yaml("version", 1) + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_version2(self): + '''Test check_snappy_version - float''' + self.set_test_pkg_yaml("version", 1.0) + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_version3(self): + '''Test check_snappy_version - MAJOR.MINOR.MICRO''' + self.set_test_pkg_yaml("version", "1.0.1") + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_version4(self): + '''Test check_snappy_version - str''' + self.set_test_pkg_yaml("version", "1.0a") + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_version5(self): + '''Test check_snappy_version - alpha''' + self.set_test_pkg_yaml("version", "a.b") + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_version_bad(self): + '''Test check_snappy_version - bad''' + self.set_test_pkg_yaml("version", "foo?bar") + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_version_bad2(self): + '''Test check_snappy_version - empty''' + self.set_test_pkg_yaml("version", "") + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_version_bad3(self): + '''Test check_snappy_version - list''' + self.set_test_pkg_yaml("version", []) + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_version_bad4(self): + '''Test check_snappy_version - dict''' + self.set_test_pkg_yaml("version", {}) + c = ClickReviewLint(self.test_name) + c.check_snappy_version() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_type(self): + '''Test check_snappy_type - unspecified''' + self.set_test_pkg_yaml("type", None) + c = ClickReviewLint(self.test_name) + c.check_snappy_type() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_type_app(self): + '''Test check_snappy_type - app''' + self.set_test_pkg_yaml("type", "app") + c = ClickReviewLint(self.test_name) + c.check_snappy_type() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_type_framework(self): + '''Test check_snappy_type - framework''' + self.set_test_pkg_yaml("type", "framework") + c = ClickReviewLint(self.test_name) + c.check_snappy_type() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_type_framework_policy(self): + '''Test check_snappy_type - framework-policy''' + self.set_test_pkg_yaml("type", "framework-policy") + c = ClickReviewLint(self.test_name) + c.check_snappy_type() + r = c.click_report + # TODO: update this when 'framework-policy' is implemented + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_type_oem(self): + '''Test check_snappy_type - oem''' + self.set_test_pkg_yaml("type", "oem") + c = ClickReviewLint(self.test_name) + c.check_snappy_type() + r = c.click_report + # TODO: update this when 'oem' is implemented + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_type_redflagged(self): + '''Test check_snappy_type_redflagged - unspecified''' + self.set_test_pkg_yaml("type", None) + c = ClickReviewLint(self.test_name) + c.check_snappy_type_redflagged() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_type_redflagged_app(self): + '''Test check_snappy_type_redflagged - app''' + self.set_test_pkg_yaml("type", "app") + c = ClickReviewLint(self.test_name) + c.check_snappy_type_redflagged() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_type_redflagged_framework(self): + '''Test check_snappy_type_redflagged - framework''' + self.set_test_pkg_yaml("type", "framework") + c = ClickReviewLint(self.test_name) + c.check_snappy_type_redflagged() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_vendor(self): + '''Test check_snappy_vendor''' + self.set_test_pkg_yaml("vendor", "Foo Bar ") + c = ClickReviewLint(self.test_name) + c.check_snappy_vendor() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_vendor_missing(self): + '''Test check_snappy_vendor - missing''' + self.set_test_pkg_yaml("vendor", None) + c = ClickReviewLint(self.test_name) + c.check_snappy_vendor() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_snappy_vendor_empty(self): + '''Test check_snappy_vendor - empty''' + self.set_test_pkg_yaml("vendor", "") + c = ClickReviewLint(self.test_name) + c.check_snappy_vendor() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_snappy_vendor_bad(self): + '''Test check_snappy_vendor - bad''' + self.set_test_pkg_yaml("vendor", "Foo Bar") + c = ClickReviewLint(self.test_name) + c.check_snappy_vendor() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_icon(self): + '''Test check_snappy_icon()''' + self.set_test_pkg_yaml("icon", "someicon") + c = ClickReviewLint(self.test_name) + c.check_snappy_icon() + r = c.click_report + expected_counts = {'info': 3, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_icon_unspecified(self): + '''Test check_snappy_icon() - unspecified''' + self.set_test_pkg_yaml("icon", None) + c = ClickReviewLint(self.test_name) + c.check_snappy_icon() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_icon_empty(self): + '''Test check_snappy_icon() - empty''' + self.set_test_pkg_yaml("icon", "") + c = ClickReviewLint(self.test_name) + c.check_snappy_icon() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_icon_absolute_path(self): + '''Test check_snappy_icon() - absolute path''' + self.set_test_pkg_yaml("icon", "/foo/bar/someicon") + c = ClickReviewLint(self.test_name) + c.check_snappy_icon() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_missing_arch(self): + '''Test check_snappy_architecture() (missing)''' + self.set_test_pkg_yaml("architecture", None) + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_arch_all(self): + '''Test check_snappy_architecture() (all)''' + self.set_test_pkg_yaml("architecture", "all") + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_arch_single_armhf(self): + '''Test check_snappy_architecture() (single arch, armhf)''' + self.set_test_pkg_yaml("architecture", "armhf") + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_arch_single_i386(self): + '''Test check_snappy_architecture() (single arch, i386)''' + self.set_test_pkg_yaml("architecture", "i386") + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_arch_single_amd64(self): + '''Test check_snappy_architecture() (single arch, amd64)''' + self.set_test_pkg_yaml("architecture", "amd64") + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_arch_single_nonexistent(self): + '''Test check_snappy_architecture() (single nonexistent arch)''' + self.set_test_pkg_yaml("architecture", "nonexistent") + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_arch_single_multi(self): + '''Test check_snappy_architecture() (single arch: invalid multi)''' + self.set_test_pkg_yaml("architecture", "multi") + c = ClickReviewLint(self.test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_valid_arch_multi(self): + '''Test check_snappy_architecture() (valid multi)''' + arch = "multi" + self.set_test_pkg_yaml("architecture", ["armhf"]) + self.set_test_control("Architecture", arch) + test_name = "%s_%s_%s.snap" % (self.test_control['Package'], + self.test_control['Version'], + arch) + c = ClickReviewLint(test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_valid_arch_multi2(self): + '''Test check_snappy_architecture() (valid multi2)''' + arch = "multi" + self.set_test_pkg_yaml("architecture", ["armhf", "i386"]) + self.set_test_control("Architecture", arch) + test_name = "%s_%s_%s.snap" % (self.test_control['Package'], + self.test_control['Version'], + arch) + c = ClickReviewLint(test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_invalid_arch_multi_nonexistent(self): + '''Test check_snappy_architecture() (invalid multi)''' + arch = "multi" + self.set_test_pkg_yaml("architecture", ["armhf", "nonexistent"]) + self.set_test_control("Architecture", arch) + test_name = "%s_%s_%s.snap" % (self.test_control['Package'], + self.test_control['Version'], + arch) + c = ClickReviewLint(test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_invalid_arch_multi_all(self): + '''Test check_snappy_architecture() (invalid all)''' + arch = "multi" + self.set_test_pkg_yaml("architecture", ["armhf", "all"]) + self.set_test_control("Architecture", arch) + test_name = "%s_%s_%s.snap" % (self.test_control['Package'], + self.test_control['Version'], + arch) + c = ClickReviewLint(test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_invalid_arch_multi_multi(self): + '''Test check_snappy_architecture() (invalid multi)''' + arch = "multi" + self.set_test_pkg_yaml("architecture", ["multi", "armhf"]) + self.set_test_control("Architecture", arch) + test_name = "%s_%s_%s.snap" % (self.test_control['Package'], + self.test_control['Version'], + arch) + c = ClickReviewLint(test_name) + c.check_snappy_architecture() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_unknown_entries(self): + '''Test check_snappy_unknown_entries - none''' + self.set_test_pkg_yaml("name", "foo") + c = ClickReviewLint(self.test_name) + c.check_snappy_unknown_entries() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_unknown_entries2(self): + '''Test check_snappy_unknown_entries - one''' + self.set_test_pkg_yaml("nonexistent", "bar") + c = ClickReviewLint(self.test_name) + c.check_snappy_unknown_entries() + r = c.click_report + expected_counts = {'info': 0, 'warn': 1, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_unknown_obsoleted(self): + '''Test check_snappy_unknown_entries - obsoleted''' + self.set_test_pkg_yaml("maintainer", "bar") + c = ClickReviewLint(self.test_name) + c.check_snappy_unknown_entries() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + # Lets check that the right warning is triggering + m = r['error']['lint_snappy_unknown']['text'] + self.assertIn("unknown entries in package.yaml: 'maintainer' " + "(maintainer obsoleted)", m) + + def test_check_snappy_readme_md(self): + '''Test check_snappy_readme_md()''' + self.set_test_pkg_yaml("name", self.test_name.split('_')[0]) + self.set_test_readme_md("%s - some description" % + self.test_name.split('_')[0]) + c = ClickReviewLint(self.test_name) + c.check_snappy_readme_md() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_readme_md_bad(self): + '''Test check_snappy_readme_md() - short''' + self.set_test_pkg_yaml("name", "prettylong.name") + self.set_test_readme_md("abc") + c = ClickReviewLint(self.test_name) + c.check_snappy_readme_md() + r = c.click_report + expected_counts = {'info': 1, 'warn': 1, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_readme_md_bad2(self): + '''Test check_snappy_readme_md() - missing''' + self.set_test_pkg_yaml("name", self.test_name.split('_')[0]) + self.set_test_readme_md(None) + c = ClickReviewLint(self.test_name) + c.check_snappy_readme_md() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) diff -Nru click-reviewers-tools-0.21/clickreviews/tests/test_cr_security.py click-reviewers-tools-0.24/clickreviews/tests/test_cr_security.py --- click-reviewers-tools-0.21/clickreviews/tests/test_cr_security.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/tests/test_cr_security.py 2015-03-10 15:16:29.000000000 +0000 @@ -1,6 +1,6 @@ '''test_cr_security.py: tests for the cr_security module''' # -# Copyright (C) 2013-2014 Canonical Ltd. +# Copyright (C) 2013-2015 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 @@ -244,7 +244,7 @@ c = ClickReviewSecurity(self.test_name) c.check_policy_vendor() report = c.click_report - expected_counts = {'info': 1, 'warn': 0, 'error': 0} + expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_ubuntu(self): @@ -254,11 +254,12 @@ "policy_vendor", "ubuntu") c.check_policy_vendor() report = c.click_report - expected_counts = {'info': 1, 'warn': 0, 'error': 0} + expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_ubuntu_snappy(self): '''Test check_policy_vendor() - ubuntu-snappy''' + self.set_test_manifest("framework", "ubuntu-core-15.04") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu-snappy") @@ -266,17 +267,112 @@ "policy_version", 1.3) c.check_policy_vendor() report = c.click_report - expected_counts = {'info': 1, 'warn': 0, 'error': 0} + expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_nonexistent(self): '''Test check_policy_vendor() - nonexistent''' + self.set_test_manifest("framework", "nonexistent") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, - "policy_vendor", "nonexistent") + "policy_vendor", "ubuntu") c.check_policy_vendor() report = c.click_report - expected_counts = {'info': 0, 'warn': 0, 'error': 1} + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(report, expected_counts) + + def test_check_policy_vendor_framework(self): + '''Test check_policy_vendor() - matching framework''' + tmp = ClickReviewSecurity(self.test_name) + # for each installed framework on the system, verify that the policy + # matches the framework + for f in tmp.valid_frameworks: + self.set_test_manifest("framework", f) + policy_vendor = "ubuntu" + for k in tmp.major_framework_policy.keys(): + if f.startswith(k): + if 'policy_vendor' not in tmp.major_framework_policy[k]: + policy_vendor = 'ubuntu' + else: + policy_vendor = tmp.major_framework_policy[k]['policy_vendor'] + self.set_test_security_manifest(self.default_appname, + "policy_vendor", + policy_vendor) + c = ClickReviewSecurity(self.test_name) + c.check_policy_vendor() + report = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(report, expected_counts) + + def test_check_policy_vendor_framework_unmatch1(self): + '''Test check_policy_vendor() - unmatching framework''' + self.set_test_security_manifest(self.default_appname, + "policy_vendor", "ubuntu-snappy") + c = ClickReviewSecurity(self.test_name) + c.check_policy_vendor() + report = c.click_report + + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(report, expected_counts) + + expected = dict() + expected['info'] = dict() + expected['warn'] = dict() + expected['error'] = dict() + expected['error']["security_policy_vendor_matches_framework (%s)" % + self.default_security_json] = \ + {"text": "ubuntu-snappy != ubuntu (ubuntu-sdk-13.10)"} + self.check_results(report, expected=expected) + + def test_check_policy_vendor_framework_unmatch2(self): + '''Test check_policy_vendor() - unmatching framework - nonexistent''' + self.set_test_manifest("framework", "nonexistent") + self.set_test_security_manifest(self.default_appname, + "policy_vendor", "ubuntu") + c = ClickReviewSecurity(self.test_name) + c.check_policy_vendor() + report = c.click_report + + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(report, expected_counts) + + expected = dict() + expected['info'] = dict() + expected['warn'] = dict() + expected['error'] = dict() + expected['error']["security_policy_vendor_matches_framework (%s)" % + self.default_security_json] = \ + {"text": "Invalid framework 'nonexistent'"} + self.check_results(report, expected=expected) + + def test_check_policy_vendor_framework_with_overrides(self): + '''Test check_policy_vendor() - override framework (nonexistent)''' + self.set_test_manifest("framework", "nonexistent") + self.set_test_security_manifest(self.default_appname, + "policy_vendor", "ubuntu") + overrides = {'framework': {'nonexistent': {'state': 'available', + 'policy_vendor': 'ubuntu', + 'policy_version': 1.2}}} + c = ClickReviewSecurity(self.test_name, overrides=overrides) + c.check_policy_vendor() + report = c.click_report + + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(report, expected_counts) + + def test_check_policy_vendor_framework_with_malformed_overrides(self): + '''Test check_policy_vendor() - incorrectly override framework''' + self.set_test_manifest("framework", "nonexistent") + self.set_test_security_manifest(self.default_appname, + "policy_vendor", "ubuntu") + overrides = {'nonexistent': {'state': 'available', + 'policy_vendor': 'ubuntu', + 'policy_version': 1.2}} + c = ClickReviewSecurity(self.test_name, overrides=overrides) + c.check_policy_vendor() + report = c.click_report + + expected_counts = {'info': 1, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_template_unspecified(self): @@ -838,8 +934,8 @@ # do the test c.check_peer_hooks() r = c.click_report - # We should end up with 2 info - expected_counts = {'info': 2, 'warn': 0, 'error': 0} + # We should end up with 4 info + expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_disallowed(self): @@ -864,6 +960,71 @@ expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) + def test_check_peer_hooks_disallowed_apparmor_profile(self): + '''Test check_peer_hooks() - disallowed (apparmor-profile)''' + c = ClickReviewSecurity(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["apparmor"] = "foo.apparmor" + + # add something not allowed + tmp["apparmor-profile"] = "foo.profile" + + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_peer_hooks_aa_profile(self): + '''Test check_peer_hooks() - apparmor-profile''' + c = ClickReviewSecurity(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["apparmor-profile"] = "foo.profile" + + # update the manifest and test_manifest + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + # We should end up with 4 info + expected_counts = {'info': 4, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_peer_hooks_aa_profile_disallowed(self): + '''Test check_peer_hooks() - disallowed - apparmor-profile''' + c = ClickReviewSecurity(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["apparmor-profile"] = "foo.profile" + + # add something not allowed + tmp["framework"] = "foo.framework" + + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + def test_check_redflag_policy_vendor_ubuntu(self): '''Test check_redflag() - policy_vendor - ubuntu''' c = ClickReviewSecurity(self.test_name) @@ -943,3 +1104,36 @@ report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) + + def test_check_apparmor_profile(self): + '''Test check_apparmor_profile()''' + policy = ''' +###VAR### +###PROFILEATTACH### { + #include + # Read-only for the install directory + @{CLICK_DIR}/@{APP_PKGNAME}/@{APP_VERSION}/** mrklix, +} +''' + self.set_test_security_profile(self.default_appname, policy) + c = ClickReviewSecurity(self.test_name) + c.check_apparmor_profile() + report = c.click_report + expected_counts = {'info': 5, 'warn': 0, 'error': 0} + self.check_results(report, expected_counts) + + def test_check_apparmor_profile_missing_var(self): + '''Test check_apparmor_profile() - missing ###VAR###''' + policy = ''' +###PROFILEATTACH### { + #include + # Read-only for the install directory + @{CLICK_DIR}/@{APP_PKGNAME}/@{APP_VERSION}/** mrklix, +} +''' + self.set_test_security_profile(self.default_appname, policy) + c = ClickReviewSecurity(self.test_name) + c.check_apparmor_profile() + report = c.click_report + expected_counts = {'info': None, 'warn': 1, 'error': 0} + self.check_results(report, expected_counts) diff -Nru click-reviewers-tools-0.21/clickreviews/tests/test_cr_systemd.py click-reviewers-tools-0.24/clickreviews/tests/test_cr_systemd.py --- click-reviewers-tools-0.21/clickreviews/tests/test_cr_systemd.py 1970-01-01 00:00:00.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/tests/test_cr_systemd.py 2015-03-10 15:16:29.000000000 +0000 @@ -0,0 +1,789 @@ +'''test_cr_systemd.py: tests for the cr_systemd module''' +# +# Copyright (C) 2015 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 . + +from clickreviews.cr_systemd import ClickReviewSystemd +import clickreviews.cr_tests as cr_tests + + +class TestClickReviewSystemd(cr_tests.TestClickReview): + """Tests for the lint review tool.""" + def setUp(self): + # Monkey patch various file access classes. stop() is handled with + # addCleanup in super() + cr_tests.mock_patch() + super() + + def _set_service(self, key, value, name=None): + d = dict() + if name is None: + d['name'] = 'foo' + else: + d['name'] = name + d[key] = value + self.set_test_pkg_yaml("services", [d]) + + def test_check_required(self): + '''Test check_required() - has start and description''' + self.set_test_systemd(self.default_appname, + key="start", + value="bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + c = ClickReviewSystemd(self.test_name) + c.check_required() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_required_empty_value(self): + '''Test check_required() - empty start''' + self.set_test_systemd(self.default_appname, + key="start", + value="") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + c = ClickReviewSystemd(self.test_name) + c.check_required() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_required_bad_value(self): + '''Test check_required() - bad start''' + self.set_test_systemd(self.default_appname, + key="start", + value=[]) + self.set_test_systemd(self.default_appname, + key="description", + value="something") + c = ClickReviewSystemd(self.test_name) + c.check_required() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_required_multiple(self): + '''Test check_required() - multiple''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + c = ClickReviewSystemd(self.test_name) + c.check_required() + r = c.click_report + expected_counts = {'info': -1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_required_multiple(self): + '''Test check_required() - multiple with nonexistent''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + self.set_test_systemd(self.default_appname, + key="nonexistent", + value="foo") + c = ClickReviewSystemd(self.test_name) + c.check_required() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_optional_none(self): + '''Test check_optional() - start only''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + c = ClickReviewSystemd(self.test_name) + c.check_optional() + r = c.click_report + expected_counts = {'info': 3, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_optional_stop_empty(self): + '''Test check_optional() - with empty stop''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + self.set_test_systemd(self.default_appname, + key="stop", + value="") + c = ClickReviewSystemd(self.test_name) + c.check_optional() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_optional_stop_bad(self): + '''Test check_optional() - with bad stop''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + self.set_test_systemd(self.default_appname, + key="stop", + value=[]) + c = ClickReviewSystemd(self.test_name) + c.check_optional() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_optional_stop_nonexistent(self): + '''Test check_optional() - with stop plus nonexistent''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + self.set_test_systemd(self.default_appname, + key="stop", + value="bin/bar") + self.set_test_systemd(self.default_appname, + key="nonexistent", + value="foo") + c = ClickReviewSystemd(self.test_name) + c.check_optional() + r = c.click_report + expected_counts = {'info': 3, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_optional_stop_without_start(self): + '''Test check_optional() - with stop, no start''' + self.set_test_systemd(self.default_appname, + key="stop", + value="/bin/bar") + c = ClickReviewSystemd(self.test_name) + c.check_optional() + r = c.click_report + expected_counts = {'info': 3, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_optional_stop_without_start2(self): + '''Test check_optional() - with stop, nonexistent, no start''' + self.set_test_systemd(self.default_appname, + key="stop", + value="/bin/bar") + self.set_test_systemd(self.default_appname, + key="nonexistent", + value="example.com") + c = ClickReviewSystemd(self.test_name) + c.check_optional() + r = c.click_report + expected_counts = {'info': 3, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_unknown(self): + '''Test check_unknown()''' + self.set_test_systemd(self.default_appname, + key="nonexistent", + value="foo") + c = ClickReviewSystemd(self.test_name) + c.check_unknown() + r = c.click_report + expected_counts = {'info': 0, 'warn': 1, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_unknown_multiple(self): + '''Test check_unknown() - multiple with nonexistent''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + self.set_test_systemd(self.default_appname, + key="stop", + value="bin/bar") + self.set_test_systemd(self.default_appname, + key="nonexistent", + value="foo") + c = ClickReviewSystemd(self.test_name) + c.check_unknown() + r = c.click_report + expected_counts = {'info': 0, 'warn': 1, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_peer_hooks(self): + '''Test check_peer_hooks()''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + c = ClickReviewSystemd(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["snappy-systemd"] = "meta/foo.snappy-systemd" + + # add required hooks + tmp["apparmor"] = "meta/foo.apparmor" + + # update the manifest and test_manifest + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + # We should end up with 2 info + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_peer_hooks2(self): + '''Test check_peer_hooks() - apparmor-profile''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + c = ClickReviewSystemd(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["snappy-systemd"] = "meta/foo.snappy-systemd" + + # add required hooks + tmp["apparmor-profile"] = "meta/foo.profile" + + # update the manifest and test_manifest + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + # We should end up with 2 info + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_peer_hooks_disallowed(self): + '''Test check_peer_hooks() - disallowed''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + c = ClickReviewSystemd(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["snappy-systemd"] = "meta/foo.snappy-systemd" + + # add required hooks + tmp["apparmor"] = "meta/foo.apparmor" + + # add something not allowed + tmp["bin-path"] = "bin/bar" + + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_peer_hooks_disallowed2(self): + '''Test check_peer_hooks() - disallowed (nonexistent)''' + self.set_test_systemd(self.default_appname, + key="start", + value="/bin/foo") + c = ClickReviewSystemd(self.test_name) + + # create a new hooks database for our peer hooks tests + tmp = dict() + + # add our hook + tmp["snappy-systemd"] = "meta/foo.snappy-systemd" + + # add required hooks + tmp["apparmor"] = "meta/foo.apparmor" + + # add something not allowed + tmp["nonexistent"] = "nonexistent-hook" + + c.manifest["hooks"][self.default_appname] = tmp + self._update_test_manifest() + + # do the test + c.check_peer_hooks() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_description(self): + '''Test check_service_description()''' + self.set_test_systemd(self.default_appname, + "description", + "some description") + c = ClickReviewSystemd(self.test_name) + c.check_service_description() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_description_unspecified(self): + '''Test check_service_description() - unspecified''' + self.set_test_systemd(self.default_appname, + "description", + None) + c = ClickReviewSystemd(self.test_name) + c.check_service_description() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_description_empty(self): + '''Test check_service_description() - empty''' + self.set_test_systemd(self.default_appname, + "description", + "") + c = ClickReviewSystemd(self.test_name) + c.check_service_description() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_start(self): + '''Test check_service_start()''' + self.set_test_systemd(self.default_appname, + "start", + "some/start") + c = ClickReviewSystemd(self.test_name) + c.check_service_start() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_start_unspecified(self): + '''Test check_service_start() - unspecified''' + self.set_test_systemd(self.default_appname, + "start", + None) + c = ClickReviewSystemd(self.test_name) + c.check_service_start() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_start_empty(self): + '''Test check_service_start() - empty''' + self.set_test_systemd(self.default_appname, + "start", + "") + c = ClickReviewSystemd(self.test_name) + c.check_service_start() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_start_absolute_path(self): + '''Test check_service_start() - absolute path''' + self.set_test_systemd(self.default_appname, + "start", + "/foo/bar/some/start") + c = ClickReviewSystemd(self.test_name) + c.check_service_start() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_stop(self): + '''Test check_service_stop()''' + self.set_test_systemd(self.default_appname, + "stop", + "some/stop") + c = ClickReviewSystemd(self.test_name) + c.check_service_stop() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_stop_unspecified(self): + '''Test check_service_stop() - unspecified''' + self.set_test_systemd(self.default_appname, + "stop", + None) + c = ClickReviewSystemd(self.test_name) + c.check_service_stop() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_stop_empty(self): + '''Test check_service_stop() - empty''' + self.set_test_systemd(self.default_appname, + "stop", + "") + c = ClickReviewSystemd(self.test_name) + c.check_service_stop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_stop_absolute_path(self): + '''Test check_service_stop() - absolute path''' + self.set_test_systemd(self.default_appname, + "stop", + "/foo/bar/some/stop") + c = ClickReviewSystemd(self.test_name) + c.check_service_stop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_poststop(self): + '''Test check_service_poststop()''' + self.set_test_systemd(self.default_appname, + "poststop", + "some/poststop") + c = ClickReviewSystemd(self.test_name) + c.check_service_poststop() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_poststop_unspecified(self): + '''Test check_service_poststop() - unspecified''' + self.set_test_systemd(self.default_appname, + "poststop", + None) + c = ClickReviewSystemd(self.test_name) + c.check_service_poststop() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_poststop_empty(self): + '''Test check_service_poststop() - empty''' + self.set_test_systemd(self.default_appname, + "poststop", + "") + c = ClickReviewSystemd(self.test_name) + c.check_service_poststop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_poststop_absolute_path(self): + '''Test check_service_poststop() - absolute path''' + self.set_test_systemd(self.default_appname, + "poststop", + "/foo/bar/some/poststop") + c = ClickReviewSystemd(self.test_name) + c.check_service_poststop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_stop_timeout(self): + '''Test check_service_stop_timeout()''' + self.set_test_systemd(self.default_appname, + key="start", + value="bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + self.set_test_systemd(self.default_appname, + key="stop-timeout", + value=30) + c = ClickReviewSystemd(self.test_name) + c.check_service_stop_timeout() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_service_stop_timeout_empty(self): + '''Test check_service_stop_timeout() - empty''' + self.set_test_systemd(self.default_appname, + key="start", + value="bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + self.set_test_systemd(self.default_appname, + key="stop-timeout", + value="") + c = ClickReviewSystemd(self.test_name) + c.check_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_stop_timeout_bad(self): + '''Test check_service_stop_timeout() - bad''' + self.set_test_systemd(self.default_appname, + key="start", + value="bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + self.set_test_systemd(self.default_appname, + key="stop-timeout", + value="a") + c = ClickReviewSystemd(self.test_name) + c.check_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_stop_timeout_range_low(self): + '''Test check_service_stop_timeout() - out of range (low)''' + self.set_test_systemd(self.default_appname, + key="start", + value="bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + self.set_test_systemd(self.default_appname, + key="stop-timeout", + value=-1) + c = ClickReviewSystemd(self.test_name) + c.check_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_service_stop_timeout_range_high(self): + '''Test check_service_stop_timeout() - out of range (high)''' + self.set_test_systemd(self.default_appname, + key="start", + value="bin/foo") + self.set_test_systemd(self.default_appname, + key="description", + value="something") + self.set_test_systemd(self.default_appname, + key="stop-timeout", + value=61) + c = ClickReviewSystemd(self.test_name) + c.check_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_description(self): + '''Test check_snappy_service_description()''' + self._set_service("description", "some description") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_description() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_description_unspecified(self): + '''Test check_snappy_service_description() - unspecified''' + # self._set_service("description", None) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_description() + r = c.click_report + # required check is done elsewhere, so no error + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_description_empty(self): + '''Test check_snappy_service_description() - empty''' + self._set_service("description", "") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_description() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_start(self): + '''Test check_snappy_service_start()''' + self._set_service("start", "some/start") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_start() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_start_unspecified(self): + '''Test check_snappy_service_start() - unspecified''' + # self._set_service("start", None) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_start() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_start_empty(self): + '''Test check_snappy_service_start() - empty''' + self._set_service("start", "") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_start() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_start_absolute_path(self): + '''Test check_snappy_service_start() - absolute path''' + self._set_service("start", "/foo/bar/some/start") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_start() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop(self): + '''Test check_snappy_service_stop()''' + self._set_service("stop", "some/stop") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_unspecified(self): + '''Test check_snappy_service_stop() - unspecified''' + # self._set_service("stop", None) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_empty(self): + '''Test check_snappy_service_stop() - empty''' + self._set_service("stop", "") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_absolute_path(self): + '''Test check_snappy_service_stop() - absolute path''' + self._set_service("stop", "/foo/bar/some/stop") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_poststop(self): + '''Test check_snappy_service_poststop()''' + self._set_service("poststop", "some/poststop") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_poststop() + r = c.click_report + expected_counts = {'info': 2, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_poststop_unspecified(self): + '''Test check_snappy_service_poststop() - unspecified''' + # self._set_service("poststop", None) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_poststop() + r = c.click_report + expected_counts = {'info': 0, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_poststop_empty(self): + '''Test check_snappy_service_poststop() - empty''' + self._set_service("poststop", "") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_poststop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_poststop_absolute_path(self): + '''Test check_snappy_service_poststop() - absolute path''' + self._set_service("poststop", "/foo/bar/some/poststop") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_poststop() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_timeout(self): + '''Test check_snappy_service_stop_timeout()''' + self._set_service(key="start", value="bin/foo") + self._set_service(key="description", value="something") + self._set_service(key="stop-timeout", value=30) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop_timeout() + r = c.click_report + expected_counts = {'info': 1, 'warn': 0, 'error': 0} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_timeout_empty(self): + '''Test check_snappy_service_stop_timeout() - empty''' + self._set_service(key="start", value="bin/foo") + self._set_service(key="description", value="something") + self._set_service(key="stop-timeout", value="") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_timeout_bad(self): + '''Test check_snappy_service_stop_timeout() - bad''' + self._set_service(key="start", value="bin/foo") + self._set_service(key="description", value="something") + self._set_service(key="stop-timeout", value="a") + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_timeout_range_low(self): + '''Test check_snappy_service_stop_timeout() - out of range (low)''' + self._set_service(key="start", value="bin/foo") + self._set_service(key="description", value="something") + self._set_service(key="stop-timeout", value=-1) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) + + def test_check_snappy_service_stop_timeout_range_high(self): + '''Test check_snappy_service_stop_timeout() - out of range (high)''' + self._set_service(key="start", value="bin/foo") + self._set_service(key="description", value="something") + self._set_service(key="stop-timeout", value=61) + c = ClickReviewSystemd(self.test_name) + c.systemd_files['foo'] = "meta/foo.snappy-systemd" + c.check_snappy_service_stop_timeout() + r = c.click_report + expected_counts = {'info': None, 'warn': 0, 'error': 1} + self.check_results(r, expected_counts) diff -Nru click-reviewers-tools-0.21/clickreviews/tests/test_cr_url_dispatcher.py click-reviewers-tools-0.24/clickreviews/tests/test_cr_url_dispatcher.py --- click-reviewers-tools-0.21/clickreviews/tests/test_cr_url_dispatcher.py 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/clickreviews/tests/test_cr_url_dispatcher.py 2015-03-10 15:16:29.000000000 +0000 @@ -171,7 +171,7 @@ key="domain-suffix", value="example.com") self.set_test_url_dispatcher(self.default_appname, - key="domain-suffix", + key="nonexistent", value="example.com", append=True) c = ClickReviewUrlDispatcher(self.test_name) @@ -216,13 +216,12 @@ key="protocol", value="some-protocol") c = ClickReviewUrlDispatcher(self.test_name) - print(c.manifest["hooks"]) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook - tmp["url-dispatcher"] = \ + tmp["urls"] = \ self.test_manifest["hooks"][self.default_appname]["urls"] # update the manifest and test_manifest diff -Nru click-reviewers-tools-0.21/debian/bzr-builder.manifest click-reviewers-tools-0.24/debian/bzr-builder.manifest --- click-reviewers-tools-0.21/debian/bzr-builder.manifest 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/debian/bzr-builder.manifest 2015-03-10 15:16:29.000000000 +0000 @@ -1,2 +1,2 @@ -# bzr-builder format 0.3 deb-version {debupstream}-0~356 -lp:click-reviewers-tools revid:daniel.holbach@canonical.com-20150120221741-0iobqmeab86pbcaj +# bzr-builder format 0.3 deb-version {debupstream}-0~407 +lp:click-reviewers-tools revid:jamie@ubuntu.com-20150309212251-98sbnjvyci0u90md diff -Nru click-reviewers-tools-0.21/debian/changelog click-reviewers-tools-0.24/debian/changelog --- click-reviewers-tools-0.21/debian/changelog 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/debian/changelog 2015-03-10 15:16:29.000000000 +0000 @@ -1,10 +1,50 @@ -click-reviewers-tools (0.21-0~356~ubuntu14.04.1) trusty; urgency=low +click-reviewers-tools (0.24-0~407~ubuntu14.04.1) trusty; urgency=low * Auto build. - -- Daniel Holbach Wed, 21 Jan 2015 10:16:42 +0000 + -- Daniel Holbach Tue, 10 Mar 2015 15:16:29 +0000 -click-reviewers-tools (0.21) UNRELEASED; urgency=medium +click-reviewers-tools (0.24) UNRELEASED; urgency=medium + + * + + -- Jamie Strandboge Mon, 09 Mar 2015 16:22:37 -0500 + +click-reviewers-tools (0.23) vivid; urgency=medium + + * fix pep8 warning when building on trusty + + -- Jamie Strandboge Mon, 09 Mar 2015 15:42:08 -0500 + +click-reviewers-tools (0.22) vivid; urgency=medium + + [ Alexandre Abreu ] + * Relax the rule that states that webapps with a model search path shouldn't + have url patterns listed in the command line. In order to avoid confusion, + we allow this to happen (and it already works fine the command line + patterns being appended to the locally defined ones). (LP: #1406643) + + [ Jamie Strandboge ] + * add testsuite test to verify apparmor-profile can't be specified with + apparmor + * add apparmor-profile hook tests + * fix test_check_optional_domain_suffix_without_protocol2() to actually test + with 'nonexistent' key + * debian/control: + - add python3-yaml to Build-Depends and Depends + - update Vcs-Bzr to point to lp:click-reviewers-tools + * add snappy-systemd hook tests and update the testsuite accordingly + * apparmor-profile hook may be used anywhere apparmor can be, but not with + apparmor itself (apparmor-profile is still redflagged) + * implement snappy package.yaml lint tests + * implement snappy package.yaml services tests + * implement snappy readme.md lint tests + * implement snappy package.yaml binaries tests + * one more snappy workaround for check_package_filename() + + -- Jamie Strandboge Mon, 09 Mar 2015 15:08:44 -0500 + +click-reviewers-tools (0.21) vivid; urgency=medium [ Pete Woods ] * Add childscopes field to recognised list. @@ -14,7 +54,22 @@ [ Michael Vogt ] * snappy: add two new optional fields: source, type. - -- Daniel Holbach Wed, 14 Jan 2015 12:13:56 +0100 + [ Jamie Strandboge ] + * also use ubuntu-devel-discuss@lists.ubuntu.com to signify a core-app + * calculate arch correctly in check_package_filename() + * add ubuntu-core-15.04 to self.major_framework_policy + * add checks for self.major_framework_policy to policy_vendor checks + * bin-path should no longer require snappy-systemd hook + * warn, don't error, on 'Could not find compiled binaries for architecture' + since it might be ok to, for example, ship a shell script but you only + want it on ARM devices + * apparmor-profile is an allowed hook, but a redflagged one + * don't error that apparmor is missing if apparmor-profile is present + + [ Daniel Holbach ] + * Deal with multi-arch clicks properly. (LP: #1395204) + + -- Daniel Holbach Tue, 03 Mar 2015 14:17:13 +0100 click-reviewers-tools (0.20) vivid; urgency=medium diff -Nru click-reviewers-tools-0.21/debian/control click-reviewers-tools-0.24/debian/control --- click-reviewers-tools-0.21/debian/control 2015-01-21 10:16:42.000000000 +0000 +++ click-reviewers-tools-0.24/debian/control 2015-03-10 15:16:29.000000000 +0000 @@ -11,10 +11,11 @@ python3-magic, python3-setuptools, python3-simplejson, - python3-xdg + python3-xdg, + python3-yaml Standards-Version: 3.9.5 Homepage: https://launchpad.net/click-reviewers-tools -Vcs-Bzr: lp:ubuntu-dev-tools +Vcs-Bzr: lp:click-reviewers-tools Vcs-Browser: http://bazaar.launchpad.net/~click-reviewers/click-reviewers-tools/trunk/files X-Python3-Version: >= 3.2 @@ -26,6 +27,7 @@ python3-magic, python3-simplejson, python3-xdg, + python3-yaml, ${misc:Depends}, ${python3:Depends} Description: tools to review click packages