/* * Syncany, www.syncany.org * Copyright (C) 2011-2015 Philipp C. Heckel <philipp.heckel@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.syncany.tests.util; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.internal.ArrayComparisonFailure; import org.syncany.chunk.Transformer; import org.syncany.database.ChunkEntry; import org.syncany.database.DatabaseConnectionFactory; import org.syncany.database.DatabaseVersion; import org.syncany.database.FileContent; import org.syncany.database.FileVersionComparator; import org.syncany.database.FileVersionComparator.FileChange; import org.syncany.database.FileVersionComparator.FileProperties; import org.syncany.database.FileVersionComparator.FileVersionComparison; import org.syncany.database.MemoryDatabase; import org.syncany.database.MultiChunkEntry; import org.syncany.database.PartialFileHistory; import org.syncany.database.VectorClock; import org.syncany.tests.unit.util.TestFileUtil; import org.syncany.util.CollectionUtil; import org.syncany.util.FileUtil; import org.syncany.util.StringUtil; import com.google.common.collect.Lists; public class TestAssertUtil { private static final Logger logger = Logger.getLogger(TestAssertUtil.class.getSimpleName()); public static void assertCollectionEquals(String message, Collection<? extends Object> expected, Collection<? extends Object> actual) { assertEquals(message + ": Different amount of objects.", expected.size(), actual.size()); int i = 0; for (Iterator<? extends Object> expectedIt = expected.iterator(), actualIt = actual.iterator(); expectedIt.hasNext() && actualIt.hasNext();) { Object expectedObj = expectedIt.next(); Object actualObj = actualIt.next(); assertEquals(message + ": actual[" + i + "] differs from expected[" + i + "]: ", expectedObj, actualObj); i++; } } public static void assertFileListEqualsExcludeLockedAndNoRead(File expectedFilesRoot, File actualFilesRoot) throws ArrayComparisonFailure, Exception { Map<String, File> expectedFiles = TestFileUtil.getLocalFilesExcludeLockedAndNoRead(expectedFilesRoot); Map<String, File> actualFiles = TestFileUtil.getLocalFilesExcludeLockedAndNoRead(actualFilesRoot); assertFileListEquals("File list does not match", expectedFiles, actualFiles); } public static void assertFileListEquals(Map<String, File> expectedFiles, Map<String, File> actualFiles) throws ArrayComparisonFailure, Exception { assertFileListEquals("File list does not match", expectedFiles, actualFiles); } public static void assertFileListEquals(String message, Map<String, File> expectedFiles, Map<String, File> actualFiles) throws ArrayComparisonFailure, Exception { assertCollectionEquals("Actual file list (" + actualFiles.size() + " entries) differs from expected file list (" + expectedFiles.size() + " entries)", expectedFiles.keySet(), actualFiles.keySet()); for (Map.Entry<String, File> expectedFileEntry : expectedFiles.entrySet()) { File expectedFile = expectedFileEntry.getValue(); File actualFile = actualFiles.remove(expectedFileEntry.getKey()); assertFileEquals(expectedFile, actualFile); } } public static void assertConflictingFileExists(String originalFile, Map<String, File> actualFiles) { boolean conflictingFileExists = conflictingFileExists(originalFile, actualFiles); assertTrue("Conflicting file for '" + originalFile + "' does NOT exist, but it should exist.", conflictingFileExists); } public static void assertConflictingFileNotExists(String originalFile, Map<String, File> actualFiles) { boolean conflictingFileExists = conflictingFileExists(originalFile, actualFiles); assertFalse("Conflicting file for '" + originalFile + "' does exist, but it should NOT exist.", conflictingFileExists); } private static boolean conflictingFileExists(String originalFile, Map<String, File> actualFiles) { String fileNameWithoutExtention = TestFileUtil.getBasename(originalFile); Pattern conflictFilePattern = Pattern.compile(fileNameWithoutExtention + ".*conflicted.*"); boolean conflictingFileFound = false; for (Map.Entry<String, File> actualFileEntry : actualFiles.entrySet()) { File actualFile = actualFileEntry.getValue(); Matcher matcher = conflictFilePattern.matcher(actualFile.getName()); if (matcher.matches()) { conflictingFileFound = true; break; } } return conflictingFileFound; } public static void assertFileEquals(File expectedFile, File actualFile) throws ArrayComparisonFailure, Exception { assertFileEquals(expectedFile, actualFile, new FileChange[] { FileChange.CHANGED_PATH }); } public static void assertFileEquals(File expectedFile, File actualFile, FileChange... allowedChanges) throws ArrayComparisonFailure, Exception { if (expectedFile == null && actualFile == null) { return; } assertNotNull("Files are not equal: Actual file is " + actualFile + ", expected file is null.", expectedFile); assertNotNull("Files are not equal: Expected file is " + expectedFile + ", actual file is null.", actualFile); Path root = Paths.get(actualFile.getAbsolutePath()).getRoot(); FileVersionComparator fileVersionComparator = new FileVersionComparator(root.toFile(), "SHA1"); if (!FileUtil.exists(expectedFile)) { fail("Files are not equal: Expected file " + expectedFile + " does not exist."); } if (!FileUtil.exists(actualFile)) { fail("Files are not equal: Actual file " + actualFile + " does not exist."); } if (FileUtil.isSymlink(actualFile) && FileUtil.isSymlink(expectedFile)) { return; } if (actualFile.isDirectory() != expectedFile.isDirectory()) { fail("Files are not equal: Comparing a directory with a file (actual is dir = " + actualFile.isDirectory() + ", expected is dir = " + expectedFile.isDirectory() + ")"); } if (actualFile.isDirectory() && expectedFile.isDirectory()) { return; } if (actualFile.length() != expectedFile.length()) { fail("Files are not equal: Actual file size (" + actualFile + " = " + actualFile.length() + ") does not match expected file size (" + expectedFile + " = " + expectedFile.length() + ")"); } byte[] expectedFileChecksum = TestFileUtil.createChecksum(expectedFile); byte[] actualFileChecksum = TestFileUtil.createChecksum(actualFile); assertArrayEquals("Files are not equal: Actual file checksum (" + StringUtil.toHex(actualFileChecksum) + ") and expected file checksum (" + StringUtil.toHex(expectedFileChecksum) + ") do not match.", expectedFileChecksum, actualFileChecksum); FileProperties actualFileProperties = fileVersionComparator.captureFileProperties(actualFile, null, true); FileProperties expectedFileProperties = fileVersionComparator.captureFileProperties(expectedFile, null, true); FileVersionComparison fileVersionComparison = fileVersionComparator.compare(expectedFileProperties, actualFileProperties, true); List<FileChange> allowedChangesList = new ArrayList<FileChange>(Arrays.asList(allowedChanges)); allowedChangesList.add(FileChange.CHANGED_PATH); if (!CollectionUtil.containsOnly(fileVersionComparison.getFileChanges(), allowedChangesList)) { fail("Files are not equal: Actual file differs from expected file: " + fileVersionComparison.getFileChanges()); } } public static void assertSqlResultEquals(File databaseFile, String sqlQuery, String expectedResultStr) throws SQLException { Connection databaseConnection = DatabaseConnectionFactory.createConnection(databaseFile, true); ResultSet resultSet = databaseConnection.prepareStatement(sqlQuery).executeQuery(); List<String> actualResultStrList = new ArrayList<String>(); while (resultSet.next()) { for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) { actualResultStrList.add(resultSet.getString(i)); } } String actualResultStr = StringUtil.join(actualResultStrList, ","); assertEquals("SQL query result differs: " + sqlQuery, expectedResultStr, actualResultStr); } public static void assertSqlDatabaseEquals(File expectedDatabaseFile, File actualDatabaseFile) throws IOException, SQLException { // Compare tables + ignore columns String[][] compareTablesAndIgnoreColumns = new String[][] { new String[] { "chunk", "DATABASEVERSION_ID" }, new String[] { "databaseversion", "ID" }, new String[] { "databaseversion_vectorclock", "DATABASEVERSION_ID" }, new String[] { "filecontent", "DATABASEVERSION_ID" }, new String[] { "filecontent_chunk" }, new String[] { "filehistory", "DATABASEVERSION_ID" }, new String[] { "fileversion", "DATABASEVERSION_ID" }, // skipped known_databases new String[] { "multichunk", "DATABASEVERSION_ID" }, new String[] { "multichunk_chunk" } }; assertSqlDatabaseTablesEqual(expectedDatabaseFile, actualDatabaseFile, compareTablesAndIgnoreColumns); } public static void assertSqlDatabaseTablesEqual(File expectedDatabaseFile, File actualDatabaseFile, String[]... compareTablesAndIgnoreColumns) throws IOException, SQLException { Connection expectedDatabaseConnection = DatabaseConnectionFactory.createConnection(expectedDatabaseFile, true); Connection actualDatabaseConnection = DatabaseConnectionFactory.createConnection(actualDatabaseFile, true); for (String[] tableNameAndIgnoreColumns : compareTablesAndIgnoreColumns) { String tableName = tableNameAndIgnoreColumns[0]; List<String> ignoreColumnNames = Lists.newArrayList(tableNameAndIgnoreColumns); ignoreColumnNames.remove(0); // Get table's primary keys List<String> primaryKeys = new ArrayList<String>(); ResultSet resultSet = actualDatabaseConnection.getMetaData().getPrimaryKeys(null, null, tableName.toUpperCase()); while (resultSet.next()) { primaryKeys.add(resultSet.getString("COLUMN_NAME")); } // Get table's columns List<String> tableColumns = new ArrayList<String>(); resultSet = actualDatabaseConnection.getMetaData().getColumns(null, null, tableName.toUpperCase(), null); while (resultSet.next()) { String columnName = resultSet.getString("COLUMN_NAME"); if (!ignoreColumnNames.contains(columnName)) { tableColumns.add(columnName); } } // Get all entries of both tables, sorted by the primary keys String columnNameList = StringUtil.join(tableColumns, ", "); String primaryKeysOrderByClause = StringUtil.join(primaryKeys, " asc, ") + " asc"; String selectQuery = String.format("select %s from %s order by %s", columnNameList, tableName, primaryKeysOrderByClause); logger.log(Level.FINE, " Comparing database table: " + selectQuery); ResultSet expectedResultSet = expectedDatabaseConnection.prepareStatement(selectQuery).executeQuery(); ResultSet actualResultSet = actualDatabaseConnection.prepareStatement(selectQuery).executeQuery(); while (true) { boolean expectedNext = expectedResultSet.next(); boolean actualNext = actualResultSet.next(); if (expectedNext && !actualNext) { fail("Table " + tableName + ": Actual is missing the following row from expected: " + getFormattedColumn(expectedResultSet)); } else if (!expectedNext && actualNext) { fail("Table " + tableName + ": Actual has a row that was not expected: " + getFormattedColumn(actualResultSet)); } else if (!expectedNext && !actualNext) { break; } else { String expectedFormattedColumn = getFormattedColumn(expectedResultSet); String actualFormattedColumn = getFormattedColumn(actualResultSet); assertEquals("Table " + tableName + ": Columns of actual and expected differ.", expectedFormattedColumn, actualFormattedColumn); } } } } private static String getFormattedColumn(ResultSet resultSet) throws SQLException { ResultSetMetaData metaData = resultSet.getMetaData(); List<String> formattedColumnLine = new ArrayList<String>(); for (int i = 0; i < metaData.getColumnCount(); i++) { formattedColumnLine.add(metaData.getColumnName(i + 1) + "=" + resultSet.getString(i + 1)); } return StringUtil.join(formattedColumnLine, ", "); } public static void assertXmlDatabaseFileEquals(File expectedDatabaseFile, File actualDatabaseFile, Transformer transformer) throws IOException { MemoryDatabase expectedDatabase = TestDatabaseUtil.readDatabaseFileFromDisk(expectedDatabaseFile, transformer); MemoryDatabase actualDatabase = TestDatabaseUtil.readDatabaseFileFromDisk(actualDatabaseFile, transformer); assertDatabaseEquals(expectedDatabase, actualDatabase); } public static void assertDatabaseEquals(MemoryDatabase expectedDatabase, MemoryDatabase actualDatabase) { logger.log(Level.INFO, "--"); logger.log(Level.INFO, "Now comparing two databases."); logger.log(Level.INFO, "DON'T WORRY. This can take a long time or even overload the heap space."); List<DatabaseVersion> writtenDatabaseVersions = expectedDatabase.getDatabaseVersions(); List<DatabaseVersion> readDatabaseVersions = actualDatabase.getDatabaseVersions(); assertEquals("Different number of database versions.", writtenDatabaseVersions.size(), readDatabaseVersions.size()); for (DatabaseVersion writtenDatabaseVersion : writtenDatabaseVersions) { DatabaseVersion readDatabaseVersion = null; for (DatabaseVersion aReadDatabaseVersion : readDatabaseVersions) { if (aReadDatabaseVersion.equals(writtenDatabaseVersion)) { readDatabaseVersion = aReadDatabaseVersion; break; } } assertNotNull("Database version " + writtenDatabaseVersion + " does not exist in read database.", readDatabaseVersion); assertDatabaseVersionEquals(writtenDatabaseVersion, readDatabaseVersion); } logger.log(Level.INFO, "End of comparing databases"); logger.log(Level.INFO, "--"); } public static void assertDatabaseVersionEquals(DatabaseVersion expectedDatabaseVersion, DatabaseVersion actualDatabaseVersion) { assertVectorClockEquals(expectedDatabaseVersion.getVectorClock(), actualDatabaseVersion.getVectorClock()); compareDatabaseVersionChunks(expectedDatabaseVersion.getChunks(), actualDatabaseVersion.getChunks()); compareDatabaseVersionMultiChunks(expectedDatabaseVersion.getMultiChunks(), actualDatabaseVersion.getMultiChunks()); compareDatabaseVersionFileContents(expectedDatabaseVersion.getFileContents(), actualDatabaseVersion.getFileContents()); compareDatabaseVersionFileHistories(expectedDatabaseVersion.getFileHistories(), actualDatabaseVersion.getFileHistories()); } public static void assertVectorClockEquals(VectorClock expectedVectorClock, VectorClock actualVectorClock) { assertEquals("Vector clocks differ.", expectedVectorClock, actualVectorClock); } private static void compareDatabaseVersionChunks(Collection<ChunkEntry> writtenChunks, Collection<ChunkEntry> readChunks) { assertEquals("Different amount of Chunk objects.", writtenChunks.size(), readChunks.size()); assertTrue("Chunk objects in written/read database version different.", writtenChunks.containsAll(readChunks)); //assertCollectionEquals("Chunk objects in written/read database version different.", writtenChunks, readChunks); } private static void compareDatabaseVersionMultiChunks(Collection<MultiChunkEntry> writtenMultiChunks, Collection<MultiChunkEntry> readMultiChunks) { assertEquals("Different amount of MultiChunk objects.", writtenMultiChunks.size(), readMultiChunks.size()); assertTrue("MultiChunk objects in written/read database version different.", writtenMultiChunks.containsAll(readMultiChunks)); //assertCollectionEquals("MultiChunk objects in written/read database version different.", writtenMultiChunks, readMultiChunks); } private static void compareDatabaseVersionFileContents(Collection<FileContent> writtenFileContents, Collection<FileContent> readFileContents) { assertEquals("Different amount of FileContent objects.", writtenFileContents.size(), readFileContents.size()); assertTrue("FileContent objects in written/read database version different.", writtenFileContents.containsAll(readFileContents)); //assertCollectionEquals("FileContent objects in written/read database version different.", writtenFileContents, readFileContents); } private static void compareDatabaseVersionFileHistories(Collection<PartialFileHistory> writtenFileHistories, Collection<PartialFileHistory> readFileHistories) { assertTrue("FileHistory objects in written/read database version different.", writtenFileHistories.containsAll(readFileHistories)); } public static void assertErrorStackTraceContains(String expectedContains, Exception e) { StringWriter errors = new StringWriter(); e.printStackTrace(new PrintWriter(errors)); String stackTrace = errors.toString(); if (stackTrace.contains(expectedContains)) { return; } e.printStackTrace(); fail("Stack trace expected to contain " + expectedContains); } public static void assertRegexInLines(String expectedLinePattern, String[] lines) { Pattern expectedPattern = Pattern.compile(expectedLinePattern); for (String line : lines) { if (expectedPattern.matcher(line).find()) { return; } } fail("Output does not contain " + expectedLinePattern); } }