run-tests: make Docker an optional dependency (bug 1357201); r?glob draft
authorGregory Szorc <gps@mozilla.com>
Mon, 17 Apr 2017 13:39:45 -0700
changeset 10790 cf3303ae4f5fe4604810fa46e80174f12a0ad6dc
parent 10789 39083507c6efa3f49237f144bc965c16d3dc077d
child 10791 06900fb2832bc0f9bc116829ebc03415d20c4020
push id1627
push userbmo:gps@mozilla.com
push dateMon, 17 Apr 2017 22:38:11 +0000
reviewersglob
bugs1357201
run-tests: make Docker an optional dependency (bug 1357201); r?glob Currently, run-tests unconditionally imports a module that imports "docker." Furthermore, it unconditionally invokes various Docker-related functionality. As part of future work to split up our monolithic development and test environment, we may not have access to Docker Python code nor will we care about Docker in some execution contexts. So refactor run-tests so the import of vcttesting.docker is optional, --no-docker is implied if vcttesting.docker doesn't import, and code doesn't try to do Docker-related things when we're not using Docker. The conditional invocation of various functions depending on whether a Docker-related context manager is needed produces less than stellar code. I'm not a big fan. The alternative is to create a dummy "docker" object that implements these primitives as no-ops. This would require a bit of extra work and I didn't feel like putting in the effort for testing code that rarely changes. MozReview-Commit-ID: 8kptOPRtNll
run-tests
--- a/run-tests
+++ b/run-tests
@@ -21,17 +21,21 @@ EXTDIR = os.path.join(HERE, 'hgext')
 
 if __name__ == '__main__':
     if 'VIRTUAL_ENV' not in os.environ:
         activate = os.path.join(HERE, 'venv', 'bin', 'activate_this.py')
         execfile(activate, dict(__file__=activate))
         sys.executable = os.path.join(HERE, 'venv', 'bin', 'python')
         os.environ['VIRTUAL_ENV'] = os.path.join(HERE, 'venv')
 
-    import vcttesting.docker as vctdocker
+    try:
+        import vcttesting.docker as vctdocker
+    except ImportError:
+        vctdocker = None
+
     from vcttesting.testing import (
         get_docker_state,
         get_extensions,
         get_hg_version,
         get_test_files,
         run_nose_tests,
         produce_coverage_reports,
         remove_err_files,
@@ -55,16 +59,19 @@ if __name__ == '__main__':
     parser.add_argument('--headless', action='store_true',
         help='Only run tests that do not require a framebuffer '
              '(skips Selenium tests)')
     parser.add_argument('--no-docker', action='store_true',
         help='Only run tests that do not require Docker')
 
     options, extra = parser.parse_known_args(sys.argv)
 
+    if not vctdocker:
+        options.no_docker = True
+
     # some arguments belong to us only. Don't pass it along to run-tests.py.
     filter_args = {
         '--all-versions',
         '--no-hg-tip',
         '--use-last-images',
         '--headless',
         '--no-docker',
     }
@@ -153,29 +160,35 @@ if __name__ == '__main__':
 
     run_all_tests = run_hg_tests + run_unit_tests
 
     if not options.jobs and len(run_all_tests) > 1:
         print('WARNING: Not running tests optimally. Specify -j to run tests '
                 'in parallel.', file=sys.stderr)
 
     # Enable tests to interact with our Docker controlling script.
-    docker_state = os.path.join(HERE, '.dockerstate')
-    docker_url, docker_tls = vctdocker.params_from_env(os.environ)
-    docker = vctdocker.Docker(docker_state, docker_url, tls=docker_tls)
-    if not options.no_docker and docker.is_alive():
-        os.environ['DOCKER_HOSTNAME'] = docker.docker_hostname
+    have_docker = False
+    if not options.no_docker:
+        docker_state = os.path.join(HERE, '.dockerstate')
+        docker_url, docker_tls = vctdocker.params_from_env(os.environ)
+        docker = vctdocker.Docker(docker_state, docker_url, tls=docker_tls)
+
+        if docker.is_alive():
+            have_docker = True
+            os.environ['DOCKER_HOSTNAME'] = docker.docker_hostname
 
-        # We build the base BMO images in the test runner because doing it
-        # from tests would be racey. It is easier to do it here instead of
-        # complicating code with locks.
-        #
-        # But only do this if a test we are running utilizes Docker.
-        os.environ.update(get_docker_state(docker, run_all_tests,
-                          verbose=verbose, use_last=options.use_last_images))
+            # We build the base BMO images in the test runner because doing it
+            # from tests would be racey. It is easier to do it here instead of
+            # complicating code with locks.
+            #
+            # But only do this if a test we are running utilizes Docker.
+            os.environ.update(
+                get_docker_state(docker, run_all_tests,
+                                 verbose=verbose,
+                                 use_last=options.use_last_images))
 
     # Our custom HGHAVE introduces a check for running Mercurial version.
     # This is done by consulting a HGVERSION environment variable.
     hg = os.path.join(os.environ['VIRTUAL_ENV'], 'bin', 'hg')
     if options.with_hg:
         hg = options.with_hg
     hgversion = get_hg_version(hg)
     if hgversion is None:
@@ -189,26 +202,34 @@ if __name__ == '__main__':
         # The Mercurial test harness has been observed to not remove the .err
         # files after execution. This is probably a result of us using
         # separate directories. Manually remove .err files.
         remove_err_files(run_hg_tests)
 
         # We should ideally not leak containers and images. But until
         # the Mercurial test harness allows tests to register cleanup
         # actions, there is only so much we can do.
-        with docker.auto_clean_orphans():
-            res = subprocess.call(hg_harness_args + run_hg_tests, cwd=HERE)
+        run = lambda: subprocess.call(hg_harness_args + run_hg_tests, cwd=HERE)
+        if have_docker:
+            with docker.auto_clean_orphans():
+                res = run()
+        else:
+            res = run()
 
     if options.no_unit:
         run_unit_tests = []
 
     if run_unit_tests:
-        with docker.auto_clean_orphans():
-            noseres = run_nose_tests(run_unit_tests, options.jobs,
-                                     verbose=verbose)
+        run = lambda: run_nose_tests(run_unit_tests, options.jobs, verbose=verbose)
+        if have_docker:
+            with docker.auto_clean_orphans():
+                noseres = run()
+        else:
+            noseres = run()
+
         if noseres:
             res = noseres
 
     # If we're running the full compatibility run, figure out what versions
     # apply to what and run them.
     if options.all_versions:
         # No need to grab code coverage for legacy versions - it just slows
         # us down.
@@ -257,18 +278,22 @@ if __name__ == '__main__':
                 '--with-hg',
                 os.path.join(mercurials_dir, version, 'bin', 'hg'),
             ] + sorted(tests)
             args[0] = RUNTESTS
 
             print('Testing with Mercurial %s' % version)
             sys.stdout.flush()
             os.environ['HGVERSION'] = version
-            with docker.auto_clean_orphans():
-                r = subprocess.call(args, cwd=HERE)
+            run = lambda: subprocess.call(args, cwd=HERE)
+            if have_docker:
+                with docker.auto_clean_orphans():
+                    run()
+            else:
+                run()
             sys.stdout.flush()
             sys.stderr.flush()
 
         for version, tests in sorted(versions.items()):
             res2 = run_hg_tests(version, tests)
             if res2:
                 res = res2