Browse Source

SONAR-11310 add temporary columns to CE tables

- add main_component_uuid temporary columns to CE_QUEUE
- add main_last_key and main_component_uuid columns to CE_ACTIVITY
- back to initial paradigm in Compute Engine: even for branches/PRs, the row in table PROJECTS a task belongs to is created in api/ce/submit
- add main component concept to CeTask
- improved consistency check when processing a report task to account for row in PROJECTS now being the one of the branche/PR
- stronger validation of characteristics passed to api/ce/submit
- add api/system/migrate_data for SonarCloud online data migration
tags/7.5
Sébastien Lesaint 5 years ago
parent
commit
2c540713f9
86 changed files with 3825 additions and 657 deletions
  1. 2
    2
      server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeQueue.java
  2. 73
    38
      server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeQueueImpl.java
  3. 69
    14
      server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeTaskSubmit.java
  4. 152
    98
      server/sonar-ce-common/src/test/java/org/sonar/ce/queue/CeQueueImplTest.java
  5. 6
    4
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java
  6. 14
    1
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/BranchLoader.java
  7. 10
    44
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImpl.java
  8. 4
    3
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dbmigration/PopulateFileSourceLineCount.java
  9. 19
    9
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java
  10. 6
    6
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutorTest.java
  11. 103
    25
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplTest.java
  12. 13
    5
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest.java
  13. 39
    2
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java
  14. 75
    32
      server/sonar-ce-task/src/main/java/org/sonar/ce/task/CeTask.java
  15. 105
    0
      server/sonar-ce-task/src/test/java/org/sonar/ce/task/CeTaskComponentTest.java
  16. 35
    19
      server/sonar-ce-task/src/test/java/org/sonar/ce/task/CeTaskTest.java
  17. 1
    1
      server/sonar-ce/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListener.java
  18. 5
    6
      server/sonar-ce/src/main/java/org/sonar/ce/queue/InternalCeQueueImpl.java
  19. 1
    1
      server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeWorkerImpl.java
  20. 6
    5
      server/sonar-ce/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerTest.java
  21. 70
    40
      server/sonar-ce/src/test/java/org/sonar/ce/queue/InternalCeQueueImplTest.java
  22. 3
    3
      server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImplTest.java
  23. 3
    1
      server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeWorkerImplTest.java
  24. 16
    0
      server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
  25. 7
    6
      server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDao.java
  26. 49
    3
      server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDto.java
  27. 2
    4
      server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityMapper.java
  28. 14
    14
      server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueDao.java
  29. 54
    4
      server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueDto.java
  30. 3
    3
      server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueMapper.java
  31. 10
    10
      server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskQuery.java
  32. 3
    3
      server/sonar-db-dao/src/main/java/org/sonar/db/ce/QueueCount.java
  33. 2
    2
      server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
  34. 37
    17
      server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeActivityMapper.xml
  35. 22
    17
      server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeQueueMapper.xml
  36. 8
    8
      server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
  37. 62
    48
      server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java
  38. 73
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDtoTest.java
  39. 58
    53
      server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueDaoTest.java
  40. 19
    1
      server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueDtoTest.java
  41. 1
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueTesting.java
  42. 14
    14
      server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskQueryTest.java
  43. 3
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java
  44. 31
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivity.java
  45. 31
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueue.java
  46. 70
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeTable.java
  47. 105
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivity.java
  48. 6
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/DbVersion74.java
  49. 104
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivity.java
  50. 135
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueue.java
  51. 83
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivity.java
  52. 56
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivityTest.java
  53. 56
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueueTest.java
  54. 62
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivityTest.java
  55. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/DbVersion74Test.java
  56. 233
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivityTest.java
  57. 216
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueueTest.java
  58. 118
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivityTest.java
  59. 67
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/Row.java
  60. 26
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivityTest/ce_activity.sql
  61. 16
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueueTest/ce_queue.sql
  62. 30
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivityTest/ce_activity.sql
  63. 87
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivityTest/ce_activity.sql
  64. 77
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueueTest/ce_queue.sql
  65. 38
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivityTest/ce_activity.sql
  66. 2
    0
      server/sonar-server/src/main/java/org/sonar/server/ce/CeModule.java
  67. 169
    0
      server/sonar-server/src/main/java/org/sonar/server/ce/queue/BranchSupport.java
  68. 55
    0
      server/sonar-server/src/main/java/org/sonar/server/ce/queue/BranchSupportDelegate.java
  69. 40
    19
      server/sonar-server/src/main/java/org/sonar/server/ce/queue/ReportSubmitter.java
  70. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java
  71. 3
    3
      server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityStatusAction.java
  72. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/ce/ws/ComponentAction.java
  73. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/ce/ws/SubmitAction.java
  74. 2
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
  75. 65
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/ws/MigrateDataAction.java
  76. 2
    1
      server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java
  77. 1
    2
      server/sonar-server/src/main/java/org/sonar/server/user/AbstractUserSession.java
  78. 301
    0
      server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchReportSubmitterTest.java
  79. 166
    0
      server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchSupportTest.java
  80. 52
    27
      server/sonar-server/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java
  81. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java
  82. 18
    17
      server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityStatusActionTest.java
  83. 13
    7
      server/sonar-server/src/test/java/org/sonar/server/ce/ws/CancelActionTest.java
  84. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/ce/ws/ComponentActionTest.java
  85. 6
    3
      server/sonar-server/src/test/java/org/sonar/server/ce/ws/SubmitActionTest.java
  86. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/project/ws/UpdateVisibilityActionTest.java

+ 2
- 2
server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeQueue.java View File

@@ -57,7 +57,7 @@ public interface CeQueue {
* <p>
* This method is equivalent to calling {@code massSubmit(Collections.singletonList(submission))}.
*
* @return empty if {@code options} contains {@link SubmitOption#UNIQUE_QUEUE_PER_COMPONENT UNIQUE_QUEUE_PER_COMPONENT}
* @return empty if {@code options} contains {@link SubmitOption#UNIQUE_QUEUE_PER_MAIN_COMPONENT UNIQUE_QUEUE_PER_MAIN_COMPONENT}
* and there's already a queued task, otherwise the created task.
*/
Optional<CeTask> submit(CeTaskSubmit submission, SubmitOption... options);
@@ -102,7 +102,7 @@ public interface CeQueue {
WorkersPauseStatus getWorkersPauseStatus();

enum SubmitOption {
UNIQUE_QUEUE_PER_COMPONENT
UNIQUE_QUEUE_PER_MAIN_COMPONENT
}

enum WorkersPauseStatus {

+ 73
- 38
server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeQueueImpl.java View File

@@ -28,9 +28,11 @@ import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.log.Loggers;
@@ -48,8 +50,11 @@ import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.property.InternalProperties;

import static com.google.common.base.Preconditions.checkState;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singleton;
import static org.sonar.ce.queue.CeQueue.SubmitOption.UNIQUE_QUEUE_PER_COMPONENT;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static org.sonar.ce.queue.CeQueue.SubmitOption.UNIQUE_QUEUE_PER_MAIN_COMPONENT;
import static org.sonar.core.util.stream.MoreCollectors.toEnumSet;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.db.ce.CeQueueDto.Status.PENDING;
@@ -78,30 +83,45 @@ public class CeQueueImpl implements CeQueue {
}

@Override
public java.util.Optional<CeTask> submit(CeTaskSubmit submission, SubmitOption... options) {
public Optional<CeTask> submit(CeTaskSubmit submission, SubmitOption... options) {
return submit(submission, toSet(options));
}

private java.util.Optional<CeTask> submit(CeTaskSubmit submission, EnumSet<SubmitOption> submitOptions) {
private Optional<CeTask> submit(CeTaskSubmit submission, EnumSet<SubmitOption> submitOptions) {
try (DbSession dbSession = dbClient.openSession(false)) {
if (submitOptions.contains(UNIQUE_QUEUE_PER_COMPONENT)
&& submission.getComponentUuid() != null
&& dbClient.ceQueueDao().countByStatusAndComponentUuid(dbSession, PENDING, submission.getComponentUuid()) > 0) {
return java.util.Optional.empty();
if (submitOptions.contains(UNIQUE_QUEUE_PER_MAIN_COMPONENT)
&& submission.getComponent()
.map(component -> dbClient.ceQueueDao().countByStatusAndMainComponentUuid(dbSession, PENDING, component.getMainComponentUuid()) > 0)
.orElse(false)) {
return Optional.empty();
}
CeQueueDto taskDto = addToQueueInDb(dbSession, submission);
dbSession.commit();

ComponentDto component = null;
String componentUuid = taskDto.getComponentUuid();
if (componentUuid != null) {
component = dbClient.componentDao().selectByUuid(dbSession, componentUuid).orElse(null);
Map<String, ComponentDto> componentsByUuid = loadComponentDtos(dbSession, taskDto);
if (componentsByUuid.isEmpty()) {
return of(convertToTask(taskDto, submission.getCharacteristics(), null, null));
}
CeTask task = convertToTask(taskDto, submission.getCharacteristics(), component);
return java.util.Optional.of(task);

return of(convertToTask(taskDto, submission.getCharacteristics(),
ofNullable(taskDto.getComponentUuid()).map(componentsByUuid::get).orElse(null),
ofNullable(taskDto.getMainComponentUuid()).map(componentsByUuid::get).orElse(null)));
}
}

Map<String, ComponentDto> loadComponentDtos(DbSession dbSession, CeQueueDto taskDto) {
Set<String> componentUuids = Stream.of(taskDto.getComponentUuid(), taskDto.getMainComponentUuid())
.filter(Objects::nonNull)
.collect(MoreCollectors.toSet(2));
if (componentUuids.isEmpty()) {
return emptyMap();
}

return dbClient.componentDao().selectByUuids(dbSession, componentUuids)
.stream()
.collect(uniqueIndex(ComponentDto::uuid, 2));
}

@Override
public List<CeTask> massSubmit(Collection<CeTaskSubmit> submissions, SubmitOption... options) {
if (submissions.isEmpty()) {
@@ -109,11 +129,11 @@ public class CeQueueImpl implements CeQueue {
}

try (DbSession dbSession = dbClient.openSession(false)) {
List<CeQueueDto> taskDto = submissions.stream()
List<CeQueueDto> taskDtos = submissions.stream()
.filter(filterBySubmitOptions(options, submissions, dbSession))
.map(submission -> addToQueueInDb(dbSession, submission))
.collect(Collectors.toList());
List<CeTask> tasks = loadTasks(dbSession, taskDto);
List<CeTask> tasks = loadTasks(dbSession, taskDtos);
dbSession.commit();
return tasks;
}
@@ -122,31 +142,34 @@ public class CeQueueImpl implements CeQueue {
private Predicate<CeTaskSubmit> filterBySubmitOptions(SubmitOption[] options, Collection<CeTaskSubmit> submissions, DbSession dbSession) {
EnumSet<SubmitOption> submitOptions = toSet(options);

if (submitOptions.contains(UNIQUE_QUEUE_PER_COMPONENT)) {
Set<String> componentUuids = submissions.stream()
.map(CeTaskSubmit::getComponentUuid)
.filter(Objects::nonNull)
if (submitOptions.contains(UNIQUE_QUEUE_PER_MAIN_COMPONENT)) {
Set<String> mainComponentUuids = submissions.stream()
.map(CeTaskSubmit::getComponent)
.filter(Optional::isPresent)
.map(Optional::get)
.map(CeTaskSubmit.Component::getMainComponentUuid)
.collect(MoreCollectors.toSet(submissions.size()));
if (componentUuids.isEmpty()) {
if (mainComponentUuids.isEmpty()) {
return t -> true;
}
return new NoPendingTaskFilter(dbSession, componentUuids);
return new NoPendingTaskFilter(dbSession, mainComponentUuids);
}

return t -> true;
}

private class NoPendingTaskFilter implements Predicate<CeTaskSubmit> {
private final Map<String, Integer> queuedItemsByComponentUuid;
private final Map<String, Integer> queuedItemsByMainComponentUuid;

private NoPendingTaskFilter(DbSession dbSession, Set<String> componentUuids) {
queuedItemsByComponentUuid = dbClient.ceQueueDao().countByStatusAndComponentUuids(dbSession, PENDING, componentUuids);
private NoPendingTaskFilter(DbSession dbSession, Set<String> projectUuids) {
queuedItemsByMainComponentUuid = dbClient.ceQueueDao().countByStatusAndMainComponentUuids(dbSession, PENDING, projectUuids);
}

@Override
public boolean test(CeTaskSubmit ceTaskSubmit) {
String componentUuid = ceTaskSubmit.getComponentUuid();
return componentUuid == null || queuedItemsByComponentUuid.getOrDefault(componentUuid, 0) == 0;
return ceTaskSubmit.getComponent()
.map(component -> queuedItemsByMainComponentUuid.getOrDefault(component.getMainComponentUuid(), 0) == 0)
.orElse(true);
}
}

@@ -167,7 +190,9 @@ public class CeQueueImpl implements CeQueue {
CeQueueDto dto = new CeQueueDto();
dto.setUuid(submission.getUuid());
dto.setTaskType(submission.getType());
dto.setComponentUuid(submission.getComponentUuid());
submission.getComponent().ifPresent(component -> dto
.setComponentUuid(component.getUuid())
.setMainComponentUuid(component.getMainComponentUuid()));
dto.setStatus(PENDING);
dto.setSubmitterUuid(submission.getSubmitterUuid());
dbClient.ceQueueDao().insert(dbSession, dto);
@@ -178,7 +203,7 @@ public class CeQueueImpl implements CeQueue {
private List<CeTask> loadTasks(DbSession dbSession, List<CeQueueDto> dtos) {
// load components, if defined
Set<String> componentUuids = dtos.stream()
.map(CeQueueDto::getComponentUuid)
.flatMap(dto -> Stream.of(dto.getComponentUuid(), dto.getMainComponentUuid()))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Map<String, ComponentDto> componentsByUuid = dbClient.componentDao()
@@ -194,13 +219,15 @@ public class CeQueueImpl implements CeQueue {

List<CeTask> result = new ArrayList<>();
for (CeQueueDto dto : dtos) {
ComponentDto component = null;
if (dto.getComponentUuid() != null) {
component = componentsByUuid.get(dto.getComponentUuid());
}
ComponentDto component = ofNullable(dto.getComponentUuid())
.map(componentsByUuid::get)
.orElse(null);
ComponentDto mainComponent = ofNullable(dto.getMainComponentUuid())
.map(componentsByUuid::get)
.orElse(null);
Map<String, String> characteristics = characteristicsByTaskUuid.get(dto.getUuid()).stream()
.collect(uniqueIndex(CeTaskCharacteristicDto::getKey, CeTaskCharacteristicDto::getValue));
result.add(convertToTask(dto, characteristics, component));
result.add(convertToTask(dto, characteristics, component, mainComponent));
}
return result;
}
@@ -272,7 +299,7 @@ public class CeQueueImpl implements CeQueue {
@Override
public WorkersPauseStatus getWorkersPauseStatus() {
try (DbSession dbSession = dbClient.openSession(false)) {
java.util.Optional<String> propValue = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.COMPUTE_ENGINE_PAUSE);
Optional<String> propValue = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.COMPUTE_ENGINE_PAUSE);
if (!propValue.isPresent() || !propValue.get().equals("true")) {
return WorkersPauseStatus.RESUMED;
}
@@ -284,18 +311,26 @@ public class CeQueueImpl implements CeQueue {
}
}

CeTask convertToTask(CeQueueDto taskDto, Map<String, String> characteristics, @Nullable ComponentDto component) {
CeTask convertToTask(CeQueueDto taskDto, Map<String, String> characteristics, @Nullable ComponentDto component, @Nullable ComponentDto mainComponent) {
CeTask.Builder builder = new CeTask.Builder()
.setUuid(taskDto.getUuid())
.setType(taskDto.getTaskType())
.setSubmitterUuid(taskDto.getSubmitterUuid())
.setComponentUuid(taskDto.getComponentUuid())
.setCharacteristics(characteristics);

String componentUuid = taskDto.getComponentUuid();
if (component != null) {
builder.setComponent(new CeTask.Component(component.uuid(), component.getDbKey(), component.name()));
builder.setOrganizationUuid(component.getOrganizationUuid());
builder.setComponentKey(component.getDbKey());
builder.setComponentName(component.name());
} else if (componentUuid != null) {
builder.setComponent(new CeTask.Component(componentUuid, null, null));
}

String mainComponentUuid = taskDto.getMainComponentUuid();
if (mainComponent != null) {
builder.setMainComponent(new CeTask.Component(mainComponent.uuid(), mainComponent.getDbKey(), mainComponent.name()));
} else if (mainComponentUuid != null) {
builder.setMainComponent(new CeTask.Component(mainComponentUuid, null, null));
}

// FIXME this should be set from the CeQueueDto

+ 69
- 14
server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeTaskSubmit.java View File

@@ -20,11 +20,16 @@
package org.sonar.ce.queue;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import org.sonar.db.component.ComponentDto;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Strings.nullToEmpty;
import static java.util.Collections.unmodifiableMap;
import static java.util.Objects.requireNonNull;

@@ -33,16 +38,16 @@ public final class CeTaskSubmit {

private final String uuid;
private final String type;
private final String componentUuid;
private final Component component;
private final String submitterUuid;
private final Map<String, String> characteristics;

private CeTaskSubmit(Builder builder) {
this.uuid = requireNonNull(emptyToNull(builder.uuid));
this.type = requireNonNull(emptyToNull(builder.type));
this.componentUuid = emptyToNull(builder.componentUuid);
this.submitterUuid = emptyToNull(builder.submitterUuid);
this.characteristics = unmodifiableMap(requireNonNull(builder.characteristics));
this.uuid = requireNonNull(builder.uuid);
this.type = requireNonNull(builder.type);
this.component = builder.component;
this.submitterUuid = builder.submitterUuid;
this.characteristics = unmodifiableMap(builder.characteristics);
}

public String getType() {
@@ -53,9 +58,8 @@ public final class CeTaskSubmit {
return uuid;
}

@CheckForNull
public String getComponentUuid() {
return componentUuid;
public Optional<Component> getComponent() {
return Optional.ofNullable(component);
}

@CheckForNull
@@ -70,12 +74,12 @@ public final class CeTaskSubmit {
public static final class Builder {
private final String uuid;
private String type;
private String componentUuid;
private Component component;
private String submitterUuid;
private Map<String, String> characteristics = null;

public Builder(String uuid) {
this.uuid = uuid;
this.uuid = emptyToNull(uuid);
}

public String getUuid() {
@@ -83,12 +87,12 @@ public final class CeTaskSubmit {
}

public Builder setType(String s) {
this.type = s;
this.type = emptyToNull(s);
return this;
}

public Builder setComponentUuid(@Nullable String s) {
this.componentUuid = s;
public Builder setComponent(@Nullable Component component) {
this.component = component;
return this;
}

@@ -103,7 +107,58 @@ public final class CeTaskSubmit {
}

public CeTaskSubmit build() {
requireNonNull(uuid, "uuid can't be null");
requireNonNull(type, "type can't be null");
requireNonNull(characteristics, "characteristics can't be null");
return new CeTaskSubmit(this);
}
}

public static class Component {
private String uuid;
private String mainComponentUuid;

public Component(String uuid, String mainComponentUuid) {
this.uuid = requireNonNull(nullToEmpty(uuid), "uuid can't be null");
this.mainComponentUuid = requireNonNull(nullToEmpty(mainComponentUuid), "mainComponentUuid can't be null");
}

public static Component fromDto(ComponentDto dto) {
String uuid = dto.uuid();
return new Component(uuid, firstNonNull(dto.getMainBranchProjectUuid(), uuid));
}

public String getUuid() {
return uuid;
}

public String getMainComponentUuid() {
return mainComponentUuid;
}

@Override
public String toString() {
return "Component{" +
"uuid='" + uuid + '\'' +
", mainComponentUuid='" + mainComponentUuid + '\'' +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Component component = (Component) o;
return uuid.equals(component.uuid) && mainComponentUuid.equals(component.mainComponentUuid);
}

@Override
public int hashCode() {
return Objects.hash(uuid, mainComponentUuid);
}
}
}

+ 152
- 98
server/sonar-ce-common/src/test/java/org/sonar/ce/queue/CeQueueImplTest.java View File

@@ -30,6 +30,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.internal.TestSystem2;
import org.sonar.ce.queue.CeTaskSubmit.Component;
import org.sonar.ce.task.CeTask;
import org.sonar.core.util.UuidFactory;
import org.sonar.core.util.UuidFactoryFast;
@@ -49,8 +50,9 @@ import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.hamcrest.Matchers.startsWith;
import static org.sonar.ce.queue.CeQueue.SubmitOption.UNIQUE_QUEUE_PER_COMPONENT;
import static org.sonar.ce.queue.CeQueue.SubmitOption.UNIQUE_QUEUE_PER_MAIN_COMPONENT;

public class CeQueueImplTest {

@@ -72,7 +74,9 @@ public class CeQueueImplTest {

@Test
public void submit_returns_task_populated_from_CeTaskSubmit_and_creates_CeQueue_row() {
CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, "PROJECT_1", "submitter uuid");
String componentUuid = randomAlphabetic(3);
String mainComponentUuid = randomAlphabetic(4);
CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, new Component(componentUuid, mainComponentUuid), "submitter uuid");

CeTask task = underTest.submit(taskSubmit);

@@ -83,7 +87,7 @@ public class CeQueueImplTest {
@Test
public void submit_populates_component_name_and_key_of_CeTask_if_component_exists() {
ComponentDto componentDto = insertComponent(ComponentTesting.newPrivateProjectDto(db.organizations().insert(), "PROJECT_1"));
CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, componentDto.uuid(), null);
CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, Component.fromDto(componentDto), null);

CeTask task = underTest.submit(taskSubmit);

@@ -100,11 +104,11 @@ public class CeQueueImplTest {
}

@Test
public void submit_with_UNIQUE_QUEUE_PER_COMPONENT_creates_task_without_component_when_there_is_a_pending_task_without_component() {
public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_without_component_when_there_is_a_pending_task_without_component() {
CeTaskSubmit taskSubmit = createTaskSubmit("no_component");
CeQueueDto dto = insertPendingInQueue(null);

Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_COMPONENT);
Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);

assertThat(task).isNotEmpty();
assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -113,13 +117,13 @@ public class CeQueueImplTest {
}

@Test
public void submit_with_UNIQUE_QUEUE_PER_COMPONENT_creates_task_when_there_is_a_pending_task_for_another_component() {
String componentUuid = randomAlphabetic(5);
String otherComponentUuid = randomAlphabetic(6);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
CeQueueDto dto = insertPendingInQueue(otherComponentUuid);
public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_a_pending_task_for_another_main_component() {
String mainComponentUuid = randomAlphabetic(5);
String otherMainComponentUuid = randomAlphabetic(6);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
CeQueueDto dto = insertPendingInQueue(newComponent(otherMainComponentUuid));

Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_COMPONENT);
Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);

assertThat(task).isNotEmpty();
assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -128,12 +132,12 @@ public class CeQueueImplTest {
}

@Test
public void submit_with_UNIQUE_QUEUE_PER_COMPONENT_does_not_create_task_when_there_is_one_pending_task_for_component() {
String componentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
CeQueueDto dto = insertPendingInQueue(componentUuid);
public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_one_pending_task_for_same_main_component() {
String mainComponentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));

Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_COMPONENT);
Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);

assertThat(task).isEmpty();
assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -142,15 +146,15 @@ public class CeQueueImplTest {
}

@Test
public void submit_with_UNIQUE_QUEUE_PER_COMPONENT_does_not_create_task_when_there_is_many_pending_task_for_component() {
String componentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_many_pending_task_for_same_main_component() {
String mainComponentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> insertPendingInQueue(componentUuid))
.mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
.map(CeQueueDto::getUuid)
.toArray(String[]::new);

Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_COMPONENT);
Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);

assertThat(task).isEmpty();
assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -159,10 +163,10 @@ public class CeQueueImplTest {
}

@Test
public void submit_without_UNIQUE_QUEUE_PER_COMPONENT_creates_task_when_there_is_one_pending_task_for_component() {
String componentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
CeQueueDto dto = insertPendingInQueue(componentUuid);
public void submit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_one_pending_task_for_same_main_component() {
String mainComponentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));

CeTask task = underTest.submit(taskSubmit);

@@ -172,11 +176,11 @@ public class CeQueueImplTest {
}

@Test
public void submit_without_UNIQUE_QUEUE_PER_COMPONENT_creates_task_when_there_is_many_pending_task_for_component() {
String componentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
public void submit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_many_pending_task_for_same_main_component() {
String mainComponentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> insertPendingInQueue(componentUuid))
.mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
.map(CeQueueDto::getUuid)
.toArray(String[]::new);

@@ -191,7 +195,8 @@ public class CeQueueImplTest {

@Test
public void massSubmit_returns_tasks_for_each_CeTaskSubmit_populated_from_CeTaskSubmit_and_creates_CeQueue_row_for_each() {
CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, "PROJECT_1", "submitter uuid");
String mainComponentUuid = randomAlphabetic(10);
CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, newComponent(mainComponentUuid), "submitter uuid");
CeTaskSubmit taskSubmit2 = createTaskSubmit("some type");

List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
@@ -204,10 +209,10 @@ public class CeQueueImplTest {
}

@Test
public void massSubmit_populates_component_name_and_key_of_CeTask_if_component_exists() {
public void massSubmit_populates_component_name_and_key_of_CeTask_if_project_exists() {
ComponentDto componentDto1 = insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), "PROJECT_1"));
CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, componentDto1.uuid(), null);
CeTaskSubmit taskSubmit2 = createTaskSubmit("something", "non existing component uuid", null);
CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, Component.fromDto(componentDto1), null);
CeTaskSubmit taskSubmit2 = createTaskSubmit("something", newComponent(randomAlphabetic(12)), null);

List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));

@@ -217,11 +222,26 @@ public class CeQueueImplTest {
}

@Test
public void massSubmit_with_UNIQUE_QUEUE_PER_COMPONENT_creates_task_without_component_when_there_is_a_pending_task_without_component() {
public void massSubmit_populates_component_name_and_key_of_CeTask_if_project_and_branch_exists() {
ComponentDto project = insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), "PROJECT_1"));
ComponentDto branch1 = db.components().insertProjectBranch(project);
ComponentDto branch2 = db.components().insertProjectBranch(project);
CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, Component.fromDto(branch1), null);
CeTaskSubmit taskSubmit2 = createTaskSubmit("something", Component.fromDto(branch2), null);

List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));

assertThat(tasks).hasSize(2);
verifyCeTask(taskSubmit1, tasks.get(0), branch1, project);
verifyCeTask(taskSubmit2, tasks.get(1), branch2, project);
}

@Test
public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_without_component_when_there_is_a_pending_task_without_component() {
CeTaskSubmit taskSubmit = createTaskSubmit("no_component");
CeQueueDto dto = insertPendingInQueue(null);

List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_COMPONENT);
List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);

assertThat(tasks).hasSize(1);
assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -230,13 +250,13 @@ public class CeQueueImplTest {
}

@Test
public void massSubmit_with_UNIQUE_QUEUE_PER_COMPONENT_creates_task_when_there_is_a_pending_task_for_another_component() {
String componentUuid = randomAlphabetic(5);
String otherComponentUuid = randomAlphabetic(6);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
CeQueueDto dto = insertPendingInQueue(otherComponentUuid);
public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_a_pending_task_for_another_main_component() {
String mainComponentUuid = randomAlphabetic(5);
String otherMainComponentUuid = randomAlphabetic(6);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
CeQueueDto dto = insertPendingInQueue(newComponent(otherMainComponentUuid));

List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_COMPONENT);
List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);

assertThat(tasks).hasSize(1);
assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -245,12 +265,12 @@ public class CeQueueImplTest {
}

@Test
public void massSubmit_with_UNIQUE_QUEUE_PER_COMPONENT_does_not_create_task_when_there_is_one_pending_task_for_component() {
String componentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
CeQueueDto dto = insertPendingInQueue(componentUuid);
public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_one_pending_task_for_same_main_component() {
String mainComponentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));

List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_COMPONENT);
List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);

assertThat(tasks).isEmpty();
assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -259,15 +279,15 @@ public class CeQueueImplTest {
}

@Test
public void massSubmit_with_UNIQUE_QUEUE_PER_COMPONENT_does_not_create_task_when_there_is_many_pending_task_for_component() {
String componentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_many_pending_task_for_same_main_component() {
String mainComponentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> insertPendingInQueue(componentUuid))
.mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
.map(CeQueueDto::getUuid)
.toArray(String[]::new);

List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_COMPONENT);
List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);

assertThat(tasks).isEmpty();
assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -276,10 +296,10 @@ public class CeQueueImplTest {
}

@Test
public void massSubmit_without_UNIQUE_QUEUE_PER_COMPONENT_creates_task_when_there_is_one_pending_task_for_component() {
String componentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
CeQueueDto dto = insertPendingInQueue(componentUuid);
public void massSubmit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_one_pending_task_for_other_main_component() {
String mainComponentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));

List<CeTask> tasks = underTest.massSubmit(of(taskSubmit));

@@ -290,11 +310,11 @@ public class CeQueueImplTest {
}

@Test
public void massSubmit_without_UNIQUE_QUEUE_PER_COMPONENT_creates_task_when_there_is_many_pending_task_for_component() {
String componentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
public void massSubmit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_many_pending_task_for_other_main_component() {
String mainComponentUuid = randomAlphabetic(5);
CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> insertPendingInQueue(componentUuid))
.mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
.map(CeQueueDto::getUuid)
.toArray(String[]::new);

@@ -309,30 +329,33 @@ public class CeQueueImplTest {
}

@Test
public void massSubmit_with_UNIQUE_QUEUE_PER_COMPONENT_creates_tasks_depending_on_whether_there_is_pending_task_for_component() {
String componentUuid1 = randomAlphabetic(5);
String componentUuid2 = randomAlphabetic(6);
String componentUuid3 = randomAlphabetic(7);
String componentUuid4 = randomAlphabetic(8);
String componentUuid5 = randomAlphabetic(9);
CeTaskSubmit taskSubmit1 = createTaskSubmit("with_one_pending", componentUuid1, null);
CeQueueDto dto1 = insertPendingInQueue(componentUuid1);
CeTaskSubmit taskSubmit2 = createTaskSubmit("no_pending", componentUuid2, null);
CeTaskSubmit taskSubmit3 = createTaskSubmit("with_many_pending", componentUuid3, null);
public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_tasks_depending_on_whether_there_is_pending_task_for_same_main_component() {
String mainComponentUuid1 = randomAlphabetic(5);
String mainComponentUuid2 = randomAlphabetic(6);
String mainComponentUuid3 = randomAlphabetic(7);
String mainComponentUuid4 = randomAlphabetic(8);
String mainComponentUuid5 = randomAlphabetic(9);
CeTaskSubmit taskSubmit1 = createTaskSubmit("with_one_pending", newComponent(mainComponentUuid1), null);
CeQueueDto dto1 = insertPendingInQueue(newComponent(mainComponentUuid1));
Component componentForMainComponentUuid2 = newComponent(mainComponentUuid2);
CeTaskSubmit taskSubmit2 = createTaskSubmit("no_pending", componentForMainComponentUuid2, null);
CeTaskSubmit taskSubmit3 = createTaskSubmit("with_many_pending", newComponent(mainComponentUuid3), null);
String[] uuids3 = IntStream.range(0, 2 + new Random().nextInt(5))
.mapToObj(i -> insertPendingInQueue(componentUuid3))
.mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid3)))
.map(CeQueueDto::getUuid)
.toArray(String[]::new);
CeTaskSubmit taskSubmit4 = createTaskSubmit("no_pending_2", componentUuid4, null);
CeTaskSubmit taskSubmit5 = createTaskSubmit("with_pending_2", componentUuid5, null);
CeQueueDto dto5 = insertPendingInQueue(componentUuid5);
Component componentForMainComponentUuid4 = newComponent(mainComponentUuid4);
CeTaskSubmit taskSubmit4 = createTaskSubmit("no_pending_2", componentForMainComponentUuid4, null);
CeTaskSubmit taskSubmit5 = createTaskSubmit("with_pending_2", newComponent(mainComponentUuid5), null);
CeQueueDto dto5 = insertPendingInQueue(newComponent(mainComponentUuid5));

List<CeTask> tasks = underTest.massSubmit(of(taskSubmit1, taskSubmit2, taskSubmit3, taskSubmit4, taskSubmit5), UNIQUE_QUEUE_PER_COMPONENT);
List<CeTask> tasks = underTest.massSubmit(of(taskSubmit1, taskSubmit2, taskSubmit3, taskSubmit4, taskSubmit5), UNIQUE_QUEUE_PER_MAIN_COMPONENT);

assertThat(tasks)
.hasSize(2)
.extracting(CeTask::getComponentUuid)
.containsOnly(componentUuid2, componentUuid4);
.extracting(task -> task.getComponent().get().getUuid(),task -> task.getMainComponent().get().getUuid())
.containsOnly(tuple(componentForMainComponentUuid2.getUuid(), componentForMainComponentUuid2.getMainComponentUuid()),
tuple(componentForMainComponentUuid4.getUuid(), componentForMainComponentUuid4.getMainComponentUuid()));
assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
.extracting(CeQueueDto::getUuid)
.hasSize(1 + uuids3.length + 1 + tasks.size())
@@ -344,7 +367,7 @@ public class CeQueueImplTest {

@Test
public void cancel_pending() {
CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();

underTest.cancel(db.getSession(), queueDto);
@@ -356,7 +379,7 @@ public class CeQueueImplTest {

@Test
public void fail_to_cancel_if_in_progress() {
submit(CeTaskTypes.REPORT, "PROJECT_1");
submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(11)));
CeQueueDto ceQueueDto = db.getDbClient().ceQueueDao().peek(session, WORKER_UUID).get();

expectedException.expect(IllegalStateException.class);
@@ -367,9 +390,9 @@ public class CeQueueImplTest {

@Test
public void cancelAll_pendings_but_not_in_progress() {
CeTask inProgressTask = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask pendingTask1 = submit(CeTaskTypes.REPORT, "PROJECT_2");
CeTask pendingTask2 = submit(CeTaskTypes.REPORT, "PROJECT_3");
CeTask inProgressTask = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
CeTask pendingTask1 = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(13)));
CeTask pendingTask2 = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(14)));

db.getDbClient().ceQueueDao().peek(session, WORKER_UUID);

@@ -386,7 +409,7 @@ public class CeQueueImplTest {

@Test
public void pauseWorkers_marks_workers_as_paused_if_zero_tasks_in_progress() {
submit(CeTaskTypes.REPORT, "PROJECT_1");
submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
// task is pending

assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
@@ -397,7 +420,7 @@ public class CeQueueImplTest {

@Test
public void pauseWorkers_marks_workers_as_pausing_if_some_tasks_in_progress() {
submit(CeTaskTypes.REPORT, "PROJECT_1");
submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
db.getDbClient().ceQueueDao().peek(session, WORKER_UUID);
// task is in-progress

@@ -418,7 +441,7 @@ public class CeQueueImplTest {

@Test
public void resumeWorkers_resumes_pausing_workers() {
submit(CeTaskTypes.REPORT, "PROJECT_1");
submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
db.getDbClient().ceQueueDao().peek(session, WORKER_UUID);
// task is in-progress

@@ -439,21 +462,37 @@ public class CeQueueImplTest {
}

private void verifyCeTask(CeTaskSubmit taskSubmit, CeTask task, @Nullable ComponentDto componentDto) {
verifyCeTask(taskSubmit, task, componentDto, componentDto);
}

private void verifyCeTask(CeTaskSubmit taskSubmit, CeTask task, @Nullable ComponentDto componentDto, @Nullable ComponentDto mainComponentDto) {
if (componentDto == null) {
assertThat(task.getOrganizationUuid()).isEqualTo(defaultOrganizationProvider.get().getUuid());
} else {
assertThat(task.getOrganizationUuid()).isEqualTo(componentDto.getOrganizationUuid());
}
assertThat(task.getUuid()).isEqualTo(taskSubmit.getUuid());
assertThat(task.getComponentUuid()).isEqualTo(task.getComponentUuid());
assertThat(task.getType()).isEqualTo(taskSubmit.getType());
if (componentDto == null) {
assertThat(task.getComponentKey()).isNull();
assertThat(task.getComponentName()).isNull();
if (componentDto != null) {
CeTask.Component component = task.getComponent().get();
assertThat(component.getUuid()).isEqualTo(componentDto.uuid());
assertThat(component.getKey()).contains(componentDto.getDbKey());
assertThat(component.getName()).contains(componentDto.name());
} else if (taskSubmit.getComponent().isPresent()) {
assertThat(task.getComponent()).contains(new CeTask.Component(taskSubmit.getComponent().get().getUuid(), null, null));
} else {
assertThat(task.getComponent()).isEmpty();
}
if (mainComponentDto != null) {
CeTask.Component component = task.getMainComponent().get();
assertThat(component.getUuid()).isEqualTo(mainComponentDto.uuid());
assertThat(component.getKey()).contains(mainComponentDto.getDbKey());
assertThat(component.getName()).contains(mainComponentDto.name());
} else if (taskSubmit.getComponent().isPresent()) {
assertThat(task.getMainComponent()).contains(new CeTask.Component(taskSubmit.getComponent().get().getMainComponentUuid(), null, null));
} else {
assertThat(task.getComponentKey()).isEqualTo(componentDto.getDbKey());
assertThat(task.getComponentName()).isEqualTo(componentDto.name());
assertThat(task.getMainComponent()).isEmpty();
}
assertThat(task.getType()).isEqualTo(taskSubmit.getType());
assertThat(task.getSubmitterUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
}

@@ -461,23 +500,30 @@ public class CeQueueImplTest {
Optional<CeQueueDto> queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), taskSubmit.getUuid());
assertThat(queueDto.isPresent()).isTrue();
assertThat(queueDto.get().getTaskType()).isEqualTo(taskSubmit.getType());
assertThat(queueDto.get().getComponentUuid()).isEqualTo(taskSubmit.getComponentUuid());
Optional<Component> component = taskSubmit.getComponent();
if (component.isPresent()) {
assertThat(queueDto.get().getComponentUuid()).isEqualTo(component.get().getUuid());
assertThat(queueDto.get().getMainComponentUuid()).isEqualTo(component.get().getMainComponentUuid());
} else {
assertThat(queueDto.get().getComponentUuid()).isNull();
assertThat(queueDto.get().getComponentUuid()).isNull();
}
assertThat(queueDto.get().getSubmitterUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
assertThat(queueDto.get().getCreatedAt()).isEqualTo(1_450_000_000_000L);
}

private CeTask submit(String reportType, String componentUuid) {
return underTest.submit(createTaskSubmit(reportType, componentUuid, null));
private CeTask submit(String reportType, Component component) {
return underTest.submit(createTaskSubmit(reportType, component, null));
}

private CeTaskSubmit createTaskSubmit(String type) {
return createTaskSubmit(type, null, null);
}

private CeTaskSubmit createTaskSubmit(String type, @Nullable String componentUuid, @Nullable String submitterUuid) {
private CeTaskSubmit createTaskSubmit(String type, @Nullable Component component, @Nullable String submitterUuid) {
return underTest.prepareSubmit()
.setType(type)
.setComponentUuid(componentUuid)
.setComponent(component)
.setSubmitterUuid(submitterUuid)
.setCharacteristics(emptyMap())
.build();
@@ -489,14 +535,22 @@ public class CeQueueImplTest {
return componentDto;
}

private CeQueueDto insertPendingInQueue(@Nullable String componentUuid) {
private CeQueueDto insertPendingInQueue(@Nullable Component component) {
CeQueueDto dto = new CeQueueDto()
.setUuid(UuidFactoryFast.getInstance().create())
.setTaskType("some type")
.setComponentUuid(componentUuid)
.setStatus(CeQueueDto.Status.PENDING);
if (component != null) {
dto.setComponentUuid(component.getUuid())
.setMainComponentUuid(component.getMainComponentUuid());
}
db.getDbClient().ceQueueDao().insert(db.getSession(), dto);
db.commit();
return dto;
}

private static int newComponentIdGenerator = new Random().nextInt(8_999_333);
private static Component newComponent(String mainComponentUuid) {
return new Component("uuid_" + newComponentIdGenerator++, mainComponentUuid);
}
}

+ 6
- 4
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java View File

@@ -148,10 +148,12 @@ public class PostProjectAnalysisTasksExecutor implements ComputationStepExecutor
}

private static Project createProject(org.sonar.ce.task.CeTask ceTask) {
return new ProjectImpl(
ceTask.getComponentUuid(),
ceTask.getComponentKey(),
ceTask.getComponentName());
return ceTask.getMainComponent()
.map(c -> new ProjectImpl(
c.getUuid(),
c.getKey().orElseThrow(() -> new IllegalStateException("Missing project key")),
c.getName().orElseThrow(() -> new IllegalStateException("Missing project name"))))
.orElseThrow(() -> new IllegalStateException("Report processed for a task of a deleted component"));
}

@CheckForNull

+ 14
- 1
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/BranchLoader.java View File

@@ -21,10 +21,11 @@ package org.sonar.ce.task.projectanalysis.component;

import javax.annotation.Nullable;
import org.sonar.api.utils.MessageException;
import org.sonar.scanner.protocol.output.ScannerReport;
import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolder;
import org.sonar.scanner.protocol.output.ScannerReport;

import static org.apache.commons.lang.StringUtils.trimToNull;
import static org.sonar.scanner.protocol.output.ScannerReport.Metadata.BranchType.UNSET;

public class BranchLoader {
private final MutableAnalysisMetadataHolder metadataHolder;
@@ -47,10 +48,22 @@ public class BranchLoader {
throw MessageException.of("Properties sonar.branch and sonar.branch.name can't be set together");
}

if (delegate == null && hasBranchProperties(metadata)) {
throw MessageException.of("Current edition does not support branch feature");
}

if (delegate != null && deprecatedBranch == null) {
delegate.load(metadata);
} else {
metadataHolder.setBranch(new DefaultBranchImpl(deprecatedBranch));
}
}

private static boolean hasBranchProperties(ScannerReport.Metadata metadata) {
return !metadata.getBranchName().isEmpty()
|| !metadata.getPullRequestKey().isEmpty()
|| !metadata.getMergeBranchName().isEmpty()
|| metadata.getBranchType() != UNSET;
}

}

+ 10
- 44
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImpl.java View File

@@ -19,10 +19,7 @@
*/
package org.sonar.ce.task.projectanalysis.component;

import java.util.Date;
import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.api.utils.System2;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.ce.task.projectanalysis.analysis.Branch;
import org.sonar.db.DbClient;
@@ -32,18 +29,16 @@ import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.protobuf.DbProjectBranches;

import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;
/**
* Creates or updates the data in table {@code PROJECT_BRANCHES} for the current root.
*/
public class BranchPersisterImpl implements BranchPersister {
private final DbClient dbClient;
private final System2 system2;
private final TreeRootHolder treeRootHolder;
private final AnalysisMetadataHolder analysisMetadataHolder;

public BranchPersisterImpl(DbClient dbClient, System2 system2, TreeRootHolder treeRootHolder, AnalysisMetadataHolder analysisMetadataHolder) {
public BranchPersisterImpl(DbClient dbClient, TreeRootHolder treeRootHolder, AnalysisMetadataHolder analysisMetadataHolder) {
this.dbClient = dbClient;
this.system2 = system2;
this.treeRootHolder = treeRootHolder;
this.analysisMetadataHolder = analysisMetadataHolder;
}
@@ -52,32 +47,14 @@ public class BranchPersisterImpl implements BranchPersister {
Branch branch = analysisMetadataHolder.getBranch();
String branchUuid = treeRootHolder.getRoot().getUuid();

Optional<ComponentDto> branchComponentDtoOpt = dbClient.componentDao().selectByUuid(dbSession, branchUuid);

ComponentDto branchComponentDto;
if (branch.isMain()) {
checkState(branchComponentDtoOpt.isPresent(), "Project has been deleted by end-user during analysis");
branchComponentDto = branchComponentDtoOpt.get();
} else {
// inserts new row in table projects if it's the first time branch is analyzed
branchComponentDto = branchComponentDtoOpt.orElseGet(() -> insertIntoProjectsTable(dbSession, branchUuid));
}
ComponentDto branchComponentDto = dbClient.componentDao().selectByUuid(dbSession, branchUuid)
.orElseThrow(() -> new IllegalStateException("Component has been deleted by end-user during analysis"));

// insert or update in table project_branches
dbClient.branchDao().upsert(dbSession, toBranchDto(branchComponentDto, branch));
}

private static void checkState(boolean condition, String msg) {
if (!condition) {
throw new IllegalStateException(msg);
}
}

private static <T> T firstNonNull(@Nullable T first, T second) {
return (first != null) ? first : second;
}

private BranchDto toBranchDto(ComponentDto componentDto, Branch branch) {
protected BranchDto toBranchDto(ComponentDto componentDto, Branch branch) {
BranchDto dto = new BranchDto();
dto.setUuid(componentDto.uuid());

@@ -103,19 +80,8 @@ public class BranchPersisterImpl implements BranchPersister {
return dto;
}

private ComponentDto insertIntoProjectsTable(DbSession dbSession, String branchUuid) {
String mainBranchProjectUuid = analysisMetadataHolder.getProject().getUuid();
ComponentDto project = dbClient.componentDao().selectOrFailByUuid(dbSession, mainBranchProjectUuid);
ComponentDto branchDto = project.copy();
branchDto.setUuid(branchUuid);
branchDto.setProjectUuid(branchUuid);
branchDto.setRootUuid(branchUuid);
branchDto.setUuidPath(UUID_PATH_OF_ROOT);
branchDto.setModuleUuidPath(UUID_PATH_SEPARATOR + branchUuid + UUID_PATH_SEPARATOR);
branchDto.setMainBranchProjectUuid(mainBranchProjectUuid);
branchDto.setDbKey(treeRootHolder.getRoot().getDbKey());
branchDto.setCreatedAt(new Date(system2.now()));
dbClient.componentDao().insert(dbSession, branchDto);
return branchDto;
private static <T> T firstNonNull(@Nullable T first, T second) {
return (first != null) ? first : second;
}

}

+ 4
- 3
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dbmigration/PopulateFileSourceLineCount.java View File

@@ -41,18 +41,19 @@ public class PopulateFileSourceLineCount extends DataChange implements ProjectAn

@Override
protected void execute(Context context) throws SQLException {
String componentUuid = ceTask.getComponent().get().getUuid();
Long unInitializedFileSources = context.prepareSelect("select count(1) from file_sources where line_count = ? and project_uuid = ?")
.setInt(1, LINE_COUNT_NOT_POPULATED)
.setString(2, ceTask.getComponentUuid())
.setString(2, componentUuid)
.get(row -> row.getLong(1));

if (unInitializedFileSources != null && unInitializedFileSources > 0) {
MassUpdate massUpdate = context.prepareMassUpdate();
massUpdate.select("select id,line_hashes from file_sources where line_count = ? and project_uuid = ?")
.setInt(1, LINE_COUNT_NOT_POPULATED)
.setString(2, ceTask.getComponentUuid());
.setString(2, componentUuid);
massUpdate.update("update file_sources set line_count = ? where id = ?");
massUpdate.rowPluralName("line counts of sources of project " + ceTask.getComponentUuid());
massUpdate.rowPluralName("line counts of sources of project " + componentUuid);
massUpdate.execute(PopulateFileSourceLineCount::handle);
}
}

+ 19
- 9
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java View File

@@ -104,30 +104,40 @@ public class LoadReportAnalysisMetadataHolderStep implements ComputationStep {
*/
private Runnable loadProject(ScannerReport.Metadata reportMetadata, Organization organization) {
String reportProjectKey = projectKeyFromReport(reportMetadata);
String componentKey = ceTask.getComponentKey();
if (componentKey == null) {
throw MessageException.of(format(
CeTask.Component mainComponent = mandatoryComponent(ceTask.getMainComponent());
String mainComponentKey = mainComponent.getKey()
.orElseThrow(() -> MessageException.of(format(
"Compute Engine task main component key is null. Project with UUID %s must have been deleted since report was uploaded. Can not proceed.",
mainComponent.getUuid())));
CeTask.Component component = mandatoryComponent(ceTask.getComponent());
String componentKey = component.getKey()
.orElseThrow(() -> MessageException.of(format(
"Compute Engine task component key is null. Project with UUID %s must have been deleted since report was uploaded. Can not proceed.",
ceTask.getComponentUuid()));
}
component.getUuid())));
ComponentDto dto = toProject(reportProjectKey);

analysisMetadata.setProject(Project.from(dto));
return () -> {
if (!componentKey.equals(reportProjectKey)) {
if (!mainComponentKey.equals(reportProjectKey)) {
throw MessageException.of(format(
"ProjectKey in report (%s) is not consistent with projectKey under which the report has been submitted (%s)",
reportProjectKey,
componentKey));
mainComponentKey));
}
if (!dto.getOrganizationUuid().equals(organization.getUuid())) {
throw MessageException.of(format("Project is not in the expected organization: %s", organization.getKey()));
}
if (dto.getMainBranchProjectUuid() != null) {
throw MessageException.of("Project should not reference a branch");
if (componentKey.equals(mainComponentKey) && dto.getMainBranchProjectUuid() != null) {
throw MessageException.of("Component should not reference a branch");
}
};
}

private static CeTask.Component mandatoryComponent(Optional<CeTask.Component> mainComponent) {
return mainComponent
.orElseThrow(() -> new IllegalStateException("component missing on ce task"));
}

private Organization loadOrganization(ScannerReport.Metadata reportMetadata) {
try (DbSession dbSession = dbClient.openSession(false)) {
Organization organization = toOrganization(dbSession, ceTask.getOrganizationUuid());

+ 6
- 6
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutorTest.java View File

@@ -87,13 +87,13 @@ public class PostProjectAnalysisTasksExecutorTest {
private String organizationName = organizationUuid + "_name";
private System2 system2 = mock(System2.class);
private ArgumentCaptor<PostProjectAnalysisTask.ProjectAnalysis> projectAnalysisArgumentCaptor = ArgumentCaptor.forClass(PostProjectAnalysisTask.ProjectAnalysis.class);
private CeTask.Component component = new CeTask.Component("component uuid", "component key", "component name");
private CeTask ceTask = new CeTask.Builder()
.setOrganizationUuid(organizationUuid)
.setType("type")
.setUuid("uuid")
.setComponentKey("component key")
.setComponentName("component name")
.setComponentUuid("component uuid")
.setComponent(component)
.setMainComponent(component)
.build();
private PostProjectAnalysisTask postProjectAnalysisTask = mock(PostProjectAnalysisTask.class);
private PostProjectAnalysisTasksExecutor underTest = new PostProjectAnalysisTasksExecutor(
@@ -203,9 +203,9 @@ public class PostProjectAnalysisTasksExecutorTest {
verify(postProjectAnalysisTask).finished(projectAnalysisArgumentCaptor.capture());

Project project = projectAnalysisArgumentCaptor.getValue().getProject();
assertThat(project.getUuid()).isEqualTo(ceTask.getComponentUuid());
assertThat(project.getKey()).isEqualTo(ceTask.getComponentKey());
assertThat(project.getName()).isEqualTo(ceTask.getComponentName());
assertThat(project.getUuid()).isEqualTo(ceTask.getComponent().get().getUuid());
assertThat(project.getKey()).isEqualTo(ceTask.getComponent().get().getKey().get());
assertThat(project.getName()).isEqualTo(ceTask.getComponent().get().getName().get());
}

@Test

+ 103
- 25
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplTest.java View File

@@ -19,25 +19,36 @@
*/
package org.sonar.ce.task.projectanalysis.component;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Optional;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.sonar.api.utils.System2;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
import org.sonar.ce.task.projectanalysis.analysis.Branch;
import org.sonar.db.DbTester;
import org.sonar.db.component.BranchDto;
import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
import org.sonar.db.protobuf.DbProjectBranches;
import org.sonar.server.project.Project;

import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
import static org.sonar.db.component.BranchType.LONG;
import static org.sonar.db.component.BranchType.PULL_REQUEST;

@RunWith(DataProviderRunner.class)
public class BranchPersisterImplTest {
private final static Component MAIN = builder(PROJECT, 1).setUuid("PROJECT_UUID").setKey("PROJECT_KEY").build();
private final static Component BRANCH = builder(PROJECT, 1).setUuid("BRANCH_UUID").setKey("BRANCH_KEY").build();
@@ -51,66 +62,133 @@ public class BranchPersisterImplTest {
@Rule
public ExpectedException exception = ExpectedException.none();

BranchPersister underTest = new BranchPersisterImpl(dbTester.getDbClient(), System2.INSTANCE, treeRootHolder, analysisMetadataHolder);
BranchPersister underTest = new BranchPersisterImpl(dbTester.getDbClient(), treeRootHolder, analysisMetadataHolder);

@Test
public void fail_if_no_component_for_main_branches() {
analysisMetadataHolder.setBranch(createBranch(BranchType.LONG, true, "master"));
public void persist_fails_with_ISE_if_no_component_for_main_branches() {
analysisMetadataHolder.setBranch(createBranch(LONG, true, "master"));
treeRootHolder.setRoot(MAIN);

exception.expect(IllegalStateException.class);
exception.expectMessage("Project has been deleted by end-user during analysis");
expectMissingComponentISE();

underTest.persist(dbTester.getSession());
}

@Test
public void persist_fails_with_ISE_if_no_component_for_long_branches() {
analysisMetadataHolder.setBranch(createBranch(LONG, false, "foo"));
treeRootHolder.setRoot(BRANCH);

expectMissingComponentISE();

underTest.persist(dbTester.getSession());
}

@Test
public void persist_secondary_branch() {
analysisMetadataHolder.setBranch(createBranch(BranchType.LONG, false, "branch"));
public void persist_fails_with_ISE_if_no_component_for_short_branches() {
analysisMetadataHolder.setBranch(createBranch(BranchType.SHORT, false, "foo"));
treeRootHolder.setRoot(BRANCH);

// add main branch in project table and in metadata
ComponentDto dto = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), MAIN.getUuid()).setDbKey(MAIN.getDbKey());
analysisMetadataHolder.setProject(Project.from(dto));
dbTester.getDbClient().componentDao().insert(dbTester.getSession(), dto);
expectMissingComponentISE();

underTest.persist(dbTester.getSession());
}

@Test
public void persist_fails_with_ISE_if_no_component_for_pull_request() {
analysisMetadataHolder.setBranch(createBranch(BranchType.PULL_REQUEST, false, "12"));
treeRootHolder.setRoot(BRANCH);

expectMissingComponentISE();

underTest.persist(dbTester.getSession());
}

@Test
@UseDataProvider("nullOrNotNullString")
public void persist_creates_row_in_PROJECTS_BRANCHES_for_long_branch(@Nullable String mergeBranchUuid) {
String branchName = "branch";

// add project and branch in table PROJECTS
ComponentDto mainComponent = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), MAIN.getUuid()).setDbKey(MAIN.getKey());
ComponentDto component = ComponentTesting.newProjectBranch(mainComponent, new BranchDto().setUuid(BRANCH.getUuid()).setKey(BRANCH.getKey()).setBranchType(LONG));
dbTester.getDbClient().componentDao().insert(dbTester.getSession(), mainComponent, component);
dbTester.commit();
// set project in metadata
treeRootHolder.setRoot(BRANCH);
analysisMetadataHolder.setBranch(createBranch(LONG, false, branchName, mergeBranchUuid));
analysisMetadataHolder.setProject(Project.from(mainComponent));

// this should add new columns in project and project_branches
underTest.persist(dbTester.getSession());

dbTester.getSession().commit();

assertThat(dbTester.countRowsOfTable("projects")).isEqualTo(2);
assertThat(dbTester.countRowsOfTable("project_branches")).isEqualTo(1);
Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH.getUuid());
assertThat(branchDto).isPresent();
assertThat(branchDto.get().getBranchType()).isEqualTo(LONG);
assertThat(branchDto.get().getKey()).isEqualTo(branchName);
assertThat(branchDto.get().getMergeBranchUuid()).isEqualTo(mergeBranchUuid);
assertThat(branchDto.get().getProjectUuid()).isEqualTo(MAIN.getUuid());
assertThat(branchDto.get().getPullRequestData()).isNull();
}

@DataProvider
public static Object[][] nullOrNotNullString() {
return new Object[][] {
{null},
{randomAlphabetic(12)}
};
}

@Test
public void persist_pull_request_data() {
@UseDataProvider("nullOrNotNullString")
public void persist_creates_row_in_PROJECTS_BRANCHES_for_pull_request(@Nullable String mergeBranchUuid) {
String pullRequestId = "pr-123";
analysisMetadataHolder.setBranch(createBranch(BranchType.PULL_REQUEST, false, pullRequestId));
analysisMetadataHolder.setPullRequestKey(pullRequestId);
treeRootHolder.setRoot(BRANCH);

// add main branch in project table and in metadata
ComponentDto dto = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), MAIN.getUuid()).setDbKey(MAIN.getDbKey());
analysisMetadataHolder.setProject(Project.from(dto));
dbTester.getDbClient().componentDao().insert(dbTester.getSession(), dto);
// add project and branch in table PROJECTS
ComponentDto mainComponent = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), MAIN.getUuid()).setDbKey(MAIN.getKey());
ComponentDto component = ComponentTesting.newProjectBranch(mainComponent, new BranchDto().setUuid(BRANCH.getUuid()).setKey(BRANCH.getKey()).setBranchType(PULL_REQUEST));
dbTester.getDbClient().componentDao().insert(dbTester.getSession(), mainComponent, component);
dbTester.commit();
// set project in metadata
treeRootHolder.setRoot(BRANCH);
analysisMetadataHolder.setBranch(createBranch(PULL_REQUEST, false, pullRequestId, mergeBranchUuid));
analysisMetadataHolder.setProject(Project.from(mainComponent));
analysisMetadataHolder.setPullRequestKey(pullRequestId);

// this should add new columns in project and project_branches
underTest.persist(dbTester.getSession());

dbTester.getSession().commit();

assertThat(dbTester.countRowsOfTable("projects")).isEqualTo(2);
assertThat(dbTester.countRowsOfTable("project_branches")).isEqualTo(1);
assertThat(dbTester.countSql("select count(*) from project_branches where pull_request_binary is not null")).isEqualTo(1);
Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH.getUuid());
assertThat(branchDto).isPresent();
assertThat(branchDto.get().getBranchType()).isEqualTo(PULL_REQUEST);
assertThat(branchDto.get().getKey()).isEqualTo(pullRequestId);
assertThat(branchDto.get().getMergeBranchUuid()).isEqualTo(mergeBranchUuid);
assertThat(branchDto.get().getProjectUuid()).isEqualTo(MAIN.getUuid());
assertThat(branchDto.get().getPullRequestData()).isEqualTo(DbProjectBranches.PullRequestData.newBuilder()
.setBranch(pullRequestId)
.setTitle(pullRequestId)
.build());
}

private static Branch createBranch(BranchType type, boolean isMain, String name) {
return createBranch(type, isMain, name, null);
}

private static Branch createBranch(BranchType type, boolean isMain, String name, @Nullable String mergeBranchUuid) {
Branch branch = mock(Branch.class);
when(branch.getType()).thenReturn(type);
when(branch.getName()).thenReturn(name);
when(branch.isMain()).thenReturn(isMain);
when(branch.getMergeBranchUuid()).thenReturn(Optional.empty());
when(branch.getMergeBranchUuid()).thenReturn(Optional.ofNullable(mergeBranchUuid));
return branch;
}

private void expectMissingComponentISE() {
exception.expect(IllegalStateException.class);
exception.expectMessage("Component has been deleted by end-user during analysis");
}
}

+ 13
- 5
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest.java View File

@@ -23,6 +23,7 @@ import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.sql.SQLException;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -57,6 +58,9 @@ public class PopulateFileSourceLineCountTest {

@Test
public void execute_has_no_effect_on_empty_table() throws SQLException {
String projectUuid = randomAlphanumeric(4);
when(ceTask.getComponent()).thenReturn(newComponent(projectUuid));

underTest.execute();
}

@@ -65,7 +69,7 @@ public class PopulateFileSourceLineCountTest {
public void execute_populates_line_count_of_any_type(String type) throws SQLException {
String projectUuid = randomAlphanumeric(4);
String fileUuid = randomAlphanumeric(5);
when(ceTask.getComponentUuid()).thenReturn(projectUuid);
when(ceTask.getComponent()).thenReturn(newComponent(projectUuid));
int lineCount = 1 + random.nextInt(15);
insertUnpopulatedFileSource(projectUuid, fileUuid, type, lineCount);
assertThat(getLineCountByFileUuid(fileUuid)).isEqualTo(LINE_COUNT_NOT_POPULATED);
@@ -86,7 +90,7 @@ public class PopulateFileSourceLineCountTest {
int lineCountFile2 = 50 + random.nextInt(15);
int lineCountFile3 = 150 + random.nextInt(15);

when(ceTask.getComponentUuid()).thenReturn(projectUuid);
when(ceTask.getComponent()).thenReturn(newComponent(projectUuid));
insertPopulatedFileSource(projectUuid, fileUuid1, type, lineCountFile1);
int badLineCountFile2 = insertInconsistentPopulatedFileSource(projectUuid, fileUuid2, type, lineCountFile2);
insertUnpopulatedFileSource(projectUuid, fileUuid3, type, lineCountFile3);
@@ -111,7 +115,7 @@ public class PopulateFileSourceLineCountTest {
int lineCountFile1 = 100 + random.nextInt(15);
int lineCountFile2 = 30 + random.nextInt(15);

when(ceTask.getComponentUuid()).thenReturn(projectUuid1);
when(ceTask.getComponent()).thenReturn(newComponent(projectUuid1));
insertUnpopulatedFileSource(projectUuid1, fileUuid1, type, lineCountFile1);
insertUnpopulatedFileSource(projectUuid2, fileUuid2, type, lineCountFile2);

@@ -127,7 +131,7 @@ public class PopulateFileSourceLineCountTest {
String projectUuid = randomAlphanumeric(4);
String fileUuid1 = randomAlphanumeric(5);

when(ceTask.getComponentUuid()).thenReturn(projectUuid);
when(ceTask.getComponent()).thenReturn(newComponent(projectUuid));
insertFileSource(projectUuid, fileUuid1, type, null, LINE_COUNT_NOT_POPULATED);

underTest.execute();
@@ -141,7 +145,7 @@ public class PopulateFileSourceLineCountTest {
String projectUuid = randomAlphanumeric(4);
String fileUuid1 = randomAlphanumeric(5);

when(ceTask.getComponentUuid()).thenReturn(projectUuid);
when(ceTask.getComponent()).thenReturn(newComponent(projectUuid));
insertFileSource(projectUuid, fileUuid1, type, "", LINE_COUNT_NOT_POPULATED);

underTest.execute();
@@ -204,4 +208,8 @@ public class PopulateFileSourceLineCountTest {
"UPDATED_AT", 1_222_333L);
db.commit();
}

private static Optional<CeTask.Component> newComponent(String projectUuid) {
return Optional.of(new CeTask.Component(projectUuid, "key_" + projectUuid, "name_" + projectUuid));
}
}

+ 39
- 2
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java View File

@@ -25,6 +25,7 @@ import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Rule;
@@ -165,10 +166,44 @@ public class LoadReportAnalysisMetadataHolderStepTest {
assertThat(analysisMetadataHolder.isCrossProjectDuplicationEnabled()).isEqualTo(false);
}

@Test
public void execute_fails_with_ISE_if_component_is_null_in_CE_task() {
CeTask res = mock(CeTask.class);
when(res.getComponent()).thenReturn(Optional.empty());
when(res.getOrganizationUuid()).thenReturn(defaultOrganizationProvider.get().getUuid());
reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());

ComputationStep underTest = createStep(res);

expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("component missing on ce task");

underTest.execute(new TestComputationStepContext());
}

@Test
public void execute_fails_with_MessageException_if_main_projectKey_is_null_in_CE_task() {
CeTask res = mock(CeTask.class);
Optional<CeTask.Component> component = Optional.of(new CeTask.Component("main_prj_uuid", null, null));
when(res.getComponent()).thenReturn(component);
when(res.getMainComponent()).thenReturn(component);
when(res.getOrganizationUuid()).thenReturn(defaultOrganizationProvider.get().getUuid());
reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());

ComputationStep underTest = createStep(res);

expectedException.expect(MessageException.class);
expectedException.expectMessage("Compute Engine task main component key is null. Project with UUID main_prj_uuid must have been deleted since report was uploaded. Can not proceed.");

underTest.execute(new TestComputationStepContext());
}

@Test
public void execute_fails_with_MessageException_if_projectKey_is_null_in_CE_task() {
CeTask res = mock(CeTask.class);
when(res.getComponentUuid()).thenReturn("prj_uuid");
Optional<CeTask.Component> component = Optional.of(new CeTask.Component("prj_uuid", null, null));
when(res.getComponent()).thenReturn(component);
when(res.getMainComponent()).thenReturn(Optional.of(new CeTask.Component("main_prj_uuid", "main_prj_key", null)));
when(res.getOrganizationUuid()).thenReturn(defaultOrganizationProvider.get().getUuid());
reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());

@@ -407,8 +442,10 @@ public class LoadReportAnalysisMetadataHolderStepTest {

private CeTask createCeTask(String projectKey, String organizationUuid) {
CeTask res = mock(CeTask.class);
Optional<CeTask.Component> component = Optional.of(new CeTask.Component(projectKey + "_uuid", projectKey, projectKey + "_name"));
when(res.getOrganizationUuid()).thenReturn(organizationUuid);
when(res.getComponentKey()).thenReturn(projectKey);
when(res.getComponent()).thenReturn(component);
when(res.getMainComponent()).thenReturn(component);
return res;
}


+ 75
- 32
server/sonar-ce-task/src/main/java/org/sonar/ce/task/CeTask.java View File

@@ -22,10 +22,13 @@ package org.sonar.ce.task;
import com.google.common.base.MoreObjects;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.emptyToNull;
import static java.util.Collections.emptyMap;
import static java.util.Collections.unmodifiableMap;
@@ -37,9 +40,8 @@ public class CeTask {
private final String organizationUuid;
private final String type;
private final String uuid;
private final String componentUuid;
private final String componentKey;
private final String componentName;
private final Component component;
private final Component mainComponent;
private final String submitterUuid;
private final Map<String, String> characteristics;

@@ -47,9 +49,10 @@ public class CeTask {
this.organizationUuid = requireNonNull(emptyToNull(builder.organizationUuid), "organizationUuid can't be null nor empty");
this.uuid = requireNonNull(emptyToNull(builder.uuid), "uuid can't be null nor empty");
this.type = requireNonNull(emptyToNull(builder.type), "type can't be null nor empty");
this.componentUuid = emptyToNull(builder.componentUuid);
this.componentKey = emptyToNull(builder.componentKey);
this.componentName = emptyToNull(builder.componentName);
checkArgument((builder.component == null) == (builder.mainComponent == null),
"None or both component and main component must be non null");
this.component = builder.component;
this.mainComponent = builder.mainComponent;
this.submitterUuid = emptyToNull(builder.submitterUuid);
if (builder.characteristics == null) {
this.characteristics = emptyMap();
@@ -70,19 +73,12 @@ public class CeTask {
return type;
}

@CheckForNull
public String getComponentUuid() {
return componentUuid;
}

@CheckForNull
public String getComponentKey() {
return componentKey;
public Optional<Component> getComponent() {
return Optional.ofNullable(component);
}

@CheckForNull
public String getComponentName() {
return componentName;
public Optional<Component> getMainComponent() {
return Optional.ofNullable(mainComponent);
}

@CheckForNull
@@ -100,9 +96,8 @@ public class CeTask {
.add("organizationUuid", organizationUuid)
.add("type", type)
.add("uuid", uuid)
.add("componentUuid", componentUuid)
.add("componentKey", componentKey)
.add("componentName", componentName)
.add("component", component)
.add("mainComponent", mainComponent)
.add("submitterUuid", submitterUuid)
.toString();
}
@@ -128,9 +123,8 @@ public class CeTask {
private String organizationUuid;
private String uuid;
private String type;
private String componentUuid;
private String componentKey;
private String componentName;
private Component component;
private Component mainComponent;
private String submitterUuid;
private Map<String, String> characteristics;

@@ -154,18 +148,13 @@ public class CeTask {
return this;
}

public Builder setComponentUuid(@Nullable String componentUuid) {
this.componentUuid = componentUuid;
return this;
}

public Builder setComponentKey(@Nullable String s) {
this.componentKey = s;
public Builder setComponent(@Nullable Component component) {
this.component = component;
return this;
}

public Builder setComponentName(@Nullable String s) {
this.componentName = s;
public Builder setMainComponent(@Nullable Component mainComponent) {
this.mainComponent = mainComponent;
return this;
}

@@ -183,4 +172,58 @@ public class CeTask {
return new CeTask(this);
}
}

public static final class Component {
private final String uuid;
@CheckForNull
private final String key;
@CheckForNull
private final String name;

public Component(String uuid, @Nullable String key, @Nullable String name) {
this.uuid = requireNonNull(emptyToNull(uuid), "uuid can't be null nor empty");
this.key = emptyToNull(key);
this.name = emptyToNull(name);
}

public String getUuid() {
return uuid;
}

public Optional<String> getKey() {
return Optional.ofNullable(key);
}

public Optional<String> getName() {
return Optional.ofNullable(name);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Component component = (Component) o;
return Objects.equals(uuid, component.uuid) &&
Objects.equals(key, component.key) &&
Objects.equals(name, component.name);
}

@Override
public int hashCode() {
return Objects.hash(uuid, key, name);
}

@Override
public String toString() {
return "Component{" +
"uuid='" + uuid + '\'' +
", key='" + key + '\'' +
", name='" + name + '\'' +
'}';
}
}
}

+ 105
- 0
server/sonar-ce-task/src/test/java/org/sonar/ce/task/CeTaskComponentTest.java View File

@@ -0,0 +1,105 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.ce.task;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;

@RunWith(DataProviderRunner.class)
public class CeTaskComponentTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
@UseDataProvider("nullOrEmpty")
public void constructor_fails_with_NPE_if_uuid_is_null_or_empty(String str) {
expectedException.expect(NullPointerException.class);
expectedException.expectMessage("uuid can't be null nor empty");

new CeTask.Component(str, "foo", "bar");
}

@Test
@UseDataProvider("nullOrEmpty")
public void constructor_considers_empty_as_null_and_accept_it_for_key(String str) {
CeTask.Component underTest = new CeTask.Component("foo", str, "bar");

assertThat(underTest.getKey()).isEmpty();
}

@Test
@UseDataProvider("nullOrEmpty")
public void constructor_considers_empty_as_null_and_accept_it_for_name(String str) {
CeTask.Component underTest = new CeTask.Component("foo", "bar", str);

assertThat(underTest.getName()).isEmpty();
}

@Test
public void equals_is_based_on_all_fields() {
String uuid = randomAlphabetic(2);
String key = randomAlphabetic(3);
String name = randomAlphabetic(4);
String somethingElse = randomAlphabetic(5);
CeTask.Component underTest = new CeTask.Component(uuid, key, name);

assertThat(underTest).isEqualTo(underTest);
assertThat(underTest).isEqualTo(new CeTask.Component(uuid, key, name));
assertThat(underTest).isNotEqualTo(null);
assertThat(underTest).isNotEqualTo(new Object());
assertThat(underTest).isNotEqualTo(new CeTask.Component(somethingElse, key, name));
assertThat(underTest).isNotEqualTo(new CeTask.Component(uuid, somethingElse, name));
assertThat(underTest).isNotEqualTo(new CeTask.Component(uuid, key, somethingElse));
assertThat(underTest).isNotEqualTo(new CeTask.Component(uuid, key, null));
}

@Test
public void hashcode_is_based_on_all_fields() {
String uuid = randomAlphabetic(2);
String key = randomAlphabetic(3);
String name = randomAlphabetic(4);
String somethingElse = randomAlphabetic(5);
CeTask.Component underTest = new CeTask.Component(uuid, key, name);

assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
assertThat(underTest.hashCode()).isEqualTo(new CeTask.Component(uuid, key, name).hashCode());
assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
assertThat(underTest.hashCode()).isNotEqualTo(new CeTask.Component(somethingElse, key, name).hashCode());
assertThat(underTest.hashCode()).isNotEqualTo(new CeTask.Component(uuid, somethingElse, name).hashCode());
assertThat(underTest.hashCode()).isNotEqualTo(new CeTask.Component(uuid, key, somethingElse).hashCode());
assertThat(underTest.hashCode()).isNotEqualTo(new CeTask.Component(uuid, key, null).hashCode());
}

@DataProvider
public static Object[][] nullOrEmpty() {
return new Object[][] {
{null},
{""},
};
}
}

+ 35
- 19
server/sonar-ce-task/src/test/java/org/sonar/ce/task/CeTaskTest.java View File

@@ -20,13 +20,18 @@
package org.sonar.ce.task;

import com.google.common.collect.ImmutableMap;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;

@RunWith(DataProviderRunner.class)
public class CeTaskTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
@@ -91,15 +96,40 @@ public class CeTaskTest {
underTest.build();
}

@Test
@UseDataProvider("oneAndOnlyOneOfComponentAndMainComponent")
public void build_fails_with_IAE_if_only_one_of_component_and_main_component_is_non_null(CeTask.Component component, CeTask.Component mainComponent) {
underTest.setOrganizationUuid("org1");
underTest.setType("TYPE_1");
underTest.setUuid("UUID_1");
underTest.setComponent(component);
underTest.setMainComponent(mainComponent);

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("None or both component and main component must be non null");
underTest.build();
}

@DataProvider
public static Object[][] oneAndOnlyOneOfComponentAndMainComponent() {
CeTask.Component component = new CeTask.Component("COMPONENT_UUID_1", "COMPONENT_KEY_1", "The component");
return new Object[][] {
{component, null},
{null, component}
};
}

@Test
public void verify_getters() {
CeTask.Component component = new CeTask.Component("COMPONENT_UUID_1", "COMPONENT_KEY_1", "The component");
CeTask.Component mainComponent = new CeTask.Component("MAIN_COMPONENT_UUID_1", "MAIN_COMPONENT_KEY_1", "The main component");
underTest.setOrganizationUuid("org1");
underTest.setType("TYPE_1");
underTest.setUuid("UUID_1");
underTest.setSubmitterUuid("LOGIN_1");
underTest.setComponentKey("COMPONENT_KEY_1");
underTest.setComponentUuid("COMPONENT_UUID_1");
underTest.setComponentName("The component");
underTest.setComponent(component);
underTest.setMainComponent(mainComponent);
underTest.setCharacteristics(ImmutableMap.of("k1", "v1", "k2", "v2"));

CeTask task = underTest.build();
@@ -108,25 +138,11 @@ public class CeTaskTest {
assertThat(task.getUuid()).isEqualTo("UUID_1");
assertThat(task.getType()).isEqualTo("TYPE_1");
assertThat(task.getSubmitterUuid()).isEqualTo("LOGIN_1");
assertThat(task.getComponentKey()).isEqualTo("COMPONENT_KEY_1");
assertThat(task.getComponentUuid()).isEqualTo("COMPONENT_UUID_1");
assertThat(task.getComponentName()).isEqualTo("The component");
assertThat(task.getComponent()).contains(component);
assertThat(task.getMainComponent()).contains(mainComponent);
assertThat(task.getCharacteristics()).containsExactly(entry("k1", "v1"), entry("k2", "v2"));
}

@Test
public void empty_in_component_properties_is_considered_as_null() {
CeTask ceTask = underTest.setOrganizationUuid("org1").setUuid("uuid").setType("type")
.setComponentKey("")
.setComponentName("")
.setComponentUuid("")
.build();

assertThat(ceTask.getComponentKey()).isNull();
assertThat(ceTask.getComponentName()).isNull();
assertThat(ceTask.getComponentUuid()).isNull();
}

@Test
public void empty_in_submitterLogin_is_considered_as_null() {
CeTask ceTask = underTest.setOrganizationUuid("org1").setUuid("uuid").setType("type")

+ 1
- 1
server/sonar-ce/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListener.java View File

@@ -64,7 +64,7 @@ public class ReportAnalysisFailureNotificationExecutionListener implements CeWor
if (status == CeActivityDto.Status.SUCCESS) {
return;
}
String projectUuid = ceTask.getComponentUuid();
String projectUuid = ceTask.getComponent().map(CeTask.Component::getUuid).orElse(null);
if (!CeTaskTypes.REPORT.equals(ceTask.getType()) || projectUuid == null) {
return;
}

+ 5
- 6
server/sonar-ce/src/main/java/org/sonar/ce/queue/InternalCeQueueImpl.java View File

@@ -53,6 +53,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;

@ComputeEngineSide
@@ -90,15 +91,13 @@ public class InternalCeQueueImpl extends CeQueueImpl implements InternalCeQueue
Optional<CeQueueDto> opt = ceQueueDao.peek(dbSession, workerUuid);
if (opt.isPresent()) {
CeQueueDto taskDto = opt.get();
ComponentDto component = null;
String componentUuid = taskDto.getComponentUuid();
if (componentUuid != null) {
component = dbClient.componentDao().selectByUuid(dbSession, componentUuid).orElse(null);
}
Map<String, ComponentDto> componentsByUuid = loadComponentDtos(dbSession, taskDto);
Map<String, String> characteristics = dbClient.ceTaskCharacteristicsDao().selectByTaskUuids(dbSession, singletonList(taskDto.getUuid())).stream()
.collect(uniqueIndex(CeTaskCharacteristicDto::getKey, CeTaskCharacteristicDto::getValue));

CeTask task = convertToTask(taskDto, characteristics, component);
CeTask task = convertToTask(taskDto, characteristics,
ofNullable(taskDto.getComponentUuid()).map(componentsByUuid::get).orElse(null),
ofNullable(taskDto.getMainComponentUuid()).map(componentsByUuid::get).orElse(null));
queueStatus.addInProgress();
return Optional.of(task);
}

+ 1
- 1
server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeWorkerImpl.java View File

@@ -180,7 +180,7 @@ public class CeWorkerImpl implements CeWorker {
private static Profiler startLogProfiler(CeTask task) {
Profiler profiler = Profiler.create(LOG)
.logTimeLast(true)
.addContext("project", task.getComponentKey())
.addContext("project", task.getMainComponent().flatMap(CeTask.Component::getKey).orElse(null))
.addContext("type", task.getType());
for (Map.Entry<String, String> characteristic : task.getCharacteristics().entrySet()) {
profiler.addContext(characteristic.getKey(), characteristic.getValue());

+ 6
- 5
server/sonar-ce/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerTest.java View File

@@ -20,6 +20,7 @@
package org.sonar.ce.notification;

import java.util.Arrays;
import java.util.Optional;
import java.util.Random;
import javax.annotation.Nullable;
import org.junit.Before;
@@ -121,7 +122,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
public void onEnd_has_no_effect_if_there_is_no_subscriber_for_ReportAnalysisFailureNotification_type() {
String componentUuid = randomAlphanumeric(6);
when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
when(ceTaskMock.getComponentUuid()).thenReturn(componentUuid);
when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
.thenReturn(false);

@@ -134,7 +135,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
public void onEnd_fails_with_RowNotFoundException_if_component_does_not_exist_in_DB() {
String componentUuid = randomAlphanumeric(6);
when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
when(ceTaskMock.getComponentUuid()).thenReturn(componentUuid);
when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
.thenReturn(true);

@@ -160,7 +161,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
Arrays.asList(module, directory, file, view, subView, projectCopy, application)
.forEach(component -> {
try {
when(ceTaskMock.getComponentUuid()).thenReturn(component.uuid());
when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(component.uuid(), null, null)));
when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.TYPE)))
.thenReturn(true);

@@ -180,7 +181,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
String taskUuid = randomAlphanumeric(6);
when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
when(ceTaskMock.getUuid()).thenReturn(taskUuid);
when(ceTaskMock.getComponentUuid()).thenReturn(componentUuid);
when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
.thenReturn(true);
dbTester.components().insertPrivateProject(s -> s.setUuid(componentUuid));
@@ -293,7 +294,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
private ComponentDto initMocksToPassConditions(String taskUuid, int createdAt, @Nullable Long executedAt) {
ComponentDto project = random.nextBoolean() ? dbTester.components().insertPrivateProject() : dbTester.components().insertPublicProject();
when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
when(ceTaskMock.getComponentUuid()).thenReturn(project.uuid());
when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(project.uuid(), null, null)));
when(ceTaskMock.getUuid()).thenReturn(taskUuid);
when(notificationService.hasProjectSubscribersForTypes(project.uuid(), singleton(ReportAnalysisFailureNotification.TYPE)))
.thenReturn(true);

+ 70
- 40
server/sonar-ce/src/test/java/org/sonar/ce/queue/InternalCeQueueImplTest.java View File

@@ -99,7 +99,7 @@ public class InternalCeQueueImplTest {

@Test
public void submit_returns_task_populated_from_CeTaskSubmit_and_creates_CeQueue_row() {
CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, "PROJECT_1", "rob");
CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"), "rob");
CeTask task = underTest.submit(taskSubmit);

verifyCeTask(taskSubmit, task, null);
@@ -108,8 +108,8 @@ public class InternalCeQueueImplTest {

@Test
public void submit_populates_component_name_and_key_of_CeTask_if_component_exists() {
ComponentDto componentDto = insertComponent(newComponentDto("PROJECT_1"));
CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, componentDto.uuid(), null);
ComponentDto componentDto = insertComponent(newProjectDto("PROJECT_1"));
CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, componentDto, null);

CeTask task = underTest.submit(taskSubmit);

@@ -127,7 +127,7 @@ public class InternalCeQueueImplTest {

@Test
public void massSubmit_returns_tasks_for_each_CeTaskSubmit_populated_from_CeTaskSubmit_and_creates_CeQueue_row_for_each() {
CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, "PROJECT_1", "rob");
CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"), "rob");
CeTaskSubmit taskSubmit2 = createTaskSubmit("some type");

List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
@@ -141,9 +141,9 @@ public class InternalCeQueueImplTest {

@Test
public void massSubmit_populates_component_name_and_key_of_CeTask_if_component_exists() {
ComponentDto componentDto1 = insertComponent(newComponentDto("PROJECT_1"));
CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, componentDto1.uuid(), null);
CeTaskSubmit taskSubmit2 = createTaskSubmit("something", "non existing component uuid", null);
ComponentDto componentDto1 = insertComponent(newProjectDto("PROJECT_1"));
CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, componentDto1, null);
CeTaskSubmit taskSubmit2 = createTaskSubmit("something", newProjectDto("non existing component uuid"), null);

List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));

@@ -162,7 +162,7 @@ public class InternalCeQueueImplTest {

@Test
public void test_remove() {
CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, null, null);

@@ -196,7 +196,7 @@ public class InternalCeQueueImplTest {

@Test
public void remove_does_not_set_analysisUuid_in_CeActivity_when_CeTaskResult_has_no_analysis_uuid() {
CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(null), null);

@@ -208,7 +208,7 @@ public class InternalCeQueueImplTest {

@Test
public void remove_sets_analysisUuid_in_CeActivity_when_CeTaskResult_has_analysis_uuid() {
CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));

Optional<CeTask> peek = underTest.peek(WORKER_UUID_2);
underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(AN_ANALYSIS_UUID), null);
@@ -223,7 +223,7 @@ public class InternalCeQueueImplTest {
public void remove_saves_error_message_and_stacktrace_when_exception_is_provided() {
Throwable error = new NullPointerException("Fake NPE to test persistence to DB");

CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
underTest.remove(peek.get(), CeActivityDto.Status.FAILED, null, error);

@@ -239,7 +239,7 @@ public class InternalCeQueueImplTest {
public void remove_saves_error_when_TypedMessageException_is_provided() {
Throwable error = new TypedExceptionImpl("aType", "aMessage");

CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
underTest.remove(peek.get(), CeActivityDto.Status.FAILED, null, error);

@@ -253,7 +253,7 @@ public class InternalCeQueueImplTest {
public void remove_updates_queueStatus_success_even_if_task_does_not_exist_in_DB() {
CEQueueStatus queueStatus = mock(CEQueueStatus.class);

CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
db.getDbClient().ceQueueDao().deleteByUuid(db.getSession(), task.getUuid());
db.commit();

@@ -271,7 +271,7 @@ public class InternalCeQueueImplTest {
public void remove_updates_queueStatus_failure_even_if_task_does_not_exist_in_DB() {
CEQueueStatus queueStatusMock = mock(CEQueueStatus.class);

CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
db.getDbClient().ceQueueDao().deleteByUuid(db.getSession(), task.getUuid());
db.commit();
InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatusMock, null, null);
@@ -288,7 +288,7 @@ public class InternalCeQueueImplTest {
public void cancelWornOuts_does_not_update_queueStatus() {
CEQueueStatus queueStatusMock = mock(CEQueueStatus.class);

CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
db.executeUpdateSql("update ce_queue set status = 'PENDING', started_at = 123 where uuid = '" + task.getUuid() + "'");
db.commit();
InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatusMock, null, null);
@@ -334,7 +334,7 @@ public class InternalCeQueueImplTest {

@Test
public void fail_to_remove_if_not_in_queue() {
CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
underTest.remove(task, CeActivityDto.Status.SUCCESS, null, null);

expectedException.expect(IllegalStateException.class);
@@ -344,13 +344,32 @@ public class InternalCeQueueImplTest {

@Test
public void test_peek() {
CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));

Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
assertThat(peek.isPresent()).isTrue();
assertThat(peek.get().getUuid()).isEqualTo(task.getUuid());
assertThat(peek.get().getType()).isEqualTo(CeTaskTypes.REPORT);
assertThat(peek.get().getComponentUuid()).isEqualTo("PROJECT_1");
assertThat(peek.get().getComponent()).contains(new CeTask.Component("PROJECT_1", null, null));
assertThat(peek.get().getMainComponent()).contains(peek.get().getComponent().get());

// no more pending tasks
peek = underTest.peek(WORKER_UUID_2);
assertThat(peek.isPresent()).isFalse();
}

@Test
public void peek_populates_name_and_key_for_existing_component_and_main_component() {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto branch = db.components().insertProjectBranch(project);
CeTask task = submit(CeTaskTypes.REPORT, branch);

Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
assertThat(peek.isPresent()).isTrue();
assertThat(peek.get().getUuid()).isEqualTo(task.getUuid());
assertThat(peek.get().getType()).isEqualTo(CeTaskTypes.REPORT);
assertThat(peek.get().getComponent()).contains(new CeTask.Component(branch.uuid(), branch.getDbKey(), branch.name()));
assertThat(peek.get().getMainComponent()).contains(new CeTask.Component(project.uuid(), project.getDbKey(), project.name()));

// no more pending tasks
peek = underTest.peek(WORKER_UUID_2);
@@ -359,7 +378,7 @@ public class InternalCeQueueImplTest {

@Test
public void peek_is_paused_then_resumed() {
CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
underTest.pauseWorkers();

Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
@@ -385,7 +404,7 @@ public class InternalCeQueueImplTest {

@Test
public void peek_nothing_if_application_status_stopping() {
submit(CeTaskTypes.REPORT, "PROJECT_1");
submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
when(computeEngineStatus.getStatus()).thenReturn(STOPPING);

Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
@@ -468,7 +487,7 @@ public class InternalCeQueueImplTest {

@Test
public void cancel_pending() {
CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();

underTest.cancel(db.getSession(), queueDto);
@@ -480,7 +499,7 @@ public class InternalCeQueueImplTest {

@Test
public void fail_to_cancel_if_in_progress() {
CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
underTest.peek(WORKER_UUID_2);
CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();

@@ -492,9 +511,9 @@ public class InternalCeQueueImplTest {

@Test
public void cancelAll_pendings_but_not_in_progress() {
CeTask inProgressTask = submit(CeTaskTypes.REPORT, "PROJECT_1");
CeTask pendingTask1 = submit(CeTaskTypes.REPORT, "PROJECT_2");
CeTask pendingTask2 = submit(CeTaskTypes.REPORT, "PROJECT_3");
CeTask inProgressTask = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
CeTask pendingTask1 = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_2"));
CeTask pendingTask2 = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_3"));
underTest.peek(WORKER_UUID_2);

int canceledCount = underTest.cancelAll();
@@ -608,14 +627,16 @@ public class InternalCeQueueImplTest {
assertThat(task.getOrganizationUuid()).isEqualTo(componentDto.getOrganizationUuid());
}
assertThat(task.getUuid()).isEqualTo(taskSubmit.getUuid());
assertThat(task.getComponentUuid()).isEqualTo(task.getComponentUuid());
assertThat(task.getType()).isEqualTo(taskSubmit.getType());
if (componentDto == null) {
assertThat(task.getComponentKey()).isNull();
assertThat(task.getComponentName()).isNull();
if (componentDto != null) {
CeTask.Component component = task.getComponent().get();
assertThat(component.getUuid()).isEqualTo(componentDto.uuid());
assertThat(component.getKey()).contains(componentDto.getDbKey());
assertThat(component.getName()).contains(componentDto.name());
} else if (taskSubmit.getComponent().isPresent()) {
assertThat(task.getComponent()).contains(new CeTask.Component(taskSubmit.getComponent().get().getUuid(), null, null));
} else {
assertThat(task.getComponentKey()).isEqualTo(componentDto.getDbKey());
assertThat(task.getComponentName()).isEqualTo(componentDto.name());
assertThat(task.getComponent()).isEmpty();
}
assertThat(task.getSubmitterUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
}
@@ -625,30 +646,39 @@ public class InternalCeQueueImplTest {
assertThat(queueDto.isPresent()).isTrue();
CeQueueDto dto = queueDto.get();
assertThat(dto.getTaskType()).isEqualTo(taskSubmit.getType());
assertThat(dto.getComponentUuid()).isEqualTo(taskSubmit.getComponentUuid());
Optional<CeTaskSubmit.Component> component = taskSubmit.getComponent();
if (component.isPresent()) {
assertThat(dto.getMainComponentUuid()).isEqualTo(component.get().getMainComponentUuid());
assertThat(dto.getComponentUuid()).isEqualTo(component.get().getUuid());
} else {
assertThat(dto.getMainComponentUuid()).isNull();
assertThat(dto.getComponentUuid()).isNull();
}
assertThat(dto.getSubmitterUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
assertThat(dto.getCreatedAt()).isEqualTo(dto.getUpdatedAt()).isNotNull();
}

private ComponentDto newComponentDto(String uuid) {
private ComponentDto newProjectDto(String uuid) {
return ComponentTesting.newPublicProjectDto(db.getDefaultOrganization(), uuid).setName("name_" + uuid).setDbKey("key_" + uuid);
}

private CeTask submit(String reportType, String componentUuid) {
return underTest.submit(createTaskSubmit(reportType, componentUuid, null));
private CeTask submit(String reportType, ComponentDto componentDto) {
return underTest.submit(createTaskSubmit(reportType, componentDto, null));
}

private CeTaskSubmit createTaskSubmit(String type) {
return createTaskSubmit(type, null, null);
}

private CeTaskSubmit createTaskSubmit(String type, @Nullable String componentUuid, @Nullable String submitterUuid) {
return underTest.prepareSubmit()
private CeTaskSubmit createTaskSubmit(String type, @Nullable ComponentDto componentDto, @Nullable String submitterUuid) {
CeTaskSubmit.Builder builder = underTest.prepareSubmit()
.setType(type)
.setComponentUuid(componentUuid)
.setSubmitterUuid(submitterUuid)
.setCharacteristics(emptyMap())
.build();
.setCharacteristics(emptyMap());
if (componentDto != null) {
builder.setComponent(CeTaskSubmit.Component.fromDto(componentDto));
}
return builder.build();
}

private CeTaskResult newTaskResult(@Nullable String analysisUuid) {

+ 3
- 3
server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImplTest.java View File

@@ -107,13 +107,13 @@ public class CeTaskProcessorRepositoryImplTest {
}

private static CeTask createCeTask(String ceTaskType, String key) {
CeTask.Component component = new CeTask.Component("uuid_" + key, key, "name_" + key);
return new CeTask.Builder()
.setOrganizationUuid("org1")
.setType(ceTaskType)
.setUuid("task_uuid_" + key)
.setComponentKey(key)
.setComponentUuid("uuid_" + key)
.setComponentName("name_" + key)
.setComponent(component)
.setMainComponent(component)
.build();
}


+ 3
- 1
server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeWorkerImplTest.java View File

@@ -554,10 +554,12 @@ public class CeWorkerImplTest {
for (int i = 0; i < characteristics.length; i += 2) {
characteristicMap.put(characteristics[i], characteristics[i + 1]);
}
CeTask.Component component = new CeTask.Component("PROJECT_1", null, null);
return new CeTask.Builder()
.setOrganizationUuid("org1")
.setUuid("TASK_1").setType(CeTaskTypes.REPORT)
.setComponentUuid("PROJECT_1")
.setComponent(component)
.setMainComponent(component)
.setSubmitterUuid(submitterLogin)
.setCharacteristics(characteristicMap)
.build();

+ 16
- 0
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl View File

@@ -669,6 +669,8 @@ CREATE TABLE "CE_QUEUE" (
"UUID" VARCHAR(40) NOT NULL,
"TASK_TYPE" VARCHAR(15) NOT NULL,
"COMPONENT_UUID" VARCHAR(40) NULL,
"TMP_COMPONENT_UUID" VARCHAR(40) NULL,
"TMP_MAIN_COMPONENT_UUID" VARCHAR(40) NULL,
"STATUS" VARCHAR(15) NOT NULL,
"SUBMITTER_UUID" VARCHAR(255) NULL,
"WORKER_UUID" VARCHAR(40) NULL,
@@ -679,6 +681,8 @@ CREATE TABLE "CE_QUEUE" (
);
CREATE UNIQUE INDEX "CE_QUEUE_UUID" ON "CE_QUEUE" ("UUID");
CREATE INDEX "CE_QUEUE_COMPONENT_UUID" ON "CE_QUEUE" ("COMPONENT_UUID");
CREATE INDEX "CE_QUEUE_TMP_CPNT_UUID" ON "CE_QUEUE" ("TMP_COMPONENT_UUID");
CREATE INDEX "CE_QUEUE_TMP_MAIN_CPNT_UUID" ON "CE_QUEUE" ("TMP_MAIN_COMPONENT_UUID");
CREATE INDEX "CE_QUEUE_STATUS" ON "CE_QUEUE" ("STATUS");


@@ -687,10 +691,16 @@ CREATE TABLE "CE_ACTIVITY" (
"UUID" VARCHAR(40) NOT NULL,
"TASK_TYPE" VARCHAR(15) NOT NULL,
"COMPONENT_UUID" VARCHAR(40) NULL,
"TMP_COMPONENT_UUID" VARCHAR(40) NULL,
"TMP_MAIN_COMPONENT_UUID" VARCHAR(40) NULL,
"ANALYSIS_UUID" VARCHAR(50) NULL,
"STATUS" VARCHAR(15) NOT NULL,
"IS_LAST" BOOLEAN NOT NULL,
"IS_LAST_KEY" VARCHAR(55) NOT NULL,
"TMP_IS_LAST" BOOLEAN,
"TMP_IS_LAST_KEY" VARCHAR(55),
"TMP_MAIN_IS_LAST" BOOLEAN,
"TMP_MAIN_IS_LAST_KEY" VARCHAR(55),
"SUBMITTER_UUID" VARCHAR(255) NULL,
"WORKER_UUID" VARCHAR(40) NULL,
"EXECUTION_COUNT" INTEGER NOT NULL,
@@ -706,8 +716,14 @@ CREATE TABLE "CE_ACTIVITY" (
);
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_TMP_CPNT_UUID" ON "CE_ACTIVITY" ("TMP_COMPONENT_UUID");
CREATE INDEX "CE_ACTIVITY_TMP_MAIN_CPNT_UUID" ON "CE_ACTIVITY" ("TMP_MAIN_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 INDEX "CE_ACTIVITY_T_ISLAST_KEY" ON "CE_ACTIVITY" ("TMP_IS_LAST_KEY");
CREATE INDEX "CE_ACTIVITY_T_ISLAST" ON "CE_ACTIVITY" ("TMP_IS_LAST", "STATUS");
CREATE INDEX "CE_ACTIVITY_T_MAIN_ISLAST_KEY" ON "CE_ACTIVITY" ("TMP_MAIN_IS_LAST_KEY");
CREATE INDEX "CE_ACTIVITY_T_MAIN_ISLAST" ON "CE_ACTIVITY" ("TMP_MAIN_IS_LAST", "STATUS");


CREATE TABLE "CE_TASK_CHARACTERISTICS" (

+ 7
- 6
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDao.java View File

@@ -46,11 +46,12 @@ public class CeActivityDao implements Dao {
public void insert(DbSession dbSession, CeActivityDto dto) {
dto.setCreatedAt(system2.now());
dto.setUpdatedAt(system2.now());
dto.setIsLast(dto.getStatus() != CeActivityDto.Status.CANCELED);
boolean isLast = dto.getStatus() != CeActivityDto.Status.CANCELED;
dto.setIsLast(isLast);

CeActivityMapper ceActivityMapper = mapper(dbSession);
if (dto.getIsLast()) {
ceActivityMapper.updateIsLastToFalseForLastKey(dto.getIsLastKey(), dto.getUpdatedAt());
if (isLast) {
ceActivityMapper.clearIsLast(dto.getIsLastKey(), dto.getMainIsLastKey(), dto.getUpdatedAt());
}
ceActivityMapper.insert(dto);
}
@@ -67,15 +68,15 @@ public class CeActivityDao implements Dao {
* Ordered by id desc -> newest to oldest
*/
public List<CeActivityDto> selectByQuery(DbSession dbSession, CeTaskQuery query, Pagination pagination) {
if (query.isShortCircuitedByComponentUuids()) {
if (query.isShortCircuitedByMainComponentUuids()) {
return Collections.emptyList();
}

return mapper(dbSession).selectByQuery(query, pagination);
}

public int countLastByStatusAndComponentUuid(DbSession dbSession, CeActivityDto.Status status, @Nullable String componentUuid) {
return mapper(dbSession).countLastByStatusAndComponentUuid(status, componentUuid);
public int countLastByStatusAndMainComponentUuid(DbSession dbSession, CeActivityDto.Status status, @Nullable String mainComponentUuid) {
return mapper(dbSession).countLastByStatusAndMainComponentUuid(status, mainComponentUuid);
}

private static CeActivityMapper mapper(DbSession dbSession) {

+ 49
- 3
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDto.java View File

@@ -23,6 +23,7 @@ import com.google.common.base.Strings;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;

import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
@@ -36,12 +37,28 @@ public class CeActivityDto {
}

private String uuid;
/**
* Can be {@code null} when task is not associated to any data in table PROJECTS, but must always be non {@code null}
* at the same time as {@link #mainComponentUuid}.
* <p>
* The component uuid of a any component (project or not) is its own UUID.
*/
private String componentUuid;
/**
* Can be {@code null} when task is not associated to any data in table PROJECTS, but must always be non {@code null}
* at the same time as {@link #componentUuid}.
* <p>
* The main component uuid of the main branch of project is its own UUID. For other branches of a project, it is the
* project UUID of the main branch of that project ({@link ComponentDto#getMainBranchProjectUuid()}).
*/
private String mainComponentUuid;
private String analysisUuid;
private Status status;
private String taskType;
private boolean isLast;
private String isLastKey;
private boolean mainIsLast;
private String mainIsLastKey;
private String submitterUuid;
private String workerUuid;
private long submittedAt;
@@ -91,7 +108,9 @@ public class CeActivityDto {
this.uuid = queueDto.getUuid();
this.taskType = queueDto.getTaskType();
this.componentUuid = queueDto.getComponentUuid();
this.mainComponentUuid = queueDto.getMainComponentUuid();
this.isLastKey = format("%s%s", taskType, Strings.nullToEmpty(componentUuid));
this.mainIsLastKey = format("%s%s", taskType, Strings.nullToEmpty(mainComponentUuid));
this.submitterUuid = queueDto.getSubmitterUuid();
this.workerUuid = queueDto.getWorkerUuid();
this.submittedAt = queueDto.getCreatedAt();
@@ -102,8 +121,8 @@ public class CeActivityDto {
return uuid;
}

public CeActivityDto setUuid(String s) {
checkArgument(s.length() <= 40, "Value is too long for column CE_ACTIVITY.UUID: %s", s);
public CeActivityDto setUuid(@Nullable String s) {
validateUuid(s, "UUID");
this.uuid = s;
return this;
}
@@ -123,11 +142,26 @@ public class CeActivityDto {
}

public CeActivityDto setComponentUuid(@Nullable String s) {
checkArgument(s == null || s.length() <= 40, "Value is too long for column CE_ACTIVITY.COMPONENT_UUID: %s", s);
validateUuid(s, "COMPONENT_UUID");
this.componentUuid = s;
return this;
}

@CheckForNull
public String getMainComponentUuid() {
return mainComponentUuid;
}

public CeActivityDto setMainComponentUuid(@Nullable String s) {
validateUuid(s, "MAIN_COMPONENT_UUID");
this.mainComponentUuid = s;
return this;
}

private static void validateUuid(@Nullable String s, String columnName) {
checkArgument(s == null || s.length() <= 40, "Value is too long for column CE_ACTIVITY.%s: %s", columnName, s);
}

public Status getStatus() {
return status;
}
@@ -143,6 +177,7 @@ public class CeActivityDto {

CeActivityDto setIsLast(boolean b) {
this.isLast = b;
this.mainIsLast = b;
return this;
}

@@ -150,6 +185,14 @@ public class CeActivityDto {
return isLastKey;
}

public boolean getMainIsLast() {
return mainIsLast;
}

public String getMainIsLastKey() {
return mainIsLastKey;
}

@CheckForNull
public String getSubmitterUuid() {
return submitterUuid;
@@ -277,11 +320,14 @@ public class CeActivityDto {
return "CeActivityDto{" +
"uuid='" + uuid + '\'' +
", componentUuid='" + componentUuid + '\'' +
", mainComponentUuid='" + mainComponentUuid + '\'' +
", analysisUuid='" + analysisUuid + '\'' +
", status=" + status +
", taskType='" + taskType + '\'' +
", isLast=" + isLast +
", isLastKey='" + isLastKey + '\'' +
", mainIsLast=" + mainIsLast +
", mainIsLastKey='" + mainIsLastKey + '\'' +
", submitterUuid='" + submitterUuid + '\'' +
", workerUuid='" + workerUuid + '\'' +
", submittedAt=" + submittedAt +

+ 2
- 4
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityMapper.java View File

@@ -30,17 +30,15 @@ public interface CeActivityMapper {
@CheckForNull
CeActivityDto selectByUuid(@Param("uuid") String uuid);

List<CeActivityDto> selectByComponentUuid(@Param("componentUuid") String componentUuid);

List<CeActivityDto> selectByQuery(@Param("query") CeTaskQuery query, @Param("pagination") Pagination pagination);

List<CeActivityDto> selectOlderThan(@Param("beforeDate") long beforeDate);

int countLastByStatusAndComponentUuid(@Param("status") CeActivityDto.Status status, @Nullable @Param("componentUuid") String componentUuid);
int countLastByStatusAndMainComponentUuid(@Param("status") CeActivityDto.Status status, @Nullable @Param("mainComponentUuid") String mainComponentUuid);

void insert(CeActivityDto dto);

void updateIsLastToFalseForLastKey(@Param("isLastKey") String isLastKey, @Param("updatedAt") long updatedAt);
void clearIsLast(@Param("isLastKey") String isLastKey, @Param("mainIsLastKey") String mainIsLastKey, @Param("updatedAt") long updatedAt);

void deleteByUuids(@Param("uuids") List<String> uuids);
}

+ 14
- 14
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueDao.java View File

@@ -55,7 +55,7 @@ public class CeQueueDao implements Dao {
}

public List<CeQueueDto> selectByQueryInDescOrder(DbSession dbSession, CeTaskQuery query, int pageSize) {
if (query.isShortCircuitedByComponentUuids()
if (query.isShortCircuitedByMainComponentUuids()
|| query.isOnlyCurrents()
|| query.getMaxExecutedAt() != null) {
return emptyList();
@@ -65,7 +65,7 @@ public class CeQueueDao implements Dao {
}

public int countByQuery(DbSession dbSession, CeTaskQuery query) {
if (query.isShortCircuitedByComponentUuids()
if (query.isShortCircuitedByMainComponentUuids()
|| query.isOnlyCurrents()
|| query.getMaxExecutedAt() != null) {
return 0;
@@ -77,8 +77,8 @@ public class CeQueueDao implements Dao {
/**
* Ordered by ascending id: oldest to newest
*/
public List<CeQueueDto> selectByComponentUuid(DbSession session, String componentUuid) {
return mapper(session).selectByComponentUuid(componentUuid);
public List<CeQueueDto> selectByMainComponentUuid(DbSession session, String projectUuid) {
return mapper(session).selectByMainComponentUuid(projectUuid);
}

public Optional<CeQueueDto> selectByUuid(DbSession session, String uuid) {
@@ -131,30 +131,30 @@ public class CeQueueDao implements Dao {
}

public int countByStatus(DbSession dbSession, CeQueueDto.Status status) {
return mapper(dbSession).countByStatusAndComponentUuid(status, null);
return mapper(dbSession).countByStatusAndMainComponentUuid(status, null);
}

public int countByStatusAndComponentUuid(DbSession dbSession, CeQueueDto.Status status, @Nullable String componentUuid) {
return mapper(dbSession).countByStatusAndComponentUuid(status, componentUuid);
public int countByStatusAndMainComponentUuid(DbSession dbSession, CeQueueDto.Status status, @Nullable String mainComponentUuid) {
return mapper(dbSession).countByStatusAndMainComponentUuid(status, mainComponentUuid);
}

/**
* Counts entries in the queue with the specified status for each specified component uuid.
* Counts entries in the queue with the specified status for each specified main component uuid.
*
* The returned map doesn't contain any entry for component uuid for which there is no entry in the queue (ie.
* The returned map doesn't contain any entry for main component uuids for which there is no entry in the queue (ie.
* all entries have a value >= 0).
*/
public Map<String, Integer> countByStatusAndComponentUuids(DbSession dbSession, CeQueueDto.Status status, Set<String> componentUuids) {
if (componentUuids.isEmpty()) {
public Map<String, Integer> countByStatusAndMainComponentUuids(DbSession dbSession, CeQueueDto.Status status, Set<String> projectUuids) {
if (projectUuids.isEmpty()) {
return emptyMap();
}

ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder();
executeLargeUpdates(
componentUuids,
projectUuids,
uuids -> {
List<QueueCount> i = mapper(dbSession).countByStatusAndComponentUuids(status, componentUuids);
i.forEach(o -> builder.put(o.getComponentUuid(), o.getTotal()));
List<QueueCount> i = mapper(dbSession).countByStatusAndMainComponentUuids(status, projectUuids);
i.forEach(o -> builder.put(o.getMainComponentUuid(), o.getTotal()));
});
return builder.build();
}

+ 54
- 4
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueDto.java View File

@@ -21,8 +21,11 @@ package org.sonar.db.ce;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.db.component.ComponentDto;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;

public class CeQueueDto {

@@ -32,7 +35,21 @@ public class CeQueueDto {

private String uuid;
private String taskType;
/**
* Can be {@code null} when task is not associated to any data in table PROJECTS, but must always be non {@code null}
* at the same time as {@link #mainComponentUuid}.
* <p>
* The component uuid of a any component (project or not) is its own UUID.
*/
private String componentUuid;
/**
* Can be {@code null} when task is not associated to any data in table PROJECTS, but must always be non {@code null}
* at the same time as {@link #componentUuid}.
* <p>
* The main component uuid of the main branch of project is its own UUID. For other branches of a project, it is the
* project UUID of the main branch of that project ({@link ComponentDto#getMainBranchProjectUuid()}).
*/
private String mainComponentUuid;
private Status status;
private String submitterUuid;
/**
@@ -48,22 +65,52 @@ public class CeQueueDto {
}

public CeQueueDto setUuid(String s) {
checkArgument(s.length() <= 40, "Value of UUID is too long: %s", s);
checkUuid(s, "UUID");
this.uuid = s;
return this;
}

/**
* Helper methods which sets both {@link #componentUuid} and {@link #mainComponentUuid} from the specified
* {@link ComponentDto}.
*/
public CeQueueDto setComponent(@Nullable ComponentDto component) {
if (component == null) {
this.componentUuid = null;
this.mainComponentUuid = null;
} else {
this.componentUuid = requireNonNull(component.uuid());
this.mainComponentUuid = firstNonNull(component.getMainBranchProjectUuid(), component.uuid());
}
return this;
}

@CheckForNull
public String getComponentUuid() {
return componentUuid;
}

public CeQueueDto setComponentUuid(@Nullable String s) {
checkArgument(s == null || s.length() <= 40, "Value of component UUID is too long: %s", s);
checkUuid(s, "COMPONENT_UUID");
this.componentUuid = s;
return this;
}

@CheckForNull
public String getMainComponentUuid() {
return mainComponentUuid;
}

public CeQueueDto setMainComponentUuid(@Nullable String s) {
checkUuid(s, "MAIN_COMPONENT_UUID");
this.mainComponentUuid = s;
return this;
}

private static void checkUuid(@Nullable String s, String columnName) {
checkArgument(s == null || s.length() <= 40, "Value is too long for column CE_QUEUE.%s: %s", columnName, s);
}

public Status getStatus() {
return status;
}
@@ -101,8 +148,9 @@ public class CeQueueDto {
/**
* Accessed by MyBatis through reflexion. Field is otherwise read-only.
*/
private void setWorkerUuid(@Nullable String workerUuid) {
protected CeQueueDto setWorkerUuid(@Nullable String workerUuid) {
this.workerUuid = workerUuid;
return this;
}

@CheckForNull
@@ -113,8 +161,9 @@ public class CeQueueDto {
/**
* Accessed by MyBatis through reflexion. Field is otherwise read-only.
*/
private void setStartedAt(@Nullable Long l) {
protected CeQueueDto setStartedAt(@Nullable Long l) {
this.startedAt = l;
return this;
}

public long getCreatedAt() {
@@ -141,6 +190,7 @@ public class CeQueueDto {
"uuid='" + uuid + '\'' +
", taskType='" + taskType + '\'' +
", componentUuid='" + componentUuid + '\'' +
", mainComponentUuid='" + mainComponentUuid + '\'' +
", status=" + status +
", submitterLogin='" + submitterUuid + '\'' +
", workerUuid='" + workerUuid + '\'' +

+ 3
- 3
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueMapper.java View File

@@ -29,7 +29,7 @@ import org.sonar.db.Pagination;

public interface CeQueueMapper {

List<CeQueueDto> selectByComponentUuid(@Param("componentUuid") String componentUuid);
List<CeQueueDto> selectByMainComponentUuid(@Param("mainComponentUuid") String mainComponentUuid);

List<CeQueueDto> selectAllInAscOrder();

@@ -62,9 +62,9 @@ public interface CeQueueMapper {
*/
void resetAllInProgressTasks(@Param("updatedAt") long updatedAt);

int countByStatusAndComponentUuid(@Param("status") CeQueueDto.Status status, @Nullable @Param("componentUuid") String componentUuid);
int countByStatusAndMainComponentUuid(@Param("status") CeQueueDto.Status status, @Nullable @Param("mainComponentUuid") String mainComponentUuid);

List<QueueCount> countByStatusAndComponentUuids(@Param("status") CeQueueDto.Status status, @Param("componentUuids") Set<String> componentUuids);
List<QueueCount> countByStatusAndMainComponentUuids(@Param("status") CeQueueDto.Status status, @Param("mainComponentUuids") Set<String> mainComponentUuids);

void insert(CeQueueDto dto);


+ 10
- 10
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskQuery.java View File

@@ -36,31 +36,31 @@ public class CeTaskQuery {

private boolean onlyCurrents = false;
// SONAR-7681 a public implementation of List must be used in MyBatis - potential concurrency exceptions otherwise
private ArrayList<String> componentUuids;
private ArrayList<String> mainComponentUuids;
private ArrayList<String> statuses;
private String type;
private Long minSubmittedAt;
private Long maxExecutedAt;

@CheckForNull
public List<String> getComponentUuids() {
return componentUuids;
public List<String> getMainComponentUuids() {
return mainComponentUuids;
}

public CeTaskQuery setComponentUuids(@Nullable List<String> l) {
this.componentUuids = l == null ? null : newArrayList(l);
public CeTaskQuery setMainComponentUuids(@Nullable List<String> l) {
this.mainComponentUuids = l == null ? null : newArrayList(l);
return this;
}

public boolean isShortCircuitedByComponentUuids() {
return componentUuids != null && (componentUuids.isEmpty() || componentUuids.size() > MAX_COMPONENT_UUIDS);
public boolean isShortCircuitedByMainComponentUuids() {
return mainComponentUuids != null && (mainComponentUuids.isEmpty() || mainComponentUuids.size() > MAX_COMPONENT_UUIDS);
}

public CeTaskQuery setComponentUuid(@Nullable String s) {
public CeTaskQuery setMainComponentUuid(@Nullable String s) {
if (s == null) {
this.componentUuids = null;
this.mainComponentUuids = null;
} else {
this.componentUuids = newArrayList(s);
this.mainComponentUuids = newArrayList(s);
}
return this;
}

+ 3
- 3
server/sonar-db-dao/src/main/java/org/sonar/db/ce/QueueCount.java View File

@@ -21,11 +21,11 @@ package org.sonar.db.ce;

public class QueueCount {
// set by reflection by MyBatis
private String componentUuid;
private String mainComponentUuid;
private int total;

public String getComponentUuid() {
return componentUuid;
public String getMainComponentUuid() {
return mainComponentUuid;
}

public int getTotal() {

+ 2
- 2
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java View File

@@ -112,7 +112,7 @@ public class ComponentDao implements Dao {
/**
* @throws IllegalArgumentException if parameter query#getComponentIds() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
* @throws IllegalArgumentException if parameter query#getComponentKeys() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
* @throws IllegalArgumentException if parameter query#getComponentUuids() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
* @throws IllegalArgumentException if parameter query#getMainComponentUuids() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
*/
public List<ComponentDto> selectByQuery(DbSession dbSession, String organizationUuid, ComponentQuery query, int offset, int limit) {
requireNonNull(organizationUuid, "organizationUuid can't be null");
@@ -130,7 +130,7 @@ public class ComponentDao implements Dao {
/**
* @throws IllegalArgumentException if parameter query#getComponentIds() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
* @throws IllegalArgumentException if parameter query#getComponentKeys() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
* @throws IllegalArgumentException if parameter query#getComponentUuids() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
* @throws IllegalArgumentException if parameter query#getMainComponentUuids() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
*/
public int countByQuery(DbSession session, String organizationUuid, ComponentQuery query) {
requireNonNull(organizationUuid, "organizationUuid can't be null");

+ 37
- 17
server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeActivityMapper.xml View File

@@ -19,7 +19,8 @@
ca.id,
ca.uuid,
ca.task_type as taskType,
ca.component_uuid as componentUuid,
ca.tmp_component_uuid as componentUuid,
ca.tmp_main_component_uuid as mainComponentUuid,
ca.analysis_uuid as analysisUuid,
ca.status as status,
ca.submitter_uuid as submitterUuid,
@@ -29,8 +30,10 @@
ca.executed_at as executedAt,
ca.created_at as createdAt,
ca.updated_at as updatedAt,
ca.is_last as isLast,
ca.is_last_key as isLastKey,
ca.tmp_is_last as isLast,
ca.tmp_is_last_key as isLastKey,
ca.tmp_main_is_last as mainIsLast,
ca.tmp_main_is_last_key as mainIsLastKey,
ca.execution_time_ms as executionTimeMs,
ca.error_message as errorMessage,
ca.error_type as errorType,
@@ -83,11 +86,11 @@
left outer join ce_scanner_context csc on csc.task_uuid = ca.uuid
<where>
<if test="query.onlyCurrents">
and ca.is_last=${_true}
and ca.tmp_main_is_last=${_true}
</if>
<if test="query.componentUuids != null and query.componentUuids.size()>0">
and ca.component_uuid in
<foreach collection="query.componentUuids" open="(" close=")" item="cUuid" separator=",">
<if test="query.mainComponentUuids != null and query.mainComponentUuids.size()>0">
and ca.tmp_main_component_uuid in
<foreach collection="query.mainComponentUuids" open="(" close=")" item="cUuid" separator=",">
#{cUuid,jdbcType=VARCHAR}
</foreach>
</if>
@@ -118,26 +121,32 @@
ca.created_at &lt; #{beforeDate,jdbcType=BIGINT}
</select>
<select id="countLastByStatusAndComponentUuid" resultType="int">
<select id="countLastByStatusAndMainComponentUuid" resultType="int">
select
count(1)
from
ce_activity
where
status=#{status,jdbcType=VARCHAR}
and is_last=${_true}
<if test="componentUuid!=null">
and component_uuid=#{componentUuid,jdbcType=VARCHAR}
tmp_main_is_last=${_true}
and status=#{status,jdbcType=VARCHAR}
<if test="mainComponentUuid!=null">
and tmp_main_component_uuid=#{mainComponentUuid,jdbcType=VARCHAR}
</if>
</select>

<insert id="insert" parameterType="org.sonar.db.ce.CeActivityDto" useGeneratedKeys="false">
insert into ce_activity (
uuid,
tmp_component_uuid,
tmp_main_component_uuid,
component_uuid,
analysis_uuid,
status,
task_type,
tmp_is_last,
tmp_is_last_key,
tmp_main_is_last,
tmp_main_is_last_key,
is_last,
is_last_key,
submitter_uuid,
@@ -156,11 +165,17 @@
values (
#{uuid,jdbcType=VARCHAR},
#{componentUuid,jdbcType=VARCHAR},
#{mainComponentUuid,jdbcType=VARCHAR},
#{mainComponentUuid,jdbcType=VARCHAR},
#{analysisUuid,jdbcType=VARCHAR},
#{status,jdbcType=VARCHAR},
#{taskType,jdbcType=VARCHAR},
#{isLast,jdbcType=BOOLEAN},
#{isLastKey,jdbcType=VARCHAR},
#{mainIsLast,jdbcType=BOOLEAN},
#{mainIsLastKey,jdbcType=VARCHAR},
#{mainIsLast,jdbcType=BOOLEAN},
#{mainIsLastKey,jdbcType=VARCHAR},
#{submitterUuid,jdbcType=VARCHAR},
#{submittedAt,jdbcType=BIGINT},
#{workerUuid,jdbcType=VARCHAR},
@@ -176,11 +191,16 @@
)
</insert>

<update id="updateIsLastToFalseForLastKey" parameterType="map">
update ce_activity
set is_last=${_false},
updated_at=#{updatedAt,jdbcType=BIGINT}
where is_last=${_true} and is_last_key=#{isLastKey,jdbcType=VARCHAR}
<update id="clearIsLast" parameterType="map">
update ce_activity set
tmp_is_last=${_false},
tmp_main_is_last=${_false},
is_last=${_false},
updated_at=#{updatedAt,jdbcType=BIGINT}
where
(tmp_is_last=${_true} and tmp_is_last_key=#{isLastKey,jdbcType=VARCHAR})
or
(tmp_main_is_last=${_true} and tmp_main_is_last_key=#{mainIsLastKey,jdbcType=VARCHAR})
</update>

<delete id="deleteByUuids" parameterType="string">

+ 22
- 17
server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeQueueMapper.xml View File

@@ -6,7 +6,8 @@
<sql id="columns">
cq.uuid,
cq.task_type as taskType,
cq.component_uuid as componentUuid,
cq.tmp_component_uuid as componentUuid,
cq.tmp_main_component_uuid as mainComponentUuid,
cq.status as status,
cq.submitter_uuid as submitterUuid,
cq.worker_uuid as workerUuid,
@@ -36,31 +37,31 @@
cq.uuid=#{uuid,jdbcType=VARCHAR}
</select>

<select id="countByStatusAndComponentUuid" parameterType="map" resultType="int">
<select id="countByStatusAndMainComponentUuid" parameterType="map" resultType="int">
select
count(1)
from
ce_queue
where
status=#{status,jdbcType=VARCHAR}
<if test="componentUuid!=null">
and component_uuid=#{componentUuid,jdbcType=VARCHAR}
<if test="mainComponentUuid!=null">
and tmp_main_component_uuid=#{mainComponentUuid,jdbcType=VARCHAR}
</if>
</select>

<select id="countByStatusAndComponentUuids" resultType="org.sonar.db.ce.QueueCount">
<select id="countByStatusAndMainComponentUuids" resultType="org.sonar.db.ce.QueueCount">
select
component_uuid as componentUuid,
tmp_main_component_uuid as mainComponentUuid,
count(1) as total
from
ce_queue
where
status=#{status,jdbcType=VARCHAR}
and component_uuid in
<foreach collection="componentUuids" open="(" close=")" item="cUuid" separator=",">
#{cUuid,jdbcType=VARCHAR}
and tmp_main_component_uuid in
<foreach collection="mainComponentUuids" open="(" close=")" item="mainComponentUuid" separator=",">
#{mainComponentUuid,jdbcType=VARCHAR}
</foreach>
group by component_uuid
group by tmp_main_component_uuid
</select>

<select id="countAll" resultType="int">
@@ -70,13 +71,13 @@
ce_queue
</select>

<select id="selectByComponentUuid" parameterType="String" resultType="org.sonar.db.ce.CeQueueDto">
<select id="selectByMainComponentUuid" parameterType="String" resultType="org.sonar.db.ce.CeQueueDto">
select
<include refid="columns"/>
from
ce_queue cq
where
cq.component_uuid=#{componentUuid,jdbcType=VARCHAR}
cq.tmp_main_component_uuid=#{mainComponentUuid,jdbcType=VARCHAR}
<include refid="orderByDateAndId"/>
</select>

@@ -105,10 +106,10 @@
from
ce_queue cq
<where>
<if test="query.componentUuids != null and query.componentUuids.size()>0">
and cq.component_uuid in
<foreach collection="query.componentUuids" open="(" close=")" item="cUuid" separator=",">
#{cUuid,jdbcType=VARCHAR}
<if test="query.mainComponentUuids != null and query.mainComponentUuids.size()>0">
and cq.tmp_main_component_uuid in
<foreach collection="query.mainComponentUuids" open="(" close=")" item="mainComponentUuid" separator=",">
#{mainComponentUuid,jdbcType=VARCHAR}
</foreach>
</if>
<if test="query.statuses != null">
@@ -176,7 +177,7 @@
from
ce_queue cq2
where
cq.component_uuid=cq2.component_uuid
cq.tmp_main_component_uuid=cq2.tmp_main_component_uuid
and cq2.status &lt;&gt; 'PENDING'
)
</sql>
@@ -211,7 +212,9 @@
(
uuid,
task_type,
tmp_component_uuid,
component_uuid,
tmp_main_component_uuid,
status,
submitter_uuid,
execution_count,
@@ -222,6 +225,8 @@
#{uuid,jdbcType=VARCHAR},
#{taskType,jdbcType=VARCHAR},
#{componentUuid,jdbcType=VARCHAR},
#{mainComponentUuid,jdbcType=VARCHAR},
#{mainComponentUuid,jdbcType=VARCHAR},
#{status,jdbcType=VARCHAR},
#{submitterUuid,jdbcType=VARCHAR},
0,

+ 8
- 8
server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml View File

@@ -315,45 +315,45 @@
<delete id="deleteCeScannerContextOfCeActivityByProjectUuid">
delete from ce_scanner_context
where
task_uuid in (select uuid from ce_activity where component_uuid=#{projectUuid,jdbcType=VARCHAR})
task_uuid in (select uuid from ce_activity where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR})
</delete>

<delete id="deleteCeTaskCharacteristicsOfCeActivityByProjectUuid">
delete from ce_task_characteristics
where
task_uuid in (select uuid from ce_activity where component_uuid=#{projectUuid,jdbcType=VARCHAR})
task_uuid in (select uuid from ce_activity where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR})
</delete>

<delete id="deleteCeTaskInputOfCeActivityByProjectUuid">
delete from ce_task_input
where
task_uuid in (select uuid from ce_activity where component_uuid=#{projectUuid,jdbcType=VARCHAR})
task_uuid in (select uuid from ce_activity where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR})
</delete>

<delete id="deleteCeActivityByProjectUuid">
delete from ce_activity where component_uuid=#{projectUuid,jdbcType=VARCHAR}
delete from ce_activity where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR}
</delete>

<delete id="deleteCeScannerContextOfCeQueueByProjectUuid">
delete from ce_scanner_context
where
task_uuid in (select uuid from ce_queue where component_uuid=#{projectUuid,jdbcType=VARCHAR})
task_uuid in (select uuid from ce_queue where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR})
</delete>

<delete id="deleteCeTaskCharacteristicsOfCeQueueByProjectUuid">
delete from ce_task_characteristics
where
task_uuid in (select uuid from ce_queue where component_uuid=#{projectUuid,jdbcType=VARCHAR})
task_uuid in (select uuid from ce_queue where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR})
</delete>

<delete id="deleteCeTaskInputOfCeQueueByProjectUuid">
delete from ce_task_input
where
task_uuid in (select uuid from ce_queue where component_uuid=#{projectUuid,jdbcType=VARCHAR})
task_uuid in (select uuid from ce_queue where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR})
</delete>

<delete id="deleteCeQueueByProjectUuid">
delete from ce_queue where component_uuid=#{projectUuid,jdbcType=VARCHAR}
delete from ce_queue where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR}
</delete>

<delete id="deleteWebhooksByProjectUuid">

+ 62
- 48
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java View File

@@ -26,6 +26,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.utils.internal.TestSystem2;
@@ -37,6 +38,7 @@ import org.sonar.db.Pagination;

import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.db.Pagination.forPage;
import static org.sonar.db.ce.CeActivityDto.Status.FAILED;
@@ -47,6 +49,10 @@ import static org.sonar.db.ce.CeTaskTypes.REPORT;

public class CeActivityDaoTest {

private static final String MAINCOMPONENT_1 = randomAlphabetic(12);
private static final String MAINCOMPONENT_2 = randomAlphabetic(13);
private static final String COMPONENT_1 = randomAlphabetic(14);

private TestSystem2 system2 = new TestSystem2().setNow(1_450_000_000_000L);

@Rule
@@ -57,19 +63,22 @@ public class CeActivityDaoTest {

@Test
public void test_insert() {
CeActivityDto inserted = insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS);
CeActivityDto inserted = insert("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);

Optional<CeActivityDto> saved = underTest.selectByUuid(db.getSession(), "TASK_1");
assertThat(saved).isPresent();
CeActivityDto dto = saved.get();
assertThat(dto.getUuid()).isEqualTo("TASK_1");
assertThat(dto.getComponentUuid()).isEqualTo("PROJECT_1");
assertThat(dto.getMainComponentUuid()).isEqualTo(MAINCOMPONENT_1);
assertThat(dto.getComponentUuid()).isEqualTo(COMPONENT_1);
assertThat(dto.getStatus()).isEqualTo(CeActivityDto.Status.SUCCESS);
assertThat(dto.getSubmitterUuid()).isEqualTo("submitter uuid");
assertThat(dto.getSubmittedAt()).isEqualTo(1_450_000_000_000L);
assertThat(dto.getWorkerUuid()).isEqualTo("worker uuid");
assertThat(dto.getIsLast()).isTrue();
assertThat(dto.getIsLastKey()).isEqualTo("REPORTPROJECT_1");
assertThat(dto.getMainIsLast()).isTrue();
assertThat(dto.getIsLastKey()).isEqualTo("REPORT" + COMPONENT_1);
assertThat(dto.getMainIsLastKey()).isEqualTo("REPORT" + MAINCOMPONENT_1);
assertThat(dto.getCreatedAt()).isEqualTo(1_450_000_000_000L);
assertThat(dto.getStartedAt()).isEqualTo(1_500_000_000_000L);
assertThat(dto.getExecutedAt()).isEqualTo(1_500_000_000_500L);
@@ -84,7 +93,7 @@ public class CeActivityDaoTest {

@Test
public void test_insert_of_errorMessage_of_1_000_chars() {
CeActivityDto dto = createActivityDto("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED)
CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.FAILED)
.setErrorMessage(Strings.repeat("x", 1_000));
underTest.insert(db.getSession(), dto);

@@ -95,7 +104,7 @@ public class CeActivityDaoTest {
@Test
public void test_insert_of_errorMessage_of_1_001_chars_is_truncated_to_1000() {
String expected = Strings.repeat("x", 1_000);
CeActivityDto dto = createActivityDto("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED)
CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.FAILED)
.setErrorMessage(expected + "y");
underTest.insert(db.getSession(), dto);

@@ -105,7 +114,7 @@ public class CeActivityDaoTest {

@Test
public void test_insert_error_message_and_stacktrace() {
CeActivityDto dto = createActivityDto("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED)
CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.FAILED)
.setErrorStacktrace("error stack");
underTest.insert(db.getSession(), dto);

@@ -118,7 +127,7 @@ public class CeActivityDaoTest {

@Test
public void test_insert_error_message_only() {
CeActivityDto dto = createActivityDto("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED);
CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.FAILED);
underTest.insert(db.getSession(), dto);

Optional<CeActivityDto> saved = underTest.selectByUuid(db.getSession(), "TASK_1");
@@ -129,22 +138,22 @@ public class CeActivityDaoTest {

@Test
public void insert_must_set_relevant_is_last_field() {
// only a single task on PROJECT_1 -> is_last=true
insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS);
// only a single task on MAINCOMPONENT_1 -> is_last=true
insert("TASK_1", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").get().getIsLast()).isTrue();

// only a single task on PROJECT_2 -> is_last=true
insert("TASK_2", REPORT, "PROJECT_2", CeActivityDto.Status.SUCCESS);
// only a single task on MAINCOMPONENT_2 -> is_last=true
insert("TASK_2", REPORT, MAINCOMPONENT_2, CeActivityDto.Status.SUCCESS);
assertThat(underTest.selectByUuid(db.getSession(), "TASK_2").get().getIsLast()).isTrue();

// two tasks on PROJECT_1, the most recent one is TASK_3
insert("TASK_3", REPORT, "PROJECT_1", FAILED);
// two tasks on MAINCOMPONENT_1, the most recent one is TASK_3
insert("TASK_3", REPORT, MAINCOMPONENT_1, FAILED);
assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").get().getIsLast()).isFalse();
assertThat(underTest.selectByUuid(db.getSession(), "TASK_2").get().getIsLast()).isTrue();
assertThat(underTest.selectByUuid(db.getSession(), "TASK_3").get().getIsLast()).isTrue();

// inserting a cancelled task does not change the last task
insert("TASK_4", REPORT, "PROJECT_1", CeActivityDto.Status.CANCELED);
insert("TASK_4", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.CANCELED);
assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").get().getIsLast()).isFalse();
assertThat(underTest.selectByUuid(db.getSession(), "TASK_2").get().getIsLast()).isTrue();
assertThat(underTest.selectByUuid(db.getSession(), "TASK_3").get().getIsLast()).isTrue();
@@ -153,9 +162,9 @@ public class CeActivityDaoTest {

@Test
public void test_selectByQuery() {
insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS);
insert("TASK_2", REPORT, "PROJECT_1", FAILED);
insert("TASK_3", REPORT, "PROJECT_2", CeActivityDto.Status.SUCCESS);
insert("TASK_1", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
insert("TASK_2", REPORT, MAINCOMPONENT_1, FAILED);
insert("TASK_3", REPORT, MAINCOMPONENT_2, CeActivityDto.Status.SUCCESS);
insert("TASK_4", "views", null, CeActivityDto.Status.SUCCESS);

// no filters
@@ -164,7 +173,7 @@ public class CeActivityDaoTest {
assertThat(dtos).extracting("uuid").containsExactly("TASK_4", "TASK_3", "TASK_2", "TASK_1");

// select by component uuid
query = new CeTaskQuery().setComponentUuid("PROJECT_1");
query = new CeTaskQuery().setMainComponentUuid(MAINCOMPONENT_1);
dtos = underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(100));
assertThat(dtos).extracting("uuid").containsExactly("TASK_2", "TASK_1");

@@ -182,17 +191,17 @@ public class CeActivityDaoTest {
assertThat(dtos).extracting("uuid").containsExactly("TASK_4");

// select by multiple conditions
query = new CeTaskQuery().setType(REPORT).setOnlyCurrents(true).setComponentUuid("PROJECT_1");
query = new CeTaskQuery().setType(REPORT).setOnlyCurrents(true).setMainComponentUuid(MAINCOMPONENT_1);
dtos = underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(100));
assertThat(dtos).extracting("uuid").containsExactly("TASK_2");
}

@Test
public void selectByQuery_does_not_populate_errorStacktrace_field() {
insert("TASK_1", REPORT, "PROJECT_1", FAILED);
underTest.insert(db.getSession(), createActivityDto("TASK_2", REPORT, "PROJECT_1", FAILED).setErrorStacktrace("some stack"));
insert("TASK_1", REPORT, MAINCOMPONENT_1, FAILED);
underTest.insert(db.getSession(), createActivityDto("TASK_2", REPORT, COMPONENT_1, MAINCOMPONENT_1, FAILED).setErrorStacktrace("some stack"));

List<CeActivityDto> dtos = underTest.selectByQuery(db.getSession(), new CeTaskQuery().setComponentUuid("PROJECT_1"), forPage(1).andSize(100));
List<CeActivityDto> dtos = underTest.selectByQuery(db.getSession(), new CeTaskQuery().setMainComponentUuid(MAINCOMPONENT_1), forPage(1).andSize(100));

assertThat(dtos)
.hasSize(2)
@@ -201,21 +210,21 @@ public class CeActivityDaoTest {

@Test
public void selectByQuery_populates_hasScannerContext_flag() {
insert("TASK_1", REPORT, "PROJECT_1", SUCCESS);
CeActivityDto dto2 = insert("TASK_2", REPORT, "PROJECT_2", SUCCESS);
insert("TASK_1", REPORT, MAINCOMPONENT_1, SUCCESS);
CeActivityDto dto2 = insert("TASK_2", REPORT, MAINCOMPONENT_2, SUCCESS);
insertScannerContext(dto2.getUuid());

CeActivityDto dto = underTest.selectByQuery(db.getSession(), new CeTaskQuery().setComponentUuid("PROJECT_1"), forPage(1).andSize(100)).iterator().next();
CeActivityDto dto = underTest.selectByQuery(db.getSession(), new CeTaskQuery().setMainComponentUuid(MAINCOMPONENT_1), forPage(1).andSize(100)).iterator().next();
assertThat(dto.isHasScannerContext()).isFalse();
dto = underTest.selectByQuery(db.getSession(), new CeTaskQuery().setComponentUuid("PROJECT_2"), forPage(1).andSize(100)).iterator().next();
dto = underTest.selectByQuery(db.getSession(), new CeTaskQuery().setMainComponentUuid(MAINCOMPONENT_2), forPage(1).andSize(100)).iterator().next();
assertThat(dto.isHasScannerContext()).isTrue();
}

@Test
public void selectByQuery_is_paginated_and_return_results_sorted_from_last_to_first() {
insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS);
insert("TASK_2", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED);
insert("TASK_3", REPORT, "PROJECT_2", CeActivityDto.Status.SUCCESS);
insert("TASK_1", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
insert("TASK_2", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.FAILED);
insert("TASK_3", REPORT, MAINCOMPONENT_2, CeActivityDto.Status.SUCCESS);
insert("TASK_4", "views", null, CeActivityDto.Status.SUCCESS);

assertThat(selectPageOfUuids(forPage(1).andSize(1))).containsExactly("TASK_4");
@@ -229,10 +238,10 @@ public class CeActivityDaoTest {

@Test
public void selectByQuery_no_results_if_shortcircuited_by_component_uuids() {
insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS);
insert("TASK_1", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);

CeTaskQuery query = new CeTaskQuery();
query.setComponentUuids(Collections.emptyList());
query.setMainComponentUuids(Collections.emptyList());
assertThat(underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(1))).isEmpty();
}

@@ -291,8 +300,8 @@ public class CeActivityDaoTest {

@Test
public void selectOlderThan_does_not_populate_errorStacktrace() {
insert("TASK_1", REPORT, "PROJECT_1", FAILED);
underTest.insert(db.getSession(), createActivityDto("TASK_2", REPORT, "PROJECT_1", FAILED).setErrorStacktrace("some stack"));
insert("TASK_1", REPORT, MAINCOMPONENT_1, FAILED);
underTest.insert(db.getSession(), createActivityDto("TASK_2", REPORT, COMPONENT_1, MAINCOMPONENT_1, FAILED).setErrorStacktrace("some stack"));

List<CeActivityDto> dtos = underTest.selectOlderThan(db.getSession(), system2.now() + 1_000_000L);

@@ -303,9 +312,9 @@ public class CeActivityDaoTest {

@Test
public void deleteByUuids() {
insert("TASK_1", "REPORT", "COMPONENT1", CeActivityDto.Status.SUCCESS);
insert("TASK_2", "REPORT", "COMPONENT1", CeActivityDto.Status.SUCCESS);
insert("TASK_3", "REPORT", "COMPONENT1", CeActivityDto.Status.SUCCESS);
insert("TASK_1", "REPORT", MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
insert("TASK_2", "REPORT", MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
insert("TASK_3", "REPORT", MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);

underTest.deleteByUuids(db.getSession(), ImmutableSet.of("TASK_1", "TASK_3"));
assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").isPresent()).isFalse();
@@ -315,7 +324,7 @@ public class CeActivityDaoTest {

@Test
public void deleteByUuids_does_nothing_if_uuid_does_not_exist() {
insert("TASK_1", "REPORT", "COMPONENT1", CeActivityDto.Status.SUCCESS);
insert("TASK_1", "REPORT", MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);

// must not fail
underTest.deleteByUuids(db.getSession(), singleton("TASK_2"));
@@ -324,33 +333,38 @@ public class CeActivityDaoTest {
}

@Test
public void count_last_by_status_and_component_uuid() {
insert("TASK_1", CeTaskTypes.REPORT, "COMPONENT1", CeActivityDto.Status.SUCCESS);
public void count_last_by_status_and_main_component_uuid() {
insert("TASK_1", CeTaskTypes.REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
// component 2
insert("TASK_2", CeTaskTypes.REPORT, "COMPONENT2", CeActivityDto.Status.SUCCESS);
insert("TASK_2", CeTaskTypes.REPORT, MAINCOMPONENT_2, CeActivityDto.Status.SUCCESS);
// status failed
insert("TASK_3", CeTaskTypes.REPORT, "COMPONENT1", CeActivityDto.Status.FAILED);
insert("TASK_3", CeTaskTypes.REPORT, MAINCOMPONENT_1, CeActivityDto.Status.FAILED);
// status canceled
insert("TASK_4", CeTaskTypes.REPORT, "COMPONENT1", CeActivityDto.Status.CANCELED);
insert("TASK_5", CeTaskTypes.REPORT, "COMPONENT1", CeActivityDto.Status.SUCCESS);
insert("TASK_4", CeTaskTypes.REPORT, MAINCOMPONENT_1, CeActivityDto.Status.CANCELED);
insert("TASK_5", CeTaskTypes.REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
db.commit();

assertThat(underTest.countLastByStatusAndComponentUuid(dbSession, SUCCESS, "COMPONENT1")).isEqualTo(1);
assertThat(underTest.countLastByStatusAndComponentUuid(dbSession, SUCCESS, null)).isEqualTo(2);
assertThat(underTest.countLastByStatusAndMainComponentUuid(dbSession, SUCCESS, MAINCOMPONENT_1)).isEqualTo(1);
assertThat(underTest.countLastByStatusAndMainComponentUuid(dbSession, SUCCESS, null)).isEqualTo(2);
}

private CeActivityDto insert(String uuid, String type, @Nullable String mainComponentUuid, CeActivityDto.Status status) {
return insert(uuid, type, mainComponentUuid, mainComponentUuid, status);
}

private CeActivityDto insert(String uuid, String type, String componentUuid, CeActivityDto.Status status) {
CeActivityDto dto = createActivityDto(uuid, type, componentUuid, status);
private CeActivityDto insert(String uuid, String type, String componentUuid, @Nullable String mainComponentUuid, CeActivityDto.Status status) {
CeActivityDto dto = createActivityDto(uuid, type, componentUuid, mainComponentUuid, status);
underTest.insert(db.getSession(), dto);
return dto;
}

private CeActivityDto createActivityDto(String uuid, String type, String componentUuid, CeActivityDto.Status status) {
private CeActivityDto createActivityDto(String uuid, String type, @Nullable String componentUuid, @Nullable String mainComponentUuid, CeActivityDto.Status status) {
CeQueueDto creating = new CeQueueDto();
creating.setUuid(uuid);
creating.setStatus(PENDING);
creating.setTaskType(type);
creating.setComponentUuid(componentUuid);
creating.setMainComponentUuid(mainComponentUuid);
creating.setSubmitterUuid("submitter uuid");
creating.setCreatedAt(1_300_000_000_000L);


+ 73
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDtoTest.java View File

@@ -23,7 +23,9 @@ import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Random;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
@@ -31,8 +33,79 @@ import static org.assertj.core.api.Assertions.assertThat;

@RunWith(DataProviderRunner.class)
public class CeActivityDtoTest {
private static final String STR_40_CHARS = "0123456789012345678901234567890123456789";

@Rule
public ExpectedException expectedException = ExpectedException.none();

private CeActivityDto underTest = new CeActivityDto();

@Test
public void constructor_from_CeQueueDto_populates_fields() {
long now = new Random().nextLong();
CeQueueDto ceQueueDto = new CeQueueDto()
.setUuid(randomAlphanumeric(10))
.setTaskType(randomAlphanumeric(11))
.setComponentUuid(randomAlphanumeric(12))
.setMainComponentUuid(randomAlphanumeric(13))
.setSubmitterUuid(randomAlphanumeric(14))
.setWorkerUuid(randomAlphanumeric(15))
.setCreatedAt(now + 9_999)
.setStartedAt(now + 865);

CeActivityDto underTest = new CeActivityDto(ceQueueDto);

assertThat(underTest.getUuid()).isEqualTo(ceQueueDto.getUuid());
assertThat(underTest.getTaskType()).isEqualTo(ceQueueDto.getTaskType());
assertThat(underTest.getComponentUuid()).isEqualTo(ceQueueDto.getComponentUuid());
assertThat(underTest.getMainComponentUuid()).isEqualTo(ceQueueDto.getMainComponentUuid());
assertThat(underTest.getIsLastKey()).isEqualTo(ceQueueDto.getTaskType() + ceQueueDto.getComponentUuid());
assertThat(underTest.getIsLast()).isFalse();
assertThat(underTest.getMainIsLastKey()).isEqualTo(ceQueueDto.getTaskType() + ceQueueDto.getMainComponentUuid());
assertThat(underTest.getMainIsLast()).isFalse();
assertThat(underTest.getSubmitterUuid()).isEqualTo(ceQueueDto.getSubmitterUuid());
assertThat(underTest.getWorkerUuid()).isEqualTo(ceQueueDto.getWorkerUuid());
assertThat(underTest.getSubmittedAt()).isEqualTo(ceQueueDto.getCreatedAt());
assertThat(underTest.getStartedAt()).isEqualTo(ceQueueDto.getStartedAt());
assertThat(underTest.getStatus()).isNull();
}

@Test
public void setComponentUuid_accepts_null_empty_and_string_40_chars_or_less() {
underTest.setComponentUuid(null);
underTest.setComponentUuid("");
underTest.setComponentUuid("bar");
underTest.setComponentUuid(STR_40_CHARS);
}

@Test
public void setComponentUuid_throws_IAE_if_value_is_41_chars() {
String str_41_chars = STR_40_CHARS + "a";

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Value is too long for column CE_ACTIVITY.COMPONENT_UUID: " + str_41_chars);

underTest.setComponentUuid(str_41_chars);
}

@Test
public void setMainComponentUuid_accepts_null_empty_and_string_40_chars_or_less() {
underTest.setMainComponentUuid(null);
underTest.setMainComponentUuid("");
underTest.setMainComponentUuid("bar");
underTest.setMainComponentUuid(STR_40_CHARS);
}

@Test
public void setMainComponentUuid_throws_IAE_if_value_is_41_chars() {
String str_41_chars = STR_40_CHARS + "a";

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Value is too long for column CE_ACTIVITY.MAIN_COMPONENT_UUID: " + str_41_chars);

underTest.setMainComponentUuid(str_41_chars);
}

@Test
@UseDataProvider("stringsWithChar0")
public void setStacktrace_filters_out_char_zero(String withChar0, String expected) {

+ 58
- 53
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueDaoTest.java View File

@@ -27,6 +27,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.junit.Rule;
@@ -54,8 +55,8 @@ public class CeQueueDaoTest {
private static final long INIT_TIME = 1_450_000_000_000L;
private static final String TASK_UUID_1 = "TASK_1";
private static final String TASK_UUID_2 = "TASK_2";
private static final String COMPONENT_UUID_1 = "PROJECT_1";
private static final String COMPONENT_UUID_2 = "PROJECT_2";
private static final String MAIN_COMPONENT_UUID_1 = "PROJECT_1";
private static final String MAIN_COMPONENT_UUID_2 = "PROJECT_2";
private static final String TASK_UUID_3 = "TASK_3";
private static final String SELECT_QUEUE_UUID_AND_STATUS_QUERY = "select uuid,status from ce_queue";
private static final String SUBMITTER_LOGIN = "submitter uuid";
@@ -79,7 +80,7 @@ public class CeQueueDaoTest {
long now = 1_334_333L;
CeQueueDto dto = new CeQueueDto()
.setTaskType(CeTaskTypes.REPORT)
.setComponentUuid(COMPONENT_UUID_1)
.setComponentUuid(MAIN_COMPONENT_UUID_1)
.setStatus(PENDING)
.setSubmitterUuid(SUBMITTER_LOGIN);

@@ -99,7 +100,7 @@ public class CeQueueDaoTest {
CeQueueDto saved = underTest.selectByUuid(db.getSession(), uuid).get();
assertThat(saved.getUuid()).isEqualTo(uuid);
assertThat(saved.getTaskType()).isEqualTo(CeTaskTypes.REPORT);
assertThat(saved.getComponentUuid()).isEqualTo(COMPONENT_UUID_1);
assertThat(saved.getComponentUuid()).isEqualTo(MAIN_COMPONENT_UUID_1);
assertThat(saved.getStatus()).isEqualTo(PENDING);
assertThat(saved.getSubmitterUuid()).isEqualTo(SUBMITTER_LOGIN);
assertThat(saved.getWorkerUuid()).isNull();
@@ -110,7 +111,7 @@ public class CeQueueDaoTest {
CeQueueDto saved = underTest.selectByUuid(db.getSession(), uuid4).get();
assertThat(saved.getUuid()).isEqualTo(uuid4);
assertThat(saved.getTaskType()).isEqualTo(CeTaskTypes.REPORT);
assertThat(saved.getComponentUuid()).isEqualTo(COMPONENT_UUID_1);
assertThat(saved.getComponentUuid()).isEqualTo(MAIN_COMPONENT_UUID_1);
assertThat(saved.getStatus()).isEqualTo(PENDING);
assertThat(saved.getSubmitterUuid()).isEqualTo(SUBMITTER_LOGIN);
assertThat(saved.getWorkerUuid()).isNull();
@@ -121,13 +122,14 @@ public class CeQueueDaoTest {

@Test
public void test_selectByUuid() {
insertPending(TASK_UUID_1, COMPONENT_UUID_1);
CeQueueDto ceQueueDto = insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);

assertThat(underTest.selectByUuid(db.getSession(), "TASK_UNKNOWN").isPresent()).isFalse();
CeQueueDto saved = underTest.selectByUuid(db.getSession(), TASK_UUID_1).get();
assertThat(saved.getUuid()).isEqualTo(TASK_UUID_1);
assertThat(saved.getTaskType()).isEqualTo(CeTaskTypes.REPORT);
assertThat(saved.getComponentUuid()).isEqualTo(COMPONENT_UUID_1);
assertThat(saved.getMainComponentUuid()).isEqualTo(MAIN_COMPONENT_UUID_1);
assertThat(saved.getComponentUuid()).isEqualTo(ceQueueDto.getComponentUuid());
assertThat(saved.getStatus()).isEqualTo(PENDING);
assertThat(saved.getSubmitterUuid()).isEqualTo("henri");
assertThat(saved.getWorkerUuid()).isNull();
@@ -137,20 +139,20 @@ public class CeQueueDaoTest {
}

@Test
public void test_selectByComponentUuid() {
insertPending(TASK_UUID_1, COMPONENT_UUID_1);
insertPending(TASK_UUID_2, COMPONENT_UUID_1);
public void test_selectByMainComponentUuid() {
insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
insertPending(TASK_UUID_2, MAIN_COMPONENT_UUID_1);
insertPending(TASK_UUID_3, "PROJECT_2");

assertThat(underTest.selectByComponentUuid(db.getSession(), "UNKNOWN")).isEmpty();
assertThat(underTest.selectByComponentUuid(db.getSession(), COMPONENT_UUID_1)).extracting("uuid").containsOnly(TASK_UUID_1, TASK_UUID_2);
assertThat(underTest.selectByComponentUuid(db.getSession(), "PROJECT_2")).extracting("uuid").containsOnly(TASK_UUID_3);
assertThat(underTest.selectByMainComponentUuid(db.getSession(), "UNKNOWN")).isEmpty();
assertThat(underTest.selectByMainComponentUuid(db.getSession(), MAIN_COMPONENT_UUID_1)).extracting("uuid").containsOnly(TASK_UUID_1, TASK_UUID_2);
assertThat(underTest.selectByMainComponentUuid(db.getSession(), "PROJECT_2")).extracting("uuid").containsOnly(TASK_UUID_3);
}

@Test
public void test_selectAllInAscOrder() {
insertPending(TASK_UUID_1, COMPONENT_UUID_1);
insertPending(TASK_UUID_2, COMPONENT_UUID_1);
insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
insertPending(TASK_UUID_2, MAIN_COMPONENT_UUID_1);
insertPending(TASK_UUID_3, "PROJECT_2");

assertThat(underTest.selectAllInAscOrder(db.getSession())).extracting("uuid").containsOnly(TASK_UUID_1, TASK_UUID_2, TASK_UUID_3);
@@ -186,8 +188,8 @@ public class CeQueueDaoTest {

@Test
public void test_delete() {
insertPending(TASK_UUID_1, COMPONENT_UUID_1);
insertPending(TASK_UUID_2, COMPONENT_UUID_1);
insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
insertPending(TASK_UUID_2, MAIN_COMPONENT_UUID_1);

int deletedCount = underTest.deleteByUuid(db.getSession(), "UNKNOWN");
assertThat(deletedCount).isEqualTo(0);
@@ -204,8 +206,8 @@ public class CeQueueDaoTest {

@Test
public void test_delete_with_expected_status() {
insertPending(TASK_UUID_1, COMPONENT_UUID_1);
insertInProgress(TASK_UUID_2, COMPONENT_UUID_1);
insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
insertInProgress(TASK_UUID_2, MAIN_COMPONENT_UUID_1);

int deletedCount = underTest.deleteByUuid(db.getSession(), "UNKNOWN", null);
assertThat(deletedCount).isEqualTo(0);
@@ -340,15 +342,15 @@ public class CeQueueDaoTest {
assertThat(underTest.peek(db.getSession(), WORKER_UUID_1).isPresent()).isFalse();

// not pending, but in progress
makeInProgress(WORKER_UUID_1, 2_232_222L, insertPending(TASK_UUID_1, COMPONENT_UUID_1));
makeInProgress(WORKER_UUID_1, 2_232_222L, insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1));
assertThat(underTest.peek(db.getSession(), WORKER_UUID_1).isPresent()).isFalse();
}

@Test
public void peek_oldest_pending() {
insertPending(TASK_UUID_1, COMPONENT_UUID_1);
insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
system2.setNow(INIT_TIME + 3_000_000);
insertPending(TASK_UUID_2, COMPONENT_UUID_2);
insertPending(TASK_UUID_2, MAIN_COMPONENT_UUID_2);

assertThat(db.countRowsOfTable("ce_queue")).isEqualTo(2);
verifyCeQueueStatuses(TASK_UUID_1, PENDING, TASK_UUID_2, PENDING);
@@ -374,15 +376,16 @@ public class CeQueueDaoTest {
}

@Test
public void do_not_peek_multiple_tasks_on_same_project_at_the_same_time() {
public void do_not_peek_multiple_tasks_on_same_main_component_at_the_same_time() {
// two pending tasks on the same project
insertPending(TASK_UUID_1, COMPONENT_UUID_1);
insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
system2.setNow(INIT_TIME + 3_000_000);
insertPending(TASK_UUID_2, COMPONENT_UUID_1);
insertPending(TASK_UUID_2, MAIN_COMPONENT_UUID_1);

Optional<CeQueueDto> peek = underTest.peek(db.getSession(), WORKER_UUID_1);
assertThat(peek).isPresent();
assertThat(peek.get().getUuid()).isEqualTo(TASK_UUID_1);
assertThat(peek.get().getMainComponentUuid()).isEqualTo(MAIN_COMPONENT_UUID_1);
assertThat(peek.get().getWorkerUuid()).isEqualTo(WORKER_UUID_1);
verifyCeQueueStatuses(TASK_UUID_1, IN_PROGRESS, TASK_UUID_2, PENDING);

@@ -401,42 +404,42 @@ public class CeQueueDaoTest {
public void select_by_query() {
// task status not in query
insertPending(newCeQueueDto(TASK_UUID_1)
.setComponentUuid(COMPONENT_UUID_1)
.setMainComponentUuid(MAIN_COMPONENT_UUID_1)
.setStatus(IN_PROGRESS)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(100_000L));

// too early
insertPending(newCeQueueDto(TASK_UUID_3)
.setComponentUuid(COMPONENT_UUID_1)
.setMainComponentUuid(MAIN_COMPONENT_UUID_1)
.setStatus(PENDING)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(90_000L));

// task type not in query
insertPending(newCeQueueDto("TASK_4")
.setComponentUuid("PROJECT_2")
.setMainComponentUuid("PROJECT_2")
.setStatus(PENDING)
.setTaskType("ANOTHER_TYPE")
.setCreatedAt(100_000L));

// correct
insertPending(newCeQueueDto(TASK_UUID_2)
.setComponentUuid(COMPONENT_UUID_1)
.setMainComponentUuid(MAIN_COMPONENT_UUID_1)
.setStatus(PENDING)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(100_000L));

// correct submitted later
insertPending(newCeQueueDto("TASK_5")
.setComponentUuid(COMPONENT_UUID_1)
.setMainComponentUuid(MAIN_COMPONENT_UUID_1)
.setStatus(PENDING)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(120_000L));

// select by component uuid, status, task type and minimum submitted at
CeTaskQuery query = new CeTaskQuery()
.setComponentUuids(newArrayList(COMPONENT_UUID_1, "PROJECT_2"))
.setMainComponentUuids(newArrayList(MAIN_COMPONENT_UUID_1, "PROJECT_2"))
.setStatuses(singletonList(PENDING.name()))
.setType(CeTaskTypes.REPORT)
.setMinSubmittedAt(100_000L);
@@ -451,7 +454,7 @@ public class CeQueueDaoTest {
@Test
public void select_by_query_returns_empty_list_when_only_current() {
insertPending(newCeQueueDto(TASK_UUID_1)
.setComponentUuid(COMPONENT_UUID_1)
.setComponentUuid(MAIN_COMPONENT_UUID_1)
.setStatus(IN_PROGRESS)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(100_000L));
@@ -468,7 +471,7 @@ public class CeQueueDaoTest {
@Test
public void select_by_query_returns_empty_list_when_max_submitted_at() {
insertPending(newCeQueueDto(TASK_UUID_1)
.setComponentUuid(COMPONENT_UUID_1)
.setComponentUuid(MAIN_COMPONENT_UUID_1)
.setStatus(IN_PROGRESS)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(100_000L));
@@ -483,14 +486,14 @@ public class CeQueueDaoTest {
}

@Test
public void select_by_query_returns_empty_list_when_empty_list_of_component_uuid() {
public void select_by_query_returns_empty_list_when_empty_list_of_main_component_uuid() {
insertPending(newCeQueueDto(TASK_UUID_1)
.setComponentUuid(COMPONENT_UUID_1)
.setComponentUuid(MAIN_COMPONENT_UUID_1)
.setStatus(IN_PROGRESS)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(100_000L));

CeTaskQuery query = new CeTaskQuery().setComponentUuids(Collections.emptyList());
CeTaskQuery query = new CeTaskQuery().setMainComponentUuids(Collections.emptyList());

List<CeQueueDto> result = underTest.selectByQueryInDescOrder(db.getSession(), query, 1_000);
int total = underTest.countByQuery(db.getSession(), query);
@@ -500,57 +503,57 @@ public class CeQueueDaoTest {
}

@Test
public void count_by_status_and_component_uuid() {
public void count_by_status_and_main_component_uuid() {
// task retrieved in the queue
insertPending(newCeQueueDto(TASK_UUID_1)
.setComponentUuid(COMPONENT_UUID_1)
.setMainComponentUuid(MAIN_COMPONENT_UUID_1)
.setStatus(IN_PROGRESS)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(100_000L));
// on component uuid 2, not returned
insertPending(newCeQueueDto(TASK_UUID_2)
.setComponentUuid(COMPONENT_UUID_2)
.setMainComponentUuid(MAIN_COMPONENT_UUID_2)
.setStatus(IN_PROGRESS)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(100_000L));
// pending status, not returned
insertPending(newCeQueueDto(TASK_UUID_3)
.setComponentUuid(COMPONENT_UUID_1)
.setMainComponentUuid(MAIN_COMPONENT_UUID_1)
.setStatus(PENDING)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(100_000L));

assertThat(underTest.countByStatusAndComponentUuid(db.getSession(), IN_PROGRESS, COMPONENT_UUID_1)).isEqualTo(1);
assertThat(underTest.countByStatusAndMainComponentUuid(db.getSession(), IN_PROGRESS, MAIN_COMPONENT_UUID_1)).isEqualTo(1);
assertThat(underTest.countByStatus(db.getSession(), IN_PROGRESS)).isEqualTo(2);
}

@Test
public void count_by_status_and_component_uuids() {
public void count_by_status_and_main_component_uuids() {
// task retrieved in the queue
insertPending(newCeQueueDto(TASK_UUID_1)
.setComponentUuid(COMPONENT_UUID_1)
.setMainComponentUuid(MAIN_COMPONENT_UUID_1)
.setStatus(IN_PROGRESS)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(100_000L));
// on component uuid 2, not returned
insertPending(newCeQueueDto(TASK_UUID_2)
.setComponentUuid(COMPONENT_UUID_2)
.setMainComponentUuid(MAIN_COMPONENT_UUID_2)
.setStatus(IN_PROGRESS)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(100_000L));
// pending status, not returned
insertPending(newCeQueueDto(TASK_UUID_3)
.setComponentUuid(COMPONENT_UUID_1)
.setMainComponentUuid(MAIN_COMPONENT_UUID_1)
.setStatus(PENDING)
.setTaskType(CeTaskTypes.REPORT)
.setCreatedAt(100_000L));

assertThat(underTest.countByStatusAndComponentUuids(db.getSession(), IN_PROGRESS, ImmutableSet.of())).isEmpty();
assertThat(underTest.countByStatusAndComponentUuids(db.getSession(), IN_PROGRESS, ImmutableSet.of("non existing component uuid"))).isEmpty();
assertThat(underTest.countByStatusAndComponentUuids(db.getSession(), IN_PROGRESS, ImmutableSet.of(COMPONENT_UUID_1, COMPONENT_UUID_2)))
.containsOnly(entry(COMPONENT_UUID_1, 1), entry(COMPONENT_UUID_2, 1));
assertThat(underTest.countByStatusAndComponentUuids(db.getSession(), PENDING, ImmutableSet.of(COMPONENT_UUID_1, COMPONENT_UUID_2)))
.containsOnly(entry(COMPONENT_UUID_1, 1));
assertThat(underTest.countByStatusAndMainComponentUuids(db.getSession(), IN_PROGRESS, ImmutableSet.of())).isEmpty();
assertThat(underTest.countByStatusAndMainComponentUuids(db.getSession(), IN_PROGRESS, ImmutableSet.of("non existing component uuid"))).isEmpty();
assertThat(underTest.countByStatusAndMainComponentUuids(db.getSession(), IN_PROGRESS, ImmutableSet.of(MAIN_COMPONENT_UUID_1, MAIN_COMPONENT_UUID_2)))
.containsOnly(entry(MAIN_COMPONENT_UUID_1, 1), entry(MAIN_COMPONENT_UUID_2, 1));
assertThat(underTest.countByStatusAndMainComponentUuids(db.getSession(), PENDING, ImmutableSet.of(MAIN_COMPONENT_UUID_1, MAIN_COMPONENT_UUID_2)))
.containsOnly(entry(MAIN_COMPONENT_UUID_1, 1));
assertThat(underTest.countByStatus(db.getSession(), IN_PROGRESS)).isEqualTo(2);
}

@@ -570,11 +573,13 @@ public class CeQueueDaoTest {
return dto;
}

private CeQueueDto insertPending(String uuid, String componentUuid) {
private int pendingComponentUuidGenerator = new Random().nextInt(200);
private CeQueueDto insertPending(String uuid, String mainComponentUuid) {
CeQueueDto dto = new CeQueueDto();
dto.setUuid(uuid);
dto.setTaskType(CeTaskTypes.REPORT);
dto.setComponentUuid(componentUuid);
dto.setMainComponentUuid(mainComponentUuid);
dto.setComponentUuid("uuid_" + pendingComponentUuidGenerator++);
dto.setStatus(PENDING);
dto.setSubmitterUuid("henri");
underTest.insert(db.getSession(), dto);

+ 19
- 1
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueDtoTest.java View File

@@ -47,11 +47,29 @@ public class CeQueueDtoTest {
String str_41_chars = STR_40_CHARS + "a";

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Value of component UUID is too long: " + str_41_chars);
expectedException.expectMessage("Value is too long for column CE_QUEUE.COMPONENT_UUID: " + str_41_chars);

underTest.setComponentUuid(str_41_chars);
}

@Test
public void setMainComponentUuid_accepts_null_empty_and_string_40_chars_or_less() {
underTest.setMainComponentUuid(null);
underTest.setMainComponentUuid("");
underTest.setMainComponentUuid("bar");
underTest.setMainComponentUuid(STR_40_CHARS);
}

@Test
public void setMainComponentUuid_throws_IAE_if_value_is_41_chars() {
String str_41_chars = STR_40_CHARS + "a";

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Value is too long for column CE_QUEUE.MAIN_COMPONENT_UUID: " + str_41_chars);

underTest.setMainComponentUuid(str_41_chars);
}

@Test
public void setTaskType_throws_NPE_if_argument_is_null() {
expectedException.expect(NullPointerException.class);

+ 1
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueTesting.java View File

@@ -38,6 +38,7 @@ public class CeQueueTesting {
return new CeQueueDto()
.setUuid(uuid)
.setComponentUuid(randomAlphanumeric(40))
.setMainComponentUuid(randomAlphanumeric(39))
.setStatus(CeQueueDto.Status.PENDING)
.setTaskType(CeTaskTypes.REPORT)
.setSubmitterUuid(randomAlphanumeric(255))

+ 14
- 14
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskQueryTest.java View File

@@ -33,22 +33,22 @@ public class CeTaskQueryTest {

@Test
public void no_filter_on_component_uuids_by_default() {
assertThat(underTest.getComponentUuids()).isNull();
assertThat(underTest.isShortCircuitedByComponentUuids()).isFalse();
assertThat(underTest.getMainComponentUuids()).isNull();
assertThat(underTest.isShortCircuitedByMainComponentUuids()).isFalse();
}

@Test
public void filter_on_component_uuid() {
underTest.setComponentUuid("UUID1");
assertThat(underTest.getComponentUuids()).containsOnly("UUID1");
assertThat(underTest.isShortCircuitedByComponentUuids()).isFalse();
underTest.setMainComponentUuid("UUID1");
assertThat(underTest.getMainComponentUuids()).containsOnly("UUID1");
assertThat(underTest.isShortCircuitedByMainComponentUuids()).isFalse();
}

@Test
public void filter_on_multiple_component_uuids() {
underTest.setComponentUuids(asList("UUID1", "UUID2"));
assertThat(underTest.getComponentUuids()).containsOnly("UUID1", "UUID2");
assertThat(underTest.isShortCircuitedByComponentUuids()).isFalse();
underTest.setMainComponentUuids(asList("UUID1", "UUID2"));
assertThat(underTest.getMainComponentUuids()).containsOnly("UUID1", "UUID2");
assertThat(underTest.isShortCircuitedByMainComponentUuids()).isFalse();
}

/**
@@ -57,9 +57,9 @@ public class CeTaskQueryTest {
*/
@Test
public void short_circuited_if_empty_component_uuid_filter() {
underTest.setComponentUuids(Collections.emptyList());
assertThat(underTest.getComponentUuids()).isEmpty();
assertThat(underTest.isShortCircuitedByComponentUuids()).isTrue();
underTest.setMainComponentUuids(Collections.emptyList());
assertThat(underTest.getMainComponentUuids()).isEmpty();
assertThat(underTest.isShortCircuitedByMainComponentUuids()).isTrue();
}

/**
@@ -72,8 +72,8 @@ public class CeTaskQueryTest {
for (int i = 0; i < CeTaskQuery.MAX_COMPONENT_UUIDS + 2; i++) {
uuids.add(String.valueOf(i));
}
underTest.setComponentUuids(uuids);
assertThat(underTest.getComponentUuids()).hasSize(CeTaskQuery.MAX_COMPONENT_UUIDS + 2);
assertThat(underTest.isShortCircuitedByComponentUuids()).isTrue();
underTest.setMainComponentUuids(uuids);
assertThat(underTest.getMainComponentUuids()).hasSize(CeTaskQuery.MAX_COMPONENT_UUIDS + 2);
assertThat(underTest.isShortCircuitedByMainComponentUuids()).isTrue();
}
}

+ 3
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java View File

@@ -63,6 +63,7 @@ import org.sonar.db.source.FileSourceDto;
import org.sonar.db.webhook.WebhookDeliveryLiteDto;
import org.sonar.db.webhook.WebhookDto;

import static com.google.common.base.MoreObjects.firstNonNull;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
@@ -905,6 +906,7 @@ public class PurgeDaoTest {
queueDto.setUuid(Uuids.create());
queueDto.setTaskType(REPORT);
queueDto.setComponentUuid(component.uuid());
queueDto.setMainComponentUuid(firstNonNull(component.getMainBranchProjectUuid(), component.uuid()));
queueDto.setSubmitterUuid("submitter uuid");
queueDto.setCreatedAt(1_300_000_000_000L);
queueDto.setStatus(status);
@@ -929,6 +931,7 @@ public class PurgeDaoTest {
.setUuid(UuidFactoryFast.getInstance().create())
.setTaskType("foo")
.setComponentUuid(project.uuid())
.setMainComponentUuid(firstNonNull(project.getMainBranchProjectUuid(), project.uuid()))
.setStatus(Status.PENDING)
.setCreatedAt(1_2323_222L)
.setUpdatedAt(1_2323_222L);

+ 31
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivity.java View File

@@ -0,0 +1,31 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.SupportsBlueGreen;

@SupportsBlueGreen
public class AddTmpColumnsToCeActivity extends AddTmpColumnsToCeTable {

public AddTmpColumnsToCeActivity(Database db) {
super(db, "ce_activity");
}
}

+ 31
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueue.java View File

@@ -0,0 +1,31 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.SupportsBlueGreen;

@SupportsBlueGreen
public class AddTmpColumnsToCeQueue extends AddTmpColumnsToCeTable {

public AddTmpColumnsToCeQueue(Database db) {
super(db, "ce_queue");
}
}

+ 70
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeTable.java View File

@@ -0,0 +1,70 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

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.AddColumnsBuilder;
import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder;
import org.sonar.server.platform.db.migration.step.DdlChange;

import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;

abstract class AddTmpColumnsToCeTable extends DdlChange {
private static final VarcharColumnDef COLUMN_TMP_COMPONENT_UUID = newVarcharColumnDefBuilder()
.setColumnName("tmp_component_uuid")
.setLimit(VarcharColumnDef.UUID_SIZE)
.setIsNullable(true)
.build();
private static final VarcharColumnDef COLUMN_TMP_MAIN_COMPONENT_UUID = newVarcharColumnDefBuilder()
.setColumnName("tmp_main_component_uuid")
.setLimit(VarcharColumnDef.UUID_SIZE)
.setIsNullable(true)
.build();
private final String tableName;

public AddTmpColumnsToCeTable(Database db, String tableName) {
super(db);
this.tableName = tableName;
}

@Override
public void execute(Context context) throws SQLException {
context.execute(new AddColumnsBuilder(getDialect(), tableName)
.addColumn(COLUMN_TMP_MAIN_COMPONENT_UUID)
.addColumn(COLUMN_TMP_COMPONENT_UUID)
.build());

// create indexes
context.execute(new CreateIndexBuilder(getDialect())
.setTable(tableName)
.setName(tableName + "_tmp_cpnt_uuid")
.addColumn(COLUMN_TMP_COMPONENT_UUID)
.setUnique(false)
.build());
context.execute(new CreateIndexBuilder(getDialect())
.setTable(tableName)
.setName(tableName + "_tmp_main_cpnt_uuid")
.addColumn(COLUMN_TMP_MAIN_COMPONENT_UUID)
.setUnique(false)
.build());
}
}

+ 105
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivity.java View File

@@ -0,0 +1,105 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import java.sql.SQLException;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.SupportsBlueGreen;
import org.sonar.server.platform.db.migration.def.BooleanColumnDef;
import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder;
import org.sonar.server.platform.db.migration.step.DdlChange;

import static org.sonar.server.platform.db.migration.def.BooleanColumnDef.newBooleanColumnDefBuilder;
import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE;
import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;

@SupportsBlueGreen
public class AddTmpLastKeyColumnsToCeActivity extends DdlChange {
private static final String TABLE_NAME = "ce_activity";
private static final int TASK_TYPE_COLUMN_SIZE = 15;
private static final BooleanColumnDef COLUMN_TMP_IS_LAST = newBooleanColumnDefBuilder()
.setColumnName("tmp_is_last")
.setIsNullable(true)
.build();
private static final VarcharColumnDef COLUMN_TMP_IS_LAST_KEY = newVarcharColumnDefBuilder()
.setColumnName("tmp_is_last_key")
.setLimit(UUID_SIZE + TASK_TYPE_COLUMN_SIZE)
.setIsNullable(true)
.build();
private static final BooleanColumnDef COLUMN_TMP_MAIN_IS_LAST = newBooleanColumnDefBuilder()
.setColumnName("tmp_main_is_last")
.setIsNullable(true)
.build();
private static final VarcharColumnDef COLUMN_TMP_MAIN_IS_LAST_KEY = newVarcharColumnDefBuilder()
.setColumnName("tmp_main_is_last_key")
.setLimit(UUID_SIZE + TASK_TYPE_COLUMN_SIZE)
.setIsNullable(true)
.build();
private static final VarcharColumnDef COLUMN_STATUS = newVarcharColumnDefBuilder()
.setColumnName("status")
.setLimit(15)
.setIsNullable(false)
.build();

public AddTmpLastKeyColumnsToCeActivity(Database db) {
super(db);
}

@Override
public void execute(Context context) throws SQLException {
context.execute(new AddColumnsBuilder(getDialect(), TABLE_NAME)
.addColumn(COLUMN_TMP_IS_LAST)
.addColumn(COLUMN_TMP_IS_LAST_KEY)
.addColumn(COLUMN_TMP_MAIN_IS_LAST)
.addColumn(COLUMN_TMP_MAIN_IS_LAST_KEY)
.build());

// create indexes
context.execute(new CreateIndexBuilder(getDialect())
.setTable(TABLE_NAME)
.setName(TABLE_NAME + "_t_islast_key")
.addColumn(COLUMN_TMP_IS_LAST_KEY)
.setUnique(false)
.build());
context.execute(new CreateIndexBuilder(getDialect())
.setTable(TABLE_NAME)
.setName(TABLE_NAME + "_t_islast")
.addColumn(COLUMN_TMP_IS_LAST)
.addColumn(COLUMN_STATUS)
.setUnique(false)
.build());
context.execute(new CreateIndexBuilder(getDialect())
.setTable(TABLE_NAME)
.setName(TABLE_NAME + "_t_main_islast_key")
.addColumn(COLUMN_TMP_MAIN_IS_LAST_KEY)
.setUnique(false)
.build());
context.execute(new CreateIndexBuilder(getDialect())
.setTable(TABLE_NAME)
.setName(TABLE_NAME + "_t_main_islast")
.addColumn(COLUMN_TMP_MAIN_IS_LAST)
.addColumn(COLUMN_STATUS)
.setUnique(false)
.build());

}
}

+ 6
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/DbVersion74.java View File

@@ -32,6 +32,12 @@ public class DbVersion74 implements DbVersion {
.add(2302, "Populate IS_AD_HOC in RULES", PopulateIsAdHocOnRules.class)
.add(2303, "Set IS_EXTERNAL and IS_AD_HOC not nullable in RULES", SetIsExternalAndIsAdHocNotNullableInRules.class)
.add(2304, "Add ad hoc related columns in RULES_METADATA", AddAdHocColumnsInInRulesMetadata.class)
.add(2305, "Add CE_QUEUE.MAIN_COMPONENT_UUID 1/5", AddTmpColumnsToCeQueue.class)
.add(2306, "Add CE_ACTIVITY.MAIN_COMPONENT_UUID 1/5", AddTmpColumnsToCeActivity.class)
.add(2307, "Populate CE_QUEUE.MAIN_COMPONENT_UUID 2/5", PopulateTmpColumnsToCeQueue.class)
.add(2308, "Populate CE_ACTIVITY.MAIN_COMPONENT_UUID 2/5", PopulateTmpColumnsToCeActivity.class)
.add(2309, "Add CE_ACTIVITY.MAIN_LAST_KEY 1/6", AddTmpLastKeyColumnsToCeActivity.class)
.add(2310, "Populate CE_ACTIVITY.MAIN_LAST_KEY 2/6", PopulateTmpLastKeyColumnsToCeActivity.class)
;
}
}

+ 104
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivity.java View File

@@ -0,0 +1,104 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import java.sql.SQLException;
import org.sonar.api.config.Configuration;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.SupportsBlueGreen;
import org.sonar.server.platform.db.migration.step.DataChange;
import org.sonar.server.platform.db.migration.step.MassUpdate;
import org.sonar.server.platform.db.migration.step.Select;
import org.sonar.server.platform.db.migration.step.SqlStatement;

@SupportsBlueGreen
public class PopulateTmpColumnsToCeActivity extends DataChange {
private final Configuration configuration;

public PopulateTmpColumnsToCeActivity(Database db, Configuration configuration) {
super(db);
this.configuration = configuration;
}

@Override
protected void execute(Context context) throws SQLException {
if (configuration.getBoolean("sonar.sonarcloud.enabled").orElse(false)) {
// data migration will be done in background so that interruption of service
// is reduced during upgrade
return;
}

// activity of long and short branches
populateCeActivityTmpColumns(context, "Archived tasks of branches",
"select" +
" cea.uuid, p.uuid, cea.component_uuid" +
" from ce_activity cea" +
" inner join projects mp on mp.uuid = cea.component_uuid" +
" inner join ce_task_characteristics ctc1 on ctc1.task_uuid = cea.uuid and ctc1.kee = 'branchType'" +
" inner join ce_task_characteristics ctc2 on ctc2.task_uuid = cea.uuid and ctc2.kee = 'branch'" +
" inner join projects p on p.kee = concat(mp.kee, ':BRANCH:', ctc2.text_value)" +
" where" +
" cea.component_uuid is not null" +
" and (cea.tmp_component_uuid is null or cea.tmp_main_component_uuid is null)");

// activity of PRs
populateCeActivityTmpColumns(context, "Archived tasks of PRs",
"select" +
" cea.uuid, p.uuid, cea.component_uuid" +
" from ce_activity cea" +
" inner join projects mp on mp.uuid = cea.component_uuid " +
" inner join ce_task_characteristics ctc1 on ctc1.task_uuid = cea.uuid and ctc1.kee = 'pullRequest'" +
" inner join projects p on p.kee = concat(mp.kee, ':PULL_REQUEST:', ctc1.text_value)" +
" where" +
" cea.component_uuid is not null" +
" and (cea.tmp_component_uuid is null or cea.tmp_main_component_uuid is null)");

// all activities which tmp columns are not populated yet (will include main and deprecated branches)
// both tmp columns will be set to CE_ACTIVITY.COMPONENT_UUID
// do not join on PROJECTS to also catch orphans
populateCeActivityTmpColumns(context, "Archived tasks of main and deprecated branches",
"select" +
" cea.uuid, cea.component_uuid, cea.component_uuid" +
" from ce_activity cea" +
" where" +
" cea.component_uuid is not null" +
" and (cea.tmp_component_uuid is null or cea.tmp_main_component_uuid is null)");
}

private static void populateCeActivityTmpColumns(Context context, String rowPluralName, String sql) throws SQLException {
MassUpdate massUpdate = context.prepareMassUpdate();
massUpdate.select(sql);
massUpdate.update("update ce_activity set tmp_component_uuid=?, tmp_main_component_uuid=? where uuid=?");
massUpdate.rowPluralName(rowPluralName);
massUpdate.execute(PopulateTmpColumnsToCeActivity::handleUpdate);
}

private static boolean handleUpdate(Select.Row row, SqlStatement update) throws SQLException {
String uuid = row.getString(1);
String componentUuuid = row.getString(2);
String mainComponentUuuid = row.getString(3);

update.setString(1, componentUuuid);
update.setString(2, mainComponentUuuid);
update.setString(3, uuid);

return true;
}
}

+ 135
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueue.java View File

@@ -0,0 +1,135 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import java.sql.SQLException;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.SupportsBlueGreen;
import org.sonar.server.platform.db.migration.step.DataChange;
import org.sonar.server.platform.db.migration.step.MassUpdate;
import org.sonar.server.platform.db.migration.step.Select;
import org.sonar.server.platform.db.migration.step.SqlStatement;

@SupportsBlueGreen
public class PopulateTmpColumnsToCeQueue extends DataChange {
public PopulateTmpColumnsToCeQueue(Database db) {
super(db);
}

@Override
protected void execute(Context context) throws SQLException {
// queued task of long and short branches which have already been analyzed at least once
populateCeQueueTmpColumns(context, "queued tasks of branches",
"select" +
" cq.uuid, p.uuid, cq.component_uuid" +
" from ce_queue cq" +
" inner join projects mp on mp.uuid = cq.component_uuid" +
" inner join ce_task_characteristics ctc1 on ctc1.task_uuid = cq.uuid and ctc1.kee = 'branchType'" +
" inner join ce_task_characteristics ctc2 on ctc2.task_uuid = cq.uuid and ctc2.kee = 'branch'" +
" inner join projects p on p.kee = concat(mp.kee, ':BRANCH:', ctc2.text_value)" +
" where" +
" cq.component_uuid is not null" +
" and (cq.tmp_component_uuid is null or cq.tmp_main_component_uuid is null)");

// queued task of pull request which have already been analyzed at least once
populateCeQueueTmpColumns(context, "queued tasks of PRs",
" select" +
" cq.uuid, p.uuid, cq.component_uuid" +
" from ce_queue cq" +
" inner join projects mp on mp.uuid = cq.component_uuid " +
" inner join ce_task_characteristics ctc1 on ctc1.task_uuid = cq.uuid and ctc1.kee = 'pullRequest'" +
" inner join projects p on p.kee = concat(mp.kee, ':PULL_REQUEST:', ctc1.text_value)" +
" where" +
" cq.component_uuid is not null" +
" and (cq.tmp_component_uuid is null or cq.tmp_main_component_uuid is null)");

// queued task of long and short branches which have never been analyzed must be deleted
deleteFromCeQueue(context, "queued tasks of never analyzed branches",
"select" +
" cq.uuid" +
" from ce_queue cq" +
" inner join projects mp on mp.uuid = cq.component_uuid" +
" inner join ce_task_characteristics ctc1 on ctc1.task_uuid = cq.uuid and ctc1.kee = 'branchType'" +
" inner join ce_task_characteristics ctc2 on ctc2.task_uuid = cq.uuid and ctc2.kee = 'branch'" +
" where" +
" cq.component_uuid is not null" +
" and (cq.tmp_component_uuid is null or cq.tmp_main_component_uuid is null)" +
" and not exists (select 1 from projects p where p.kee = concat(mp.kee, ':BRANCH:', ctc2.text_value))");

// queued of pull request which have never been analyzed must be deleted
deleteFromCeQueue(context, "queued tasks of never analyzed PRs",
"select" +
" cq.uuid" +
" from ce_queue cq" +
" inner join projects mp on mp.uuid = cq.component_uuid " +
" inner join ce_task_characteristics ctc1 on ctc1.task_uuid = cq.uuid and ctc1.kee = 'pullRequest'" +
" where" +
" cq.component_uuid is not null" +
" and (cq.tmp_component_uuid is null or cq.tmp_main_component_uuid is null)" +
" and not exists (select 1 from projects p where p.kee = concat(mp.kee, ':PULL_REQUEST:', ctc1.text_value))");

// all queue which tmp columns are not populated yet (will include main and deprecated branches)
// both tmp columns will be set to CE_QUEUE.COMPONENT_UUID
// do not join on PROJECTS to also catch orphans (there are many for branch and PRs due to SONAR-10642)
populateCeQueueTmpColumns(context, "queued tasks of main and deprecated branches",
"select" +
" cq.uuid, cq.component_uuid, cq.component_uuid" +
" from ce_queue cq" +
" where" +
" cq.component_uuid is not null" +
" and (cq.tmp_component_uuid is null or cq.tmp_main_component_uuid is null)");
}

private static void populateCeQueueTmpColumns(Context context, String pluralName, String selectSQL) throws SQLException {
MassUpdate massUpdate = context.prepareMassUpdate();
massUpdate.select(selectSQL);
massUpdate.update("update ce_queue set tmp_component_uuid=?, tmp_main_component_uuid=? where uuid=?");
massUpdate.rowPluralName(pluralName);
massUpdate.execute(PopulateTmpColumnsToCeQueue::handleUpdate);
}

private static boolean handleUpdate(Select.Row row, SqlStatement update) throws SQLException {
String uuid = row.getString(1);
String componentUuuid = row.getString(2);
String mainComponentUuuid = row.getString(3);

update.setString(1, componentUuuid);
update.setString(2, mainComponentUuuid);
update.setString(3, uuid);

return true;
}

private static void deleteFromCeQueue(Context context, String pluralName, String sql) throws SQLException {
MassUpdate massUpdate = context.prepareMassUpdate();
massUpdate.select(sql);
massUpdate.update("delete from ce_queue where uuid = ?");
massUpdate.rowPluralName(pluralName);
massUpdate.execute(PopulateTmpColumnsToCeQueue::handleDelete);
}

private static boolean handleDelete(Select.Row row, SqlStatement update) throws SQLException {
String uuid = row.getString(1);

update.setString(1, uuid);

return true;
}
}

+ 83
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivity.java View File

@@ -0,0 +1,83 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import java.sql.SQLException;
import org.sonar.api.config.Configuration;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.SupportsBlueGreen;
import org.sonar.server.platform.db.migration.step.DataChange;
import org.sonar.server.platform.db.migration.step.MassUpdate;
import org.sonar.server.platform.db.migration.step.Select;
import org.sonar.server.platform.db.migration.step.SqlStatement;

@SupportsBlueGreen
public class PopulateTmpLastKeyColumnsToCeActivity extends DataChange {
private final Configuration configuration;

public PopulateTmpLastKeyColumnsToCeActivity(Database db, Configuration configuration) {
super(db);
this.configuration = configuration;
}

@Override
protected void execute(Context context) throws SQLException {
if (configuration.getBoolean("sonar.sonarcloud.enabled").orElse(false)) {
// data migration will be done in background so that interruption of service
// is reduced during upgrade
return;
}

MassUpdate massUpdate = context.prepareMassUpdate();
massUpdate.select("select" +
" cea.uuid, cea.task_type, cea.component_uuid, cea.tmp_component_uuid, cea.tmp_main_component_uuid" +
" from ce_activity cea" +
" where" +
" cea.tmp_is_last is null" +
" or cea.tmp_is_last_key is null" +
" or cea.tmp_main_is_last is null" +
" or cea.tmp_main_is_last_key is null");
massUpdate.update("update ce_activity" +
" set" +
" tmp_is_last=?" +
" ,tmp_is_last_key=?" +
" ,tmp_main_is_last=?" +
" ,tmp_main_is_last_key=?" +
" where uuid=?");
massUpdate.rowPluralName("rows of ce_activity");
massUpdate.execute(PopulateTmpLastKeyColumnsToCeActivity::handleUpdate);
}

private static boolean handleUpdate(Select.Row row, SqlStatement update) throws SQLException {
String uuid = row.getString(1);
String taskType = row.getString(2);
String oldComponentUuid = row.getString(3);
String componentUuuid = row.getString(4);
String mainComponentUuuid = row.getString(5);

update.setBoolean(1, false);
update.setString(2, oldComponentUuid == null ? taskType : (taskType + componentUuuid));
update.setBoolean(3, false);
update.setString(4, oldComponentUuid == null ? taskType : (taskType + mainComponentUuuid));
update.setString(5, uuid);

return true;
}
}

+ 56
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivityTest.java View File

@@ -0,0 +1,56 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import java.sql.SQLException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.db.CoreDbTester;

import static java.sql.Types.VARCHAR;

public class AddTmpColumnsToCeActivityTest {
@Rule
public final CoreDbTester db = CoreDbTester.createForSchema(AddTmpColumnsToCeActivityTest.class, "ce_activity.sql");
@Rule
public ExpectedException expectedException = ExpectedException.none();

private AddTmpColumnsToCeActivity underTest = new AddTmpColumnsToCeActivity(db.database());

@Test
public void columns_and_indexes_are_added_to_table() throws SQLException {
underTest.execute();

db.assertColumnDefinition("ce_activity", "tmp_component_uuid", VARCHAR, 40, true);
db.assertColumnDefinition("ce_activity", "tmp_main_component_uuid", VARCHAR, 40, true);
db.assertIndex("ce_activity", "ce_activity_tmp_cpnt_uuid", "tmp_component_uuid");
db.assertIndex("ce_activity", "ce_activity_tmp_main_cpnt_uuid", "tmp_main_component_uuid");
}

@Test
public void migration_is_not_reentrant() throws SQLException {
underTest.execute();

expectedException.expect(IllegalStateException.class);

underTest.execute();
}
}

+ 56
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueueTest.java View File

@@ -0,0 +1,56 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import java.sql.SQLException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.db.CoreDbTester;

import static java.sql.Types.VARCHAR;

public class AddTmpColumnsToCeQueueTest {
@Rule
public final CoreDbTester db = CoreDbTester.createForSchema(AddTmpColumnsToCeQueueTest.class, "ce_queue.sql");
@Rule
public ExpectedException expectedException = ExpectedException.none();

private AddTmpColumnsToCeQueue underTest = new AddTmpColumnsToCeQueue(db.database());

@Test
public void columns_and_indexes_are_added_to_table() throws SQLException {
underTest.execute();

db.assertColumnDefinition("ce_queue", "tmp_component_uuid", VARCHAR, 40, true);
db.assertColumnDefinition("ce_queue", "tmp_main_component_uuid", VARCHAR, 40, true);
db.assertIndex("ce_queue", "ce_queue_tmp_cpnt_uuid", "tmp_component_uuid");
db.assertIndex("ce_queue", "ce_queue_tmp_main_cpnt_uuid", "tmp_main_component_uuid");
}

@Test
public void migration_is_not_reentrant() throws SQLException {
underTest.execute();

expectedException.expect(IllegalStateException.class);

underTest.execute();
}
}

+ 62
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivityTest.java View File

@@ -0,0 +1,62 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import java.sql.SQLException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.db.CoreDbTester;

import static java.sql.Types.BOOLEAN;
import static java.sql.Types.VARCHAR;

public class AddTmpLastKeyColumnsToCeActivityTest {
@Rule
public final CoreDbTester db = CoreDbTester.createForSchema(AddTmpLastKeyColumnsToCeActivityTest.class, "ce_activity.sql");
@Rule
public ExpectedException expectedException = ExpectedException.none();

private AddTmpLastKeyColumnsToCeActivity underTest = new AddTmpLastKeyColumnsToCeActivity(db.database());

@Test
public void columns_and_indexes_are_added_to_table() throws SQLException {
underTest.execute();

db.assertColumnDefinition("ce_activity", "tmp_is_last", BOOLEAN, null, true);
db.assertColumnDefinition("ce_activity", "tmp_is_last_key", VARCHAR, 55, true);
db.assertColumnDefinition("ce_activity", "tmp_main_is_last", BOOLEAN, null, true);
db.assertColumnDefinition("ce_activity", "tmp_main_is_last_key", VARCHAR, 55, true);
db.assertIndex("ce_activity", "ce_activity_t_islast_key", "tmp_is_last_key");
db.assertIndex("ce_activity", "ce_activity_t_main_islast", "tmp_main_is_last", "status");
db.assertIndex("ce_activity", "ce_activity_t_main_islast_key", "tmp_main_is_last_key");
db.assertIndex("ce_activity", "ce_activity_t_main_islast", "tmp_main_is_last", "status");
}

@Test
public void migration_is_not_reentrant() throws SQLException {
underTest.execute();

expectedException.expect(IllegalStateException.class);

underTest.execute();
}

}

+ 1
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/DbVersion74Test.java View File

@@ -35,6 +35,6 @@ public class DbVersion74Test {

@Test
public void verify_migration_count() {
verifyMigrationCount(underTest, 5);
verifyMigrationCount(underTest, 11);
}
}

+ 233
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivityTest.java View File

@@ -0,0 +1,233 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import com.google.common.collect.ImmutableMap;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
import java.util.Random;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.db.CoreDbTester;

import static java.util.Arrays.stream;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;

@RunWith(DataProviderRunner.class)
public class PopulateTmpColumnsToCeActivityTest {
private static final Map<String, String> NO_CHARACTERISTICS = Collections.emptyMap();

@Rule
public CoreDbTester db = CoreDbTester.createForSchema(PopulateTmpColumnsToCeActivityTest.class, "ce_activity.sql");

private MapSettings settings = new MapSettings();
private PopulateTmpColumnsToCeActivity underTest = new PopulateTmpColumnsToCeActivity(db.database(), settings.asConfig());

@Test
public void execute_has_no_effect_on_empty_table() throws SQLException {
underTest.execute();

assertThat(rowsInCeActivity()).isEmpty();
}

@Test
public void execute_has_no_effect_on_empty_table_on_sonarcloud() throws SQLException {
settings.setProperty("sonar.sonarcloud.enabled", true);

underTest.execute();

assertThat(rowsInCeActivity()).isEmpty();
}

@Test
@UseDataProvider("characteriticsOfMainBranchesAndPr")
public void execute_populates_tmp_columns_with_component_uuid_if_task_has_no_row_in_PROJECTS(Map<String, String> characteristics) throws SQLException {
Row[] notUpdatedRows = Stream.of(
// not updated because no component_uuid
new Row(newUuid(), null, null, null),
new Row(newUuid(), null, randomAlphabetic(2), null),
new Row(newUuid(), null, randomAlphabetic(3), randomAlphabetic(4)),
new Row(newUuid(), null, null, randomAlphabetic(5)),
// not updated because both target fields are already set (re-entrance)
new Row(newUuid(), randomAlphabetic(14), randomAlphabetic(6), randomAlphabetic(7)))
.toArray(Row[]::new);
Row[] updatedRows = {
new Row(newUuid(), randomAlphabetic(12), null, null),
new Row(newUuid(), randomAlphabetic(13), randomAlphabetic(5), null),
new Row(newUuid(), randomAlphabetic(14), null, randomAlphabetic(6)),
};
stream(notUpdatedRows).forEach(row -> insertCeActivity(row, characteristics));
stream(updatedRows).forEach(row -> insertCeActivity(row, characteristics));

underTest.execute();

assertThat(rowsInCeActivity())
.hasSize(notUpdatedRows.length + updatedRows.length)
.contains(notUpdatedRows)
.contains(stream(updatedRows)
.map(row -> new Row(row.taskUuid, row.componentUuid, row.componentUuid, row.componentUuid))
.toArray(Row[]::new));
}

@DataProvider
public static Object[][] characteriticsOfMainBranchesAndPr() {
return new Object[][] {
{NO_CHARACTERISTICS},
{branchCharacteristics("LONG", randomAlphabetic(15))},
{branchCharacteristics("SHORT", randomAlphabetic(16))},
{branchCharacteristics(randomAlphabetic(17), randomAlphabetic(18))},
{prCharacteristics(randomAlphabetic(19))},
};
}

@Test
public void execute_populates_tmp_columns_with_component_uuid_for_existing_main_branch() throws SQLException {
String mainComponentUuid = randomAlphabetic(2);
insertProjects(mainComponentUuid, randomAlphabetic(3));
String taskUuid = insertCeActivity(new Row(newUuid(), mainComponentUuid, null, null), NO_CHARACTERISTICS);

underTest.execute();

assertThat(rowsInCeActivity())
.containsOnly(new Row(taskUuid, mainComponentUuid, mainComponentUuid, mainComponentUuid));
}

@Test
public void execute_deletes_populates_branches_of_task_without_row_in_PROJECTS_with_COMPONENT_UUID_and_those_with_row_in_PROJECTS_by_KEE() throws SQLException {
String mainComponentUuid = randomAlphabetic(2);
String mainComponentKey = randomAlphabetic(3);
String branchUuid = randomAlphabetic(4);
String branchType1 = randomAlphabetic(5);
String branchName1 = randomAlphabetic(6);
String branchType2 = randomAlphabetic(7);
String branchName2 = randomAlphabetic(8);
insertProjects(mainComponentUuid, mainComponentKey);
insertProjects(branchUuid, mainComponentKey + ":BRANCH:" + branchName2);
String orphanTaskUuid = insertCeActivity(new Row(newUuid(), mainComponentUuid, null, null), branchCharacteristics(branchType1, branchName1));
String regularTaskUuid = insertCeActivity(new Row(newUuid(), mainComponentUuid, null, null), branchCharacteristics(branchType2, branchName2));

underTest.execute();

assertThat(rowsInCeActivity())
.containsOnly(
new Row(orphanTaskUuid, mainComponentUuid, mainComponentUuid, mainComponentUuid),
new Row(regularTaskUuid, mainComponentUuid, branchUuid, mainComponentUuid));
}

@Test
public void execute_deletes_populates_prs_of_task_without_row_in_PROJECTS_with_COMPONENT_UUID_and_those_with_row_in_PROJECTS_by_KEE() throws SQLException {
String mainComponentUuid = randomAlphabetic(2);
String mainComponentKey = randomAlphabetic(3);
String prUuid = randomAlphabetic(4);
String prName1 = randomAlphabetic(6);
String prName2 = randomAlphabetic(8);
insertProjects(mainComponentUuid, mainComponentKey);
insertProjects(prUuid, mainComponentKey + ":PULL_REQUEST:" + prName2);
String orphanTaskUuid = insertCeActivity(new Row(newUuid(), mainComponentUuid, null, null), prCharacteristics(prName1));
String regularTaskUuid = insertCeActivity(new Row(newUuid(), mainComponentUuid, null, null), prCharacteristics(prName2));

underTest.execute();

assertThat(rowsInCeActivity())
.containsOnly(
new Row(orphanTaskUuid, mainComponentUuid, mainComponentUuid, mainComponentUuid),
new Row(regularTaskUuid, mainComponentUuid, prUuid, mainComponentUuid));
}

private Stream<Row> rowsInCeActivity() {
return db.select("select" +
" uuid as \"UUID\", component_uuid as \"COMPONENT_UUID\", tmp_component_uuid as \"TMP_COMPONENT_UUID\", tmp_main_component_UUID as \"TMP_MAIN_COMPONENT_UUID\"" +
" from ce_activity")
.stream()
.map(row -> new Row(
(String) row.get("UUID"),
(String) row.get("COMPONENT_UUID"),
(String) row.get("TMP_COMPONENT_UUID"),
(String) row.get("TMP_MAIN_COMPONENT_UUID")));
}

private String insertCeActivity(Row row, Map<String, String> characteristics) {
String uuid = insertCeActivity(row.taskUuid, row.componentUuid, row.tmpComponentUuid, row.tmpMainComponentUuid);
characteristics.forEach((key, value) -> insertCeCharacteristic(uuid, key, value));
return uuid;
}

private String insertCeActivity(String uuid, @Nullable String componentUuid, @Nullable String tmpComponentUuid, @Nullable String tmpMainComponentUuid) {
Random random = new Random();
db.executeInsert("ce_activity",
"UUID", uuid,
"TASK_TYPE", randomAlphabetic(6),
"COMPONENT_UUID", componentUuid,
"TMP_COMPONENT_UUID", tmpComponentUuid,
"TMP_MAIN_COMPONENT_UUID", tmpMainComponentUuid,
"STATUS", randomAlphabetic(7),
"IS_LAST", random.nextBoolean(),
"IS_LAST_KEY", random.nextBoolean(),
"EXECUTION_COUNT", random.nextInt(500),
"SUBMITTED_AT", (long) random.nextInt(500),
"CREATED_AT", (long) random.nextInt(500),
"UPDATED_AT", (long) random.nextInt(500));
return uuid;
}

private void insertCeCharacteristic(String taskUuid, String key, String value) {
db.executeInsert(
"ce_task_characteristics",
"UUID", newUuid(),
"TASK_UUID", taskUuid,
"KEE", key,
"TEXT_VALUE", value);
}

private void insertProjects(String uuid, String key) {
db.executeInsert(
"PROJECTS",
"UUID", uuid,
"KEE", key,
"ORGANIZATION_UUID", "org_" + uuid,
"ROOT_UUID", uuid + "_root",
"UUID_PATH", uuid + "_path",
"PROJECT_UUID", uuid + "_project",
"PRIVATE", new Random().nextBoolean());
}

private int uuidGenerator = new Random().nextInt(9000);

private String newUuid() {
return "uuid_" + uuidGenerator++;
}

private static Map<String, String> branchCharacteristics(String branchType, String branchName) {
return ImmutableMap.of("branchType", branchType, "branch", branchName);
}

private static Map<String, String> prCharacteristics(String prName) {
return ImmutableMap.of("pullRequest", prName);
}
}

+ 216
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueueTest.java View File

@@ -0,0 +1,216 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import com.google.common.collect.ImmutableMap;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
import java.util.Random;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.sonar.db.CoreDbTester;

import static java.util.Arrays.stream;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;

@RunWith(DataProviderRunner.class)
public class PopulateTmpColumnsToCeQueueTest {
private static final Map<String, String> NO_CHARACTERISTICS = Collections.emptyMap();

@Rule
public CoreDbTester db = CoreDbTester.createForSchema(PopulateTmpColumnsToCeQueueTest.class, "ce_queue.sql");

private PopulateTmpColumnsToCeQueue underTest = new PopulateTmpColumnsToCeQueue(db.database());

@Test
public void no_action_on_empty_table() throws SQLException {
underTest.execute();

assertThat(db.countRowsOfTable("ce_queue")).isZero();
}

@Test
@UseDataProvider("characteriticsOfMainBranchesAndPr")
public void execute_populates_tmp_columns_with_component_uuid_if_task_has_no_row_in_PROJECTS(Map<String, String> characteristics) throws SQLException {
Row[] notUpdatedRows = Stream.of(
// not updated because no component_uuid
new Row(newUuid(), null, null, null),
new Row(newUuid(), null, randomAlphabetic(2), null),
new Row(newUuid(), null, randomAlphabetic(3), randomAlphabetic(4)),
new Row(newUuid(), null, null, randomAlphabetic(5)),
// not updated because both target fields are already set (re-entrance)
new Row(newUuid(), randomAlphabetic(14), randomAlphabetic(6), randomAlphabetic(7)))
.toArray(Row[]::new);
Row[] updatedRows = {
new Row(newUuid(), randomAlphabetic(12), null, null),
new Row(newUuid(), randomAlphabetic(13), randomAlphabetic(5), null),
new Row(newUuid(), randomAlphabetic(14), null, randomAlphabetic(6)),
};
stream(notUpdatedRows).forEach(row -> insertCeQueue(row, characteristics));
stream(updatedRows).forEach(row -> insertCeQueue(row, characteristics));

underTest.execute();

assertThat(rowsInCeQueue())
.hasSize(notUpdatedRows.length + updatedRows.length)
.contains(notUpdatedRows)
.contains(stream(updatedRows)
.map(row -> new Row(row.taskUuid, row.componentUuid, row.componentUuid, row.componentUuid))
.toArray(Row[]::new));
}

@DataProvider
public static Object[][] characteriticsOfMainBranchesAndPr() {
return new Object[][] {
{NO_CHARACTERISTICS},
{branchCharacteristics("LONG", randomAlphabetic(15))},
{branchCharacteristics("SHORT", randomAlphabetic(16))},
{branchCharacteristics(randomAlphabetic(17), randomAlphabetic(18))},
{prCharacteristics(randomAlphabetic(19))},
};
}

@Test
public void execute_populates_tmp_columns_with_component_uuid_for_existing_main_branch() throws SQLException {
String mainComponentUuid = randomAlphabetic(2);
insertProjects(mainComponentUuid, randomAlphabetic(3));
String taskUuid = insertCeQueue(new Row(newUuid(), mainComponentUuid, null, null), NO_CHARACTERISTICS);

underTest.execute();

assertThat(rowsInCeQueue())
.containsOnly(new Row(taskUuid, mainComponentUuid, mainComponentUuid, mainComponentUuid));
}

@Test
public void execute_deletes_tasks_of_branches_without_row_in_PROJECTS_and_populates_others_matching_row_in_PROJECTS_by_KEE() throws SQLException {
String mainComponentUuid = randomAlphabetic(2);
String mainComponentKey = randomAlphabetic(3);
String branchUuid = randomAlphabetic(4);
String branchType1 = randomAlphabetic(5);
String branchName1 = randomAlphabetic(6);
String branchType2 = randomAlphabetic(7);
String branchName2 = randomAlphabetic(8);
insertProjects(mainComponentUuid, mainComponentKey);
insertProjects(branchUuid, mainComponentKey + ":BRANCH:" + branchName2);
String deletedTaskUuid = insertCeQueue(new Row(newUuid(), mainComponentUuid, null, null), branchCharacteristics(branchType1, branchName1));
String updatedTaskUuid = insertCeQueue(new Row(newUuid(), mainComponentUuid, null, null), branchCharacteristics(branchType2, branchName2));

underTest.execute();

assertThat(rowsInCeQueue())
.containsOnly(new Row(updatedTaskUuid, mainComponentUuid, branchUuid, mainComponentUuid));
}

@Test
public void execute_deletes_tasks_of_prs_without_row_in_PROJECTS_and_populates_others_matching_row_in_PROJECTS_by_KEE() throws SQLException {
String mainComponentUuid = randomAlphabetic(2);
String mainComponentKey = randomAlphabetic(3);
String prUuid = randomAlphabetic(4);
String prName1 = randomAlphabetic(6);
String prName2 = randomAlphabetic(8);
insertProjects(mainComponentUuid, mainComponentKey);
insertProjects(prUuid, mainComponentKey + ":PULL_REQUEST:" + prName2);
String deletedTaskUuid = insertCeQueue(new Row(newUuid(), mainComponentUuid, null, null), prCharacteristics(prName1));
String updatedTaskUuid = insertCeQueue(new Row(newUuid(), mainComponentUuid, null, null), prCharacteristics(prName2));

underTest.execute();

assertThat(rowsInCeQueue())
.containsOnly(new Row(updatedTaskUuid, mainComponentUuid, prUuid, mainComponentUuid));
}

private Stream<Row> rowsInCeQueue() {
return db.select("select" +
" uuid as \"UUID\", component_uuid as \"COMPONENT_UUID\", tmp_component_uuid as \"TMP_COMPONENT_UUID\", tmp_main_component_UUID as \"TMP_MAIN_COMPONENT_UUID\"" +
" from ce_queue")
.stream()
.map(row -> new Row(
(String) row.get("UUID"),
(String) row.get("COMPONENT_UUID"),
(String) row.get("TMP_COMPONENT_UUID"),
(String) row.get("TMP_MAIN_COMPONENT_UUID")));
}

private String insertCeQueue(Row row, Map<String, String> characteristics) {
String uuid = insertCeQueue(row.taskUuid, row.componentUuid, row.tmpComponentUuid, row.tmpMainComponentUuid);
characteristics.forEach((key, value) -> insertCeCharacteristic(uuid, key, value));
return uuid;
}

private String insertCeQueue(String uuid, @Nullable String componentUuid, @Nullable String tmpComponentUuid, @Nullable String tmpMainComponentUuid) {
Random random = new Random();
db.executeInsert("ce_queue",
"UUID", uuid,
"TASK_TYPE", randomAlphabetic(6),
"COMPONENT_UUID", componentUuid,
"TMP_COMPONENT_UUID", tmpComponentUuid,
"TMP_MAIN_COMPONENT_UUID", tmpMainComponentUuid,
"STATUS", randomAlphabetic(7),
"EXECUTION_COUNT", random.nextInt(500),
"CREATED_AT", (long) random.nextInt(500),
"UPDATED_AT", (long) random.nextInt(500));
return uuid;
}

private void insertCeCharacteristic(String taskUuid, String key, String value) {
db.executeInsert(
"ce_task_characteristics",
"UUID", newUuid(),
"TASK_UUID", taskUuid,
"KEE", key,
"TEXT_VALUE", value);
}

private void insertProjects(String uuid, String key) {
db.executeInsert(
"PROJECTS",
"UUID", uuid,
"KEE", key,
"ORGANIZATION_UUID", "org_" + uuid,
"ROOT_UUID", uuid + "_root",
"UUID_PATH", uuid + "_path",
"PROJECT_UUID", uuid + "_project",
"PRIVATE", new Random().nextBoolean());
}

private int uuidGenerator = new Random().nextInt(9000);

private String newUuid() {
return "uuid_" + uuidGenerator++;
}

private static Map<String, String> branchCharacteristics(String branchType, String branchName) {
return ImmutableMap.of("branchType", branchType, "branch", branchName);
}

private static Map<String, String> prCharacteristics(String prName) {
return ImmutableMap.of("pullRequest", prName);
}

}

+ 118
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivityTest.java View File

@@ -0,0 +1,118 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import java.sql.SQLException;
import java.util.List;
import java.util.Random;
import javax.annotation.Nullable;
import org.assertj.core.api.AbstractIterableAssert;
import org.assertj.core.api.ObjectAssert;
import org.assertj.core.groups.Tuple;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.CoreDbTester;

import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;

public class PopulateTmpLastKeyColumnsToCeActivityTest {

@Rule
public CoreDbTester db = CoreDbTester.createForSchema(PopulateTmpLastKeyColumnsToCeActivityTest.class, "ce_activity.sql");

private MapSettings settings = new MapSettings();
private PopulateTmpLastKeyColumnsToCeActivity underTest = new PopulateTmpLastKeyColumnsToCeActivity(db.database(), settings.asConfig());

@Test
public void execute_has_no_effect_on_empty_table() throws SQLException {
underTest.execute();

assertThat(db.countRowsOfTable("ce_activity")).isZero();
}

@Test
public void execute_populate_tmp_last_key_columns_from_type_and_component_uuid_columns() throws SQLException {
String type = randomAlphabetic(6);
String oldComponentUuid = randomAlphabetic(7);
String tmpComponentUuid = randomAlphabetic(8);
String tmpMainComponentUuid = randomAlphabetic(9);

String taskWithComponentUuid = insertCeActivity(type, oldComponentUuid, tmpComponentUuid, tmpMainComponentUuid);
String taskWithInconsistentComponentUuid = insertCeActivity(type, null, tmpComponentUuid, tmpMainComponentUuid);
String taskNoComponentUuid = insertCeActivity(type, null, null, null);

underTest.execute();

assertThatTmpLastKeyAndMainLastKeyOf(taskWithComponentUuid).containsOnly(tuple(type + tmpComponentUuid, type + tmpMainComponentUuid));
assertThatTmpLastKeyAndMainLastKeyOf(taskWithInconsistentComponentUuid).containsOnly(tuple(type, type));
assertThatTmpLastKeyAndMainLastKeyOf(taskNoComponentUuid).containsOnly(tuple(type, type));

assertThatTmpIsLastAndMainIsLastOf(taskWithComponentUuid).containsOnly(tuple(false, false));
assertThatTmpIsLastAndMainIsLastOf(taskWithInconsistentComponentUuid).containsOnly(tuple(false, false));
assertThatTmpIsLastAndMainIsLastOf(taskNoComponentUuid).containsOnly(tuple(false, false));
}
@Test
public void execute_is_reentrant() throws SQLException {
execute_populate_tmp_last_key_columns_from_type_and_component_uuid_columns();

underTest.execute();
}

private String insertCeActivity(String type,
@Nullable String oldComponentUuid,
@Nullable String tmpComponentUuid, @Nullable String tmpMainComponentUuid) {
checkArgument((tmpComponentUuid == null) == (tmpMainComponentUuid == null));

String uuid = UuidFactoryFast.getInstance().create();

Random random = new Random();
db.executeInsert(
"ce_activity",
"UUID", uuid,
"TASK_TYPE", type,
"COMPONENT_UUID", oldComponentUuid,
"TMP_COMPONENT_UUID", tmpComponentUuid,
"TMP_MAIN_COMPONENT_UUID", tmpMainComponentUuid,
"STATUS", randomAlphabetic(5),
"IS_LAST", random.nextBoolean(),
"IS_LAST_KEY", randomAlphabetic(12),
"EXECUTION_COUNT", random.nextInt(10),
"SUBMITTED_AT", (long) random.nextInt(5_999),
"CREATED_AT", (long) random.nextInt(5_999),
"UPDATED_AT", (long) random.nextInt(5_999));

return uuid;
}

private AbstractIterableAssert<?, List<? extends Tuple>, Tuple, ObjectAssert<Tuple>> assertThatTmpLastKeyAndMainLastKeyOf(String uuid) {
return assertThat(db.select("select tmp_is_last_key as \"LAST_KEY\", tmp_main_is_last_key as \"MAIN_LAST_KEY\" from ce_activity where uuid='" + uuid + "'"))
.extracting(t -> (String) t.get("LAST_KEY"), t -> (String) t.get("MAIN_LAST_KEY"));
}

private AbstractIterableAssert<?, List<? extends Tuple>, Tuple, ObjectAssert<Tuple>> assertThatTmpIsLastAndMainIsLastOf(String uuid) {
return assertThat(db.select("select tmp_is_last as \"LAST\", tmp_main_is_last as \"MAIN_LAST\" from ce_activity where uuid='" + uuid + "'"))
.extracting(t -> (Boolean) t.get("LAST"), t -> (Boolean) t.get("MAIN_LAST"));
}
}

+ 67
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/Row.java View File

@@ -0,0 +1,67 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v74;

import java.util.Objects;
import javax.annotation.Nullable;

final class Row {
final String taskUuid;
final String componentUuid;
final String tmpComponentUuid;
final String tmpMainComponentUuid;

Row(String taskUuid, @Nullable String componentUuid, @Nullable String tmpComponentUuid, @Nullable String tmpMainComponentUuid) {
this.taskUuid = taskUuid;
this.componentUuid = componentUuid;
this.tmpComponentUuid = tmpComponentUuid;
this.tmpMainComponentUuid = tmpMainComponentUuid;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Row row = (Row) o;
return Objects.equals(taskUuid, row.taskUuid) &&
Objects.equals(componentUuid, row.componentUuid) &&
Objects.equals(tmpComponentUuid, row.tmpComponentUuid) &&
Objects.equals(tmpMainComponentUuid, row.tmpMainComponentUuid);
}

@Override
public int hashCode() {
return Objects.hash(taskUuid, componentUuid, tmpComponentUuid, tmpMainComponentUuid);
}

@Override
public String toString() {
return "Row{" +
"uuid='" + taskUuid + '\'' +
", componentUuid='" + componentUuid + '\'' +
", tmpComponentUuid='" + tmpComponentUuid + '\'' +
", tmpMainComponentUuid='" + tmpMainComponentUuid + '\'' +
'}';
}
}

+ 26
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivityTest/ce_activity.sql View File

@@ -0,0 +1,26 @@
CREATE TABLE "CE_ACTIVITY" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"UUID" VARCHAR(40) NOT NULL,
"TASK_TYPE" VARCHAR(15) NOT NULL,
"COMPONENT_UUID" VARCHAR(40) NULL,
"ANALYSIS_UUID" VARCHAR(50) NULL,
"STATUS" VARCHAR(15) NOT NULL,
"IS_LAST" BOOLEAN NOT NULL,
"IS_LAST_KEY" VARCHAR(55) NOT NULL,
"SUBMITTER_UUID" VARCHAR(255) NULL,
"WORKER_UUID" VARCHAR(40) NULL,
"EXECUTION_COUNT" INTEGER NOT NULL,
"SUBMITTED_AT" BIGINT NOT NULL,
"STARTED_AT" BIGINT NULL,
"EXECUTED_AT" BIGINT NULL,
"CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL,
"EXECUTION_TIME_MS" BIGINT NULL,
"ERROR_MESSAGE" VARCHAR(1000),
"ERROR_STACKTRACE" CLOB,
"ERROR_TYPE" VARCHAR(20)
);
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");

+ 16
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueueTest/ce_queue.sql View File

@@ -0,0 +1,16 @@
CREATE TABLE "CE_QUEUE" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"UUID" VARCHAR(40) NOT NULL,
"TASK_TYPE" VARCHAR(15) NOT NULL,
"COMPONENT_UUID" VARCHAR(40) NULL,
"STATUS" VARCHAR(15) NOT NULL,
"SUBMITTER_UUID" VARCHAR(255) NULL,
"WORKER_UUID" VARCHAR(40) NULL,
"EXECUTION_COUNT" INTEGER NOT NULL,
"STARTED_AT" BIGINT NULL,
"CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL
);
CREATE UNIQUE INDEX "CE_QUEUE_UUID" ON "CE_QUEUE" ("UUID");
CREATE INDEX "CE_QUEUE_COMPONENT_UUID" ON "CE_QUEUE" ("COMPONENT_UUID");
CREATE INDEX "CE_QUEUE_STATUS" ON "CE_QUEUE" ("STATUS");

+ 30
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivityTest/ce_activity.sql View File

@@ -0,0 +1,30 @@
CREATE TABLE "CE_ACTIVITY" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"UUID" VARCHAR(40) NOT NULL,
"TASK_TYPE" VARCHAR(15) NOT NULL,
"COMPONENT_UUID" VARCHAR(40) NULL,
"TMP_COMPONENT_UUID" VARCHAR(40) NULL,
"TMP_MAIN_COMPONENT_UUID" VARCHAR(40) NULL,
"ANALYSIS_UUID" VARCHAR(50) NULL,
"STATUS" VARCHAR(15) NOT NULL,
"IS_LAST" BOOLEAN NOT NULL,
"IS_LAST_KEY" VARCHAR(55) NOT NULL,
"SUBMITTER_UUID" VARCHAR(255) NULL,
"WORKER_UUID" VARCHAR(40) NULL,
"EXECUTION_COUNT" INTEGER NOT NULL,
"SUBMITTED_AT" BIGINT NOT NULL,
"STARTED_AT" BIGINT NULL,
"EXECUTED_AT" BIGINT NULL,
"CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL,
"EXECUTION_TIME_MS" BIGINT NULL,
"ERROR_MESSAGE" VARCHAR(1000),
"ERROR_STACKTRACE" CLOB,
"ERROR_TYPE" VARCHAR(20)
);
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_TMP_CMPT_UUID" ON "CE_ACTIVITY" ("TMP_COMPONENT_UUID");
CREATE INDEX "CE_ACTIVITY_TMP_MAIN_CMPT_UUID" ON "CE_ACTIVITY" ("TMP_MAIN_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");

+ 87
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivityTest/ce_activity.sql View File

@@ -0,0 +1,87 @@
CREATE TABLE "CE_ACTIVITY" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"UUID" VARCHAR(40) NOT NULL,
"TASK_TYPE" VARCHAR(15) NOT NULL,
"COMPONENT_UUID" VARCHAR(40) NULL,
"TMP_COMPONENT_UUID" VARCHAR(40) NULL,
"TMP_MAIN_COMPONENT_UUID" VARCHAR(40) NULL,
"ANALYSIS_UUID" VARCHAR(50) NULL,
"STATUS" VARCHAR(15) NOT NULL,
"IS_LAST" BOOLEAN NOT NULL,
"IS_LAST_KEY" VARCHAR(55) NOT NULL,
"SUBMITTER_UUID" VARCHAR(255) NULL,
"WORKER_UUID" VARCHAR(40) NULL,
"EXECUTION_COUNT" INTEGER NOT NULL,
"SUBMITTED_AT" BIGINT NOT NULL,
"STARTED_AT" BIGINT NULL,
"EXECUTED_AT" BIGINT NULL,
"CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL,
"EXECUTION_TIME_MS" BIGINT NULL,
"ERROR_MESSAGE" VARCHAR(1000),
"ERROR_STACKTRACE" CLOB,
"ERROR_TYPE" VARCHAR(20)
);
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_TMP_CMPT_UUID" ON "CE_ACTIVITY" ("TMP_COMPONENT_UUID");
CREATE INDEX "CE_ACTIVITY_TMP_MAIN_CMPT_UUID" ON "CE_ACTIVITY" ("TMP_MAIN_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,
"TASK_UUID" VARCHAR(40) NOT NULL,
"KEE" VARCHAR(50) NOT NULL,
"TEXT_VALUE" VARCHAR(4000),

CONSTRAINT "PK_CE_TASK_CHARACTERISTICS" PRIMARY KEY ("UUID")
);
CREATE INDEX "CE_TASK_CHARACTERISTICS_TASK_UUID" ON "CE_TASK_CHARACTERISTICS" ("TASK_UUID");

CREATE TABLE "PROJECTS" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"ORGANIZATION_UUID" VARCHAR(40) NOT NULL,
"KEE" VARCHAR(400),
"UUID" VARCHAR(50) NOT NULL,
"UUID_PATH" VARCHAR(1500) NOT NULL,
"ROOT_UUID" VARCHAR(50) NOT NULL,
"PROJECT_UUID" VARCHAR(50) NOT NULL,
"MODULE_UUID" VARCHAR(50),
"MODULE_UUID_PATH" VARCHAR(1500),
"MAIN_BRANCH_PROJECT_UUID" VARCHAR(50),
"NAME" VARCHAR(2000),
"DESCRIPTION" VARCHAR(2000),
"PRIVATE" BOOLEAN NOT NULL,
"TAGS" VARCHAR(500),
"ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
"SCOPE" VARCHAR(3),
"QUALIFIER" VARCHAR(10),
"DEPRECATED_KEE" VARCHAR(400),
"PATH" VARCHAR(2000),
"LANGUAGE" VARCHAR(20),
"COPY_COMPONENT_UUID" VARCHAR(50),
"LONG_NAME" VARCHAR(2000),
"DEVELOPER_UUID" VARCHAR(50),
"CREATED_AT" TIMESTAMP,
"AUTHORIZATION_UPDATED_AT" BIGINT,
"B_CHANGED" BOOLEAN,
"B_COPY_COMPONENT_UUID" VARCHAR(50),
"B_DESCRIPTION" VARCHAR(2000),
"B_ENABLED" BOOLEAN,
"B_UUID_PATH" VARCHAR(1500),
"B_LANGUAGE" VARCHAR(20),
"B_LONG_NAME" VARCHAR(500),
"B_MODULE_UUID" VARCHAR(50),
"B_MODULE_UUID_PATH" VARCHAR(1500),
"B_NAME" VARCHAR(500),
"B_PATH" VARCHAR(2000),
"B_QUALIFIER" VARCHAR(10)
);
CREATE INDEX "PROJECTS_ORGANIZATION" ON "PROJECTS" ("ORGANIZATION_UUID");
CREATE UNIQUE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE");
CREATE INDEX "PROJECTS_ROOT_UUID" ON "PROJECTS" ("ROOT_UUID");
CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID");
CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID");
CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID");
CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER");

+ 77
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueueTest/ce_queue.sql View File

@@ -0,0 +1,77 @@
CREATE TABLE "CE_QUEUE" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"UUID" VARCHAR(40) NOT NULL,
"TASK_TYPE" VARCHAR(15) NOT NULL,
"COMPONENT_UUID" VARCHAR(40) NULL,
"TMP_COMPONENT_UUID" VARCHAR(40) NULL,
"TMP_MAIN_COMPONENT_UUID" VARCHAR(40) NULL,
"STATUS" VARCHAR(15) NOT NULL,
"SUBMITTER_UUID" VARCHAR(255) NULL,
"WORKER_UUID" VARCHAR(40) NULL,
"EXECUTION_COUNT" INTEGER NOT NULL,
"STARTED_AT" BIGINT NULL,
"CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL
);
CREATE UNIQUE INDEX "CE_QUEUE_UUID" ON "CE_QUEUE" ("UUID");
CREATE INDEX "CE_QUEUE_COMPONENT_UUID" ON "CE_QUEUE" ("COMPONENT_UUID");
CREATE INDEX "CE_QUEUE_TMP_COMPONENT_UUID" ON "CE_QUEUE" ("TMP_COMPONENT_UUID");
CREATE INDEX "CE_QUEUE_TMP_MAIN_CMPT_UUID" ON "CE_QUEUE" ("TMP_MAIN_COMPONENT_UUID");
CREATE INDEX "CE_QUEUE_STATUS" ON "CE_QUEUE" ("STATUS");

CREATE TABLE "CE_TASK_CHARACTERISTICS" (
"UUID" VARCHAR(40) NOT NULL,
"TASK_UUID" VARCHAR(40) NOT NULL,
"KEE" VARCHAR(50) NOT NULL,
"TEXT_VALUE" VARCHAR(4000),

CONSTRAINT "PK_CE_TASK_CHARACTERISTICS" PRIMARY KEY ("UUID")
);
CREATE INDEX "CE_TASK_CHARACTERISTICS_TASK_UUID" ON "CE_TASK_CHARACTERISTICS" ("TASK_UUID");

CREATE TABLE "PROJECTS" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"ORGANIZATION_UUID" VARCHAR(40) NOT NULL,
"KEE" VARCHAR(400),
"UUID" VARCHAR(50) NOT NULL,
"UUID_PATH" VARCHAR(1500) NOT NULL,
"ROOT_UUID" VARCHAR(50) NOT NULL,
"PROJECT_UUID" VARCHAR(50) NOT NULL,
"MODULE_UUID" VARCHAR(50),
"MODULE_UUID_PATH" VARCHAR(1500),
"MAIN_BRANCH_PROJECT_UUID" VARCHAR(50),
"NAME" VARCHAR(2000),
"DESCRIPTION" VARCHAR(2000),
"PRIVATE" BOOLEAN NOT NULL,
"TAGS" VARCHAR(500),
"ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
"SCOPE" VARCHAR(3),
"QUALIFIER" VARCHAR(10),
"DEPRECATED_KEE" VARCHAR(400),
"PATH" VARCHAR(2000),
"LANGUAGE" VARCHAR(20),
"COPY_COMPONENT_UUID" VARCHAR(50),
"LONG_NAME" VARCHAR(2000),
"DEVELOPER_UUID" VARCHAR(50),
"CREATED_AT" TIMESTAMP,
"AUTHORIZATION_UPDATED_AT" BIGINT,
"B_CHANGED" BOOLEAN,
"B_COPY_COMPONENT_UUID" VARCHAR(50),
"B_DESCRIPTION" VARCHAR(2000),
"B_ENABLED" BOOLEAN,
"B_UUID_PATH" VARCHAR(1500),
"B_LANGUAGE" VARCHAR(20),
"B_LONG_NAME" VARCHAR(500),
"B_MODULE_UUID" VARCHAR(50),
"B_MODULE_UUID_PATH" VARCHAR(1500),
"B_NAME" VARCHAR(500),
"B_PATH" VARCHAR(2000),
"B_QUALIFIER" VARCHAR(10)
);
CREATE INDEX "PROJECTS_ORGANIZATION" ON "PROJECTS" ("ORGANIZATION_UUID");
CREATE UNIQUE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE");
CREATE INDEX "PROJECTS_ROOT_UUID" ON "PROJECTS" ("ROOT_UUID");
CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID");
CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID");
CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID");
CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER");

+ 38
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivityTest/ce_activity.sql View File

@@ -0,0 +1,38 @@
CREATE TABLE "CE_ACTIVITY" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"UUID" VARCHAR(40) NOT NULL,
"TASK_TYPE" VARCHAR(15) NOT NULL,
"COMPONENT_UUID" VARCHAR(40) NULL,
"TMP_COMPONENT_UUID" VARCHAR(40) NULL,
"TMP_MAIN_COMPONENT_UUID" VARCHAR(40) NULL,
"ANALYSIS_UUID" VARCHAR(50) NULL,
"STATUS" VARCHAR(15) NOT NULL,
"IS_LAST" BOOLEAN NOT NULL,
"IS_LAST_KEY" VARCHAR(55) NOT NULL,
"TMP_IS_LAST" BOOLEAN,
"TMP_IS_LAST_KEY" VARCHAR(55),
"TMP_MAIN_IS_LAST" BOOLEAN,
"TMP_MAIN_IS_LAST_KEY" VARCHAR(55),
"SUBMITTER_UUID" VARCHAR(255) NULL,
"WORKER_UUID" VARCHAR(40) NULL,
"EXECUTION_COUNT" INTEGER NOT NULL,
"SUBMITTED_AT" BIGINT NOT NULL,
"STARTED_AT" BIGINT NULL,
"EXECUTED_AT" BIGINT NULL,
"CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT NOT NULL,
"EXECUTION_TIME_MS" BIGINT NULL,
"ERROR_MESSAGE" VARCHAR(1000),
"ERROR_STACKTRACE" CLOB,
"ERROR_TYPE" VARCHAR(20)
);
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_TMP_CPNT_UUID" ON "CE_ACTIVITY" ("TMP_COMPONENT_UUID");
CREATE INDEX "CE_ACTIVITY_TMP_MAIN_CPNT_UUID" ON "CE_ACTIVITY" ("TMP_MAIN_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 INDEX "CE_ACTIVITY_T_ISLAST_KEY" ON "CE_ACTIVITY" ("TMP_IS_LAST_KEY");
CREATE INDEX "CE_ACTIVITY_T_ISLAST" ON "CE_ACTIVITY" ("TMP_IS_LAST", "STATUS");
CREATE INDEX "CE_ACTIVITY_T_MAIN_ISLAST_KEY" ON "CE_ACTIVITY" ("TMP_MAIN_IS_LAST_KEY");
CREATE INDEX "CE_ACTIVITY_T_MAIN_ISLAST" ON "CE_ACTIVITY" ("TMP_MAIN_IS_LAST", "STATUS");

+ 2
- 0
server/sonar-server/src/main/java/org/sonar/server/ce/CeModule.java View File

@@ -24,6 +24,7 @@ import org.sonar.ce.task.log.CeTaskLogging;
import org.sonar.ce.task.projectanalysis.taskprocessor.ReportTaskProcessor;
import org.sonar.core.platform.Module;
import org.sonar.server.ce.http.CeHttpClientImpl;
import org.sonar.server.ce.queue.BranchSupport;
import org.sonar.server.ce.queue.ReportSubmitter;

public class CeModule extends Module {
@@ -34,6 +35,7 @@ public class CeModule extends Module {

// Queue
CeQueueImpl.class,
BranchSupport.class,
ReportSubmitter.class,

// Core tasks processors

+ 169
- 0
server/sonar-server/src/main/java/org/sonar/server/ce/queue/BranchSupport.java View File

@@ -0,0 +1,169 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.ce.queue;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.server.ServerSide;
import org.sonar.core.component.ComponentKeys;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

/**
* Branch code for {@link ReportSubmitter}.
* <p>
* Does not support branches (except deprecated branch feature provided by "sonar.branch") unless an implementation of
* {@link BranchSupportDelegate} is available.
*/
@ServerSide
public class BranchSupport {
@CheckForNull
private final BranchSupportDelegate delegate;

/**
* Constructor called by Pico when no implementation of {@link BranchSupportDelegate} is available.
*/
public BranchSupport() {
this(null);
}

public BranchSupport(@Nullable BranchSupportDelegate delegate) {
this.delegate = delegate;
}

ComponentKey createComponentKey(String projectKey, @Nullable String deprecatedBranch, Map<String, String> characteristics) {
if (characteristics.isEmpty()) {
return new ComponentKeyImpl(projectKey, deprecatedBranch, ComponentKeys.createKey(projectKey, deprecatedBranch));
} else {
checkState(delegate != null, "Current edition does not support branch feature");
}

checkArgument(deprecatedBranch == null, "Deprecated branch feature can't be used at the same time as new branch support");
return delegate.createComponentKey(projectKey, characteristics);
}

ComponentDto createBranchComponent(DbSession dbSession, ComponentKey componentKey, OrganizationDto organization, ComponentDto mainComponentDto) {
checkState(delegate != null, "Current edition does not support branch feature");

return delegate.createBranchComponent(dbSession, componentKey, organization, mainComponentDto);
}

public interface ComponentKey {
String getKey();

String getDbKey();

Optional<String> getDeprecatedBranchName();

Optional<String> getBranchName();

Optional<String> getPullRequestKey();

boolean isMainBranch();

boolean isDeprecatedBranch();

/**
* @return the {@link ComponentKey} of the main branch for this component.
* If this component is the main branch (ie. {@link #isMainBranch()} returns true), this method returns
* {@code this}.
*/
ComponentKey getMainBranchComponentKey();
}

private static final class ComponentKeyImpl implements ComponentKey {
private final String key;
private final String dbKey;
@CheckForNull
private final String deprecatedBranchName;

public ComponentKeyImpl(String key, @Nullable String deprecatedBranchName, String dbKey) {
this.key = key;
this.deprecatedBranchName = deprecatedBranchName;
this.dbKey = dbKey;
}

@Override
public String getKey() {
return key;
}

@Override
public String getDbKey() {
return dbKey;
}

@Override
public Optional<String> getDeprecatedBranchName() {
return Optional.ofNullable(deprecatedBranchName);
}

@Override
public Optional<String> getBranchName() {
return Optional.empty();
}

@Override
public Optional<String> getPullRequestKey() {
return Optional.empty();
}

@Override
public boolean isMainBranch() {
return key.equals(dbKey);
}

@Override
public boolean isDeprecatedBranch() {
return deprecatedBranchName != null;
}

@Override
public ComponentKey getMainBranchComponentKey() {
return this;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ComponentKeyImpl that = (ComponentKeyImpl) o;
return Objects.equals(key, that.key) &&
Objects.equals(dbKey, that.dbKey) &&
Objects.equals(deprecatedBranchName, that.deprecatedBranchName);
}

@Override
public int hashCode() {
return Objects.hash(key, dbKey, deprecatedBranchName);
}
}
}

+ 55
- 0
server/sonar-server/src/main/java/org/sonar/server/ce/queue/BranchSupportDelegate.java View File

@@ -0,0 +1,55 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.ce.queue;

import java.util.Map;
import org.sonar.api.server.ServerSide;
import org.sonar.db.DbSession;
import org.sonar.db.ce.CeTaskCharacteristicDto;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.server.ce.queue.BranchSupport.ComponentKey;

@ServerSide
public interface BranchSupportDelegate {
/**
* Creates a {@link ComponentKey} for the specified projectKey and the specified {@code characteristics}.
*
* @throws IllegalArgumentException if {@code characteristics} is empty
* @throws IllegalArgumentException if does not contain a supported value for {@link CeTaskCharacteristicDto#BRANCH_TYPE_KEY BRANCH_TYPE_KEY}
* @throws IllegalArgumentException if does not contain a value for expected
* {@link CeTaskCharacteristicDto#BRANCH_KEY BRANCH_KEY} or {@link CeTaskCharacteristicDto#PULL_REQUEST PULL_REQUEST}
* given the value of {@link CeTaskCharacteristicDto#BRANCH_TYPE_KEY BRANCH_TYPE_KEY}
* @throws IllegalArgumentException if incorrectly contains a value in
* {@link CeTaskCharacteristicDto#BRANCH_KEY BRANCH_KEY} or {@link CeTaskCharacteristicDto#PULL_REQUEST PULL_REQUEST}
* given the value of {@link CeTaskCharacteristicDto#BRANCH_TYPE_KEY BRANCH_TYPE_KEY}
*/
ComponentKey createComponentKey(String projectKey, Map<String, String> characteristics);

/**
* Creates the ComponentDto for the branch described in {@code componentKey} which belongs to the specified
* {@code mainComponentDto} in the specified {@code organization}.
*
* @throws IllegalArgumentException if arguments are inconsistent (such as {@code mainComponentDto} not having the same
* key as {@code componentKey.getKey()}, ...)
*/
ComponentDto createBranchComponent(DbSession dbSession, ComponentKey componentKey,
OrganizationDto organization, ComponentDto mainComponentDto);
}

+ 40
- 19
server/sonar-server/src/main/java/org/sonar/server/ce/queue/ReportSubmitter.java View File

@@ -31,7 +31,6 @@ import org.sonar.api.server.ServerSide;
import org.sonar.ce.queue.CeQueue;
import org.sonar.ce.queue.CeTaskSubmit;
import org.sonar.ce.task.CeTask;
import org.sonar.core.component.ComponentKeys;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.ce.CeTaskTypes;
@@ -60,31 +59,33 @@ public class ReportSubmitter {
private final ComponentUpdater componentUpdater;
private final PermissionTemplateService permissionTemplateService;
private final DbClient dbClient;
private final BranchSupport branchSupport;

public ReportSubmitter(CeQueue queue, UserSession userSession, ComponentUpdater componentUpdater,
PermissionTemplateService permissionTemplateService, DbClient dbClient) {
PermissionTemplateService permissionTemplateService, DbClient dbClient, BranchSupport branchSupport) {
this.queue = queue;
this.userSession = userSession;
this.componentUpdater = componentUpdater;
this.permissionTemplateService = permissionTemplateService;
this.dbClient = dbClient;
this.branchSupport = branchSupport;
}

/**
* @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 deprecatedBranch, @Nullable String projectName, Map<String, String> characteristics,
InputStream reportInput) {
public CeTask submit(String organizationKey, String projectKey, @Nullable String deprecatedBranch, @Nullable String projectName,
Map<String, String> characteristics, InputStream reportInput) {
try (DbSession dbSession = dbClient.openSession(false)) {
OrganizationDto organizationDto = getOrganizationDtoOrFail(dbSession, organizationKey);
String effectiveProjectKey = ComponentKeys.createKey(projectKey, deprecatedBranch);
Optional<ComponentDto> component = dbClient.componentDao().selectByKey(dbSession, effectiveProjectKey);
validateProject(dbSession, component, projectKey);
ensureOrganizationIsConsistent(component, organizationDto);
ComponentDto project = component.orElseGet(() -> createProject(dbSession, organizationDto, projectKey, deprecatedBranch, projectName));
checkScanPermission(project);
return submitReport(dbSession, reportInput, project, characteristics);
BranchSupport.ComponentKey componentKey = branchSupport.createComponentKey(projectKey, deprecatedBranch, characteristics);
Optional<ComponentDto> existingComponent = dbClient.componentDao().selectByKey(dbSession, componentKey.getDbKey());
validateProject(dbSession, existingComponent, projectKey);
ensureOrganizationIsConsistent(existingComponent, organizationDto);
ComponentDto component = existingComponent.orElseGet(() -> createComponent(dbSession, organizationDto, componentKey, projectName));
checkScanPermission(component);
return submitReport(dbSession, reportInput, component, characteristics);
}
}

@@ -135,13 +136,32 @@ public class ReportSubmitter {
}
}

private ComponentDto createProject(DbSession dbSession, OrganizationDto organization, String projectKey, @Nullable String deprecatedBranch, @Nullable String projectName) {
private ComponentDto createComponent(DbSession dbSession, OrganizationDto organization, BranchSupport.ComponentKey componentKey, @Nullable String projectName) {
if (componentKey.isMainBranch() || componentKey.isDeprecatedBranch()) {
ComponentDto project = createProject(dbSession, organization, componentKey, projectName);
componentUpdater.commitAndIndex(dbSession, project);
return project;
}

Optional<ComponentDto> existingMainComponent = dbClient.componentDao().selectByKey(dbSession, componentKey.getKey());
ComponentDto mainComponentDto = existingMainComponent
.orElseGet(() -> createProject(dbSession, organization, componentKey.getMainBranchComponentKey(), projectName));
ComponentDto branchComponent = branchSupport.createBranchComponent(dbSession, componentKey, organization, mainComponentDto);
if (existingMainComponent.isPresent()) {
dbSession.commit();
} else {
componentUpdater.commitAndIndex(dbSession, mainComponentDto);
}
return branchComponent;
}

private ComponentDto createProject(DbSession dbSession, OrganizationDto organization, BranchSupport.ComponentKey componentKey,
@Nullable String projectName) {
userSession.checkPermission(OrganizationPermission.PROVISION_PROJECTS, organization);
Integer userId = userSession.getUserId();

String effectiveProjectKey = ComponentKeys.createEffectiveKey(projectKey, deprecatedBranch);
boolean wouldCurrentUserHaveScanPermission = permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(
dbSession, organization.getUuid(), userId, effectiveProjectKey, Qualifiers.PROJECT);
dbSession, organization.getUuid(), userId, componentKey.getDbKey(), Qualifiers.PROJECT);
if (!wouldCurrentUserHaveScanPermission) {
throw insufficientPrivilegesException();
}
@@ -150,13 +170,13 @@ public class ReportSubmitter {

NewComponent newProject = newComponentBuilder()
.setOrganizationUuid(organization.getUuid())
.setKey(projectKey)
.setName(defaultIfBlank(projectName, projectKey))
.setDeprecatedBranch(deprecatedBranch)
.setKey(componentKey.getKey())
.setName(defaultIfBlank(projectName, componentKey.getKey()))
.setDeprecatedBranch(componentKey.getDeprecatedBranchName().orElse(null))
.setQualifier(Qualifiers.PROJECT)
.setPrivate(newProjectPrivate)
.build();
return componentUpdater.create(dbSession, newProject, userId);
return componentUpdater.createWithoutCommit(dbSession, newProject, userId);
}

private CeTask submitReport(DbSession dbSession, InputStream reportInput, ComponentDto project, Map<String, String> characteristics) {
@@ -167,9 +187,10 @@ public class ReportSubmitter {
dbSession.commit();

submit.setType(CeTaskTypes.REPORT);
submit.setComponentUuid(project.uuid());
submit.setComponent(CeTaskSubmit.Component.fromDto(project));
submit.setSubmitterUuid(userSession.getUuid());
submit.setCharacteristics(characteristics);
return queue.submit(submit.build());
}

}

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java View File

@@ -237,9 +237,9 @@ public class ActivityAction implements CeWsAction {

String componentQuery = request.getQ();
if (component != null) {
query.setComponentUuid(component.uuid());
query.setMainComponentUuid(component.uuid());
} else if (componentQuery != null) {
query.setComponentUuids(loadComponents(dbSession, componentQuery).stream()
query.setMainComponentUuids(loadComponents(dbSession, componentQuery).stream()
.map(ComponentDto::uuid)
.collect(toList()));
}

+ 3
- 3
server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityStatusAction.java View File

@@ -83,9 +83,9 @@ public class ActivityStatusAction implements CeWsAction {
Optional<ComponentDto> component = searchComponent(dbSession, request);
String componentUuid = component.isPresent() ? component.get().uuid() : null;
checkPermissions(component);
int pendingCount = dbClient.ceQueueDao().countByStatusAndComponentUuid(dbSession, CeQueueDto.Status.PENDING, componentUuid);
int inProgressCount = dbClient.ceQueueDao().countByStatusAndComponentUuid(dbSession, CeQueueDto.Status.IN_PROGRESS, componentUuid);
int failingCount = dbClient.ceActivityDao().countLastByStatusAndComponentUuid(dbSession, CeActivityDto.Status.FAILED, componentUuid);
int pendingCount = dbClient.ceQueueDao().countByStatusAndMainComponentUuid(dbSession, CeQueueDto.Status.PENDING, componentUuid);
int inProgressCount = dbClient.ceQueueDao().countByStatusAndMainComponentUuid(dbSession, CeQueueDto.Status.IN_PROGRESS, componentUuid);
int failingCount = dbClient.ceActivityDao().countLastByStatusAndMainComponentUuid(dbSession, CeActivityDto.Status.FAILED, componentUuid);

return ActivityStatusWsResponse.newBuilder()
.setPending(pendingCount)

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/ce/ws/ComponentAction.java View File

@@ -88,9 +88,9 @@ public class ComponentAction implements CeWsAction {
try (DbSession dbSession = dbClient.openSession(false)) {
ComponentDto component = loadComponent(dbSession, wsRequest);
userSession.checkComponentPermission(UserRole.USER, component);
List<CeQueueDto> queueDtos = dbClient.ceQueueDao().selectByComponentUuid(dbSession, component.uuid());
List<CeQueueDto> queueDtos = dbClient.ceQueueDao().selectByMainComponentUuid(dbSession, component.uuid());
CeTaskQuery activityQuery = new CeTaskQuery()
.setComponentUuid(component.uuid())
.setMainComponentUuid(component.uuid())
.setOnlyCurrents(true);
List<CeActivityDto> activityDtos = dbClient.ceActivityDao().selectByQuery(dbSession, activityQuery, forPage(1).andSize(1));


+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/ce/ws/SubmitAction.java View File

@@ -121,7 +121,7 @@ public class SubmitAction implements CeWsAction {
CeTask task = reportSubmitter.submit(organizationKey, projectKey, deprecatedBranch, projectName, characteristics, report);
Ce.SubmitResponse submitResponse = Ce.SubmitResponse.newBuilder()
.setTaskId(task.getUuid())
.setProjectId(task.getComponentUuid())
.setProjectId(task.getComponent().get().getUuid())
.build();
WsUtils.writeProtobuf(submitResponse, wsRequest, wsResponse);
}

+ 2
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java View File

@@ -128,6 +128,7 @@ import org.sonar.server.platform.ws.DbMigrationStatusAction;
import org.sonar.server.platform.ws.HealthActionModule;
import org.sonar.server.platform.ws.L10nWs;
import org.sonar.server.platform.ws.LogsAction;
import org.sonar.server.platform.ws.MigrateDataAction;
import org.sonar.server.platform.ws.MigrateDbAction;
import org.sonar.server.platform.ws.PingAction;
import org.sonar.server.platform.ws.RestartAction;
@@ -512,6 +513,7 @@ public class PlatformLevel4 extends PlatformLevel {
UpgradesAction.class,
StatusAction.class,
MigrateDbAction.class,
MigrateDataAction.class,
LogsAction.class,
ChangeLogLevelAction.class,
DbMigrationStatusAction.class,

+ 65
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/ws/MigrateDataAction.java View File

@@ -0,0 +1,65 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.ws;

import org.sonar.api.config.Configuration;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.DbClient;
import org.sonar.server.platform.db.migration.version.v74.PopulateTmpColumnsToCeActivity;
import org.sonar.server.platform.db.migration.version.v74.PopulateTmpColumnsToCeQueue;
import org.sonar.server.platform.db.migration.version.v74.PopulateTmpLastKeyColumnsToCeActivity;
import org.sonar.server.user.UserSession;

public class MigrateDataAction implements SystemWsAction {
private static final Logger LOG = Loggers.get(MigrateDataAction.class);

private final UserSession userSession;
private final DbClient dbClient;

public MigrateDataAction(UserSession userSession, DbClient dbClient) {
this.userSession = userSession;
this.dbClient = dbClient;
}

@Override
public void define(WebService.NewController controller) {
controller.createAction("migrate_data")
.setPost(true)
.setHandler(this);
}

@Override
public void handle(Request request, Response response) throws Exception {
userSession.isSystemAdministrator();

Configuration emptyConfiguration = new MapSettings().asConfig();
new PopulateTmpColumnsToCeQueue(dbClient.getDatabase()).execute();
new PopulateTmpColumnsToCeActivity(dbClient.getDatabase(), emptyConfiguration).execute();
new PopulateTmpLastKeyColumnsToCeActivity(dbClient.getDatabase(), emptyConfiguration).execute();
LOG.info("done");

response.noContent();
}
}

+ 2
- 1
server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java View File

@@ -126,7 +126,8 @@ public class UpdateVisibilityAction implements ProjectsWsAction {
}

private boolean noPendingTask(DbSession dbSession, ComponentDto rootComponent) {
return dbClient.ceQueueDao().selectByComponentUuid(dbSession, rootComponent.uuid()).isEmpty();
// FIXME this is probably broken in case a branch is passed to the WS
return dbClient.ceQueueDao().selectByMainComponentUuid(dbSession, rootComponent.uuid()).isEmpty();
}

private void updatePermissionsToPrivate(DbSession dbSession, ComponentDto component) {

+ 1
- 2
server/sonar-server/src/main/java/org/sonar/server/user/AbstractUserSession.java View File

@@ -39,7 +39,6 @@ import static org.apache.commons.lang.StringUtils.defaultString;

public abstract class AbstractUserSession implements UserSession {
private static final String INSUFFICIENT_PRIVILEGES_MESSAGE = "Insufficient privileges";
private static final ForbiddenException INSUFFICIENT_PRIVILEGES_EXCEPTION = new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE);
private static final String AUTHENTICATION_IS_REQUIRED_MESSAGE = "Authentication is required";

protected static Identity computeIdentity(UserDto userDto) {
@@ -191,7 +190,7 @@ public abstract class AbstractUserSession implements UserSession {
}

public static ForbiddenException insufficientPrivilegesException() {
return INSUFFICIENT_PRIVILEGES_EXCEPTION;
return new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE);
}

@Override

+ 301
- 0
server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchReportSubmitterTest.java View File

@@ -0,0 +1,301 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.ce.queue;

import com.google.common.collect.ImmutableMap;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Random;
import java.util.function.BiConsumer;
import java.util.stream.IntStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
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.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.ce.CeTaskTypes;
import org.sonar.db.component.BranchDto;
import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.user.UserDto;
import org.sonar.server.component.ComponentUpdater;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.favorite.FavoriteUpdater;
import org.sonar.server.permission.PermissionTemplateService;
import org.sonar.server.tester.UserSessionRule;

import static java.util.Collections.emptyMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.core.permission.GlobalPermissions.PROVISIONING;
import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.db.component.ComponentTesting.newBranchDto;
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;

/**
* Tests of {@link ReportSubmitter} when branch support is installed.
*/
@RunWith(DataProviderRunner.class)
public class BranchReportSubmitterTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public DbTester db = DbTester.create(System2.INSTANCE);

private CeQueue queue = mock(CeQueueImpl.class);
private ComponentUpdater componentUpdater = mock(ComponentUpdater.class);
private PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class);
private FavoriteUpdater favoriteUpdater = mock(FavoriteUpdater.class);
private BranchSupportDelegate branchSupportDelegate = mock(BranchSupportDelegate.class);
private BranchSupport branchSupport = spy(new BranchSupport(branchSupportDelegate));

private ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), branchSupport);

@Test
public void submit_does_not_use_delegate_if_characteristics_are_empty() {
OrganizationDto organization = db.organizations().insert();
ComponentDto project = db.components().insertPrivateProject(organization);
UserDto user = db.users().insertUser();
userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
mockSuccessfulPrepareSubmitCall();
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);

underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), emptyMap(), reportInput);

verifyZeroInteractions(branchSupportDelegate);
}

@Test
public void submit_a_report_on_existing_branch() {
OrganizationDto organization = db.organizations().insert();
ComponentDto project = db.components().insertPrivateProject(organization);
ComponentDto branch = db.components().insertProjectBranch(project);
UserDto user = db.users().insertUser();
userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
Map<String, String> randomCharacteristics = randomNonEmptyMap();
BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(branch);
when(branchSupportDelegate.createComponentKey(project.getDbKey(), randomCharacteristics))
.thenReturn(componentKey);
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
String taskUuid = mockSuccessfulPrepareSubmitCall();

underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), randomCharacteristics, reportInput);

verifyZeroInteractions(permissionTemplateService);
verifyZeroInteractions(favoriteUpdater);
verify(branchSupport, times(0)).createBranchComponent(any(), any(), any(), any());
verify(branchSupportDelegate).createComponentKey(project.getDbKey(), randomCharacteristics);
verify(branchSupportDelegate, times(0)).createBranchComponent(any(), any(), any(), any());
verifyNoMoreInteractions(branchSupportDelegate);
verifyQueueSubmit(project, branch, user, randomCharacteristics, taskUuid);
}

@Test
public void submit_a_report_on_missing_branch_but_existing_project() {
OrganizationDto organization = db.organizations().insert();
ComponentDto nonExistingProject = db.components().insertPrivateProject(organization);
UserDto user = db.users().insertUser();
userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, nonExistingProject);
Map<String, String> randomCharacteristics = randomNonEmptyMap();
ComponentDto createdBranch = createButDoNotInsertBranch(nonExistingProject);
BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(createdBranch);
when(branchSupportDelegate.createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics))
.thenReturn(componentKey);
when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject)))
.thenReturn(createdBranch);
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
String taskUuid = mockSuccessfulPrepareSubmitCall();

underTest.submit(organization.getKey(), nonExistingProject.getDbKey(), null, nonExistingProject.name(), randomCharacteristics, reportInput);

verifyZeroInteractions(permissionTemplateService);
verifyZeroInteractions(favoriteUpdater);
verify(branchSupport).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject));
verify(branchSupportDelegate).createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics);
verify(branchSupportDelegate).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject));
verifyNoMoreInteractions(branchSupportDelegate);
verify(componentUpdater, times(0)).commitAndIndex(any(), any());
verifyQueueSubmit(nonExistingProject, createdBranch, user, randomCharacteristics, taskUuid);
}

@Test
public void submit_report_on_missing_branch_of_missing_project_provisions_project_when_org_PROVISION_PROJECT_perm() {
OrganizationDto organization = db.organizations().insert();
ComponentDto nonExistingProject = newPrivateProjectDto(organization);
UserDto user = db.users().insertUser();
userSession.logIn(user)
.addPermission(PROVISION_PROJECTS, organization)
.addPermission(SCAN, organization);

Map<String, String> randomCharacteristics = randomNonEmptyMap();
ComponentDto createdBranch = createButDoNotInsertBranch(nonExistingProject);
BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(createdBranch);
when(branchSupportDelegate.createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics))
.thenReturn(componentKey);
when(componentUpdater.createWithoutCommit(any(), any(), eq(user.getId()))).thenReturn(nonExistingProject);
when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject)))
.thenReturn(createdBranch);
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(organization.getUuid()), any(),
eq(nonExistingProject.getKey()), eq(Qualifiers.PROJECT)))
.thenReturn(true);
String taskUuid = mockSuccessfulPrepareSubmitCall();
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);

underTest.submit(organization.getKey(), nonExistingProject.getDbKey(), null, nonExistingProject.name(), randomCharacteristics, reportInput);

verify(branchSupport).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject));
verify(branchSupportDelegate).createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics);
verify(branchSupportDelegate).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject));
verifyNoMoreInteractions(branchSupportDelegate);
verifyQueueSubmit(nonExistingProject, createdBranch, user, randomCharacteristics, taskUuid);
verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(nonExistingProject));
}

@Test
public void submit_fails_if_branch_support_delegate_createComponentKey_throws_an_exception() {
OrganizationDto organization = db.organizations().insert();
ComponentDto project = db.components().insertPrivateProject(organization);
UserDto user = db.users().insertUser();
userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
Map<String, String> randomCharacteristics = randomNonEmptyMap();
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
RuntimeException expected = new RuntimeException("Faking an exception thrown by branchSupportDelegate");
when(branchSupportDelegate.createComponentKey(any(), any())).thenThrow(expected);

try {
underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), randomCharacteristics, reportInput);
fail("exception should have been thrown");
} catch (Exception e) {
assertThat(e).isSameAs(expected);
}
}

@DataProvider
public static Object[][] permissionsAllowingProjectProvisioning() {
BiConsumer<ComponentDto, UserSessionRule> noProjectPerm = (cpt, userSession) -> {
};
BiConsumer<OrganizationDto, UserSessionRule> noOrgPerm = (cpt, userSession) -> {
};
BiConsumer<ComponentDto, UserSessionRule> provisionOnProject = (cpt, userSession) -> userSession.addProjectPermission(PROVISIONING, cpt);
BiConsumer<OrganizationDto, UserSessionRule> provisionOnOrganization = (cpt, userSession) -> userSession.addPermission(PROVISION_PROJECTS, cpt);
return new Object[][] {
{provisionOnProject, noOrgPerm},
{noProjectPerm, provisionOnOrganization},
{provisionOnProject, provisionOnOrganization}
};
}

@Test
public void submit_report_on_missing_branch_of_missing_project_fails_with_ForbiddenException_if_only_scan_permission() {
OrganizationDto organization = db.organizations().insert();
ComponentDto nonExistingProject = newPrivateProjectDto(organization);
UserDto user = db.users().insertUser();
userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, nonExistingProject);
Map<String, String> randomCharacteristics = randomNonEmptyMap();
ComponentDto createdBranch = createButDoNotInsertBranch(nonExistingProject);
BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(createdBranch);
when(branchSupportDelegate.createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics))
.thenReturn(componentKey);
when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject)))
.thenReturn(createdBranch);
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);

expectedException.expect(ForbiddenException.class);
expectedException.expectMessage("Insufficient privileges");

underTest.submit(organization.getKey(), nonExistingProject.getDbKey(), null, nonExistingProject.name(), randomCharacteristics, reportInput);
}

private static ComponentDto createButDoNotInsertBranch(ComponentDto project) {
BranchType randomBranchType = BranchType.values()[new Random().nextInt(BranchType.values().length)];
BranchDto branchDto = newBranchDto(project.projectUuid(), randomBranchType);
return ComponentTesting.newProjectBranch(project, branchDto);
}

private String mockSuccessfulPrepareSubmitCall() {
String taskUuid = RandomStringUtils.randomAlphabetic(12);
when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(taskUuid));
return taskUuid;
}

private void verifyQueueSubmit(ComponentDto project, ComponentDto branch, UserDto user, Map<String, String> characteristics, String taskUuid) {
verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT)
&& submit.getComponent().filter(cpt -> cpt.getUuid().equals(branch.uuid()) && cpt.getMainComponentUuid().equals(project.uuid())).isPresent()
&& submit.getSubmitterUuid().equals(user.getUuid())
&& submit.getCharacteristics().equals(characteristics)
&& submit.getUuid().equals(taskUuid)));
}

private static BranchSupport.ComponentKey createComponentKeyOfBranch(ComponentDto branch) {
BranchSupport.ComponentKey mainComponentKey = mockComponentKey(branch.getKey(), branch.getKey());
when(mainComponentKey.getMainBranchComponentKey()).thenReturn(mainComponentKey);

BranchSupport.ComponentKey componentKey = mockComponentKey(branch.getKey(), branch.getDbKey());
when(componentKey.getMainBranchComponentKey()).thenReturn(mainComponentKey);

return componentKey;
}

private static BranchSupport.ComponentKey mockComponentKey(String key, String dbKey) {
BranchSupport.ComponentKey componentKey = mock(BranchSupport.ComponentKey.class);
when(componentKey.isDeprecatedBranch()).thenReturn(false);
when(componentKey.isMainBranch()).thenReturn(key.equals(dbKey));
when(componentKey.getKey()).thenReturn(key);
when(componentKey.getDbKey()).thenReturn(dbKey);
return componentKey;
}

private static ImmutableMap<String, String> randomNonEmptyMap() {
return IntStream.range(0, 1 + new Random().nextInt(5))
.boxed()
.collect(uniqueIndex(i -> "key_" + i, i -> "val_" + i));
}

}

+ 166
- 0
server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchSupportTest.java View File

@@ -0,0 +1,166 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.ce.queue;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Collections;
import java.util.Map;
import java.util.Random;
import java.util.stream.IntStream;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.server.ce.queue.BranchSupport.ComponentKey;

import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;

@RunWith(DataProviderRunner.class)
public class BranchSupportTest {
private static final Map<String, String> NO_CHARACTERISTICS = Collections.emptyMap();

@Rule
public ExpectedException expectedException = ExpectedException.none();

private BranchSupportDelegate branchSupportDelegate = mock(BranchSupportDelegate.class);
private BranchSupport underTestNoBranch = new BranchSupport();
private BranchSupport underTestWithBranch = new BranchSupport(branchSupportDelegate);

@Test
public void createComponentKey_of_main_branch() {
String projectKey = randomAlphanumeric(12);

ComponentKey componentKey = underTestNoBranch.createComponentKey(projectKey, null, NO_CHARACTERISTICS);

assertThat(componentKey)
.isEqualTo(underTestWithBranch.createComponentKey(projectKey, null, NO_CHARACTERISTICS));
assertThat(componentKey.getKey()).isEqualTo(projectKey);
assertThat(componentKey.getDbKey()).isEqualTo(projectKey);
assertThat(componentKey.isMainBranch()).isTrue();
assertThat(componentKey.isDeprecatedBranch()).isFalse();
assertThat(componentKey.getMainBranchComponentKey()).isSameAs(componentKey);
assertThat(componentKey.getBranchName()).isEmpty();
assertThat(componentKey.getPullRequestKey()).isEmpty();
}

@Test
public void createComponentKey_of_deprecated_branch() {
String projectKey = randomAlphanumeric(12);
String deprecatedBranchName = randomAlphanumeric(12);

ComponentKey componentKey = underTestNoBranch.createComponentKey(projectKey, deprecatedBranchName, NO_CHARACTERISTICS);

assertThat(componentKey)
.isEqualTo(underTestWithBranch.createComponentKey(projectKey, deprecatedBranchName, NO_CHARACTERISTICS));
assertThat(componentKey.getKey()).isEqualTo(projectKey);
assertThat(componentKey.getDbKey()).isEqualTo(projectKey + ":" + deprecatedBranchName);
assertThat(componentKey.isMainBranch()).isFalse();
assertThat(componentKey.isDeprecatedBranch()).isTrue();
assertThat(componentKey.getMainBranchComponentKey()).isSameAs(componentKey);
assertThat(componentKey.getBranchName()).isEmpty();
assertThat(componentKey.getPullRequestKey()).isEmpty();
}

@Test
@UseDataProvider("nullOrNonEmpty")
public void createComponentKey_with_ISE_if_characteristics_is_not_empty_and_delegate_is_null(String deprecatedBranchName) {
String projectKey = randomAlphanumeric(12);
Map<String, String> nonEmptyMap = newRandomNonEmptyMap();

expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Current edition does not support branch feature");
underTestNoBranch.createComponentKey(projectKey, deprecatedBranchName, nonEmptyMap);
}

@Test
public void createComponentKey_fails_with_IAE_if_characteristics_is_not_empty_and_deprecatedBranchName_is_non_null() {
String projectKey = randomAlphanumeric(12);
String deprecatedBranchName = randomAlphanumeric(13);
Map<String, String> nonEmptyMap = newRandomNonEmptyMap();

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Deprecated branch feature can't be used at the same time as new branch support");
underTestWithBranch.createComponentKey(projectKey, deprecatedBranchName, nonEmptyMap);
}

@Test
public void createComponentKey_delegates_to_delegate_if_characteristics_is_not_empty() {
String projectKey = randomAlphanumeric(12);
Map<String, String> nonEmptyMap = newRandomNonEmptyMap();
ComponentKey expected = mock(ComponentKey.class);
when(branchSupportDelegate.createComponentKey(projectKey, nonEmptyMap)).thenReturn(expected);

ComponentKey componentKey = underTestWithBranch.createComponentKey(projectKey, null, nonEmptyMap);

assertThat(componentKey).isSameAs(expected);
}

@Test
public void createBranchComponent_fails_with_ISE_if_delegate_is_null() {
DbSession dbSession = mock(DbSession.class);
ComponentKey componentKey = mock(ComponentKey.class);
OrganizationDto organization = new OrganizationDto();
ComponentDto mainComponentDto = new ComponentDto();

expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Current edition does not support branch feature");
underTestNoBranch.createBranchComponent(dbSession, componentKey, organization, mainComponentDto);
}

@Test
public void createBranchComponent_delegates_to_delegate() {
DbSession dbSession = mock(DbSession.class);
ComponentKey componentKey = mock(ComponentKey.class);
OrganizationDto organization = new OrganizationDto();
ComponentDto mainComponentDto = new ComponentDto();
ComponentDto expected = new ComponentDto();
when(branchSupportDelegate.createBranchComponent(dbSession, componentKey, organization, mainComponentDto))
.thenReturn(expected);

ComponentDto dto = underTestWithBranch.createBranchComponent(dbSession, componentKey, organization, mainComponentDto);

assertThat(dto).isSameAs(expected);
}

@DataProvider
public static Object[][] nullOrNonEmpty() {
return new Object[][] {
{null},
{randomAlphabetic(5)},
};
}

private static Map<String, String> newRandomNonEmptyMap() {
return IntStream.range(0, 1 + new Random().nextInt(10)).boxed().collect(uniqueIndex(i -> "key_" + i, i -> "val_" + i));
}
}

+ 52
- 27
server/sonar-server/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java View File

@@ -19,15 +19,16 @@
*/
package org.sonar.server.ce.queue;

import com.google.common.collect.ImmutableMap;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Random;
import java.util.stream.IntStream;
import org.apache.commons.io.IOUtils;
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;
@@ -36,7 +37,6 @@ import org.sonar.ce.queue.CeTaskSubmit;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.ce.CeTaskCharacteristicDto;
import org.sonar.db.ce.CeTaskTypes;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
@@ -55,8 +55,8 @@ import org.sonar.server.tester.UserSessionRule;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyMap;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
@@ -67,6 +67,7 @@ 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.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS;
@@ -92,8 +93,9 @@ public class ReportSubmitterTest {
private ComponentUpdater componentUpdater = mock(ComponentUpdater.class);
private PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class);
private FavoriteUpdater favoriteUpdater = mock(FavoriteUpdater.class);
private BranchSupport ossEditionBranchSupport = new BranchSupport();

private ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient());
private ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), ossEditionBranchSupport);

@Before
public void setUp() throws Exception {
@@ -102,25 +104,45 @@ public class ReportSubmitterTest {
}

@Test
public void submit_stores_report() {
public void submit_with_characteristics_fails_with_ISE_when_no_branch_support_delegate() {
userSession
.addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid())
.addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());

mockSuccessfulPrepareSubmitCall();
ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setDbKey(PROJECT_KEY);
mockSuccessfulPrepareSubmitCall();
when(componentUpdater.create(any(), any(), any())).thenReturn(project);
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(defaultOrganizationUuid), any(), eq(PROJECT_KEY),
eq(Qualifiers.PROJECT)))
.thenReturn(true);
Map<String, String> nonEmptyCharacteristics = IntStream.range(0, 1 + new Random().nextInt(5))
.boxed()
.collect(uniqueIndex(i -> randomAlphabetic(i + 10), i -> randomAlphabetic(i + 20)));
InputStream reportInput = IOUtils.toInputStream("{binary}", UTF_8);

expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Current edition does not support branch feature");

underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, nonEmptyCharacteristics, reportInput);
}

@Test
public void submit_stores_report() {
userSession
.addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid())
.addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());

ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setDbKey(PROJECT_KEY);
mockSuccessfulPrepareSubmitCall();
when(componentUpdater.createWithoutCommit(any(), any(), any())).thenReturn(project);
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(defaultOrganizationUuid), any(), eq(PROJECT_KEY),
eq(Qualifiers.PROJECT)))
.thenReturn(true);

Map<String, String> taskCharacteristics = ImmutableMap.of(CeTaskCharacteristicDto.PULL_REQUEST, "123");
underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, taskCharacteristics, IOUtils.toInputStream("{binary}", UTF_8));
underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}", UTF_8));

ArgumentCaptor<CeTaskSubmit> submittedTask = ArgumentCaptor.forClass(CeTaskSubmit.class);
verify(queue).submit(submittedTask.capture());
assertThat(submittedTask.getValue().getCharacteristics())
.containsExactly(entry("pullRequest", "123"));
verifyReportIsPersisted(TASK_UUID);
verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(project));
}

@Test
@@ -128,7 +150,6 @@ public class ReportSubmitterTest {
ComponentDto project = db.components().insertPrivateProject(db.getDefaultOrganization());
UserDto user = db.users().insertUser();
userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);

mockSuccessfulPrepareSubmitCall();

underTest.submit(defaultOrganizationKey, project.getDbKey(), null, project.name(), emptyMap(), IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8));
@@ -137,7 +158,7 @@ public class ReportSubmitterTest {
verifyZeroInteractions(permissionTemplateService);
verifyZeroInteractions(favoriteUpdater);
verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT)
&& submit.getComponentUuid().equals(project.uuid())
&& submit.getComponent().filter(cpt -> cpt.getUuid().equals(project.uuid()) && cpt.getMainComponentUuid().equals(project.uuid())).isPresent()
&& submit.getSubmitterUuid().equals(user.getUuid())
&& submit.getUuid().equals(TASK_UUID)));
}
@@ -151,7 +172,7 @@ public class ReportSubmitterTest {

mockSuccessfulPrepareSubmitCall();
ComponentDto createdProject = newPrivateProjectDto(organization, PROJECT_UUID).setDbKey(PROJECT_KEY);
when(componentUpdater.create(any(), any(), isNull())).thenReturn(createdProject);
when(componentUpdater.createWithoutCommit(any(), any(), isNull())).thenReturn(createdProject);
when(
permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(organization.getUuid()), any(), eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
.thenReturn(true);
@@ -160,7 +181,10 @@ public class ReportSubmitterTest {
underTest.submit(organization.getKey(), PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));

verifyReportIsPersisted(TASK_UUID);
verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT) && submit.getComponentUuid().equals(PROJECT_UUID) && submit.getUuid().equals(TASK_UUID)));
verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT)
&& submit.getComponent().filter(cpt -> cpt.getUuid().equals(PROJECT_UUID) && cpt.getMainComponentUuid().equals(PROJECT_UUID)).isPresent()
&& submit.getUuid().equals(TASK_UUID)));
verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(createdProject));
}

@Test
@@ -169,17 +193,18 @@ public class ReportSubmitterTest {
.addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid())
.addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());

mockSuccessfulPrepareSubmitCall();
ComponentDto createdProject = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setDbKey(PROJECT_KEY);
when(componentUpdater.create(any(), any(), isNull())).thenReturn(createdProject);
when(componentUpdater.createWithoutCommit(any(), any(), isNull())).thenReturn(createdProject);
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(defaultOrganizationUuid), any(),
eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
.thenReturn(true);
when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(), eq(defaultOrganizationUuid), any())).thenReturn(false);
mockSuccessfulPrepareSubmitCall();

underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));

verifyZeroInteractions(favoriteUpdater);
verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(createdProject));
}

@Test
@@ -188,9 +213,9 @@ public class ReportSubmitterTest {
.addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid())
.addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());

mockSuccessfulPrepareSubmitCall();
ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setDbKey(PROJECT_KEY);
when(componentUpdater.create(any(), any(), any())).thenReturn(project);
mockSuccessfulPrepareSubmitCall();
when(componentUpdater.createWithoutCommit(any(), any(), any())).thenReturn(project);
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(defaultOrganizationUuid), any(),
eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
.thenReturn(true);
@@ -198,6 +223,7 @@ public class ReportSubmitterTest {
underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));

verify(queue).submit(any(CeTaskSubmit.class));
verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(project));
}

@Test
@@ -205,7 +231,6 @@ public class ReportSubmitterTest {
OrganizationDto org = db.organizations().insert();
ComponentDto project = db.components().insertPrivateProject(org);
userSession.addPermission(SCAN, org);

mockSuccessfulPrepareSubmitCall();

underTest.submit(org.getKey(), project.getDbKey(), null, project.name(), emptyMap(), IOUtils.toInputStream("{binary}"));
@@ -217,7 +242,6 @@ public class ReportSubmitterTest {
public void submit_a_report_on_existing_project_with_project_scan_permission() {
ComponentDto project = db.components().insertPrivateProject(db.getDefaultOrganization());
userSession.addProjectPermission(SCAN_EXECUTION, project);

mockSuccessfulPrepareSubmitCall();

underTest.submit(defaultOrganizationKey, project.getDbKey(), null, project.name(), emptyMap(), IOUtils.toInputStream("{binary}"));
@@ -230,15 +254,16 @@ public class ReportSubmitterTest {
*/
@Test
public void project_branch_must_not_benefit_from_the_scan_permission_on_main_project() {
String branchName = "branchFoo";

ComponentDto mainProject = db.components().insertPrivateProject();
userSession.addProjectPermission(GlobalPermissions.SCAN_EXECUTION, mainProject);

// user does not have the "scan" permission on the branch, so it can't scan it
String branchName = "branchFoo";
ComponentDto branchProject = db.components().insertPrivateProject(p -> p.setDbKey(mainProject.getDbKey() + ":" + branchName));

expectedException.expect(ForbiddenException.class);
underTest.submit(defaultOrganizationKey, mainProject.getDbKey(), branchName, PROJECT_NAME,emptyMap(), IOUtils.toInputStream("{binary}"));
underTest.submit(defaultOrganizationKey, mainProject.getDbKey(), branchName, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
}

@Test
@@ -298,8 +323,8 @@ public class ReportSubmitterTest {

@Test
public void fail_with_forbidden_exception_on_new_project_when_only_project_scan_permission() {
userSession.addProjectPermission(SCAN_EXECUTION, ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID));
ComponentDto component = ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID);
userSession.addProjectPermission(SCAN_EXECUTION, component);
mockSuccessfulPrepareSubmitCall();
when(componentUpdater.create(any(DbSession.class), any(NewComponent.class), eq(null))).thenReturn(new ComponentDto().setUuid(PROJECT_UUID).setDbKey(PROJECT_KEY));


+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java View File

@@ -507,7 +507,7 @@ public class ActivityActionTest {
private CeQueueDto insertQueue(String taskUuid, @Nullable ComponentDto project, CeQueueDto.Status status) {
CeQueueDto queueDto = new CeQueueDto();
queueDto.setTaskType(CeTaskTypes.REPORT);
queueDto.setComponentUuid(project == null ? null : project.uuid());
queueDto.setComponent(project);
queueDto.setUuid(taskUuid);
queueDto.setStatus(status);
db.getDbClient().ceQueueDao().insert(db.getSession(), queueDto);
@@ -522,7 +522,7 @@ public class ActivityActionTest {
private CeActivityDto insertActivity(String taskUuid, ComponentDto project, Status status, @Nullable SnapshotDto analysis) {
CeQueueDto queueDto = new CeQueueDto();
queueDto.setTaskType(CeTaskTypes.REPORT);
queueDto.setComponentUuid(project.uuid());
queueDto.setComponent(project);
queueDto.setUuid(taskUuid);
queueDto.setCreatedAt(EXECUTED_AT);
CeActivityDto activityDto = new CeActivityDto(queueDto);

+ 18
- 17
server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityStatusActionTest.java View File

@@ -45,9 +45,9 @@ import org.sonarqube.ws.Ce;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.db.ce.CeQueueTesting.newCeQueueDto;
import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
import static org.sonar.test.JsonAssert.assertJson;
import static org.sonar.server.ce.ws.CeWsParameters.DEPRECATED_PARAM_COMPONENT_KEY;
import static org.sonar.server.ce.ws.CeWsParameters.PARAM_COMPONENT_ID;
import static org.sonar.test.JsonAssert.assertJson;

public class ActivityStatusActionTest {

@@ -84,22 +84,23 @@ public class ActivityStatusActionTest {
String anotherProjectUuid = "another-project-uuid";
OrganizationDto organizationDto = db.organizations().insert();
ComponentDto project = newPrivateProjectDto(organizationDto, projectUuid);
ComponentDto anotherProject = newPrivateProjectDto(organizationDto, anotherProjectUuid);
db.components().insertComponent(project);
db.components().insertComponent(newPrivateProjectDto(organizationDto, anotherProjectUuid));
userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
// pending tasks returned
insertInQueue(CeQueueDto.Status.PENDING, projectUuid);
insertInQueue(CeQueueDto.Status.PENDING, projectUuid);
insertInQueue(CeQueueDto.Status.PENDING, project);
insertInQueue(CeQueueDto.Status.PENDING, project);
// other tasks not returned
insertInQueue(CeQueueDto.Status.IN_PROGRESS, projectUuid);
insertInQueue(CeQueueDto.Status.PENDING, anotherProjectUuid);
insertInQueue(CeQueueDto.Status.IN_PROGRESS, project);
insertInQueue(CeQueueDto.Status.PENDING, anotherProject);
insertInQueue(CeQueueDto.Status.PENDING, null);
// only one last activity for a given project
insertActivity(CeActivityDto.Status.SUCCESS, projectUuid);
insertActivity(CeActivityDto.Status.CANCELED, projectUuid);
insertActivity(CeActivityDto.Status.FAILED, projectUuid);
insertActivity(CeActivityDto.Status.FAILED, projectUuid);
insertActivity(CeActivityDto.Status.FAILED, anotherProjectUuid);
insertActivity(CeActivityDto.Status.SUCCESS, project);
insertActivity(CeActivityDto.Status.CANCELED, project);
insertActivity(CeActivityDto.Status.FAILED, project);
insertActivity(CeActivityDto.Status.FAILED, project);
insertActivity(CeActivityDto.Status.FAILED, anotherProject);

Ce.ActivityStatusWsResponse result = call(projectUuid);

@@ -159,18 +160,18 @@ public class ActivityStatusActionTest {
callByComponentKey(project.getDbKey());
}

private void insertInQueue(CeQueueDto.Status status, @Nullable String componentUuid) {
private void insertInQueue(CeQueueDto.Status status, @Nullable ComponentDto componentDto) {
dbClient.ceQueueDao().insert(dbSession, newCeQueueDto(Uuids.createFast())
.setStatus(status)
.setComponentUuid(componentUuid));
.setComponent(componentDto));
db.commit();
}

private void insertActivity(CeActivityDto.Status status, @Nullable String componentUuid) {
dbClient.ceActivityDao().insert(dbSession, new CeActivityDto(
newCeQueueDto(Uuids.createFast())
.setComponentUuid(componentUuid))
.setStatus(status));
private void insertActivity(CeActivityDto.Status status, @Nullable ComponentDto dto) {
CeQueueDto ceQueueDto = newCeQueueDto(Uuids.createFast());
ceQueueDto.setComponent(dto);
dbClient.ceActivityDao().insert(dbSession, new CeActivityDto(ceQueueDto)
.setStatus(status));
db.commit();
}


+ 13
- 7
server/sonar-server/src/test/java/org/sonar/server/ce/ws/CancelActionTest.java View File

@@ -62,7 +62,7 @@ public class CancelActionTest {
public void cancel_pending_task_on_project() {
logInAsSystemAdministrator();
ComponentDto project = db.components().insertPrivateProject();
CeQueueDto queue = createTaskSubmit(project.uuid());
CeQueueDto queue = createTaskSubmit(project);

tester.newRequest()
.setParam("id", queue.getUuid())
@@ -87,7 +87,7 @@ public class CancelActionTest {
public void cancel_pending_task_when_system_administer() {
logInAsSystemAdministrator();
ComponentDto project = db.components().insertPrivateProject();
CeQueueDto queue = createTaskSubmit(project.uuid());
CeQueueDto queue = createTaskSubmit(project);

tester.newRequest()
.setParam("id", queue.getUuid())
@@ -100,7 +100,7 @@ public class CancelActionTest {
public void cancel_pending_task_when_project_administer() {
ComponentDto project = db.components().insertPrivateProject();
userSession.addProjectPermission(UserRole.ADMIN, project);
CeQueueDto queue = createTaskSubmit(project.uuid());
CeQueueDto queue = createTaskSubmit(project);

tester.newRequest()
.setParam("id", queue.getUuid())
@@ -132,7 +132,7 @@ public class CancelActionTest {
public void throw_ForbiddenException_if_not_enough_permission_when_canceling_task_on_project() {
userSession.logIn().setNonSystemAdministrator();
ComponentDto project = db.components().insertPrivateProject();
CeQueueDto queue = createTaskSubmit(project.uuid());
CeQueueDto queue = createTaskSubmit(project);

expectedException.expect(ForbiddenException.class);
expectedException.expectMessage("Insufficient privileges");
@@ -158,7 +158,7 @@ public class CancelActionTest {
@Test
public void throw_ForbiddenException_if_not_enough_permission_when_canceling_task_when_project_does_not_exist() {
userSession.logIn().setNonSystemAdministrator();
CeQueueDto queue = createTaskSubmit("UNKNOWN");
CeQueueDto queue = createTaskSubmit(nonExistentComponentDot());

expectedException.expect(ForbiddenException.class);
expectedException.expectMessage("Insufficient privileges");
@@ -168,16 +168,22 @@ public class CancelActionTest {
.execute();
}

private static ComponentDto nonExistentComponentDot() {
return new ComponentDto().setUuid("does_not_exist").setProjectUuid("unknown");
}

private void logInAsSystemAdministrator() {
userSession.logIn().setSystemAdministrator();
}

private CeQueueDto createTaskSubmit(@Nullable String componentUuid) {
private CeQueueDto createTaskSubmit(@Nullable ComponentDto componentDto) {
CeTaskSubmit.Builder submission = queue.prepareSubmit()
.setType(CeTaskTypes.REPORT)
.setComponentUuid(componentUuid)
.setSubmitterUuid(null)
.setCharacteristics(emptyMap());
if (componentDto != null) {
submission.setComponent(CeTaskSubmit.Component.fromDto(componentDto));
}
CeTask task = queue.submit(submission.build());
return db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
}

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/ce/ws/ComponentActionTest.java View File

@@ -281,7 +281,7 @@ public class ComponentActionTest {
private CeQueueDto insertQueue(String taskUuid, ComponentDto component, CeQueueDto.Status status) {
CeQueueDto queueDto = new CeQueueDto();
queueDto.setTaskType(CeTaskTypes.REPORT);
queueDto.setComponentUuid(component.uuid());
queueDto.setComponent(component);
queueDto.setUuid(taskUuid);
queueDto.setStatus(status);
db.getDbClient().ceQueueDao().insert(db.getSession(), queueDto);
@@ -296,7 +296,7 @@ public class ComponentActionTest {
private CeActivityDto insertActivity(String taskUuid, ComponentDto component, CeActivityDto.Status status, @Nullable SnapshotDto analysis) {
CeQueueDto queueDto = new CeQueueDto();
queueDto.setTaskType(CeTaskTypes.REPORT);
queueDto.setComponentUuid(component.uuid());
queueDto.setComponent(component);
queueDto.setUuid(taskUuid);
CeActivityDto activityDto = new CeActivityDto(queueDto);
activityDto.setStatus(status);

+ 6
- 3
server/sonar-server/src/test/java/org/sonar/server/ce/ws/SubmitActionTest.java View File

@@ -51,11 +51,14 @@ import static org.mockito.Mockito.when;

public class SubmitActionTest {

private static final String PROJECT_UUID = "PROJECT_1";
private static final CeTask.Component COMPONENT = new CeTask.Component(PROJECT_UUID, "KEY_1", "NAME_1");
private static final CeTask A_CE_TASK = new CeTask.Builder()
.setOrganizationUuid("org1")
.setUuid("TASK_1")
.setType(CeTaskTypes.REPORT)
.setComponentUuid("PROJECT_1")
.setComponent(COMPONENT)
.setMainComponent(COMPONENT)
.setSubmitterUuid("robert")
.build();

@@ -89,7 +92,7 @@ public class SubmitActionTest {
anyMap(), any());

assertThat(submitResponse.getTaskId()).isEqualTo("TASK_1");
assertThat(submitResponse.getProjectId()).isEqualTo("PROJECT_1");
assertThat(submitResponse.getProjectId()).isEqualTo(PROJECT_UUID);
}

@Test
@@ -132,7 +135,7 @@ public class SubmitActionTest {
anyMap(), any());

assertThat(submitResponse.getTaskId()).isEqualTo("TASK_1");
assertThat(submitResponse.getProjectId()).isEqualTo("PROJECT_1");
assertThat(submitResponse.getProjectId()).isEqualTo(PROJECT_UUID);
}

@Test

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/project/ws/UpdateVisibilityActionTest.java View File

@@ -266,7 +266,7 @@ public class UpdateVisibilityActionTest {
}

@Test
public void execute_throws_BadRequestException_if_specified_component_has_in_progress_tasks() {
public void execute_throws_BadRequestException_if_main_component_of_specified_component_has_in_progress_tasks() {
ComponentDto project = randomPublicOrPrivateProject();
IntStream.range(0, 1 + Math.abs(random.nextInt(5)))
.forEach(i -> insertInProgressTask(project));
@@ -734,7 +734,7 @@ public class UpdateVisibilityActionTest {
private void insertCeQueueDto(ComponentDto project, CeQueueDto.Status status) {
dbClient.ceQueueDao().insert(dbTester.getSession(), new CeQueueDto()
.setUuid("pending" + counter++)
.setComponentUuid(project.uuid())
.setComponent(project)
.setTaskType("foo")
.setStatus(status));
dbTester.commit();

Loading…
Cancel
Save