From 0f2e8db77193a4eba48bfce0848bc857fc9f5c2d Mon Sep 17 00:00:00 2001 From: Sebastian Luther Date: Tue, 14 Sep 2010 20:26:17 +0200 Subject: portage.dep: Add support for ::repo syntax to all functions and classes This also adds the dep_getrepo() function --- pym/portage/dep/__init__.py | 96 +++++++++++++--- pym/portage/tests/dep/testAtom.py | 151 ++++++++++++++------------ pym/portage/tests/dep/test_match_from_list.py | 20 +++- 3 files changed, 178 insertions(+), 89 deletions(-) diff --git a/pym/portage/dep/__init__.py b/pym/portage/dep/__init__.py index f0d7e0190..3c651d22d 100644 --- a/pym/portage/dep/__init__.py +++ b/pym/portage/dep/__init__.py @@ -9,7 +9,8 @@ __all__ = [ 'get_operator', 'isjustname', 'isspecific', 'isvalidatom', 'match_from_list', 'match_to_list', 'paren_enclose', 'paren_normalize', 'paren_reduce', - 'remove_slot', 'strip_empty', 'use_reduce' + 'remove_slot', 'strip_empty', 'use_reduce', + '_repo_separator', '_slot_separator', ] # DEPEND SYNTAX: @@ -973,11 +974,11 @@ class Atom(_atom_base): def __init__(self, forbid_overlap=False): self.overlap = self._overlap(forbid=forbid_overlap) - def __new__(cls, s, unevaluated_atom=None, allow_wildcard=False, + def __new__(cls, s, unevaluated_atom=None, allow_wildcard=False, allow_repo=False, _use=None, eapi=None, is_valid_flag=None): return _atom_base.__new__(cls, s) - def __init__(self, s, unevaluated_atom=None, allow_wildcard=False, + def __init__(self, s, unevaluated_atom=None, allow_wildcard=False, allow_repo=False, _use=None, eapi=None, is_valid_flag=None): if isinstance(s, Atom): # This is an efficiency assertion, to ensure that the Atom @@ -1009,6 +1010,7 @@ class Atom(_atom_base): if cpv.find("**") != -1: raise InvalidAtom(self) slot = gdict['slot'] + repo = None use_str = None extended_syntax = True else: @@ -1018,7 +1020,8 @@ class Atom(_atom_base): op = m.group(base + 1) cpv = m.group(base + 2) cp = m.group(base + 3) - slot = m.group(_atom_re.groups - 1) + slot = m.group(_atom_re.groups - 2) + repo = m.group(_atom_re.groups - 1) use_str = m.group(_atom_re.groups) if m.group(base + 4) is not None: raise InvalidAtom(self) @@ -1027,31 +1030,38 @@ class Atom(_atom_base): op = '=*' cpv = m.group(base + 1) cp = m.group(base + 2) - slot = m.group(_atom_re.groups - 1) + slot = m.group(_atom_re.groups - 2) + repo = m.group(_atom_re.groups - 1) use_str = m.group(_atom_re.groups) if m.group(base + 3) is not None: raise InvalidAtom(self) elif m.group('simple') is not None: op = None cpv = cp = m.group(_atom_re.groupindex['simple'] + 1) - slot = m.group(_atom_re.groups - 1) + slot = m.group(_atom_re.groups - 2) + repo = m.group(_atom_re.groups - 1) use_str = m.group(_atom_re.groups) if m.group(_atom_re.groupindex['simple'] + 2) is not None: raise InvalidAtom(self) + else: raise AssertionError(_("required group not found in atom: '%s'") % self) self.__dict__['cp'] = cp self.__dict__['cpv'] = cpv + self.__dict__['repo'] = repo self.__dict__['slot'] = slot self.__dict__['operator'] = op self.__dict__['extended_syntax'] = extended_syntax + if not (repo is None or allow_repo): + raise InvalidAtom(self) + if use_str is not None: if _use is not None: use = _use else: use = _use_dep(use_str[1:-1].split(",")) - without_use = Atom(m.group('without_use')) + without_use = Atom(m.group('without_use'), allow_repo=allow_repo) else: use = None without_use = self @@ -1152,7 +1162,7 @@ class Atom(_atom_base): atom += ":%s" % self.slot use_dep = self.use.evaluate_conditionals(use) atom += str(use_dep) - return Atom(atom, unevaluated_atom=self, _use=use_dep) + return Atom(atom, unevaluated_atom=self, allow_repo=(self.repo is not None), _use=use_dep) def violated_conditionals(self, other_use, is_valid_flag, parent_use=None): """ @@ -1174,7 +1184,7 @@ class Atom(_atom_base): atom += ":%s" % self.slot use_dep = self.use.violated_conditionals(other_use, is_valid_flag, parent_use) atom += str(use_dep) - return Atom(atom, unevaluated_atom=self, _use=use_dep) + return Atom(atom, unevaluated_atom=self, allow_repo=(self.repo is not None), _use=use_dep) def _eval_qa_conditionals(self, use_mask, use_force): if not (self.use and self.use.conditional): @@ -1184,7 +1194,7 @@ class Atom(_atom_base): atom += ":%s" % self.slot use_dep = self.use._eval_qa_conditionals(use_mask, use_force) atom += str(use_dep) - return Atom(atom, unevaluated_atom=self, _use=use_dep) + return Atom(atom, unevaluated_atom=self, allow_repo=(self.repo is not None), _use=use_dep) def __copy__(self): """Immutable, so returns self.""" @@ -1369,7 +1379,11 @@ def dep_getslot(mydep): slot = getattr(mydep, "slot", False) if slot is not False: return slot - colon = mydep.find(":") + + #remove repo_name if present + mydep = mydep.split(_repo_separator)[0] + + colon = mydep.find(_slot_separator) if colon != -1: bracket = mydep.find("[", colon) if bracket == -1: @@ -1378,14 +1392,46 @@ def dep_getslot(mydep): return mydep[colon+1:bracket] return None +def dep_getrepo(mydep): + """ + Retrieve the repo on a depend. + + Example usage: + >>> dep_getrepo('app-misc/test::repository') + 'repository' + + @param mydep: The depstring to retrieve the repository of + @type mydep: String + @rtype: String + @return: The repository name + """ + repo = getattr(mydep, "repo", False) + if repo is not False: + return repo + + metadata = getattr(mydep, "metadata", False) + if metadata: + repo = metadata.get('repository', False) + if repo is not False: + return repo + + colon = mydep.find(_repo_separator) + if colon != -1: + bracket = mydep.find("[", colon) + if bracket == -1: + return mydep[colon+2:] + else: + return mydep[colon+2:bracket] + return None def remove_slot(mydep): """ Removes dep components from the right side of an atom: * slot * use * repo + And repo_name from the left side. """ - colon = mydep.find(":") + colon = mydep.find(_slot_separator) if colon != -1: mydep = mydep[:colon] else: @@ -1450,16 +1496,21 @@ def dep_getusedeps( depend ): # 2.1.3 A slot name may contain any of the characters [A-Za-z0-9+_.-]. # It must not begin with a hyphen or a dot. +_slot_separator = ":" _slot = r'([\w+][\w+.-]*)' _slot_re = re.compile('^' + _slot + '$', re.VERBOSE) _use = r'\[.*\]' _op = r'([=~]|[><]=?)' +_repo_separator = "::" +_repo_name = r'[\w+][\w+.-]*' +_repo = r'(?:' + _repo_separator + '(' + _repo_name + ')' + ')?' _atom_re = re.compile('^(?P(?:' + '(?P' + _op + _cpv + ')|' + '(?P=' + _cpv + r'\*)|' + - '(?P' + _cp + '))(:' + _slot + ')?)(' + _use + ')?$', re.VERBOSE) + '(?P' + _cp + '))' + + '(' + _slot_separator + _slot + ')?' + _repo + ')(' + _use + ')?$', re.VERBOSE) _extended_cat = r'[\w+*][\w+.*-]*' _extended_pkg = r'[\w+*][\w+*-]*?' @@ -1468,7 +1519,7 @@ _atom_wildcard_re = re.compile('(?P(' + _extended_cat + ')/(' + _extende _valid_use_re = re.compile(r'^[A-Za-z0-9][A-Za-z0-9+_@-]*$') -def isvalidatom(atom, allow_blockers=False, allow_wildcard=False): +def isvalidatom(atom, allow_blockers=False, allow_wildcard=False, allow_repo=False): """ Check to see if a depend atom is valid @@ -1487,7 +1538,7 @@ def isvalidatom(atom, allow_blockers=False, allow_wildcard=False): """ try: if not isinstance(atom, Atom): - atom = Atom(atom, allow_wildcard=allow_wildcard) + atom = Atom(atom, allow_wildcard=allow_wildcard, allow_repo=allow_repo) if not allow_blockers and atom.blocker: return False return True @@ -1565,7 +1616,7 @@ def dep_getkey(mydep): @return: The package category/package-name """ if not isinstance(mydep, Atom): - mydep = Atom(mydep) + mydep = Atom(mydep, allow_wildcard=True, allow_repo=True) return mydep.cp @@ -1651,7 +1702,7 @@ def match_from_list(mydep, candidate_list): else: mydep = mydep[1:] if not isinstance(mydep, Atom): - mydep = Atom(mydep, allow_wildcard=True) + mydep = Atom(mydep, allow_wildcard=True, allow_repo=True) mycpv = mydep.cpv mycpv_cps = catpkgsplit(mycpv) # Can be None if not specific @@ -1811,8 +1862,19 @@ def match_from_list(mydep, candidate_list): if use_config_mismatch: continue + mylist.append(x) + if mydep.repo: + candidate_list = mylist + mylist = [] + for x in candidate_list: + repo = getattr(x, "repo", False) + if repo is False: + repo = dep_getrepo(x) + if repo is not None and repo != mydep.repo: + continue mylist.append(x) + return mylist def human_readable_required_use(required_use): diff --git a/pym/portage/tests/dep/testAtom.py b/pym/portage/tests/dep/testAtom.py index 31999dc2c..a1dea8c1f 100644 --- a/pym/portage/tests/dep/testAtom.py +++ b/pym/portage/tests/dep/testAtom.py @@ -11,90 +11,103 @@ class TestAtom(TestCase): tests = ( ( "=sys-apps/portage-2.1-r1:0[doc,a=,!b=,c?,!d?,-e]", - ('=', 'sys-apps/portage', '2.1-r1', '0', '[doc,a=,!b=,c?,!d?,-e]'), False ), + ('=', 'sys-apps/portage', '2.1-r1', '0', '[doc,a=,!b=,c?,!d?,-e]', None), False, False ), ( "=sys-apps/portage-2.1-r1*:0[doc]", - ('=*', 'sys-apps/portage', '2.1-r1', '0', '[doc]'), False ), + ('=*', 'sys-apps/portage', '2.1-r1', '0', '[doc]', None), False, False ), ( "sys-apps/portage:0[doc]", - (None, 'sys-apps/portage', None, '0', '[doc]'), False ), + (None, 'sys-apps/portage', None, '0', '[doc]', None), False, False ), ( "sys-apps/portage:0[doc]", - (None, 'sys-apps/portage', None, '0', '[doc]'), False ), + (None, 'sys-apps/portage', None, '0', '[doc]', None), False, False ), ( "*/*", - (None, '*/*', None, None, None), True ), + (None, '*/*', None, None, None, None), True, False ), ( "sys-apps/*", - (None, 'sys-apps/*', None, None, None), True ), + (None, 'sys-apps/*', None, None, None, None), True, False ), ( "*/portage", - (None, '*/portage', None, None, None), True ), + (None, '*/portage', None, None, None, None), True, False ), ( "s*s-*/portage:1", - (None, 's*s-*/portage', None, '1', None), True ), + (None, 's*s-*/portage', None, '1', None, None), True, False ), ( "*/po*ge:2", - (None, '*/po*ge', None, '2', None), True ), + (None, '*/po*ge', None, '2', None, None), True, False ), ( "!dev-libs/A", - (None, 'dev-libs/A', None, None, None), True ), + (None, 'dev-libs/A', None, None, None, None), True, True ), ( "!!dev-libs/A", - (None, 'dev-libs/A', None, None, None), True ), + (None, 'dev-libs/A', None, None, None, None), True, True ), ( "!!dev-libs/A", - (None, 'dev-libs/A', None, None, None), True ), + (None, 'dev-libs/A', None, None, None, None), True, True ), ( "dev-libs/A[foo(+)]", - (None, 'dev-libs/A', None, None, "[foo(+)]"), True ), + (None, 'dev-libs/A', None, None, "[foo(+)]", None), True, True ), ( "dev-libs/A[a(+),b(-)=,!c(+)=,d(-)?,!e(+)?,-f(-)]", - (None, 'dev-libs/A', None, None, "[a(+),b(-)=,!c(+)=,d(-)?,!e(+)?,-f(-)]"), True ), + (None, 'dev-libs/A', None, None, "[a(+),b(-)=,!c(+)=,d(-)?,!e(+)?,-f(-)]", None), True, True ), ( "dev-libs/A:2[a(+),b(-)=,!c(+)=,d(-)?,!e(+)?,-f(-)]", - (None, 'dev-libs/A', None, "2", "[a(+),b(-)=,!c(+)=,d(-)?,!e(+)?,-f(-)]"), True ), + (None, 'dev-libs/A', None, "2", "[a(+),b(-)=,!c(+)=,d(-)?,!e(+)?,-f(-)]", None), True, True ), + + ( "=sys-apps/portage-2.1-r1:0::repo_name[doc,a=,!b=,c?,!d?,-e]", + ('=', 'sys-apps/portage', '2.1-r1', '0', '[doc,a=,!b=,c?,!d?,-e]', 'repo_name'), False, True ), + ( "=sys-apps/portage-2.1-r1*:0::repo_name[doc]", + ('=*', 'sys-apps/portage', '2.1-r1', '0', '[doc]', 'repo_name'), False, True ), + ( "sys-apps/portage:0::repo_name[doc]", + (None, 'sys-apps/portage', None, '0', '[doc]', 'repo_name'), False, True ), ) tests_xfail = ( - ( Atom("sys-apps/portage"), False ), - ( "cat/pkg[a!]", False ), - ( "cat/pkg[!a]", False ), - ( "cat/pkg[!a!]", False ), - ( "cat/pkg[!a-]", False ), - ( "cat/pkg[-a=]", False ), - ( "cat/pkg[-a?]", False ), - ( "cat/pkg[-a!]", False ), - ( "cat/pkg[=a]", False ), - ( "cat/pkg[=a=]", False ), - ( "cat/pkg[=a?]", False ), - ( "cat/pkg[=a!]", False ), - ( "cat/pkg[=a-]", False ), - ( "cat/pkg[?a]", False ), - ( "cat/pkg[?a=]", False ), - ( "cat/pkg[?a?]", False ), - ( "cat/pkg[?a!]", False ), - ( "cat/pkg[?a-]", False ), - ( "sys-apps/portage[doc]:0", False ), - ( "*/*", False ), - ( "sys-apps/*", False ), - ( "*/portage", False ), - ( "*/**", True ), - ( "*/portage[use]", True ), - ( "cat/pkg[a()]", False ), - ( "cat/pkg[a(]", False ), - ( "cat/pkg[a)]", False ), - ( "cat/pkg[a(,b]", False ), - ( "cat/pkg[a),b]", False ), - ( "cat/pkg[a(*)]", False ), - ( "cat/pkg[a(*)]", True ), - ( "cat/pkg[a(+-)]", False ), - ( "cat/pkg[a()]", False ), - ( "cat/pkg[(+)a]", False ), - ( "cat/pkg[a=(+)]", False ), - ( "cat/pkg[!(+)a=]", False ), - ( "cat/pkg[!a=(+)]", False ), - ( "cat/pkg[a?(+)]", False ), - ( "cat/pkg[!a?(+)]", False ), - ( "cat/pkg[!(+)a?]", False ), - ( "cat/pkg[-(+)a]", False ), - ( "cat/pkg[a(+),-a]", False ), - ( "cat/pkg[a(-),-a]", False ), - ( "cat/pkg[-a,a(+)]", False ), - ( "cat/pkg[-a,a(-)]", False ), - ( "cat/pkg[-a(+),a(-)]", False ), - ( "cat/pkg[-a(-),a(+)]", False ), + ( Atom("sys-apps/portage"), False, False ), + ( "cat/pkg[a!]", False, False ), + ( "cat/pkg[!a]", False, False ), + ( "cat/pkg[!a!]", False, False ), + ( "cat/pkg[!a-]", False, False ), + ( "cat/pkg[-a=]", False, False ), + ( "cat/pkg[-a?]", False, False ), + ( "cat/pkg[-a!]", False, False ), + ( "cat/pkg[=a]", False, False ), + ( "cat/pkg[=a=]", False, False ), + ( "cat/pkg[=a?]", False, False ), + ( "cat/pkg[=a!]", False, False ), + ( "cat/pkg[=a-]", False, False ), + ( "cat/pkg[?a]", False, False ), + ( "cat/pkg[?a=]", False, False ), + ( "cat/pkg[?a?]", False, False ), + ( "cat/pkg[?a!]", False, False ), + ( "cat/pkg[?a-]", False, False ), + ( "sys-apps/portage[doc]:0", False, False ), + ( "*/*", False, False ), + ( "sys-apps/*", False, False ), + ( "*/portage", False, False ), + ( "*/**", True, False ), + ( "*/portage[use]", True, False ), + ( "cat/pkg[a()]", False, False ), + ( "cat/pkg[a(]", False, False ), + ( "cat/pkg[a)]", False, False ), + ( "cat/pkg[a(,b]", False, False ), + ( "cat/pkg[a),b]", False, False ), + ( "cat/pkg[a(*)]", False, False ), + ( "cat/pkg[a(*)]", True, False ), + ( "cat/pkg[a(+-)]", False, False ), + ( "cat/pkg[a()]", False, False ), + ( "cat/pkg[(+)a]", False, False ), + ( "cat/pkg[a=(+)]", False, False ), + ( "cat/pkg[!(+)a=]", False, False ), + ( "cat/pkg[!a=(+)]", False, False ), + ( "cat/pkg[a?(+)]", False, False ), + ( "cat/pkg[!a?(+)]", False, False ), + ( "cat/pkg[!(+)a?]", False, False ), + ( "cat/pkg[-(+)a]", False, False ), + ( "cat/pkg[a(+),-a]", False, False ), + ( "cat/pkg[a(-),-a]", False, False ), + ( "cat/pkg[-a,a(+)]", False, False ), + ( "cat/pkg[-a,a(-)]", False, False ), + ( "cat/pkg[-a(+),a(-)]", False, False ), + ( "cat/pkg[-a(-),a(+)]", False, False ), + ( "sys-apps/portage[doc]::repo_name", False, False ), + ( "sys-apps/portage:0[doc]::repo_name", False, False ), + ( "sys-apps/portage[doc]:0::repo_name", False, False ), + ( "=sys-apps/portage-2.1-r1:0::repo_name[doc,a=,!b=,c?,!d?,-e]", False, False ), + ( "=sys-apps/portage-2.1-r1*:0::repo_name[doc]", False, False ), + ( "sys-apps/portage:0::repo_name[doc]", False, False ), ) - for atom, parts, allow_wildcard in tests: - a = Atom(atom, allow_wildcard=allow_wildcard) - op, cp, ver, slot, use = parts + for atom, parts, allow_wildcard, allow_repo in tests: + a = Atom(atom, allow_wildcard=allow_wildcard, allow_repo=allow_repo) + op, cp, ver, slot, use, repo = parts self.assertEqual( op, a.operator, msg="Atom('%s').operator = %s == '%s'" % ( atom, a.operator, op ) ) self.assertEqual( cp, a.cp, @@ -107,6 +120,9 @@ class TestAtom(TestCase): msg="Atom('%s').cpv = %s == '%s'" % ( atom, a.cpv, cpv ) ) self.assertEqual( slot, a.slot, msg="Atom('%s').slot = %s == '%s'" % ( atom, a.slot, slot ) ) + self.assertEqual( repo, a.repo, + msg="Atom('%s').repo == %s == '%s'" % ( atom, a.repo, repo ) ) + if a.use: returned_use = str(a.use) else: @@ -114,8 +130,9 @@ class TestAtom(TestCase): self.assertEqual( use, returned_use, msg="Atom('%s').use = %s == '%s'" % ( atom, returned_use, use ) ) - for atom, allow_wildcard in tests_xfail: - self.assertRaisesMsg(atom, (InvalidAtom, TypeError), Atom, atom, allow_wildcard=allow_wildcard) + for atom, allow_wildcard, allow_repo in tests_xfail: + self.assertRaisesMsg(atom, (InvalidAtom, TypeError), Atom, atom, \ + allow_wildcard=allow_wildcard, allow_repo=allow_repo) def test_intersects(self): test_cases = ( diff --git a/pym/portage/tests/dep/test_match_from_list.py b/pym/portage/tests/dep/test_match_from_list.py index 50b2b4a51..afba4141f 100644 --- a/pym/portage/tests/dep/test_match_from_list.py +++ b/pym/portage/tests/dep/test_match_from_list.py @@ -3,7 +3,7 @@ import sys from portage.tests import TestCase -from portage.dep import Atom, match_from_list +from portage.dep import Atom, match_from_list, _repo_separator from portage.versions import catpkgsplit if sys.hexversion >= 0x3000000: @@ -14,11 +14,12 @@ class Package(object): Provides a minimal subset of attributes of _emerge.Package.Package """ def __init__(self, atom): - atom = Atom(atom) + atom = Atom(atom, allow_repo=True) self.cp = atom.cp self.cpv = atom.cpv self.cpv_split = catpkgsplit(self.cpv) self.slot = atom.slot + self.repo = atom.repo if atom.use: self.use = self._use_class(atom.use.enabled) self.iuse = self._iuse_class(atom.use.required) @@ -37,7 +38,6 @@ class Package(object): def is_valid_flag(self, flags): if isinstance(flags, basestring): flags = [flags] - missing_iuse = [] for flag in flags: if not flag in self.all: return False @@ -76,7 +76,7 @@ class Test_match_from_list(TestCase): ("*/tar", ["sys-apps/portage-2.1.2"], [] ), ("*/*", ["dev-libs/A-1", "dev-libs/B-1"], ["dev-libs/A-1", "dev-libs/B-1"] ), ("dev-libs/*", ["dev-libs/A-1", "sci-libs/B-1"], ["dev-libs/A-1"] ), - + ("dev-libs/A[foo]", [Package("=dev-libs/A-1[foo]"), Package("=dev-libs/A-2[-foo]")], ["dev-libs/A-1"] ), ("dev-libs/A[-foo]", [Package("=dev-libs/A-1[foo]"), Package("=dev-libs/A-2[-foo]")], ["dev-libs/A-2"] ), ("dev-libs/A[-foo]", [Package("=dev-libs/A-1[foo]"), Package("=dev-libs/A-2")], [] ), @@ -86,13 +86,23 @@ class Test_match_from_list(TestCase): ("dev-libs/A[foo,bar(+)]", [Package("=dev-libs/A-1[-foo]"), Package("=dev-libs/A-2[foo]")], ["dev-libs/A-2"] ), ("dev-libs/A[foo,bar(-)]", [Package("=dev-libs/A-1[-foo]"), Package("=dev-libs/A-2[foo]")], [] ), ("dev-libs/A[foo,-bar(-)]", [Package("=dev-libs/A-1[-foo,bar]"), Package("=dev-libs/A-2[foo]")], ["dev-libs/A-2"] ), + + ("dev-libs/A::repo1", [Package("=dev-libs/A-1::repo1"), Package("=dev-libs/A-1::repo2")], ["dev-libs/A-1::repo1"] ), + ("dev-libs/A::repo2", [Package("=dev-libs/A-1::repo1"), Package("=dev-libs/A-1::repo2")], ["dev-libs/A-1::repo2"] ), + ("dev-libs/A::repo2[foo]", [Package("=dev-libs/A-1::repo1[foo]"), Package("=dev-libs/A-1::repo2[-foo]")], [] ), + ("dev-libs/A::repo2[foo]", [Package("=dev-libs/A-1::repo1[-foo]"), Package("=dev-libs/A-1::repo2[foo]")], ["dev-libs/A-1::repo2"] ), + ("dev-libs/A:1::repo2[foo]", [Package("=dev-libs/A-1:1::repo1"), Package("=dev-libs/A-1:2::repo2")], [] ), + ("dev-libs/A:1::repo2[foo]", [Package("=dev-libs/A-1:2::repo1"), Package("=dev-libs/A-1:1::repo2[foo]")], ["dev-libs/A-1::repo2"] ), ) for atom, cpv_list, expected_result in tests: result = [] for pkg in match_from_list( atom, cpv_list ): if isinstance(pkg, Package): - result.append(pkg.cpv) + if pkg.repo: + result.append(pkg.cpv + _repo_separator + pkg.repo) + else: + result.append(pkg.cpv) else: result.append(pkg) self.assertEqual( result, expected_result ) -- cgit v1.2.3-1-g7c22