diff options
author | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2017-02-15 18:18:27 +0100 |
---|---|---|
committer | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2017-02-17 13:31:49 +0100 |
commit | 9824b94f16109305b4847a611379029695823427 (patch) | |
tree | 85b339103ce4c56df78e3e26f21117da7fb04976 /server/sonar-db-core/src/test/java | |
parent | 0d3be9943570cf5bfb7fbb72fdfe5a6fcb2798c0 (diff) | |
download | sonarqube-9824b94f16109305b4847a611379029695823427.tar.gz sonarqube-9824b94f16109305b4847a611379029695823427.zip |
split sonar-db into server/sonar-db-core and server/sonar-db-dao
Diffstat (limited to 'server/sonar-db-core/src/test/java')
21 files changed, 2769 insertions, 0 deletions
diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java b/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java new file mode 100644 index 00000000000..5777e7cb974 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java @@ -0,0 +1,601 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import java.io.InputStream; +import java.math.BigDecimal; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.apache.commons.dbutils.QueryRunner; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.dbunit.Assertion; +import org.dbunit.DatabaseUnitException; +import org.dbunit.assertion.DiffCollectingFailureHandler; +import org.dbunit.assertion.Difference; +import org.dbunit.database.DatabaseConfig; +import org.dbunit.database.IDatabaseConnection; +import org.dbunit.dataset.CompositeDataSet; +import org.dbunit.dataset.IDataSet; +import org.dbunit.dataset.ITable; +import org.dbunit.dataset.ReplacementDataSet; +import org.dbunit.dataset.filter.DefaultColumnFilter; +import org.dbunit.dataset.xml.FlatXmlDataSet; +import org.dbunit.ext.mssql.InsertIdentityOperation; +import org.dbunit.operation.DatabaseOperation; +import org.junit.rules.ExternalResource; +import org.sonar.api.utils.log.Loggers; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Lists.asList; +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Maps.newHashMap; +import static java.sql.ResultSetMetaData.columnNoNulls; +import static java.sql.ResultSetMetaData.columnNullable; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class AbstractDbTester<T extends CoreTestDb> extends ExternalResource { + protected static final Joiner COMMA_JOINER = Joiner.on(", "); + protected final T db; + + public AbstractDbTester(T db) { + this.db = db; + } + + public void executeUpdateSql(String sql, Object... params) { + try (Connection connection = getConnection()) { + new QueryRunner().update(connection, sql, params); + if (!connection.getAutoCommit()) { + connection.commit(); + } + } catch (SQLException e) { + SQLException nextException = e.getNextException(); + if (nextException != null) { + throw new IllegalStateException("Fail to execute sql: " + sql, + new SQLException(e.getMessage(), nextException.getSQLState(), nextException.getErrorCode(), nextException)); + } + throw new IllegalStateException("Fail to execute sql: " + sql, e); + } catch (Exception e) { + throw new IllegalStateException("Fail to execute sql: " + sql, e); + } + } + + public void executeDdl(String ddl) { + try (Connection connection = getConnection(); + Statement stmt = connection.createStatement()) { + stmt.execute(ddl); + } catch (SQLException e) { + throw new IllegalStateException("Failed to execute DDL: " + ddl, e); + } + } + + /** + * Very simple helper method to insert some data into a table. + * It's the responsibility of the caller to convert column values to string. + */ + public void executeInsert(String table, String firstColumn, Object... others) { + executeInsert(table, mapOf(firstColumn, others)); + } + + private static Map<String, Object> mapOf(String firstColumn, Object... values) { + ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder(); + List<Object> args = asList(firstColumn, values); + for (int i = 0; i < args.size(); i++) { + String key = args.get(i).toString(); + Object value = args.get(i + 1); + if (value != null) { + builder.put(key, value); + } + i++; + } + return builder.build(); + } + + /** + * Very simple helper method to insert some data into a table. + * It's the responsibility of the caller to convert column values to string. + */ + public void executeInsert(String table, Map<String, Object> valuesByColumn) { + if (valuesByColumn.isEmpty()) { + throw new IllegalArgumentException("Values cannot be empty"); + } + + String sql = "insert into " + table.toLowerCase(Locale.ENGLISH) + " (" + + COMMA_JOINER.join(valuesByColumn.keySet()) + + ") values (" + + COMMA_JOINER.join(Collections.nCopies(valuesByColumn.size(), '?')) + + ")"; + executeUpdateSql(sql, valuesByColumn.values().toArray(new Object[valuesByColumn.size()])); + } + + /** + * Returns the number of rows in the table. Example: + * <pre>int issues = countRowsOfTable("issues")</pre> + */ + public int countRowsOfTable(String tableName) { + return countRowsOfTable(tableName, new NewConnectionSupplier()); + } + + protected int countRowsOfTable(String tableName, ConnectionSupplier connectionSupplier) { + checkArgument(StringUtils.containsNone(tableName, " "), "Parameter must be the name of a table. Got " + tableName); + return countSql("select count(1) from " + tableName.toLowerCase(Locale.ENGLISH), connectionSupplier); + } + + /** + * Executes a SQL request starting with "SELECT COUNT(something) FROM", for example: + * <pre>int OpenIssues = countSql("select count('id') from issues where status is not null")</pre> + */ + public int countSql(String sql) { + return countSql(sql, new NewConnectionSupplier()); + } + + protected int countSql(String sql, ConnectionSupplier connectionSupplier) { + checkArgument(StringUtils.contains(sql, "count("), + "Parameter must be a SQL request containing 'count(x)' function. Got " + sql); + try ( + ConnectionSupplier supplier = connectionSupplier; + PreparedStatement stmt = supplier.get().prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + return rs.getInt(1); + } + throw new IllegalStateException("No results for " + sql); + + } catch (Exception e) { + throw new IllegalStateException("Fail to execute sql: " + sql, e); + } + } + + public List<Map<String, Object>> select(String selectSql) { + return select(selectSql, new NewConnectionSupplier()); + } + + protected List<Map<String, Object>> select(String selectSql, ConnectionSupplier connectionSupplier) { + try ( + ConnectionSupplier supplier = connectionSupplier; + PreparedStatement stmt = supplier.get().prepareStatement(selectSql); + ResultSet rs = stmt.executeQuery()) { + return getHashMap(rs); + } catch (Exception e) { + throw new IllegalStateException("Fail to execute sql: " + selectSql, e); + } + } + + public Map<String, Object> selectFirst(String selectSql) { + return selectFirst(selectSql, new NewConnectionSupplier()); + } + + protected Map<String, Object> selectFirst(String selectSql, ConnectionSupplier connectionSupplier) { + List<Map<String, Object>> rows = select(selectSql, connectionSupplier); + if (rows.isEmpty()) { + throw new IllegalStateException("No results for " + selectSql); + } else if (rows.size() > 1) { + throw new IllegalStateException("Too many results for " + selectSql); + } + return rows.get(0); + } + + private static List<Map<String, Object>> getHashMap(ResultSet resultSet) throws Exception { + ResultSetMetaData metaData = resultSet.getMetaData(); + int colCount = metaData.getColumnCount(); + List<Map<String, Object>> rows = newArrayList(); + while (resultSet.next()) { + Map<String, Object> columns = newHashMap(); + for (int i = 1; i <= colCount; i++) { + Object value = resultSet.getObject(i); + if (value instanceof Clob) { + Clob clob = (Clob) value; + value = IOUtils.toString((clob.getAsciiStream())); + doClobFree(clob); + } else if (value instanceof BigDecimal) { + // In Oracle, INTEGER types are mapped as BigDecimal + BigDecimal bgValue = ((BigDecimal) value); + if (bgValue.scale() == 0) { + value = bgValue.longValue(); + } else { + value = bgValue.doubleValue(); + } + } else if (value instanceof Integer) { + // To be consistent, all INTEGER types are mapped as Long + value = ((Integer) value).longValue(); + } + columns.put(metaData.getColumnLabel(i), value); + } + rows.add(columns); + } + return rows; + } + + public void prepareDbUnit(Class testClass, String... testNames) { + InputStream[] streams = new InputStream[testNames.length]; + try { + for (int i = 0; i < testNames.length; i++) { + String path = "/" + testClass.getName().replace('.', '/') + "/" + testNames[i]; + streams[i] = testClass.getResourceAsStream(path); + if (streams[i] == null) { + throw new IllegalStateException("DbUnit file not found: " + path); + } + } + + prepareDbUnit(streams); + db.getCommands().resetPrimaryKeys(db.getDatabase().getDataSource()); + } catch (SQLException e) { + throw translateException("Could not setup DBUnit data", e); + } finally { + for (InputStream stream : streams) { + IOUtils.closeQuietly(stream); + } + } + } + + private void prepareDbUnit(InputStream... dataSetStream) { + IDatabaseConnection connection = null; + try { + IDataSet[] dataSets = new IDataSet[dataSetStream.length]; + for (int i = 0; i < dataSetStream.length; i++) { + dataSets[i] = dbUnitDataSet(dataSetStream[i]); + } + db.getDbUnitTester().setDataSet(new CompositeDataSet(dataSets)); + connection = dbUnitConnection(); + new InsertIdentityOperation(DatabaseOperation.INSERT).execute(connection, db.getDbUnitTester().getDataSet()); + } catch (Exception e) { + throw translateException("Could not setup DBUnit data", e); + } finally { + closeQuietly(connection); + } + } + + public void assertDbUnitTable(Class testClass, String filename, String table, String... columns) { + IDatabaseConnection connection = dbUnitConnection(); + try { + IDataSet dataSet = connection.createDataSet(); + String path = "/" + testClass.getName().replace('.', '/') + "/" + filename; + IDataSet expectedDataSet = dbUnitDataSet(testClass.getResourceAsStream(path)); + ITable filteredTable = DefaultColumnFilter.includedColumnsTable(dataSet.getTable(table), columns); + ITable filteredExpectedTable = DefaultColumnFilter.includedColumnsTable(expectedDataSet.getTable(table), columns); + Assertion.assertEquals(filteredExpectedTable, filteredTable); + } catch (DatabaseUnitException e) { + fail(e.getMessage()); + } catch (SQLException e) { + throw translateException("Error while checking results", e); + } finally { + closeQuietly(connection); + } + } + + public void assertDbUnit(Class testClass, String filename, String... tables) { + assertDbUnit(testClass, filename, new String[0], tables); + } + + public void assertDbUnit(Class testClass, String filename, String[] excludedColumnNames, String... tables) { + IDatabaseConnection connection = null; + try { + connection = dbUnitConnection(); + + IDataSet dataSet = connection.createDataSet(); + String path = "/" + testClass.getName().replace('.', '/') + "/" + filename; + InputStream inputStream = testClass.getResourceAsStream(path); + if (inputStream == null) { + throw new IllegalStateException(String.format("File '%s' does not exist", path)); + } + IDataSet expectedDataSet = dbUnitDataSet(inputStream); + for (String table : tables) { + DiffCollectingFailureHandler diffHandler = new DiffCollectingFailureHandler(); + + ITable filteredTable = DefaultColumnFilter.excludedColumnsTable(dataSet.getTable(table), excludedColumnNames); + ITable filteredExpectedTable = DefaultColumnFilter.excludedColumnsTable(expectedDataSet.getTable(table), excludedColumnNames); + Assertion.assertEquals(filteredExpectedTable, filteredTable, diffHandler); + // Evaluate the differences and ignore some column values + List diffList = diffHandler.getDiffList(); + for (Object o : diffList) { + Difference diff = (Difference) o; + if (!"[ignore]".equals(diff.getExpectedValue())) { + throw new DatabaseUnitException(diff.toString()); + } + } + } + } catch (DatabaseUnitException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (Exception e) { + throw translateException("Error while checking results", e); + } finally { + closeQuietly(connection); + } + } + + public void assertColumnDefinition(String table, String column, int expectedType, @Nullable Integer expectedSize) { + assertColumnDefinition(table, column, expectedType, expectedSize, null); + } + + public void assertColumnDefinition(String table, String column, int expectedType, @Nullable Integer expectedSize, @Nullable Boolean isNullable) { + try (Connection connection = getConnection(); + PreparedStatement stmt = connection.prepareStatement("select * from " + table); + ResultSet res = stmt.executeQuery()) { + Integer columnIndex = getColumnIndex(res, column); + if (columnIndex == null) { + fail("The column '" + column + "' does not exist"); + } + + assertThat(res.getMetaData().getColumnType(columnIndex)).isEqualTo(expectedType); + if (expectedSize != null) { + assertThat(res.getMetaData().getColumnDisplaySize(columnIndex)).isEqualTo(expectedSize); + } + if (isNullable != null) { + assertThat(res.getMetaData().isNullable(columnIndex)).isEqualTo(isNullable ? columnNullable : columnNoNulls); + } + } catch (Exception e) { + throw new IllegalStateException("Fail to check column", e); + } + } + + public void assertTableDoesNotExist(String table) { + try (Connection connection = getConnection()) { + boolean tableExists = DatabaseUtils.tableExists(table, connection); + assertThat(tableExists).isFalse(); + } catch (Exception e) { + throw new IllegalStateException("Fail to check if table exists", e); + } + } + + /** + * Verify that non-unique index exists on columns + */ + public void assertIndex(String tableName, String indexName, String expectedColumn, String... expectedSecondaryColumns) { + assertIndexImpl(tableName, indexName, false, expectedColumn, expectedSecondaryColumns); + } + + /** + * Verify that unique index exists on columns + */ + public void assertUniqueIndex(String tableName, String indexName, String expectedColumn, String... expectedSecondaryColumns) { + assertIndexImpl(tableName, indexName, true, expectedColumn, expectedSecondaryColumns); + } + + private void assertIndexImpl(String tableName, String indexName, boolean expectedUnique, String expectedColumn, String... expectedSecondaryColumns) { + try (Connection connection = getConnection(); + ResultSet rs = connection.getMetaData().getIndexInfo(null, null, tableName.toUpperCase(Locale.ENGLISH), false, false)) { + List<String> onColumns = new ArrayList<>(); + while (rs.next()) { + if (indexName.equalsIgnoreCase(rs.getString("INDEX_NAME"))) { + assertThat(rs.getBoolean("NON_UNIQUE")).isEqualTo(!expectedUnique); + int position = rs.getInt("ORDINAL_POSITION"); + onColumns.add(position - 1, rs.getString("COLUMN_NAME").toLowerCase(Locale.ENGLISH)); + } + } + assertThat(asList(expectedColumn, expectedSecondaryColumns)).isEqualTo(onColumns); + } catch (SQLException e) { + throw new IllegalStateException("Fail to check index", e); + } + } + + /** + * Verify that index with name {@code indexName} does not exist on the table {@code tableName} + */ + public void assertIndexDoesNotExist(String tableName, String indexName) { + try (Connection connection = getConnection(); + ResultSet rs = connection.getMetaData().getIndexInfo(null, null, tableName.toUpperCase(Locale.ENGLISH), false, false)) { + List<String> indices = new ArrayList<>(); + while (rs.next()) { + indices.add(rs.getString("INDEX_NAME").toLowerCase(Locale.ENGLISH)); + } + assertThat(indices).doesNotContain(indexName); + } catch (SQLException e) { + throw new IllegalStateException("Fail to check existence of index", e); + } + } + + public void assertPrimaryKey(String tableName, @Nullable String expectedPkName, String columnName, String... otherColumnNames) { + try (Connection connection = getConnection()) { + PK pk = pkOf(connection, tableName.toUpperCase(Locale.ENGLISH)); + if (pk == null) { + pkOf(connection, tableName.toLowerCase(Locale.ENGLISH)); + } + assertThat(pk).as("No primary key is defined on table %s", tableName).isNotNull(); + if (expectedPkName != null) { + assertThat(pk.getName()).isEqualToIgnoringCase(expectedPkName); + } + List<String> expectedColumns = ImmutableList.copyOf(Iterables.concat(Collections.singletonList(columnName), Arrays.asList(otherColumnNames))); + assertThat(pk.getColumns()).as("Primary key does not have the '%s' expected columns", expectedColumns.size()).hasSize(expectedColumns.size()); + + Iterator<String> expectedColumnsIt = expectedColumns.iterator(); + Iterator<String> actualColumnsIt = pk.getColumns().iterator(); + while (expectedColumnsIt.hasNext() && actualColumnsIt.hasNext()) { + assertThat(actualColumnsIt.next()).isEqualToIgnoringCase(expectedColumnsIt.next()); + } + } catch (SQLException e) { + throw new IllegalStateException("Fail to check primary key", e); + } + } + + @CheckForNull + private PK pkOf(Connection connection, String tableName) throws SQLException { + try (ResultSet resultSet = connection.getMetaData().getPrimaryKeys(null, null, tableName)) { + String pkName = null; + ArrayList<String> columnNames = null; + while (resultSet.next()) { + if (columnNames == null) { + pkName = resultSet.getString("PK_NAME"); + columnNames = new ArrayList<>(1); + } else { + assertThat(pkName).as("Multiple primary keys found").isEqualTo(resultSet.getString("PK_NAME")); + } + columnNames.add(resultSet.getInt("KEY_SEQ") - 1, resultSet.getString("COLUMN_NAME")); + } + if (columnNames == null) { + return null; + } + return new PK(pkName, columnNames); + } + } + + @CheckForNull + private Integer getColumnIndex(ResultSet res, String column) { + try { + ResultSetMetaData meta = res.getMetaData(); + int numCol = meta.getColumnCount(); + for (int i = 1; i < numCol + 1; i++) { + if (meta.getColumnLabel(i).toLowerCase().equals(column.toLowerCase())) { + return i; + } + } + return null; + + } catch (Exception e) { + throw new IllegalStateException("Fail to get column index"); + } + } + + private IDataSet dbUnitDataSet(InputStream stream) { + try { + ReplacementDataSet dataSet = new ReplacementDataSet(new FlatXmlDataSet(stream)); + dataSet.addReplacementObject("[null]", null); + dataSet.addReplacementObject("[false]", Boolean.FALSE); + dataSet.addReplacementObject("[true]", Boolean.TRUE); + + return dataSet; + } catch (Exception e) { + throw translateException("Could not read the dataset stream", e); + } + } + + private IDatabaseConnection dbUnitConnection() { + try { + IDatabaseConnection connection = db.getDbUnitTester().getConnection(); + connection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, db.getDbUnitFactory()); + return connection; + } catch (Exception e) { + throw translateException("Error while getting connection", e); + } + } + + public static RuntimeException translateException(String msg, Exception cause) { + RuntimeException runtimeException = new RuntimeException(String.format("%s: [%s] %s", msg, cause.getClass().getName(), cause.getMessage())); + runtimeException.setStackTrace(cause.getStackTrace()); + return runtimeException; + } + + private static void doClobFree(Clob clob) throws SQLException { + try { + clob.free(); + } catch (AbstractMethodError e) { + // JTS driver do not implement free() as it's using JDBC 3.0 + } + } + + private void closeQuietly(@Nullable IDatabaseConnection connection) { + try { + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + // ignore + } + } + + public Connection openConnection() throws SQLException { + return getConnection(); + } + + private Connection getConnection() throws SQLException { + return db.getDatabase().getDataSource().getConnection(); + } + + public Database database() { + return db.getDatabase(); + } + + public DatabaseCommands getCommands() { + return db.getCommands(); + } + + /** + * An {@link AutoCloseable} supplier of {@link Connection}. + */ + protected interface ConnectionSupplier extends AutoCloseable { + Connection get() throws SQLException; + + @Override + void close(); + } + + private static class PK { + @CheckForNull + private final String name; + private final List<String> columns; + + private PK(@Nullable String name, List<String> columns) { + this.name = name; + this.columns = ImmutableList.copyOf(columns); + } + + @CheckForNull + public String getName() { + return name; + } + + public List<String> getColumns() { + return columns; + } + } + + private class NewConnectionSupplier implements ConnectionSupplier { + private Connection connection; + + @Override + public Connection get() throws SQLException { + if (this.connection == null) { + this.connection = getConnection(); + } + return this.connection; + } + + @Override + public void close() { + if (this.connection != null) { + try { + this.connection.close(); + } catch (SQLException e) { + Loggers.get(CoreDbTester.class).warn("Fail to close connection", e); + // do not re-throw the exception + } + } + } + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/CoreDbTester.java b/server/sonar-db-core/src/test/java/org/sonar/db/CoreDbTester.java new file mode 100644 index 00000000000..f2ac87ac736 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/CoreDbTester.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; + +/** + * This class should be called using @Rule. + * Data is truncated between each tests. The schema is created between each test. + */ +public class CoreDbTester extends AbstractDbTester<CoreTestDb> { + private CoreDbTester(@Nullable String schemaPath) { + super(CoreTestDb.create(schemaPath)); + } + + public static CoreDbTester createForSchema(Class testClass, String filename) { + String path = StringUtils.replaceChars(testClass.getCanonicalName(), '.', '/'); + String schemaPath = path + "/" + filename; + return new CoreDbTester(schemaPath); + } + + public static CoreDbTester createEmpty() { + String path = StringUtils.replaceChars(CoreDbTester.class.getCanonicalName(), '.', '/'); + String schemaPath = path + "/empty.sql"; + return new CoreDbTester(schemaPath); + } + + @Override + protected void before() throws Throwable { + db.start(); + db.truncateTables(); + } + + @Override + protected void after() { + db.stop(); + } + +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/CoreTestDb.java b/server/sonar-db-core/src/test/java/org/sonar/db/CoreTestDb.java new file mode 100644 index 00000000000..bc5cd3cb6d4 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/CoreTestDb.java @@ -0,0 +1,184 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import java.io.File; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.sql.SQLException; +import java.util.Map; +import java.util.Properties; +import javax.annotation.Nullable; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.text.StrSubstitutor; +import org.dbunit.DataSourceDatabaseTester; +import org.dbunit.IDatabaseTester; +import org.dbunit.dataset.datatype.IDataTypeFactory; +import org.junit.AssumptionViolatedException; +import org.sonar.api.config.Settings; +import org.sonar.api.config.MapSettings; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.db.dialect.H2; +import org.sonar.process.logging.LogbackHelper; + +/** + * This class should be call using @ClassRule in order to create the schema once (if @Rule is used + * the schema will be recreated before each test). + */ +class CoreTestDb { + + private static CoreTestDb DEFAULT; + + private static final Logger LOG = Loggers.get(CoreTestDb.class); + + private Database db; + private DatabaseCommands commands; + private IDatabaseTester tester; + private boolean isDefault; + + static CoreTestDb create(@Nullable String schemaPath) { + if (schemaPath == null) { + if (DEFAULT == null) { + DEFAULT = new CoreTestDb(null); + } + return DEFAULT; + } + return new CoreTestDb(schemaPath); + } + + CoreTestDb(@Nullable String schemaPath) { + if (db == null) { + Settings settings = new MapSettings().addProperties(System.getProperties()); + if (settings.hasKey("orchestrator.configUrl")) { + loadOrchestratorSettings(settings); + } + String login = settings.getString("sonar.jdbc.username"); + for (String key : settings.getKeysStartingWith("sonar.jdbc")) { + LOG.info(key + ": " + settings.getString(key)); + } + String dialect = settings.getString("sonar.jdbc.dialect"); + if (dialect != null && !"h2".equals(dialect)) { + db = new DefaultDatabase(new LogbackHelper(), settings); + } else { + db = new H2Database("h2Tests" + DigestUtils.md5Hex(StringUtils.defaultString(schemaPath)), schemaPath == null); + } + db.start(); + if (schemaPath != null) { + // will fail if not H2 + if (db.getDialect().getId().equals("h2")) { + ((H2Database) db).executeScript(schemaPath); + } else { + db.stop(); + + } + } + isDefault = (schemaPath == null); + LOG.info("Test Database: " + db); + + commands = DatabaseCommands.forDialect(db.getDialect()); + tester = new DataSourceDatabaseTester(db.getDataSource(), commands.useLoginAsSchema() ? login : null); + + extendStart(db); + } + } + + /** + * to be overridden by subclasses to extend what's done when db is created + * @param db + */ + protected void extendStart(Database db) { + // nothing done here + } + + public void start() { + if (!isDefault && !H2.ID.equals(db.getDialect().getId())) { + throw new AssumptionViolatedException("Test disabled because it supports only H2"); + } + } + + void stop() { + if (!isDefault) { + db.stop(); + } + } + + void truncateTables() { + try { + commands.truncateDatabase(db.getDataSource()); + } catch (SQLException e) { + throw new IllegalStateException("Fail to truncate db tables", e); + } + } + + Database getDatabase() { + return db; + } + + DatabaseCommands getCommands() { + return commands; + } + + IDatabaseTester getDbUnitTester() { + return tester; + } + + IDataTypeFactory getDbUnitFactory() { + return commands.getDbUnitFactory(); + } + + private void loadOrchestratorSettings(Settings settings) { + String url = settings.getString("orchestrator.configUrl"); + InputStream input = null; + try { + URI uri = new URI(url); + if (url.startsWith("file:")) { + File file = new File(uri); + input = FileUtils.openInputStream(file); + } else { + HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection(); + int responseCode = connection.getResponseCode(); + if (responseCode >= 400) { + throw new IllegalStateException("Fail to request: " + uri + ". Status code=" + responseCode); + } + + input = connection.getInputStream(); + + } + Properties props = new Properties(); + props.load(input); + settings.addProperties(props); + for (Map.Entry<String, String> entry : settings.getProperties().entrySet()) { + String interpolatedValue = StrSubstitutor.replace(entry.getValue(), System.getenv(), "${", "}"); + settings.setProperty(entry.getKey(), interpolatedValue); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + IOUtils.closeQuietly(input); + } + } + + +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCheckerTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCheckerTest.java new file mode 100644 index 00000000000..ebe2e86e4ba --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCheckerTest.java @@ -0,0 +1,141 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; +import org.sonar.api.utils.MessageException; +import org.sonar.db.dialect.Dialect; +import org.sonar.db.dialect.H2; +import org.sonar.db.dialect.MySql; +import org.sonar.db.dialect.Oracle; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DatabaseCheckerTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void requires_oracle_driver_11_2() throws Exception { + Database db = mockDb(new Oracle(), 11, 2, "11.2.0.0.1"); + new DatabaseChecker(db).start(); + // no error + + db = mockDb(new Oracle(), 11, 2, "11.3.1"); + new DatabaseChecker(db).start(); + // no error + + db = mockDb(new Oracle(), 11, 2, "12.0.2"); + new DatabaseChecker(db).start(); + // no error + + db = mockDb(new Oracle(), 11, 2, "11.1.0.2"); + try { + new DatabaseChecker(db).start(); + fail(); + } catch (MessageException e) { + assertThat(e).hasMessage("Unsupported Oracle driver version: 11.1.0.2. Minimal supported version is 11.2."); + } + } + + @Test + public void requires_oracle_11g_or_greater() throws Exception { + // oracle 11.0 is ok + Database db = mockDb(new Oracle(), 11, 0, "11.2.0.0.1"); + new DatabaseChecker(db).start(); + + // oracle 11.1 is ok + db = mockDb(new Oracle(), 11, 1, "11.2.0.0.1"); + new DatabaseChecker(db).start(); + + // oracle 11.2 is ok + db = mockDb(new Oracle(), 11, 2, "11.2.0.0.1"); + new DatabaseChecker(db).start(); + + // oracle 12 is ok + db = mockDb(new Oracle(), 12, 0, "11.2.0.0.1"); + new DatabaseChecker(db).start(); + + // oracle 10 is not supported + db = mockDb(new Oracle(), 10, 2, "11.2.0.0.1"); + try { + new DatabaseChecker(db).start(); + fail(); + } catch (MessageException e) { + assertThat(e).hasMessage("Unsupported oracle version: 10.2. Minimal supported version is 11.0."); + } + } + + @Test + public void log_warning_if_h2() throws Exception { + Database db = mockDb(new H2(), 13, 4, "13.4"); + DatabaseChecker checker = new DatabaseChecker(db); + checker.start(); + checker.stop(); + // TODO test log + } + + @Test + public void test_mysql() throws Exception { + Database db = mockDb(new MySql(), 5, 7, "5.7"); + new DatabaseChecker(db).start(); + // no error + } + + @Test + public void fail_if_mysql_less_than_5_6() throws Exception { + expectedException.expect(MessageException.class); + expectedException.expectMessage("Unsupported mysql version: 5.5. Minimal supported version is 5.6."); + + Database db = mockDb(new MySql(), 5, 5, "5.6"); + new DatabaseChecker(db).start(); + } + + @Test + public void fail_if_cant_get_db_version() throws Exception { + SQLException sqlException = new SQLException(); + expectedException.expect(RuntimeException.class); + expectedException.expectCause(is(sqlException)); + + Database db = mock(Database.class, Mockito.RETURNS_DEEP_STUBS); + when(db.getDialect()).thenReturn(new MySql()); + when(db.getDataSource().getConnection().getMetaData()).thenThrow(sqlException); + + new DatabaseChecker(db).start(); + } + + private Database mockDb(Dialect dialect, int dbMajorVersion, int dbMinorVersion, String driverVersion) throws SQLException { + Database db = mock(Database.class, Mockito.RETURNS_DEEP_STUBS); + when(db.getDialect()).thenReturn(dialect); + when(db.getDataSource().getConnection().getMetaData().getDatabaseMajorVersion()).thenReturn(dbMajorVersion); + when(db.getDataSource().getConnection().getMetaData().getDatabaseMinorVersion()).thenReturn(dbMinorVersion); + when(db.getDataSource().getConnection().getMetaData().getDriverVersion()).thenReturn(driverVersion); + return db; + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCommands.java b/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCommands.java new file mode 100644 index 00000000000..db10daec84c --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCommands.java @@ -0,0 +1,215 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.List; +import javax.sql.DataSource; +import org.apache.commons.dbutils.DbUtils; +import org.apache.commons.lang.StringUtils; +import org.dbunit.dataset.datatype.DefaultDataTypeFactory; +import org.dbunit.dataset.datatype.IDataTypeFactory; +import org.dbunit.dataset.datatype.ToleratedDeltaMap; +import org.dbunit.ext.h2.H2DataTypeFactory; +import org.dbunit.ext.mssql.MsSqlDataTypeFactory; +import org.dbunit.ext.mysql.MySqlDataTypeFactory; +import org.dbunit.ext.oracle.Oracle10DataTypeFactory; +import org.dbunit.ext.postgresql.PostgresqlDataTypeFactory; +import org.sonar.db.dialect.Dialect; +import org.sonar.db.dialect.MsSql; +import org.sonar.db.dialect.MySql; +import org.sonar.db.dialect.Oracle; +import org.sonar.db.dialect.PostgreSql; +import org.sonar.db.version.SqTables; + +public abstract class DatabaseCommands { + private final IDataTypeFactory dbUnitFactory; + + private DatabaseCommands(DefaultDataTypeFactory dbUnitFactory) { + this.dbUnitFactory = dbUnitFactory; + + // Hack for MsSQL failure in IssueMapperTest. + // All the Double fields should be listed here. + dbUnitFactory.addToleratedDelta(new ToleratedDeltaMap.ToleratedDelta("issues", "effort_to_fix", 0.0001)); + } + + public final IDataTypeFactory getDbUnitFactory() { + return dbUnitFactory; + } + + abstract List<String> resetSequenceSql(String table, int minSequenceValue); + + String truncateSql(String table) { + return "TRUNCATE TABLE " + table; + } + + boolean useLoginAsSchema() { + return false; + } + + public static DatabaseCommands forDialect(Dialect dialect) { + DatabaseCommands command = ImmutableMap.of( + org.sonar.db.dialect.H2.ID, H2, + MsSql.ID, MSSQL, + MySql.ID, MYSQL, + Oracle.ID, ORACLE, + PostgreSql.ID, POSTGRESQL).get(dialect.getId()); + + return Preconditions.checkNotNull(command, "Unknown database: " + dialect); + } + + private static final DatabaseCommands H2 = new DatabaseCommands(new H2DataTypeFactory()) { + @Override + List<String> resetSequenceSql(String table, int minSequenceValue) { + return Arrays.asList("ALTER TABLE " + table + " ALTER COLUMN ID RESTART WITH " + minSequenceValue); + } + }; + + private static final DatabaseCommands POSTGRESQL = new DatabaseCommands(new PostgresqlDataTypeFactory()) { + @Override + List<String> resetSequenceSql(String table, int minSequenceValue) { + return Arrays.asList("ALTER SEQUENCE " + table + "_id_seq RESTART WITH " + minSequenceValue); + } + }; + + private static final DatabaseCommands ORACLE = new DatabaseCommands(new Oracle10DataTypeFactory()) { + @Override + List<String> resetSequenceSql(String table, int minSequenceValue) { + String sequence = StringUtils.upperCase(table) + "_SEQ"; + return Arrays.asList( + "DROP SEQUENCE " + sequence, + "CREATE SEQUENCE " + sequence + " INCREMENT BY 1 MINVALUE 1 START WITH " + minSequenceValue); + } + + @Override + String truncateSql(String table) { + return "TRUNCATE TABLE " + table + " REUSE STORAGE"; + } + + @Override + boolean useLoginAsSchema() { + return true; + } + }; + + private static final DatabaseCommands MSSQL = new DatabaseCommands(new MsSqlDataTypeFactory()) { + @Override + public void resetPrimaryKeys(DataSource dataSource) { + } + + @Override + List<String> resetSequenceSql(String table, int minSequenceValue) { + return null; + } + + @Override + protected boolean shouldTruncate(Connection connection, String table) throws SQLException { + // truncate all tables on mssql, else unexpected errors in some tests + return true; + } + }; + + private static final DatabaseCommands MYSQL = new DatabaseCommands(new MySqlDataTypeFactory()) { + @Override + public void resetPrimaryKeys(DataSource dataSource) { + } + + @Override + List<String> resetSequenceSql(String table, int minSequenceValue) { + return null; + } + }; + + public void truncateDatabase(DataSource dataSource) throws SQLException { + Connection connection = dataSource.getConnection(); + Statement statement = null; + try { + connection.setAutoCommit(false); + statement = connection.createStatement(); + for (String table : SqTables.TABLES) { + try { + if (shouldTruncate(connection, table)) { + statement.executeUpdate(truncateSql(table)); + connection.commit(); + } + } catch (Exception e) { + connection.rollback(); + throw new IllegalStateException("Fail to truncate table " + table, e); + } + } + } finally { + DbUtils.closeQuietly(connection); + DbUtils.closeQuietly(statement); + } + } + + protected boolean shouldTruncate(Connection connection, String table) throws SQLException { + Statement stmt = connection.createStatement(); + ResultSet rs = null; + try { + rs = stmt.executeQuery("select count(1) from " + table); + if (rs.next()) { + return rs.getInt(1) > 0; + } + + } catch (SQLException ignored) { + // probably because table does not exist. That's the case with H2 tests. + } finally { + DbUtils.closeQuietly(rs); + DbUtils.closeQuietly(stmt); + } + return false; + } + + public void resetPrimaryKeys(DataSource dataSource) throws SQLException { + Connection connection = null; + Statement statement = null; + ResultSet resultSet = null; + try { + connection = dataSource.getConnection(); + connection.setAutoCommit(false); + + statement = connection.createStatement(); + for (String table : SqTables.TABLES) { + try { + resultSet = statement.executeQuery("SELECT CASE WHEN MAX(ID) IS NULL THEN 1 ELSE MAX(ID)+1 END FROM " + table); + resultSet.next(); + int maxId = resultSet.getInt(1); + resultSet.close(); + + for (String resetCommand : resetSequenceSql(table, maxId)) { + statement.executeUpdate(resetCommand); + } + connection.commit(); + } catch (Exception e) { + connection.rollback(); // this table has no primary key + } + } + } finally { + DbUtils.closeQuietly(connection, statement, resultSet); + } + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseUtilsTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseUtilsTest.java new file mode 100644 index 00000000000..d5855c87a2c --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseUtilsTest.java @@ -0,0 +1,325 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import com.google.common.base.Function; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.util.stream.Collectors; +import org.sonar.db.dialect.Oracle; + +import static com.google.common.collect.Lists.newArrayList; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.sonar.db.DatabaseUtils.toUniqueAndSortedList; + +public class DatabaseUtilsTest { + + @Rule + public CoreDbTester dbTester = CoreDbTester.createForSchema(DatabaseUtilsTest.class, "just_one_table.sql"); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public LogTester logTester = new LogTester(); + + @Test + public void should_close_connection() throws Exception { + Connection connection = dbTester.openConnection(); + assertThat(isClosed(connection)).isFalse(); + + DatabaseUtils.closeQuietly(connection); + assertThat(isClosed(connection)).isTrue(); + } + + @Test + public void should_support_null_connection() { + DatabaseUtils.closeQuietly((Connection) null); + // no failure + } + + @Test + public void should_close_statement_and_resultset() throws Exception { + Connection connection = dbTester.openConnection(); + try { + PreparedStatement statement = connection.prepareStatement(selectDual()); + ResultSet rs = statement.executeQuery(); + + DatabaseUtils.closeQuietly(rs); + DatabaseUtils.closeQuietly(statement); + + assertThat(isClosed(statement)).isTrue(); + assertThat(isClosed(rs)).isTrue(); + } finally { + DatabaseUtils.closeQuietly(connection); + } + } + + @Test + public void should_not_fail_on_connection_errors() throws SQLException { + Connection connection = mock(Connection.class); + doThrow(new SQLException()).when(connection).close(); + + DatabaseUtils.closeQuietly(connection); + + // no failure + verify(connection).close(); // just to be sure + } + + @Test + public void should_not_fail_on_statement_errors() throws SQLException { + Statement statement = mock(Statement.class); + doThrow(new SQLException()).when(statement).close(); + + DatabaseUtils.closeQuietly(statement); + + // no failure + verify(statement).close(); // just to be sure + } + + @Test + public void should_not_fail_on_resulset_errors() throws SQLException { + ResultSet rs = mock(ResultSet.class); + doThrow(new SQLException()).when(rs).close(); + + DatabaseUtils.closeQuietly(rs); + + // no failure + verify(rs).close(); // just to be sure + } + + @Test + public void toUniqueAndSortedList_throws_NPE_if_arg_is_null() { + expectedException.expect(NullPointerException.class); + + toUniqueAndSortedList(null); + } + + @Test + public void toUniqueAndSortedList_throws_NPE_if_arg_contains_a_null() { + expectedException.expect(NullPointerException.class); + + toUniqueAndSortedList(asList("A", null, "C")); + } + + @Test + public void toUniqueAndSortedList_throws_NPE_if_arg_is_a_set_containing_a_null() { + expectedException.expect(NullPointerException.class); + + toUniqueAndSortedList(new HashSet<Comparable>(asList("A", null, "C"))); + } + + @Test + public void toUniqueAndSortedList_enforces_natural_order() { + assertThat(toUniqueAndSortedList(asList("A", "B", "C"))).containsExactly("A", "B", "C"); + assertThat(toUniqueAndSortedList(asList("B", "A", "C"))).containsExactly("A", "B", "C"); + assertThat(toUniqueAndSortedList(asList("B", "C", "A"))).containsExactly("A", "B", "C"); + } + + @Test + public void toUniqueAndSortedList_removes_duplicates() { + assertThat(toUniqueAndSortedList(asList("A", "A", "A"))).containsExactly("A"); + assertThat(toUniqueAndSortedList(asList("A", "C", "A"))).containsExactly("A", "C"); + assertThat(toUniqueAndSortedList(asList("C", "C", "B", "B", "A", "N", "C", "A"))).containsExactly("A", "B", "C", "N"); + } + + @Test + public void toUniqueAndSortedList_removes_duplicates_and_apply_natural_order_of_any_Comparable() { + assertThat( + toUniqueAndSortedList(asList(myComparable(2), myComparable(5), myComparable(2), myComparable(4), myComparable(-1), myComparable(10)))) + .containsExactly( + myComparable(-1), myComparable(2), myComparable(4), myComparable(5), myComparable(10)); + } + + private static DatabaseUtilsTest.MyComparable myComparable(int ordinal) { + return new DatabaseUtilsTest.MyComparable(ordinal); + } + + private static final class MyComparable implements Comparable<MyComparable> { + private final int ordinal; + + private MyComparable(int ordinal) { + this.ordinal = ordinal; + } + + @Override + public int compareTo(MyComparable o) { + return ordinal - o.ordinal; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MyComparable that = (MyComparable) o; + return ordinal == that.ordinal; + } + + @Override + public int hashCode() { + return Objects.hash(ordinal); + } + } + + /** + * Connection.isClosed() has been introduced in java 1.6 + */ + private boolean isClosed(Connection c) { + try { + c.createStatement().execute(selectDual()); + return false; + } catch (Exception e) { + return true; + } + } + + /** + * Statement.isClosed() has been introduced in java 1.6 + */ + private boolean isClosed(Statement s) { + try { + s.execute("SELECT 1"); + return false; + } catch (Exception e) { + return true; + } + } + + /** + * ResultSet.isClosed() has been introduced in java 1.6 + */ + private boolean isClosed(ResultSet rs) { + try { + rs.next(); + return false; + } catch (Exception e) { + return true; + } + } + + private String selectDual() { + String sql = "SELECT 1"; + if (Oracle.ID.equals(dbTester.database().getDialect().getId())) { + sql = "SELECT 1 FROM DUAL"; + } + return sql; + } + + @Test + public void repeatCondition() { + assertThat(DatabaseUtils.repeatCondition("uuid=?", 1, "or")).isEqualTo("uuid=?"); + assertThat(DatabaseUtils.repeatCondition("uuid=?", 3, "or")).isEqualTo("uuid=? or uuid=? or uuid=?"); + } + + @Test + public void executeLargeInputs() { + List<Integer> inputs = newArrayList(); + List<String> expectedOutputs = newArrayList(); + for (int i = 0; i < 2010; i++) { + inputs.add(i); + expectedOutputs.add(Integer.toString(i)); + } + + List<String> outputs = DatabaseUtils.executeLargeInputs(inputs, input -> { + // Check that each partition is only done on 1000 elements max + assertThat(input.size()).isLessThanOrEqualTo(1000); + return input.stream().map(String::valueOf).collect(Collectors.toList()); + }); + + assertThat(outputs).isEqualTo(expectedOutputs); + } + + @Test + public void executeLargeInputs_on_empty_list() { + List<String> outputs = DatabaseUtils.executeLargeInputs(Collections.emptyList(), new Function<List<Integer>, List<String>>() { + @Override + public List<String> apply(List<Integer> input) { + fail("No partition should be made on empty list"); + return Collections.emptyList(); + } + }); + + assertThat(outputs).isEmpty(); + } + + @Test + public void executeLargeUpdates() { + List<Integer> inputs = newArrayList(); + for (int i = 0; i < 2010; i++) { + inputs.add(i); + } + + List<Integer> processed = newArrayList(); + DatabaseUtils.executeLargeUpdates(inputs, input -> { + assertThat(input.size()).isLessThanOrEqualTo(1000); + processed.addAll(input); + }); + assertThat(processed).containsExactlyElementsOf(inputs); + } + + @Test + public void executeLargeUpdates_on_empty_list() { + DatabaseUtils.executeLargeUpdates(Collections.<Integer>emptyList(), input -> { + fail("No partition should be made on empty list"); + }); + } + + @Test + public void log_all_sql_exceptions() { + SQLException root = new SQLException("this is root", "123"); + SQLException next = new SQLException("this is next", "456"); + root.setNextException(next); + + DatabaseUtils.log(Loggers.get(getClass()), root); + + assertThat(logTester.logs(LoggerLevel.ERROR)).contains("SQL error: 456. Message: this is next"); + } + + @Test + public void tableExists_returns_true_if_table_is_referenced_in_db_metadata() throws Exception { + try (Connection connection = dbTester.openConnection()) { + assertThat(DatabaseUtils.tableExists("SCHEMA_MIGRATIONS", connection)).isTrue(); + assertThat(DatabaseUtils.tableExists("schema_migrations", connection)).isTrue(); + assertThat(DatabaseUtils.tableExists("schema_MIGRATIONS", connection)).isTrue(); + assertThat(DatabaseUtils.tableExists("foo", connection)).isFalse(); + } + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/DdlUtilsTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/DdlUtilsTest.java new file mode 100644 index 00000000000..68f64dbf321 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/DdlUtilsTest.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.assertj.core.api.Assertions; +import org.h2.Driver; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DdlUtilsTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void shouldSupportOnlyH2() { + Assertions.assertThat(DdlUtils.supportsDialect("h2")).isTrue(); + assertThat(DdlUtils.supportsDialect("mysql")).isFalse(); + assertThat(DdlUtils.supportsDialect("oracle")).isFalse(); + assertThat(DdlUtils.supportsDialect("mssql")).isFalse(); + } + + @Test + public void shouldCreateSchema_with_schema_migrations() throws SQLException { + DriverManager.registerDriver(new Driver()); + try (Connection connection = DriverManager.getConnection("jdbc:h2:mem:sonar_test");) { + DdlUtils.createSchema(connection, "h2", true); + + int tableCount = countTables(connection); + assertThat(tableCount).isGreaterThan(30); + + verifySchemaMigrations(connection); + } + } + + @Test + public void shouldCreateSchema_without_schema_migrations() throws SQLException { + DriverManager.registerDriver(new Driver()); + try (Connection connection = DriverManager.getConnection("jdbc:h2:mem:sonar_test2")) { + try (Statement statement = connection.createStatement()) { + statement.execute("create table schema_migrations (version varchar(255) not null)"); + } + DdlUtils.createSchema(connection, "h2", false); + + verifySchemaMigrations(connection); + } + } + + static int countTables(Connection connection) throws SQLException { + int count = 0; + ResultSet resultSet = connection.getMetaData().getTables("", null, null, new String[] {"TABLE"}); + while (resultSet.next()) { + count++; + } + resultSet.close(); + return count; + } + + private void verifySchemaMigrations(Connection connection) throws SQLException { + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("select count(*) from schema_migrations")) { + assertThat(resultSet.next()).isTrue(); + assertThat(resultSet.getLong(1)).isGreaterThan(150); + assertThat(resultSet.next()).isFalse(); + } + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/DefaultDatabaseTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/DefaultDatabaseTest.java new file mode 100644 index 00000000000..1ffc411cb44 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/DefaultDatabaseTest.java @@ -0,0 +1,116 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import java.util.Properties; +import org.apache.commons.dbcp.BasicDataSource; +import org.junit.Test; +import org.sonar.api.config.Settings; +import org.sonar.api.config.MapSettings; +import org.sonar.db.dialect.PostgreSql; +import org.sonar.process.logging.LogbackHelper; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class DefaultDatabaseTest { + private LogbackHelper logbackHelper = mock(LogbackHelper.class); + + @Test + public void shouldLoadDefaultValues() { + DefaultDatabase db = new DefaultDatabase(logbackHelper, new MapSettings()); + db.initSettings(); + + Properties props = db.getProperties(); + assertThat(props.getProperty("sonar.jdbc.url")).isEqualTo("jdbc:h2:tcp://localhost/sonar"); + assertThat(props.getProperty("sonar.jdbc.driverClassName")).isEqualTo("org.h2.Driver"); + assertThat(db.toString()).isEqualTo("Database[jdbc:h2:tcp://localhost/sonar]"); + } + + @Test + public void shouldExtractCommonsDbcpProperties() { + Properties props = new Properties(); + props.setProperty("sonar.jdbc.driverClassName", "my.Driver"); + props.setProperty("sonar.jdbc.username", "me"); + props.setProperty("sonar.jdbc.maxActive", "5"); + + Properties commonsDbcpProps = DefaultDatabase.extractCommonsDbcpProperties(props); + + assertThat(commonsDbcpProps.getProperty("username")).isEqualTo("me"); + assertThat(commonsDbcpProps.getProperty("driverClassName")).isEqualTo("my.Driver"); + assertThat(commonsDbcpProps.getProperty("maxActive")).isEqualTo("5"); + } + + @Test + public void shouldCompleteProperties() { + Settings settings = new MapSettings(); + + DefaultDatabase db = new DefaultDatabase(logbackHelper, settings) { + @Override + protected void doCompleteProperties(Properties properties) { + properties.setProperty("sonar.jdbc.maxActive", "2"); + } + }; + db.initSettings(); + + Properties props = db.getProperties(); + + assertThat(props.getProperty("sonar.jdbc.maxActive")).isEqualTo("2"); + } + + @Test + public void shouldStart() { + Settings settings = new MapSettings(); + settings.setProperty("sonar.jdbc.url", "jdbc:h2:mem:sonar"); + settings.setProperty("sonar.jdbc.driverClassName", "org.h2.Driver"); + settings.setProperty("sonar.jdbc.username", "sonar"); + settings.setProperty("sonar.jdbc.password", "sonar"); + settings.setProperty("sonar.jdbc.maxActive", "1"); + + DefaultDatabase db = new DefaultDatabase(logbackHelper, settings); + db.start(); + db.stop(); + + assertThat(db.getDialect().getId()).isEqualTo("h2"); + assertThat(((BasicDataSource) db.getDataSource()).getMaxActive()).isEqualTo(1); + } + + @Test + public void shouldGuessDialectFromUrl() { + Settings settings = new MapSettings(); + settings.setProperty("sonar.jdbc.url", "jdbc:postgresql://localhost/sonar"); + + DefaultDatabase database = new DefaultDatabase(logbackHelper, settings); + database.initSettings(); + + assertThat(database.getDialect().getId()).isEqualTo(PostgreSql.ID); + } + + @Test + public void shouldGuessDefaultDriver() { + Settings settings = new MapSettings(); + settings.setProperty("sonar.jdbc.url", "jdbc:postgresql://localhost/sonar"); + + DefaultDatabase database = new DefaultDatabase(logbackHelper, settings); + database.initSettings(); + + assertThat(database.getProperties().getProperty("sonar.jdbc.driverClassName")).isEqualTo("org.postgresql.Driver"); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/H2Database.java b/server/sonar-db-core/src/test/java/org/sonar/db/H2Database.java new file mode 100644 index 00000000000..a6854973bd7 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/H2Database.java @@ -0,0 +1,122 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import java.sql.Connection; +import java.sql.SQLException; +import javax.sql.DataSource; +import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.dbutils.DbUtils; +import org.sonar.db.dialect.Dialect; +import org.sonar.db.dialect.H2; + +import static java.lang.String.format; + +/** + * H2 in-memory database, used for unit tests only. + * + * @since 3.2 + */ +public class H2Database implements Database { + private final String name; + private final boolean createSchema; + private BasicDataSource datasource; + + /** + * IMPORTANT: change DB name in order to not conflict with {@link DefaultDatabaseTest} + */ + public H2Database(String name, boolean createSchema) { + this.name = name; + this.createSchema = createSchema; + } + + @Override + public void start() { + startDatabase(); + if (createSchema) { + createSchema(); + } + } + + private void startDatabase() { + try { + datasource = new BasicDataSource(); + datasource.setDriverClassName("org.h2.Driver"); + datasource.setUsername("sonar"); + datasource.setPassword("sonar"); + datasource.setUrl("jdbc:h2:mem:" + name); + } catch (Exception e) { + throw new IllegalStateException("Fail to start H2", e); + } + } + + private void createSchema() { + Connection connection = null; + try { + connection = datasource.getConnection(); + DdlUtils.createSchema(connection, "h2", true); + + } catch (SQLException e) { + throw new IllegalStateException("Fail to create schema", e); + } finally { + DbUtils.closeQuietly(connection); + } + } + + public void executeScript(String classloaderPath) { + Connection connection = null; + try { + connection = datasource.getConnection(); + DdlUtils.executeScript(connection, classloaderPath); + + } catch (SQLException e) { + throw new IllegalStateException("Fail to execute script: " + classloaderPath, e); + } finally { + DbUtils.closeQuietly(connection); + } + } + + @Override + public void stop() { + try { + datasource.close(); + } catch (SQLException e) { + // Ignore error + } + } + + public DataSource getDataSource() { + return datasource; + } + + public Dialect getDialect() { + return new H2(); + } + + @Override + public void enableSqlLogging(boolean enable) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return format("H2 Database[%s]", name); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/H2DatabaseTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/H2DatabaseTest.java new file mode 100644 index 00000000000..c4cb43011d6 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/H2DatabaseTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import java.sql.Connection; +import java.sql.SQLException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class H2DatabaseTest { + H2Database db = new H2Database("sonar2", true); + + @Before + public void startDb() { + db.start(); + } + + @After + public void stopDb() { + db.stop(); + } + + @Test + public void shouldExecuteDdlAtStartup() throws SQLException { + Connection connection = db.getDataSource().getConnection(); + int tableCount = DdlUtilsTest.countTables(connection); + connection.close(); + + assertThat(tableCount).isGreaterThan(30); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/ResultSetIteratorTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/ResultSetIteratorTest.java new file mode 100644 index 00000000000..19a9e7089ae --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/ResultSetIteratorTest.java @@ -0,0 +1,169 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.NoSuchElementException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + + +public class ResultSetIteratorTest { + + @Rule + public CoreDbTester dbTester = CoreDbTester.createForSchema(ResultSetIteratorTest.class, "schema.sql"); + + @Test + public void create_iterator_from_statement() throws Exception { + dbTester.prepareDbUnit(getClass(), "feed.xml"); + + try (Connection connection = dbTester.openConnection()) { + PreparedStatement stmt = connection.prepareStatement("select * from issues order by id"); + FirstIntColumnIterator iterator = new FirstIntColumnIterator(stmt); + + assertThat(iterator.hasNext()).isTrue(); + + // calling multiple times hasNext() is ok + assertThat(iterator.hasNext()).isTrue(); + + assertThat(iterator.next()).isEqualTo(10); + assertThat(iterator.hasNext()).isTrue(); + assertThat(iterator.next()).isEqualTo(20); + + // call next() without calling hasNext() + assertThat(iterator.next()).isEqualTo(30); + assertThat(iterator.hasNext()).isFalse(); + + try { + iterator.next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + + iterator.close(); + // statement is closed by ResultSetIterator + assertThat(stmt.isClosed()).isTrue(); + } + } + + @Test + public void iterate_empty_list() throws Exception { + dbTester.prepareDbUnit(getClass(), "feed.xml"); + + try (Connection connection = dbTester.openConnection()) { + PreparedStatement stmt = connection.prepareStatement("select * from issues where id < 0"); + FirstIntColumnIterator iterator = new FirstIntColumnIterator(stmt); + + assertThat(iterator.hasNext()).isFalse(); + } + } + + @Test + public void create_iterator_from_result_set() throws Exception { + dbTester.prepareDbUnit(getClass(), "feed.xml"); + + try (Connection connection = dbTester.openConnection()) { + PreparedStatement stmt = connection.prepareStatement("select * from issues order by id"); + ResultSet rs = stmt.executeQuery(); + FirstIntColumnIterator iterator = new FirstIntColumnIterator(rs); + + assertThat(iterator.next()).isEqualTo(10); + assertThat(iterator.next()).isEqualTo(20); + assertThat(iterator.next()).isEqualTo(30); + + iterator.close(); + assertThat(rs.isClosed()).isTrue(); + stmt.close(); + } + } + + @Test + public void remove_row_is_not_supported() throws Exception { + try (Connection connection = dbTester.openConnection()) { + PreparedStatement stmt = connection.prepareStatement("select * from issues order by id"); + FirstIntColumnIterator iterator = new FirstIntColumnIterator(stmt); + + try { + iterator.remove(); + fail(); + } catch (UnsupportedOperationException ok) { + // ok + } + + iterator.close(); + } + } + + @Test + public void fail_to_read_row() throws Exception { + dbTester.prepareDbUnit(getClass(), "feed.xml"); + + try (Connection connection = dbTester.openConnection()) { + PreparedStatement stmt = connection.prepareStatement("select * from issues order by id"); + FailIterator iterator = new FailIterator(stmt); + + assertThat(iterator.hasNext()).isTrue(); + try { + iterator.next(); + fail(); + } catch (IllegalStateException e) { + assertThat(e.getCause()).isInstanceOf(SQLException.class); + } + iterator.close(); + } + } + + private static class FirstIntColumnIterator extends ResultSetIterator<Integer> { + + public FirstIntColumnIterator(PreparedStatement stmt) throws SQLException { + super(stmt); + } + + public FirstIntColumnIterator(ResultSet rs) { + super(rs); + } + + @Override + protected Integer read(ResultSet rs) throws SQLException { + return rs.getInt(1); + } + } + + private static class FailIterator extends ResultSetIterator<Integer> { + + public FailIterator(PreparedStatement stmt) throws SQLException { + super(stmt); + } + + @Override + protected Integer read(ResultSet rs) throws SQLException { + // column does not exist + return rs.getInt(1234); + } + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/dialect/DialectUtilsTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/DialectUtilsTest.java new file mode 100644 index 00000000000..57aaa9132ce --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/DialectUtilsTest.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.dialect; + +import org.junit.Test; +import org.sonar.api.utils.MessageException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DialectUtilsTest { + + @Test + public void testFindById() { + Dialect d = DialectUtils.find("mysql", null); + assertThat(d).isInstanceOf(MySql.class); + } + + @Test + public void testFindByJdbcUrl() { + Dialect d = DialectUtils.find(null, "jdbc:mysql:foo:bar"); + assertThat(d).isInstanceOf(MySql.class); + } + + @Test(expected = MessageException.class) + public void testFindNoMatch() { + DialectUtils.find("foo", "bar"); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/dialect/H2Test.java b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/H2Test.java new file mode 100644 index 00000000000..93249a6d59c --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/H2Test.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.dialect; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class H2Test { + + H2 dialect = new H2(); + + @Test + public void matchesJdbcURL() { + assertThat(dialect.matchesJdbcURL("jdbc:h2:foo")).isTrue(); + assertThat(dialect.matchesJdbcURL("jdbc:hsql:foo")).isFalse(); + } + + @Test + public void testBooleanSqlValues() { + assertThat(dialect.getTrueSqlValue()).isEqualTo("true"); + assertThat(dialect.getFalseSqlValue()).isEqualTo("false"); + } + + @Test + public void should_configure() { + assertThat(dialect.getId()).isEqualTo("h2"); + assertThat(dialect.getActiveRecordDialectCode()).isEqualTo(".h2."); + assertThat(dialect.getDefaultDriverClassName()).isEqualTo("org.h2.Driver"); + assertThat(dialect.getValidationQuery()).isEqualTo("SELECT 1"); + } + + @Test + public void testFetchSizeForScrolling() throws Exception { + assertThat(dialect.getScrollDefaultFetchSize()).isEqualTo(200); + } + + @Test + public void h2_does_not_supportMigration() { + assertThat(dialect.supportsMigration()).isFalse(); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/dialect/MsSqlTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/MsSqlTest.java new file mode 100644 index 00000000000..0b03fa6bc0f --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/MsSqlTest.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.dialect; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MsSqlTest { + + private MsSql msSql = new MsSql(); + + @Test + public void matchesJdbcURL() { + assertThat(msSql.matchesJdbcURL("jdbc:sqlserver://localhost:1433;databasename=sonar")).isTrue(); + + assertThat(msSql.matchesJdbcURL("jdbc:hsql:foo")).isFalse(); + assertThat(msSql.matchesJdbcURL("jdbc:mysql:foo")).isFalse(); + } + + @Test + public void testBooleanSqlValues() { + assertThat(msSql.getTrueSqlValue()).isEqualTo("1"); + assertThat(msSql.getFalseSqlValue()).isEqualTo("0"); + } + + @Test + public void should_configure() { + assertThat(msSql.getId()).isEqualTo("mssql"); + assertThat(msSql.getActiveRecordDialectCode()).isEqualTo("sqlserver"); + assertThat(msSql.getDefaultDriverClassName()).isEqualTo("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + assertThat(msSql.getValidationQuery()).isEqualTo("SELECT 1"); + } + + @Test + public void do_not_support_jtds_since_5_2() throws Exception { + assertThat(msSql.matchesJdbcURL("jdbc:jtds:sqlserver://localhost;databaseName=SONAR;SelectMethod=Cursor")).isFalse(); + + } + + @Test + public void msSql_does_supportMigration() { + assertThat(msSql.supportsMigration()).isTrue(); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/dialect/MySqlTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/MySqlTest.java new file mode 100644 index 00000000000..e191124a9ed --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/MySqlTest.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.dialect; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MySqlTest { + + private MySql mySql = new MySql(); + + @Test + public void matchesJdbcURL() { + assertThat(mySql.matchesJdbcURL("jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8")).isTrue(); + assertThat(mySql.matchesJdbcURL("JDBC:MYSQL://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8")).isTrue(); + + assertThat(mySql.matchesJdbcURL("jdbc:hsql:foo")).isFalse(); + assertThat(mySql.matchesJdbcURL("jdbc:oracle:foo")).isFalse(); + } + + @Test + public void testBooleanSqlValues() { + assertThat(mySql.getTrueSqlValue()).isEqualTo("true"); + assertThat(mySql.getFalseSqlValue()).isEqualTo("false"); + } + + @Test + public void should_configure() { + assertThat(mySql.getId()).isEqualTo("mysql"); + assertThat(mySql.getActiveRecordDialectCode()).isEqualTo("mysql"); + assertThat(mySql.getDefaultDriverClassName()).isEqualTo("com.mysql.jdbc.Driver"); + assertThat(mySql.getValidationQuery()).isEqualTo("SELECT 1"); + } + + @Test + public void testFetchSizeForScrolling() throws Exception { + assertThat(mySql.getScrollDefaultFetchSize()).isEqualTo(Integer.MIN_VALUE); + assertThat(mySql.getScrollSingleRowFetchSize()).isEqualTo(Integer.MIN_VALUE); + } + + @Test + public void mysql_does_supportMigration() { + assertThat(mySql.supportsMigration()).isTrue(); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/dialect/OracleTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/OracleTest.java new file mode 100644 index 00000000000..6b3698a6e2e --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/OracleTest.java @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.dialect; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OracleTest { + + private Oracle underTest = new Oracle(); + + @Test + public void matchesJdbcURL() { + assertThat(underTest.matchesJdbcURL("jdbc:oracle:thin:@localhost/XE")).isTrue(); + assertThat(underTest.matchesJdbcURL("jdbc:hsql:foo")).isFalse(); + } + + @Test + public void testBooleanSqlValues() { + assertThat(underTest.getTrueSqlValue()).isEqualTo("1"); + assertThat(underTest.getFalseSqlValue()).isEqualTo("0"); + } + + @Test + public void should_configure() { + assertThat(underTest.getId()).isEqualTo("oracle"); + assertThat(underTest.getActiveRecordDialectCode()).isEqualTo("oracle"); + assertThat(underTest.getDefaultDriverClassName()).isEqualTo("oracle.jdbc.OracleDriver"); + assertThat(underTest.getValidationQuery()).isEqualTo("SELECT 1 FROM DUAL"); + } + + @Test + public void testFetchSizeForScrolling() throws Exception { + assertThat(underTest.getScrollDefaultFetchSize()).isEqualTo(200); + assertThat(underTest.getScrollSingleRowFetchSize()).isEqualTo(1); + } + + @Test + public void oracle_does_supportMigration() { + assertThat(underTest.supportsMigration()).isTrue(); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/dialect/PostgreSqlTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/PostgreSqlTest.java new file mode 100644 index 00000000000..c42c50d7db5 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/PostgreSqlTest.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.dialect; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PostgreSqlTest { + + PostgreSql dialect = new PostgreSql(); + + @Test + public void matchesJdbcURL() { + assertThat(dialect.matchesJdbcURL("jdbc:postgresql://localhost/sonar")).isTrue(); + assertThat(dialect.matchesJdbcURL("jdbc:hsql:foo")).isFalse(); + } + + @Test + public void should_set_connection_properties() { + assertThat(dialect.getConnectionInitStatements()).isEqualTo(PostgreSql.INIT_STATEMENTS); + } + + @Test + public void testBooleanSqlValues() { + assertThat(dialect.getTrueSqlValue()).isEqualTo("true"); + assertThat(dialect.getFalseSqlValue()).isEqualTo("false"); + } + + @Test + public void should_configure() { + assertThat(dialect.getId()).isEqualTo("postgresql"); + assertThat(dialect.getActiveRecordDialectCode()).isEqualTo("postgre"); + assertThat(dialect.getDefaultDriverClassName()).isEqualTo("org.postgresql.Driver"); + assertThat(dialect.getValidationQuery()).isEqualTo("SELECT 1"); + } + + @Test + public void testFetchSizeForScrolling() throws Exception { + assertThat(dialect.getScrollDefaultFetchSize()).isEqualTo(200); + } + + @Test + public void postgres_does_supportMigration() { + assertThat(dialect.supportsMigration()).isTrue(); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java new file mode 100644 index 00000000000..708c24f221a --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.profiling; + +import java.sql.Connection; +import java.sql.SQLException; +import org.junit.Test; +import org.sonar.test.TestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class InvocationUtilsTest { + + @Test + public void should_return_result() throws Throwable { + String toString = "toString"; + Object target = mock(Object.class); + when(target.toString()).thenReturn(toString); + + assertThat(InvocationUtils.invokeQuietly(target, Object.class.getMethod("toString"), new Object[0])).isEqualTo(toString); + } + + @Test + public void should_throw_declared_exception() throws Throwable { + Connection target = mock(Connection.class); + String failSql = "any sql"; + when(target.prepareStatement(failSql)).thenThrow(new SQLException("Expected")); + + try { + InvocationUtils.invokeQuietly(target, Connection.class.getMethod("prepareStatement", String.class), new Object[] {failSql}); + fail(); + } catch (Throwable t) { + assertThat(t).isInstanceOf(SQLException.class); + } + } + + @Test + public void should_wrap_undeclared_exception() throws Throwable { + Connection target = mock(Connection.class); + String failSql = "any sql"; + when(target.prepareStatement(failSql)).thenThrow(new SQLException("Expected")); + + try { + InvocationUtils.invokeQuietly(target, Object.class.getMethod("wait"), new Object[0]); + fail(); + } catch (Throwable t) { + assertThat(t).isInstanceOf(IllegalStateException.class); + } + } + + @Test + public void only_static_methods() { + assertThat(TestUtils.hasOnlyPrivateConstructors(InvocationUtils.class)).isTrue(); + + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java new file mode 100644 index 00000000000..3f0a29e0dea --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java @@ -0,0 +1,149 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.profiling; + +import java.io.ByteArrayInputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.sql.Timestamp; +import org.apache.commons.dbcp.BasicDataSource; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ProfiledDataSourceTest { + + @Rule + public LogTester logTester = new LogTester(); + + BasicDataSource originDataSource = mock(BasicDataSource.class); + + @Test + public void execute_and_log_statement() throws Exception { + logTester.setLevel(LoggerLevel.TRACE); + + Connection connection = mock(Connection.class); + when(originDataSource.getConnection()).thenReturn(connection); + + String sql = "select from dual"; + Statement stmt = mock(Statement.class); + when(connection.createStatement()).thenReturn(stmt); + when(stmt.execute(sql)).thenReturn(true); + + ProfiledDataSource underTest = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE); + + assertThat(underTest.getUrl()).isNull(); + assertThat(underTest.getConnection().getClientInfo()).isNull(); + final Statement statementProxy = underTest.getConnection().createStatement(); + assertThat(statementProxy.getConnection()).isNull(); + assertThat(statementProxy.execute(sql)).isTrue(); + + assertThat(logTester.logs(LoggerLevel.TRACE)).hasSize(1); + assertThat(logTester.logs(LoggerLevel.TRACE).get(0)) + .contains("sql=select from dual"); + } + + @Test + public void execute_and_log_prepared_statement_with_parameters() throws Exception { + logTester.setLevel(LoggerLevel.TRACE); + + Connection connection = mock(Connection.class); + when(originDataSource.getConnection()).thenReturn(connection); + + String sqlWithParams = "insert into polop (col1, col2, col3, col4) values (?, ?, ?, ?, ?)"; + int param1 = 42; + String param2 = "plouf"; + Date param3 = new Date(System.currentTimeMillis()); + Timestamp param4 = new Timestamp(System.currentTimeMillis()); + byte[] param5 = "blob".getBytes("UTF-8"); + + PreparedStatement preparedStatement = mock(PreparedStatement.class); + when(connection.prepareStatement(sqlWithParams)).thenReturn(preparedStatement); + when(preparedStatement.execute()).thenReturn(true); + + ProfiledDataSource ds = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE); + + assertThat(ds.getUrl()).isNull(); + assertThat(ds.getConnection().getClientInfo()).isNull(); + PreparedStatement preparedStatementProxy = ds.getConnection().prepareStatement(sqlWithParams); + preparedStatementProxy.setInt(1, param1); + preparedStatementProxy.setString(2, param2); + preparedStatementProxy.setDate(3, param3); + preparedStatementProxy.setTimestamp(4, param4); + preparedStatementProxy.setBlob(5, new ByteArrayInputStream(param5)); + assertThat(preparedStatementProxy.getConnection()).isNull(); + assertThat(preparedStatementProxy.execute()).isTrue(); + + assertThat(logTester.logs(LoggerLevel.TRACE)).hasSize(1); + assertThat(logTester.logs(LoggerLevel.TRACE).get(0)) + .contains("sql=insert into polop (col1, col2, col3, col4) values (?, ?, ?, ?, ?)") + .contains("params=42, plouf"); + } + + @Test + public void execute_and_log_prepared_statement_without_parameters() throws Exception { + logTester.setLevel(LoggerLevel.TRACE); + + Connection connection = mock(Connection.class); + when(originDataSource.getConnection()).thenReturn(connection); + + String sqlWithParams = "select from dual"; + PreparedStatement preparedStatement = mock(PreparedStatement.class); + when(connection.prepareStatement(sqlWithParams)).thenReturn(preparedStatement); + when(preparedStatement.execute()).thenReturn(true); + + ProfiledDataSource ds = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE); + + assertThat(ds.getUrl()).isNull(); + assertThat(ds.getConnection().getClientInfo()).isNull(); + PreparedStatement preparedStatementProxy = ds.getConnection().prepareStatement(sqlWithParams); + assertThat(preparedStatementProxy.getConnection()).isNull(); + assertThat(preparedStatementProxy.execute()).isTrue(); + + assertThat(logTester.logs(LoggerLevel.TRACE)).hasSize(1); + assertThat(logTester.logs(LoggerLevel.TRACE).get(0)) + .contains("sql=select from dual") + .doesNotContain("params="); + } + + @Test + public void delegate_to_underlying_data_source() throws Exception { + ProfiledDataSource proxy = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE); + + // painful to call all methods + // so using reflection to check that calls does not fail + // Limitation: methods with parameters are not tested and calls to + // underlying datasource are not verified + for (Method method : ProfiledDataSource.class.getDeclaredMethods()) { + if (method.getParameterTypes().length == 0 && Modifier.isPublic(method.getModifiers())) { + method.invoke(proxy); + } + } + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/profiling/SqlLogFormatterTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/SqlLogFormatterTest.java new file mode 100644 index 00000000000..c3f6f528c01 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/SqlLogFormatterTest.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.profiling; + +import org.junit.Test; + +import static org.apache.commons.lang.StringUtils.repeat; +import static org.assertj.core.api.Assertions.assertThat; + +public class SqlLogFormatterTest { + + @Test + public void formatSql() { + assertThat(SqlLogFormatter.formatSql("select *")).isEqualTo("select *"); + } + + @Test + public void formatSql_removes_newlines() { + assertThat(SqlLogFormatter.formatSql("select *\nfrom issues")).isEqualTo("select * from issues"); + } + + @Test + public void formatParam() { + assertThat(SqlLogFormatter.formatParam(null)).isEqualTo("[null]"); + assertThat(SqlLogFormatter.formatParam("")).isEqualTo(""); + assertThat(SqlLogFormatter.formatParam("foo")).isEqualTo("foo"); + } + + @Test + public void formatParam_escapes_newlines() { + assertThat(SqlLogFormatter.formatParam("foo\nbar\nbaz")).isEqualTo("foo\\nbar\\nbaz"); + } + + @Test + public void formatParam_truncates_if_too_long() { + String param = repeat("a", SqlLogFormatter.PARAM_MAX_WIDTH + 10); + String formattedParam = SqlLogFormatter.formatParam(param); + assertThat(formattedParam) + .hasSize(SqlLogFormatter.PARAM_MAX_WIDTH) + .endsWith("...") + .startsWith(repeat("a", SqlLogFormatter.PARAM_MAX_WIDTH - 3)); + } + + @Test + public void formatParams() { + String formattedParams = SqlLogFormatter.formatParams(new Object[] {"foo", 42, null, true}); + assertThat(formattedParams).isEqualTo("foo, 42, [null], true"); + } + + @Test + public void formatParams_returns_blank_if_zero_params() { + String formattedParams = SqlLogFormatter.formatParams(new Object[0]); + assertThat(formattedParams).isEqualTo(""); + } + + @Test + public void countArguments() { + assertThat(SqlLogFormatter.countArguments("select * from issues")).isEqualTo(0); + assertThat(SqlLogFormatter.countArguments("select * from issues where id=?")).isEqualTo(1); + assertThat(SqlLogFormatter.countArguments("select * from issues where id=? and kee=?")).isEqualTo(2); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/semaphore/SemaphoresImplTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/semaphore/SemaphoresImplTest.java new file mode 100644 index 00000000000..e83b1c608a7 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/semaphore/SemaphoresImplTest.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.db.semaphore; + +import org.junit.Test; + +public class SemaphoresImplTest { + + @Test(expected = UnsupportedOperationException.class) + public void acquire_is_unsupported() { + new SemaphoresImpl().acquire("foo"); + } + + @Test(expected = UnsupportedOperationException.class) + public void acquire_with_timeout_is_unsupported() { + new SemaphoresImpl().acquire("foo", 1, 2); + } + + @Test(expected = UnsupportedOperationException.class) + public void release_is_unsupported() { + new SemaphoresImpl().release("foo"); + } +} |