diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 957b77367..6702aacf1 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -13,7 +13,7 @@ - + Add repo-root README.adoc, rendered natively by GitHub via Asciidoctor, so the repository landing page shows a pitch, build/reproducible-build badges, a pointer to the "dbUnit in 5 Minutes" tutorial, and links to the documentation site, Maven coordinates, GitHub Discussions, and CONTRIBUTING.md instead of nothing. @@ -230,6 +230,9 @@ Add MariaDB support in a new org.dbunit.ext.mariadb package, separate from org.dbunit.ext.mysql since MariaDB is its own distinct, independently-branded product (mirroring how org.dbunit.ext.netezza stays independent of org.dbunit.ext.postgresql despite Netezza's Postgres lineage): MariaDbDataTypeFactory (extends MySqlDataTypeFactory to reuse its real, verified-shared type handling) declares "mariadb" as a valid database product, silencing the "might cause problems with the current database" warning, and recognizes MariaDB's native UUID (10.7+) and INET4/INET6 (10.10+) column types, which MariaDB Connector/J reports as SQL type OTHER with no MySQL equivalent; MariaDB's JSON type is a LONGTEXT alias and already worked via the inherited longtext handling. Add a mariadb-11-4 Maven profile and Docker-backed IT suite mirroring the existing mysql-9-20 profile, and a dedicated databases/mariadb.adoc site page (with its own navigation entry) instead of folding coverage into the MySQL page. Along the way, found and documented that MariaDB Connector/J, unlike MySQL Connector/J, has no nullCatalogMeansCurrent-equivalent default: an unfiltered DatabaseMetaData#getTables() call leaks information_schema/performance_schema tables into dbUnit's table map, surfacing as a SQLSyntaxErrorException the moment an operation like DELETE_ALL touches one of them, unless MySqlMetadataHandler is registered or nullCatalogMeansCurrent=true is added to the JDBC URL. + + Add DefaultPrepAndExpectedTestCase.setFailureHandler(FailureHandler), letting verifyData() use a caller-supplied FailureHandler such as DiffCollectingFailureHandler instead of the default fail-fast DefaultFailureHandler; unset (null) keeps the pre-existing default behavior unchanged. + diff --git a/src/main/java/org/dbunit/DefaultPrepAndExpectedTestCase.java b/src/main/java/org/dbunit/DefaultPrepAndExpectedTestCase.java index 8ec4913a7..16fb69502 100644 --- a/src/main/java/org/dbunit/DefaultPrepAndExpectedTestCase.java +++ b/src/main/java/org/dbunit/DefaultPrepAndExpectedTestCase.java @@ -30,6 +30,7 @@ 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; @@ -67,6 +68,13 @@ * 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. + *

+ * 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" @@ -127,6 +135,22 @@ public class DefaultPrepAndExpectedTestCase extends DBTestCase 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.4.1 + */ + private FailureHandler failureHandler; + final TableFormatter tableFormatter = new TableFormatter(); /** Create new instance. */ @@ -1067,6 +1091,11 @@ private void logSortedTable(final String tableTypeName, /** * Compare the tables, enables easy overriding. + *

+ * 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. @@ -1081,9 +1110,17 @@ protected void compareData(final ITable expectedTable, final Map columnValueComparers) throws DatabaseUnitException { - Assertion.assertWithValueComparer(expectedTable, actualTable, - additionalColumnInfo, defaultValueComparer, - columnValueComparers); + if (failureHandler == null) + { + Assertion.assertWithValueComparer(expectedTable, actualTable, + additionalColumnInfo, defaultValueComparer, + columnValueComparers); + } else + { + Assertion.assertWithValueComparer(expectedTable, actualTable, + failureHandler, defaultValueComparer, + columnValueComparers); + } } /** @@ -1424,6 +1461,33 @@ public void setExpectedDataSetAndVerifyTableDefinitionVerifier( expectedDataSetAndVerifyTableDefinitionVerifier; } + /** + * Get the failureHandler. + * + * @see #failureHandler + * + * @return The failureHandler. + * @since 3.4.1 + */ + public FailureHandler getFailureHandler() + { + return failureHandler; + } + + /** + * Set the failureHandler. + * + * @see #failureHandler + * + * @param failureHandler + * The failureHandler to set. + * @since 3.4.1 + */ + public void setFailureHandler(final FailureHandler failureHandler) + { + this.failureHandler = failureHandler; + } + /** * {@link IDatabaseTester} that runs setUp/tearDown operations against a * connection supplied by the given {@link Callable} instead of calling diff --git a/src/test/java/org/dbunit/DefaultPrepAndExpectedTestCaseTest.java b/src/test/java/org/dbunit/DefaultPrepAndExpectedTestCaseTest.java index 431bd65e9..0f10054e4 100644 --- a/src/test/java/org/dbunit/DefaultPrepAndExpectedTestCaseTest.java +++ b/src/test/java/org/dbunit/DefaultPrepAndExpectedTestCaseTest.java @@ -7,6 +7,7 @@ import java.sql.Connection; import org.dbunit.assertion.DbComparisonFailure; +import org.dbunit.assertion.DiffCollectingFailureHandler; import org.dbunit.database.DatabaseConfig; import org.dbunit.database.IDatabaseConnection; import org.dbunit.database.MockDatabaseConnection; @@ -515,6 +516,73 @@ void testVerifyData_withTurkishDefaultLocale_matchesAsciiIColumns() .doesNotThrowAnyException(); } + @Test + void testGetFailureHandler_withDefaultConfiguration_returnsNull() + { + assertThat(tc.getFailureHandler()) + .as("Default must be null so verifyData() keeps using" + + " Assertion's own default FailureHandler, matching" + + " pre-existing behavior for callers who have not" + + " configured a custom FailureHandler.") + .isNull(); + } + + @Test + void testVerifyData_withMismatchAndNoFailureHandlerConfigured_throwsError() + throws Exception + { + final Column[] columns = {new Column("COL1", DataType.VARCHAR)}; + + final DefaultTable expectedTable = + new DefaultTable("TEST_TABLE", columns); + expectedTable.addRow(new Object[] {"expected"}); + + final DefaultTable actualTable = + new DefaultTable("TEST_TABLE", columns); + actualTable.addRow(new Object[] {"actual"}); + + final Throwable thrown = catchThrowable(() -> tc.verifyData( + expectedTable, actualTable, null, null, null, null)); + + assertThat(thrown) + .as("Without a configured FailureHandler, verifyData() must" + + " keep failing fast on the first mismatch, matching" + + " pre-existing behavior.") + .isInstanceOf(DbComparisonFailure.class); + } + + @Test + void testVerifyData_withMismatchAndDiffCollectingFailureHandlerConfigured_collectsDifferenceInsteadOfThrowing() + throws Exception + { + final Column[] columns = {new Column("COL1", DataType.VARCHAR)}; + + final DefaultTable expectedTable = + new DefaultTable("TEST_TABLE", columns); + expectedTable.addRow(new Object[] {"expected"}); + + final DefaultTable actualTable = + new DefaultTable("TEST_TABLE", columns); + actualTable.addRow(new Object[] {"actual"}); + + final DiffCollectingFailureHandler diffCollectingFailureHandler = + new DiffCollectingFailureHandler(); + tc.setFailureHandler(diffCollectingFailureHandler); + + assertThatCode(() -> tc.verifyData(expectedTable, actualTable, null, + null, null, null)) + .as("A configured FailureHandler that collects" + + " differences instead of throwing must be" + + " used instead of the default fail-fast" + + " handler.") + .doesNotThrowAnyException(); + + assertThat(diffCollectingFailureHandler.getDiffList()) + .as("The mismatch must be recorded by the configured" + + " DiffCollectingFailureHandler.") + .hasSize(1); + } + @Test void testCleanupData_withDeleteAllTearDownOperation_executesTearDownOperation() throws Exception