]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9616 add branch to CE API and webhook JSON payload
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Mon, 31 Jul 2017 15:46:48 +0000 (17:46 +0200)
committerJanos Gyerik <janos.gyerik@sonarsource.com>
Tue, 12 Sep 2017 08:52:52 +0000 (10:52 +0200)
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/analysis/Branch.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/BranchImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/component/MainBranchImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadFactoryImpl.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutorTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportPersistComponentsStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPayloadFactoryImplTest.java
sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/Branch.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/PostProjectAnalysisTask.java
sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/PostProjectAnalysisTaskTester.java

index 7695ae14653c3c05862fafad943072b194a8852e..55b628585e0c112a7dda2c2045c8bf1fc2486bd4 100644 (file)
@@ -31,6 +31,12 @@ public interface Branch extends ComponentKeyGenerator {
 
   boolean isMain();
 
+  /**
+   * Whether branch has been created through the legacy configuration
+   * (scanner parameter sonar.branch) or not
+   */
+  boolean isLegacyFeature();
+
   /**
    * Name can be empty when it's not known on the main branch
    * (regular analysis without the branch parameters)
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/BranchImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/BranchImpl.java
new file mode 100644 (file)
index 0000000..5699dd8
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.task.projectanalysis.api.posttask;
+
+import java.util.Optional;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.ce.posttask.Branch;
+
+@Immutable
+public class BranchImpl implements Branch {
+  private final boolean isMain;
+  @Nullable
+  private final String name;
+  private final Type type;
+
+  public BranchImpl(boolean isMain, @Nullable String name, Type type) {
+    this.isMain = isMain;
+    this.name = name;
+    this.type = type;
+  }
+
+  @Override
+  public boolean isMain() {
+    return isMain;
+  }
+
+  @Override
+  public Optional<String> getName() {
+    return Optional.ofNullable(name);
+  }
+
+  @Override
+  public Type getType() {
+    return type;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("Branch{");
+    sb.append("isMain=").append(isMain);
+    sb.append(", name='").append(name).append('\'');
+    sb.append(", type=").append(type);
+    sb.append('}');
+    return sb.toString();
+  }
+}
index 27904e164bb54f6fe2dd75fc4c819be3901dc754..254547e3063f1c803a8058d804ef884a3074a1f3 100644 (file)
@@ -26,6 +26,7 @@ import java.util.Map;
 import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import org.sonar.api.ce.posttask.Branch;
 import org.sonar.api.ce.posttask.CeTask;
 import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
 import org.sonar.api.ce.posttask.Project;
@@ -111,13 +112,15 @@ public class PostProjectAnalysisTasksExecutor implements ComputationStepExecutor
 
   private ProjectAnalysis createProjectAnalysis(CeTask.Status status) {
     Long analysisDate = getAnalysisDate();
+
     return new ProjectAnalysis(
       new CeTaskImpl(this.ceTask.getUuid(), status),
       createProject(this.ceTask),
       analysisDate,
       analysisDate == null ? system2.now() : analysisDate,
       ScannerContextImpl.from(reportReader.readContextProperties()),
-      status == SUCCESS ? createQualityGate(this.qualityGateHolder) : null);
+      status == SUCCESS ? createQualityGate() : null,
+      createBranch());
   }
 
   private static Project createProject(org.sonar.ce.queue.CeTask ceTask) {
@@ -136,8 +139,8 @@ public class PostProjectAnalysisTasksExecutor implements ComputationStepExecutor
   }
 
   @CheckForNull
-  private QualityGateImpl createQualityGate(QualityGateHolder qualityGateHolder) {
-    Optional<org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGate> qualityGateOptional = qualityGateHolder.getQualityGate();
+  private QualityGateImpl createQualityGate() {
+    Optional<org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGate> qualityGateOptional = this.qualityGateHolder.getQualityGate();
     if (qualityGateOptional.isPresent()) {
       org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGate qualityGate = qualityGateOptional.get();
 
@@ -150,6 +153,16 @@ public class PostProjectAnalysisTasksExecutor implements ComputationStepExecutor
     return null;
   }
 
+  @CheckForNull
+  private BranchImpl createBranch() {
+    java.util.Optional<org.sonar.server.computation.task.projectanalysis.analysis.Branch> analysisBranchOpt = analysisMetadataHolder.getBranch();
+    if (analysisBranchOpt.isPresent() && !analysisBranchOpt.get().isLegacyFeature()) {
+      org.sonar.server.computation.task.projectanalysis.analysis.Branch branch = analysisBranchOpt.get();
+      return new BranchImpl(branch.isMain(), branch.getName().orElse(null), Branch.Type.valueOf(branch.getType().name()));
+    }
+    return null;
+  }
+
   private static QualityGate.Status convert(QualityGateStatus status) {
     switch (status) {
       case OK:
@@ -174,22 +187,25 @@ public class PostProjectAnalysisTasksExecutor implements ComputationStepExecutor
   private static class ProjectAnalysis implements PostProjectAnalysisTask.ProjectAnalysis {
     private final CeTask ceTask;
     private final Project project;
-    @CheckForNull
+    @Nullable
     private final Long analysisDate;
     private final long date;
     private final ScannerContext scannerContext;
-    @CheckForNull
+    @Nullable
     private final QualityGate qualityGate;
+    @Nullable
+    private final Branch branch;
 
     private ProjectAnalysis(CeTask ceTask, Project project,
       @Nullable Long analysisDate, long date,
-      ScannerContext scannerContext, @Nullable QualityGate qualityGate) {
+      ScannerContext scannerContext, @Nullable QualityGate qualityGate, @Nullable Branch branch) {
       this.ceTask = requireNonNull(ceTask, "ceTask can not be null");
       this.project = requireNonNull(project, "project can not be null");
       this.analysisDate = analysisDate;
       this.date = date;
       this.scannerContext = requireNonNull(scannerContext, "scannerContext can not be null");
       this.qualityGate = qualityGate;
+      this.branch = branch;
     }
 
     @Override
@@ -202,6 +218,11 @@ public class PostProjectAnalysisTasksExecutor implements ComputationStepExecutor
       return project;
     }
 
+    @Override
+    public java.util.Optional<Branch> getBranch() {
+      return java.util.Optional.ofNullable(branch);
+    }
+
     @Override
     @CheckForNull
     public QualityGate getQualityGate() {
index 6893a43d7d9472b338c08ecf3e8ec59c11216aa9..87904bdafe64c1410fc2bc18754635ade9ecdb73 100644 (file)
@@ -60,6 +60,11 @@ public class MainBranchImpl implements Branch {
     return true;
   }
 
+  @Override
+  public boolean isLegacyFeature() {
+    return true;
+  }
+
   @Override
   public Optional<String> getName() {
     return Optional.ofNullable(branchName);
index 4f8ae6a03bd272bb5399d0cfa4399ab7694385d3..e465cdd778a13639d32c76e963986268bdc44865 100644 (file)
@@ -23,6 +23,7 @@ import java.io.StringWriter;
 import java.io.Writer;
 import javax.annotation.Nullable;
 import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.ce.posttask.Branch;
 import org.sonar.api.ce.posttask.CeTask;
 import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
 import org.sonar.api.ce.posttask.Project;
@@ -51,6 +52,7 @@ public class WebhookPayloadFactoryImpl implements WebhookPayloadFactory {
       writeTask(writer, analysis.getCeTask());
       analysis.getAnalysisDate().ifPresent(date -> writer.propDateTime("analysedAt", date));
       writeProject(analysis, writer, analysis.getProject());
+      analysis.getBranch().ifPresent(b -> writeBranch(writer, b));
       writeQualityGate(writer, analysis.getQualityGate());
       writeAnalysisProperties(writer, analysis.getScannerContext());
       writer.endObject().close();
@@ -88,6 +90,16 @@ public class WebhookPayloadFactoryImpl implements WebhookPayloadFactory {
       .endObject();
   }
 
+  private static void writeBranch(JsonWriter writer, Branch branch) {
+    writer
+      .name("branch")
+      .beginObject()
+      .prop("name", branch.getName().orElse(null))
+      .prop("type", branch.getType().name())
+      .prop("isMain", branch.isMain())
+      .endObject();
+  }
+
   private static void writeQualityGate(JsonWriter writer, @Nullable QualityGate gate) {
     if (gate != null) {
       writer
index cc8cd7706df44d7eaa164d561b29aa925530eabc..7fa5223a74950c1b68fe446a8e764a4c9f96ca72 100644 (file)
@@ -25,6 +25,8 @@ import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
+import javax.annotation.Nullable;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -35,9 +37,12 @@ import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
 import org.sonar.api.ce.posttask.Project;
 import org.sonar.api.utils.System2;
 import org.sonar.ce.queue.CeTask;
+import org.sonar.db.component.BranchType;
 import org.sonar.scanner.protocol.output.ScannerReport;
 import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
 import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReaderRule;
+import org.sonar.server.computation.task.projectanalysis.component.MainBranchImpl;
 import org.sonar.server.computation.task.projectanalysis.metric.Metric;
 import org.sonar.server.computation.task.projectanalysis.qualitygate.Condition;
 import org.sonar.server.computation.task.projectanalysis.qualitygate.ConditionStatus;
@@ -95,6 +100,7 @@ public class PostProjectAnalysisTasksExecutorTest {
     qualityGateStatusHolder.setStatus(QualityGateStatus.OK, ImmutableMap.of(
       CONDITION_1, ConditionStatus.create(ConditionStatus.EvaluationStatus.OK, "value"),
       CONDITION_2, ConditionStatus.NO_VALUE_STATUS));
+    analysisMetadataHolder.setBranch(null);
   }
 
   @Test
@@ -161,7 +167,7 @@ public class PostProjectAnalysisTasksExecutorTest {
 
   @Test
   public void date_comes_from_AnalysisMetadataHolder() {
-    analysisMetadataHolder.setAnalysisDate(8465132498L);
+    analysisMetadataHolder.setAnalysisDate(8_465_132_498L);
 
     underTest.finished(true);
 
@@ -204,6 +210,70 @@ public class PostProjectAnalysisTasksExecutorTest {
     assertThat(projectAnalysisArgumentCaptor.getValue().getAnalysisDate()).isEmpty();
   }
 
+  @Test
+  public void branch_is_empty_when_not_set_in_AnalysisMetadataHolder() {
+    underTest.finished(false);
+
+    verify(postProjectAnalysisTask).finished(projectAnalysisArgumentCaptor.capture());
+
+    assertThat(projectAnalysisArgumentCaptor.getValue().getBranch()).isEmpty();
+  }
+
+  @Test
+  public void branch_is_empty_when_legacy_branch_implementation_is_used() {
+    analysisMetadataHolder.setBranch(new MainBranchImpl("feature/foo"));
+
+    underTest.finished(true);
+
+    verify(postProjectAnalysisTask).finished(projectAnalysisArgumentCaptor.capture());
+
+    assertThat(projectAnalysisArgumentCaptor.getValue().getBranch()).isEmpty();
+  }
+
+  @Test
+  public void branch_comes_from_AnalysisMetadataHolder_when_set() {
+    analysisMetadataHolder.setBranch(new Branch() {
+      @Override
+      public BranchType getType() {
+        return BranchType.SHORT;
+      }
+
+      @Override
+      public boolean isMain() {
+        return false;
+      }
+
+      @Override
+      public boolean isLegacyFeature() {
+        return false;
+      }
+
+      @Override
+      public Optional<String> getName() {
+        return Optional.of("feature/foo");
+      }
+
+      @Override
+      public boolean supportsCrossProjectCpd() {
+        throw new UnsupportedOperationException();
+      }
+
+      @Override
+      public String generateKey(ScannerReport.Component module, @Nullable ScannerReport.Component fileOrDir) {
+        throw new UnsupportedOperationException();
+      }
+    });
+
+    underTest.finished(true);
+
+    verify(postProjectAnalysisTask).finished(projectAnalysisArgumentCaptor.capture());
+
+    org.sonar.api.ce.posttask.Branch branch = projectAnalysisArgumentCaptor.getValue().getBranch().get();
+    assertThat(branch.isMain()).isFalse();
+    assertThat(branch.getName()).hasValue("feature/foo");
+    assertThat(branch.getType()).isEqualTo(BranchImpl.Type.SHORT);
+  }
+
   @Test
   public void qualityGate_is_null_when_finished_method_argument_is_false() {
     underTest.finished(false);
index d013003c1b83e99f18563f878a2b20151011c183..2f8788dcd997d627fdfb1ae65077963a374082c0 100644 (file)
@@ -921,6 +921,11 @@ public class ReportPersistComponentsStepTest extends BaseStepTest {
       return false;
     }
 
+    @Override
+    public boolean isLegacyFeature() {
+      return false;
+    }
+
     @Override
     public java.util.Optional<String> getName() {
       return java.util.Optional.ofNullable(name);
index 7a9624be8d960d90029bf8d781285ea00dc784e5..67a0cc4564e62e5c0152a99b70022cbe96004796 100644 (file)
@@ -26,12 +26,14 @@ import java.util.Optional;
 import javax.annotation.Nullable;
 import org.junit.Before;
 import org.junit.Test;
+import org.sonar.api.ce.posttask.Branch;
 import org.sonar.api.ce.posttask.CeTask;
 import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
 import org.sonar.api.ce.posttask.Project;
 import org.sonar.api.ce.posttask.QualityGate;
 import org.sonar.api.ce.posttask.ScannerContext;
 import org.sonar.api.platform.Server;
+import org.sonar.server.computation.task.projectanalysis.api.posttask.BranchImpl;
 
 import static java.util.Collections.emptyMap;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -75,7 +77,7 @@ public class WebhookPayloadFactoryImplTest {
         .setErrorThreshold("70.0")
         .build(QualityGate.EvaluationStatus.WARN, "74.0"))
       .build();
-    PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate, emptyMap());
+    PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate, null, emptyMap());
 
     WebhookPayload payload = underTest.create(analysis);
     assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY);
@@ -99,7 +101,7 @@ public class WebhookPayloadFactoryImplTest {
         .setErrorThreshold("70.0")
         .buildNoValue())
       .build();
-    PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate, emptyMap());
+    PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate, null, emptyMap());
 
     WebhookPayload payload = underTest.create(analysis);
     assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY);
@@ -121,9 +123,8 @@ public class WebhookPayloadFactoryImplTest {
       "sonar.analysis.revision", "ab45d24",
       "sonar.analysis.buildNumber", "B123",
       "not.prefixed.with.sonar.analysis", "should be ignored",
-      "ignored", "should be ignored too"
-    );
-    PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate, scannerProperties);
+      "ignored", "should be ignored too");
+    PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate, null, scannerProperties);
 
     WebhookPayload payload = underTest.create(analysis);
     assertJson(payload.getJson()).isSimilarTo(getClass().getResource("WebhookPayloadTest/with_analysis_properties.json"));
@@ -135,7 +136,7 @@ public class WebhookPayloadFactoryImplTest {
   @Test
   public void create_payload_for_failed_analysis() {
     CeTask ceTask = newCeTaskBuilder().setStatus(CeTask.Status.FAILED).setId("#1").build();
-    PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(ceTask, null, emptyMap());
+    PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(ceTask, null, null, emptyMap());
 
     WebhookPayload payload = underTest.create(analysis);
 
@@ -143,8 +144,43 @@ public class WebhookPayloadFactoryImplTest {
     assertJson(payload.getJson()).isSimilarTo(getClass().getResource("WebhookPayloadTest/failed.json"));
   }
 
-  private static PostProjectAnalysisTask.ProjectAnalysis newAnalysis(CeTask task, @Nullable QualityGate gate,
-    Map<String, String> scannerProperties) {
+  @Test
+  public void create_payload_on_branch() {
+    CeTask task = newCeTaskBuilder()
+      .setStatus(CeTask.Status.SUCCESS)
+      .setId("#1")
+      .build();
+    PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, null, new BranchImpl(false, "feature/foo", Branch.Type.SHORT), emptyMap());
+
+    WebhookPayload payload = underTest.create(analysis);
+    assertJson(payload.getJson()).isSimilarTo("{" +
+      "\"branch\": {" +
+      "  \"name\": \"feature/foo\"" +
+      "  \"type\": \"SHORT\"" +
+      "  \"isMain\": false" +
+      "}" +
+      "}");
+  }
+
+  @Test
+  public void create_payload_on_main_branch_without_name() {
+    CeTask task = newCeTaskBuilder()
+      .setStatus(CeTask.Status.SUCCESS)
+      .setId("#1")
+      .build();
+    PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, null, new BranchImpl(true, null, Branch.Type.LONG), emptyMap());
+
+    WebhookPayload payload = underTest.create(analysis);
+    assertJson(payload.getJson()).isSimilarTo("{" +
+      "\"branch\": {" +
+      "  \"type\": \"LONG\"" +
+      "  \"isMain\": true" +
+      "}" +
+      "}");
+  }
+
+  private static PostProjectAnalysisTask.ProjectAnalysis newAnalysis(CeTask task, @Nullable QualityGate gate, @Nullable Branch branch,
+                                                                     Map<String, String> scannerProperties) {
     return new PostProjectAnalysisTask.ProjectAnalysis() {
       @Override
       public CeTask getCeTask() {
@@ -160,6 +196,11 @@ public class WebhookPayloadFactoryImplTest {
           .build();
       }
 
+      @Override
+      public Optional<Branch> getBranch() {
+        return Optional.ofNullable(branch);
+      }
+
       @Override
       public QualityGate getQualityGate() {
         return gate;
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/Branch.java b/sonar-plugin-api/src/main/java/org/sonar/api/ce/posttask/Branch.java
new file mode 100644 (file)
index 0000000..a5f0eb0
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.ce.posttask;
+
+import java.util.Optional;
+
+/**
+ * @since 6.6
+ */
+public interface Branch {
+
+  enum Type {
+    LONG, SHORT
+  }
+
+  boolean isMain();
+
+  Optional<String> getName();
+
+  Type getType();
+
+}
index 8d3cbd56d7fb462494c06becf146622ca6a4df50..c63043a85e201592b691516b77de4e3ad8b55695 100644 (file)
@@ -62,6 +62,13 @@ public interface PostProjectAnalysisTask {
      */
     Project getProject();
 
+    /**
+     * The branch that is being analyzed.
+     *
+     * @since 6.6
+     */
+    Optional<Branch> getBranch();
+
     /**
      * Status and details of the Quality Gate of the project (if any was configured on the project).
      */
index 5b4b42b2ac7a8557c7abb0b20cf3193c463f11b9..2c9e358071b58aec3365dc8c7be068191ae0d90a 100644 (file)
@@ -25,6 +25,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
@@ -107,6 +108,8 @@ public class PostProjectAnalysisTaskTester {
   private Date date;
   @CheckForNull
   private QualityGate qualityGate;
+  @CheckForNull
+  private Branch branch;
   private ScannerContext scannerContext;
 
   private PostProjectAnalysisTaskTester(PostProjectAnalysisTask underTest) {
@@ -125,6 +128,10 @@ public class PostProjectAnalysisTaskTester {
     return new ProjectBuilder();
   }
 
+  public static BranchBuilder newBranchBuilder() {
+    return new BranchBuilder();
+  }
+
   public static QualityGateBuilder newQualityGateBuilder() {
     return new QualityGateBuilder();
   }
@@ -165,6 +172,11 @@ public class PostProjectAnalysisTaskTester {
     return this;
   }
 
+  public PostProjectAnalysisTaskTester withBranch(@Nullable Branch b) {
+    this.branch = b;
+    return this;
+  }
+
   public void execute() {
     requireNonNull(ceTask, CE_TASK_CAN_NOT_BE_NULL);
     requireNonNull(project, PROJECT_CAN_NOT_BE_NULL);
@@ -187,6 +199,11 @@ public class PostProjectAnalysisTaskTester {
           return project;
         }
 
+        @Override
+        public Optional<Branch> getBranch() {
+          return Optional.ofNullable(branch);
+        }
+
         @Override
         public QualityGate getQualityGate() {
           return qualityGate;
@@ -323,6 +340,52 @@ public class PostProjectAnalysisTaskTester {
     }
   }
 
+  public static final class BranchBuilder {
+    private boolean isMain = true;
+    private String name = null;
+    private Branch.Type type = Branch.Type.LONG;
+
+    private BranchBuilder() {
+      // prevents instantiation outside PostProjectAnalysisTaskTester
+    }
+
+    public BranchBuilder setName(@Nullable String s) {
+      this.name = s;
+      return this;
+    }
+
+    public BranchBuilder setType(Branch.Type t) {
+      this.type = Objects.requireNonNull(t);
+      return this;
+    }
+
+    public BranchBuilder setIsMain(boolean b) {
+      this.isMain = b;
+      return this;
+    }
+
+    public Branch build() {
+      return new Branch() {
+
+
+        @Override
+        public boolean isMain() {
+          return isMain;
+        }
+
+        @Override
+        public Optional<String> getName() {
+          return Optional.ofNullable(name);
+        }
+
+        @Override
+        public Type getType() {
+          return type;
+        }
+      };
+    }
+  }
+
   public static final class QualityGateBuilder {
     private static final String ID_CAN_NOT_BE_NULL = "id cannot be null";
     private static final String NAME_CAN_NOT_BE_NULL = "name cannot be null";