def test_postgresql_connects_with_schema(self): dburi = next(iter(get_test_dburis(only={"postgresql"})), None) if dburi is None: pytest.skip("PostgreSQL backend not available") backend = get_backend(dburi) with backend.transaction(): backend.execute("CREATE SCHEMA foo") try: assert get_backend(dburi + "?schema=foo").execute( "SHOW search_path").fetchone() == ("foo", ) finally: with backend.transaction(): backend.execute("DROP SCHEMA foo CASCADE")
def test_post_apply_hooks_are_run_every_time(self): backend = get_backend(dburi) migrations = migrations_dir( **{ "a": "step('create table postapply (i int)')", "post-apply": "step('insert into postapply values (1)')", }) with migrations as tmp: def count_postapply_calls(): cursor = backend.cursor() cursor.execute("SELECT count(1) FROM postapply") return cursor.fetchone()[0] def _apply_migrations(): backend.apply_migrations(backend.to_apply( read_migrations(tmp))) # Should apply migration 'a' and call the post-apply hook _apply_migrations() assert count_postapply_calls() == 1 # No outstanding migrations: post-apply hook should not be called _apply_migrations() assert count_postapply_calls() == 1 # New migration added: post-apply should be called a second time migrations.add_migration("b", "") _apply_migrations() assert count_postapply_calls() == 2
def test_migrations_can_import_step_and_group(tmpdir): backend = get_backend(dburi) migrations = read_migrations(tmpdir) backend.apply_migrations(migrations) cursor = backend.cursor() cursor.execute("SELECT id FROM yoyo_test") assert cursor.fetchall() == [(1, )]
def test_apply_migrations_only_does_not_run_hooks(self, tmpdir): backend = get_backend(dburi) backend.apply_migrations_only(backend.to_apply( read_migrations(tmpdir))) cursor = backend.cursor() cursor.execute("SELECT * FROM postapply") assert cursor.fetchall() == []
def test_specify_migration_table(tmpdir): backend = get_backend(dburi, migration_table="another_migration_table") migrations = read_migrations(tmpdir) backend.apply_migrations(migrations) cursor = backend.cursor() cursor.execute("SELECT migration_id FROM another_migration_table") assert cursor.fetchall() == [("0", )]
def test_execution_continues_with_ignore_errors_in_transaction(tmpdir): backend = get_backend(dburi) migrations = read_migrations(tmpdir) backend.apply_migrations(migrations) cursor = backend.cursor() cursor.execute("SELECT * FROM yoyo_test") assert cursor.fetchall() == [(2, )]
def test_transaction_is_not_committed_on_error(tmpdir): backend = get_backend(dburi) migrations = read_migrations(tmpdir) with pytest.raises(backend.DatabaseError): backend.apply_migrations(migrations) cursor = backend.cursor() cursor.execute("SELECT count(1) FROM yoyo_test") assert cursor.fetchone() == (0, )
def test_migration_functions_have_namespace_access(tmpdir): """ Test that functions called via step have access to the script namespace """ backend = get_backend(dburi) migrations = read_migrations(tmpdir) backend.apply_migrations(migrations) cursor = backend.cursor() cursor.execute("SELECT id FROM foo_test") assert cursor.fetchall() == [(1, )]
def test_rollbacks_happen_in_reverse(tmpdir): backend = get_backend(dburi) migrations = read_migrations(tmpdir) backend.apply_migrations(migrations) cursor = backend.cursor() cursor.execute("SELECT * FROM yoyo_test") assert cursor.fetchall() == [(2, )] backend.rollback_migrations(migrations) cursor.execute("SELECT * FROM yoyo_test") assert cursor.fetchall() == []
def test_limits(tmpdir): backend = get_backend(dburi) migrations = read_migrations(tmpdir) # Should insert a single 1 backend.apply_migrations(migrations, limit=2) cursor = backend.cursor() cursor.execute("SELECT * FROM yoyo_limit_test") all_results = cursor.fetchall() assert len(all_results) == 1 cursor.execute("SELECT * FROM yoyo_limit_test where id = 0") all_results = cursor.fetchall() assert len(all_results) == 0 cursor.execute("SELECT * FROM yoyo_limit_test where id = 1") all_results = cursor.fetchall() assert len(all_results) == 1 # Should insert Zero backend2 = get_backend(dburi) forward_migrations = read_migrations(tmpdir) # Should Apply the Last Migration backend2.apply_migrations(forward_migrations, limit=1) cursor2 = backend2.cursor() cursor2.execute("SELECT * FROM yoyo_limit_test where id = 0") all_results = cursor2.fetchall() assert len(all_results) == 1 # Round 3 Let's Do a limited Rollback # Should Remove the Zero backend3 = get_backend(dburi) backward_migrations = read_migrations(tmpdir) backend3.apply_migrations(backward_migrations, limit=1) backend3.rollback_migrations(migrations, limit=1) cursor3 = backend3.cursor() cursor3.execute("SELECT * FROM yoyo_limit_test where id = 0") all_results = cursor3.fetchall() assert len(all_results) == 0 cursor3.execute("SELECT * FROM yoyo_limit_test where id = 1") all_results = cursor3.fetchall() assert len(all_results) == 1
def test_show_migrations(tmpdir): backend = get_backend(dburi) migrations = read_migrations(tmpdir) backend.apply_migrations(migrations) migrations = backend.get_migrations_with_applied_status(migrations) assert migrations[0].applied backend.rollback_migrations(migrations) migrations = backend.get_migrations_with_applied_status(migrations) assert not migrations[0].applied
def test_migrations_display_selected_data(tmpdir): backend = get_backend(dburi) migrations = read_migrations(tmpdir) with patch("yoyo.migrations.stdout") as stdout: backend.apply_migrations(migrations) written = "".join(a[0] for a, kw in stdout.write.call_args_list) assert written == (" id | c \n" "----+---\n" " 1 | a \n" " 2 | b \n" "(2 rows)\n")
def test_postgresql_list_table_uses_current_schema(self): dburi = next(iter(get_test_dburis(only={"postgresql"})), None) if dburi is None: pytest.skip("PostgreSQL backend not available") backend = get_backend(dburi) dbname = backend.uri.database with backend.transaction(): backend.execute( "ALTER DATABASE {} SET SEARCH_PATH = custom_schema,public". format(dbname)) try: with backend.transaction(): backend.execute("CREATE SCHEMA custom_schema") backend.execute("CREATE TABLE custom_schema.foo (x int)") assert "foo" in get_backend(dburi).list_tables() finally: with backend.transaction(): backend.execute( "ALTER DATABASE {} RESET SEARCH_PATH".format(dbname)) backend.execute("DROP SCHEMA custom_schema CASCADE")
def test_disabling_transactions_in_sqlite(self, tmpdir): """ Transactions cause sqlite databases to become locked, preventing other tools from accessing them: https://bitbucket.org/ollyc/yoyo/issues/43/run-step-outside-of-transaction """ with NamedTemporaryFile() as tmp: backend = get_backend("sqlite:///" + tmp.name) backend.apply_migrations(read_migrations(tmpdir)) assert "yoyo_test_a" in backend.list_tables() assert "yoyo_test_b" in backend.list_tables() assert "yoyo_test_c" in backend.list_tables()
def test_lock_times_out(self, dburi): backend = get_backend(dburi) self.skip_if_not_concurrency_safe(backend) thread = Thread(target=partial(self.do_something_with_lock, dburi)) thread.start() # Give the thread time to acquire the lock, but not enough # to complete time.sleep(self.lock_duration * 0.6) with pytest.raises(exceptions.LockTimeout): with backend.lock(timeout=0.001): assert False, "Execution should never reach this point" thread.join()
def backend(request): """ Return all backends configured in ``test_databases.ini`` """ backend = get_backend(request.param) with backend.transaction(): if backend.__class__ is backends.MySQLBackend: backend.execute("CREATE TABLE yoyo_t " "(id CHAR(1) primary key) " "ENGINE=InnoDB") else: backend.execute("CREATE TABLE yoyo_t " "(id CHAR(1) primary key)") try: yield backend finally: backend.rollback() drop_yoyo_tables(backend)
def test_lock(self, dburi): """ Test that :meth:`~yoyo.backends.DatabaseBackend.lock` acquires an exclusive lock """ backend = get_backend(dburi) self.skip_if_not_concurrency_safe(backend) thread = Thread(target=partial(self.do_something_with_lock, dburi)) t = time.time() thread.start() # Give the thread time to acquire the lock, but not enough # to complete time.sleep(self.lock_duration * 0.6) with backend.lock(): delta = time.time() - t assert delta >= self.lock_duration thread.join()
def get_backend(args, config): try: dburi = args.database except AttributeError: dburi = config.get("DEFAULT", "database") try: migration_table = args.migration_table except AttributeError: migration_table = config.get("DEFAULT", "migration_table") if dburi is None: raise InvalidArgument("Please specify a database uri") try: if args.prompt_password: password = getpass("Password for %s: " % dburi) parsed = connections.parse_uri(dburi) dburi = parsed._replace(password=password).uri except AttributeError: pass return connections.get_backend(dburi, migration_table)
def test_it_runs_multiple_post_apply_hooks(self, tmpdir): backend = get_backend(dburi) backend.apply_migrations(backend.to_apply(read_migrations(tmpdir))) cursor = backend.cursor() cursor.execute("SELECT * FROM postapply") assert cursor.fetchall() == [(1, ), (2, )]
def do_something_with_lock(self, dburi): with get_backend(dburi).lock(): time.sleep(self.lock_duration)
def dburi(request): try: yield request.param finally: drop_yoyo_tables(get_backend(request.param))
def get_test_backends(only=frozenset(), exclude=frozenset()): return [get_backend(dburi) for dburi in get_test_dburis(only, exclude)]