Skip to content

Commit

Permalink
test(sql-script): stabilize cleanup and comparison (camunda#3625)
Browse files Browse the repository at this point in the history
* Uses direct comparison via existing snapshots to avoid creating databases
  from those snapshots again.
* Introduces a second `Liquibase#dropAll` invocation to clean up all data. 
  A single invocation can leave data in the tables if the cleanup takes too 
  long or is interrupted. The method seems to ignore such issues and 
  progress as if successful silently.

related to camunda#3613
  • Loading branch information
tmetzke authored Aug 17, 2023
1 parent 2724973 commit e573859
Showing 1 changed file with 43 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@

import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.SortedSet;
import java.util.stream.Collectors;
import liquibase.Contexts;
import liquibase.Liquibase;
Expand All @@ -35,7 +33,7 @@
import liquibase.changelog.ChangeSet;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.OfflineConnection;
import liquibase.diff.DiffGeneratorFactory;
import liquibase.diff.DiffResult;
import liquibase.diff.ObjectDifferences;
import liquibase.diff.compare.CompareControl;
Expand All @@ -44,13 +42,8 @@
import liquibase.diff.output.changelog.DiffToChangeLog;
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
import liquibase.exception.LiquibaseParseException;
import liquibase.parser.SnapshotParser;
import liquibase.parser.SnapshotParserFactory;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.FileSystemResourceAccessor;
import liquibase.resource.InputStreamList;
import liquibase.resource.ResourceAccessor;
import liquibase.snapshot.DatabaseSnapshot;
import liquibase.snapshot.SnapshotControl;
import liquibase.snapshot.SnapshotGeneratorFactory;
Expand Down Expand Up @@ -81,6 +74,7 @@ public class SqlScriptTest {
protected String databaseType;
protected String projectVersion;
protected Liquibase liquibase;
protected DiffGeneratorFactory databaseDiffer;

@Before
public void setup() throws Exception {
Expand All @@ -91,52 +85,47 @@ public void setup() throws Exception {
databaseType = properties.getProperty("database.type");
projectVersion = properties.getProperty("project.version");

SnapshotParserFactory.getInstance().register(new DirectAccessSnapshotParser());

database = getDatabase();
liquibase = getLiquibase();
liquibase.dropAll();
databaseDiffer = DiffGeneratorFactory.getInstance();
cleanUpDatabaseTables();
}

@After
public void tearDown() throws Exception {
try {
liquibase.dropAll();
} finally {
liquibase.close();
}
cleanUpDatabaseTables();
liquibase.close();
}

@Test
public void shouldEqualLiquibaseChangelogAndCreateScripts() throws Exception {
// given
executeSqlScript("create", "engine");
executeSqlScript("create", "identity");
DatabaseSnapshot snapshotManualScripts = createCurrentDatabaseSnapshot();
liquibase.dropAll();
DatabaseSnapshot snapshotScripts = createCurrentDatabaseSnapshot();
cleanUpDatabaseTables();

// when set up with Liquibase changelog
liquibase.update(new Contexts());

// then
DatabaseSnapshot snapshotLiquibaseChangelog = createCurrentDatabaseSnapshot();
Database currentDatabase = getDatabaseForSnapshot(snapshotManualScripts);
Database upgradedDatabase = getDatabaseForSnapshot(snapshotLiquibaseChangelog);
DiffResult diffResult = liquibase.diff(currentDatabase, upgradedDatabase, new CompareControl());
List<ChangeSet> changeSetsToApply = new DiffToChangeLog(diffResult, new CustomDiffOutputControl()).generateChangeSets();
DatabaseSnapshot snapshotLiquibase = createCurrentDatabaseSnapshot();
DiffResult diffResult = databaseDiffer.compare(snapshotScripts, snapshotLiquibase, new CompareControl());
List<ChangeSet> changeSetsToApply = new DiffToChangeLog(diffResult, new CustomDiffOutputControl())
.generateChangeSets();

assertThat(changeSetsToApply)
.withFailMessage("Liquibase database schema misses changes: %s", getChanges(changeSetsToApply))
.isEmpty();
.withFailMessage("Liquibase database schema misses changes: %s", getChanges(changeSetsToApply))
.isEmpty();
}

@Test
public void shouldEqualOldUpgradedAndNewCreatedViaLiquibase() throws Exception {
try (Liquibase liquibaseOld = getLiquibase("scripts-old/")) {
try (Liquibase liquibaseOld = getLiquibase("scripts-old/", getDatabase())) {
// given
liquibase.update(new Contexts());
DatabaseSnapshot snapshotCurrent = createCurrentDatabaseSnapshot();
liquibase.dropAll();
cleanUpDatabaseTables();

// old changelog executed
liquibaseOld.update(new Contexts());
Expand All @@ -146,14 +135,12 @@ public void shouldEqualOldUpgradedAndNewCreatedViaLiquibase() throws Exception {

// then
DatabaseSnapshot snapshotUpgraded = createCurrentDatabaseSnapshot();
Database currentDatabase = getDatabaseForSnapshot(snapshotCurrent);
Database upgradedDatabase = getDatabaseForSnapshot(snapshotUpgraded);
DiffResult diffResult = liquibase.diff(currentDatabase, upgradedDatabase, new CompareControl());
DiffResult diffResult = databaseDiffer.compare(snapshotCurrent, snapshotUpgraded, new CompareControl());
List<ChangeSet> changeSetsToApply = new DiffToChangeLog(diffResult, new DiffOutputControl()).generateChangeSets();

assertThat(changeSetsToApply)
.withFailMessage("Resulting upgraded database misses changes: %s", getChanges(changeSetsToApply))
.isEmpty();
.withFailMessage("Resulting upgraded database misses changes: %s", getChanges(changeSetsToApply))
.isEmpty();
}
}

Expand All @@ -167,7 +154,7 @@ public void shouldEqualOldUpgradedAndNewCreatedViaScripts() throws Exception {
executeSqlScript("create", "identity");
DatabaseSnapshot snapshotCurrent = createCurrentDatabaseSnapshot();

liquibase.dropAll();
cleanUpDatabaseTables();

// old CREATE scripts executed
executeSqlScript("scripts-old/", "create", "engine_" + oldMajorMinor + ".0");
Expand All @@ -180,14 +167,13 @@ public void shouldEqualOldUpgradedAndNewCreatedViaScripts() throws Exception {

// then
DatabaseSnapshot snapshotUpgraded = createCurrentDatabaseSnapshot();
Database currentDatabase = getDatabaseForSnapshot(snapshotCurrent);
Database upgradedDatabase = getDatabaseForSnapshot(snapshotUpgraded);
DiffResult diffResult = liquibase.diff(currentDatabase, upgradedDatabase, new CompareControl());
List<ChangeSet> changeSetsToApply = new DiffToChangeLog(diffResult, new DiffOutputControl()).generateChangeSets();
DiffResult diffResult = databaseDiffer.compare(snapshotCurrent, snapshotUpgraded, new CompareControl());
List<ChangeSet> changeSetsToApply = new DiffToChangeLog(diffResult, new CustomDiffOutputControl())
.generateChangeSets();

assertThat(changeSetsToApply)
.withFailMessage("Resulting upgraded database schema differs: %s", getChanges(changeSetsToApply))
.isEmpty();
.withFailMessage("Resulting upgraded database schema differs: %s", getChanges(changeSetsToApply))
.isEmpty();
}

protected void executeSqlScript(String sqlFolder, String sqlScript) throws LiquibaseException {
Expand All @@ -202,25 +188,36 @@ protected void executeSqlScript(String baseDirectory, String sqlFolder, String s
database.execute(sqlFileChange.generateStatements(database), null);
}

protected void cleanUpDatabaseTables() {
try {
liquibase.dropAll();
// dropAll can be incomplete if it takes too long, second attempt should
// clean up leftovers
liquibase.dropAll();
} catch (Exception e) {
// ignored
}
}

protected Database getDatabase() throws DatabaseException {
String databaseUrl = properties.getProperty("database.url");
String databaseUser = properties.getProperty("database.username");
String databasePassword = properties.getProperty("database.password");
String databaseClass = properties.getProperty("database.driver");
return DatabaseFactory.getInstance().openDatabase(databaseUrl, databaseUser, databasePassword, databaseClass,
null, null, null, new ClassLoaderResourceAccessor());
return DatabaseFactory.getInstance().openDatabase(databaseUrl, databaseUser, databasePassword, databaseClass, null,
null, null, new ClassLoaderResourceAccessor());
}

protected Liquibase getLiquibase() throws URISyntaxException {
return new Liquibase("camunda-changelog.xml", getAccessorForChangelogDirectory(""), database);
return getLiquibase("", database);
}

protected Liquibase getLiquibase(String baseDirectory) throws URISyntaxException {
protected static Liquibase getLiquibase(String baseDirectory, Database database) throws URISyntaxException {
return new Liquibase("camunda-changelog.xml", getAccessorForChangelogDirectory(baseDirectory), database);
}

protected FileSystemResourceAccessor getAccessorForChangelogDirectory(String baseDirectory) throws URISyntaxException {
URI changelogUri = getClass().getClassLoader().getResource(baseDirectory + "sql/liquibase").toURI();
protected static FileSystemResourceAccessor getAccessorForChangelogDirectory(String baseDirectory) throws URISyntaxException {
URI changelogUri = SqlScriptTest.class.getClassLoader().getResource(baseDirectory + "sql/liquibase").toURI();
return new FileSystemResourceAccessor(Paths.get(changelogUri).toAbsolutePath().toFile());
}

Expand All @@ -229,75 +226,13 @@ protected DatabaseSnapshot createCurrentDatabaseSnapshot() throws Exception {
.createSnapshot(database.getDefaultSchema(), database, new SnapshotControl(database));
}

protected Database getDatabaseForSnapshot(DatabaseSnapshot snapshot) throws Exception {
String offlineDatabaseUrl = "offline:" + databaseType + "?snapshot=foo";
SnapshotResourceAccessor snapshotAccessor = new SnapshotResourceAccessor(snapshot);
OfflineConnection offlineDatabaseConnection = new OfflineConnection(offlineDatabaseUrl, snapshotAccessor);
return DatabaseFactory.getInstance().findCorrectDatabaseImplementation(offlineDatabaseConnection);
}

protected List<String> getChanges(List<ChangeSet> changeSetsToApply) {
protected static List<String> getChanges(List<ChangeSet> changeSetsToApply) {
return changeSetsToApply.stream()
.flatMap(cs -> cs.getChanges().stream())
.map(Change::getDescription)
.collect(Collectors.toList());
}

protected static class DirectAccessSnapshotParser implements SnapshotParser {

@Override
public int getPriority() {
return 0;
}

@Override
public DatabaseSnapshot parse(String path, ResourceAccessor resourceAccessor) throws LiquibaseParseException {
return ((SnapshotResourceAccessor) resourceAccessor).getSnapshot();
}

@Override
public boolean supports(String path, ResourceAccessor resourceAccessor) {
return resourceAccessor instanceof SnapshotResourceAccessor;
}
}

protected static class SnapshotResourceAccessor implements ResourceAccessor {

private DatabaseSnapshot snapshot;

public SnapshotResourceAccessor(DatabaseSnapshot snapshot) {
this.snapshot = snapshot;
}

@Override
public InputStreamList openStreams(String relativeTo, String streamPath) throws IOException {
return null;
}

@Override
public InputStream openStream(String relativeTo, String streamPath) throws IOException {
return null;
}

@Override
public SortedSet<String> list(String relativeTo,
String path,
boolean recursive,
boolean includeFiles,
boolean includeDirectories) throws IOException {
return null;
}

@Override
public SortedSet<String> describeLocations() {
return null;
}

public DatabaseSnapshot getSnapshot() {
return snapshot;
}
}

protected static class CustomDiffOutputControl extends DiffOutputControl {

public CustomDiffOutputControl() {
Expand Down

0 comments on commit e573859

Please sign in to comment.