DefaultPrepAndExpectedTestCase.java
/*
*
* The DbUnit Database Testing Framework
* Copyright (C)2002-2008, DbUnit.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package org.dbunit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import org.dbunit.assertion.FailureHandler;
import org.dbunit.assertion.comparer.value.ValueComparer;
import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.database.connection.AutoCommitOffWarning;
import org.dbunit.database.connection.ConnectionOwnership;
import org.dbunit.database.connection.TestScopedConnection;
import org.dbunit.database.rowcount.RowCountCheck;
import org.dbunit.database.rowcount.RowCountChecker;
import org.dbunit.dataset.Column;
import org.dbunit.dataset.CompositeDataSet;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.DefaultDataSet;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.ITableMetaData;
import org.dbunit.dataset.SortedTable;
import org.dbunit.dataset.datatype.DataType;
import org.dbunit.dataset.filter.DefaultColumnFilter;
import org.dbunit.operation.DatabaseOperation;
import org.dbunit.util.TableFormatter;
import org.dbunit.util.fileloader.DataFileLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Test case base class supporting prep data and expected data. Prep data is the
* data needed for the test to run. Expected data is the data needed to compare
* if the test ran successfully.
* <p>
* configureTest(), setupData(), verifyData(), and cleanupData() share one
* {@link org.dbunit.database.IDatabaseConnection} for a test's lifecycle,
* acquired lazily on first use and closed once by cleanupData(), instead of
* each acquiring (and often closing) its own. Calling any of those methods
* without an eventual cleanupData() call - e.g. testing them individually
* rather than through preTest()/postTest() - leaves that connection open.
* <p>
* If databaseTester is configured with a
* {@link org.dbunit.database.CachingConnectionProvider} shared across test
* methods, set {@link #setCloseConnectionAfterTest(boolean)} to false so
* cleanupData() does not close a connection other tests still expect to
* reuse; the provider's owner is then responsible for closing it once,
* itself, when the whole run finishes.
* <p>
* The connection must be in autocommit mode. The setup and teardown
* operations run here (CLEAN_INSERT, DELETE_ALL, ...) do not manage a
* transaction of their own, and {@link org.dbunit.operation.TransactionOperation}
* refuses a connection whose autocommit is already off, so on a
* non-autocommit connection the prep and teardown writes are never
* committed: invisible to any other connection, and - with
* {@link #setCloseConnectionAfterTest(boolean)} false - held as locks on a
* connection the database's idle-in-transaction timeout may terminate
* mid-run. This class logs a warning when it detects such a connection.
* <p>
* The {@code verifyData()} method hands assertion failures to
* {@link org.dbunit.assertion.DefaultFailureHandler} by default, which throws
* on the first mismatch found. Set {@link #setFailureHandler(FailureHandler)}
* to, for example, a {@link org.dbunit.assertion.DiffCollectingFailureHandler}
* to collect every {@link org.dbunit.assertion.Difference} instead; that is
* not the default since most tests want to keep failing fast.
*
* @see "org.dbunit.DefaultPrepAndExpectedTestCaseDiIT, a composition-based (DI) usage example in the test sources"
* @see "org.dbunit.DefaultPrepAndExpectedTestCaseExtIT, an inheritance-based usage example in the test sources"
*
* @author Jeff Jensen jeffjensen AT users.sourceforge.net
* @author Last changed by: $Author$
* @version $Revision$ $Date$
* @since 2.4.8
*/
public class DefaultPrepAndExpectedTestCase extends DBTestCase
implements PrepAndExpectedTestCase
{
private final Logger log =
LoggerFactory.getLogger(DefaultPrepAndExpectedTestCase.class);
private static final String DATABASE_TESTER_IS_NULL_MSG =
"databaseTester is null; must configure or set it first";
/** Message prefix used for wrapped test failures. */
public static final String TEST_ERROR_MSG = "DbUnit test error.";
private IDatabaseTester databaseTester;
private DataFileLoader dataFileLoader;
/**
* Whether lookupFeatureValue() and cleanupData() close the connection
* they are done with; false when databaseTester shares a
* CachingConnectionProvider across test methods and this instance must
* not close a connection other tests still expect to reuse.
*
* @since 3.4.0
*/
private boolean closeConnectionAfterTest = true;
// per test data
private IDataSet prepDataSet = new DefaultDataSet();
private IDataSet expectedDataSet = new DefaultDataSet();
private VerifyTableDefinition[] verifyTableDefs = {};
/**
* Connection shared by setupData()/verifyData()/cleanupData() for one
* test's lifecycle instead of each acquiring (and often closing) its own:
* acquired lazily on first use from {@link #getConnection()}, closed once
* by cleanupData() unless {@link #closeConnectionAfterTest} is false or
* {@link #getOperationListener()} is the no-op listener, and re-acquired if
* the pool or server closed it between reused test methods.
*
* @since 3.6.0
*/
private final TestScopedConnection reusableConnectionHolder = newReusableConnectionHolder();
/**
* Builds {@link #reusableConnectionHolder}. Its {@link ConnectionOwnership}
* reads {@link #closeConnectionAfterTest} and {@link #getOperationListener()}
* fresh at release time; its third input - whether the borrowing lifecycle
* ran - is always {@code true} here, since this class only ever releases the
* connection from its own cleanupData(), which runs only after setupData()
* already acquired it. Since 3.6.0 an {@link #getOperationListener()} that is
* (or wraps) {@link IOperationListener#NO_OP_OPERATION_LISTENER} keeps
* cleanupData() from closing the connection even when
* {@link #closeConnectionAfterTest} is true - the established signal that the
* connection is owned elsewhere, now honored on this path too and not only
* by the annotation runtime.
*
* @return The holder.
*/
private TestScopedConnection newReusableConnectionHolder()
{
final ConnectionOwnership ownership = new ConnectionOwnership(
() -> closeConnectionAfterTest, this::getOperationListener, () -> true);
return new TestScopedConnection(this::getConnection, ownership,
new AutoCommitOffWarning());
}
/**
* isCaseSensitiveTableNames as resolved by configureTest(), cached so
* cleanupData() does not need a second connection just to re-read this
* same DatabaseConfig feature flag.
*
* @since 3.4.0
*/
private Boolean cachedIsCaseSensitiveTableNames;
/**
* Manages the row count check baseline used by preTest() and cleanupData() to detect a
* table the test left dirty - either one it should have cleaned up and did not, or a
* reference table it wrongly cleaned.
*
* @since 3.6.0
*/
private final RowCountChecker rowCountChecker = new RowCountChecker();
private ExpectedDataSetAndVerifyTableDefinitionVerifier expectedDataSetAndVerifyTableDefinitionVerifier =
new DefaultExpectedDataSetAndVerifyTableDefinitionVerifier();
/**
* FailureHandler for verifyData()'s assertion failures. Null (the
* default) leaves compareData() using
* {@link org.dbunit.Assertion#assertWithValueComparer(ITable, ITable, Column[], ValueComparer, Map)}'s
* own {@link org.dbunit.assertion.DefaultFailureHandler}, configured with
* the additionalColumnInfo computed by {@link #makeAdditionalColumnInfo};
* that default is intentional so most tests keep failing fast on the
* first mismatch. Set this, for example to a
* {@link org.dbunit.assertion.DiffCollectingFailureHandler}, only when a
* test needs to collect every {@link org.dbunit.assertion.Difference}
* instead.
*
* @since 3.5.0
*/
private FailureHandler failureHandler;
/**
* DatabaseConfig property name/value pairs applied to the connection shared by
* setupData(), verifyData() and cleanupData(), every time {@link #getConnection()}
* resolves it - see {@link #setUpDatabaseConfig(DatabaseConfig)}. Null (the default)
* applies none.
*
* @since 3.6.0
*/
private Properties databaseConfigProperties;
final TableFormatter tableFormatter = new TableFormatter();
/** Create new instance. */
public DefaultPrepAndExpectedTestCase()
{
}
/**
* Create new instance with specified dataFileLoader and databaseTester.
*
* @param dataFileLoader
* Load to use for loading the data files.
* @param databaseTester
* Tester to use for database manipulation.
*/
public DefaultPrepAndExpectedTestCase(final DataFileLoader dataFileLoader,
final IDatabaseTester databaseTester)
{
this.dataFileLoader = dataFileLoader;
this.databaseTester = databaseTester;
}
/**
* Create new instance with specified dataFileLoader and databaseTester.
*
* @param dataFileLoader
* Load to use for loading the data files.
* @param databaseTester
* Tester to use for database manipulation.
* @param closeConnectionAfterTest
* Whether or not to close the database connection after each test.
*
* @since 3.4.0
*/
public DefaultPrepAndExpectedTestCase(final DataFileLoader dataFileLoader,
final IDatabaseTester databaseTester,
final boolean closeConnectionAfterTest)
{
this.dataFileLoader = dataFileLoader;
this.databaseTester = databaseTester;
this.closeConnectionAfterTest = closeConnectionAfterTest;
}
/**
* Create new instance with specified test case name.
*
* @param name
* The test case name.
*/
public DefaultPrepAndExpectedTestCase(final String name)
{
super(name);
}
/**
* {@inheritDoc} This implementation returns the databaseTester set by the
* test.
*/
@Override
public IDatabaseTester newDatabaseTester() throws Exception
{
// questionable, but there is not a "setter" for any parent...
return databaseTester;
}
/**
* {@inheritDoc} Returns the prep dataset.
*/
@Override
public IDataSet getDataSet() throws Exception
{
return prepDataSet;
}
/**
* {@inheritDoc}
* <p>
* Executes against the connection shared with setupData(), verifyData()
* and cleanupData() for this test's lifecycle rather than a fresh one,
* and leaves it open; cleanupData() closes it. See #800.
*/
@Override
public void configureTest(
final VerifyTableDefinition[] verifyTableDefinitions,
final String[] prepDataFiles, final String[] expectedDataFiles)
throws Exception
{
log.debug("configureTest: saving instance variables");
final boolean isCaseSensitiveTableNames = lookupFeatureValue(
DatabaseConfig.FEATURE_CASE_SENSITIVE_TABLE_NAMES);
log.debug("configureTest: using case sensitive table names={}",
isCaseSensitiveTableNames);
this.cachedIsCaseSensitiveTableNames = isCaseSensitiveTableNames;
this.prepDataSet = makeCompositeDataSet(prepDataFiles, "prep",
isCaseSensitiveTableNames);
this.expectedDataSet = makeCompositeDataSet(expectedDataFiles,
"expected", isCaseSensitiveTableNames);
this.verifyTableDefs = verifyTableDefinitions;
}
/**
* {@inheritDoc} Applies {@link #databaseConfigProperties}, when set - the composition-based
* equivalent of overriding this method, for a caller that cannot subclass to do so directly
* (e.g. {@code org.dbunit.annotation}'s {@code @DbUnitProperty}).
*
* <p><strong>Note:</strong> a subclass overriding this method must call
* {@code super.setUpDatabaseConfig(config)} to keep
* {@link #setDatabaseConfigProperties(java.util.Properties)} - and therefore
* {@code @DbUnitProperty} on the annotation-driven path - working.
*
* @throws IllegalStateException If a property value is invalid for its target
* {@link DatabaseConfig} entry.
*/
@Override
protected void setUpDatabaseConfig(final DatabaseConfig config)
{
if (databaseConfigProperties == null || databaseConfigProperties.isEmpty())
{
return;
}
try
{
config.setPropertiesByString(databaseConfigProperties);
} catch (final DatabaseUnitException e)
{
throw new IllegalStateException("Failed to apply a databaseConfigProperties value.",
e);
}
}
private boolean lookupFeatureValue(final String featureName)
throws Exception
{
final boolean acquiredConnectionHere = reusableConnectionHolder.peekConnection() == null;
try
{
final IDatabaseConnection reusableConnection =
getReusableConnection();
final DatabaseConfig config = reusableConnection.getConfig();
return config.getFeature(featureName);
} catch (final Exception e)
{
if (acquiredConnectionHere)
{
closeReusableConnectionSuppressing(e);
}
throw e;
}
}
/**
* Return the connection shared by lookupFeatureValue(), setupData(),
* verifyData() and cleanupData() for the current test's lifecycle,
* acquiring it on first use instead of a fresh connection at each step.
* <p>
* When {@link #closeConnectionAfterTest} is false this connection is kept
* across test methods, where the connection pool or the database server can
* close it between tests - a pool max-lifetime or reap, a bounced
* application context, a
* {@link org.dbunit.database.CachingConnectionProvider#close()}. A closed
* one is discarded before it is handed back, so the next call re-acquires
* from databaseTester - letting a
* {@link org.dbunit.database.CachingConnectionProvider} behind it supply a
* live replacement - rather than this instance reusing a connection every
* later lifecycle step would only fail on.
*
* @return The shared connection.
* @throws Exception On dbUnit errors.
* @since 3.4.0
*/
@Override
public IDatabaseConnection getReusableConnection() throws Exception
{
return reusableConnectionHolder.getConnection();
}
/**
* Release the connection shared by lookupFeatureValue(), setupData(),
* verifyData() and cleanupData(), if one was acquired: closes it and
* forgets it when {@link #closeConnectionAfterTest} is true (the default)
* and {@link #getOperationListener()} is not the no-op listener; otherwise
* leaves it open for its real owner - a
* {@link org.dbunit.database.CachingConnectionProvider}, an
* externally-supplied fixed connection - and keeps it memoized so a later
* lifecycle step reuses it rather than orphaning another. A kept connection
* the pool or server has since closed is dropped and re-acquired on the
* next {@link #getReusableConnection()}.
*
* @throws Exception On close errors.
* @since 3.4.0
*/
private void closeReusableConnection() throws Exception
{
reusableConnectionHolder.release();
}
/**
* Close the reusable connection, attaching any close failure to the given
* primary throwable via {@link Throwable#addSuppressed(Throwable)} rather
* than letting it replace and hide the primary. Mirrors the exception
* safety of {@link #runTest} and {@code DatabaseTestCase.tearDown(Throwable)}.
* <p>
* Only ever called from a lifecycle step that has already failed, so it also
* forgets the shared connection even when {@link #closeConnectionAfterTest}
* is false and {@link #closeReusableConnection()} therefore left it open: a
* step that just threw may have broken it, so the next
* {@link #getReusableConnection()} re-acquires rather than reusing it. Any
* {@link org.dbunit.database.CachingConnectionProvider} behind
* {@code databaseTester} still owns closing it.
*
* @param primary
* The exception already in flight to attach a close failure
* to.
* @since 3.4.0
*/
private void closeReusableConnectionSuppressing(final Throwable primary)
{
reusableConnectionHolder.releaseSuppressing(primary);
}
/**
* Make a {@link ReusableConnectionDatabaseTester} configured with the
* given dataset and databaseTester's setUpOperation and
* tearDownOperation, for setupData() or cleanupData() to run
* {@link IDatabaseTester#onSetup()} or {@link IDatabaseTester#onTearDown()}
* on, respectively. Setting both operations regardless of which one the
* caller uses is safe: {@link AbstractDatabaseTester#onSetup()} only
* reads setUpOperation and {@link AbstractDatabaseTester#onTearDown()}
* only reads tearDownOperation, so the other is simply never consulted.
*
* @param dataSet
* The dataset to run the operation against.
* @return The configured tester.
* @throws Exception On dbUnit errors.
* @since 3.4.0
*/
private IDatabaseTester makeReusableConnectionDatabaseTester(
final IDataSet dataSet) throws Exception
{
final IDatabaseTester reusableTester =
new ReusableConnectionDatabaseTester(
this::getReusableConnection);
reusableTester.setSetUpOperation(getSetUpOperation());
reusableTester.setTearDownOperation(getTearDownOperation());
reusableTester.setDataSet(dataSet);
// This instance, not the listener, owns the shared connection's lifecycle (see
// getReusableConnection()/closeReusableConnection()), so a blanket
// ConnectionPreservingOperationListener keeps operationSetUpFinished/
// operationTearDownFinished from closing it while still forwarding connectionRetrieved
// so a user-defined listener runs its connection-configuration logic.
reusableTester.setOperationListener(
new ConnectionPreservingOperationListener(getOperationListener()));
return reusableTester;
}
/**
* {@inheritDoc}
* <p>
* Captures the row count check baseline, if enabled, before setting up the prep data - see
* {@link RowCountCheck#capture(IDatabaseConnection)}.
*/
@Override
public void preTest() throws Exception
{
captureRowCountBaseline();
setupData();
}
/**
* Capture the row count check baseline, using the connection shared with the rest of this
* test's lifecycle. A no-op that leaves no baseline captured when the check is disabled.
*
* @throws Exception On dbUnit errors.
* @since 3.6.0
*/
private void captureRowCountBaseline() throws Exception
{
final boolean acquiredConnectionHere = reusableConnectionHolder.peekConnection() == null;
try
{
rowCountChecker.capture(getReusableConnection());
} catch (final Exception e)
{
if (acquiredConnectionHere)
{
closeReusableConnectionSuppressing(e);
}
throw e;
}
}
/**
* {@inheritDoc}
*/
@Override
public void preTest(final VerifyTableDefinition[] tables,
final String[] prepDataFiles, final String[] expectedDataFiles)
throws Exception
{
configureTest(tables, prepDataFiles, expectedDataFiles);
preTest();
}
/**
* {@inheritDoc}
*/
@Override
public Object runTest(final VerifyTableDefinition[] verifyTables,
final String[] prepDataFiles, final String[] expectedDataFiles,
final PrepAndExpectedTestCaseSteps testSteps) throws Exception
{
final Object result;
try
{
preTest(verifyTables, prepDataFiles, expectedDataFiles);
log.info("runTest: running test steps");
result = runTestSteps(testSteps);
} catch (final Throwable e)
{
log.error(TEST_ERROR_MSG, e);
// don't verify table data when test execution has errors as:
// * a verify data failure masks the test error exception
// * tables in unknown state and therefore probably not accurate
try
{
postTest(false);
} catch (final Throwable cleanupFailure)
{
// never let a cleanup failure replace and hide the real test
// failure; keep e as the thrown exception, cleanupFailure alongside
e.addSuppressed(cleanupFailure);
}
throw e;
}
postTest();
return result;
}
/**
* Run the provided test steps. Override as necessary for custom logic.
*
* @param testSteps
* The test steps to run.
* @return the user-defined object returned by the test steps.
* @throws Exception if the test steps fail.
*/
protected Object runTestSteps(final PrepAndExpectedTestCaseSteps testSteps)
throws Exception
{
return testSteps.run();
}
/**
* {@inheritDoc}
*/
@Override
public void postTest() throws Exception
{
postTest(true);
}
/**
* {@inheritDoc}
* <p>
* When {@code verifyData} is false - the test steps already failed - discards any
* captured row count check baseline, so cleanupData() skips that check too: the database
* is in an unknown state, so a count difference would be noise around the real failure,
* not a finding worth its own report.
*/
@Override
public void postTest(final boolean verifyData) throws Exception
{
Throwable verifyFailure = null;
try
{
if (verifyData)
{
verifyData();
} else
{
rowCountChecker.discardBaseline();
}
} catch (final Throwable t)
{
verifyFailure = t;
throw t;
} finally
{
// it is deliberate to have cleanup exceptions shadow verify
// failures so user knows db is probably in unknown state (for
// those not using an in-memory db or transaction rollback),
// otherwise would mask probable cause of subsequent test
// failures; the verify failure rides along as suppressed on the
// cleanup exception so it is not lost entirely
try
{
cleanupData();
} catch (final Throwable cleanupFailure)
{
if (verifyFailure != null)
{
cleanupFailure.addSuppressed(verifyFailure);
}
throw cleanupFailure;
}
}
}
/**
* {@inheritDoc}
* <p>
* Runs the tear down operation against the connection shared with
* setupData() and verifyData() for this test's lifecycle, then closes it.
* See #800.
*/
@Override
public void cleanupData() throws Exception
{
try
{
final boolean isCaseSensitiveTableNames;
if (cachedIsCaseSensitiveTableNames == null)
{
isCaseSensitiveTableNames = lookupFeatureValue(
DatabaseConfig.FEATURE_CASE_SENSITIVE_TABLE_NAMES);
} else
{
isCaseSensitiveTableNames = cachedIsCaseSensitiveTableNames;
}
log.debug("cleanupData: using case sensitive table names={}",
isCaseSensitiveTableNames);
final IDataSet[] dataSets = {prepDataSet, expectedDataSet};
final IDataSet dataset = new CompositeDataSet(dataSets, true,
isCaseSensitiveTableNames);
final String[] tableNames = dataset.getTableNames();
final int count = tableNames.length;
log.info("cleanupData: about to clean up {} tables={}", count,
tableNames);
if (databaseTester == null)
{
throw new IllegalStateException(DATABASE_TESTER_IS_NULL_MSG);
}
final IDatabaseTester reusableTester =
makeReusableConnectionDatabaseTester(dataset);
reusableTester.onTearDown();
log.debug("cleanupData: Clean up done");
verifyRowCountUnchanged();
closeReusableConnection();
} catch (final Exception e)
{
log.error("cleanupData: Exception:", e);
closeReusableConnectionSuppressing(e);
throw e;
}
}
/**
* Verify the row count check baseline, if one was captured, using the connection shared
* with the rest of this test's lifecycle. A no-op when no baseline was captured - the
* check is disabled, capture never ran, or {@link #postTest(boolean)} discarded it
* because the test steps already failed.
*
* @throws Exception On dbUnit errors, including {@link org.dbunit.database.rowcount.UnexpectedRowCountException}
* when a table's row count no longer matches the baseline.
* @since 3.6.0
*/
private void verifyRowCountUnchanged() throws Exception
{
rowCountChecker.verify(getReusableConnection());
}
/**
* Legacy JUnit-3-era tear-down hook. Not invoked automatically under JUnit 5;
* kept for subclasses that drive the lifecycle manually. Calling it after a
* full {@link #runTest} or {@link #postTest} cycle cleans up a second time
* (parent tearDown() re-runs the tear down operation on the prep dataset with
* a fresh connection).
*/
@Override
protected void tearDown() throws Exception
{
// parent tearDown() only cleans up prep data
cleanupData();
super.tearDown();
}
/**
* Use the provided databaseTester to prep the database with the provided
* prep dataset. See {@link org.dbunit.IDatabaseTester#onSetup()}.
* <p>
* Executes against the connection shared with verifyData() and
* cleanupData() for this test's lifecycle rather than a fresh one, and
* leaves it open; cleanupData() closes it. See #800.
*
* @throws Exception if preparing the data fails.
*/
public void setupData() throws Exception
{
log.info("setupData: setting prep dataset and inserting rows");
if (databaseTester == null)
{
throw new IllegalStateException(DATABASE_TESTER_IS_NULL_MSG);
}
try
{
final IDatabaseTester reusableTester =
makeReusableConnectionDatabaseTester(getDataSet());
reusableTester.onSetup();
} catch (final Exception e)
{
log.error("setupData: Exception with setting up data:", e);
throw e;
}
}
@Override
protected DatabaseOperation getSetUpOperation() throws Exception
{
if (databaseTester == null)
{
throw new IllegalStateException(DATABASE_TESTER_IS_NULL_MSG);
}
return databaseTester.getSetUpOperation();
}
@Override
protected DatabaseOperation getTearDownOperation() throws Exception
{
if (databaseTester == null)
{
throw new IllegalStateException(DATABASE_TESTER_IS_NULL_MSG);
}
return databaseTester.getTearDownOperation();
}
/**
* {@inheritDoc} Uses the connection from the provided databaseTester,
* shared with setupData() and cleanupData() for this test's lifecycle.
* Left open on return; cleanupData() closes it. See #800.
*/
@Override
public void verifyData() throws Exception
{
if (databaseTester == null)
{
throw new IllegalStateException(DATABASE_TESTER_IS_NULL_MSG);
}
final IDatabaseConnection reusableConnection = getReusableConnection();
final DatabaseConfig config = reusableConnection.getConfig();
expectedDataSetAndVerifyTableDefinitionVerifier.verify(verifyTableDefs,
expectedDataSet, config);
try
{
final int tableDefsCount = verifyTableDefs.length;
if (tableDefsCount == 0)
{
log.info("verifyData: No tables to verify as"
+ " no VerifyTableDefinitions specified");
} else
{
log.info(
"verifyData: about to verify {} tables"
+ " using verifyTableDefinitions={}",
tableDefsCount, verifyTableDefs);
}
for (int i = 0; i < tableDefsCount; i++)
{
final VerifyTableDefinition td = verifyTableDefs[i];
verifyData(reusableConnection, td);
}
} catch (final Exception e)
{
log.error("verifyData: Exception:", e);
throw e;
}
}
/**
* Verifies a single table's actual data against its expected data.
*
* @param connection the database connection to load the actual data from.
* @param verifyTableDefinition the table definition to verify.
* @throws Exception if verifying the table fails.
*/
protected void verifyData(final IDatabaseConnection connection,
final VerifyTableDefinition verifyTableDefinition) throws Exception
{
final String tableName = verifyTableDefinition.getTableName();
log.debug("verifyData: Verifying table '{}'", tableName);
final String[] excludeColumns =
verifyTableDefinition.getColumnExclusionFilters();
final String[] includeColumns =
verifyTableDefinition.getColumnInclusionFilters();
final Map<String, ValueComparer> columnValueComparers =
verifyTableDefinition.getColumnValueComparers();
final ValueComparer defaultValueComparer =
verifyTableDefinition.getDefaultValueComparer();
final boolean sortOnFilteredColumnsOnly =
verifyTableDefinition.isSortOnFilteredColumnsOnly();
final ITable expectedTable = loadTableDataFromDataSet(tableName);
final ITable actualTable =
loadTableDataFromDatabase(tableName, connection);
if (sortOnFilteredColumnsOnly)
{
verifyData(expectedTable, actualTable, excludeColumns,
includeColumns, defaultValueComparer, columnValueComparers,
true);
} else
{
verifyData(expectedTable, actualTable, excludeColumns,
includeColumns, defaultValueComparer,
columnValueComparers);
}
}
/**
* Loads the given table's expected data from the expected dataset.
*
* @param tableName the name of the table to load.
* @return the table's expected data.
* @throws DataSetException if loading the table fails.
*/
public ITable loadTableDataFromDataSet(final String tableName)
throws DataSetException
{
ITable table = null;
final String methodName = "loadTableDataFromDataSet";
log.debug("{}: Loading table {} from expected dataset", methodName,
tableName);
try
{
table = expectedDataSet.getTable(tableName);
} catch (final Exception e)
{
final String msg = methodName + ": Problem obtaining table '"
+ tableName + "' from expected dataset";
log.error(msg, e);
throw new DataSetException(msg, e);
}
return table;
}
/**
* Loads the given table's actual data from the database.
*
* @param tableName the name of the table to load.
* @param connection the database connection to load the data from.
* @return the table's actual data.
* @throws Exception if loading the table fails.
*/
public ITable loadTableDataFromDatabase(final String tableName,
final IDatabaseConnection connection) throws Exception
{
ITable table = null;
final String methodName = "loadTableDataFromDatabase";
log.debug("{}: Loading table {} from database", methodName, tableName);
try
{
table = connection.createTable(tableName);
} catch (final Exception e)
{
final String msg = methodName + ": Problem obtaining table '"
+ tableName + "' from database";
log.error(msg, e);
throw new DataSetException(msg, e);
}
return table;
}
/**
* For the specified expected and actual tables (and excluding and including
* the specified columns), verify the actual data is as expected.
*
* @param expectedTable
* The expected table to compare the actual table to.
* @param actualTable
* The actual table to compare to the expected table.
* @param excludeColumns
* The column names to exclude from comparison. See
* {@link org.dbunit.dataset.filter.DefaultColumnFilter#excludeColumn(String)}
* .
* @param includeColumns
* The column names to only include in comparison. See
* {@link org.dbunit.dataset.filter.DefaultColumnFilter#includeColumn(String)}
* .
* @param defaultValueComparer
* {@link ValueComparer} to use with column value comparisons
* when the column name for the table is not in the
* columnValueComparers {@link Map}. Can be <code>null</code> and
* will default.
* @param columnValueComparers
* {@link Map} of {@link ValueComparer}s to use for specific
* columns. Key is column name, value is the
* {@link ValueComparer}. Can be <code>null</code> and will
* default to defaultValueComparer for all columns in all tables.
* @throws DatabaseUnitException if the tables' row counts, columns, or data do not match.
* @see #verifyData(ITable, ITable, String[], String[], ValueComparer, Map, boolean)
* to also control whether sorting considers only the filtered
* columns; this overload always sorts by all native columns.
*/
protected void verifyData(final ITable expectedTable,
final ITable actualTable, final String[] excludeColumns,
final String[] includeColumns,
final ValueComparer defaultValueComparer,
final Map<String, ValueComparer> columnValueComparers)
throws DatabaseUnitException
{
verifyData(expectedTable, actualTable, excludeColumns, includeColumns,
defaultValueComparer, columnValueComparers, false);
}
/**
* For the specified expected and actual tables (and excluding and including
* the specified columns), verify the actual data is as expected.
*
* @param expectedTable
* The expected table to compare the actual table to.
* @param actualTable
* The actual table to compare to the expected table.
* @param excludeColumns
* The column names to exclude from comparison. See
* {@link org.dbunit.dataset.filter.DefaultColumnFilter#excludeColumn(String)}
* .
* @param includeColumns
* The column names to only include in comparison. See
* {@link org.dbunit.dataset.filter.DefaultColumnFilter#includeColumn(String)}
* .
* @param defaultValueComparer
* {@link ValueComparer} to use with column value comparisons
* when the column name for the table is not in the
* columnValueComparers {@link Map}. Can be <code>null</code> and
* will default.
* @param columnValueComparers
* {@link Map} of {@link ValueComparer}s to use for specific
* columns. Key is column name, value is the
* {@link ValueComparer}. Can be <code>null</code> and will
* default to defaultValueComparer for all columns in all tables.
* @param sortOnFilteredColumnsOnly
* True to sort the expected and actual tables by only the
* columns that survive excludeColumns/includeColumns, instead
* of by all of the actual table's native columns; see
* {@link VerifyTableDefinition#isSortOnFilteredColumnsOnly()}.
* @throws DatabaseUnitException if the tables' row counts, columns, or data do not match.
* @since 3.5.0
*/
protected void verifyData(final ITable expectedTable,
final ITable actualTable, final String[] excludeColumns,
final String[] includeColumns,
final ValueComparer defaultValueComparer,
final Map<String, ValueComparer> columnValueComparers,
final boolean sortOnFilteredColumnsOnly)
throws DatabaseUnitException
{
final String methodName = "verifyData";
final ITableMetaData actualTableMetaData =
actualTable.getTableMetaData();
final ITableMetaData expectedTableMetaData =
expectedTable.getTableMetaData();
final Column[] actualTableColumns = actualTableMetaData.getColumns();
final Column[] expectedTableColumns = makeExpectedTableColumns(
actualTableColumns, expectedTableMetaData);
final Column[] actualSortColumns;
final Column[] expectedSortColumns;
if (sortOnFilteredColumnsOnly)
{
log.debug("{}: Sorting using only filtered columns", methodName);
final String tableName = actualTableMetaData.getTableName();
actualSortColumns = makeSortColumns(actualTableColumns,
excludeColumns, includeColumns, tableName);
expectedSortColumns = makeSortColumns(expectedTableColumns,
excludeColumns, includeColumns, tableName);
} else
{
log.debug("{}: Sorting using all columns", methodName);
actualSortColumns = actualTableColumns;
expectedSortColumns = expectedTableColumns;
}
final SortedTable expectedSortedTable =
new SortedTable(expectedTable, expectedSortColumns, true);
expectedSortedTable.setUseComparable(true);
log.trace("{}: Sorted expected table={}", methodName,
expectedSortedTable);
final SortedTable actualSortedTable =
new SortedTable(actualTable, actualSortColumns);
actualSortedTable.setUseComparable(true);
log.trace("{}: Sorted actual table={}", methodName, actualSortedTable);
// Filter out the columns from the expected and actual results
log.debug(
"{}: Applying column exclude and include filters to sorted expected table",
methodName);
final ITable expectedFilteredTable = applyColumnFilters(
expectedSortedTable, excludeColumns, includeColumns);
log.debug(
"{}: Applying column exclude and include filters to sorted actual table",
methodName);
final ITable actualFilteredTable = applyColumnFilters(actualSortedTable,
excludeColumns, includeColumns);
log.debug("{}: Creating additionalColumnInfo for expected table",
methodName);
final Column[] additionalColumnInfo =
makeAdditionalColumnInfo(expectedTable, excludeColumns);
log.trace("{}: additionalColumnInfo={}", methodName,
additionalColumnInfo);
logSortedTables(expectedSortedTable, actualSortedTable);
log.debug("{}: Comparing expected table to actual table", methodName);
compareData(expectedFilteredTable, actualFilteredTable,
additionalColumnInfo, defaultValueComparer,
columnValueComparers);
}
/**
* Reduces the given columns to those that survive the given exclude and
* include column filters, using the same matching semantics - including
* {@link DefaultColumnFilter}'s wildcard pattern support - as
* {@link #applyColumnFilters(ITable, String[], String[])}, so the sort
* key always matches the columns that end up compared.
*
* @param columns
* The columns to filter.
* @param excludeColumns
* The column names to exclude; null or empty to exclude none.
* @param includeColumns
* The column names to only include; null to include all.
* @param tableName
* The table name; passed only to
* {@link DefaultColumnFilter#accept(String, Column)} for its
* debug logging.
* @return The filtered columns, in columns' original order.
*/
private Column[] makeSortColumns(final Column[] columns,
final String[] excludeColumns, final String[] includeColumns,
final String tableName)
{
final DefaultColumnFilter columnFilter = new DefaultColumnFilter();
if (includeColumns != null)
{
for (final String includeColumn : includeColumns)
{
columnFilter.includeColumn(includeColumn);
}
}
if (excludeColumns != null)
{
for (final String excludeColumn : excludeColumns)
{
columnFilter.excludeColumn(excludeColumn);
}
}
final List<Column> sortColumns = new ArrayList<>();
for (final Column column : columns)
{
if (columnFilter.accept(tableName, column))
{
sortColumns.add(column);
}
}
return sortColumns.toArray(new Column[sortColumns.size()]);
}
/**
* If expected column definitions exist and are {@link DataType.UNKNOWN},
* make them from actual table column definitions.
*
* @throws DataSetException if the actual table's columns cannot be retrieved.
*/
private Column[] makeExpectedTableColumns(final Column[] actualColumns,
final ITableMetaData expectedTableMetaData) throws DataSetException
{
final Column[] expectedTableColumns;
final Column[] expectedColumns = expectedTableMetaData.getColumns();
if (expectedColumns.length > 0)
{
final DataType dataType = expectedColumns[0].getDataType();
if (DataType.UNKNOWN.equals(dataType))
{
// all column definitions probably unknown, use actual's
expectedTableColumns = makeExpectedTableColumns(actualColumns,
expectedColumns);
} else
{
// all expected column definitions probably known, use them
expectedTableColumns = expectedColumns;
}
} else
{
// no column definitions exist, so don't falsely add any
expectedTableColumns = expectedColumns;
}
return expectedTableColumns;
}
/**
* Make expected Column[] from actual table column definitions so expected
* data comparisons use data types from database (and expected data columns
* handled same as actual data in comparisons). Don't include columns from
* actual that are not in expected.
*/
private Column[] makeExpectedTableColumns(final Column[] actualColumns,
final Column[] expectedColumns)
{
final Set<String> expectedColumnNames =
Arrays.stream(expectedColumns).map(Column::getColumnName)
.map(name -> name.toLowerCase(Locale.ENGLISH))
.collect(Collectors.toSet());
final List<Column> expectedColumnsList = Arrays.stream(actualColumns)
.filter(col -> expectedColumnNames.contains(
col.getColumnName().toLowerCase(Locale.ENGLISH)))
.collect(Collectors.toList());
return expectedColumnsList
.toArray(new Column[expectedColumnsList.size()]);
}
private void logSortedTables(final SortedTable expectedSortedTable,
final SortedTable actualSortedTable)
{
if (log.isTraceEnabled())
{
logSortedTable("expectedSortedTable", expectedSortedTable);
logSortedTable("actualSortedTable", actualSortedTable);
}
}
private void logSortedTable(final String tableTypeName,
final SortedTable table)
{
final String methodName = "logSortedTable:";
final Column[] sortColumns = table.getSortColumns();
log.trace("{} {} sortColumns={}", methodName, tableTypeName,
sortColumns);
try
{
final String tableContents = tableFormatter.format(table);
log.trace("{} {} tableContents={}", methodName, tableTypeName,
tableContents);
} catch (final DataSetException e)
{
log.error("{} Error trying to log table={}", methodName,
tableTypeName, e);
}
}
/**
* Compare the tables, enables easy overriding.
* <p>
* Uses {@link #failureHandler} when set; otherwise defers to
* {@link Assertion#assertWithValueComparer(ITable, ITable, Column[], ValueComparer, Map)}'s
* own {@link org.dbunit.assertion.DefaultFailureHandler}, configured with
* additionalColumnInfo.
*
* @param expectedTable the table containing all expected results.
* @param actualTable the table containing all actual results.
* @param additionalColumnInfo the additional columns to include in failure messages.
* @param defaultValueComparer the value comparer used when no more specific comparer is configured.
* @param columnValueComparers the per-column value comparers to use.
* @throws DatabaseUnitException if the tables' row counts, columns, or data do not match.
*/
protected void compareData(final ITable expectedTable,
final ITable actualTable, final Column[] additionalColumnInfo,
final ValueComparer defaultValueComparer,
final Map<String, ValueComparer> columnValueComparers)
throws DatabaseUnitException
{
if (failureHandler == null)
{
Assertion.assertWithValueComparer(expectedTable, actualTable,
additionalColumnInfo, defaultValueComparer,
columnValueComparers);
} else
{
Assertion.assertWithValueComparer(expectedTable, actualTable,
failureHandler, defaultValueComparer,
columnValueComparers);
}
}
/**
* Don't add excluded columns to additionalColumnInfo as they are not found
* and generate a not found message in the fail message.
*
* @param expectedTable
* Not null.
* @param excludeColumns
* Nullable.
* @return the additional column info, excluding excludeColumns.
* @throws DataSetException if the expected table's columns cannot be retrieved.
*/
protected Column[] makeAdditionalColumnInfo(final ITable expectedTable,
final String[] excludeColumns) throws DataSetException
{
final Column[] allColumns =
expectedTable.getTableMetaData().getColumns();
return excludeColumns == null ? allColumns
: makeAdditionalColumnInfo(excludeColumns, allColumns);
}
/**
* Don't add excluded columns to additionalColumnInfo as they are not found
* and generate a not found message in the fail message.
*
* @param excludeColumns
* Not null.
* @param allColumns
* Not null.
* @return the additional column info, excluding excludeColumns.
*/
protected Column[] makeAdditionalColumnInfo(final String[] excludeColumns,
final Column[] allColumns)
{
final List<Column> keepColumnsList = new ArrayList<>();
final List<String> excludeColumnsList = Arrays.asList(excludeColumns);
for (final Column column : allColumns)
{
final String columnName = column.getColumnName();
if (!excludeColumnsList.contains(columnName))
{
keepColumnsList.add(column);
}
}
return keepColumnsList.toArray(new Column[keepColumnsList.size()]);
}
/**
* Make a <code>IDataSet</code> from the specified files with case sensitive
* table names as false.
*
* @param dataFiles
* Represents the array of dbUnit data files.
* @param dataFilesName
* Concept name of the data files, e.g. prep, expected.
* @return The composite dataset.
* @throws DataSetException
* On dbUnit errors.
*/
public IDataSet makeCompositeDataSet(final String[] dataFiles,
final String dataFilesName) throws DataSetException
{
return makeCompositeDataSet(dataFiles, dataFilesName, false);
}
/**
* Make a <code>IDataSet</code> from the specified files.
*
* @param dataFiles
* Represents the array of dbUnit data files.
* @param dataFilesName
* Concept name of the data files, e.g. prep, expected.
* @param isCaseSensitiveTableNames
* true if case sensitive table names is on.
* @return The composite dataset.
* @throws DataSetException
* On dbUnit errors.
*/
public IDataSet makeCompositeDataSet(final String[] dataFiles,
final String dataFilesName, final boolean isCaseSensitiveTableNames)
throws DataSetException
{
if (dataFileLoader == null)
{
throw new IllegalStateException(
"dataFileLoader is null; must configure or set it first");
}
final int count = dataFiles.length;
log.debug("makeCompositeDataSet: {} dataFiles count={}", dataFilesName,
count);
if (count == 0)
{
log.info("makeCompositeDataSet: Specified zero {} data files",
dataFilesName);
}
final List<IDataSet> list = new ArrayList<>();
for (int i = 0; i < count; i++)
{
final IDataSet ds = dataFileLoader.load(dataFiles[i]);
list.add(ds);
}
final IDataSet[] dataSet = list.toArray(new IDataSet[0]);
return new CompositeDataSet(dataSet, true, isCaseSensitiveTableNames);
}
/**
* Apply the specified exclude and include column filters to the specified
* table.
*
* @param table
* The table to apply the filters to.
* @param excludeColumns
* The exclude filters; use null or empty array to mean exclude
* none.
* @param includeColumns
* The include filters; use null to mean include all.
* @return The filtered table.
* @throws DataSetException if applying the filters fails.
*/
public ITable applyColumnFilters(final ITable table,
final String[] excludeColumns, final String[] includeColumns)
throws DataSetException
{
if (table == null)
{
throw new IllegalArgumentException("table is null");
}
ITable filteredTable = table;
// note: dbunit interprets an empty inclusion filter array as one
// not wanting to compare anything!
if (includeColumns == null)
{
log.debug("applyColumnFilters: including columns=(all)");
} else
{
log.debug("applyColumnFilters: including columns='{}'",
Arrays.toString(includeColumns));
filteredTable = DefaultColumnFilter
.includedColumnsTable(filteredTable, includeColumns);
}
if (excludeColumns == null || excludeColumns.length == 0)
{
log.debug("applyColumnFilters: excluding columns=(none)");
} else
{
log.debug("applyColumnFilters: excluding columns='{}'",
Arrays.toString(excludeColumns));
filteredTable = DefaultColumnFilter
.excludedColumnsTable(filteredTable, excludeColumns);
}
return filteredTable;
}
/**
* {@inheritDoc}
*/
@Override
public IDataSet getPrepDataset()
{
return prepDataSet;
}
/**
* {@inheritDoc}
*/
@Override
public IDataSet getExpectedDataset()
{
return expectedDataSet;
}
/**
* Get the databaseTester.
*
* @see #databaseTester
*
* @return The databaseTester.
*/
@Override
public IDatabaseTester getDatabaseTester()
{
return databaseTester;
}
/**
* Set the databaseTester.
*
* @see #databaseTester
*
* @param databaseTester
* The databaseTester to set.
*/
@Override
public void setDatabaseTester(final IDatabaseTester databaseTester)
{
this.databaseTester = databaseTester;
}
/**
* Get whether the connection lookupFeatureValue() and cleanupData() are
* done with is closed.
*
* @see #closeConnectionAfterTest
*
* @return True if it is closed, false if not.
* @since 3.4.0
*/
public boolean isCloseConnectionAfterTest()
{
return closeConnectionAfterTest;
}
/**
* Set whether the connection lookupFeatureValue() and cleanupData() are
* done with is closed. Default is true. Set to false when databaseTester
* shares a {@link org.dbunit.database.CachingConnectionProvider} across
* test methods, so this instance does not close a connection other tests
* still expect to reuse.
*
* @see #closeConnectionAfterTest
*
* @param closeConnectionAfterTest
* True to close it, false to leave it open.
* @since 3.4.0
*/
@Override
public void setCloseConnectionAfterTest(
final boolean closeConnectionAfterTest)
{
this.closeConnectionAfterTest = closeConnectionAfterTest;
}
/**
* Get the dataFileLoader.
*
* @see #dataFileLoader
*
* @return The dataFileLoader.
*/
public DataFileLoader getDataFileLoader()
{
return dataFileLoader;
}
/**
* Set the dataFileLoader.
*
* @see #dataFileLoader
*
* @param dataFileLoader
* The dataFileLoader to set.
*/
@Override
public void setDataFileLoader(final DataFileLoader dataFileLoader)
{
this.dataFileLoader = dataFileLoader;
}
/**
* Set the prepDs.
*
* @see #prepDataSet
*
* @param prepDataSet
* The prepDs to set.
*/
public void setPrepDs(final IDataSet prepDataSet)
{
this.prepDataSet = prepDataSet;
}
/**
* Set the expectedDs.
*
* @see #expectedDataSet
*
* @param expectedDataSet
* The expectedDs to set.
*/
public void setExpectedDs(final IDataSet expectedDataSet)
{
this.expectedDataSet = expectedDataSet;
}
/**
* Get the tableDefs.
*
* @see #verifyTableDefs
*
* @return The tableDefs.
*/
public VerifyTableDefinition[] getVerifyTableDefs()
{
return verifyTableDefs;
}
/**
* Set the tableDefs.
*
* @see #verifyTableDefs
*
* @param verifyTableDefs
* The tableDefs to set.
*/
public void setVerifyTableDefs(
final VerifyTableDefinition[] verifyTableDefs)
{
this.verifyTableDefs = verifyTableDefs;
}
/**
* Returns the verifier used to check that verify table definitions and the expected dataset agree.
*
* @return the verifier used to check that verify table definitions and the expected dataset agree.
*/
public ExpectedDataSetAndVerifyTableDefinitionVerifier getExpectedDataSetAndVerifyTableDefinitionVerifier()
{
return expectedDataSetAndVerifyTableDefinitionVerifier;
}
/**
* Sets the verifier used to check that verify table definitions and the expected dataset agree.
*
* @param expectedDataSetAndVerifyTableDefinitionVerifier the verifier to use.
*/
public void setExpectedDataSetAndVerifyTableDefinitionVerifier(
final ExpectedDataSetAndVerifyTableDefinitionVerifier expectedDataSetAndVerifyTableDefinitionVerifier)
{
this.expectedDataSetAndVerifyTableDefinitionVerifier =
expectedDataSetAndVerifyTableDefinitionVerifier;
}
/**
* Get the failureHandler.
*
* @see #failureHandler
*
* @return The failureHandler.
* @since 3.5.0
*/
public FailureHandler getFailureHandler()
{
return failureHandler;
}
/**
* Set the failureHandler.
*
* @see #failureHandler
*
* @param failureHandler
* The failureHandler to set.
* @since 3.5.0
*/
@Override
public void setFailureHandler(final FailureHandler failureHandler)
{
this.failureHandler = failureHandler;
}
/**
* Get the RowCountCheck in use.
*
* @see #rowCountChecker
*
* @return The RowCountCheck, or null if none has been resolved or set yet.
* @since 3.6.0
*/
public RowCountCheck getRowCountCheck()
{
return rowCountChecker.getRowCountCheck();
}
/**
* Set the RowCountCheck, overriding the one otherwise lazily built from the shared
* connection's DatabaseConfig.
*
* @see #rowCountChecker
*
* @param rowCountCheck
* The RowCountCheck to use.
* @since 3.6.0
*/
public void setRowCountCheck(final RowCountCheck rowCountCheck)
{
rowCountChecker.setRowCountCheck(rowCountCheck);
}
/**
* Set the enabled flag and excluded table patterns to resolve a RowCountCheck from, instead
* of the shared connection's DatabaseConfig - the values an annotation such as
* {@code @DbUnitRowCountCheck} declares.
*
* @see #rowCountChecker
*
* @param enabled
* Whether the check is enabled.
* @param exclude
* The excluded table patterns; null is treated as empty (excludes none).
* @since 3.6.0
*/
@Override
public void setRowCountCheckOverride(final boolean enabled, final String[] exclude)
{
rowCountChecker.setEnabledOverride(enabled, exclude);
}
/**
* Clear a previously set enabled flag and excluded table patterns override, returning to
* resolving a RowCountCheck from the shared connection's DatabaseConfig.
*
* <p>A caller reusing one instance across several tests - e.g. one held by a
* {@code @DbUnitTestCase} static field - must call this for a test that declares no
* {@code @DbUnitRowCountCheck} of its own, so an earlier test's override does not
* silently carry over onto this one.
*
* @see #rowCountChecker
* @since 3.6.0
*/
@Override
public void clearRowCountCheckOverride()
{
rowCountChecker.clearEnabledOverride();
}
/**
* Set DatabaseConfig property name/value pairs to apply to the connection shared by
* setupData(), verifyData() and cleanupData(), every time {@link #getConnection()}
* resolves it.
*
* @see #databaseConfigProperties
*
* @param databaseConfigProperties
* The properties to apply; null or empty applies none.
* @since 3.6.0
*/
@Override
public void setDatabaseConfigProperties(final Properties databaseConfigProperties)
{
if (databaseConfigProperties == null)
{
this.databaseConfigProperties = null;
} else
{
final Properties copy = new Properties();
copy.putAll(databaseConfigProperties);
this.databaseConfigProperties = copy;
}
}
/**
* {@link IDatabaseTester} that runs setUp/tearDown operations against a
* connection supplied by the given {@link Callable} instead of calling
* {@code getConnection()} on a wrapped {@link IDatabaseTester}, so
* repeated {@link #onSetup()}/{@link #onTearDown()} calls driven through
* this instance share whatever connection the supplier itself caches
* (here, {@link DefaultPrepAndExpectedTestCase#getReusableConnection()})
* rather than each opening a new one.
*
* @since 3.4.0
*/
private static final class ReusableConnectionDatabaseTester
extends AbstractDatabaseTester
{
private final Callable<IDatabaseConnection> connectionSupplier;
/**
* Create new instance with the specified connection supplier.
*
* @param connectionSupplier
* Supplies the connection to use; invoked once per
* {@link #getConnection()} call, so it is responsible for
* any caching of its own.
*/
private ReusableConnectionDatabaseTester(
final Callable<IDatabaseConnection> connectionSupplier)
{
this.connectionSupplier = connectionSupplier;
}
@Override
public IDatabaseConnection getConnection() throws Exception
{
return connectionSupplier.call();
}
}
}