View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2002-2008, DbUnit.org
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2.1 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   *
20   */
21  package org.dbunit.assertion;
22  
23  import java.sql.SQLException;
24  
25  import org.dbunit.Assertion;
26  import org.dbunit.DatabaseUnitException;
27  import org.dbunit.database.IDatabaseConnection;
28  import org.dbunit.dataset.Column;
29  import org.dbunit.dataset.Columns;
30  import org.dbunit.dataset.DataSetException;
31  import org.dbunit.dataset.IDataSet;
32  import org.dbunit.dataset.ITable;
33  import org.dbunit.dataset.ITableMetaData;
34  import org.dbunit.dataset.datatype.DataType;
35  import org.dbunit.dataset.datatype.UnknownDataType;
36  import org.dbunit.dataset.filter.DefaultColumnFilter;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  /**
41   * Default implementation of DbUnit assertions, based on the original methods
42   * present at {@link Assertion}.
43   *
44   * All are equality comparisons.
45   *
46   * @author Felipe Leme (dbunit@felipeal.net)
47   * @author gommma (gommma AT users.sourceforge.net)
48   * @version $Revision$ $Date$
49   * @since 2.4.0
50   */
51  public class DbUnitAssert extends DbUnitAssertBase
52  {
53      private static final Logger logger =
54              LoggerFactory.getLogger(DbUnitAssert.class);
55  
56      /**
57       * Compare one table present in two datasets ignoring specified columns.
58       *
59       * @param expectedDataset
60       *            First dataset.
61       * @param actualDataset
62       *            Second dataset.
63       * @param tableName
64       *            Table name of the table to be compared.
65       * @param ignoreCols
66       *            Columns to be ignored in comparison.
67       * @throws org.dbunit.DatabaseUnitException
68       *             If an error occurs.
69       */
70      public void assertEqualsIgnoreCols(final IDataSet expectedDataset,
71              final IDataSet actualDataset, final String tableName,
72              final String[] ignoreCols) throws DatabaseUnitException
73      {
74          logger.debug(
75                  "assertEqualsIgnoreCols(expectedDataset={}, actualDataset={}, tableName={}, ignoreCols={}) - start",
76                  expectedDataset, actualDataset, tableName, ignoreCols);
77  
78          assertEqualsIgnoreCols(expectedDataset.getTable(tableName),
79                  actualDataset.getTable(tableName), ignoreCols);
80      }
81  
82      /**
83       * Compare the given tables ignoring specified columns.
84       *
85       * @param expectedTable
86       *            First table.
87       * @param actualTable
88       *            Second table.
89       * @param ignoreCols
90       *            Columns to be ignored in comparison.
91       * @throws org.dbunit.DatabaseUnitException
92       *             If an error occurs.
93       */
94      public void assertEqualsIgnoreCols(final ITable expectedTable,
95              final ITable actualTable, final String[] ignoreCols)
96              throws DatabaseUnitException
97      {
98          logger.debug(
99                  "assertEqualsIgnoreCols(expectedTable={}, actualTable={}, ignoreCols={}) - start",
100                 expectedTable, actualTable, ignoreCols);
101 
102         final ITable expectedTableFiltered = DefaultColumnFilter
103                 .excludedColumnsTable(expectedTable, ignoreCols);
104         final ITable actualTableFiltered = DefaultColumnFilter
105                 .excludedColumnsTable(actualTable, ignoreCols);
106         assertEquals(expectedTableFiltered, actualTableFiltered);
107     }
108 
109     /**
110      * Compare a table from a dataset with a table generated from an sql query.
111      *
112      * @param expectedDataset
113      *            Dataset to retrieve the first table from.
114      * @param connection
115      *            Connection to use for the SQL statement.
116      * @param sqlQuery
117      *            SQL query that will build the data in returned second table
118      *            rows.
119      * @param tableName
120      *            Table name of the table to compare.
121      * @param ignoreCols
122      *            Columns to be ignored in comparison.
123      * @throws DatabaseUnitException
124      *             If an error occurs while performing the comparison.
125      * @throws java.sql.SQLException
126      *             If an SQL error occurs.
127      */
128     public void assertEqualsByQuery(final IDataSet expectedDataset,
129             final IDatabaseConnection connection, final String sqlQuery,
130             final String tableName, final String[] ignoreCols)
131             throws DatabaseUnitException, SQLException
132     {
133         logger.debug(
134                 "assertEqualsByQuery(expectedDataset={}, connection={}, tableName={}, sqlQuery={}, ignoreCols={}) - start",
135                 expectedDataset, connection, tableName, sqlQuery, ignoreCols);
136 
137         final ITable expectedTable = expectedDataset.getTable(tableName);
138         assertEqualsByQuery(expectedTable, connection, tableName, sqlQuery,
139                 ignoreCols);
140     }
141 
142     /**
143      * Compare a table with a table generated from an sql query.
144      *
145      * @param expectedTable
146      *            Table containing all expected results.
147      * @param connection
148      *            Connection to use for the SQL statement.
149      * @param tableName
150      *            The name of the table to query from the database.
151      * @param sqlQuery
152      *            SQL query that will build the data in returned second table
153      *            rows.
154      * @param ignoreCols
155      *            Columns to be ignored in comparison.
156      * @throws DatabaseUnitException
157      *             If an error occurs while performing the comparison.
158      * @throws java.sql.SQLException
159      *             If an SQL error occurs.
160      */
161     public void assertEqualsByQuery(final ITable expectedTable,
162             final IDatabaseConnection connection, final String tableName,
163             final String sqlQuery, final String[] ignoreCols)
164             throws DatabaseUnitException, SQLException
165     {
166         logger.debug(
167                 "assertEqualsByQuery(expectedTable={}, connection={}, tableName={}, sqlQuery={}, ignoreCols={}) - start",
168                 expectedTable, connection, tableName, sqlQuery, ignoreCols);
169 
170         final ITable expected = DefaultColumnFilter
171                 .excludedColumnsTable(expectedTable, ignoreCols);
172         final ITable queriedTable =
173                 connection.createQueryTable(tableName, sqlQuery);
174         final ITable actual = DefaultColumnFilter
175                 .excludedColumnsTable(queriedTable, ignoreCols);
176         assertEquals(expected, actual);
177     }
178 
179     /**
180      * Asserts that the two specified dataset are equals. This method ignore the
181      * tables order.
182      */
183     public void assertEquals(final IDataSet expectedDataSet,
184             final IDataSet actualDataSet) throws DatabaseUnitException
185     {
186         logger.debug(
187                 "assertEquals(expectedDataSet={}, actualDataSet={}) - start",
188                 expectedDataSet, actualDataSet);
189         assertEquals(expectedDataSet, actualDataSet, null);
190     }
191 
192     /**
193      * Asserts that the two specified dataset are equals. This method ignore the
194      * tables order.
195      *
196      * @since 2.4
197      */
198     public void assertEquals(final IDataSet expectedDataSet,
199             final IDataSet actualDataSet, final FailureHandler failureHandler)
200             throws DatabaseUnitException
201     {
202         logger.debug(
203                 "assertEquals(expectedDataSet={}, actualDataSet={}, failureHandler={}) - start",
204                 expectedDataSet, actualDataSet, failureHandler);
205 
206         // do not continue if same instance
207         if (expectedDataSet == actualDataSet)
208         {
209             logger.debug("The given datasets reference the same object."
210                     + " Skipping comparisons.");
211             return;
212         }
213 
214         final FailureHandler validFailureHandler =
215                 determineFailureHandler(failureHandler);
216 
217         final String[] expectedNames = getSortedTableNames(expectedDataSet);
218         final String[] actualNames = getSortedTableNames(actualDataSet);
219 
220         compareTableCounts(expectedNames, actualNames, validFailureHandler);
221 
222         // table names in no specific order
223         compareTableNames(expectedNames, actualNames, validFailureHandler);
224 
225         compareTables(expectedDataSet, actualDataSet, expectedNames,
226                 validFailureHandler);
227     }
228 
229     protected void compareTables(final IDataSet expectedDataSet,
230             final IDataSet actualDataSet, final String[] expectedNames,
231             final FailureHandler failureHandler) throws DatabaseUnitException
232     {
233         for (int i = 0; i < expectedNames.length; i++)
234         {
235             final String tableName = expectedNames[i];
236 
237             final ITable expectedTable = expectedDataSet.getTable(tableName);
238             final ITable actualTable = actualDataSet.getTable(tableName);
239 
240             assertEquals(expectedTable, actualTable, failureHandler);
241         }
242     }
243 
244     /**
245      * Asserts that the two specified tables are equals. This method ignores the
246      * table names, the columns order, the columns data type and which columns
247      * are composing the primary keys.
248      *
249      * @param expectedTable
250      *            Table containing all expected results.
251      * @param actualTable
252      *            Table containing all actual results.
253      * @throws DatabaseUnitException
254      */
255     public void assertEquals(final ITable expectedTable,
256             final ITable actualTable) throws DatabaseUnitException
257     {
258         logger.debug("assertEquals(expectedTable={}, actualTable={}) - start",
259                 expectedTable, actualTable);
260         assertEquals(expectedTable, actualTable, (Column[]) null);
261     }
262 
263     /**
264      * Asserts that the two specified tables are equals. This method ignores the
265      * table names, the columns order, the columns data type and which columns
266      * are composing the primary keys. <br />
267      * Example: <code><pre>
268      * ITable actualTable = ...;
269      * ITable expectedTable = ...;
270      * ITableMetaData metaData = actualTable.getTableMetaData();
271      * Column[] additionalInfoCols = Columns.getColumns(new String[] {"MY_PK_COLUMN"}, metaData.getColumns());
272      * assertEquals(expectedTable, actualTable, additionalInfoCols);
273      * </pre></code>
274      *
275      * @param expectedTable
276      *            Table containing all expected results.
277      * @param actualTable
278      *            Table containing all actual results.
279      * @param additionalColumnInfo
280      *            The columns to be printed out if the assert fails because of a
281      *            data mismatch. Provides some additional column values that may
282      *            be useful to quickly identify the columns for which the
283      *            mismatch occurred (for example a primary key column). Can be
284      *            <code>null</code>.
285      * @throws DatabaseUnitException
286      */
287     public void assertEquals(final ITable expectedTable,
288             final ITable actualTable, final Column[] additionalColumnInfo)
289             throws DatabaseUnitException
290     {
291         logger.debug(
292                 "assertEquals(expectedTable={}, actualTable={}, additionalColumnInfo={}) - start",
293                 expectedTable, actualTable, additionalColumnInfo);
294 
295         FailureHandler failureHandler = null;
296         if (additionalColumnInfo != null)
297         {
298             failureHandler = getDefaultFailureHandler(additionalColumnInfo);
299         }
300 
301         assertEquals(expectedTable, actualTable, failureHandler);
302     }
303 
304     /**
305      * Asserts that the two specified tables are equals. This method ignores the
306      * table names, the columns order, the columns data type and which columns
307      * are composing the primary keys. <br />
308      * Example: <code><pre>
309      * ITable actualTable = ...;
310      * ITable expectedTable = ...;
311      * ITableMetaData metaData = actualTable.getTableMetaData();
312      * FailureHandler failureHandler = new DefaultFailureHandler();
313      * assertEquals(expectedTable, actualTable, failureHandler);
314      * </pre></code>
315      *
316      * @param expectedTable
317      *            Table containing all expected results.
318      * @param actualTable
319      *            Table containing all actual results.
320      * @param failureHandler
321      *            The failure handler used if the assert fails because of a data
322      *            mismatch. Provides some additional information that may be
323      *            useful to quickly identify the rows for which the mismatch
324      *            occurred (for example by printing an additional primary key
325      *            column). Can be <code>null</code>.
326      * @throws DatabaseUnitException
327      * @since 2.4
328      */
329     public void assertEquals(final ITable expectedTable,
330             final ITable actualTable, final FailureHandler failureHandler)
331             throws DatabaseUnitException
332     {
333         logger.trace(
334                 "assertEquals(expectedTable, actualTable, failureHandler) - start");
335         logger.debug("assertEquals: expectedTable={}", expectedTable);
336         logger.debug("assertEquals: actualTable={}", actualTable);
337         logger.debug("assertEquals: failureHandler={}", failureHandler);
338 
339         // Do not continue if same instance
340         if (expectedTable == actualTable)
341         {
342             logger.debug("The given tables reference the same object."
343                     + " Skipping comparisons.");
344             return;
345         }
346 
347         final FailureHandler validFailureHandler =
348                 determineFailureHandler(failureHandler);
349 
350         final ITableMetaData expectedMetaData =
351                 expectedTable.getTableMetaData();
352         final ITableMetaData actualMetaData = actualTable.getTableMetaData();
353         final String expectedTableName = expectedMetaData.getTableName();
354 
355         final boolean isTablesEmpty = compareRowCounts(expectedTable,
356                 actualTable, validFailureHandler, expectedTableName);
357         if (isTablesEmpty)
358         {
359             return;
360         }
361 
362         // Put the columns into the same order
363         final Column[] expectedColumns =
364                 Columns.getSortedColumns(expectedMetaData);
365         final Column[] actualColumns = Columns.getSortedColumns(actualMetaData);
366 
367         compareColumns(expectedColumns, actualColumns, expectedMetaData,
368                 actualMetaData, validFailureHandler);
369 
370         // Get the datatypes to be used for comparing the sorted columns
371         final ComparisonColumn[] comparisonCols =
372                 getComparisonColumns(expectedTableName, expectedColumns,
373                         actualColumns, validFailureHandler);
374 
375         // Finally compare the data
376         compareData(expectedTable, actualTable, comparisonCols,
377                 validFailureHandler);
378     }
379 
380     /**
381      * @param expectedTable
382      *            Table containing all expected results.
383      * @param actualTable
384      *            Table containing all actual results.
385      * @param comparisonCols
386      *            The columns to be compared, also including the correct
387      *            {@link DataType}s for comparison
388      * @param failureHandler
389      *            The failure handler used if the assert fails because of a data
390      *            mismatch. Provides some additional information that may be
391      *            useful to quickly identify the rows for which the mismatch
392      *            occurred (for example by printing an additional primary key
393      *            column). Must not be <code>null</code> at this stage
394      * @throws DataSetException
395      * @since 2.4
396      */
397     protected void compareData(final ITable expectedTable,
398             final ITable actualTable, final ComparisonColumn[] comparisonCols,
399             final FailureHandler failureHandler) throws DataSetException
400     {
401         logger.debug(
402                 "compareData(expectedTable={}, actualTable={}, "
403                         + "comparisonCols={}, failureHandler={}) - start",
404                 expectedTable, actualTable, comparisonCols, failureHandler);
405 
406         if (expectedTable == null)
407         {
408             throw new NullPointerException(
409                     "The parameter 'expectedTable' must not be null");
410         }
411         if (actualTable == null)
412         {
413             throw new NullPointerException(
414                     "The parameter 'actualTable' must not be null");
415         }
416         if (comparisonCols == null)
417         {
418             throw new NullPointerException(
419                     "The parameter 'comparisonCols' must not be null");
420         }
421         if (failureHandler == null)
422         {
423             throw new NullPointerException(
424                     "The parameter 'failureHandler' must not be null");
425         }
426 
427         // iterate over all rows
428         for (int i = 0; i < expectedTable.getRowCount(); i++)
429         {
430             // iterate over all columns of the current row
431             for (int j = 0; j < comparisonCols.length; j++)
432             {
433                 final ComparisonColumn compareColumn = comparisonCols[j];
434 
435                 final String columnName = compareColumn.getColumnName();
436                 final DataType dataType = compareColumn.getDataType();
437 
438                 final Object expectedValue =
439                         expectedTable.getValue(i, columnName);
440                 final Object actualValue = actualTable.getValue(i, columnName);
441 
442                 // Compare the values
443                 if (skipCompare(columnName, expectedValue, actualValue))
444                 {
445                     logger.trace(
446                             "skipCompare: ignoring comparison"
447                                     + " {}={} on column={}",
448                             expectedValue, actualValue, columnName);
449                     continue;
450                 }
451 
452                 if (dataType.compare(expectedValue, actualValue) != 0)
453                 {
454                     final Difference diff =
455                             new Difference(expectedTable, actualTable, i,
456                                     columnName, expectedValue, actualValue);
457 
458                     failureHandler.handle(diff);
459                 }
460             }
461         }
462     }
463 
464     /**
465      * Represents a single column to be used for the comparison of table data.
466      * It contains the {@link DataType} to be used for comparing the given
467      * column. This {@link DataType} matches the expected and actual column's
468      * datatype.
469      *
470      * @author gommma (gommma AT users.sourceforge.net)
471      * @author Last changed by: $Author: gommma $
472      * @version $Revision: 864 $ $Date: 2008-11-07 06:27:26 -0800 (Fri, 07 Nov
473      *          2008) $
474      * @since 2.4.0
475      */
476     public static class ComparisonColumn
477     {
478         private static final Logger logger =
479                 LoggerFactory.getLogger(ComparisonColumn.class);
480 
481         private String columnName;
482         private DataType dataType;
483 
484         /**
485          * @param tableName
486          *            The table name which is only needed for debugging output.
487          * @param expectedColumn
488          *            The expected column needed to resolve the {@link DataType}
489          *            to use for the actual comparison.
490          * @param actualColumn
491          *            The actual column needed to resolve the {@link DataType}
492          *            to use for the actual comparison.
493          * @param failureHandler
494          *            The {@link FailureHandler} to be used when no datatype can
495          *            be determined.
496          */
497         public ComparisonColumn(final String tableName,
498                 final Column expectedColumn, final Column actualColumn,
499                 final FailureHandler failureHandler)
500         {
501             this.columnName = expectedColumn.getColumnName();
502             this.dataType = getComparisonDataType(tableName, expectedColumn,
503                     actualColumn, failureHandler);
504         }
505 
506         /**
507          * @return The column actually being compared.
508          */
509         public String getColumnName()
510         {
511             return this.columnName;
512         }
513 
514         /**
515          * @return The {@link DataType} to use for the actual comparison.
516          */
517         public DataType getDataType()
518         {
519             return this.dataType;
520         }
521 
522         /**
523          * @param tableName
524          *            The table name which is only needed for debugging output.
525          * @param expectedColumn
526          * @param actualColumn
527          * @param failureHandler
528          *            The {@link FailureHandler} to be used when no datatype can
529          *            be determined.
530          * @return The dbunit {@link DataType} to use for comparing the given
531          *         column.
532          */
533         private DataType getComparisonDataType(final String tableName,
534                 final Column expectedColumn, final Column actualColumn,
535                 final FailureHandler failureHandler)
536         {
537             logger.debug(
538                     "getComparisonDataType(tableName={}, expectedColumn={}, actualColumn={}, failureHandler={}) - start",
539                     tableName, expectedColumn, actualColumn, failureHandler);
540 
541             final DataType expectedDataType = expectedColumn.getDataType();
542             final DataType actualDataType = actualColumn.getDataType();
543 
544             // The two columns have different data type
545             if (!expectedDataType.getClass().isInstance(actualDataType))
546             {
547                 // Expected column data type is unknown, use actual column data
548                 // type
549                 if (expectedDataType instanceof UnknownDataType)
550                 {
551                     return actualDataType;
552                 }
553 
554                 // Actual column data type is unknown, use expected column data
555                 // type
556                 if (actualDataType instanceof UnknownDataType)
557                 {
558                     return expectedDataType;
559                 }
560 
561                 // Impossible to determine which data type to use
562                 final String msg = "Incompatible data types: (table="
563                         + tableName + ", col=" + expectedColumn.getColumnName()
564                         + ")";
565                 throw failureHandler.createFailure(msg,
566                         String.valueOf(expectedDataType),
567                         String.valueOf(actualDataType));
568             }
569 
570             // Both columns have same data type, return any one of them
571             return expectedDataType;
572         }
573     }
574 }