]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9639 Support CE task characteristics
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Fri, 28 Jul 2017 08:27:21 +0000 (10:27 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Mon, 7 Aug 2017 09:44:06 +0000 (11:44 +0200)
25 files changed:
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDao.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskCharacteristicMapper.java [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeTaskCharacteristicMapper.xml [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskCharacteristicDaoTest.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskCharacteristicDtoTest.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/CreateTableCeTaskCharacteristics.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v66/DbVersion66.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v66/CreateTableCeTaskCharacteristicsTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v66/DbVersion66Test.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v66/CreateTableCeTaskCharacteristicsTest/empty.sql [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/ce/ws/SubmitAction.java
server/sonar-server/src/main/java/org/sonar/server/computation/queue/ReportSubmitter.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistAnalysisStep.java
server/sonar-server/src/test/java/org/sonar/server/ce/ws/SubmitActionTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/queue/ReportSubmitterTest.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ReportPublisher.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java

index 71e6a8b17f2020b9076cca02d12d25b81ef1977d..dc65872c10b46e664f29904b1902154e8dea4f64 100644 (file)
@@ -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(
index bff4cb2eb80e2aaeb36b91ecd3f448ecb34f290d..873d38eb2d150c600e05a2ce1204b7dec09350ec 100644 (file)
@@ -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",
index c8cbe0b5ea44bf685c70e0a60a5edb9ba4cac048..cd0260a6fc16bea85497bab2c6da83bd24153fe3 100644 (file)
@@ -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,
index a6f33ee50a6cc3fd8b71168a92063ac4b3c4b408..bc5efc71ccb77fda0d0541f7febfcc6b808f64b5 100644 (file)
@@ -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,
index 2948b8469f8dbafdb52e6d44834bf597e1a875c8..5d31776cf6e29502d072c317a9192a3b8676d8df 100644 (file)
@@ -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;
   }
index 3e879c9e948eef153cc0f3e75cb311b91c5c3df1..73f39eea73b1b5450ac047799f464e32a61d3614 100644 (file)
@@ -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 (file)
index 0000000..ceebc81
--- /dev/null
@@ -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 (file)
index 0000000..993049a
--- /dev/null
@@ -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 (file)
index 0000000..492b885
--- /dev/null
@@ -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 (file)
index 0000000..002219c
--- /dev/null
@@ -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>
index 4e2c7cb812e4e4ff162a937bb8da518a5730a8cb..2ffc454abd940f86f7af069848d5506fc5fd2d9b 100644 (file)
@@ -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 (file)
index 0000000..e4d3d3b
--- /dev/null
@@ -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 (file)
index 0000000..f25b71e
--- /dev/null
@@ -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 (file)
index 0000000..79770d2
--- /dev/null
@@ -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());
+  }
+
+}
index a9bb2dc26be8a2733646d3eb83109c2a413a006f..9ea2becfe2034ed3fad43d42784019f24fa27854 100644 (file)
@@ -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 (file)
index 0000000..da3e2fb
--- /dev/null
@@ -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 (file)
index 0000000..5700844
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
index eca4c5d48140aab95f2b5bcbe5b9e4288dc2a57f..1b5dd39b5972bb25ea1fbe430db26fec95a55947 100644 (file)
  */
 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;
+  }
+
 }
index bd0cbb8d743afa8ca06a8d80bf1ee96a0bb5629b..aad44371d6fdace6dfc1ac7e76f4fea9a4ef2958 100644 (file)
  */
 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;
+  }
 }
index 5af65d9c03f2e104dbe4461442f99851d4888935..df884f39c5c2b160e93311286463cb9328459917 100644 (file)
@@ -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)
index 18635d71ac154410ccd3e743ad0a9a587d6143f9..4f08695fa3a0a3d0d8c2ddaaf4be4bae59027001 100644 (file)
  */
 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));
 
   }
 }
index 1ff0cd12cadd27d3ead0879d1fa3590503600f6f..346ed54470ac7d45c3c1fe153ded1e98dfd36092 100644 (file)
  */
 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 {
@@ -109,6 +117,33 @@ public class ReportSubmitterTest {
     underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), IOUtils.toInputStream("{binary}"));
   }
 
+  @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());
index 84eeab591f422dad22190373c202a26c5f4736a0..0815ffdf81d78b3b7c531586c667e3714805690a 100644 (file)
@@ -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();
index 2a2651e162f7d75e6d0694f22cac8f3b4fd01232..ae65b6d01b751cd2bebfe77e7e0d1840eb0c585f 100644 (file)
@@ -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"));
+  }
 
 }