From e9ac521dd1204c523dc0e497f141529e6abd4e3f Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Wed, 17 Aug 2016 16:49:34 +0200 Subject: [PATCH] SONAR-7844 add ScannerContextDao --- .../ComputeEngineContainerImplTest.java | 2 +- .../src/main/java/org/sonar/db/DaoModule.java | 10 +- .../src/main/java/org/sonar/db/DbClient.java | 7 ++ .../src/main/java/org/sonar/db/MyBatis.java | 8 +- .../LogsIteratorInputStream.java | 91 +++++++++++++++ .../db/scannercontext/ScannerContextDao.java | 84 +++++++++++++ .../sonar/db/scannercontext/package-info.java | 24 ++++ .../org/sonar/db/version/schema-h2.ddl | 1 + .../test/java/org/sonar/db/DaoModuleTest.java | 2 +- .../src/test/java/org/sonar/db/DbTester.java | 60 ++++++++-- .../LogsIteratorInputStreamTest.java | 86 ++++++++++++++ .../scannercontext/ScannerContextDaoTest.java | 110 ++++++++++++++++++ 12 files changed, 465 insertions(+), 20 deletions(-) create mode 100644 sonar-db/src/main/java/org/sonar/db/scannercontext/LogsIteratorInputStream.java create mode 100644 sonar-db/src/main/java/org/sonar/db/scannercontext/ScannerContextDao.java create mode 100644 sonar-db/src/main/java/org/sonar/db/scannercontext/package-info.java create mode 100644 sonar-db/src/test/java/org/sonar/db/scannercontext/LogsIteratorInputStreamTest.java create mode 100644 sonar-db/src/test/java/org/sonar/db/scannercontext/ScannerContextDaoTest.java diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java index 6a4709b1260..22db63201ce 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java @@ -105,7 +105,7 @@ public class ComputeEngineContainerImplTest { assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize( COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION + 26 // level 1 - + 47 // content of DaoModule + + 48 // content of DaoModule + 2 // content of EsSearchModule + 54 // content of CorePropertyDefinitions + 1 // content of CePropertyDefinitions diff --git a/sonar-db/src/main/java/org/sonar/db/DaoModule.java b/sonar-db/src/main/java/org/sonar/db/DaoModule.java index ba299a873ca..d18f35a86dd 100644 --- a/sonar-db/src/main/java/org/sonar/db/DaoModule.java +++ b/sonar-db/src/main/java/org/sonar/db/DaoModule.java @@ -27,10 +27,10 @@ import org.sonar.db.ce.CeActivityDao; import org.sonar.db.ce.CeQueueDao; import org.sonar.db.ce.CeTaskInputDao; import org.sonar.db.component.ComponentDao; +import org.sonar.db.component.ComponentKeyUpdaterDao; import org.sonar.db.component.ComponentLinkDao; import org.sonar.db.component.ResourceDao; import org.sonar.db.component.ResourceIndexDao; -import org.sonar.db.component.ComponentKeyUpdaterDao; import org.sonar.db.component.SnapshotDao; import org.sonar.db.dashboard.ActiveDashboardDao; import org.sonar.db.dashboard.DashboardDao; @@ -50,8 +50,8 @@ import org.sonar.db.measure.custom.CustomMeasureDao; import org.sonar.db.metric.MetricDao; import org.sonar.db.notification.NotificationQueueDao; import org.sonar.db.permission.PermissionDao; -import org.sonar.db.permission.template.PermissionTemplateDao; import org.sonar.db.permission.template.PermissionTemplateCharacteristicDao; +import org.sonar.db.permission.template.PermissionTemplateDao; import org.sonar.db.property.PropertiesDao; import org.sonar.db.purge.PurgeDao; import org.sonar.db.qualitygate.ProjectQgateAssociationDao; @@ -60,6 +60,7 @@ import org.sonar.db.qualitygate.QualityGateDao; import org.sonar.db.qualityprofile.ActiveRuleDao; import org.sonar.db.qualityprofile.QualityProfileDao; import org.sonar.db.rule.RuleDao; +import org.sonar.db.scannercontext.ScannerContextDao; import org.sonar.db.source.FileSourceDao; import org.sonar.db.user.AuthorDao; import org.sonar.db.user.AuthorizationDao; @@ -102,11 +103,12 @@ public class DaoModule extends Module { PermissionTemplateDao.class, PermissionTemplateCharacteristicDao.class, PropertiesDao.class, + ProjectQgateAssociationDao.class, + PurgeDao.class, QualityGateDao.class, QualityGateConditionDao.class, - ProjectQgateAssociationDao.class, QualityProfileDao.class, - PurgeDao.class, + ScannerContextDao.class, RuleDao.class, ActiveRuleDao.class, ResourceIndexDao.class, diff --git a/sonar-db/src/main/java/org/sonar/db/DbClient.java b/sonar-db/src/main/java/org/sonar/db/DbClient.java index 527b3dd7630..929813bfd66 100644 --- a/sonar-db/src/main/java/org/sonar/db/DbClient.java +++ b/sonar-db/src/main/java/org/sonar/db/DbClient.java @@ -60,6 +60,7 @@ import org.sonar.db.qualitygate.QualityGateDao; import org.sonar.db.qualityprofile.ActiveRuleDao; import org.sonar.db.qualityprofile.QualityProfileDao; import org.sonar.db.rule.RuleDao; +import org.sonar.db.scannercontext.ScannerContextDao; import org.sonar.db.source.FileSourceDao; import org.sonar.db.user.AuthorDao; import org.sonar.db.user.AuthorizationDao; @@ -121,6 +122,7 @@ public class DbClient { private final GroupDao groupDao; private final RuleDao ruleDao; private final ActiveRuleDao activeRuleDao; + private final ScannerContextDao scannerContextDao; public DbClient(Database database, MyBatis myBatis, Dao... daos) { this.database = database; @@ -177,6 +179,7 @@ public class DbClient { groupDao = getDao(map, GroupDao.class); ruleDao = getDao(map, RuleDao.class); activeRuleDao = getDao(map, ActiveRuleDao.class); + scannerContextDao = getDao(map, ScannerContextDao.class); doOnLoad(map); } @@ -385,6 +388,10 @@ public class DbClient { return activeRuleDao; } + public ScannerContextDao scannerContextDao() { + return scannerContextDao; + } + protected K getDao(Map map, Class clazz) { return (K) map.get(clazz); } diff --git a/sonar-db/src/main/java/org/sonar/db/MyBatis.java b/sonar-db/src/main/java/org/sonar/db/MyBatis.java index a4666c5c1e9..9ea48e9e2cb 100644 --- a/sonar-db/src/main/java/org/sonar/db/MyBatis.java +++ b/sonar-db/src/main/java/org/sonar/db/MyBatis.java @@ -36,6 +36,7 @@ import org.sonar.db.ce.CeQueueMapper; import org.sonar.db.ce.CeTaskInputMapper; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDtoWithSnapshotId; +import org.sonar.db.component.ComponentKeyUpdaterMapper; import org.sonar.db.component.ComponentLinkDto; import org.sonar.db.component.ComponentLinkMapper; import org.sonar.db.component.ComponentMapper; @@ -43,7 +44,6 @@ import org.sonar.db.component.FilePathWithHashDto; import org.sonar.db.component.ResourceDto; import org.sonar.db.component.ResourceIndexDto; import org.sonar.db.component.ResourceIndexMapper; -import org.sonar.db.component.ComponentKeyUpdaterMapper; import org.sonar.db.component.ResourceMapper; import org.sonar.db.component.SnapshotDto; import org.sonar.db.component.SnapshotMapper; @@ -84,13 +84,13 @@ import org.sonar.db.metric.MetricMapper; import org.sonar.db.notification.NotificationQueueDto; import org.sonar.db.notification.NotificationQueueMapper; import org.sonar.db.permission.GroupWithPermissionDto; +import org.sonar.db.permission.UserWithPermissionDto; +import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto; +import org.sonar.db.permission.template.PermissionTemplateCharacteristicMapper; import org.sonar.db.permission.template.PermissionTemplateDto; import org.sonar.db.permission.template.PermissionTemplateGroupDto; import org.sonar.db.permission.template.PermissionTemplateMapper; import org.sonar.db.permission.template.PermissionTemplateUserDto; -import org.sonar.db.permission.UserWithPermissionDto; -import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto; -import org.sonar.db.permission.template.PermissionTemplateCharacteristicMapper; import org.sonar.db.property.PropertiesMapper; import org.sonar.db.property.PropertyDto; import org.sonar.db.purge.IdUuidPair; diff --git a/sonar-db/src/main/java/org/sonar/db/scannercontext/LogsIteratorInputStream.java b/sonar-db/src/main/java/org/sonar/db/scannercontext/LogsIteratorInputStream.java new file mode 100644 index 00000000000..ab55e461818 --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/scannercontext/LogsIteratorInputStream.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scannercontext; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import org.sonar.core.util.CloseableIterator; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * An {@link InputStream} that will read from a {@link CloseableIterator} of {@link String}, inserting {@code \n} between + * each element of the Iterator. + */ +final class LogsIteratorInputStream extends InputStream { + private static final int UNSET = -1; + private static final int END_OF_STREAM = -1; + + private final Charset charset; + private final byte[] lineFeed; + private CloseableIterator logsIterator; + private byte[] buf; + private int nextChar = UNSET; + + LogsIteratorInputStream(CloseableIterator logsIterator, Charset charset) { + checkArgument(logsIterator.hasNext(), "LogsIterator can't be empty or already read"); + this.charset = charset; + this.lineFeed = "\n".getBytes(charset); + this.logsIterator = logsIterator; + } + + @Override + public int read() throws IOException { + if (nextChar == UNSET || nextChar >= buf.length) { + fill(); + if (nextChar == UNSET) { + return END_OF_STREAM; + } + } + return buf[nextChar++]; + } + + private void fill() { + if (logsIterator.hasNext()) { + byte[] line = logsIterator.next().getBytes(charset); + boolean hasNextLine = logsIterator.hasNext(); + int bufLength = hasNextLine ? (line.length + lineFeed.length) : line.length; + // empty last line + if (bufLength == 0) { + this.buf = null; + this.nextChar = UNSET; + } else { + this.buf = new byte[bufLength]; + System.arraycopy(line, 0, buf, 0, line.length); + if (hasNextLine) { + System.arraycopy(lineFeed, 0, buf, line.length, lineFeed.length); + } + this.nextChar = 0; + } + } else { + this.buf = null; + this.nextChar = UNSET; + } + } + + @Override + public void close() throws IOException { + this.logsIterator.close(); + this.buf = null; + + super.close(); + } +} diff --git a/sonar-db/src/main/java/org/sonar/db/scannercontext/ScannerContextDao.java b/sonar-db/src/main/java/org/sonar/db/scannercontext/ScannerContextDao.java new file mode 100644 index 00000000000..c294922548c --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/scannercontext/ScannerContextDao.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scannercontext; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Optional; +import org.apache.commons.io.IOUtils; +import org.sonar.api.utils.System2; +import org.sonar.core.util.CloseableIterator; +import org.sonar.db.Dao; +import org.sonar.db.DbSession; + +import static com.google.common.base.Preconditions.checkArgument; + +public class ScannerContextDao implements Dao { + + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + private final System2 system; + + public ScannerContextDao(System2 system) { + this.system = system; + } + + /** + * @throws IllegalArgumentException if {@code scannerContextLines} is empty or fully read. + */ + public void insert(DbSession dbSession, String analysisUuid, CloseableIterator scannerContextLines) { + checkArgument(scannerContextLines.hasNext(), "Scanner context can not be empty"); + long now = system.now(); + try (PreparedStatement stmt = dbSession.getConnection().prepareStatement( + "INSERT INTO scanner_context (analysis_uuid, created_at, updated_at, data) VALUES (?, ?, ?, ?)"); + InputStream inputStream = new LogsIteratorInputStream(scannerContextLines, UTF_8)) { + stmt.setString(1, analysisUuid); + stmt.setLong(2, now); + stmt.setLong(3, now); + stmt.setBinaryStream(4, inputStream); + stmt.executeUpdate(); + } catch (SQLException | IOException e) { + throw new IllegalStateException("Fail to insert scanner context for analysis " + analysisUuid, e); + } + } + + /** + * The scanner context is very likely to contain lines, which are forcefully separated by {@code \n} characters, + * whichever the platform SQ is running on ({@see LogsIteratorInputStream}). + */ + public Optional selectScannerContext(DbSession dbSession, String analysisUuid) { + try (PreparedStatement stmt = dbSession.getConnection().prepareStatement("select data from scanner_context where analysis_uuid=?")) { + stmt.setString(1, analysisUuid); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + return Optional.of(IOUtils.toString(rs.getBinaryStream(1), UTF_8)); + } + return Optional.empty(); + } + } catch (SQLException | IOException e) { + throw new IllegalStateException("Fail to retrieve scanner context of analysis " + analysisUuid, e); + } + } + +} diff --git a/sonar-db/src/main/java/org/sonar/db/scannercontext/package-info.java b/sonar-db/src/main/java/org/sonar/db/scannercontext/package-info.java new file mode 100644 index 00000000000..de8b7394f01 --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/scannercontext/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.db.scannercontext; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl b/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl index 7559720c657..3aeed281ed8 100644 --- a/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl +++ b/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl @@ -544,6 +544,7 @@ CREATE TABLE "CE_ACTIVITY" ( ); CREATE TABLE "CE_TASK_INPUT" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), "TASK_UUID" VARCHAR(40) NOT NULL, "DATA" BLOB(167772150), "CREATED_AT" BIGINT NOT NULL, diff --git a/sonar-db/src/test/java/org/sonar/db/DaoModuleTest.java b/sonar-db/src/test/java/org/sonar/db/DaoModuleTest.java index 5e8d8910d50..f653c76bfce 100644 --- a/sonar-db/src/test/java/org/sonar/db/DaoModuleTest.java +++ b/sonar-db/src/test/java/org/sonar/db/DaoModuleTest.java @@ -29,6 +29,6 @@ public class DaoModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new DaoModule().configure(container); - assertThat(container.size()).isEqualTo(2 + 47); + assertThat(container.size()).isEqualTo(2 + 48); } } diff --git a/sonar-db/src/test/java/org/sonar/db/DbTester.java b/sonar-db/src/test/java/org/sonar/db/DbTester.java index b729146fbf1..04e229a2d3f 100644 --- a/sonar-db/src/test/java/org/sonar/db/DbTester.java +++ b/sonar-db/src/test/java/org/sonar/db/DbTester.java @@ -20,8 +20,7 @@ package org.sonar.db; import com.google.common.base.Joiner; -import com.google.common.base.Preconditions; -import com.google.common.collect.Collections2; +import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import java.io.InputStream; @@ -59,6 +58,7 @@ import org.junit.rules.ExternalResource; import org.picocontainer.containers.TransientPicoContainer; import org.sonar.api.utils.System2; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.newHashMap; import static java.sql.ResultSetMetaData.columnNoNulls; @@ -133,7 +133,7 @@ public class DbTester extends ExternalResource { } public void executeUpdateSql(String sql, Object... params) { - try (Connection connection = db.getDatabase().getDataSource().getConnection()) { + try (Connection connection = getConnection()) { new QueryRunner().update(connection, sql, params); } catch (Exception e) { throw new IllegalStateException("Fail to execute sql: " + sql, e); @@ -186,8 +186,16 @@ public class DbTester extends ExternalResource { *
int issues = countRowsOfTable("issues")
*/ public int countRowsOfTable(String tableName) { - Preconditions.checkArgument(StringUtils.containsNone(tableName, " "), "Parameter must be the name of a table. Got " + tableName); - return countSql("select count(1) from " + tableName.toLowerCase(Locale.ENGLISH)); + return countRowsOfTable(tableName, this::getConnection); + } + + public int countRowsOfTable(DbSession dbSession, String tableName) { + return countRowsOfTable(tableName, () -> dbSession.getConnection()); + } + + private int countRowsOfTable(String tableName, SqlExceptionSupplier 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); } /** @@ -195,10 +203,18 @@ public class DbTester extends ExternalResource { *
int OpenIssues = countSql("select count('id') from issues where status is not null")
*/ public int countSql(String sql) { - Preconditions.checkArgument(StringUtils.contains(sql, "count("), + return countSql(sql, this::getConnection); + } + + public int countSql(DbSession dbSession, String sql) { + return countSql(sql, () -> dbSession.getConnection()); + } + + private int countSql(String sql, SqlExceptionSupplier connectionSupplier) { + checkArgument(StringUtils.contains(sql, "count("), "Parameter must be a SQL request containing 'count(x)' function. Got " + sql); try ( - Connection connection = db.getDatabase().getDataSource().getConnection(); + Connection connection = connectionSupplier.get(); PreparedStatement stmt = connection.prepareStatement(sql); ResultSet rs = stmt.executeQuery()) { if (rs.next()) { @@ -212,8 +228,12 @@ public class DbTester extends ExternalResource { } public List> select(String selectSql) { + return select(selectSql, this::getConnection); + } + + private List> select(String selectSql, SqlExceptionSupplier connectionSupplier) { try ( - Connection connection = db.getDatabase().getDataSource().getConnection(); + Connection connection = connectionSupplier.get(); PreparedStatement stmt = connection.prepareStatement(selectSql); ResultSet rs = stmt.executeQuery()) { return getHashMap(rs); @@ -223,7 +243,15 @@ public class DbTester extends ExternalResource { } public Map selectFirst(String selectSql) { - List> rows = select(selectSql); + return selectFirst(selectSql, this::getConnection); + } + + public Map selectFirst(DbSession dbSession, String selectSql) { + return selectFirst(selectSql, () -> dbSession.getConnection()); + } + + private Map selectFirst(String selectSql, SqlExceptionSupplier connectionSupplier) { + List> rows = select(selectSql, connectionSupplier); if (rows.isEmpty()) { throw new IllegalStateException("No results for " + selectSql); } else if (rows.size() > 1) { @@ -365,7 +393,7 @@ public class DbTester extends ExternalResource { } public void assertColumnDefinition(String table, String column, int expectedType, @Nullable Integer expectedSize, @Nullable Boolean isNullable) { - try (Connection connection = db.getDatabase().getDataSource().getConnection(); + try (Connection connection = getConnection(); PreparedStatement stmt = connection.prepareStatement("select * from " + table); ResultSet res = stmt.executeQuery()) { Integer columnIndex = getColumnIndex(res, column); @@ -456,6 +484,10 @@ public class DbTester extends ExternalResource { @Deprecated public Connection openConnection() throws Exception { + return getConnection(); + } + + private Connection getConnection() throws SQLException { return db.getDatabase().getDataSource().getConnection(); } @@ -468,4 +500,12 @@ public class DbTester extends ExternalResource { return db.getCommands(); } + /** + * A {@link Supplier} that declares the checked exception {@link SQLException}. + */ + @FunctionalInterface + private interface SqlExceptionSupplier { + T get() throws SQLException; + } + } diff --git a/sonar-db/src/test/java/org/sonar/db/scannercontext/LogsIteratorInputStreamTest.java b/sonar-db/src/test/java/org/sonar/db/scannercontext/LogsIteratorInputStreamTest.java new file mode 100644 index 00000000000..06d6e1b3b79 --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/scannercontext/LogsIteratorInputStreamTest.java @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scannercontext; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Arrays; +import org.apache.commons.io.IOUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.core.util.CloseableIterator; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LogsIteratorInputStreamTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void read_from_ClosableIterator_with_several_lines() throws IOException { + assertThat(read(create("line1", "line2", "line3"))).isEqualTo("line1" + '\n' + "line2" + '\n' + "line3"); + } + + @Test + public void read_from_ClosableIterator_with_single_line() throws IOException { + assertThat(read(create("line1"))).isEqualTo("line1"); + } + + @Test + public void read_from_ClosableIterator_with_single_empty_line() throws IOException { + assertThat(read(create(""))).isEqualTo(""); + } + + @Test + public void read_from_ClosableIterator_with_several_empty_lines() throws IOException { + assertThat(read(create("", "line2", "", "line4", "", "", "", "line8", ""))) + .isEqualTo('\n' + "line2" + '\n' + '\n' + "line4" + '\n' + '\n' + '\n' + '\n' + "line8" + '\n'); + } + + @Test + public void constructor_throws_IAE_when_ClosableIterator_is_empty() throws IOException { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("LogsIterator can't be empty or already read"); + + create(); + } + + @Test + public void constructor_throws_IAE_when_ClosableIterator_has_already_been_read() throws IOException { + CloseableIterator iterator = CloseableIterator.from(Arrays.asList("line1").iterator()); + + // read iterator to the end + iterator.next(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("LogsIterator can't be empty or already read"); + + new LogsIteratorInputStream(iterator, Charset.forName("UTF-8")); + } + + private static LogsIteratorInputStream create(String... lines) { + return new LogsIteratorInputStream(CloseableIterator.from(Arrays.asList(lines).iterator()), Charset.forName("UTF-8")); + } + + private static String read(LogsIteratorInputStream logsIteratorInputStream) throws IOException { + return IOUtils.toString(logsIteratorInputStream, "UTF-8"); + } +} diff --git a/sonar-db/src/test/java/org/sonar/db/scannercontext/ScannerContextDaoTest.java b/sonar-db/src/test/java/org/sonar/db/scannercontext/ScannerContextDaoTest.java new file mode 100644 index 00000000000..ef635a53f2d --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/scannercontext/ScannerContextDaoTest.java @@ -0,0 +1,110 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scannercontext; + +import java.util.Collections; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.core.util.CloseableIterator; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; + +import static java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class ScannerContextDaoTest { + + private static final String TABLE_NAME = "scanner_context"; + private static final String SOME_UUID = "some UUID"; + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private System2 system = mock(System2.class); + private DbSession dbSession = dbTester.getSession(); + + private ScannerContextDao underTest = new ScannerContextDao(system); + + @Test + public void selectScannerContext_returns_empty_on_empty_table() { + assertThat(underTest.selectScannerContext(dbSession, SOME_UUID)).isEmpty(); + } + + @Test + public void selectScannerContext_returns_empty_when_no_row_exist_for_analysisUuid() { + String data = "some data"; + underTest.insert(dbSession, SOME_UUID, scannerContextInputStreamOf(data)); + dbSession.commit(); + + assertThat(underTest.selectScannerContext(dbSession, "OTHER_uuid")).isEmpty(); + assertThat(underTest.selectScannerContext(dbSession, SOME_UUID)).contains(data); + } + + @Test + public void insert_fails_with_IAE_if_data_is_empty() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Scanner context can not be empty"); + + underTest.insert(dbSession, SOME_UUID, CloseableIterator.emptyCloseableIterator()); + } + + @Test + public void insert_fails_with_IAE_if_data_is_fully_read() { + CloseableIterator iterator = scannerContextInputStreamOf("aa"); + iterator.next(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Scanner context can not be empty"); + + underTest.insert(dbSession, SOME_UUID, iterator); + } + + @Test + public void insert_fails_if_row_already_exists_for_analysis_uuid() { + underTest.insert(dbSession, SOME_UUID, scannerContextInputStreamOf("bla")); + dbSession.commit(); + + assertThat(dbTester.countRowsOfTable(dbSession, TABLE_NAME)).isEqualTo(1); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Fail to insert scanner context for analysis " + SOME_UUID); + + underTest.insert(dbSession, SOME_UUID, scannerContextInputStreamOf("blo")); + } + + @Test + public void insert_and_select_line_reader() { + String scannerContext = "line 1" + lineSeparator() + "line 2" + lineSeparator() + "line 3"; + underTest.insert(dbSession, SOME_UUID, scannerContextInputStreamOf(scannerContext)); + dbSession.commit(true); + + assertThat(underTest.selectScannerContext(dbSession, SOME_UUID)).contains(scannerContext); + } + + private static CloseableIterator scannerContextInputStreamOf(String data) { + return CloseableIterator.from(Collections.singleton(data).iterator()); + } + +} -- 2.39.5