[reprotest] 02/02: Support substractable parameters for variations, progress towards > 2 builds
Ximin Luo
infinity0 at debian.org
Wed Sep 13 11:43:10 UTC 2017
This is an automated email from the git hooks/post-receive script.
infinity0 pushed a commit to branch master
in repository reprotest.
commit a22e52bad6b7a1f0703e438195ebeeab2d190aa1
Author: Ximin Luo <infinity0 at debian.org>
Date: Wed Sep 13 13:41:59 2017 +0200
Support substractable parameters for variations, progress towards > 2 builds
---
README.rst | 6 +-
debian/changelog | 4 +
reprotest/__init__.py | 44 ++++----
reprotest/build.py | 135 ++++++++++++++++++-----
reprotest/mdiffconf.py | 194 ++++++++++++++++++++++++++++++++++
tests/test_mdiffconf.py | 19 ++++
tests/{tests.py => test_reprotest.py} | 8 +-
tox.ini | 2 +-
8 files changed, 353 insertions(+), 59 deletions(-)
diff --git a/README.rst b/README.rst
index 04faecc..d3cbed1 100644
--- a/README.rst
+++ b/README.rst
@@ -208,9 +208,11 @@ your use-case::
$ cat <<EOF | sudo tee -a /etc/sudoers.d/local-reprotest
$USER ALL = ($OTHERUSER) NOPASSWD: ALL
$USER ALL = NOPASSWD: /bin/chown -h -R --from=$OTHERUSER $USER /tmp/autopkgtest.$a$a$a$a$a$a/const_build_path/
- $USER ALL = NOPASSWD: /bin/chown -h -R --from=$OTHERUSER $USER /tmp/autopkgtest.$a$a$a$a$a$a/experiment/
+ $USER ALL = NOPASSWD: /bin/chown -h -R --from=$OTHERUSER $USER /tmp/autopkgtest.$a$a$a$a$a$a/build-experiment/
+ $USER ALL = NOPASSWD: /bin/chown -h -R --from=$OTHERUSER $USER /tmp/autopkgtest.$a$a$a$a$a$a/build-experiment-before-disorderfs/
$USER ALL = NOPASSWD: /bin/chown -h -R --from=$USER $OTHERUSER /tmp/autopkgtest.$a$a$a$a$a$a/const_build_path/
- $USER ALL = NOPASSWD: /bin/chown -h -R --from=$USER $OTHERUSER /tmp/autopkgtest.$a$a$a$a$a$a/experiment/
+ $USER ALL = NOPASSWD: /bin/chown -h -R --from=$USER $OTHERUSER /tmp/autopkgtest.$a$a$a$a$a$a/build-experiment/
+ $USER ALL = NOPASSWD: /bin/chown -h -R --from=$USER $OTHERUSER /tmp/autopkgtest.$a$a$a$a$a$a/build-experiment-before-disorderfs/
EOF
Repeat this for each user you'd like to use. Obviously, don't pick a privileged
diff --git a/debian/changelog b/debian/changelog
index e48a9c4..e841fd1 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,11 +1,15 @@
reprotest (0.7) UNRELEASED; urgency=medium
+ FIXME do not release
+ - update the docs
+
[ Ximin Luo ]
* Document when one should use --diffoscope-args=--exclude-directory-metadata
and do this in our Debian package presets.
* Bump diffoscope Recommends version to >= 84 to support this flag.
* Import autopkgtest 4.4, with minimal patches.
* Choose an existent HOME for the control build. (Closes: #860428)
+ * Add the ability to vary the user (Closes: #872412)
[ Mattia Rizzolo ]
* Bump Standards-Version to 4.0.0.
diff --git a/reprotest/__init__.py b/reprotest/__init__.py
index 487b242..e9a07b6 100644
--- a/reprotest/__init__.py
+++ b/reprotest/__init__.py
@@ -20,7 +20,7 @@ import pkg_resources
from reprotest.lib import adtlog
from reprotest.lib import adt_testbed
-from reprotest.build import Build, VARIATIONS, VariationContext
+from reprotest.build import Build, VariationSpec, Variations
from reprotest import presets
@@ -105,13 +105,15 @@ class BuildContext(collections.namedtuple('_BuildContext', 'testbed_root local_d
tree = self.testbed_src
)
- def plan_variations(self, build, is_control, variation_context, variations):
- actions = [(not is_control and v in variations, v) for v in VARIATIONS]
+ def plan_variations(self, build, is_control, variations): # XXX
+ if is_control:
+ variations = variations._replace(spec=VariationSpec.empty())
+ actions = variations.spec.actions()
logging.info('build "%s": %s',
self.build_name,
- ", ".join("%s %s" % ("FIX" if not vary else "vary", v) for vary, v in actions))
- for vary, v in actions:
- build = VARIATIONS[v](variation_context, build, vary)
+ ", ".join("%s %s" % ("FIX" if not vary else "vary", v) for v, vary, action in actions))
+ for v, vary, action in actions:
+ build = action(variations, build, vary)
return build
def copydown(self, testbed):
@@ -175,7 +177,7 @@ def run_diff(dist_0, dist_1, diffoscope_args, store_dir):
def check(build_command, artifact_pattern, virtual_server_args, source_root,
no_clean_on_error=False, store_dir=None, diffoscope_args=[],
- variations=VARIATIONS, variation_context=VariationContext.default(),
+ variations=Variations.default(),
testbed_pre=None, testbed_init=None, host_distro='debian'):
# default argument [] is safe here because we never mutate it.
if not source_root:
@@ -201,7 +203,7 @@ def check(build_command, artifact_pattern, virtual_server_args, source_root,
source_root = new_source_root
logging.debug("source_root: %s", source_root)
- variation_context = variation_context.guess_default_faketime(source_root)
+ variations = variations._replace(spec=variations.spec.apply_dynamic_defaults(source_root))
# TODO: an alternative strategy is to run the testbed many times, one for each build
# not sure if it's worth implementing at this stage, but perhaps in the future.
@@ -221,7 +223,7 @@ def check(build_command, artifact_pattern, virtual_server_args, source_root,
for bctx in build_contexts]
logging.log(5, "builds: %r", builds)
- builds = [c.plan_variations(b, c.build_name == "control", variation_context, variations)
+ builds = [c.plan_variations(b, c.build_name == "control", variations)
for c, b in zip(build_contexts, builds)]
logging.log(5, "builds: %r", builds)
@@ -351,20 +353,14 @@ def cli_parser():
help='Save the artifacts in this directory, which must be empty or '
'non-existent. Otherwise, the artifacts will be deleted and you only '
'see their hashes (if reproducible) or the diff output (if not).')
- group1.add_argument('--variations', default=frozenset(VARIATIONS.keys()),
- type=lambda s: frozenset(sss for ss in s.split(',') for sss in ss.split()),
+ group1.add_argument('--variations', default=["+all"], action='append',
help='Build variations to test as a whitespace-or-comma-separated '
- 'list. Default is to test all available variations: %s.' %
- ', '.join(VARIATIONS.keys()))
- group1.add_argument('--dont-vary', default=frozenset(),
- type=lambda s: frozenset(sss for ss in s.split(',') for sss in ss.split()),
+ 'list. Default is to test all available variations: %(default)s.')
+ # TODO: hide this from --help, and deprecate it
+ group1.add_argument('--dont-vary', default=[], action='append',
help='Build variations *not* to test as a whitespace-or-comma-separated '
'list. These take precedence over what you set for --variations. '
'Default is nothing, i.e. test whatever you set for --variations.')
- group1.add_argument('--user-groups', default=frozenset(),
- type=lambda s: frozenset(tuple(x.split(':',1)) for x in s.split(',')),
- help='Comma-separated list of possible user:group combinations which '
- 'will be randomly chosen to `sudo -i` to when varying user_group.')
group2 = parser.add_argument_group('diff options')
group2.add_argument('--diffoscope-arg', default=[], action='append',
@@ -505,11 +501,9 @@ def run(argv, check):
diffoscope_args = values.diffoscope_args + diffoscope_args
# Variations args
- variations = parsed_args.variations - parsed_args.dont_vary
- _ = VariationContext.default()
- _ = _._replace(verbosity=verbosity)
- _ = _._replace(user_groups=_.user_groups | parsed_args.user_groups)
- variation_context = _
+ variations = parsed_args.variations + ["-%s" % a for x in parsed_args.dont_vary for a in x.split(",")]
+ spec = VariationSpec().extend(variations)
+ variations = Variations(verbosity, spec)
# Remaining args
host_distro = parsed_args.host_distro
@@ -525,7 +519,7 @@ def run(argv, check):
check_args_keys = (
"build_command", "artifact_pattern", "virtual_server_args", "source_root",
"no_clean_on_error", "store_dir", "diffoscope_args",
- "variations", "variation_context",
+ "variations",
"testbed_pre", "testbed_init", "host_distro")
l = locals()
check_args = collections.OrderedDict([(k, l[k]) for k in check_args_keys])
diff --git a/reprotest/build.py b/reprotest/build.py
index 392dc66..072dad7 100644
--- a/reprotest/build.py
+++ b/reprotest/build.py
@@ -1,13 +1,18 @@
+# Licensed under the GPL: https://www.gnu.org/licenses/gpl-3.0.en.html
+# For details: reprotest/debian/copyright
+
import collections
import getpass
import grp
import logging
import os
+import shlex
import random
import time
import types
from reprotest import _shell_ast
+from reprotest import mdiffconf
def dirname(p):
@@ -141,21 +146,6 @@ fi
return str(subshell)
-class VariationContext(collections.namedtuple('_VariationContext', 'verbosity user_groups default_faketime')):
-
- @classmethod
- def default(cls):
- return cls(0, frozenset(), 0)
-
- def guess_default_faketime(self, source_root):
- # Get the latest modification date of all the files in the source root.
- # This tries hard to avoid bad interactions with faketime and make(1) etc.
- # However if you're building this too soon after changing one of the source
- # files then the effect of this variation is not very great.
- filemtimes = (os.path.getmtime(os.path.join(root, f)) for root, dirs, files in os.walk(source_root) for f in files)
- return self._replace(default_faketime=int(max(filemtimes, default=0)))
-
-
# time zone, locales, disorderfs, host name, user/group, shell, CPU
# number, architecture for uname (using linux64), umask, HOME, see
# also: https://tests.reproducible-builds.org/index_variations.html
@@ -272,10 +262,12 @@ def timezone(ctx, build, vary):
def faketime(ctx, build, vary):
if not vary:
+ # FIXME: this does not actually fix the time, it just lets the system clock run normally
return build
- lastmt = ctx.default_faketime
+ lastmt = random.choice(ctx.spec.time.faketimes)
now = time.time()
- if lastmt < now - 32253180:
+ # FIXME: better way of choosing which faketime to use
+ if lastmt.startswith("@") and int(lastmt[1:]) < now - 32253180:
# if lastmt is far in the past, use that, it's a bit safer
faket = '@%s' % lastmt
else:
@@ -299,14 +291,26 @@ def user_group(ctx, build, vary):
if not vary:
return build
- if not ctx.user_groups:
- logging.warn("IGNORING user_group variation, because no --user-groups were given. To suppress this warning, give --dont-vary user_group")
+ if not ctx.spec.user_group.available:
+ logging.warn("IGNORING user_group variation; supply more usergroups "
+ "with --variations=user_group.available+=USER1:GROUP1;USER2:GROUP2 or "
+ "alternatively, suppress this warning with --variations=-user_group")
return build
olduser = getpass.getuser()
oldgroup = grp.getgrgid(os.getgid()).gr_name
- user, group = random.choice(list(set(ctx.user_groups) - set([(olduser, oldgroup)])))
- sudobuild = _shell_ast.SimpleCommand.make('sudo', '-E', '-u', user, '-g', group)
+ user_group = random.choice(list(set(ctx.spec.user_group.available) - set([(olduser, oldgroup)])))
+ if ":" in user_group:
+ user, group = user_group.split(":", 1)
+ if user:
+ sudo_command = ('sudo', '-E', '-u', user, '-g', group)
+ else:
+ user = olduser
+ sudo_command = ('sudo', '-E', '-g', group)
+ else:
+ user = user_group # "user" is used below
+ sudo_command = ('sudo', '-E', '-u', user)
+ sudobuild = _shell_ast.SimpleCommand.make(*sudo_command)
binpath = os.path.join(dirname(build.tree), 'bin')
_ = build.append_to_build_command(sudobuild)
@@ -314,17 +318,18 @@ def user_group(ctx, build, vary):
# we prefer that to running it as root, principle of least-privilege.
_ = _.append_setup_exec('sh', '-ec', r'''
mkdir "{0}"
-printf '#!/bin/sh\nsudo -u "{1}" -g "{2}" /usr/bin/disorderfs "$@"\n' > "{0}"/disorderfs
+printf '#!/bin/sh\n{1} /usr/bin/disorderfs "$@"\n' > "{0}"/disorderfs
chmod +x "{0}"/disorderfs
-printf '#!/bin/sh\nsudo -u "{1}" -g "{2}" /bin/mkdir "$@"\n' > "{0}"/mkdir
+printf '#!/bin/sh\n{1} /bin/mkdir "$@"\n' > "{0}"/mkdir
chmod +x "{0}"/mkdir
-printf '#!/bin/sh\nsudo -u "{1}" -g "{2}" /bin/fusermount "$@"\n' > "{0}"/fusermount
+printf '#!/bin/sh\n{1} /bin/fusermount "$@"\n' > "{0}"/fusermount
chmod +x "{0}"/fusermount
-'''.format(binpath, user, group))
+'''.format(binpath, " ".join(map(shlex.quote, sudo_command))))
_ = _.append_setup_exec_raw('export', 'PATH="%s:$PATH"' % binpath)
- _ = _.append_setup_exec('sudo', 'chown', '-h', '-R', '--from=%s' % olduser, user, build.tree)
- # TODO: artifacts probably shouldn't be chown'd back
- _ = _.prepend_cleanup_exec('sudo', 'chown', '-h', '-R', '--from=%s' % user, olduser, build.tree)
+ if user != olduser:
+ _ = _.append_setup_exec('sudo', 'chown', '-h', '-R', '--from=%s' % olduser, user, build.tree)
+ # TODO: artifacts probably shouldn't be chown'd back
+ _ = _.prepend_cleanup_exec('sudo', 'chown', '-h', '-R', '--from=%s' % user, olduser, build.tree)
return _
@@ -347,3 +352,77 @@ VARIATIONS = collections.OrderedDict([
('timezone', timezone),
('umask', umask),
])
+
+
+class TimeVariation(collections.namedtuple('_TimeVariation', 'faketimes auto_faketimes')):
+ @classmethod
+ def default(cls):
+ return cls(mdiffconf.strlist_set(";"), mdiffconf.strlist_set(";", ['SOURCE_DATE_EPOCH']))
+
+ @classmethod
+ def empty(cls):
+ return cls(mdiffconf.strlist_set(";"), mdiffconf.strlist_set(";"))
+
+ def apply_dynamic_defaults(self, source_root):
+ new_faketimes = []
+ for a in self.auto_faketimes:
+ if a == "SOURCE_DATE_EPOCH":
+ # Get the latest modification date of all the files in the source root.
+ # This tries hard to avoid bad interactions with faketime and make(1) etc.
+ # However if you're building this too soon after changing one of the source
+ # files then the effect of this variation is not very great.
+ filemtimes = (os.path.getmtime(os.path.join(root, f)) for root, dirs, files in os.walk(source_root) for f in files)
+ new_faketimes.append("@%d" % int(max(filemtimes, default=0)))
+ else:
+ raise ValueError("unrecognized auto_faketime: %s" % a)
+ return self.empty()._replace(faketimes=self.faketimes + new_faketimes)
+
+
+class UserGroupVariation(collections.namedtuple('_UserGroupVariation', 'available')):
+ @classmethod
+ def default(cls):
+ return cls(mdiffconf.strlist_set(";"))
+
+
+class VariationSpec(mdiffconf.ImmutableNamespace):
+ @classmethod
+ def default(cls, variations=VARIATIONS):
+ default_overrides = {
+ "user_group": UserGroupVariation.default(),
+ "time": TimeVariation.default(),
+ }
+ return cls(**{k: default_overrides.get(k, True) for k in variations})
+
+ @classmethod
+ def empty(cls):
+ return cls()
+
+ aliases = { ("@+-", "all"): list(VARIATIONS.keys()) }
+ def extend(self, actions):
+ one = self.default()
+ return mdiffconf.parse_all(self, actions, one, one, self.aliases, sep=",")
+
+ def actions(self):
+ return [(k, k in self.__dict__, v) for k, v in VARIATIONS.items()]
+
+ def apply_dynamic_defaults(self, source_root):
+ return self.__class__(**{
+ k: v.apply_dynamic_defaults(source_root) if hasattr(v, "apply_dynamic_defaults") else v
+ for k, v in self.__dict__.items()
+ })
+
+
+class Variations(collections.namedtuple('_Variations', 'verbosity spec')):
+ @classmethod
+ def default(cls, *args, **kwargs):
+ return cls(0, VariationSpec.default(*args, **kwargs))
+
+
+if __name__ == "__main__":
+ import sys
+ d = VariationSpec()
+ for s in sys.argv[1:]:
+ d = d.append(s)
+ print(s)
+ print(">>>", d)
+ print("result", d.apply_dynamic_defaults("."))
diff --git a/reprotest/mdiffconf.py b/reprotest/mdiffconf.py
new file mode 100644
index 0000000..98775ae
--- /dev/null
+++ b/reprotest/mdiffconf.py
@@ -0,0 +1,194 @@
+# Licensed under the GPL: https://www.gnu.org/licenses/gpl-3.0.en.html
+# For details: reprotest/debian/copyright
+"""Merge-diff config.
+
+This module implements a basic generic language for representing an object
+tree, described via a sequence of addition and subtraction operations on
+different parts of the tree, that are merged together to form the new tree.
+
+This is useful for manually specifying object trees in a compact way. This
+saves human users time instead of typing the same thing many many times.
+
+This module is totally independent of reprotest.
+"""
+
+import collections
+import functools
+import re
+import sys
+import types
+
+
+def rgetattr2(obj, attr, one=None):
+ if one is not None:
+ def _getattr2(arg, attr):
+ obj, one = arg
+ val = getattr(one, attr)
+ return (getattr(obj, attr) if hasattr(obj, attr) else val), val
+ else:
+ def _getattr2(arg, attr):
+ obj, one = arg
+ val = getattr(obj, attr)
+ return val, val
+ return functools.reduce(_getattr2, attr.split('.') if attr else [], (obj, one))
+
+def rsetattr(obj, attr, val, one=None):
+ pre, _, post = attr.rpartition('.')
+ target, target_one = rgetattr2(obj, pre, one) if pre else (obj, one)
+ target = target._replace(**{post: val})
+ return rsetattr(obj, pre, target) if pre else target
+
+def rdelattr(obj, attr, zero=None):
+ pre, _, post = attr.rpartition('.')
+ target, target_zero = rgetattr2(obj, pre, zero) if pre else (obj, zero)
+ try:
+ target = target._delete(post)
+ except AttributeError:
+ if zero is not None:
+ target = target._replace(**{post: rgetattr2(target_zero, post)[0]})
+ else:
+ raise
+ return rsetattr(obj, pre, target) if pre else target
+
+class ImmutableNamespace(types.SimpleNamespace):
+ def _replace(self, **kwargs):
+ new = self.__dict__.copy()
+ new.update(**kwargs)
+ return self.__class__(**new)
+
+ def _delete(self, name):
+ new = self.__dict__.copy()
+ if name in new:
+ del new[name]
+ return self.__class__(**new)
+
+def strlist_set(sep, value=[]):
+ class strlist_set(list):
+ def __init__(self, value=[]):
+ if isinstance(value, str):
+ value = value.split(sep) if value else []
+ return super().__init__(value)
+
+ def dedup(self, seq):
+ seen = set()
+ seen_add = seen.add
+ return self.__class__([x for x in seq if not (x in seen or seen_add(x))])
+
+ def __add__(self, value):
+ return self.dedup(super().__add__(value))
+
+ def __iadd__(self, value):
+ return self.__add__(value)
+
+ def __sub__(self, value):
+ return self.dedup([x for x in self if x not in set(value)])
+
+ def __isub__(self, value):
+ return self.__sub__(value)
+
+ def sep(self):
+ return sep
+ return strlist_set(value)
+
+def parse(d, action, one, zero=None, aliases={}):
+ """Parse an action, apply it to an object and return the new value.
+
+ Args:
+
+ obj: The top-level object to apply the action to.
+
+ action: The action to apply, specified as a string. The string is split on
+ the leftmost occurence of any supported $operator; everything to the
+ left of it is the $attribute and to the right of it is the $operand.
+
+ $attribute is in dotted-notation and specifies which $target attribute
+ (or sub-attribute, etc) of the $obj to apply $operator and $operand to.
+ If $attribute is the empty string, we apply these to $obj itself. If
+ the actual attribute does not exist on $obj, we implicitly add it as if
+ it had just been added with the "+" operator.
+
+ Supported operators:
+ +
+ Add the attribute named $operand to the $target. The value of the
+ attribute is taken from the $one default object. However if the
+ attribute is already on $target, it retains its existing value.
+ -
+ Remove the attribute named $operand from the $target. If it does
+ not support removing attributes, instead we set its value to that
+ taken from the $zero default object.
+ @
+ Re-set the attribute to its default value from $one. This is just a
+ shortcut for doing - and then +.
+ =, +=, -=
+ Apply the $operand to $target using the normal Python =, += and -=
+ operators respectively. If $operand is not of the same type as
+ $target, it is first coerced into it, using its class constructor.
+ ++, --
+ Apply 1 to the $target using the operators +=, -= respectively.
+
+ one: Having the same structure as $obj, this is used for default values
+ when adding new attributes; see "+" operator.
+
+ zero: Having the same structure as $obj. this is used for default values
+ when it is not possible to remove an attribute; see "-" operator.
+
+ aliases: A dictionary specifying aliases for various operations. Currently
+ supports the following keys:
+
+ ("@+-", $alias_operand): $real_operands:
+ When the operator is one of "@+-", this allows the user to use a
+ single $alias_operand to specify one-or-more $real_operands to
+ actually apply the operator to.
+ """
+ parts = re.split(r"(\+=|-=|\+\+|--|=|\+|-|@)", action, 1)
+ attr, op, operand = ("", "+", parts[0]) if len(parts) == 1 else parts
+ target, target_one = rgetattr2(d, attr, one)
+
+ if op in ("++", "--"):
+ if operand:
+ raise ValueError("action %s should have no operand: %s" % (action, operand))
+ else:
+ if not operand:
+ raise ValueError("action %s has no operand" % action)
+
+ operands = aliases.get(("@+-", operand), [operand]) if op in "@+-" else [operand]
+ for operand in operands:
+ if op == "@":
+ # equivalent to - then +
+ target = rdelattr(target, operand, rgetattr2(zero, attr)[0])
+ target = rsetattr(target, operand, rgetattr2(target, operand, target_one)[0])
+ elif op == "-":
+ target = rdelattr(target, operand, rgetattr2(zero, attr)[0])
+ elif op == "+":
+ target = rsetattr(target, operand, rgetattr2(target, operand, target_one)[0])
+ elif op == "=":
+ if not isinstance(operand, target.__class__):
+ operand = target.__class__(operand)
+ target = operand
+ elif op == "+=":
+ if not isinstance(operand, target.__class__):
+ operand = target.__class__(operand)
+ target += operand
+ elif op == "-=":
+ if not isinstance(operand, target.__class__):
+ operand = target.__class__(operand)
+ target -= operand
+ elif op == "++":
+ target += 1
+ elif op == "--":
+ target -= 1
+ else:
+ assert False
+
+ return rsetattr(d, attr, target, one) if attr else target
+
+def parse_all(d, actions, one, zero=None, aliases={}, sep=None):
+ """Parse a list of actions.
+
+ If sep is given, each list element is further split on that separator.
+ """
+ if isinstance(actions, str):
+ actions = [actions]
+ if sep:
+ actions = [a for aa in actions for a in aa.split(sep)]
+ return functools.reduce(lambda d, a: parse(d, a, one, zero, aliases), actions, d)
diff --git a/tests/test_mdiffconf.py b/tests/test_mdiffconf.py
new file mode 100644
index 0000000..e781aff
--- /dev/null
+++ b/tests/test_mdiffconf.py
@@ -0,0 +1,19 @@
+# Licensed under the GPL: https://www.gnu.org/licenses/gpl-3.0.en.html
+# For details: reprotest/debian/copyright
+
+import pytest
+
+from reprotest.mdiffconf import *
+
+
+class LolX(collections.namedtuple("LolX", "x y z")):
+ pass
+
+def test_basic():
+ one = ImmutableNamespace(lol=LolX(x=strlist_set(";"), y=10, z=True), wtf=True)
+ zero = ImmutableNamespace(lol=LolX(x=strlist_set(";", ['less than nothing yo!']), y=0, z=False), wtf=False)
+ d = ImmutableNamespace()
+ d = parse_all(d, '+lol,wtf,-xxx,lol.x+=4;5;6,lol.y+=123,lol.x-=5,-lol.z', one, zero, sep=",")
+ assert d == ImmutableNamespace(lol=LolX(x=['4', '6'], y=133, z=False), wtf=True)
+ d = parse_all(d, '@lol,-lol.x,-lol.z,lol.x+=3;4;5', one, zero, sep=",")
+ assert d == ImmutableNamespace(lol=LolX(x=['less than nothing yo!', '3', '4', '5'], y=10, z=False), wtf=True)
diff --git a/tests/tests.py b/tests/test_reprotest.py
old mode 100755
new mode 100644
similarity index 94%
rename from tests/tests.py
rename to tests/test_reprotest.py
index 480b721..48984d4
--- a/tests/tests.py
+++ b/tests/test_reprotest.py
@@ -7,6 +7,7 @@ import sys
import pytest
import reprotest
+import reprotest.build
REPROTEST = [sys.executable, "-m", "reprotest", "--no-diffoscope"]
REPROTEST_TEST_SERVERS = os.getenv("REPROTEST_TEST_SERVERS", "null").split(",")
@@ -15,11 +16,12 @@ REPROTEST_TEST_DONTVARY = os.getenv("REPROTEST_TEST_DONTVARY", "").split(",")
if REPROTEST_TEST_DONTVARY:
REPROTEST += ["--dont-vary", ",".join(REPROTEST_TEST_DONTVARY)]
-TEST_VARIATIONS = frozenset(reprotest.VARIATIONS.keys()) - frozenset(REPROTEST_TEST_DONTVARY)
+TEST_VARIATIONS = frozenset(reprotest.build.VARIATIONS.keys()) - frozenset(REPROTEST_TEST_DONTVARY)
def check_return_code(command, virtual_server, code):
try:
- retcode = reprotest.check(command, 'artifact', virtual_server, 'tests', variations=TEST_VARIATIONS)
+ retcode = reprotest.check(command, 'artifact', virtual_server, 'tests',
+ variations=reprotest.build.Variations.default(TEST_VARIATIONS))
except SystemExit as system_exit:
retcode = system_exit.args[0]
finally:
@@ -59,7 +61,7 @@ def test_simple_builds(virtual_server):
check_return_code('python3 mock_build.py irreproducible', virtual_server, 1)
# TODO: test all variations that we support
- at pytest.mark.parametrize('captures', list(reprotest.VARIATIONS.keys()))
+ at pytest.mark.parametrize('captures', list(reprotest.build.VARIATIONS.keys()))
def test_variations(virtual_server, captures):
expected = 1 if captures in TEST_VARIATIONS else 0
check_return_code('python3 mock_build.py ' + captures, virtual_server, expected)
diff --git a/tox.ini b/tox.ini
index feb0ba8..0362e07 100644
--- a/tox.ini
+++ b/tox.ini
@@ -24,5 +24,5 @@ deps =
# pytest-cov
pytest
# commands = py.test --cov-report html --cov=reprotest tests/tests.py
-commands = {envpython} -m coverage run --omit .tox/* --parallel -m py.test {posargs} tests/tests.py
+commands = {envpython} -m coverage run --omit .tox/* --parallel -m py.test {posargs} tests/
# commands = py.test {posargs} tests/tests.py
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/reprotest.git
More information about the Reproducible-commits
mailing list