def test_no_unknown_patch_versions(self): """ L{PatchApplier.get_unknown_patch_versions} returns an empty set if no patches are unapplied. """ patches = [Patch(42), Patch(380)] my_store = MockPatchStore("database", patches=patches) patch_applier = PatchApplier(my_store, self.mypackage) self.assertEqual(set(), patch_applier.get_unknown_patch_versions())
def test_get_unknown_patch_versions(self): """ L{PatchApplier.get_unknown_patch_versions} returns the versions of all unapplied patches. """ patches = [Patch(42), Patch(380), Patch(381)] my_store = MockPatchStore("database", patches=patches) patch_applier = PatchApplier(my_store, self.mypackage) self.assertEqual(set([381]), patch_applier.get_unknown_patch_versions())
def setUp(self): super(PatchApplierTest, self).setUp() self.patchdir = self.makeDir() self.pkgdir = os.path.join(self.patchdir, "mypackage") os.makedirs(self.pkgdir) f = open(os.path.join(self.pkgdir, "__init__.py"), "w") f.write("shared_data = []") f.close() # Order of creation here is important to try to screw up the # patch ordering, as os.listdir returns in order of mtime (or # something). for pname, data in [("patch_380.py", patch_test_1), ("patch_42.py", patch_test_0)]: self.add_module(pname, data) sys.path.append(self.patchdir) self.filename = self.makeFile() self.uri = "sqlite:///%s" % self.filename self.store = Store(create_database(self.uri)) self.store.execute("CREATE TABLE patch " "(version INTEGER NOT NULL PRIMARY KEY)") self.assertFalse(self.store.get(Patch, (42))) self.assertFalse(self.store.get(Patch, (380))) import mypackage self.mypackage = mypackage self.patch_set = PatchSet(mypackage) # Create another connection just to keep track of the state of the # whole transaction manager. See the assertion functions below. self.another_store = Store(create_database("sqlite:")) self.another_store.execute("CREATE TABLE test (id INT)") self.another_store.commit() self.prepare_for_transaction_check() class Committer(object): def commit(committer): self.store.commit() self.another_store.commit() def rollback(committer): self.store.rollback() self.another_store.rollback() self.committer = Committer() self.patch_applier = PatchApplier(self.store, self.patch_set, self.committer)
def has_pending_patches(self, store): """Check if the C{store} needs a schema upgrade. Nothing will be committed to the database. Run C{upgrade()} to apply the unapplied schema patches. Returns True if the schema needs an upgrade, False otherwise. """ try: store.execute("SELECT * FROM patch WHERE version=0") except StormError: return True else: patch_applier = PatchApplier(store, self._patch_package) return patch_applier.has_pending_patches()
def getPatchStatus(store, schema): """Get the patch status for a database. @param store: The C{Store} for the database. @param schema: The Storm C{Schema} for the database. @return: A L{PatchStatus} instance with information about the patch level of the database. """ # FIXME It's a bit unfortunate that we're accessing private attributes and # methods of Schema and PatchApplier in this code, but there's no way to # get the information we need with the public API. This is really a bug # in Storm, see bug #754468 for more details about it. patchApplier = PatchApplier(store, schema._patch_package) unappliedPatches = sorted(patchApplier._get_unapplied_versions()) unknownPatches = sorted(patchApplier.get_unknown_patch_versions()) return PatchStatus(unappliedPatches, unknownPatches)
def setUp(self): super(PatchTest, self).setUp() self.patchdir = self.makeDir() self.pkgdir = os.path.join(self.patchdir, "mypackage") os.makedirs(self.pkgdir) f = open(os.path.join(self.pkgdir, "__init__.py"), "w") f.write("shared_data = []") f.close() # Order of creation here is important to try to screw up the # patch ordering, as os.listdir returns in order of mtime (or # something). for pname, data in [("patch_380.py", patch_test_1), ("patch_42.py", patch_test_0)]: self.add_module(pname, data) sys.path.append(self.patchdir) self.filename = self.makeFile() self.uri = "sqlite:///%s" % self.filename self.store = Store(create_database(self.uri)) self.store.execute("CREATE TABLE patch " "(version INTEGER NOT NULL PRIMARY KEY)") self.assertFalse(self.store.get(Patch, (42))) self.assertFalse(self.store.get(Patch, (380))) import mypackage self.mypackage = mypackage # Create another connection just to keep track of the state of the # whole transaction manager. See the assertion functions below. self.another_store = Store(create_database("sqlite:")) self.another_store.execute("CREATE TABLE test (id INT)") self.another_store.commit() self.prepare_for_transaction_check() class Committer(object): def commit(committer): self.store.commit() self.another_store.commit() def rollback(committer): self.store.rollback() self.another_store.rollback() self.committer = Committer() self.patch_applier = PatchApplier(self.store, self.mypackage, self.committer)
def upgrade(self, store): """Upgrade C{store} to have the latest schema. If a schema isn't present a new one will be created. Unapplied patches will be applied to an existing schema. """ patch_applier = PatchApplier(store, self._patch_package, self._committer) try: store.execute("SELECT * FROM patch WHERE 1=2") except StormError: # No schema at all. Create it from the ground. store.rollback() self.create(store) patch_applier.mark_applied_all() store.commit() else: patch_applier.apply_all()
def upgrade(self, store): """Upgrade C{store} to have the latest schema. If a schema isn't present a new one will be created. Unapplied patches will be applied to an existing schema. """ class NoopCommitter(object): commit = lambda _: None rollback = lambda _: None committer = self._committer if self._autocommit else NoopCommitter() patch_applier = PatchApplier(store, self._patch_package, committer) try: store.execute("SELECT * FROM patch WHERE version=0") except StormError: # No schema at all. Create it from the ground. store.rollback() self.create(store) patch_applier.mark_applied_all() if self._autocommit: store.commit() else: patch_applier.apply_all()
class PatchApplierTest(MockerTestCase): def setUp(self): super(PatchApplierTest, self).setUp() self.patchdir = self.makeDir() self.pkgdir = os.path.join(self.patchdir, "mypackage") os.makedirs(self.pkgdir) f = open(os.path.join(self.pkgdir, "__init__.py"), "w") f.write("shared_data = []") f.close() # Order of creation here is important to try to screw up the # patch ordering, as os.listdir returns in order of mtime (or # something). for pname, data in [("patch_380.py", patch_test_1), ("patch_42.py", patch_test_0)]: self.add_module(pname, data) sys.path.append(self.patchdir) self.filename = self.makeFile() self.uri = "sqlite:///%s" % self.filename self.store = Store(create_database(self.uri)) self.store.execute("CREATE TABLE patch " "(version INTEGER NOT NULL PRIMARY KEY)") self.assertFalse(self.store.get(Patch, (42))) self.assertFalse(self.store.get(Patch, (380))) import mypackage self.mypackage = mypackage self.patch_set = PatchSet(mypackage) # Create another connection just to keep track of the state of the # whole transaction manager. See the assertion functions below. self.another_store = Store(create_database("sqlite:")) self.another_store.execute("CREATE TABLE test (id INT)") self.another_store.commit() self.prepare_for_transaction_check() class Committer(object): def commit(committer): self.store.commit() self.another_store.commit() def rollback(committer): self.store.rollback() self.another_store.rollback() self.committer = Committer() self.patch_applier = PatchApplier(self.store, self.patch_set, self.committer) def tearDown(self): super(PatchApplierTest, self).tearDown() self.committer.rollback() sys.path.remove(self.patchdir) for name in list(sys.modules): if name == "mypackage" or name.startswith("mypackage."): del sys.modules[name] def add_module(self, module_filename, contents): filename = os.path.join(self.pkgdir, module_filename) file = open(filename, "w") file.write(contents) file.close() def remove_all_modules(self): for filename in os.listdir(self.pkgdir): os.unlink(os.path.join(self.pkgdir, filename)) def prepare_for_transaction_check(self): self.another_store.execute("DELETE FROM test") self.another_store.execute("INSERT INTO test VALUES (1)") def assert_transaction_committed(self): self.another_store.rollback() result = self.another_store.execute("SELECT * FROM test").get_one() self.assertEquals(result, (1, ), "Transaction manager wasn't committed.") def assert_transaction_aborted(self): self.another_store.commit() result = self.another_store.execute("SELECT * FROM test").get_one() self.assertEquals(result, None, "Transaction manager wasn't aborted.") def test_apply(self): """ L{PatchApplier.apply} executes the patch with the given version. """ self.patch_applier.apply(42) x = getattr(self.mypackage, "patch_42").x self.assertEquals(x, 42) self.assertTrue(self.store.get(Patch, (42))) self.assertTrue("mypackage.patch_42" in sys.modules) self.assert_transaction_committed() def test_apply_with_patch_directory(self): """ If the given L{PatchSet} uses sub-level patches, then the L{PatchApplier.apply} method will look at the per-patch directory and apply the relevant sub-level patch. """ path = os.path.join(self.pkgdir, "patch_99") self.makeDir(path=path) self.makeFile(content="", path=os.path.join(path, "__init__.py")) self.makeFile(content=patch_test_0, path=os.path.join(path, "foo.py")) self.patch_set._sub_level = "foo" self.add_module("patch_99/foo.py", patch_test_0) self.patch_applier.apply(99) self.assertTrue(self.store.get(Patch, (99))) def test_apply_all(self): """ L{PatchApplier.apply_all} executes all unapplied patches. """ self.patch_applier.apply_all() self.assertTrue("mypackage.patch_42" in sys.modules) self.assertTrue("mypackage.patch_380" in sys.modules) x = getattr(self.mypackage, "patch_42").x y = getattr(self.mypackage, "patch_380").y self.assertEquals(x, 42) self.assertEquals(y, 380) self.assert_transaction_committed() def test_apply_exploding_patch(self): """ L{PatchApplier.apply} aborts the transaction if the patch fails. """ self.remove_all_modules() self.add_module("patch_666.py", patch_explosion) self.assertRaises(StormError, self.patch_applier.apply, 666) self.assert_transaction_aborted() def test_wb_apply_all_exploding_patch(self): """ When a patch explodes the store is rolled back to make sure that any changes the patch made to the database are removed. Any other patches that have been applied successfully before it should not be rolled back. Any patches pending after the exploding patch should remain unapplied. """ self.add_module("patch_666.py", patch_explosion) self.add_module("patch_667.py", patch_after_explosion) self.assertEquals(list(self.patch_applier.get_unapplied_versions()), [42, 380, 666, 667]) self.assertRaises(StormError, self.patch_applier.apply_all) self.assertEquals(list(self.patch_applier.get_unapplied_versions()), [666, 667]) def test_mark_applied(self): """ L{PatchApplier.mark} marks a patch has applied by inserting a new row in the patch table. """ self.patch_applier.mark_applied(42) self.assertFalse("mypackage.patch_42" in sys.modules) self.assertFalse("mypackage.patch_380" in sys.modules) self.assertTrue(self.store.get(Patch, 42)) self.assertFalse(self.store.get(Patch, 380)) self.assert_transaction_committed() def test_mark_applied_all(self): """ L{PatchApplier.mark_applied_all} marks all pending patches as applied. """ self.patch_applier.mark_applied_all() self.assertFalse("mypackage.patch_42" in sys.modules) self.assertFalse("mypackage.patch_380" in sys.modules) self.assertTrue(self.store.get(Patch, 42)) self.assertTrue(self.store.get(Patch, 380)) self.assert_transaction_committed() def test_application_order(self): """ L{PatchApplier.apply_all} applies the patches in increasing version order. """ self.patch_applier.apply_all() self.assertEquals(self.mypackage.shared_data, [42, 380]) def test_has_pending_patches(self): """ L{PatchApplier.has_pending_patches} returns C{True} if there are patches to be applied, C{False} otherwise. """ self.assertTrue(self.patch_applier.has_pending_patches()) self.patch_applier.apply_all() self.assertFalse(self.patch_applier.has_pending_patches()) def test_abort_if_unknown_patches(self): """ L{PatchApplier.mark_applied} raises and error if the patch table contains patches without a matching file in the patch module. """ self.patch_applier.mark_applied(381) self.assertRaises(UnknownPatchError, self.patch_applier.apply_all) def test_get_unknown_patch_versions(self): """ L{PatchApplier.get_unknown_patch_versions} returns the versions of all unapplied patches. """ patches = [Patch(42), Patch(380), Patch(381)] my_store = MockPatchStore("database", patches=patches) patch_applier = PatchApplier(my_store, self.mypackage) self.assertEqual(set([381]), patch_applier.get_unknown_patch_versions()) def test_no_unknown_patch_versions(self): """ L{PatchApplier.get_unknown_patch_versions} returns an empty set if no patches are unapplied. """ patches = [Patch(42), Patch(380)] my_store = MockPatchStore("database", patches=patches) patch_applier = PatchApplier(my_store, self.mypackage) self.assertEqual(set(), patch_applier.get_unknown_patch_versions()) def test_patch_with_incorrect_apply(self): """ L{PatchApplier.apply_all} raises an error as soon as one of the patches to be applied fails. """ self.add_module("patch_999.py", patch_no_args_apply) try: self.patch_applier.apply_all() except BadPatchError as e: self.assertTrue("mypackage/patch_999.py" in str(e)) self.assertTrue("takes no arguments" in str(e)) self.assertTrue("TypeError" in str(e)) else: self.fail("BadPatchError not raised") def test_patch_with_missing_apply(self): """ L{PatchApplier.apply_all} raises an error if one of the patches to to be applied has no 'apply' function defined. """ self.add_module("patch_999.py", patch_missing_apply) try: self.patch_applier.apply_all() except BadPatchError as e: self.assertTrue("mypackage/patch_999.py" in str(e)) self.assertTrue("no attribute" in str(e)) self.assertTrue("AttributeError" in str(e)) else: self.fail("BadPatchError not raised") def test_patch_with_syntax_error(self): """ L{PatchApplier.apply_all} raises an error if one of the patches to to be applied contains a syntax error. """ self.add_module("patch_999.py", "that's not python") try: self.patch_applier.apply_all() except BadPatchError as e: self.assertTrue(" 999 " in str(e)) self.assertTrue("SyntaxError" in str(e)) else: self.fail("BadPatchError not raised") def test_patch_error_includes_traceback(self): """ The exception raised by L{PatchApplier.apply_all} when a patch fails include the relevant traceback from the patch. """ self.add_module("patch_999.py", patch_name_error) try: self.patch_applier.apply_all() except BadPatchError as e: self.assertTrue("mypackage/patch_999.py" in str(e)) self.assertTrue("NameError" in str(e)) self.assertTrue("blah" in str(e)) formatted = traceback.format_exc() self.assertTrue("# Comment" in formatted) else: self.fail("BadPatchError not raised")
def _build_patch_applier(self, store): """Build a L{PatchApplier} to use for the given C{store}.""" committer = self._committer if not self._autocommit: committer = _NoopCommitter() return PatchApplier(store, self._patch_set, committer)
class PatchTest(MockerTestCase): def setUp(self): super(PatchTest, self).setUp() self.patchdir = self.makeDir() self.pkgdir = os.path.join(self.patchdir, "mypackage") os.makedirs(self.pkgdir) f = open(os.path.join(self.pkgdir, "__init__.py"), "w") f.write("shared_data = []") f.close() # Order of creation here is important to try to screw up the # patch ordering, as os.listdir returns in order of mtime (or # something). for pname, data in [("patch_380.py", patch_test_1), ("patch_42.py", patch_test_0)]: self.add_module(pname, data) sys.path.append(self.patchdir) self.filename = self.makeFile() self.uri = "sqlite:///%s" % self.filename self.store = Store(create_database(self.uri)) self.store.execute("CREATE TABLE patch " "(version INTEGER NOT NULL PRIMARY KEY)") self.assertFalse(self.store.get(Patch, (42))) self.assertFalse(self.store.get(Patch, (380))) import mypackage self.mypackage = mypackage # Create another connection just to keep track of the state of the # whole transaction manager. See the assertion functions below. self.another_store = Store(create_database("sqlite:")) self.another_store.execute("CREATE TABLE test (id INT)") self.another_store.commit() self.prepare_for_transaction_check() class Committer(object): def commit(committer): self.store.commit() self.another_store.commit() def rollback(committer): self.store.rollback() self.another_store.rollback() self.committer = Committer() self.patch_applier = PatchApplier(self.store, self.mypackage, self.committer) def tearDown(self): super(PatchTest, self).tearDown() self.committer.rollback() sys.path.remove(self.patchdir) for name in list(sys.modules): if name == "mypackage" or name.startswith("mypackage."): del sys.modules[name] def add_module(self, module_filename, contents): filename = os.path.join(self.pkgdir, module_filename) file = open(filename, "w") file.write(contents) file.close() def remove_all_modules(self): for filename in os.listdir(self.pkgdir): os.unlink(os.path.join(self.pkgdir, filename)) def prepare_for_transaction_check(self): self.another_store.execute("DELETE FROM test") self.another_store.execute("INSERT INTO test VALUES (1)") def assert_transaction_committed(self): self.another_store.rollback() result = self.another_store.execute("SELECT * FROM test").get_one() self.assertEquals(result, (1,), "Transaction manager wasn't committed.") def assert_transaction_aborted(self): self.another_store.commit() result = self.another_store.execute("SELECT * FROM test").get_one() self.assertEquals(result, None, "Transaction manager wasn't aborted.") def test_apply(self): """ L{PatchApplier.apply} executes the patch with the given version. """ self.patch_applier.apply(42) x = getattr(self.mypackage, "patch_42").x self.assertEquals(x, 42) self.assertTrue(self.store.get(Patch, (42))) self.assertTrue("mypackage.patch_42" in sys.modules) self.assert_transaction_committed() def test_apply_all(self): """ L{PatchApplier.apply_all} executes all unapplied patches. """ self.patch_applier.apply_all() self.assertTrue("mypackage.patch_42" in sys.modules) self.assertTrue("mypackage.patch_380" in sys.modules) x = getattr(self.mypackage, "patch_42").x y = getattr(self.mypackage, "patch_380").y self.assertEquals(x, 42) self.assertEquals(y, 380) self.assert_transaction_committed() def test_apply_exploding_patch(self): """ L{PatchApplier.apply} aborts the transaction if the patch fails. """ self.remove_all_modules() self.add_module("patch_666.py", patch_explosion) self.assertRaises(StormError, self.patch_applier.apply, 666) self.assert_transaction_aborted() def test_wb_apply_all_exploding_patch(self): """ When a patch explodes the store is rolled back to make sure that any changes the patch made to the database are removed. Any other patches that have been applied successfully before it should not be rolled back. Any patches pending after the exploding patch should remain unapplied. """ self.add_module("patch_666.py", patch_explosion) self.add_module("patch_667.py", patch_after_explosion) self.assertEquals(list(self.patch_applier._get_unapplied_versions()), [42, 380, 666, 667]) self.assertRaises(StormError, self.patch_applier.apply_all) self.assertEquals(list(self.patch_applier._get_unapplied_versions()), [666, 667]) def test_mark_applied(self): """ L{PatchApplier.mark} marks a patch has applied by inserting a new row in the patch table. """ self.patch_applier.mark_applied(42) self.assertFalse("mypackage.patch_42" in sys.modules) self.assertFalse("mypackage.patch_380" in sys.modules) self.assertTrue(self.store.get(Patch, 42)) self.assertFalse(self.store.get(Patch, 380)) self.assert_transaction_committed() def test_mark_applied_all(self): """ L{PatchApplier.mark_applied_all} marks all pending patches as applied. """ self.patch_applier.mark_applied_all() self.assertFalse("mypackage.patch_42" in sys.modules) self.assertFalse("mypackage.patch_380" in sys.modules) self.assertTrue(self.store.get(Patch, 42)) self.assertTrue(self.store.get(Patch, 380)) self.assert_transaction_committed() def test_application_order(self): """ L{PatchApplier.apply_all} applies the patches in increasing version order. """ self.patch_applier.apply_all() self.assertEquals(self.mypackage.shared_data, [42, 380]) def test_has_pending_patches(self): """ L{PatchApplier.has_pending_patches} returns C{True} if there are patches to be applied, C{False} otherwise. """ self.assertTrue(self.patch_applier.has_pending_patches()) self.patch_applier.apply_all() self.assertFalse(self.patch_applier.has_pending_patches()) def test_abort_if_unknown_patches(self): """ L{PatchApplier.mark_applied} raises and error if the patch table contains patches without a matching file in the patch module. """ self.patch_applier.mark_applied(381) self.assertRaises(UnknownPatchError, self.patch_applier.apply_all) def test_get_unknown_patch_versions(self): """ L{PatchApplier.get_unknown_patch_versions} returns the versions of all unapplied patches. """ patches = [Patch(42), Patch(380), Patch(381)] my_store = MockPatchStore("database", patches=patches) patch_applier = PatchApplier(my_store, self.mypackage) self.assertEqual(set([381]), patch_applier.get_unknown_patch_versions()) def test_no_unknown_patch_versions(self): """ L{PatchApplier.get_unknown_patch_versions} returns an empty set if no patches are unapplied. """ patches = [Patch(42), Patch(380)] my_store = MockPatchStore("database", patches=patches) patch_applier = PatchApplier(my_store, self.mypackage) self.assertEqual(set(), patch_applier.get_unknown_patch_versions()) def test_patch_with_incorrect_apply(self): """ L{PatchApplier.apply_all} raises an error as soon as one of the patches to be applied fails. """ self.add_module("patch_999.py", patch_no_args_apply) try: self.patch_applier.apply_all() except BadPatchError, e: self.assertTrue("mypackage/patch_999.py" in str(e)) self.assertTrue("takes no arguments" in str(e)) self.assertTrue("TypeError" in str(e)) else: