[Reproducible-commits] [reprotest] 01/04: Add build_path and restore file_ordering variation
Ceridwen
ceridwen-guest at moszumanska.debian.org
Wed Jul 27 22:23:57 UTC 2016
This is an automated email from the git hooks/post-receive script.
ceridwen-guest pushed a commit to branch variations
in repository reprotest.
commit a92123bb46d4b427abaf99cfbfece3ab510e6b83
Author: Ceridwen <ceridwenv at gmail.com>
Date: Mon Jul 25 21:55:23 2016 -0400
Add build_path and restore file_ordering variation
---
reprotest/__init__.py | 305 ++++++++++++++++++++++++++------------------------
tests/mock_build.py | 8 +-
tests/tests.py | 2 +-
3 files changed, 167 insertions(+), 148 deletions(-)
diff --git a/reprotest/__init__.py b/reprotest/__init__.py
index 500b177..4249eeb 100644
--- a/reprotest/__init__.py
+++ b/reprotest/__init__.py
@@ -48,16 +48,6 @@ def start_testbed(args, temp_dir):
testbed.stop()
-Pair = collections.namedtuple('Pair', 'control experiment')
-Pair.__doc__ = ('Holds one object for each run of the build process.'
- + Pair.__doc__)
-
-def add(mapping, key, value):
- '''Helper function for adding a key-value pair to an immutable mapping.'''
- new_mapping = mapping.copy()
- new_mapping[key] = value
- return types.MappingProxyType(new_mapping)
-
class Script(collections.namedtuple('_Script', 'build_command setup cleanup')):
'''Holds the shell ASTs used to construct the final build script.
@@ -76,7 +66,7 @@ class Script(collections.namedtuple('_Script', 'build_command setup cleanup')):
because otherwise the cleanup has to occur after the build artifact
is copied out. These execution unconditionally, one after another,
because all cleanup commands should be attempted irrespective of
- whether others succeed. Examples: fileordering.
+ whether others succeed. Examples: file_ordering.
'''
def __new__(cls, build_command, setup=_shell_ast.AndList(),
@@ -129,61 +119,70 @@ class Script(collections.namedtuple('_Script', 'build_command setup cleanup')):
(' ||\n' + str(self.cleanup) if self.cleanup else ''))
+
# 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
# TODO: relies on a pbuilder-specific command to parallelize
# @_contextlib.contextmanager
-# def cpu(env, tree, testbed):
-# yield script, env, tree
+# def cpu(env, build_dir, testbed):
+# yield script, env, build_dir
- at _contextlib.contextmanager
-def captures_environment(script, env, tree, testbed):
- new_env = add(env.experiment, 'CAPTURE_ENVIRONMENT',
- 'i_capture_the_environment')
- yield script, Pair(env.control, new_env), tree
+# TODO: Linux-specific. unshare --uts requires superuser privileges.
+# How is this related to host/domainname?
+# def namespace(script, env, build_dir, testbed):
+# # command1 = ['unshare', '--uts'] + command1
+# # command2 = ['unshare', '--uts'] + command2
+# yield script, env, build_dir
-# TODO: this requires superuser privileges.
- at _contextlib.contextmanager
-def domain_host(script, env, tree, testbed):
- yield script, env, tree
@_contextlib.contextmanager
-def fileordering(script, env, tree, testbed):
- new_tree = os.path.dirname(os.path.dirname(tree.control)) + '/disorderfs/'
- # testbed.check_exec(['id'])
- testbed.check_exec(['mkdir', '-p', new_tree])
- # TODO: this is a temporary hack, there will eventually be
- # multiple variations that depend on whether the testbed has root
- # privileges.
- if 'root-on-testbed' in testbed.capabilities:
- disorderfs = ['disorderfs', '--shuffle-dirents=yes',
- '--multi-user=yes', tree.experiment, new_tree]
- else:
- disorderfs = ['disorderfs', '--shuffle-dirents=yes',
- tree.experiment, new_tree]
- testbed.check_exec(disorderfs)
- unmount = _shell_ast.SimpleCommand.make('fusermount', '-u', new_tree)
- # If there's an error in the build process, the virt/ program will
- # try to delete the temporary directory containing disorderfs
- # before it's unmounted unless it's unmounted in the script
- # itself.
- new_script = script.experiment.append_cleanup(unmount)
- try:
- yield Pair(script.control, new_script), env, Pair(tree.control, new_tree)
- finally:
- testbed.check_exec(str(unmount).split())
+def identity(script, env, build_dir, testbed):
+ '''Identity context manager for variations that don't need to do anything.'''
+ yield script, env, build_dir
-# @_contextlib.contextmanager
-# def fileordering(script, env, tree, testbed):
-# yield script, env, tree
+def add(mapping, key, value):
+ '''Helper function for adding a key-value pair to an immutable mapping.'''
+ new_mapping = mapping.copy()
+ new_mapping[key] = value
+ return types.MappingProxyType(new_mapping)
+
+def environment_variable_variation(name, value):
+ '''Create a context manager to set an environment variable to a value.'''
+ @_contextlib.contextmanager
+ def set_environment_variable(script, env, build_dir, testbed):
+ yield script, add(env, name, value), build_dir
+ return set_environment_variable
@_contextlib.contextmanager
-def home(script, env, tree, testbed):
- control = add(env.control, 'HOME', '/nonexistent/first-build')
- experiment = add(env.experiment, 'HOME', '/nonexistent/second-build')
- yield script, Pair(control, experiment), tree
+def build_path(script, env, build_dir, testbed):
+ new_build_dir = os.path.dirname(os.path.dirname(build_dir)) + '/other/'
+ testbed.check_exec(['mv', build_dir, new_build_dir])
+ yield script, env, new_build_dir
+
+def file_ordering(disorderfs_mount):
+ @_contextlib.contextmanager
+ def file_ordering(script, env, build_dir, testbed):
+ # testbed.check_exec(['id'])
+ # Move the directory holding the source tree to a new path.
+ real_dir = os.path.dirname(os.path.dirname(build_dir)) + '/real/'
+ testbed.check_exec(['mv', build_dir, real_dir])
+ # Recreate the original build directory and mount the real
+ # source tree there.
+ testbed.check_exec(['mkdir', '-p', build_dir])
+ testbed.check_exec(disorderfs_mount + [real_dir, build_dir])
+ unmount = _shell_ast.SimpleCommand.make('fusermount', '-u', build_dir)
+ # If there's an error in the build process, the virt/ program will
+ # try to delete the temporary directory containing disorderfs
+ # before it's unmounted unless it's unmounted in the script
+ # itself.
+ new_script = script.append_cleanup(unmount)
+ try:
+ yield new_script, env, build_dir
+ finally:
+ testbed.check_exec(str(unmount).split())
+ return file_ordering
# TODO: uname is a POSIX standard. The related Linux command
# (setarch) only affects uname at the moment according to the docs.
@@ -191,15 +190,17 @@ def home(script, env, tree, testbed):
# reference to a setname command on another Unix variant:
# https://en.wikipedia.org/wiki/Uname
@_contextlib.contextmanager
-def kernel(script, env, tree, testbed):
+def kernel(script, env, build_dir, testbed):
setarch = _shell_ast.SimpleCommand.make('linux64', '--uname-2.6')
# setarch = _shell_ast.SimpleCommand(
# '', 'linux64', _shell_ast.CmdSuffix(
# ['--uname-2.6', script.experiment[0].command]))
# new_script = (script.experiment[:-1] +
# _shell_ast.List([_shell_ast.Term(setarch, ';')]))
- new_script = script.experiment.append_command(setarch)
- yield Pair(script.control, new_script), env, tree
+ yield script.append_command(setarch), env, build_dir
+
+# TODO: what exact locales and how to many test is probably a mailing
+# list question.
# TODO: if this locale doesn't exist on the system, Python's
# locales.getlocale() will return (None, None) rather than this
@@ -210,85 +211,101 @@ def kernel(script, env, tree, testbed):
# installs the right locale. A weaker but still reasonable solution
# is to figure out what locales are installed (how?) and use another
# locale if this one isn't installed.
-
-# TODO: what exact locales and how to many test is probably a mailing
-# list question.
@_contextlib.contextmanager
-def locales(script, env, tree, testbed):
+def locales(script, env, build_dir, testbed):
# env1['LANG'] = 'C'
- new_env = add(add(env.experiment, 'LANG', 'fr_CH.UTF-8'),
- 'LC_ALL', 'fr_CH.UTF-8')
+ new_env = add(add(env, 'LANG', 'fr_CH.UTF-8'), 'LC_ALL', 'fr_CH.UTF-8')
# env1['LANGUAGE'] = 'en_US:en'
# env2['LANGUAGE'] = 'fr_CH:fr'
- yield script, Pair(env.control, new_env), tree
-
-# TODO: Linux-specific. unshare --uts requires superuser privileges.
-# How is this related to host/domainname?
-# def namespace(script, env, tree, testbed):
-# # command1 = ['unshare', '--uts'] + command1
-# # command2 = ['unshare', '--uts'] + command2
-# yield script, env, tree
-
- at _contextlib.contextmanager
-def path(script, env, tree, testbed):
- new_env = add(env.experiment, 'PATH', env.control['PATH'] +
- '/i_capture_the_path')
- yield script, Pair(env.control, new_env), tree
-
-# This doesn't require superuser privileges, but the chsh command
-# affects all user shells, which would be bad.
- at _contextlib.contextmanager
-def shell(script, env, tree, testbed):
- yield script, env, tree
+ yield script, new_env, build_dir
@_contextlib.contextmanager
-def timezone(script, env, tree, testbed):
- # These time zones are theoretically in the POSIX time zone format
- # (http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08),
- # so they should be cross-platform compatible.
- control = add(env.control, 'TZ', 'GMT+12')
- experiment = add(env.experiment, 'TZ', 'GMT-14')
- yield script, Pair(control, experiment), tree
+def path(script, env, build_dir, testbed):
+ new_env = add(env, 'PATH', env['PATH'] + '/i_capture_the_path')
+ yield script, new_env, build_dir
@_contextlib.contextmanager
-def umask(script, env, tree, testbed):
- # umask = _shell_ast.SimpleCommand('', 'umask', _shell_ast.CmdSuffix(['0002']))
- # new_script = (_shell_ast.List([_shell_ast.Term(umask, ';')])
- # + script.experiment)
+def umask(script, env, build_dir, testbed):
umask = _shell_ast.SimpleCommand.make('umask', '0002')
- new_script = script.experiment.append_setup(umask)
- yield Pair(script.control, new_script), env, tree
+ yield script.append_setup(umask), env, build_dir
-# TODO: This requires superuser privileges.
- at _contextlib.contextmanager
-def user_group(script, env, tree, testbed):
- yield script, env, tree
+class MultipleDispatch(collections.OrderedDict):
+ '''This is a mapping that imitates a dictionary with tuple keys using
+ nested mappings and sequences, to make it easier to specify the
+ full set of combinations without needing to write out a tuple for
+ every possible combination.
+
+ '''
+ def __getitem__(self, keys):
+ value = super().__getitem__(keys[0])
+ for key in keys[1:]:
+ try:
+ value = value[key]
+ except (IndexError, KeyError, TypeError):
+ break
+ return value
+
+
+# The order of the dispatch tuple is designed so that the values that
+# require the most different functions occur earlier. Variations is
+# first and run number second because each variation requires
+# different code for each of control and experiment. (Note: at the
+# moment, two builds are hard-coded.) Root privileges is third
+# because only some variations change depending on root privileges.
+# The last, OS/system, is not implemented at the moment but only has
+# one variation I know of.
+
+# TODO: still true?
# The order of the variations *is* important, because the command to
# be executed in the container needs to be built from the inside out.
-VARIATIONS = types.MappingProxyType(collections.OrderedDict([
- ('captures_environment', captures_environment),
- # 'cpu': cpu,
- ('domain_host', domain_host), ('fileordering', fileordering),
- ('home', home), ('kernel', kernel), ('locales', locales),
- # 'namespace': namespace,
- ('path', path), ('shell', shell),
- ('timezone', timezone), ('umask', umask),
- ('user_group', user_group)
+VARIATIONS = types.MappingProxyType(MultipleDispatch([
+ ('build_path', (identity, build_path)),
+ ('captures_environment',
+ (identity,
+ environment_variable_variation(
+ 'CAPTURE_ENVIRONMENT', 'i_capture_the_environment'))),
+ # TODO: this requires superuser privileges.
+ ('domain_host', identity),
+ ('file_ordering',
+ (identity,
+ types.MappingProxyType(collections.OrderedDict(
+ [('user', file_ordering(['disorderfs', '--shuffle-dirents=yes'])),
+ ('root', file_ordering(
+ ['disorderfs', '--shuffle-dirents=yes', '--multi-user=yes']))
+ ])))),
+ ('home',
+ (environment_variable_variation('HOME', '/nonexistent/first-build'),
+ environment_variable_variation('HOME', '/nonexistent/second-build'))),
+ ('kernel', (identity, kernel)),
+ ('locales', (identity, locales)),
+ ('path', (identity, path)),
+ # TODO: This doesn't require superuser privileges, but the chsh command
+ # affects all user shells, which would be bad.
+ ('shell', identity),
+ # These time zones are theoretically in the POSIX time zone format
+ # (http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08),
+ # so they should be cross-platform compatible.
+ ('time_zone',
+ (environment_variable_variation('TZ', 'GMT+12'),
+ environment_variable_variation('TZ', 'GMT-14'))),
+ ('umask', (identity, umask)),
+ # TODO: This requires superuser privileges.
+ ('user_group', identity)
]))
-def build(script, source_root, built_artifact, testbed, artifact_store, env):
- print(source_root)
- # testbed.execute(['ls', '-l', source_root])
+def build(script, source_root, build_dir, built_artifact, testbed,
+ artifact_store, env):
+ # print(source_root)
+ # print(build_dir)
+ # testbed.execute(['ls', '-l', build_dir])
# testbed.execute(['pwd'])
- print(built_artifact)
- # cd = _shell_ast.SimpleCommand('', 'cd', _shell_ast.CmdSuffix([source_root]))
- # new_script = (_shell_ast.List([_shell_ast.Term(cd, ';')]) + script)
- cd = _shell_ast.SimpleCommand.make('cd', source_root)
+ # print(built_artifact)
+ cd = _shell_ast.SimpleCommand.make('cd', build_dir)
new_script = script.append_setup(cd)
- # lsof = _shell_ast.SimpleCommand.make('lsof', '-w', source_root)
+ # lsof = _shell_ast.SimpleCommand.make('lsof', '-w', build_dir)
# new_script = new_script.append_cleanup(lsof)
# ls = _shell_ast.SimpleCommand.make('ls', '-l', testbed.scratch)
# new_script = new_script.append_cleanup(ls)
@@ -297,10 +314,10 @@ def build(script, source_root, built_artifact, testbed, artifact_store, env):
print(new_script)
# exit_code, stdout, stderr = testbed.execute(['sh', '-ec', str(new_script)], xenv=[str(k) + '=' + str(v) for k, v in env.items()], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
testbed.check_exec(['sh', '-ec', str(new_script)], xenv=[str(k) + '=' + str(v) for k, v in env.items()])
- # exit_code, stdout, stderr = testbed.execute(['lsof', source_root], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ # exit_code, stdout, stderr = testbed.execute(['lsof', build_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# print(exit_code, stdout, stderr)
- # testbed.execute(['ls', '-l', source_root])
- # testbed.execute(['stat', source_root])
+ # testbed.execute(['ls', '-l', build_dir])
+ # testbed.execute(['stat', build_dir])
# testbed.execute(['stat', built_artifact])
testbed.command('copyup', (built_artifact, artifact_store))
@@ -309,37 +326,35 @@ def check(build_command, artifact_name, virtual_server_args, source_root,
variations=VARIATIONS):
# print(virtual_server_args)
with tempfile.TemporaryDirectory() as temp_dir, start_testbed(virtual_server_args, temp_dir) as testbed:
- script = Pair(Script(build_command), Script(build_command))
- env = Pair(types.MappingProxyType(os.environ.copy()),
- types.MappingProxyType(os.environ.copy()))
+ script = Script(build_command)
+ env = types.MappingProxyType(os.environ.copy())
# TODO, why?: directories need explicit '/' appended for VirtSubproc
- tree = Pair(testbed.scratch + '/control/', testbed.scratch + '/experiment/')
- testbed.command('copydown', (str(source_root) + '/', tree.control))
- testbed.command('copydown', (str(source_root) + '/', tree.experiment))
- # print(source_root)
+ build_dir = testbed.scratch + '/build/'
+ if 'root-on-testbed' in testbed.capabilities:
+ user = 'root'
+ else:
+ user = 'user'
try:
- with _contextlib.ExitStack() as stack:
- for variation in variations:
- # print('START')
- # print(variation)
- script, env, tree = stack.enter_context(VARIATIONS[variation](script, env, tree, testbed))
- # print(script)
- # print(env)
- # print(tree)
- build(script.control, tree.control,
- os.path.normpath(tree.control + artifact_name),
- testbed,
- os.path.normpath(temp_dir + '/control_artifact'),
- env=env.control)
- build(script.experiment, tree.experiment,
- os.path.normpath(tree.experiment + artifact_name),
- testbed,
- os.path.normpath(temp_dir + '/experiment_artifact'),
- env=env.experiment)
+ for i in range(2):
+ testbed.command('copydown', (str(source_root) + '/', build_dir))
+ new_script, new_env, new_build_dir = script, env, build_dir
+ with _contextlib.ExitStack() as stack:
+ for variation in variations:
+ # print('START')
+ # print(variation)
+ new_script, new_env, new_build_dir = stack.enter_context(VARIATIONS[(variation, i, user)](new_script, new_env, new_build_dir, testbed))
+ # print(new_script)
+ # print(new_env)
+ # print(new_build_dir)
+ build(new_script, str(source_root), new_build_dir,
+ os.path.normpath(new_build_dir + artifact_name),
+ testbed,
+ os.path.normpath(temp_dir + '/artifact' + str(i)),
+ env=new_env)
except Exception:
traceback.print_exc()
sys.exit(2)
- sys.exit(subprocess.call(['diffoscope', temp_dir + '/control_artifact', temp_dir + '/experiment_artifact']))
+ # sys.exit(subprocess.call(['diffoscope', temp_dir + '/artifact0', temp_dir + '/artifact1']))
COMMAND_LINE_OPTIONS = types.MappingProxyType(collections.OrderedDict([
diff --git a/tests/mock_build.py b/tests/mock_build.py
index 5b43a1e..7469aa5 100755
--- a/tests/mock_build.py
+++ b/tests/mock_build.py
@@ -23,10 +23,14 @@ if __name__ == '__main__':
# random bits in both runs, but it is extremely unlikely.
if 'irreproducible' in variations:
output.append(os.urandom(1024))
+ if 'build_path' in variations:
+ output.append(os.getcwd().encode('ascii'))
+ if 'captures_environment' in variations:
+ output.append(str(os.environ).encode('ascii'))
# Like the above test, this test can theoretically fail by
# producing the same file order, but this is unlikely, if not
# as unlikely as in the above test.
- if 'fileordering' in variations:
+ if 'file_ordering' in variations:
# Ensure this temporary directory is created in the disorders
# mount point by passing the dir argument.
with tempfile.TemporaryDirectory(dir=str(pathlib.Path.cwd())) as temp:
@@ -42,7 +46,7 @@ if __name__ == '__main__':
output.extend(l.encode('ascii') for l in locale.getlocale())
if 'path' in variations:
output.extend(p.encode('ascii') for p in os.get_exec_path())
- if 'timezone' in variations:
+ if 'time_zone' in variations:
output.append(str(time.timezone).encode('ascii'))
if 'umask' in variations:
with tempfile.TemporaryDirectory(dir=str(pathlib.Path.cwd())) as temp:
diff --git a/tests/tests.py b/tests/tests.py
index f8d85ca..88e380d 100755
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -33,7 +33,7 @@ def test_simple_builds(virtual_server):
check_return_code('python3 mock_failure.py', virtual_server, 2)
check_return_code('python3 mock_build.py irreproducible', virtual_server, 1)
- at pytest.mark.parametrize('variation', ['fileordering', 'home', 'kernel', 'locales', 'path', 'timezone', 'umask'])
+ at pytest.mark.parametrize('variation', reprotest.VARIATIONS.keys())
def test_variations(virtual_server, variation):
check_return_code('python3 mock_build.py ' + variation, virtual_server, 1)
--
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