package liquibase.change; import liquibase.database.Database; import liquibase.exception.ValidationErrors; import liquibase.serializer.core.string.StringChangeLogSerializer; import liquibase.statement.SqlStatement; import liquibase.statement.DatabaseFunction; import liquibase.test.TestContext; import static org.junit.Assert.*; import org.junit.Test; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.math.BigInteger; import java.util.*; /** * Base test class for changes */ public abstract class AbstractChangeTest { @Test public abstract void getRefactoringName() throws Exception; @Test public abstract void generateStatement() throws Exception; @Test public abstract void getConfirmationMessage() throws Exception; @Test public void generateCheckSum() throws Exception { Change change = createClassUnderTest(); if (change == null) { return; } CheckSum checkSum = change.generateCheckSum(); assertNotNull(checkSum); assertEquals(CheckSum.getCurrentVersion(), checkSum.getVersion()); assertTrue(checkSum.toString().startsWith(CheckSum.getCurrentVersion()+":")); Map<String, String> seenCheckSums = new HashMap<String, String>(); for (Field field : change.getClass().getDeclaredFields()) { field.setAccessible(true); if (field.getName().startsWith("$VR") || field.getName().equals("serialVersionUID")) { //code coverage related } else if (field.getName().equals("associatedWith")) { //currently not used } else if (String.class.isAssignableFrom(field.getType())) { field.set(change, "asdghasdgasdg"); checkThatChecksumIsNew(change, seenCheckSums, field); field.set(change, "gsgasdgasdggasdg sdg a"); checkThatChecksumIsNew(change, seenCheckSums, field); } else if (Boolean.class.isAssignableFrom(field.getType())) { field.set(change, Boolean.TRUE); checkThatChecksumIsNew(change, seenCheckSums, field); field.set(change, Boolean.FALSE); checkThatChecksumIsNew(change, seenCheckSums, field); } else if (Integer.class.isAssignableFrom(field.getType())) { field.set(change, 6532); checkThatChecksumIsNew(change, seenCheckSums, field); field.set(change, -52352); checkThatChecksumIsNew(change, seenCheckSums, field); } else if (DatabaseFunction.class.isAssignableFrom(field.getType())) { field.set(change, new DatabaseFunction("FUNC1")); checkThatChecksumIsNew(change, seenCheckSums, field); field.set(change, new DatabaseFunction("FUNC 2")); checkThatChecksumIsNew(change, seenCheckSums, field); } else if (BigInteger.class.isAssignableFrom(field.getType())) { field.set(change, new BigInteger("6532")); checkThatChecksumIsNew(change, seenCheckSums, field); field.set(change, new BigInteger("-52352")); checkThatChecksumIsNew(change, seenCheckSums, field); } else if (List.class.isAssignableFrom(field.getType())) { ColumnConfig column1 = new ColumnConfig(); ((List) field.get(change)).add(column1); column1.setName("ghsdgasdg"); checkThatChecksumIsNew(change, seenCheckSums, field); column1.setType("gasdgasdg"); checkThatChecksumIsNew(change, seenCheckSums, field); ColumnConfig column2 = new ColumnConfig(); ((List) field.get(change)).add(column2); column2.setName("87682346asgasdg"); checkThatChecksumIsNew(change, seenCheckSums, field); } else { throw new RuntimeException("Unknown field type: "+field.getType()+" for "+field.getName()); } } } private Change createClassUnderTest() throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { String className = getClass().getName().replaceAll("Test$", ""); if (className.indexOf("Abstract") > 0) { return null; } return (Change) Class.forName(className).getConstructor().newInstance(); } protected void checkThatChecksumIsNew(Change change, Map<String, String> seenCheckSums, Field field) { String serialized = new StringChangeLogSerializer().serialize(change); CheckSum newCheckSum = change.generateCheckSum(); if (seenCheckSums.containsKey(newCheckSum.toString())) { fail("generated duplicate checksum channging "+field.getName()+"\n"+serialized+"\nmatches\n"+seenCheckSums.get(newCheckSum.toString())); } seenCheckSums.put(newCheckSum.toString(), serialized); } // @Test // public void saveStatement() throws Exception { // Change change = new AbstractChange("test", "Test Refactoring", ChangeMetaData.PRIORITY_DEFAULT) { // public SqlStatement[] generateStatements(Database database) { // return new SqlStatement[]{new RawSqlStatement("GENERATED STATEMENT")}; // } // // public String getConfirmationMessage() { // return null; // } // }; // // StringWriter stringWriter = new StringWriter(); // // OracleDatabase database = new OracleDatabase(); // database.saveStatements(change, new ArrayList<SqlVisitor>(), stringWriter); // // assertEquals("GENERATED STATEMENT;" + StreamUtil.getLineSeparator() + StreamUtil.getLineSeparator(), stringWriter.getBuffer().toString()); // } //todo: reintroduce @Test // public void executeStatement() throws Exception { // Change change = new AbstractChange("test", "Test Refactorign", ChangeMetaData.PRIORITY_DEFAULT) { // public SqlStatement[] generateStatements(Database database) { // return new SqlStatement[]{new RawSqlStatement("GENERATED STATEMENT;")}; // } // // public String getConfirmationMessage() { // return null; // } // }; // // DatabaseConnection conn = createMock(DatabaseConnection.class); //// Statement statement = createMock(Statement.class); // conn.setAutoCommit(false); //// expect(((JdbcConnection) conn).getUnderlyingConnection().createStatement()).andReturn(statement); // //// expect(statement.execute("GENERATED STATEMENT;")).andStubReturn(true); //// statement.close(); //// expectLastCall(); // replay(conn); //// replay(statement); // // OracleDatabase database = new OracleDatabase(); // database.setConnection(conn); // // database.executeStatements(change, new ArrayList<SqlVisitor>()); // // verify(conn); //// verify(statement); // } protected void testChangeOnAllExcept(Change change, GenerateAllValidator validator, Class<? extends Database>... databases) throws Exception { List<Class<? extends Database>> databsesToRun = new ArrayList<Class<? extends Database>>(); for (Database database : TestContext.getInstance().getAllDatabases()) { List<Class<? extends Database>> databaseClasses = Arrays.asList(databases); if (!databaseClasses.contains(database.getClass())) { databsesToRun.add(database.getClass()); } } testChange(change, validator, databsesToRun.toArray(new Class[databsesToRun.size()])); } protected void testChangeOnAll(Change change, GenerateAllValidator validator) throws Exception { for (Database database : TestContext.getInstance().getAllDatabases()) { SqlStatement[] sqlStatements = change.generateStatements(database); try { validator.validate(sqlStatements, database); } catch (AssertionError e) { AssertionError error = new AssertionError("GenerateAllValidator failed for " + database.getTypeName() + ": " + e.getMessage()); error.setStackTrace(e.getStackTrace()); throw error; } } } protected void testChange(Change change, GenerateAllValidator validator, Class<? extends Database>... databases) throws Exception { for (Database database : TestContext.getInstance().getAllDatabases()) { List<Class<? extends Database>> databaseClasses = Arrays.asList(databases); if (!databaseClasses.contains(database.getClass())) { continue; } SqlStatement[] sqlStatements = change.generateStatements(database); try { validator.validate(sqlStatements, database); } catch (AssertionError e) { AssertionError error = new AssertionError("GenerateAllValidator failed for " + database.getTypeName() + ": " + e.getMessage()); error.setStackTrace(e.getStackTrace()); throw error; } } } protected void testInverseOnAllExcept(AbstractChange change, InverseValidator validator, Class<? extends Database>... databases) throws Exception { List<Class<? extends Database>> databsesToRun = new ArrayList<Class<? extends Database>>(); for (Database database : TestContext.getInstance().getAllDatabases()) { List<Class<? extends Database>> databaseClasses = Arrays.asList(databases); if (!databaseClasses.contains(database.getClass())) { databsesToRun.add(database.getClass()); } } testInverse(change, validator, databsesToRun.toArray(new Class[databsesToRun.size()])); } protected void testInverseOnAll(AbstractChange change, InverseValidator validator) throws Exception { for (Database database : TestContext.getInstance().getAllDatabases()) { Change[] inverses = change.createInverses(); try { validator.validate(inverses); } catch (AssertionError e) { AssertionError error = new AssertionError("InverseValidator failed for " + database.getTypeName() + ": " + e.getMessage()); error.setStackTrace(e.getStackTrace()); throw error; } } } protected void testInverse(AbstractChange change, InverseValidator validator, Class<? extends Database>... databases) throws Exception { for (Database database : TestContext.getInstance().getAllDatabases()) { List<Class<? extends Database>> databaseClasses = Arrays.asList(databases); if (!databaseClasses.contains(database.getClass())) { continue; } Change[] inverses = change.createInverses(); try { validator.validate(inverses); } catch (AssertionError e) { AssertionError error = new AssertionError("InverseValidator failed for " + database.getTypeName() + ": " + e.getMessage()); error.setStackTrace(e.getStackTrace()); throw error; } } } @Test public void isSupported() throws Exception { Change change = createClassUnderTest(); if (change == null) { return; } for (Database database : TestContext.getInstance().getAllDatabases()) { assertEquals("Unexpected availablity on "+database.getTypeName(), !changeIsUnsupported(database), change.supports(database)); } } protected boolean changeIsUnsupported(Database database) { return false; } @Test public void validate() throws Exception { Change change = createClassUnderTest(); if (change == null) { return; } for (Database database : TestContext.getInstance().getAllDatabases()) { if (change.supports(database)) { ValidationErrors validationErrors = change.validate(database); assertTrue("no errors found for "+database.getClass().getName(), validationErrors.hasErrors()); } } } protected static interface GenerateAllValidator { public void validate(SqlStatement[] statements, Database database); } protected static interface InverseValidator { public void validate(Change[] statements); } }