class Test_schema_upgrader(object): def setUp(self): # make an in-memory sqlite database to use during testing self.connection = Connection_wrapper( sqlite.connect(":memory:", detect_types=sqlite.PARSE_DECLTYPES, check_same_thread=False)) self.cache = Stub_cache() cursor = self.connection.cursor() cursor.execute(Stub_object.sql_create_table()) self.fake_files = { } # map of fake filename (full path) to fake file contents self.database = Database(self.connection, self.cache) self.upgrader = Schema_upgrader(self.database, glob=self.glob, read_file=self.read_file) def tearDown(self): self.database.close() def glob(self, glob_pattern): """ A fake glob function that doesn't use the filesystem. """ re_pattern = re.compile(glob_pattern.replace("*", "[^/]*")) return [ filename for filename in self.fake_files.keys() if re_pattern.search(filename) ] def read_file(self, filename): """ A fake read file function that doesn't use the filesystem. """ contents = self.fake_files.get(filename) if not contents: raise IOError() return contents def test_upgrade_schema(self, to_version=None): if not to_version: to_version = u"5.7.11" self.fake_files = { u"model/delta/5.6.7.sqlite": u"create table new_table ( foo text ); insert into new_table values ( 'hi' );", u"model/delta/5.6.8.sqlite": u"insert into new_table values ( 'bye' );", u"model/delta/5.6.10.sqlite": u"alter table new_table add column bar text;", u"model/delta/5.7.11.sqlite": u"insert into new_table values ( 'whee', 'stuff' );", u"model/delta/5.7.18.sqlite": u"insert into new_table values ( 'more', 'things' );", } self.upgrader.upgrade_schema(to_version) result = self.database.select_many(tuple, u"select * from new_table;") if to_version == u"5.7.11": assert result == [(u"hi", None), (u"bye", None), ("whee", "stuff")] else: assert result == [(u"hi", None), (u"bye", None), ("whee", "stuff"), ("more", "things")] result = self.database.select_many(tuple, u"select * from schema_version;") if to_version == u"5.7.11": assert result == [(5, 7, 11)] else: assert result == [(5, 7, 18)] def test_upgrade_schema_with_schema_version_table(self): self.database.execute( u"create table schema_version ( major numeric, minor numeric, \"release\" numeric );" ) self.database.execute( u"insert into schema_version values ( 0, 0, 0 );") self.test_upgrade_schema() def test_upgrade_schema_with_schema_version_table_and_starting_version( self): self.database.execute( u"create table schema_version ( major numeric, minor numeric, \"release\" numeric );" ) self.database.execute( u"insert into schema_version values ( 5, 6, 6 );") self.fake_files[ u"model/delta/5.6.1.sqlite"] = u"this is not valid sql and should not be executed anyway;" self.fake_files[u"model/delta/5.6.6.sqlite"] = u"also invalid;" self.test_upgrade_schema() def test_upgrade_schema_with_schema_version_table_and_target_version_without_schema( self): self.database.execute( u"create table schema_version ( major numeric, minor numeric, \"release\" numeric );" ) self.database.execute( u"insert into schema_version values ( 0, 0, 0 );") self.test_upgrade_schema(to_version=u"5.7.20") def test_upgrade_schema_with_schema_version_table_and_starting_version_and_target_version_without_schema( self): self.database.execute( u"create table schema_version ( major numeric, minor numeric, \"release\" numeric );" ) self.database.execute( u"insert into schema_version values ( 5, 6, 6 );") self.test_upgrade_schema(to_version=u"5.7.20") def test_upgrade_schema_with_future_ending_version(self): self.fake_files = { u"model/delta/5.6.7.sqlite": u"create table new_table ( foo text ); insert into new_table values ( 'hi' );", u"model/delta/5.6.8.sqlite": u"insert into new_table values ( 'bye' );", u"model/delta/5.6.10.sqlite": u"alter table new_table add column bar text;", u"model/delta/5.7.11.sqlite": u"insert into new_table values ( 'whee', 'stuff' );", u"model/delta/5.7.18.sqlite": u"insert into new_table values ( 'more', 'and more' );", } self.upgrader.upgrade_schema(u"5.8.55") result = self.database.select_many(tuple, u"select * from new_table;") assert result == [(u"hi", None), (u"bye", None), ("whee", "stuff"), ("more", "and more")] result = self.database.select_many(tuple, u"select * from schema_version;") assert result == [(5, 7, 18)] def test_upgrade_schema_twice(self): self.test_upgrade_schema() # the second upgrade should have no effect, because at this point it's already upgraded self.test_upgrade_schema() def test_upgrade_schema_with_filename_with_invalid_version(self): # the filename, not composed of all-integer parts, should be skipped self.fake_files[ u"model/delta/5.6.9b.sqlite"] = u"this is not valid sql and should not be executed anyway;" self.test_upgrade_schema() def test_upgrade_schema_default_to_start_version_of_1_5_4(self): # test that if no schema_version table exists, then the starting version is assumed to be 1.5.4 self.fake_files = { u"model/delta/1.5.3.sqlite": u"invalid sql;", u"model/delta/1.5.4.sqlite": u"should not be invoked;", u"model/delta/1.5.5.sqlite": u"create table new_table ( foo text ); insert into new_table values ( 'hi' );", u"model/delta/1.5.6.sqlite": u"insert into new_table values ( 'bye' );", } self.upgrader.upgrade_schema(u"1.5.6") result = self.database.select_many(tuple, u"select * from new_table;") assert result == [ (u"hi", ), (u"bye", ), ] result = self.database.select_many(tuple, u"select * from schema_version;") assert result == [(1, 5, 6)] def test_apply_schema_delta(self): self.database.execute( u"create table schema_version ( major numeric, minor numeric, \"release\" numeric );" ) self.database.execute( u"insert into schema_version values ( 0, 0, 0 );") self.fake_files = { u"model/delta/5.6.5.sqlite": u"insert into new_table values ( 'should not show up' );", u"model/delta/5.6.7.sqlite": u"create table new_table ( foo text ); insert into new_table values ( 'hi' );", u"model/delta/5.7.18.sqlite": u"insert into new_table values ( 'should not be present' );", } self.upgrader.apply_schema_delta((5, 6, 7), u"model/delta/5.6.7.sqlite") result = self.database.select_many(unicode, u"select * from new_table;") assert result == [u"hi"] result = self.database.select_many(tuple, u"select * from schema_version;") assert result == [(5, 6, 7)] @raises(IOError) def test_apply_schema_delta_with_unknown_file(self): self.upgrader.apply_schema_delta((5, 6, 7), u"model/delta/5.6.7.sqlite") def test_version_string_to_tuple(self): version = self.upgrader.version_string_to_tuple("2.5.13") assert len(version) == 3 assert version[0] == 2 assert version[1] == 5 assert version[2] == 13 def test_version_string_to_tuple_with_extension(self): version = self.upgrader.version_string_to_tuple("2.5.13.sqlite") assert len(version) == 3 assert version[0] == 2 assert version[1] == 5 assert version[2] == 13 @raises(ValueError) def test_version_string_to_tuple_with_too_many_parts(self): version = self.upgrader.version_string_to_tuple("3.14.159.26.5") @raises(ValueError) def test_version_string_to_tuple_with_too_few_parts(self): version = self.upgrader.version_string_to_tuple("3.14") @raises(ValueError) def test_version_string_to_tuple_with_non_integer_part(self): version = self.upgrader.version_string_to_tuple("2.5b.13") @raises(ValueError) def test_version_string_to_tuple_with_empty_part(self): version = self.upgrader.version_string_to_tuple("2..13")
class Test_database( object ): def setUp( self ): # make an in-memory sqlite database to use during testing self.connection = Connection_wrapper( sqlite.connect( ":memory:", detect_types = sqlite.PARSE_DECLTYPES, check_same_thread = False ) ) self.cache = Stub_cache() cursor = self.connection.cursor() cursor.execute( Stub_object.sql_create_table() ) self.database = Database( self.connection, self.cache ) def tearDown( self ): self.database.close() def test_save_and_load( self ): basic_obj = Stub_object( object_id = "5", value = 1 ) original_revision = basic_obj.revision self.database.save( basic_obj ) obj = self.database.load( Stub_object, basic_obj.object_id ) assert obj.object_id == basic_obj.object_id assert obj.revision.replace( tzinfo = utc ) == original_revision assert obj.value == basic_obj.value def test_save_and_load_without_commit( self ): basic_obj = Stub_object( object_id = "5", value = 1 ) original_revision = basic_obj.revision self.database.save( basic_obj, commit = False ) self.connection.rollback() # if commit wasn't called, this should back out the save obj = self.database.load( Stub_object, basic_obj.object_id ) assert obj == None def test_save_and_load_with_explicit_commit( self ): basic_obj = Stub_object( object_id = "5", value = 1 ) original_revision = basic_obj.revision self.database.save( basic_obj, commit = False ) self.database.commit() self.connection.rollback() # should have no effect because of the call to commit obj = self.database.load( Stub_object, basic_obj.object_id ) assert obj.object_id == basic_obj.object_id assert obj.revision.replace( tzinfo = utc ) == original_revision assert obj.value == basic_obj.value def test_select_one( self ): basic_obj = Stub_object( object_id = "5", value = 1 ) original_revision = basic_obj.revision self.database.save( basic_obj ) obj = self.database.select_one( Stub_object, Stub_object.sql_load( basic_obj.object_id ) ) assert obj.object_id == basic_obj.object_id assert obj.revision.replace( tzinfo = utc ) == original_revision assert obj.value == basic_obj.value def test_select_datetime( self ): # this revision (with .504099) happens to test for a bug caused by floating point rounding errors original_revision = "2008-01-01 01:00:42.504099+00:00" basic_obj = Stub_object( object_id = "5", revision = original_revision, value = 1 ) self.database.save( basic_obj ) obj = self.database.select_one( Stub_object, Stub_object.sql_load( basic_obj.object_id ) ) assert obj.object_id == basic_obj.object_id assert str( obj.revision.replace( tzinfo = utc ) ) == original_revision assert obj.value == basic_obj.value def test_select_datetime_with_many_fractional_digits( self ): original_revision = "2008-01-01 01:00:42.5032429489284+00:00" basic_obj = Stub_object( object_id = "5", revision = original_revision, value = 1 ) self.database.save( basic_obj ) obj = self.database.select_one( Stub_object, Stub_object.sql_load( basic_obj.object_id ) ) assert obj.object_id == basic_obj.object_id assert str( obj.revision.replace( tzinfo = utc ) ) == "2008-01-01 01:00:42.503242+00:00" assert obj.value == basic_obj.value def test_select_datetime_with_zero_fractional_seconds( self ): original_revision = "2008-01-01 01:00:42.0+00:00" basic_obj = Stub_object( object_id = "5", revision = original_revision, value = 1 ) self.database.save( basic_obj ) obj = self.database.select_one( Stub_object, Stub_object.sql_load( basic_obj.object_id ) ) assert obj.object_id == basic_obj.object_id assert str( obj.revision.replace( tzinfo = utc ) ) == "2008-01-01 01:00:42+00:00" assert obj.value == basic_obj.value def test_select_one_tuple( self ): obj = self.database.select_one( tuple, Stub_object.sql_tuple() ) assert len( obj ) == 2 assert obj[ 0 ] == 1 assert obj[ 1 ] == 2 def test_select_many( self ): basic_obj = Stub_object( object_id = "5", value = 1 ) original_revision = basic_obj.revision basic_obj2 = Stub_object( object_id = "6", value = 2 ) original_revision2 = basic_obj2.revision self.database.save( basic_obj ) self.database.save( basic_obj2 ) objs = self.database.select_many( Stub_object, Stub_object.sql_load_em_all() ) assert len( objs ) == 2 assert objs[ 0 ].object_id == basic_obj.object_id assert objs[ 0 ].revision.replace( tzinfo = utc ) == original_revision assert objs[ 0 ].value == basic_obj.value assert objs[ 1 ].object_id == basic_obj2.object_id assert objs[ 1 ].revision.replace( tzinfo = utc ) == original_revision2 assert objs[ 1 ].value == basic_obj2.value def test_select_many_tuples( self ): objs = self.database.select_many( tuple, Stub_object.sql_tuple() ) assert len( objs ) == 1 assert len( objs[ 0 ] ) == 2 assert objs[ 0 ][ 0 ] == 1 assert objs[ 0 ][ 1 ] == 2 def test_select_many_with_no_matches( self ): objs = self.database.select_many( Stub_object, Stub_object.sql_load_em_all() ) assert len( objs ) == 0 def test_save_and_load_revision( self ): basic_obj = Stub_object( object_id = "5", value = 1 ) original_revision = basic_obj.revision self.database.save( basic_obj ) basic_obj.value = 2 self.database.save( basic_obj ) obj = self.database.load( Stub_object, basic_obj.object_id ) assert obj.object_id == basic_obj.object_id assert obj.revision.replace( tzinfo = utc ) == basic_obj.revision assert obj.value == basic_obj.value revised = self.database.load( Stub_object, basic_obj.object_id, revision = original_revision ) assert revised.object_id == basic_obj.object_id assert revised.value == 1 assert revised.revision.replace( tzinfo = utc ) == original_revision def test_execute( self ): basic_obj = Stub_object( object_id = "5", value = 1 ) original_revision = basic_obj.revision self.database.execute( basic_obj.sql_create() ) obj = self.database.load( Stub_object, basic_obj.object_id ) assert obj.object_id == basic_obj.object_id assert obj.revision.replace( tzinfo = utc ) == original_revision assert obj.value == basic_obj.value def test_execute_without_commit( self ): basic_obj = Stub_object( object_id = "5", value = 1 ) original_revision = basic_obj.revision self.database.execute( basic_obj.sql_create(), commit = False ) self.connection.rollback() obj = self.database.load( Stub_object, basic_obj.object_id ) assert obj == None def test_execute_with_explicit_commit( self ): basic_obj = Stub_object( object_id = "5", value = 1 ) original_revision = basic_obj.revision self.database.execute( basic_obj.sql_create(), commit = False ) self.database.commit() obj = self.database.load( Stub_object, basic_obj.object_id ) assert obj.object_id == basic_obj.object_id assert obj.revision.replace( tzinfo = utc ) == original_revision assert obj.value == basic_obj.value def test_load_unknown( self ): basic_obj = Stub_object( object_id = "5", value = 1 ) obj = self.database.load( Stub_object, basic_obj.object_id ) assert obj == None def test_next_id( self ): next_id = self.database.next_id( Stub_object ) assert next_id assert self.database.load( Stub_object, next_id ) prev_ids = [ next_id ] next_id = self.database.next_id( Stub_object ) assert next_id assert next_id not in prev_ids assert self.database.load( Stub_object, next_id ) prev_ids.append( next_id ) next_id = self.database.next_id( Stub_object ) assert next_id assert next_id not in prev_ids assert self.database.load( Stub_object, next_id ) def test_next_id_without_commit( self ): next_id = self.database.next_id( Stub_object, commit = False ) self.connection.rollback() assert self.database.load( Stub_object, next_id ) == None def test_next_id_with_explicit_commit( self ): next_id = self.database.next_id( Stub_object, commit = False ) self.database.commit() assert next_id assert self.database.load( Stub_object, next_id ) def test_synchronize( self ): def make_objects(): for i in range( 50 ): object_id = self.database.next_id( Stub_object ) basic_obj = Stub_object( object_id, value = 1 ) original_revision = basic_obj.revision self.database.execute( basic_obj.sql_create() ) obj = self.database.load( Stub_object, basic_obj.object_id ) assert obj.object_id == basic_obj.object_id delta = abs( obj.revision.replace( tzinfo = utc ) - original_revision ) assert delta <= timedelta( seconds = 0.000001 ) assert obj.value == basic_obj.value object_id = self.database.next_id( Stub_object ) # if synchronization (locking) is working properly, then these two threads should be able to run # simultaneously without error. without locking, SQLite will raise thread1 = Thread( target = make_objects ) thread2 = Thread( target = make_objects ) thread1.start() thread2.start() thread1.join() thread2.join() def test_backend( self ): assert self.database.backend == Persistent.SQLITE_BACKEND
class Test_database(object): def setUp(self): # make an in-memory sqlite database to use during testing self.connection = Connection_wrapper( sqlite.connect(":memory:", detect_types=sqlite.PARSE_DECLTYPES, check_same_thread=False)) self.cache = Stub_cache() cursor = self.connection.cursor() cursor.execute(Stub_object.sql_create_table()) self.database = Database(self.connection, self.cache) def tearDown(self): self.database.close() def test_save_and_load(self): basic_obj = Stub_object(object_id="5", value=1) original_revision = basic_obj.revision self.database.save(basic_obj) obj = self.database.load(Stub_object, basic_obj.object_id) assert obj.object_id == basic_obj.object_id assert obj.revision.replace(tzinfo=utc) == original_revision assert obj.value == basic_obj.value def test_save_and_load_without_commit(self): basic_obj = Stub_object(object_id="5", value=1) original_revision = basic_obj.revision self.database.save(basic_obj, commit=False) self.connection.rollback( ) # if commit wasn't called, this should back out the save obj = self.database.load(Stub_object, basic_obj.object_id) assert obj == None def test_save_and_load_with_explicit_commit(self): basic_obj = Stub_object(object_id="5", value=1) original_revision = basic_obj.revision self.database.save(basic_obj, commit=False) self.database.commit() self.connection.rollback( ) # should have no effect because of the call to commit obj = self.database.load(Stub_object, basic_obj.object_id) assert obj.object_id == basic_obj.object_id assert obj.revision.replace(tzinfo=utc) == original_revision assert obj.value == basic_obj.value def test_select_one(self): basic_obj = Stub_object(object_id="5", value=1) original_revision = basic_obj.revision self.database.save(basic_obj) obj = self.database.select_one( Stub_object, Stub_object.sql_load(basic_obj.object_id)) assert obj.object_id == basic_obj.object_id assert obj.revision.replace(tzinfo=utc) == original_revision assert obj.value == basic_obj.value def test_select_datetime(self): # this revision (with .504099) happens to test for a bug caused by floating point rounding errors original_revision = "2008-01-01 01:00:42.504099+00:00" basic_obj = Stub_object(object_id="5", revision=original_revision, value=1) self.database.save(basic_obj) obj = self.database.select_one( Stub_object, Stub_object.sql_load(basic_obj.object_id)) assert obj.object_id == basic_obj.object_id assert str(obj.revision.replace(tzinfo=utc)) == original_revision assert obj.value == basic_obj.value def test_select_datetime_with_many_fractional_digits(self): original_revision = "2008-01-01 01:00:42.5032429489284+00:00" basic_obj = Stub_object(object_id="5", revision=original_revision, value=1) self.database.save(basic_obj) obj = self.database.select_one( Stub_object, Stub_object.sql_load(basic_obj.object_id)) assert obj.object_id == basic_obj.object_id assert str(obj.revision.replace( tzinfo=utc)) == "2008-01-01 01:00:42.503242+00:00" assert obj.value == basic_obj.value def test_select_datetime_with_zero_fractional_seconds(self): original_revision = "2008-01-01 01:00:42.0+00:00" basic_obj = Stub_object(object_id="5", revision=original_revision, value=1) self.database.save(basic_obj) obj = self.database.select_one( Stub_object, Stub_object.sql_load(basic_obj.object_id)) assert obj.object_id == basic_obj.object_id assert str( obj.revision.replace(tzinfo=utc)) == "2008-01-01 01:00:42+00:00" assert obj.value == basic_obj.value def test_select_one_tuple(self): obj = self.database.select_one(tuple, Stub_object.sql_tuple()) assert len(obj) == 2 assert obj[0] == 1 assert obj[1] == 2 def test_select_many(self): basic_obj = Stub_object(object_id="5", value=1) original_revision = basic_obj.revision basic_obj2 = Stub_object(object_id="6", value=2) original_revision2 = basic_obj2.revision self.database.save(basic_obj) self.database.save(basic_obj2) objs = self.database.select_many(Stub_object, Stub_object.sql_load_em_all()) assert len(objs) == 2 assert objs[0].object_id == basic_obj.object_id assert objs[0].revision.replace(tzinfo=utc) == original_revision assert objs[0].value == basic_obj.value assert objs[1].object_id == basic_obj2.object_id assert objs[1].revision.replace(tzinfo=utc) == original_revision2 assert objs[1].value == basic_obj2.value def test_select_many_tuples(self): objs = self.database.select_many(tuple, Stub_object.sql_tuple()) assert len(objs) == 1 assert len(objs[0]) == 2 assert objs[0][0] == 1 assert objs[0][1] == 2 def test_select_many_with_no_matches(self): objs = self.database.select_many(Stub_object, Stub_object.sql_load_em_all()) assert len(objs) == 0 def test_save_and_load_revision(self): basic_obj = Stub_object(object_id="5", value=1) original_revision = basic_obj.revision self.database.save(basic_obj) basic_obj.value = 2 self.database.save(basic_obj) obj = self.database.load(Stub_object, basic_obj.object_id) assert obj.object_id == basic_obj.object_id assert obj.revision.replace(tzinfo=utc) == basic_obj.revision assert obj.value == basic_obj.value revised = self.database.load(Stub_object, basic_obj.object_id, revision=original_revision) assert revised.object_id == basic_obj.object_id assert revised.value == 1 assert revised.revision.replace(tzinfo=utc) == original_revision def test_execute(self): basic_obj = Stub_object(object_id="5", value=1) original_revision = basic_obj.revision self.database.execute(basic_obj.sql_create()) obj = self.database.load(Stub_object, basic_obj.object_id) assert obj.object_id == basic_obj.object_id assert obj.revision.replace(tzinfo=utc) == original_revision assert obj.value == basic_obj.value def test_execute_without_commit(self): basic_obj = Stub_object(object_id="5", value=1) original_revision = basic_obj.revision self.database.execute(basic_obj.sql_create(), commit=False) self.connection.rollback() obj = self.database.load(Stub_object, basic_obj.object_id) assert obj == None def test_execute_with_explicit_commit(self): basic_obj = Stub_object(object_id="5", value=1) original_revision = basic_obj.revision self.database.execute(basic_obj.sql_create(), commit=False) self.database.commit() obj = self.database.load(Stub_object, basic_obj.object_id) assert obj.object_id == basic_obj.object_id assert obj.revision.replace(tzinfo=utc) == original_revision assert obj.value == basic_obj.value def test_load_unknown(self): basic_obj = Stub_object(object_id="5", value=1) obj = self.database.load(Stub_object, basic_obj.object_id) assert obj == None def test_next_id(self): next_id = self.database.next_id(Stub_object) assert next_id assert self.database.load(Stub_object, next_id) prev_ids = [next_id] next_id = self.database.next_id(Stub_object) assert next_id assert next_id not in prev_ids assert self.database.load(Stub_object, next_id) prev_ids.append(next_id) next_id = self.database.next_id(Stub_object) assert next_id assert next_id not in prev_ids assert self.database.load(Stub_object, next_id) def test_next_id_without_commit(self): next_id = self.database.next_id(Stub_object, commit=False) self.connection.rollback() assert self.database.load(Stub_object, next_id) == None def test_next_id_with_explicit_commit(self): next_id = self.database.next_id(Stub_object, commit=False) self.database.commit() assert next_id assert self.database.load(Stub_object, next_id) def test_synchronize(self): def make_objects(): for i in range(50): object_id = self.database.next_id(Stub_object) basic_obj = Stub_object(object_id, value=1) original_revision = basic_obj.revision self.database.execute(basic_obj.sql_create()) obj = self.database.load(Stub_object, basic_obj.object_id) assert obj.object_id == basic_obj.object_id delta = abs( obj.revision.replace(tzinfo=utc) - original_revision) assert delta <= timedelta(seconds=0.000001) assert obj.value == basic_obj.value object_id = self.database.next_id(Stub_object) # if synchronization (locking) is working properly, then these two threads should be able to run # simultaneously without error. without locking, SQLite will raise thread1 = Thread(target=make_objects) thread2 = Thread(target=make_objects) thread1.start() thread2.start() thread1.join() thread2.join() def test_backend(self): assert self.database.backend == Persistent.SQLITE_BACKEND