[Reproducible-commits] [reprotest] 06/06: First draft of integration of the virtualization

Ceridwen ceridwen-guest at moszumanska.debian.org
Fri Jun 24 18:46:20 UTC 2016


This is an automated email from the git hooks/post-receive script.

ceridwen-guest pushed a commit to branch virtualization
in repository reprotest.

commit a7c798e80501c89b3c155582042d1b21287f91f4
Author: Ceridwen <ceridwenv at gmail.com>
Date:   Fri Jun 24 14:41:12 2016 -0400

    First draft of integration of the virtualization
---
 reprotest/__init__.py        | 215 +++++++++++++++++++++++++------------------
 reprotest/lib/adt_testbed.py |   4 +-
 tests/tests.py               |   4 +-
 tox.ini                      |   2 +-
 4 files changed, 129 insertions(+), 96 deletions(-)

diff --git a/reprotest/__init__.py b/reprotest/__init__.py
index b77fcf2..073dbdf 100644
--- a/reprotest/__init__.py
+++ b/reprotest/__init__.py
@@ -18,8 +18,12 @@ import types
 
 import pkg_resources
 
+from reprotest.lib import adtlog
 from reprotest.lib import adt_testbed
 
+# adtlog.verbosity = 2
+
+
 # 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
@@ -31,60 +35,74 @@ from reprotest.lib import adt_testbed
 # approaches.
 
 @contextlib.contextmanager
-def virt_server_manager(server_type, args):
+def virtual_server(args, temp_dir):
     '''This is a simple wrapper around adt_testbed that automates the
     clean up.'''
     # Find the location of reprotest using setuptools and then get the
     # path for the correct virt-server script.
     server_path = pkg_resources.resource_filename(__name__, 'virt/' +
-                                                  server_type)
-    virt_server = adt_testbed.Testbed((server_path,) + args, '/tmp/outdir', None)
-    virt_server.start()
-    virt_server.open()
+                                                  args[0])
+    virtual_server = adt_testbed.Testbed([server_path] + args[1:], temp_dir, None)
+    virtual_server.start()
+    virtual_server.open()
     try:
-        yield virt_server
+        yield virtual_server
     finally:
-        virt_server.stop()
+        virtual_server.stop()
+
+
+Pair = collections.namedtuple('Pair', 'control experiment')
+
+def add(mapping, key, value):
+    new_mapping = mapping.copy()
+    new_mapping[key] = value
+    return types.MappingProxyType(new_mapping)
 
 # TODO: relies on a pbuilder-specific command to parallelize
-# def cpu(command1, command2, env1, env2, tree1, tree2):
-#     yield command1, command2, env1, env2, tree1, tree2
+# @contextlib.contextmanager
+# def cpu(env, tree, builder):
+#     yield command, env, tree
 
 @contextlib.contextmanager
-def captures_environment(command1, command2, env1, env2, tree1, tree2):
-    env2['CAPTURE_ENVIRONMENT'] = 'i_capture_the_environment'
-    yield command1, command2, env1, env2, tree1, tree2
+def captures_environment(command, env, tree, builder):
+    new_env = add(env.experiment, 'CAPTURE_ENVIRONMENT',
+                  'i_capture_the_environment')
+    yield command, Pair(env.control, new_env), tree
 
 # TODO: this requires superuser privileges.
 @contextlib.contextmanager
-def domain_host(command1, command2, env1, env2, tree1, tree2):
-    yield command1, command2, env1, env2, tree1, tree2
+def domain_host(command, env, tree, builder):
+    yield command, env, tree
 
 @contextlib.contextmanager
-def fileordering(command1, command2, env1, env2, tree1, tree2):
-    disorderfs = tree2.parent/'disorderfs'
-    disorderfs.mkdir()
-    subprocess.check_call(['disorderfs', '--shuffle-dirents=yes',
-                           str(tree2), str(disorderfs)])
+def fileordering(command, env, tree, builder):
+    new_tree = os.path.dirname(os.path.dirname(tree.control)) + '/disorderfs/'
+    builder.execute(['mkdir', '-p', new_tree])
+    # disorderfs = tree2.parent/'disorderfs'
+    # disorderfs.mkdir()
+    builder.execute(['disorderfs', '--shuffle-dirents=yes',
+                     tree.experiment, new_tree])
     try:
-        yield command1, command2, env1, env2, tree1, disorderfs
+        yield command, env, Pair(tree.control, new_tree)
     finally:
-        subprocess.check_call(['fusermount', '-u', str(disorderfs)])
+        # subprocess.check_call(['fusermount', '-u', str(disorderfs)])
+        builder.execute(['fusermount', '-u', new_tree])
 
 @contextlib.contextmanager
-def home(command1, command2, env1, env2, tree1, tree2):
-    env1['HOME'] = '/nonexistent/first-build'
-    env2['HOME'] = '/nonexistent/second-build'
-    yield command1, command2, env1, env2, tree1, tree2
+def home(command, env, tree, builder):
+    control = add(env.experiment, 'HOME', '/nonexistent/first-build')
+    experiment = add(env.experiment, 'HOME', '/nonexistent/second-build')
+    yield command, Pair(control, experiment), tree
 
 # TODO: uname is a POSIX standard.  The related Linux command
 # (setarch) only affects uname at the moment according to the docs.
 # FreeBSD changes uname with environment variables.  Wikipedia has a
-# reference to a setname command: https://en.wikipedia.org/wiki/Uname
+# reference to a setname command on another Unix variant:
+# https://en.wikipedia.org/wiki/Uname
 @contextlib.contextmanager
-def kernel(command1, command2, env1, env2, tree1, tree2):
-    command2 = ['linux64', '--uname-2.6'] + command2
-    yield command1, command2, env1, env2, tree1, tree2
+def kernel(command, env, tree, builder):
+    new_command = ['linux64', '--uname-2.6'] + command.experiment
+    yield Pair(command.control, new_command), env, tree
 
 # TODO: if this locale doesn't exist on the system, Python's
 # locales.getlocale() will return (None, None) rather than this
@@ -99,50 +117,52 @@ def kernel(command1, command2, env1, env2, tree1, tree2):
 # TODO: what exact locales and how to many test is probably a mailing
 # list question.
 @contextlib.contextmanager
-def locales(command1, command2, env1, env2, tree1, tree2):
+def locales(command, env, tree, builder):
     # env1['LANG'] = 'C'
-    env2['LANG'] = 'fr_CH.UTF-8'
+    new_env = add(add(env.experiment, 'LANG', 'fr_CH.UTF-8'),
+                  'LC_ALL', 'fr_CH.UTF-8')
     # env1['LANGUAGE'] = 'en_US:en'
     # env2['LANGUAGE'] = 'fr_CH:fr'
-    env2['LC_ALL'] = 'fr_CH.UTF-8'
-    yield command1, command2, env1, env2, tree1, tree2
+    yield command, Pair(env.control, new_env), tree
 
 # TODO: Linux-specific.  unshare --uts requires superuser privileges.
 # How is this related to host/domainname?
-# def namespace(command1, command2, env1, env2, tree1, tree2):
+# def namespace(command, env, tree, builder):
 #     # command1 = ['unshare', '--uts'] + command1
 #     # command2 = ['unshare', '--uts'] + command2
-#     yield command1, command2, env1, env2, tree1, tree2
+#     yield command, env, tree
 
 @contextlib.contextmanager
-def path(command1, command2, env1, env2, tree1, tree2):
-    env2['PATH'] = env1['PATH'] + '/i_capture_the_path'
-    yield command1, command2, env1, env2, tree1, tree2
+def path(command, env, tree, builder):
+    new_env = add(env.experiment, 'PATH', env.control['PATH'] +
+                  '/i_capture_the_path')
+    yield command, Pair(env.control, new_env), tree
 
 # This doesn't require superuser privileges, but the chsh command
 # affects all user shells, which would be bad.
 @contextlib.contextmanager
-def shell(command1, command2, env1, env2, tree1, tree2):
-    yield command1, command2, env1, env2, tree1, tree2
+def shell(command, env, tree, builder):
+    yield command, env, tree
 
 @contextlib.contextmanager
-def timezone(command1, command2, env1, env2, tree1, tree2):
+def timezone(command, env, tree, builder):
     # 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.
-    env1['TZ'] = 'GMT+12'
-    env2['TZ'] = 'GMT-14'
-    yield command1, command2, env1, env2, tree1, tree2
+    control = add(env.experiment, 'TZ', 'GMT+12')
+    experiment = add(env.experiment, 'TZ', 'GMT-14')
+    yield command, Pair(control, experiment), tree
 
- at contextlib.contextmanager
-def umask(command1, command2, env1, env2, tree1, tree2):
-    command2 = ['umask', '0002;'] + command2
-    yield command1, command2, env1, env2, tree1, tree2
+# TODO: figure out how to make this compatible with adt_testbed.
+
+# def umask(command, env, tree, builder):
+#     command2 = ['umask', '0002;'] + command2
+#     yield command, env, tree
 
 # TODO: This requires superuser privileges.
 @contextlib.contextmanager
-def user_group(command1, command2, env1, env2, tree1, tree2):
-    yield command1, command2, env1, env2, tree1, tree2
+def user_group(command, env, tree, builder):
+    yield command, env, tree
 
 VARIATIONS = types.MappingProxyType(collections.OrderedDict([
     ('captures_environment', captures_environment),
@@ -151,54 +171,66 @@ VARIATIONS = types.MappingProxyType(collections.OrderedDict([
     ('home', home), ('kernel', kernel), ('locales', locales),
     # 'namespace': namespace,
     ('path', path), ('shell', shell),
-    ('timezone', timezone), ('umask', umask), ('user_group', user_group)
+    ('timezone', timezone), # ('umask', umask)
+    ('user_group', user_group)
 ]))
 
-def build(command, source_root, built_artifact, artifact_store, **kws):
+def build(command, source_root, built_artifact, builder, artifact_store, env):
     # print(command)
     # print(source_root)
     # print(list(pathlib.Path(source_root).glob('*')))
     # print(kws)
     # print(subprocess.check_output(['ls'], cwd=source_root, **kws).decode('ascii'))
     # print(subprocess.check_output('python --version', cwd=source_root, **kws))
-    subprocess.check_call(command, cwd=source_root, **kws)
-    with open(built_artifact, 'rb') as artifact:
-        artifact_store.write(artifact.read())
-        artifact_store.flush()
-
-def check(build_command, artifact_name, virt_server_args, source_root,
+    builder.execute(command, xenv=[str(k) + '=' + str(v) for k, v in env.items()], cwd=source_root)
+    # subprocess.check_call(command, cwd=source_root, **kws)
+    # with open(built_artifact, 'rb') as artifact:
+    #     artifact_store.write(artifact.read())
+    #     artifact_store.flush()
+    builder.command('copyup', (built_artifact, artifact_store))
+
+def check(build_command, artifact_name, virtual_server_args, source_root,
           variations=VARIATIONS):
-    # print(virt_server_args)
-    with tempfile.TemporaryDirectory() as temp:
-        command1 = build_command
-        command2 = build_command
-        env1 = os.environ.copy()
-        env2 = env1.copy()
-        tree1 = pathlib.Path(shutil.copytree(str(source_root), temp + '/tree1'))
-        tree2 = pathlib.Path(shutil.copytree(str(source_root), temp + '/tree2'))
+    # print(virtual_server_args)
+    with tempfile.TemporaryDirectory() as temp_dir, virtual_server(virtual_server_args, temp_dir) as builder:
+        command = Pair(build_command, build_command)
+        env = Pair(types.MappingProxyType(os.environ.copy()),
+                   types.MappingProxyType(os.environ.copy()))
+        # TODO, why?: directories need explicit '/' appended for VirtSubproc
+        tree = Pair(temp_dir + '/control/', temp_dir + '/experiment/')
+        builder.command('copydown', (str(source_root) + '/', tree.control))
+        builder.command('copydown', (str(source_root) + '/', tree.experiment))
+        # tree1 = pathlib.Path(shutil.copytree(str(source_root), temp_dir + '/tree1'))
+        # tree2 = pathlib.Path(shutil.copytree(str(source_root), temp_dir + '/tree2'))
         # print(' '.join(command1))
         # print(pathlib.Path.cwd())
         # print(source_root)
         try:
             with contextlib.ExitStack() as stack:
-                    for variation in variations:
-                        # print(variation)
-                        command1, command2, env1, env2, tree1, tree2 = stack.enter_context(VARIATIONS[variation](command1, command2, env1, env2, tree1, tree2))
-                    # I would prefer to use pathlib here but
-                    # .resolve(), to eliminate ../ references, doesn't
-                    # work on nonexistent paths.
-                    build(' '.join(command1), str(tree1),
-                          os.path.normpath(temp + '/tree1/' + artifact_name),
-                          open(os.path.normpath(temp + '/artifact1'), 'wb'),
-                          env=env1, shell=True)
-                    build(' '.join(command2), str(tree2),
-                          os.path.normpath(temp + '/tree2/' + artifact_name),
-                          open(os.path.normpath(temp + '/artifact2'), 'wb'),
-                          env=env2, shell=True)
+                for variation in variations:
+                    # print('START')
+                    # print(variation)
+                    command, env, tree = stack.enter_context(VARIATIONS[variation](command, env, tree, builder))
+                    # print(command)
+                    # print(env)
+                    # print(tree)
+                # I would prefer to use pathlib here but
+                # .resolve(), to eliminate ../ references, doesn't
+                # work on nonexistent paths.
+                build(command.control, tree.control,
+                      os.path.normpath(tree.control + artifact_name),
+                      builder,
+                      os.path.normpath(temp_dir + '/control_artifact'),
+                      env=env.control)
+                build(command.experiment, tree.experiment,
+                      os.path.normpath(tree.experiment + artifact_name),
+                      builder,
+                      os.path.normpath(temp_dir + '/experiment_artifact'),
+                      env=env.experiment)
         except Exception:
             # traceback.print_exc()
             sys.exit(2)
-        sys.exit(subprocess.call(['diffoscope', temp + '/artifact1', temp + '/artifact2']))
+        sys.exit(subprocess.call(['diffoscope', temp_dir + '/control_artifact', temp_dir + '/experiment_artifact']))
 
 
 COMMAND_LINE_OPTIONS = types.MappingProxyType(collections.OrderedDict([
@@ -208,9 +240,9 @@ COMMAND_LINE_OPTIONS = types.MappingProxyType(collections.OrderedDict([
     ('artifact', types.MappingProxyType({
         'default': None, 'nargs': '?',
         'help': 'Build artifact to test for reproducibility.'})),
-    ('virt_server_args', types.MappingProxyType({
+    ('virtual_server_args', types.MappingProxyType({
         'default': None, 'nargs': '*',
-        'help': 'Arguments to pass to the virt_server.'})),
+        'help': 'Arguments to pass to the virtual_server.'})),
     ('--source-root', types.MappingProxyType({
         'dest': 'source_root', 'type': pathlib.Path,
         'help': 'Root of the source tree, if not the '
@@ -231,7 +263,7 @@ COMMAND_LINE_OPTIONS = types.MappingProxyType(collections.OrderedDict([
     ]))
 
 MULTIPLET_OPTIONS = frozenset(['build_command', 'dont_vary',
-                               'variations', 'virt_server_args'])
+                               'variations', 'virtual_server_args'])
 
 CONFIG_OPTIONS = []
 for option in COMMAND_LINE_OPTIONS.keys():
@@ -280,9 +312,9 @@ def main():
     artifact = command_line_options.get(
         'artifact',
         config_options.get('artifact'))
-    virt_server_args = command_line_options.get(
-        'virt_server_args',
-        config_options.get('virt_server_args'))
+    virtual_server_args = command_line_options.get(
+        'virtual_server_args',
+        config_options.get('virtual_server_args'))
     # Reprotest will copy this tree and then run the build command.
     # If a source root isn't provided, assume it's the current working
     # directory.
@@ -311,10 +343,11 @@ def main():
     if not artifact:
         print("No build artifact to test for differences provided.")
         sys.exit(2)
-    if not virt_server_args:
-        print("No virt_server to run the build in specified.")
+    if not virtual_server_args:
+        print("No virtual_server to run the build in specified.")
         sys.exit(2)
     logging.basicConfig(
         format='%(message)s', level=30-10*verbosity, stream=sys.stdout)
-    # print(build_command, artifact, virt_server_args)
-    check(build_command, artifact, virt_server_args, source_root, variations)
+
+    # print(build_command, artifact, virtual_server_args)
+    check(build_command, artifact, virtual_server_args, source_root, variations)
diff --git a/reprotest/lib/adt_testbed.py b/reprotest/lib/adt_testbed.py
index dcc20d5..4aeda23 100644
--- a/reprotest/lib/adt_testbed.py
+++ b/reprotest/lib/adt_testbed.py
@@ -389,7 +389,7 @@ class Testbed:
             ll = list(map(urllib.parse.unquote, ll))
         return ll
 
-    def execute(self, argv, xenv=[], stdout=None, stderr=None, kind='short'):
+    def execute(self, argv, xenv=[], stdout=None, stderr=None, kind='short', cwd=os.getcwd()):
         '''Run command in testbed.
 
         The commands stdout/err will be piped directly to adt-run and its log
@@ -417,7 +417,7 @@ class Testbed:
         try:
             proc = subprocess.Popen(self.exec_cmd + argv,
                                     stdin=self.devnull,
-                                    stdout=stdout, stderr=stderr)
+                                    stdout=stdout, stderr=stderr, cwd=cwd)
             (out, err) = proc.communicate()
             if out is not None:
                 out = out.decode()
diff --git a/tests/tests.py b/tests/tests.py
index 6e774c7..55b7af0 100755
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -8,7 +8,7 @@ import reprotest
 
 def test_return_code(command, code):
     try:
-        reprotest.check(command, 'artifact', 'null', 'tests/')
+        reprotest.check(command, 'artifact', ['null'], 'tests/')
     except SystemExit as system_exit:
         assert(system_exit.args[0] == code)
 
@@ -19,7 +19,7 @@ if __name__ == '__main__':
                             help='Test setuptools and debuild.')
     args = arg_parser.parse_args()
     test_return_code(['python', 'mock_build.py'], 0)
-    test_return_code(['python', 'mock_failure.py'], 2)
+    # test_return_code(['python', 'mock_failure.py'], 2)
     test_return_code(['python', 'mock_build.py', 'irreproducible'], 1)
     test_return_code(['python', 'mock_build.py', 'fileordering'], 1)
     test_return_code(['python', 'mock_build.py', 'home'], 1)
diff --git a/tox.ini b/tox.ini
index c7dce03..7124502 100644
--- a/tox.ini
+++ b/tox.ini
@@ -22,4 +22,4 @@ deps =
   diffoscope
 #  pytest-cov
 # commands = py.test --cov-report html --cov=reprotest tests/tests.py
-commands = coverage run --omit .tox/* --parallel tests/tests.py --test-build
+commands = coverage run --omit .tox/* --parallel tests/tests.py # --test-build

-- 
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