Sandboxed layers allow tests with lengthy ZopeTestCases whose setUp funtions are lentghy to build ZODB.FileStorage databases on first run allowing successive runs to use the existing ZODB.FileStorage databases without running the lengthy setUp functions. The ZODB.FileStorage databases will be recreated on next run if they've been deleted allowing one to recreate the test fixture when needed.
WARNING: Sandbox layers give one a lot of rope with which to hang themselves. One should always try write to fast running unit tests instead of tests with expensive setUp functions. If one must live with tests with expensive setUp functions then Sandbox layers can save a lot of time, but only if one is absolutely certain that the test fixture hasn't changed since the layer setUp was last run. When in doubt, delete all ZODB.FileStorage files and run the layer setUp functions fresh.
Expensive setup is done by normal zope.testing layers.
>>> from Testing import ZopeTestCase >>> class FooLayer(object): ... @classmethod ... def setUp(cls): ... cls.app = ZopeTestCase.app() ... cls.app.foo = 'foo' ... @classmethod ... def tearDown(cls): ... ZopeTestCase.close(cls.app) ... del cls.app
In your tests, use a Sandbox layer instead of using the expensive layer.
>>> from grouparchy.testing import sandbox >>> from grouparchy.testing.tests import test_sandbox >>> layer = sandbox.Sandbox(FooLayer, var=test_sandbox.Layer.tmp)
When the testrunner runs the setUp function of this layer for the first time a ZODB.FileStorage is created and ZopeTestCase.app is patched to use this storage.
>>> import os.path >>> foo_filename = sandbox.getFilename(FooLayer) >>> foo_path = os.path.join(layer.var, foo_filename) >>> foo_path '.../__builtin__.FooLayer.fs'>>> os.path.isfile(foo_path) False >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.DemoStorage.DemoStorage instance at ...> >>> ZopeTestCase.close(app)>>> layer.setUp() Set up FooLayer at .../__builtin__.FooLayer.fs in ... seconds.>>> os.path.isfile(foo_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.FileStorage.FileStorage.FileStorage object at ...> >>> ZopeTestCase.close(app)
On layer tearDown, the ZODB.FileStorage database is closed but not removed and ZopeTestCase.app is restored.
>>> layer.tearDown()>>> os.path.isfile(foo_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.DemoStorage.DemoStorage instance at ...> >>> ZopeTestCase.close(app)
If the layer setUp is run again, the existing ZODB.FileStorage database will be used if present.
>>> layer.setUp() Load FooLayer at .../__builtin__.FooLayer.fs in ... seconds.>>> os.path.isfile(foo_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.FileStorage.FileStorage.FileStorage object at ...> >>> ZopeTestCase.close(app)>>> layer.tearDown()>>> os.path.isfile(foo_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.DemoStorage.DemoStorage instance at ...> >>> ZopeTestCase.close(app)
If we delete the ZODB.FileStorage database and run the layer setUp again, the file will be recreated.
>>> import os >>> os.remove(foo_path)>>> os.path.isfile(foo_path) False >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.DemoStorage.DemoStorage instance at ...> >>> ZopeTestCase.close(app)>>> layer.setUp() Set up FooLayer at .../__builtin__.FooLayer.fs in ... seconds.>>> os.path.isfile(foo_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.FileStorage.FileStorage.FileStorage object at ...> >>> ZopeTestCase.close(app)>>> layer.tearDown()>>> os.path.isfile(foo_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.DemoStorage.DemoStorage instance at ...> >>> ZopeTestCase.close(app)
If the layer to be setUp by the Sanbox layer has base layers, those base layers will be setup in separate databases which will be copied as the base for running the setUp functions of the subclassing layer.
>>> class BarLayer(FooLayer): ... @classmethod ... def setUp(cls): ... cls.app = ZopeTestCase.app() ... cls.app.bar = 'bar' ... @classmethod ... def tearDown(cls): ... ZopeTestCase.close(cls.app) ... del cls.app>>> layer = sandbox.Sandbox(BarLayer, var=layer.var)>>> bar_filename = sandbox.getFilename(BarLayer) >>> bar_path = os.path.join(layer.var, bar_filename) >>> bar_path '.../__builtin__.BarLayer.fs'>>> os.path.isfile(foo_path) True >>> os.path.isfile(bar_path) False >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.DemoStorage.DemoStorage instance at ...> >>> ZopeTestCase.close(app)>>> layer.setUp() Load FooLayer at .../__builtin__.FooLayer.fs in ... seconds. Set up BarLayer at .../__builtin__.BarLayer.fs in ... seconds.>>> os.path.isfile(foo_path) True >>> os.path.isfile(bar_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.FileStorage.FileStorage.FileStorage object at ...> >>> ZopeTestCase.close(app)
On tearDown, none of the base layer databases will be removed.
>>> layer.tearDown()>>> os.path.isfile(foo_path) True >>> os.path.isfile(bar_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.DemoStorage.DemoStorage instance at ...> >>> ZopeTestCase.close(app)
On subsequent setUp, only the necessary databases are loaded.
>>> layer.setUp() Load BarLayer at .../__builtin__.BarLayer.fs in ... seconds.>>> os.path.isfile(foo_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.FileStorage.FileStorage.FileStorage object at ...> >>> ZopeTestCase.close(app)>>> layer.tearDown()>>> os.path.isfile(foo_path) True >>> os.path.isfile(bar_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.DemoStorage.DemoStorage instance at ...> >>> ZopeTestCase.close(app)
When the database for a layer with bases is removed, only that layer needs to be setUp again.
>>> os.remove(bar_path)>>> os.path.isfile(foo_path) True >>> os.path.isfile(bar_path) False >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.DemoStorage.DemoStorage instance at ...> >>> ZopeTestCase.close(app)>>> layer.setUp() Load FooLayer at .../__builtin__.FooLayer.fs in ... seconds. Set up BarLayer at .../__builtin__.BarLayer.fs in ... seconds.>>> os.path.isfile(foo_path) True >>> os.path.isfile(bar_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.FileStorage.FileStorage.FileStorage object at ...> >>> ZopeTestCase.close(app)>>> layer.tearDown()>>> os.path.isfile(foo_path) True >>> os.path.isfile(bar_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.DemoStorage.DemoStorage instance at ...> >>> ZopeTestCase.close(app)
If the database for a base layer is removed but the database for the top layer remains, no layer will need to be setUp.
>>> os.remove(foo_path)>>> os.path.isfile(foo_path) False >>> os.path.isfile(bar_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.DemoStorage.DemoStorage instance at ...> >>> ZopeTestCase.close(app)>>> layer.setUp() Load BarLayer at .../__builtin__.BarLayer.fs in ... seconds.>>> os.path.isfile(foo_path) False >>> os.path.isfile(bar_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.FileStorage.FileStorage.FileStorage object at ...> >>> ZopeTestCase.close(app)>>> layer.tearDown()>>> os.path.isfile(foo_path) False >>> os.path.isfile(bar_path) True >>> app = ZopeTestCase.app() >>> app._p_jar.db()._storage <ZODB.DemoStorage.DemoStorage instance at ...> >>> ZopeTestCase.close(app)
If the var arg is not passed to the Sandbox layer constructor, it will try to use the instance home if present.
>>> import App.config >>> sandbox.Sandbox(FooLayer).var == os.path.join( ... App.config.getConfiguration().instancehome, 'var') True