MultiDataSourceAssertionError.java
/*
*
* The DbUnit Database Testing Framework
* Copyright (C)2002-2026, 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.List;
/**
* Aggregates the {@link PrepAndExpectedTestCase#postTest(boolean)} failures a
* {@link MultiDataSourcePrepAndExpectedTestCase} collects across every involved data source,
* instead of stopping at the first one.
* <p>
* Extends {@link AssertionError} - not a checked exception, and not
* {@code org.opentest4j.MultipleFailuresError} - so a caller that already
* {@code assertThrows(AssertionError.class, ...)} around a single data source's
* {@link org.dbunit.assertion.DbComparisonFailure} keeps working whether one data source fails or
* several, and this class stays usable from a non-JUnit {@link PrepAndExpectedTestCase} delegate.
* <p>
* The first failure - in the wired data sources' declared order, not necessarily the reverse
* order they were torn down in - becomes this instance's {@linkplain #getCause() cause}, so it
* still surfaces one {@code Caused by:} hop below this instance, exactly as thrown by its
* delegate. Every other failure is {@linkplain #addSuppressed(Throwable) added as suppressed},
* labelled with its data source name and lifecycle phase so it reads without unwrapping.
*
* @author Jeff Jensen
* @since 3.6.0
*/
public class MultiDataSourceAssertionError extends AssertionError
{
private static final long serialVersionUID = 1L;
private MultiDataSourceAssertionError(final String message, final Throwable cause)
{
super(message, cause);
}
/**
* Builds the aggregate from one or more {@code postTest} failures: names every failing data
* source and its phase in the message, uses the first failure as this instance's cause, and
* attaches the rest as labelled suppressed exceptions.
*
* @param namedFailures
* The failures to aggregate, in the order they should be named and reported; not
* empty.
* @return The aggregate error.
*/
static MultiDataSourceAssertionError aggregate(final List<NamedFailure> namedFailures)
{
final int failureCount = namedFailures.size();
final String message = makeMessage(failureCount, namedFailures);
return makeAggregateError(failureCount, message, namedFailures);
}
private static String makeMessage(final int failureCount, final List<NamedFailure> namedFailures)
{
final StringBuilder message = new StringBuilder();
appendCountPrefix(failureCount, message);
appendFailureDetails(failureCount, message, namedFailures);
return message.toString();
}
private static void appendCountPrefix(final int failureCount, final StringBuilder message)
{
final String plurality = failureCount == 1 ? "" : "s";
message.append(failureCount);
message.append(" data source");
message.append(plurality);
message.append(" failed in postTest: ");
}
private static void appendFailureDetails(final int failureCount, final StringBuilder message, final List<NamedFailure> namedFailures)
{
for (int i = 0; i < failureCount; i++)
{
if (i > 0)
{
message.append(", ");
}
final NamedFailure namedFailure = namedFailures.get(i);
message.append(namedFailure.dataSourceName);
message.append(" (");
message.append(namedFailure.phase);
message.append(')');
}
}
private static MultiDataSourceAssertionError makeAggregateError(final int failureCount, final String message, final List<NamedFailure> namedFailures)
{
final NamedFailure first = namedFailures.get(0);
final MultiDataSourceAssertionError aggregateError =
new MultiDataSourceAssertionError(message, first.failure);
for (int i = 1; i < failureCount; i++)
{
final NamedFailure other = namedFailures.get(i);
final DataSourceFailure dataSourceFailure =
new DataSourceFailure(other.dataSourceName, other.phase, other.failure);
aggregateError.addSuppressed(dataSourceFailure);
}
return aggregateError;
}
/**
* One data source's {@link PrepAndExpectedTestCase#postTest(boolean)} failure, paired with
* the data source name and lifecycle phase {@link #aggregate(List)} reports it under.
*/
static class NamedFailure
{
private final String dataSourceName;
private final String phase;
private final Throwable failure;
NamedFailure(final String dataSourceName, final String phase, final Throwable failure)
{
this.dataSourceName = dataSourceName;
this.phase = phase;
this.failure = failure;
}
}
/**
* Labels a data source's failure with its name and lifecycle phase, so an entry this class
* adds via {@link Throwable#addSuppressed(Throwable)} reads without unwrapping.
*/
static class DataSourceFailure extends RuntimeException
{
private static final long serialVersionUID = 1L;
DataSourceFailure(final String dataSourceName, final String phase, final Throwable cause)
{
super(dataSourceName + " failed in " + phase + ".", cause);
}
}
}