diff options
25 files changed, 694 insertions, 50 deletions
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 71e6a8b17f2..dc65872c10b 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 @@ -129,7 +129,7 @@ public class ComputeEngineContainerImplTest { ); assertThat(picoContainer.getParent().getParent().getComponentAdapters()).hasSize( CONTAINER_ITSELF - + 11 // MigrationConfigurationModule + + 12 // MigrationConfigurationModule + 17 // level 2 ); assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize( diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java index bff4cb2eb80..873d38eb2d1 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java @@ -50,6 +50,7 @@ public final class SqTables { "active_rule_parameters", "ce_activity", "ce_queue", + "ce_task_characteristics", "ce_task_input", "ce_scanner_context", "default_qprofiles", diff --git a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl index c8cbe0b5ea4..cd0260a6fc1 100644 --- a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl +++ b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl @@ -610,11 +610,20 @@ CREATE TABLE "CE_ACTIVITY" ( "ERROR_MESSAGE" VARCHAR(1000), "ERROR_STACKTRACE" CLOB(2147483647) ); + CREATE UNIQUE INDEX "CE_ACTIVITY_UUID" ON "CE_ACTIVITY" ("UUID"); CREATE INDEX "CE_ACTIVITY_COMPONENT_UUID" ON "CE_ACTIVITY" ("COMPONENT_UUID"); CREATE INDEX "CE_ACTIVITY_ISLASTKEY" ON "CE_ACTIVITY" ("IS_LAST_KEY"); CREATE INDEX "CE_ACTIVITY_ISLAST_STATUS" ON "CE_ACTIVITY" ("IS_LAST", "STATUS"); +CREATE TABLE "CE_TASK_CHARACTERISTICS" ( + "UUID" VARCHAR(40) NOT NULL PRIMARY KEY, + "TASK_UUID" VARCHAR(40) NOT NULL, + "KEE" VARCHAR(50) NOT NULL, + "TEXT_VALUE" VARCHAR(4000) +); +CREATE INDEX "CE_TASK_CHARACTERISTICS_TASK_UUID" ON "CE_TASK_CHARACTERISTICS" ("TASK_UUID"); + CREATE TABLE "CE_TASK_INPUT" ( "TASK_UUID" VARCHAR(40) NOT NULL PRIMARY KEY, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java index a6f33ee50a6..bc5efc71ccb 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java @@ -25,6 +25,7 @@ import org.sonar.core.platform.Module; import org.sonar.db.ce.CeActivityDao; import org.sonar.db.ce.CeQueueDao; import org.sonar.db.ce.CeScannerContextDao; +import org.sonar.db.ce.CeTaskCharacteristicDao; import org.sonar.db.ce.CeTaskInputDao; import org.sonar.db.component.ComponentDao; import org.sonar.db.component.ComponentKeyUpdaterDao; @@ -79,6 +80,7 @@ public class DaoModule extends Module { CeActivityDao.class, CeQueueDao.class, CeScannerContextDao.class, + CeTaskCharacteristicDao.class, CeTaskInputDao.class, ComponentDao.class, ComponentKeyUpdaterDao.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java index 2948b8469f8..5d31776cf6e 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java @@ -24,6 +24,7 @@ import java.util.Map; import org.sonar.db.ce.CeActivityDao; import org.sonar.db.ce.CeQueueDao; import org.sonar.db.ce.CeScannerContextDao; +import org.sonar.db.ce.CeTaskCharacteristicDao; import org.sonar.db.ce.CeTaskInputDao; import org.sonar.db.component.ComponentDao; import org.sonar.db.component.ComponentKeyUpdaterDao; @@ -98,6 +99,7 @@ public class DbClient { private final CeActivityDao ceActivityDao; private final CeQueueDao ceQueueDao; private final CeTaskInputDao ceTaskInputDao; + private final CeTaskCharacteristicDao ceTaskCharacteristicsDao; private final CeScannerContextDao ceScannerContextDao; private final FileSourceDao fileSourceDao; private final ComponentLinkDao componentLinkDao; @@ -154,6 +156,7 @@ public class DbClient { ceActivityDao = getDao(map, CeActivityDao.class); ceQueueDao = getDao(map, CeQueueDao.class); ceTaskInputDao = getDao(map, CeTaskInputDao.class); + ceTaskCharacteristicsDao = getDao(map, CeTaskCharacteristicDao.class); ceScannerContextDao = getDao(map, CeScannerContextDao.class); fileSourceDao = getDao(map, FileSourceDao.class); componentLinkDao = getDao(map, ComponentLinkDao.class); @@ -285,6 +288,10 @@ public class DbClient { return ceTaskInputDao; } + public CeTaskCharacteristicDao ceTaskCharacteristicsDao() { + return ceTaskCharacteristicsDao; + } + public CeScannerContextDao ceScannerContextDao() { return ceScannerContextDao; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index 3e879c9e948..73f39eea73b 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -32,6 +32,8 @@ import org.sonar.api.Startable; import org.sonar.db.ce.CeActivityMapper; import org.sonar.db.ce.CeQueueMapper; import org.sonar.db.ce.CeScannerContextMapper; +import org.sonar.db.ce.CeTaskCharacteristicDto; +import org.sonar.db.ce.CeTaskCharacteristicMapper; import org.sonar.db.ce.CeTaskInputMapper; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDtoWithSnapshotId; @@ -137,6 +139,7 @@ public class MyBatis implements Startable { // DTO aliases, keep them sorted alphabetically confBuilder.loadAlias("ActiveRule", ActiveRuleDto.class); confBuilder.loadAlias("ActiveRuleParam", ActiveRuleParamDto.class); + confBuilder.loadAlias("CeTaskCharacteristic", CeTaskCharacteristicDto.class); confBuilder.loadAlias("Component", ComponentDto.class); confBuilder.loadAlias("ComponentLink", ComponentLinkDto.class); confBuilder.loadAlias("ComponentWithSnapshot", ComponentDtoWithSnapshotId.class); @@ -188,6 +191,7 @@ public class MyBatis implements Startable { CeQueueMapper.class, CeScannerContextMapper.class, CeTaskInputMapper.class, + CeTaskCharacteristicMapper.class, ComponentKeyUpdaterMapper.class, ComponentLinkMapper.class, ComponentMapper.class, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDao.java new file mode 100644 index 00000000000..ceebc8134d0 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDao.java @@ -0,0 +1,47 @@ +/* + * 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.ce; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.sonar.db.Dao; +import org.sonar.db.DbSession; + +public class CeTaskCharacteristicDao implements Dao { + public void insert(DbSession dbSession, Collection<CeTaskCharacteristicDto> characteristics) { + for (CeTaskCharacteristicDto dto : characteristics) { + mapper(dbSession).insert(dto); + } + } + + public Map<String, String> getTaskCharacteristics(DbSession dbSession, String taskUuid) { + Map<String, String> map = new LinkedHashMap<>(); + List<CeTaskCharacteristicDto> characteristics = mapper(dbSession).selectTaskCharacteristics(taskUuid); + characteristics.stream().forEach(dto -> map.put(dto.getKey(), dto.getValue())); + return map; + } + + private static CeTaskCharacteristicMapper mapper(DbSession session) { + return session.getMapper(CeTaskCharacteristicMapper.class); + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDto.java new file mode 100644 index 00000000000..993049aa93d --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDto.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.ce; + +public class CeTaskCharacteristicDto { + private String uuid; + private String taskUuid; + private String key; + private String value; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getTaskUuid() { + return taskUuid; + } + + public void setTaskUuid(String taskUuid) { + this.taskUuid = taskUuid; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicMapper.java new file mode 100644 index 00000000000..492b88505e8 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicMapper.java @@ -0,0 +1,31 @@ +/* + * 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.ce; + +import java.util.List; + +import org.apache.ibatis.annotations.Param; + +public interface CeTaskCharacteristicMapper { + List<CeTaskCharacteristicDto> selectTaskCharacteristics(@Param("taskUuid") String taskUuid); + + void insert(CeTaskCharacteristicDto taskCharacteristic); + +} diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeTaskCharacteristicMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeTaskCharacteristicMapper.xml new file mode 100644 index 00000000000..002219c4b71 --- /dev/null +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeTaskCharacteristicMapper.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd"> + +<mapper namespace="org.sonar.db.ce.CeTaskCharacteristicMapper"> + <insert id="insert" parameterType="CeTaskCharacteristic" useGeneratedKeys="false"> + insert into ce_task_characteristics ( + uuid, + task_uuid, + kee, + text_value + ) + values ( + #{uuid,jdbcType=VARCHAR}, + #{taskUuid,jdbcType=VARCHAR}, + #{key,jdbcType=VARCHAR}, + #{value,jdbcType=VARCHAR} + ) + </insert> + + <select id="selectTaskCharacteristics" parameterType="map" resultType="CeTaskCharacteristic"> + SELECT + c.uuid as "uuid", + c.task_uuid as "taskUuid", + c.kee as "key", + c.text_value as "value" + FROM + ce_task_characteristics c + WHERE + c.task_uuid = #{taskUuid,jdbcType=VARCHAR} + </select> +</mapper> diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java index 4e2c7cb812e..2ffc454abd9 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java +++ b/server/sonar-db-dao/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 + 46); + assertThat(container.size()).isEqualTo(2 + 47); } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskCharacteristicDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskCharacteristicDaoTest.java new file mode 100644 index 00000000000..e4d3d3b9f9a --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskCharacteristicDaoTest.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.ce; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +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.db.DbTester; + +public class CeTaskCharacteristicDaoTest { + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private CeTaskCharacteristicDao underTest = new CeTaskCharacteristicDao(); + + @Test + public void test_insert() { + CeTaskCharacteristicDto dto = new CeTaskCharacteristicDto(); + dto.setKey("key"); + dto.setValue("value"); + dto.setUuid("uuid"); + dto.setTaskUuid("task"); + underTest.insert(dbTester.getSession(), Collections.singletonList(dto)); + dbTester.getSession().commit(); + + assertThat(underTest.getTaskCharacteristics(dbTester.getSession(), "task")).containsOnly(entry("key", "value")); + } + + @Test + public void test_no_result() { + assertThat(underTest.getTaskCharacteristics(dbTester.getSession(), "task")).isEmpty(); + + } +} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskCharacteristicDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskCharacteristicDtoTest.java new file mode 100644 index 00000000000..f25b71ebc98 --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskCharacteristicDtoTest.java @@ -0,0 +1,41 @@ +/* + * 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.ce; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +public class CeTaskCharacteristicDtoTest { + + @Test + public void test_set_get() { + CeTaskCharacteristicDto dto = new CeTaskCharacteristicDto(); + dto.setKey("key"); + dto.setValue("value"); + dto.setTaskUuid("task"); + dto.setUuid("uuid"); + assertThat(dto.getKey()).isEqualTo("key"); + assertThat(dto.getValue()).isEqualTo("value"); + assertThat(dto.getTaskUuid()).isEqualTo("task"); + assertThat(dto.getUuid()).isEqualTo("uuid"); + + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/CreateTableCeTaskCharacteristics.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/CreateTableCeTaskCharacteristics.java new file mode 100644 index 00000000000..79770d26fc0 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/CreateTableCeTaskCharacteristics.java @@ -0,0 +1,84 @@ +/* + * 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.server.platform.db.migration.version.v66; + +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +import java.sql.SQLException; + +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.def.VarcharColumnDef; +import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder; +import org.sonar.server.platform.db.migration.sql.CreateTableBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +public class CreateTableCeTaskCharacteristics extends DdlChange { + private static final String TABLE_NAME = "ce_task_characteristics"; + + public CreateTableCeTaskCharacteristics(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + VarcharColumnDef uuidColumn = newVarcharColumnDefBuilder() + .setColumnName("uuid") + .setLimit(UUID_SIZE) + .setIsNullable(false) + .setIgnoreOracleUnit(true) + .build(); + VarcharColumnDef ceTaskUuidColumn = newVarcharColumnDefBuilder() + .setColumnName("task_uuid") + .setLimit(UUID_SIZE) + .setIsNullable(false) + .setIgnoreOracleUnit(true) + .build(); + VarcharColumnDef keyColumn = newVarcharColumnDefBuilder() + .setColumnName("kee") + .setLimit(512) + .setIsNullable(false) + .setIgnoreOracleUnit(true) + .build(); + VarcharColumnDef valueColumn = newVarcharColumnDefBuilder() + .setColumnName("text_value") + .setLimit(512) + .setIsNullable(true) + .setIgnoreOracleUnit(true) + .build(); + + context.execute( + new CreateTableBuilder(getDialect(), TABLE_NAME) + .addPkColumn(uuidColumn) + .addColumn(ceTaskUuidColumn) + .addColumn(keyColumn) + .addColumn(valueColumn) + .build()); + + context.execute( + new CreateIndexBuilder(getDialect()) + .setTable(TABLE_NAME) + .setName("ce_characteristics_" + ceTaskUuidColumn.getName()) + .addColumn(ceTaskUuidColumn) + .setUnique(false) + .build()); + } + +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/DbVersion66.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/DbVersion66.java index a9bb2dc26be..9ea2becfe20 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/DbVersion66.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/DbVersion66.java @@ -26,6 +26,8 @@ import org.sonar.server.platform.db.migration.version.DbVersion; public class DbVersion66 implements DbVersion { @Override public void addSteps(MigrationStepRegistry registry) { - registry.add(1800, "Add incremental column to snapthots table", AddIncrementalColumnToSnapshotsTable.class); + registry + .add(1800, "Add incremental column to snapthots table", AddIncrementalColumnToSnapshotsTable.class) + .add(1801, "Create table CE task characteristics", CreateTableCeTaskCharacteristics.class); } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v66/CreateTableCeTaskCharacteristicsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v66/CreateTableCeTaskCharacteristicsTest.java new file mode 100644 index 00000000000..da3e2fbd9e4 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v66/CreateTableCeTaskCharacteristicsTest.java @@ -0,0 +1,61 @@ +/* + * 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.server.platform.db.migration.version.v66; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.sql.SQLException; +import java.sql.Types; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.db.CoreDbTester; + +public class CreateTableCeTaskCharacteristicsTest { + private static final String TABLE = "ce_task_characteristics"; + + @Rule + public final CoreDbTester db = CoreDbTester.createForSchema(CreateTableCeTaskCharacteristicsTest.class, "empty.sql"); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private CreateTableCeTaskCharacteristics underTest = new CreateTableCeTaskCharacteristics(db.database()); + + @Test + public void creates_table_on_empty_db() throws SQLException { + underTest.execute(); + + assertThat(db.countRowsOfTable(TABLE)).isEqualTo(0); + db.assertPrimaryKey(TABLE, "pk_" + TABLE, "uuid"); + db.assertColumnDefinition(TABLE, "task_uuid", Types.VARCHAR, 40, false); + db.assertColumnDefinition(TABLE, "kee", Types.VARCHAR, 512, false); + db.assertColumnDefinition(TABLE, "text_value", Types.VARCHAR, 512, true); + } + + @Test + public void migration_is_not_reentrant() throws SQLException { + underTest.execute(); + + expectedException.expect(IllegalStateException.class); + + underTest.execute(); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v66/DbVersion66Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v66/DbVersion66Test.java new file mode 100644 index 00000000000..57008442615 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v66/DbVersion66Test.java @@ -0,0 +1,39 @@ +/* + * 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.server.platform.db.migration.version.v66; + +import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationCount; +import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber; + +import org.junit.Test; + +public class DbVersion66Test { + private DbVersion66 underTest = new DbVersion66(); + + @Test + public void migrationNumber_starts_at_1800() { + verifyMinimumMigrationNumber(underTest, 1800); + } + + @Test + public void verify_migration_count() { + verifyMigrationCount(underTest, 2); + } +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v66/CreateTableCeTaskCharacteristicsTest/empty.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v66/CreateTableCeTaskCharacteristicsTest/empty.sql new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v66/CreateTableCeTaskCharacteristicsTest/empty.sql diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/SubmitAction.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/SubmitAction.java index eca4c5d4814..1b5dd39b597 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/SubmitAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/SubmitAction.java @@ -19,8 +19,13 @@ */ package org.sonar.server.ce.ws; +import static org.sonar.server.ws.WsUtils.checkRequest; + import java.io.BufferedInputStream; import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.Map; + import org.apache.commons.lang.StringUtils; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; @@ -38,6 +43,7 @@ public class SubmitAction implements CeWsAction { private static final String PARAM_PROJECT_BRANCH = "projectBranch"; private static final String PARAM_PROJECT_NAME = "projectName"; private static final String PARAM_REPORT_DATA = "report"; + private static final String PARAM_ANALYSIS_CHARACTERISTIC = "characteristic"; private final ReportSubmitter reportSubmitter; private final DefaultOrganizationProvider defaultOrganizationProvider; @@ -85,6 +91,13 @@ public class SubmitAction implements CeWsAction { .createParam(PARAM_REPORT_DATA) .setRequired(true) .setDescription("Report file. Format is not an API, it changes among SonarQube versions."); + + action + .createParam(PARAM_ANALYSIS_CHARACTERISTIC) + .setRequired(false) + .setDescription("Optional characteristic of the analysis. Can be repeated to define multiple characteristics.") + .setExampleValue("incremental=true") + .setSince("6.6"); } @Override @@ -96,8 +109,10 @@ public class SubmitAction implements CeWsAction { String projectBranch = wsRequest.param(PARAM_PROJECT_BRANCH); String projectName = StringUtils.defaultIfBlank(wsRequest.param(PARAM_PROJECT_NAME), projectKey); + Map<String, String> characteristics = parseTaskCharacteristics(wsRequest); + try (InputStream report = new BufferedInputStream(wsRequest.mandatoryParamAsPart(PARAM_REPORT_DATA).getInputStream())) { - CeTask task = reportSubmitter.submit(organizationKey, projectKey, projectBranch, projectName, report); + CeTask task = reportSubmitter.submit(organizationKey, projectKey, projectBranch, projectName, characteristics, report); WsCe.SubmitResponse submitResponse = WsCe.SubmitResponse.newBuilder() .setTaskId(task.getUuid()) .setProjectId(task.getComponentUuid()) @@ -105,4 +120,17 @@ public class SubmitAction implements CeWsAction { WsUtils.writeProtobuf(submitResponse, wsRequest, wsResponse); } } + + private static Map<String, String> parseTaskCharacteristics(Request wsRequest) { + Map<String, String> characteristics = new LinkedHashMap<>(); + + for (String param : wsRequest.multiParam(PARAM_ANALYSIS_CHARACTERISTIC)) { + String[] pair = StringUtils.split(param, "=", 2); + checkRequest(pair.length == 2, "Parameter '%s' must be a key-value pair with the format 'key=value'.", PARAM_ANALYSIS_CHARACTERISTIC); + checkRequest(!characteristics.containsKey(pair[0]), "Key '%s' was provided twice with parameters '%s'", pair[0], PARAM_ANALYSIS_CHARACTERISTIC); + characteristics.put(pair[0], pair[1]); + } + return characteristics; + } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/queue/ReportSubmitter.java b/server/sonar-server/src/main/java/org/sonar/server/computation/queue/ReportSubmitter.java index bd0cbb8d743..aad44371d6f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/queue/ReportSubmitter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/queue/ReportSubmitter.java @@ -19,9 +19,20 @@ */ package org.sonar.server.computation.queue; -import com.google.common.base.Optional; +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; +import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION; +import static org.sonar.server.component.NewComponent.newComponentBuilder; +import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; + import java.io.InputStream; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + import javax.annotation.Nullable; + import org.apache.commons.lang.StringUtils; import org.sonar.api.resources.Qualifiers; import org.sonar.api.server.ServerSide; @@ -29,8 +40,10 @@ import org.sonar.ce.queue.CeQueue; import org.sonar.ce.queue.CeTask; import org.sonar.ce.queue.CeTaskSubmit; import org.sonar.core.component.ComponentKeys; +import org.sonar.core.util.UuidFactory; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.db.ce.CeTaskCharacteristicDto; import org.sonar.db.ce.CeTaskTypes; import org.sonar.db.component.ComponentDto; import org.sonar.db.organization.OrganizationDto; @@ -41,11 +54,7 @@ import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.permission.PermissionTemplateService; import org.sonar.server.user.UserSession; -import static com.google.common.base.Preconditions.checkArgument; -import static java.lang.String.format; -import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION; -import static org.sonar.server.component.NewComponent.newComponentBuilder; -import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; +import com.google.common.base.Optional; @ServerSide public class ReportSubmitter { @@ -55,21 +64,28 @@ public class ReportSubmitter { private final ComponentUpdater componentUpdater; private final PermissionTemplateService permissionTemplateService; private final DbClient dbClient; + private final UuidFactory uuidFactory; public ReportSubmitter(CeQueue queue, UserSession userSession, ComponentUpdater componentUpdater, - PermissionTemplateService permissionTemplateService, DbClient dbClient) { + PermissionTemplateService permissionTemplateService, UuidFactory uuidFactory, DbClient dbClient) { this.queue = queue; this.userSession = userSession; this.componentUpdater = componentUpdater; this.permissionTemplateService = permissionTemplateService; + this.uuidFactory = uuidFactory; this.dbClient = dbClient; } + public CeTask submit(String organizationKey, String projectKey, @Nullable String projectBranch, @Nullable String projectName, InputStream reportInput) { + return submit(organizationKey, projectKey, projectBranch, projectName, Collections.emptyMap(), reportInput); + } + /** * @throws NotFoundException if the organization with the specified key does not exist * @throws IllegalArgumentException if the organization with the specified key is not the organization of the specified project (when it already exists in DB) */ - public CeTask submit(String organizationKey, String projectKey, @Nullable String projectBranch, @Nullable String projectName, InputStream reportInput) { + public CeTask submit(String organizationKey, String projectKey, @Nullable String projectBranch, @Nullable String projectName, Map<String, String> characteristics, + InputStream reportInput) { try (DbSession dbSession = dbClient.openSession(false)) { String effectiveProjectKey = ComponentKeys.createKey(projectKey, projectBranch); OrganizationDto organizationDto = getOrganizationDtoOrFail(dbSession, organizationKey); @@ -77,7 +93,7 @@ public class ReportSubmitter { ensureOrganizationIsConsistent(opt, organizationDto); ComponentDto project = opt.or(() -> createProject(dbSession, organizationDto, projectKey, projectBranch, projectName)); checkScanPermission(project); - return submitReport(dbSession, reportInput, project); + return submitReport(dbSession, reportInput, project, characteristics); } } @@ -129,10 +145,16 @@ public class ReportSubmitter { return componentUpdater.create(dbSession, newProject, userId); } - private CeTask submitReport(DbSession dbSession, InputStream reportInput, ComponentDto project) { - // the report file must be saved before submitting the task + private CeTask submitReport(DbSession dbSession, InputStream reportInput, ComponentDto project, Map<String, String> characteristicsMap) { CeTaskSubmit.Builder submit = queue.prepareSubmit(); + List<CeTaskCharacteristicDto> characteristics = characteristicsMap.entrySet().stream() + .map(e -> toDto(submit.getUuid(), e.getKey(), e.getValue())).collect(Collectors.toList()); + + // the report file must be saved before submitting the task dbClient.ceTaskInputDao().insert(dbSession, submit.getUuid(), reportInput); + if (!characteristics.isEmpty()) { + dbClient.ceTaskCharacteristicsDao().insert(dbSession, characteristics); + } dbSession.commit(); submit.setType(CeTaskTypes.REPORT); @@ -140,4 +162,13 @@ public class ReportSubmitter { submit.setSubmitterLogin(userSession.getLogin()); return queue.submit(submit.build()); } + + private CeTaskCharacteristicDto toDto(String taskUuid, String key, String value) { + CeTaskCharacteristicDto dto = new CeTaskCharacteristicDto(); + dto.setTaskUuid(taskUuid); + dto.setKey(key); + dto.setValue(value); + dto.setUuid(uuidFactory.create()); + return dto; + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistAnalysisStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistAnalysisStep.java index 5af65d9c03f..df884f39c5c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistAnalysisStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistAnalysisStep.java @@ -19,8 +19,6 @@ */ package org.sonar.server.computation.task.projectanalysis.step; -import javax.annotation.Nullable; - import org.sonar.api.utils.System2; import org.sonar.db.DbClient; import org.sonar.db.DbSession; @@ -85,7 +83,7 @@ public class PersistAnalysisStep implements ComputationStep { @Override public void visitView(Component view) { - SnapshotDto snapshot = createAnalysis(analysisMetadataHolder.getUuid(), view, false, null); + SnapshotDto snapshot = createAnalysis(analysisMetadataHolder.getUuid(), view, false, false); updateSnapshotPeriods(snapshot); persist(snapshot, dbSession); } @@ -100,7 +98,7 @@ public class PersistAnalysisStep implements ComputationStep { snapshotDto.setPeriodDate(period.getSnapshotDate()); } - private SnapshotDto createAnalysis(String snapshotUuid, Component component, boolean setVersion, @Nullable Boolean incremental) { + private SnapshotDto createAnalysis(String snapshotUuid, Component component, boolean setVersion, boolean incremental) { String componentUuid = component.getUuid(); return new SnapshotDto() .setUuid(snapshotUuid) diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/SubmitActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/SubmitActionTest.java index 18635d71ac1..4f08695fa3a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/SubmitActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/SubmitActionTest.java @@ -19,10 +19,26 @@ */ package org.sonar.server.ce.ws; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyMapOf; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.util.Arrays; +import java.util.Map; + +import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Matchers; +import org.mockito.MockitoAnnotations; import org.sonar.ce.queue.CeTask; import org.sonar.db.ce.CeTaskTypes; import org.sonar.server.computation.queue.ReportSubmitter; @@ -34,13 +50,6 @@ import org.sonar.test.JsonAssert; import org.sonarqube.ws.MediaTypes; import org.sonarqube.ws.WsCe; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - public class SubmitActionTest { private static final CeTask A_CE_TASK = new CeTask.Builder() @@ -50,16 +59,24 @@ public class SubmitActionTest { .setComponentUuid("PROJECT_1").setSubmitterLogin("robert") .build(); + @Captor + ArgumentCaptor<Map<String, String>> map; + private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.fromUuid("org1"); private String organizationKey = defaultOrganizationProvider.get().getKey(); private ReportSubmitter reportSubmitter = mock(ReportSubmitter.class); private SubmitAction underTest = new SubmitAction(reportSubmitter, defaultOrganizationProvider); private WsActionTester tester = new WsActionTester(underTest); + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + @Test public void submit_task_to_the_queue_and_ask_for_immediate_processing() { - when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), Matchers.isNull(String.class), eq("My Project"), any(InputStream.class))) - .thenReturn(A_CE_TASK); + when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), Matchers.isNull(String.class), eq("My Project"), + anyMapOf(String.class, String.class), any(InputStream.class))).thenReturn(A_CE_TASK); WsCe.SubmitResponse submitResponse = tester.newRequest() .setParam("projectKey", "my_project") @@ -68,16 +85,38 @@ public class SubmitActionTest { .setMethod("POST") .executeProtobuf(WsCe.SubmitResponse.class); - verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), Matchers.isNull(String.class), eq("My Project"), any(InputStream.class)); + verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), Matchers.isNull(String.class), eq("My Project"), + anyMapOf(String.class, String.class), any(InputStream.class)); assertThat(submitResponse.getTaskId()).isEqualTo("TASK_1"); assertThat(submitResponse.getProjectId()).isEqualTo("PROJECT_1"); } @Test + public void submit_task_with_multiple_characteristics() { + when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), Matchers.isNull(String.class), eq("My Project"), + anyMapOf(String.class, String.class), any(InputStream.class))).thenReturn(A_CE_TASK); + + String[] characteristics = {"branch=branch1", "incremental=true", "key=value1=value2"}; + WsCe.SubmitResponse submitResponse = tester.newRequest() + .setParam("projectKey", "my_project") + .setParam("projectName", "My Project") + .setMultiParam("characteristic", Arrays.asList(characteristics)) + .setPart("report", new ByteArrayInputStream("{binary}".getBytes()), "foo.bar") + .setMethod("POST") + .executeProtobuf(WsCe.SubmitResponse.class); + + assertThat(submitResponse.getTaskId()).isEqualTo("TASK_1"); + verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), Matchers.isNull(String.class), eq("My Project"), + map.capture(), any(InputStream.class)); + + assertThat(map.getValue()).containsOnly(entry("incremental", "true"), entry("branch", "branch1"), entry("key", "value1=value2")); + } + + @Test public void test_example_json_response() { - when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), Matchers.isNull(String.class), eq("My Project"), any(InputStream.class))) - .thenReturn(A_CE_TASK); + when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), Matchers.isNull(String.class), eq("My Project"), + anyMapOf(String.class, String.class), any(InputStream.class))).thenReturn(A_CE_TASK); TestResponse wsResponse = tester.newRequest() .setParam("projectKey", "my_project") @@ -95,8 +134,8 @@ public class SubmitActionTest { */ @Test public void project_name_is_optional() { - when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), Matchers.isNull(String.class), eq("my_project"), any(InputStream.class))) - .thenReturn(A_CE_TASK); + when(reportSubmitter.submit(eq(organizationKey), eq("my_project"), Matchers.isNull(String.class), eq("my_project"), + anyMapOf(String.class, String.class), any(InputStream.class))).thenReturn(A_CE_TASK); tester.newRequest() .setParam("projectKey", "my_project") @@ -105,7 +144,8 @@ public class SubmitActionTest { .setMethod("POST") .execute(); - verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), Matchers.isNull(String.class), eq("my_project"), any(InputStream.class)); + verify(reportSubmitter).submit(eq(organizationKey), eq("my_project"), Matchers.isNull(String.class), eq("my_project"), + anyMapOf(String.class, String.class), any(InputStream.class)); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/queue/ReportSubmitterTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/queue/ReportSubmitterTest.java index 1ff0cd12cad..346ed54470a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/queue/ReportSubmitterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/queue/ReportSubmitterTest.java @@ -19,6 +19,25 @@ */ package org.sonar.server.computation.queue; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION; +import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; +import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS; +import static org.sonar.db.permission.OrganizationPermission.SCAN; + +import java.util.HashMap; +import java.util.Map; + import org.apache.commons.io.IOUtils; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; @@ -26,12 +45,15 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; import org.sonar.api.resources.Qualifiers; import org.sonar.api.utils.System2; import org.sonar.ce.queue.CeQueue; import org.sonar.ce.queue.CeQueueImpl; import org.sonar.ce.queue.CeTaskSubmit; import org.sonar.core.permission.GlobalPermissions; +import org.sonar.core.util.SequenceUuidFactory; +import org.sonar.core.util.UuidFactory; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.ce.CeTaskTypes; @@ -47,21 +69,6 @@ import org.sonar.server.favorite.FavoriteUpdater; import org.sonar.server.permission.PermissionTemplateService; import org.sonar.server.tester.UserSessionRule; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; -import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION; -import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; -import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS; -import static org.sonar.db.permission.OrganizationPermission.SCAN; - public class ReportSubmitterTest { private static final String PROJECT_KEY = "MY_PROJECT"; @@ -82,8 +89,9 @@ public class ReportSubmitterTest { private ComponentUpdater componentUpdater = mock(ComponentUpdater.class); private PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class); private FavoriteUpdater favoriteUpdater = mock(FavoriteUpdater.class); + private UuidFactory uuidFactory = new SequenceUuidFactory(); - private ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient()); + private ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, uuidFactory, db.getDbClient()); @Before public void setUp() throws Exception { @@ -110,6 +118,33 @@ public class ReportSubmitterTest { } @Test + public void submit_inserts_characteristics() { + userSession + .addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid()) + .addPermission(PROVISION_PROJECTS, db.getDefaultOrganization()); + + mockSuccessfulPrepareSubmitCall(); + ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setKey(PROJECT_KEY); + when(componentUpdater.create(any(DbSession.class), any(NewComponent.class), eq(null))).thenReturn(project); + when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(defaultOrganizationUuid), anyInt(), anyString(), + eq(PROJECT_KEY), eq(Qualifiers.PROJECT))) + .thenReturn(true); + + Map<String, String> taskCharacteristics = new HashMap<>(); + taskCharacteristics.put("incremental", "true"); + taskCharacteristics.put("pr", "mypr"); + + underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, taskCharacteristics, IOUtils.toInputStream("{binary}")); + + ArgumentCaptor<CeTaskSubmit> submittedTask = ArgumentCaptor.forClass(CeTaskSubmit.class); + verify(queue).submit(submittedTask.capture()); + String taskUuid = submittedTask.getValue().getUuid(); + + Map<String, String> insertedCharacteristics = db.getDbClient().ceTaskCharacteristicsDao().getTaskCharacteristics(db.getSession(), taskUuid); + assertThat(insertedCharacteristics).containsOnly(entry("incremental", "true"), entry("pr", "mypr")); + } + + @Test public void submit_a_report_on_existing_project() { ComponentDto project = db.components().insertPrivateProject(db.getDefaultOrganization()); userSession.logIn().addProjectPermission(SCAN_EXECUTION, project); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ReportPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ReportPublisher.java index 84eeab591f4..0815ffdf81d 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ReportPublisher.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ReportPublisher.java @@ -173,6 +173,10 @@ public class ReportPublisher implements Startable { .setParam("projectBranch", moduleHierarchy.root().getBranch()) .setPart("report", filePart); + if (analysisMode.isIncremental()) { + post.setParam("characteristic", "incremental=true"); + } + WsResponse response; try { response = wsClient.call(post).failIfNotSuccessful(); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java index 2a2651e162f..ae65b6d01b7 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java @@ -225,5 +225,36 @@ public class ReportPublisherTest { entry("organization", "MyOrg"), entry("projectKey", "struts")); } + + @Test + public void test_send_characteristics() throws Exception { + ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, moduleHierarchy, mode, mock(TempFolder.class), + new ReportPublisherStep[0]); + + when(mode.isIncremental()).thenReturn(true); + settings.setProperty(CoreProperties.PROJECT_ORGANIZATION_PROPERTY, "MyOrg"); + + WsResponse response = mock(WsResponse.class); + + PipedOutputStream out = new PipedOutputStream(); + PipedInputStream in = new PipedInputStream(out); + WsCe.SubmitResponse.newBuilder().build().writeTo(out); + out.close(); + + when(response.failIfNotSuccessful()).thenReturn(response); + when(response.contentStream()).thenReturn(in); + + when(wsClient.call(any(WsRequest.class))).thenReturn(response); + underTest.upload(temp.newFile()); + + ArgumentCaptor<WsRequest> capture = ArgumentCaptor.forClass(WsRequest.class); + verify(wsClient).call(capture.capture()); + + WsRequest wsRequest = capture.getValue(); + assertThat(wsRequest.getParams()).containsOnly( + entry("organization", "MyOrg"), + entry("projectKey", "struts"), + entry("characteristic", "incremental=true")); + } } |