2800: Introduce TestCaseWithServers to Python SDK.
authorBrett Smith <brett@curoverse.com>
Wed, 20 Aug 2014 13:46:49 +0000 (09:46 -0400)
committerBrett Smith <brett@curoverse.com>
Wed, 20 Aug 2014 18:18:10 +0000 (14:18 -0400)
This is a subclass of unittest.TestCase.  It looks for specific class
variables to launch supporting Arvados servers for subclass test
cases, taking care to adjust the environment and local Arvados
configuration to match.  Put another away, this refactors a number of
similar setUpClass/tearDownClass methods throughout the SDK tests.

sdk/python/tests/arvados_testutil.py
sdk/python/tests/run_test_server.py
sdk/python/tests/test_arv_put.py
sdk/python/tests/test_collections.py
sdk/python/tests/test_keep_client.py
sdk/python/tests/test_websockets.py

index cd86d80dc0d863f48b4fa692426e9ca4c0a24534..369d5618044036c30698bb263a2b1aa188b3621b 100644 (file)
@@ -27,20 +27,6 @@ class ArvadosBaseTestCase(unittest.TestCase):
             basedir = '.'
         return open(os.path.join(basedir, 'data', filename))
 
-
-class ArvadosKeepLocalStoreTestCase(ArvadosBaseTestCase):
-    def setUp(self):
-        super(ArvadosKeepLocalStoreTestCase, self).setUp()
-        self._orig_keep_local_store = os.environ.get('KEEP_LOCAL_STORE')
-        os.environ['KEEP_LOCAL_STORE'] = self.make_tmpdir()
-
-    def tearDown(self):
-        if self._orig_keep_local_store is None:
-            del os.environ['KEEP_LOCAL_STORE']
-        else:
-            os.environ['KEEP_LOCAL_STORE'] = self._orig_keep_local_store
-        super(ArvadosKeepLocalStoreTestCase, self).tearDown()
-
     def build_directory_tree(self, tree):
         tree_root = self.make_tmpdir()
         for leaf in tree:
index a225b745587e0c88b6737ccb80cd12033149b2ad..0173718debf5b2eee05c06dcc6cc8c7f4b79a036 100644 (file)
@@ -8,6 +8,7 @@ import subprocess
 import sys
 import tempfile
 import time
+import unittest
 import yaml
 
 MY_DIRNAME = os.path.dirname(os.path.realpath(__file__))
@@ -216,7 +217,7 @@ def run_keep_proxy(auth):
     api = arvados.api('v1', cache=False)
     api.keep_services().create(body={"keep_service": {"service_host": "localhost",  "service_port": 25101, "service_type": "proxy"} }).execute()
 
-    arvados.config.settings()["ARVADOS_KEEP_PROXY"] = "http://localhost:25101"
+    os.environ["ARVADOS_KEEP_PROXY"] = "http://localhost:25101"
 
 def stop_keep_proxy():
     kill_server_pid("tests/tmp/keepproxy.pid", 0)
@@ -233,6 +234,65 @@ def authorize_with(token):
     arvados.config.settings()["ARVADOS_API_HOST"] = os.environ.get("ARVADOS_API_HOST")
     arvados.config.settings()["ARVADOS_API_HOST_INSECURE"] = "true"
 
+class TestCaseWithServers(unittest.TestCase):
+    """TestCase to start and stop supporting Arvados servers.
+
+    Define any of MAIN_SERVER, KEEP_SERVER, and/or KEEP_PROXY_SERVER
+    class variables as a dictionary of keyword arguments.  If you do,
+    setUpClass will start the corresponding servers by passing these
+    keyword arguments to the run, run_keep, and/or run_keep_server
+    functions, respectively.  It will also set Arvados environment
+    variables to point to these servers appropriately.  If you don't
+    run a Keep or Keep proxy server, setUpClass will set up a
+    temporary directory for Keep local storage, and set it as
+    KEEP_LOCAL_STORE.
+
+    tearDownClass will stop any servers started, and restore the
+    original environment.
+    """
+    MAIN_SERVER = None
+    KEEP_SERVER = None
+    KEEP_PROXY_SERVER = None
+
+    @staticmethod
+    def _restore_dict(src, dest):
+        for key in dest.keys():
+            if key not in src:
+                del dest[key]
+        dest.update(src)
+
+    @classmethod
+    def setUpClass(cls):
+        cls._orig_environ = os.environ.copy()
+        cls._orig_config = arvados.config.settings().copy()
+        cls._cleanup_funcs = []
+        for server_kwargs, start_func, stop_func in (
+              (cls.MAIN_SERVER, run, stop),
+              (cls.KEEP_SERVER, run_keep, stop_keep),
+              (cls.KEEP_PROXY_SERVER, run_keep_proxy, stop_keep_proxy)):
+            if server_kwargs is not None:
+                start_func(**server_kwargs)
+                cls._cleanup_funcs.append(stop_func)
+        os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
+        if cls.KEEP_PROXY_SERVER is None:
+            os.environ.pop('ARVADOS_KEEP_PROXY', None)
+        if (cls.KEEP_SERVER is None) and (cls.KEEP_PROXY_SERVER is None):
+            cls.local_store = tempfile.mkdtemp()
+            os.environ['KEEP_LOCAL_STORE'] = cls.local_store
+            cls._cleanup_funcs.append(
+                lambda: shutil.rmtree(cls.local_store, ignore_errors=True))
+        else:
+            os.environ.pop('KEEP_LOCAL_STORE', None)
+        arvados.config.initialize()
+
+    @classmethod
+    def tearDownClass(cls):
+        for clean_func in cls._cleanup_funcs:
+            clean_func()
+        cls._restore_dict(cls._orig_environ, os.environ)
+        cls._restore_dict(cls._orig_config, arvados.config.settings())
+
+
 if __name__ == "__main__":
     parser = argparse.ArgumentParser()
     parser.add_argument('action', type=str, help='''one of "start", "stop", "start_keep", "stop_keep"''')
index 5ce475ee9c47de59bb8e2175c740a0510fb2cc44..de4fdd2be9f570b54147ece8bb5c6efa53e00b15 100644 (file)
@@ -18,7 +18,7 @@ from cStringIO import StringIO
 import arvados
 import arvados.commands.put as arv_put
 
-from arvados_testutil import ArvadosBaseTestCase, ArvadosKeepLocalStoreTestCase
+from arvados_testutil import ArvadosBaseTestCase
 import run_test_server
 
 class ArvadosPutResumeCacheTest(ArvadosBaseTestCase):
@@ -196,9 +196,11 @@ class ArvadosPutResumeCacheTest(ArvadosBaseTestCase):
                           arv_put.ResumeCache, path)
 
 
-class ArvadosPutCollectionWriterTest(ArvadosKeepLocalStoreTestCase):
+class ArvadosPutCollectionWriterTest(run_test_server.TestCaseWithServers,
+                                     ArvadosBaseTestCase):
     def setUp(self):
         super(ArvadosPutCollectionWriterTest, self).setUp()
+        run_test_server.authorize_with('active')
         with tempfile.NamedTemporaryFile(delete=False) as cachefile:
             self.cache = arv_put.ResumeCache(cachefile.name)
             self.cache_filename = cachefile.name
@@ -398,7 +400,9 @@ class ArvadosPutProjectLinkTest(ArvadosBaseTestCase):
                           ['--project-uuid', self.Z_UUID, '--stream'])
 
 
-class ArvadosPutTest(ArvadosKeepLocalStoreTestCase):
+class ArvadosPutTest(run_test_server.TestCaseWithServers, ArvadosBaseTestCase):
+    MAIN_SERVER = {}
+
     def call_main_with_args(self, args):
         self.main_stdout = StringIO()
         self.main_stderr = StringIO()
@@ -415,6 +419,7 @@ class ArvadosPutTest(ArvadosKeepLocalStoreTestCase):
 
     def setUp(self):
         super(ArvadosPutTest, self).setUp()
+        run_test_server.authorize_with('active')
         arv_put.api_client = None
 
     def tearDown(self):
@@ -454,41 +459,32 @@ class ArvadosPutTest(ArvadosKeepLocalStoreTestCase):
                           ['--name', 'test without Collection',
                            '--stream', '/dev/null'])
 
-class ArvPutIntegrationTest(unittest.TestCase):
-    PROJECT_UUID = run_test_server.fixture('groups')['aproject']['uuid']
-    ENVIRON = os.environ
-    ENVIRON['PYTHONPATH'] = ':'.join(sys.path)
-
-    @classmethod
-    def setUpClass(cls):
-        try:
-            del os.environ['KEEP_LOCAL_STORE']
-        except KeyError:
-            pass
-
-        # Use the blob_signing_key from the Rails "test" configuration
-        # to provision the Keep server.
-        config_blob_signing_key = None
+class ArvPutIntegrationTest(run_test_server.TestCaseWithServers,
+                            ArvadosBaseTestCase):
+    def _getKeepServerConfig():
         for config_file in ['application.yml', 'application.default.yml']:
             with open(os.path.join(run_test_server.SERVICES_SRC_DIR,
                                    "api", "config", config_file)) as f:
                 rails_config = yaml.load(f.read())
                 for config_section in ['test', 'common']:
                     try:
-                        config_blob_signing_key = rails_config[config_section]["blob_signing_key"]
-                        break
-                    except KeyError:
+                        key = rails_config[config_section]["blob_signing_key"]
+                    except (KeyError, TypeError):
                         pass
-            if config_blob_signing_key is not None:
-                break
-        run_test_server.run()
-        run_test_server.run_keep(blob_signing_key=config_blob_signing_key,
-                                 enforce_permissions=(config_blob_signing_key != None))
+                    else:
+                        return {'blob_signing_key': key,
+                                'enforce_permissions': True}
+        return {'blog_signing_key': None, 'enforce_permissions': False}
+
+    MAIN_SERVER = {}
+    KEEP_SERVER = _getKeepServerConfig()
+    PROJECT_UUID = run_test_server.fixture('groups')['aproject']['uuid']
 
     @classmethod
-    def tearDownClass(cls):
-        run_test_server.stop()
-        run_test_server.stop_keep()
+    def setUpClass(cls):
+        super(ArvPutIntegrationTest, cls).setUpClass()
+        cls.ENVIRON = os.environ.copy()
+        cls.ENVIRON['PYTHONPATH'] = ':'.join(sys.path)
 
     def setUp(self):
         super(ArvPutIntegrationTest, self).setUp()
@@ -499,7 +495,7 @@ class ArvPutIntegrationTest(unittest.TestCase):
         for v in ["ARVADOS_API_HOST",
                   "ARVADOS_API_HOST_INSECURE",
                   "ARVADOS_API_TOKEN"]:
-            os.environ[v] = arvados.config.settings()[v]
+            self.ENVIRON[v] = arvados.config.settings()[v]
         arv_put.api_client = arvados.api('v1', cache=False)
 
     def current_user(self):
@@ -556,7 +552,7 @@ class ArvPutIntegrationTest(unittest.TestCase):
             notfound = arv_put.api_client.collections().get(
                 uuid=manifest_uuid).execute()
 
-        datadir = tempfile.mkdtemp()
+        datadir = self.make_tmpdir()
         with open(os.path.join(datadir, "foo"), "w") as f:
             f.write("The quick brown fox jumped over the lazy dog")
         p = subprocess.Popen([sys.executable, arv_put.__file__, datadir],
@@ -573,9 +569,6 @@ class ArvPutIntegrationTest(unittest.TestCase):
             c['manifest_text'],
             r'^\. 08a008a01d498c404b0c30852b39d3b8\+44\+A[0-9a-f]+@[0-9a-f]+ 0:44:foo\n')
 
-        os.remove(os.path.join(datadir, "foo"))
-        os.rmdir(datadir)
-
     def run_and_find_link(self, text, extra_args=[]):
         self.authorize_with('active')
         pipe = subprocess.Popen(
index b1af723635f46def25abb8516d631de5362e6329..68c9698c32e3feaebf6402297d30168bbea2d44f 100644 (file)
@@ -11,7 +11,8 @@ import subprocess
 import tempfile
 import unittest
 
-from arvados_testutil import ArvadosKeepLocalStoreTestCase
+import run_test_server
+from arvados_testutil import ArvadosBaseTestCase
 
 class TestResumableWriter(arvados.ResumableCollectionWriter):
     KEEP_BLOCK_SIZE = 1024  # PUT to Keep every 1K.
@@ -20,7 +21,15 @@ class TestResumableWriter(arvados.ResumableCollectionWriter):
         return self.dump_state(copy.deepcopy)
 
 
-class ArvadosCollectionsTest(ArvadosKeepLocalStoreTestCase):
+class ArvadosCollectionsTest(run_test_server.TestCaseWithServers,
+                             ArvadosBaseTestCase):
+    MAIN_SERVER = {}
+
+    @classmethod
+    def setUpClass(cls):
+        super(ArvadosCollectionsTest, cls).setUpClass()
+        run_test_server.authorize_with('active')
+
     def write_foo_bar_baz(self):
         cw = arvados.CollectionWriter()
         self.assertEqual(cw.current_stream_name(), '.',
index 9d3cecd9614ede083a770204f63736518a2b618d..fb52ab4ab56bb879865c4bd68ed0788d5fcd49c5 100644 (file)
@@ -2,38 +2,33 @@
 #
 # ARVADOS_API_TOKEN=abc ARVADOS_API_HOST=arvados.local python -m unittest discover
 
+import contextlib
 import os
 import unittest
 
 import arvados
 import run_test_server
 
-class KeepTestCase(unittest.TestCase):
-    @classmethod
-    def setUpClass(cls):
-        super(KeepTestCase, cls).setUpClass()
-        try:
-            del os.environ['KEEP_LOCAL_STORE']
-        except KeyError:
-            pass
-
-        # Make sure these are clear, we want to talk to the Keep servers
-        # directly.
-        os.environ["ARVADOS_KEEP_PROXY"] = ""
-        os.environ["ARVADOS_EXTERNAL_CLIENT"] = ""
-
-        run_test_server.run()
-        run_test_server.run_keep()
+@contextlib.contextmanager
+def unauthenticated_client(keep_client=None):
+    if keep_client is None:
+        keep_client = arvados.keep.global_client_object
+    if not hasattr(keep_client, 'api_token'):
+        yield keep_client
+    else:
+        orig_token = keep_client.api_token
+        keep_client.api_token = ''
+        yield keep_client
+        keep_client.api_token = orig_token
+
+class KeepTestCase(run_test_server.TestCaseWithServers):
+    MAIN_SERVER = {}
+    KEEP_SERVER = {}
+
+    def setUp(self):
         arvados.keep.global_client_object = None
-        arvados.config._settings = None
         run_test_server.authorize_with("admin")
 
-    @classmethod
-    def tearDownClass(cls):
-        super(KeepTestCase, cls).tearDownClass()
-        run_test_server.stop()
-        run_test_server.stop_keep()
-
     def test_KeepBasicRWTest(self):
         foo_locator = arvados.Keep.put('foo')
         self.assertRegexpMatches(
@@ -79,22 +74,11 @@ class KeepTestCase(unittest.TestCase):
                          blob_str,
                          'wrong content from Keep.get(md5(<binarydata>))')
 
-class KeepPermissionTestCase(unittest.TestCase):
-    @classmethod
-    def setUpClass(cls):
-        try:
-            del os.environ['KEEP_LOCAL_STORE']
-        except KeyError:
-            pass
 
-        run_test_server.run()
-        run_test_server.run_keep(blob_signing_key='abcdefghijk0123456789',
-                                 enforce_permissions=True)
-
-    @classmethod
-    def tearDownClass(cls):
-        run_test_server.stop()
-        run_test_server.stop_keep()
+class KeepPermissionTestCase(run_test_server.TestCaseWithServers):
+    MAIN_SERVER = {}
+    KEEP_SERVER = {'blob_signing_key': 'abcdefghijk0123456789',
+                   'enforce_permissions': True}
 
     def test_KeepBasicRWTest(self):
         run_test_server.authorize_with('active')
@@ -126,13 +110,14 @@ class KeepPermissionTestCase(unittest.TestCase):
 
         # Unauthenticated GET for a signed locator => NotFound
         # Unauthenticated GET for an unsigned locator => NotFound
-        arvados.keep.global_client_object.api_token = ''
-        self.assertRaises(arvados.errors.NotFoundError,
-                          arvados.Keep.get,
-                          bar_locator)
-        self.assertRaises(arvados.errors.NotFoundError,
-                          arvados.Keep.get,
-                          unsigned_bar_locator)
+        with unauthenticated_client():
+            self.assertRaises(arvados.errors.NotFoundError,
+                              arvados.Keep.get,
+                              bar_locator)
+            self.assertRaises(arvados.errors.NotFoundError,
+                              arvados.Keep.get,
+                              unsigned_bar_locator)
+
 
 # KeepOptionalPermission: starts Keep with --permission-key-file
 # but not --enforce-permissions (i.e. generate signatures on PUT
@@ -143,22 +128,10 @@ class KeepPermissionTestCase(unittest.TestCase):
 # * authenticated request, unsigned locator
 # * unauthenticated request, signed locator
 # * unauthenticated request, unsigned locator
-
-class KeepOptionalPermission(unittest.TestCase):
-    @classmethod
-    def setUpClass(cls):
-        try:
-            del os.environ['KEEP_LOCAL_STORE']
-        except KeyError:
-            pass
-        run_test_server.run()
-        run_test_server.run_keep(blob_signing_key='abcdefghijk0123456789',
-                                 enforce_permissions=False)
-
-    @classmethod
-    def tearDownClass(cls):
-        run_test_server.stop()
-        run_test_server.stop_keep()
+class KeepOptionalPermission(run_test_server.TestCaseWithServers):
+    MAIN_SERVER = {}
+    KEEP_SERVER = {'blob_signing_key': 'abcdefghijk0123456789',
+                   'enforce_permissions': False}
 
     def test_KeepAuthenticatedSignedTest(self):
         run_test_server.authorize_with('active')
@@ -192,10 +165,10 @@ class KeepOptionalPermission(unittest.TestCase):
             r'^acbd18db4cc2f85cedef654fccc4a4d8\+3\+A[a-f0-9]+@[a-f0-9]+$',
             'invalid locator from Keep.put("foo"): ' + signed_locator)
 
-        arvados.keep.global_client_object.api_token = ''
-        self.assertEqual(arvados.Keep.get(signed_locator),
-                         'foo',
-                         'wrong content from Keep.get(md5("foo"))')
+        with unauthenticated_client():
+            self.assertEqual(arvados.Keep.get(signed_locator),
+                             'foo',
+                             'wrong content from Keep.get(md5("foo"))')
 
     def test_KeepUnauthenticatedUnsignedTest(self):
         # Since --enforce-permissions is not in effect, GET requests
@@ -207,48 +180,30 @@ class KeepOptionalPermission(unittest.TestCase):
             r'^acbd18db4cc2f85cedef654fccc4a4d8\+3\+A[a-f0-9]+@[a-f0-9]+$',
             'invalid locator from Keep.put("foo"): ' + signed_locator)
 
-        arvados.keep.global_client_object.api_token = ''
-        self.assertEqual(arvados.Keep.get("acbd18db4cc2f85cedef654fccc4a4d8"),
-                         'foo',
-                         'wrong content from Keep.get(md5("foo"))')
+        with unauthenticated_client():
+            self.assertEqual(arvados.Keep.get("acbd18db4cc2f85cedef654fccc4a4d8"),
+                             'foo',
+                             'wrong content from Keep.get(md5("foo"))')
 
 
-class KeepProxyTestCase(unittest.TestCase):
+class KeepProxyTestCase(run_test_server.TestCaseWithServers):
+    MAIN_SERVER = {}
+    KEEP_SERVER = {}
+    KEEP_PROXY_SERVER = {'auth': 'admin'}
+
     @classmethod
     def setUpClass(cls):
         super(KeepProxyTestCase, cls).setUpClass()
+        cls.proxy_addr = os.environ['ARVADOS_KEEP_PROXY']
 
-        try:
-            del os.environ['KEEP_LOCAL_STORE']
-        except KeyError:
-            pass
-
-        os.environ["ARVADOS_KEEP_PROXY"] = ""
-        os.environ["ARVADOS_EXTERNAL_CLIENT"] = ""
-
-        run_test_server.run()
-        run_test_server.run_keep()
+    def setUp(self):
         arvados.keep.global_client_object = None
-        arvados.config._settings = None
-        run_test_server.run_keep_proxy("admin")
-        KeepProxyTestCase.arvados_keep_proxy = arvados.config.get("ARVADOS_KEEP_PROXY")
-
-    @classmethod
-    def tearDownClass(cls):
-        super(KeepProxyTestCase, cls).tearDownClass()
-        run_test_server.stop()
-        run_test_server.stop_keep()
-        run_test_server.stop_keep_proxy()
+        os.environ['ARVADOS_KEEP_PROXY'] = self.proxy_addr
+        os.environ.pop('ARVADOS_EXTERNAL_CLIENT', None)
 
     def test_KeepProxyTest1(self):
         # Will use ARVADOS_KEEP_PROXY environment variable that is set by
-        # run_keep_proxy() in setUpClass()
-
-        os.environ["ARVADOS_KEEP_PROXY"] = KeepProxyTestCase.arvados_keep_proxy
-        os.environ["ARVADOS_EXTERNAL_CLIENT"] = ""
-        arvados.keep.global_client_object = None
-        arvados.config._settings = None
-
+        # setUpClass().
         baz_locator = arvados.Keep.put('baz')
         self.assertRegexpMatches(
             baz_locator,
@@ -266,12 +221,9 @@ class KeepProxyTestCase(unittest.TestCase):
         # contact the API server.
         os.environ["ARVADOS_KEEP_PROXY"] = ""
         os.environ["ARVADOS_EXTERNAL_CLIENT"] = "true"
-        arvados.keep.global_client_object = None
-        arvados.config._settings = None
 
         # Will send X-External-Client to server and get back the proxy from
         # keep_services/accessible
-
         baz_locator = arvados.Keep.put('baz2')
         self.assertRegexpMatches(
             baz_locator,
index 6b57fe3c8ecdf8fb64c0618c24c49ff201785b5f..1dae978c843b052a19469c1e8dfc0ee876a0ede9 100644 (file)
@@ -4,9 +4,8 @@ import arvados
 import arvados.events
 import time
 
-class WebsocketTest(unittest.TestCase):
-    def setUp(self):
-        run_test_server.run(websockets=True)
+class WebsocketTest(run_test_server.TestCaseWithServers):
+    MAIN_SERVER = {'websockets': True}
 
     def on_event(self, ev):
         if self.state == 1:
@@ -27,6 +26,3 @@ class WebsocketTest(unittest.TestCase):
         time.sleep(1)
         self.h = api.humans().create(body={}).execute()
         time.sleep(1)
-
-    def tearDown(self):
-        run_test_server.stop()