]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12487 improve coverage of Telemetry classes
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 8 Oct 2019 07:48:04 +0000 (09:48 +0200)
committerSonarTech <sonartech@sonarsource.com>
Tue, 8 Oct 2019 18:21:04 +0000 (20:21 +0200)
makes tests more unique by moving much logic out TelemetryDaemonTest which will make this test faster (less tests) and more reliable (simpler)
as this test was troublesom because if was testing multithreaded code

14 files changed:
server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresStatistics.java
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java
server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java [new file with mode: 0644]
server/sonar-webserver-core/src/main/java/org/sonar/server/platform/AbstractSystemInfoWriter.java
server/sonar-webserver-core/src/main/java/org/sonar/server/platform/ClusterSystemInfoWriter.java
server/sonar-webserver-core/src/main/java/org/sonar/server/platform/StandaloneSystemInfoWriter.java
server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDaemon.java
server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/ClusterSystemInfoWriterTest.java
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StandaloneSystemInfoWriterTest.java
server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDaemonTest.java
server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java [new file with mode: 0644]
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java

index b51987e9ca9c13b8fd8aafe23040db175170759f..56423fb3ec2f1d3f15afaeff1d17479a31bf067c 100644 (file)
@@ -64,8 +64,9 @@ public class ProjectMeasuresStatistics {
       return this;
     }
 
-    public void setProjectCountByLanguage(Map<String, Long> projectCountByLanguage) {
+    public Builder setProjectCountByLanguage(Map<String, Long> projectCountByLanguage) {
       this.projectCountByLanguage = projectCountByLanguage;
+      return this;
     }
 
     public Builder setNclocByLanguage(Map<String, Long> nclocByLanguage) {
index dba25d97e37058e0d70a954d08ce14658dfcfc96..9ab4ad11291d807954fd77da8a8efa827f77ec24 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.telemetry;
 
 import java.util.Map;
 import java.util.Optional;
+import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.core.platform.EditionProvider;
 import org.sonar.server.measure.index.ProjectMeasuresStatistics;
@@ -38,7 +39,8 @@ public class TelemetryData {
   private final Database database;
   private final Map<String, Long> projectCountByLanguage;
   private final Map<String, Long> nclocByLanguage;
-  private final Optional<EditionProvider.Edition> edition;
+  @CheckForNull
+  private final EditionProvider.Edition edition;
   private final String licenseType;
   private final Long installationDate;
   private final String installationVersion;
@@ -103,7 +105,7 @@ public class TelemetryData {
   }
 
   public Optional<EditionProvider.Edition> getEdition() {
-    return edition;
+    return Optional.ofNullable(edition);
   }
 
   public Optional<String> getLicenseType() {
@@ -135,7 +137,7 @@ public class TelemetryData {
     private ProjectMeasuresStatistics projectMeasuresStatistics;
     private Long ncloc;
     private Boolean usingBranches;
-    private Optional<EditionProvider.Edition> edition;
+    private EditionProvider.Edition edition;
     private String licenseType;
     private Long installationDate;
     private String installationVersion;
@@ -185,7 +187,7 @@ public class TelemetryData {
       return this;
     }
 
-    public Builder setEdition(Optional<EditionProvider.Edition> edition) {
+    public Builder setEdition(@Nullable EditionProvider.Edition edition) {
       this.edition = edition;
       return this;
     }
@@ -218,7 +220,6 @@ public class TelemetryData {
       requireNonNull(ncloc);
       requireNonNull(database);
       requireNonNull(usingBranches);
-      requireNonNull(edition);
 
       return new TelemetryData(this);
     }
index ea104d269c08ddf70409a3c7d924a5d9f403fb61..5ba6a5bffd551ad145eac353ce325e52a2253387 100644 (file)
@@ -25,11 +25,8 @@ import org.sonar.api.utils.text.JsonWriter;
 import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
 
 public class TelemetryDataJsonWriter {
-  private TelemetryDataJsonWriter() {
-    // static methods
-  }
 
-  public static void writeTelemetryData(JsonWriter json, TelemetryData statistics) {
+  public void writeTelemetryData(JsonWriter json, TelemetryData statistics) {
     json.beginObject();
     json.prop("id", statistics.getServerId());
     json.prop("version", statistics.getVersion());
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java
new file mode 100644 (file)
index 0000000..b302eb9
--- /dev/null
@@ -0,0 +1,316 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.telemetry;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.IntStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.core.platform.EditionProvider;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.server.measure.index.ProjectMeasuresStatistics;
+
+import static java.util.stream.Collectors.joining;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.test.JsonAssert.assertJson;
+
+@RunWith(DataProviderRunner.class)
+public class TelemetryDataJsonWriterTest {
+
+  private static final TelemetryData.Builder SOME_TELEMETRY_DATA = TelemetryData.builder()
+    .setServerId("foo")
+    .setVersion("bar")
+    .setPlugins(Collections.emptyMap())
+    .setProjectMeasuresStatistics(ProjectMeasuresStatistics.builder()
+      .setProjectCount(12)
+      .setProjectCountByLanguage(Collections.emptyMap())
+      .setNclocByLanguage(Collections.emptyMap())
+      .build())
+    .setNcloc(42L)
+    .setDatabase(new TelemetryData.Database("H2", "11"))
+    .setUsingBranches(true);
+
+  private final Random random = new Random();
+
+  private final TelemetryDataJsonWriter underTest = new TelemetryDataJsonWriter();
+
+  @Test
+  public void write_server_id_and_version() {
+    TelemetryData data = SOME_TELEMETRY_DATA.build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"id\": \"" + data.getServerId() + "\"," +
+      "  \"version\": \"" + data.getVersion() + "\"" +
+      "}");
+  }
+
+  @Test
+  public void does_not_write_edition_if_null() {
+    TelemetryData data = SOME_TELEMETRY_DATA.build();
+
+    String json = writeTelemetryData(data);
+
+    assertThat(json).doesNotContain("edition");
+  }
+
+  @Test
+  @UseDataProvider("allEditions")
+  public void writes_edition_if_non_null(EditionProvider.Edition edition) {
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setEdition(edition)
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"edition\": \"" + edition.name().toLowerCase(Locale.ENGLISH) + "\"" +
+      "}");
+  }
+
+  @Test
+  public void does_not_write_license_type_if_null() {
+    TelemetryData data = SOME_TELEMETRY_DATA.build();
+
+    String json = writeTelemetryData(data);
+
+    assertThat(json).doesNotContain("licenseType");
+  }
+
+  @Test
+  public void writes_licenseType_if_non_null() {
+    String expected = randomAlphabetic(12);
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setLicenseType(expected)
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"licenseType\": \"" + expected + "\"" +
+      "}");
+  }
+
+  @Test
+  public void writes_database() {
+    String name = randomAlphabetic(12);
+    String version = randomAlphabetic(10);
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setDatabase(new TelemetryData.Database(name, version))
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"database\": {" +
+      "    \"name\": \"" + name + "\"," +
+      "    \"version\": \"" + version + "\"" +
+      "  }" +
+      "}");
+  }
+
+  @Test
+  public void writes_no_plugins() {
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setPlugins(Collections.emptyMap())
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"plugins\": []" +
+      "}");
+  }
+
+  @Test
+  public void writes_all_plugins() {
+    Map<String, String> plugins = IntStream.range(0, 1 + random.nextInt(10))
+      .boxed()
+      .collect(MoreCollectors.uniqueIndex(i -> "P" + i, i -> "V" + i));
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setPlugins(plugins)
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"plugins\": " +
+      "[" +
+      plugins.entrySet().stream().map(e -> "{\"name\":\"" + e.getKey() + "\",\"version\":\"" + e.getValue() + "\"}").collect(joining(",")) +
+      "]" +
+      "}");
+  }
+
+  @Test
+  public void write_user_count() {
+    int userCount = random.nextInt(590);
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setUserCount(userCount)
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"userCount\": " + userCount +
+      "}");
+  }
+
+  @Test
+  public void write_project_count_and_ncloc_and_no_stat_by_language() {
+    int projectCount = random.nextInt(8909);
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setProjectMeasuresStatistics(ProjectMeasuresStatistics.builder()
+        .setProjectCount(projectCount)
+        .setProjectCountByLanguage(Collections.emptyMap())
+        .setNclocByLanguage(Collections.emptyMap())
+        .build())
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"projectCount\": " + projectCount + "," +
+      "  \"projectCountByLanguage\": []," +
+      "  \"nclocByLanguage\": []" +
+      "}");
+  }
+
+  @Test
+  public void write_project_stats_by_language() {
+    int projectCount = random.nextInt(8909);
+    Map<String, Long> countByLanguage = IntStream.range(0, 1 + random.nextInt(10))
+      .boxed()
+      .collect(MoreCollectors.uniqueIndex(i -> "P" + i, i -> 100L + i));
+    Map<String, Long> nclocByLanguage = IntStream.range(0, 1 + random.nextInt(10))
+      .boxed()
+      .collect(MoreCollectors.uniqueIndex(i -> "P" + i, i -> 1_000L + i));
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setProjectMeasuresStatistics(ProjectMeasuresStatistics.builder()
+        .setProjectCount(projectCount)
+        .setProjectCountByLanguage(countByLanguage)
+        .setNclocByLanguage(nclocByLanguage)
+        .build())
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"projectCount\": " + projectCount + "," +
+      "  \"projectCountByLanguage\": " +
+      "[" +
+      countByLanguage.entrySet().stream().map(e -> "{\"language\":\"" + e.getKey() + "\",\"count\":" + e.getValue() + "}").collect(joining()) +
+      "]," +
+      "  \"nclocByLanguage\": " +
+      "[" +
+      nclocByLanguage.entrySet().stream().map(e -> "{\"language\":\"" + e.getKey() + "\",\"ncloc\":" + e.getValue() + "}").collect(joining()) +
+      "]" +
+      "}");
+  }
+
+  @Test
+  public void does_not_write_installation_date_if_null() {
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setInstallationDate(null)
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertThat(json).doesNotContain("installationDate");
+  }
+
+  @Test
+  public void write_installation_date() {
+    long installationDate = random.nextInt(590);
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setInstallationDate(installationDate)
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"installationDate\": " + installationDate +
+      "}");
+  }
+
+  @Test
+  public void does_not_write_installation_version_if_null() {
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setInstallationVersion(null)
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertThat(json).doesNotContain("installationVersion");
+  }
+
+  @Test
+  public void write_installation_version() {
+    String installationVersion = randomAlphabetic(5);
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setInstallationVersion(installationVersion)
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"installationVersion\":\"" + installationVersion + "\"" +
+      "}");
+  }
+
+  @Test
+  public void write_docker_flag() {
+    boolean inDocker = random.nextBoolean();
+    TelemetryData data = SOME_TELEMETRY_DATA
+      .setInDocker(inDocker)
+      .build();
+
+    String json = writeTelemetryData(data);
+
+    assertJson(json).isSimilarTo("{" +
+      "  \"docker\":" + inDocker +
+      "}");
+  }
+
+  @DataProvider
+  public static Object[][] allEditions() {
+    return Arrays.stream(EditionProvider.Edition.values())
+      .map(t -> new Object[] {t})
+      .toArray(Object[][]::new);
+  }
+
+  private String writeTelemetryData(TelemetryData data) {
+    StringWriter jsonString = new StringWriter();
+    try (JsonWriter json = JsonWriter.of(jsonString)) {
+      underTest.writeTelemetryData(json, data);
+    }
+    return jsonString.toString();
+  }
+}
index fad900559ede49cc44e1f31d80a839956dfcb580..e74f6dae9f395508734eec00c1d1fc2cb8038778 100644 (file)
@@ -24,10 +24,9 @@ import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.process.systeminfo.SystemInfoUtils;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 import org.sonar.server.health.Health;
+import org.sonar.server.telemetry.TelemetryDataJsonWriter;
 import org.sonar.server.telemetry.TelemetryDataLoader;
 
-import static org.sonar.server.telemetry.TelemetryDataJsonWriter.writeTelemetryData;
-
 public abstract class AbstractSystemInfoWriter implements SystemInfoWriter {
   private static final String[] ORDERED_SECTION_NAMES = {
     // standalone
@@ -39,9 +38,11 @@ public abstract class AbstractSystemInfoWriter implements SystemInfoWriter {
     "Search State", "Search Indexes"};
 
   private final TelemetryDataLoader telemetry;
+  private final TelemetryDataJsonWriter dataJsonWriter;
 
-  AbstractSystemInfoWriter(TelemetryDataLoader telemetry) {
+  AbstractSystemInfoWriter(TelemetryDataLoader telemetry, TelemetryDataJsonWriter dataJsonWriter) {
     this.telemetry = telemetry;
+    this.dataJsonWriter = dataJsonWriter;
   }
 
   protected void writeSections(Collection<ProtobufSystemInfo.Section> sections, JsonWriter json) {
@@ -88,6 +89,6 @@ public abstract class AbstractSystemInfoWriter implements SystemInfoWriter {
 
   protected void writeTelemetry(JsonWriter json) {
     json.name("Statistics");
-    writeTelemetryData(json, telemetry.load());
+    dataJsonWriter.writeTelemetryData(json, telemetry.load());
   }
 }
index 27ae9fb83ff8dcc97ac03405b4386e7dae718022..e6b609236a3a48c1a4aee27c8a3a15234fbe9992 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.server.platform.monitoring.cluster.AppNodesInfoLoader;
 import org.sonar.server.platform.monitoring.cluster.GlobalInfoLoader;
 import org.sonar.server.platform.monitoring.cluster.NodeInfo;
 import org.sonar.server.platform.monitoring.cluster.SearchNodesInfoLoader;
+import org.sonar.server.telemetry.TelemetryDataJsonWriter;
 import org.sonar.server.telemetry.TelemetryDataLoader;
 
 public class ClusterSystemInfoWriter extends AbstractSystemInfoWriter {
@@ -36,8 +37,8 @@ public class ClusterSystemInfoWriter extends AbstractSystemInfoWriter {
   private final HealthChecker healthChecker;
 
   public ClusterSystemInfoWriter(GlobalInfoLoader globalInfoLoader, AppNodesInfoLoader appNodesInfoLoader, SearchNodesInfoLoader searchNodesInfoLoader,
-    HealthChecker healthChecker, TelemetryDataLoader telemetry) {
-    super(telemetry);
+    HealthChecker healthChecker, TelemetryDataLoader telemetry, TelemetryDataJsonWriter dataJsonWriter) {
+    super(telemetry, dataJsonWriter);
     this.globalInfoLoader = globalInfoLoader;
     this.appNodesInfoLoader = appNodesInfoLoader;
     this.searchNodesInfoLoader = searchNodesInfoLoader;
index 36bf38e5aa1dc0cc8986ad6f3fc6bc9e30c1bd9b..6e1ddb7077476efb711719717e032f48d16ca92c 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 import org.sonar.server.ce.http.CeHttpClient;
 import org.sonar.server.health.Health;
 import org.sonar.server.health.HealthChecker;
+import org.sonar.server.telemetry.TelemetryDataJsonWriter;
 import org.sonar.server.telemetry.TelemetryDataLoader;
 
 import static java.util.Arrays.stream;
@@ -36,8 +37,9 @@ public class StandaloneSystemInfoWriter extends AbstractSystemInfoWriter {
   private final HealthChecker healthChecker;
   private final SystemInfoSection[] systemInfoSections;
 
-  public StandaloneSystemInfoWriter(TelemetryDataLoader telemetry, CeHttpClient ceHttpClient, HealthChecker healthChecker, SystemInfoSection... systemInfoSections) {
-    super(telemetry);
+  public StandaloneSystemInfoWriter(TelemetryDataLoader telemetry, CeHttpClient ceHttpClient, HealthChecker healthChecker,
+    TelemetryDataJsonWriter dataJsonWriter, SystemInfoSection... systemInfoSections) {
+    super(telemetry, dataJsonWriter);
     this.ceHttpClient = ceHttpClient;
     this.healthChecker = healthChecker;
     this.systemInfoSections = systemInfoSections;
index 64f44c3cc8811a719f17bf5ddbd42648659c1fb9..2f95b093791636b9a8df7fd51422076570ddb9c3 100644 (file)
@@ -43,7 +43,6 @@ import static org.sonar.api.utils.DateUtils.parseDate;
 import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_ENABLE;
 import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_FREQUENCY_IN_SECONDS;
 import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
-import static org.sonar.server.telemetry.TelemetryDataJsonWriter.writeTelemetryData;
 
 @ServerSide
 public class TelemetryDaemon implements Startable {
@@ -56,6 +55,7 @@ public class TelemetryDaemon implements Startable {
   private static final String LOCK_DELAY_SEC = "sonar.telemetry.lock.delay";
 
   private final TelemetryDataLoader dataLoader;
+  private final TelemetryDataJsonWriter dataJsonWriter;
   private final TelemetryClient telemetryClient;
   private final GlobalLockManager lockManager;
   private final Configuration config;
@@ -64,9 +64,10 @@ public class TelemetryDaemon implements Startable {
 
   private ScheduledExecutorService executorService;
 
-  public TelemetryDaemon(TelemetryDataLoader dataLoader, TelemetryClient telemetryClient, Configuration config,
+  public TelemetryDaemon(TelemetryDataLoader dataLoader, TelemetryDataJsonWriter dataJsonWriter, TelemetryClient telemetryClient, Configuration config,
     InternalProperties internalProperties, GlobalLockManager lockManager, System2 system2) {
     this.dataLoader = dataLoader;
+    this.dataJsonWriter = dataJsonWriter;
     this.telemetryClient = telemetryClient;
     this.config = config;
     this.internalProperties = internalProperties;
@@ -130,7 +131,7 @@ public class TelemetryDaemon implements Startable {
           internalProperties.write(I_PROP_LAST_PING, String.valueOf(startOfDay(now)));
         }
       } catch (Exception e) {
-        LOG.debug("Error while checking SonarQube statistics: {}", e.getMessage());
+        LOG.debug("Error while checking SonarQube statistics: {}", e);
       }
       // do not check at start up to exclude test instance which are not up for a long time
     };
@@ -150,7 +151,7 @@ public class TelemetryDaemon implements Startable {
     TelemetryData statistics = dataLoader.load();
     StringWriter jsonString = new StringWriter();
     try (JsonWriter json = JsonWriter.of(jsonString)) {
-      writeTelemetryData(json, statistics);
+      dataJsonWriter.writeTelemetryData(json, statistics);
     }
     telemetryClient.upload(jsonString.toString());
   }
index 8b888ab79bdcef1ad81df38597bafd80fca3415a..a61b758c077455df7be51a5e4297ba450e27f269 100644 (file)
@@ -96,7 +96,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
 
     data.setServerId(server.getId());
     data.setVersion(server.getVersion());
-    data.setEdition(editionProvider.get());
+    data.setEdition(editionProvider.get().orElse(null));
     ofNullable(licenseReader)
       .flatMap(reader -> licenseReader.read())
       .ifPresent(license -> data.setLicenseType(license.getType()));
index 233bbe2967a6801900bf407d37e49173dc08acd2..3ac4cdd85aaa4400b5bffbdf3d429c8160ad8374 100644 (file)
@@ -34,6 +34,7 @@ import org.sonar.server.platform.monitoring.cluster.AppNodesInfoLoader;
 import org.sonar.server.platform.monitoring.cluster.GlobalInfoLoader;
 import org.sonar.server.platform.monitoring.cluster.NodeInfo;
 import org.sonar.server.platform.monitoring.cluster.SearchNodesInfoLoader;
+import org.sonar.server.telemetry.TelemetryDataJsonWriter;
 import org.sonar.server.telemetry.TelemetryDataLoader;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -46,7 +47,9 @@ public class ClusterSystemInfoWriterTest {
   private SearchNodesInfoLoader searchNodesInfoLoader = mock(SearchNodesInfoLoader.class);
   private HealthChecker healthChecker = mock(HealthChecker.class);
   private TelemetryDataLoader telemetry = mock(TelemetryDataLoader.class, Mockito.RETURNS_MOCKS);
-  private ClusterSystemInfoWriter underTest = new ClusterSystemInfoWriter(globalInfoLoader, appNodesInfoLoader, searchNodesInfoLoader, healthChecker, telemetry);
+  private TelemetryDataJsonWriter dataJsonWriter = new TelemetryDataJsonWriter();
+  private ClusterSystemInfoWriter underTest = new ClusterSystemInfoWriter(globalInfoLoader, appNodesInfoLoader,
+    searchNodesInfoLoader, healthChecker, telemetry, dataJsonWriter);
 
   @Before
   public void before() throws InterruptedException {
index ad829e7ddf73ae3b29f1c21b813e48011736e136..75e10c69a7e7c3daf04eed26c72da8aafefc7743 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.server.ce.http.CeHttpClientImpl;
 import org.sonar.process.systeminfo.SystemInfoSection;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 import org.sonar.server.health.TestStandaloneHealthChecker;
+import org.sonar.server.telemetry.TelemetryDataJsonWriter;
 import org.sonar.server.telemetry.TelemetryDataLoader;
 import org.sonar.server.tester.UserSessionRule;
 
@@ -52,8 +53,9 @@ public class StandaloneSystemInfoWriterTest {
   private CeHttpClient ceHttpClient = mock(CeHttpClientImpl.class, Mockito.RETURNS_MOCKS);
   private TestStandaloneHealthChecker healthChecker = new TestStandaloneHealthChecker();
   private TelemetryDataLoader telemetry = mock(TelemetryDataLoader.class, Mockito.RETURNS_MOCKS);
+  private TelemetryDataJsonWriter dataJsonWriter = new TelemetryDataJsonWriter();
 
-  private StandaloneSystemInfoWriter underTest = new StandaloneSystemInfoWriter(telemetry, ceHttpClient, healthChecker, section1, section2);
+  private StandaloneSystemInfoWriter underTest = new StandaloneSystemInfoWriter(telemetry, ceHttpClient, healthChecker, dataJsonWriter, section1, section2);
 
   @Test
   public void write_json() {
index 8e78971a7eeceff690ddc901ce8859ee315a6711..a5c8b67eb96771ff34c8df74cc4894f232121dcf 100644 (file)
 package org.sonar.server.telemetry;
 
 import java.io.IOException;
-import java.net.URL;
-import java.sql.DatabaseMetaData;
-import java.sql.SQLException;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.IntStream;
+import java.util.Collections;
 import org.junit.After;
 import org.junit.Rule;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.impl.utils.TestSystem2;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.core.platform.EditionProvider;
-import org.sonar.core.platform.PlatformEditionProvider;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginRepository;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.metric.MetricDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.measure.index.ProjectMeasuresIndex;
-import org.sonar.server.measure.index.ProjectMeasuresIndexer;
-import org.sonar.server.organization.DefaultOrganizationProviderImpl;
-import org.sonar.server.platform.DockerSupport;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.server.measure.index.ProjectMeasuresStatistics;
 import org.sonar.server.property.InternalProperties;
 import org.sonar.server.property.MapInternalProperties;
-import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.user.index.UserIndex;
-import org.sonar.server.user.index.UserIndexer;
 import org.sonar.server.util.GlobalLockManager;
 import org.sonar.server.util.GlobalLockManagerImpl;
-import org.sonar.updatecenter.common.Version;
 
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptySet;
-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.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.sonar.api.measures.CoreMetrics.COVERAGE_KEY;
-import static org.sonar.api.measures.CoreMetrics.LINES_KEY;
-import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
-import static org.sonar.api.measures.CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY;
 import static org.sonar.api.utils.DateUtils.parseDate;
-import static org.sonar.db.component.BranchType.LONG;
-import static org.sonar.db.component.BranchType.SHORT;
 import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_ENABLE;
 import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_FREQUENCY_IN_SECONDS;
 import static org.sonar.process.ProcessProperties.Property.SONAR_TELEMETRY_URL;
-import static org.sonar.test.JsonAssert.assertJson;
 
 public class TelemetryDaemonTest {
+  @Rule
+  public LogTester logger = new LogTester().setLevel(LoggerLevel.DEBUG);
 
   private static final long ONE_HOUR = 60 * 60 * 1_000L;
   private static final long ONE_DAY = 24 * ONE_HOUR;
-
-  @Rule
-  public UserSessionRule userSession = UserSessionRule.standalone();
-  @Rule
-  public DbTester db = DbTester.create();
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public LogTester logger = new LogTester().setLevel(LoggerLevel.DEBUG);
+  private static final TelemetryData SOME_TELEMETRY_DATA = TelemetryData.builder()
+    .setServerId("foo")
+    .setVersion("bar")
+    .setPlugins(Collections.emptyMap())
+    .setProjectMeasuresStatistics(ProjectMeasuresStatistics.builder()
+      .setProjectCount(12)
+      .setProjectCountByLanguage(Collections.emptyMap())
+      .setNclocByLanguage(Collections.emptyMap())
+      .build())
+    .setNcloc(42L)
+    .setDatabase(new TelemetryData.Database("H2", "11"))
+    .setUsingBranches(true)
+    .build();
 
   private TelemetryClient client = mock(TelemetryClient.class);
   private InternalProperties internalProperties = spy(new MapInternalProperties());
   private final GlobalLockManager lockManager = mock(GlobalLockManagerImpl.class);
-  private FakeServer server = new FakeServer();
-  private PluginRepository pluginRepository = mock(PluginRepository.class);
   private TestSystem2 system2 = new TestSystem2().setNow(System.currentTimeMillis());
   private MapSettings settings = new MapSettings();
-  private ProjectMeasuresIndexer projectMeasuresIndexer = new ProjectMeasuresIndexer(db.getDbClient(), es.client());
-  private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client());
-  private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class);
-
-  private final DockerSupport dockerSupport = mock(DockerSupport.class);
-  private final TelemetryDataLoader communityDataLoader = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, new UserIndex(es.client(), system2),
-    new ProjectMeasuresIndex(es.client(), null, system2), editionProvider, new DefaultOrganizationProviderImpl(db.getDbClient()), internalProperties, dockerSupport, null);
-  private TelemetryDaemon communityUnderTest = new TelemetryDaemon(communityDataLoader, client, settings.asConfig(), internalProperties, lockManager, system2);
 
-  private final LicenseReader licenseReader = mock(LicenseReader.class);
-  private final TelemetryDataLoader commercialDataLoader = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, new UserIndex(es.client(), system2),
-    new ProjectMeasuresIndex(es.client(), null, system2), editionProvider, new DefaultOrganizationProviderImpl(db.getDbClient()), internalProperties, dockerSupport, licenseReader);
-  private TelemetryDaemon commercialUnderTest = new TelemetryDaemon(commercialDataLoader, client, settings.asConfig(), internalProperties, lockManager, system2);
+  private final TelemetryDataLoader dataLoader = mock(TelemetryDataLoader.class);
+  private final TelemetryDataJsonWriter dataJsonWriter = mock(TelemetryDataJsonWriter.class);
+  private TelemetryDaemon underTest = new TelemetryDaemon(dataLoader, dataJsonWriter, client, settings.asConfig(), internalProperties, lockManager, system2);
 
   @After
   public void tearDown() {
-    communityUnderTest.stop();
-  }
-
-  @Test
-  public void send_telemetry_data() throws IOException {
-    initTelemetrySettingsToDefaultValues();
-    settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
-    server.setId("AU-TpxcB-iU5OvuD2FL7");
-    server.setVersion("7.5.4");
-    List<PluginInfo> plugins = asList(newPlugin("java", "4.12.0.11033"), newPlugin("scmgit", "1.2"), new PluginInfo("other"));
-    when(pluginRepository.getPluginInfos()).thenReturn(plugins);
-    when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));
-    when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
-
-    IntStream.range(0, 3).forEach(i -> db.users().insertUser());
-    db.users().insertUser(u -> u.setActive(false));
-    userIndexer.indexOnStartup(emptySet());
-
-    MetricDto lines = db.measures().insertMetric(m -> m.setKey(LINES_KEY));
-    MetricDto ncloc = db.measures().insertMetric(m -> m.setKey(NCLOC_KEY));
-    MetricDto coverage = db.measures().insertMetric(m -> m.setKey(COVERAGE_KEY));
-    MetricDto nclocDistrib = db.measures().insertMetric(m -> m.setKey(NCLOC_LANGUAGE_DISTRIBUTION_KEY));
-
-    ComponentDto project1 = db.components().insertMainBranch(db.getDefaultOrganization());
-    ComponentDto project1Branch = db.components().insertProjectBranch(project1);
-    db.measures().insertLiveMeasure(project1, lines, m -> m.setValue(200d));
-    db.measures().insertLiveMeasure(project1, ncloc, m -> m.setValue(100d));
-    db.measures().insertLiveMeasure(project1, coverage, m -> m.setValue(80d));
-    db.measures().insertLiveMeasure(project1, nclocDistrib, m -> m.setValue(null).setData("java=200;js=50"));
-
-    ComponentDto project2 = db.components().insertMainBranch(db.getDefaultOrganization());
-    db.measures().insertLiveMeasure(project2, lines, m -> m.setValue(300d));
-    db.measures().insertLiveMeasure(project2, ncloc, m -> m.setValue(200d));
-    db.measures().insertLiveMeasure(project2, coverage, m -> m.setValue(80d));
-    db.measures().insertLiveMeasure(project2, nclocDistrib, m -> m.setValue(null).setData("java=300;kotlin=2500"));
-    projectMeasuresIndexer.indexOnStartup(emptySet());
-
-    communityUnderTest.start();
-
-    ArgumentCaptor<String> jsonCaptor = captureJson();
-    String json = jsonCaptor.getValue();
-    URL url = getClass().getResource("telemetry-example.json");
-    assertJson(json).ignoreFields("database").isSimilarTo(url);
-    assertJson(url).ignoreFields("database").isSimilarTo(json);
-    assertDatabaseMetadata(json);
-    assertThat(logger.logs(LoggerLevel.INFO)).contains("Sharing of SonarQube statistics is enabled.");
-  }
-
-  private void assertDatabaseMetadata(String json) {
-    try (DbSession dbSession = db.getDbClient().openSession(false)) {
-      DatabaseMetaData metadata = dbSession.getConnection().getMetaData();
-      assertJson(json).isSimilarTo("{\n" +
-        "  \"database\": {\n" +
-        "    \"name\": \"H2\",\n" +
-        "    \"version\": \"" + metadata.getDatabaseProductVersion() + "\"\n" +
-        "  }\n" +
-        "}");
-    } catch (SQLException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  @Test
-  public void take_biggest_long_living_branches() throws IOException {
-    initTelemetrySettingsToDefaultValues();
-    when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
-    settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
-    server.setId("AU-TpxcB-iU5OvuD2FL7").setVersion("7.5.4");
-    MetricDto ncloc = db.measures().insertMetric(m -> m.setKey(NCLOC_KEY));
-    ComponentDto project = db.components().insertMainBranch(db.getDefaultOrganization());
-    ComponentDto longBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(LONG));
-    ComponentDto shortBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(SHORT));
-    db.measures().insertLiveMeasure(project, ncloc, m -> m.setValue(10d));
-    db.measures().insertLiveMeasure(longBranch, ncloc, m -> m.setValue(20d));
-    db.measures().insertLiveMeasure(shortBranch, ncloc, m -> m.setValue(30d));
-    projectMeasuresIndexer.indexOnStartup(emptySet());
-
-    communityUnderTest.start();
-
-    ArgumentCaptor<String> jsonCaptor = captureJson();
-    assertJson(jsonCaptor.getValue()).isSimilarTo("{\n" +
-      "  \"ncloc\": 20\n" +
-      "}\n");
+    underTest.stop();
   }
 
   @Test
@@ -210,52 +94,23 @@ public class TelemetryDaemonTest {
     initTelemetrySettingsToDefaultValues();
     when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
     settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
-    communityUnderTest.start();
+    when(dataLoader.load()).thenReturn(SOME_TELEMETRY_DATA);
+    mockDataJsonWriterDoingSomething();
 
-    verify(client, timeout(2_000).atLeastOnce()).upload(anyString());
-  }
-
-  @Test
-  public void data_contains_no_license_type_on_community_edition() throws IOException {
-    initTelemetrySettingsToDefaultValues();
-    when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
-    settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
-
-    communityUnderTest.start();
+    underTest.start();
 
-    ArgumentCaptor<String> jsonCaptor = captureJson();
-    assertThat(jsonCaptor.getValue()).doesNotContain("licenseType");
-  }
-
-  @Test
-  public void data_contains_no_license_type_on_commercial_edition_if_no_license() throws IOException {
-    initTelemetrySettingsToDefaultValues();
-    settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
-    when(licenseReader.read()).thenReturn(Optional.empty());
-    when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
-
-    commercialUnderTest.start();
-
-    ArgumentCaptor<String> jsonCaptor = captureJson();
-    assertThat(jsonCaptor.getValue()).doesNotContain("licenseType");
+    verify(client, timeout(2_000).atLeastOnce()).upload(anyString());
+    verify(dataJsonWriter).writeTelemetryData(any(JsonWriter.class), same(SOME_TELEMETRY_DATA));
   }
 
-  @Test
-  public void data_has_license_type_on_commercial_edition_if_no_license() throws IOException {
-    String licenseType = randomAlphabetic(12);
-    initTelemetrySettingsToDefaultValues();
-    settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
-    LicenseReader.License license = mock(LicenseReader.License.class);
-    when(license.getType()).thenReturn(licenseType);
-    when(licenseReader.read()).thenReturn(Optional.of(license));
-    when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
-
-    commercialUnderTest.start();
-
-    ArgumentCaptor<String> jsonCaptor = captureJson();
-    assertJson(jsonCaptor.getValue()).isSimilarTo("{\n" +
-      "  \"licenseType\": \"" + licenseType + "\"\n" +
-      "}\n");
+  private void mockDataJsonWriterDoingSomething() {
+    doAnswer(t -> {
+      JsonWriter json = t.getArgument(0);
+      json.beginObject().prop("foo", "bar").endObject();
+      return null;
+    })
+      .when(dataJsonWriter)
+      .writeTelemetryData(any(), any());
   }
 
   @Test
@@ -267,54 +122,18 @@ public class TelemetryDaemonTest {
     long sevenDaysAgo = now - (ONE_DAY * 7L);
     internalProperties.write("telemetry.lastPing", String.valueOf(sixDaysAgo));
     settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
-    communityUnderTest.start();
-    verify(client, after(2_000).never()).upload(anyString());
-    internalProperties.write("telemetry.lastPing", String.valueOf(sevenDaysAgo));
+    when(dataLoader.load()).thenReturn(SOME_TELEMETRY_DATA);
+    mockDataJsonWriterDoingSomething();
 
-    verify(client, timeout(2_000).atLeastOnce()).upload(anyString());
-  }
+    underTest.start();
 
-  @Test
-  public void send_server_id_and_version() throws IOException {
-    initTelemetrySettingsToDefaultValues();
-    when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
-    settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
-    String id = randomAlphanumeric(40);
-    String version = randomAlphanumeric(10);
-    server.setId(id);
-    server.setVersion(version);
-    communityUnderTest.start();
+    verify(dataJsonWriter, after(2_000).never()).writeTelemetryData(any(JsonWriter.class), same(SOME_TELEMETRY_DATA));
+    verify(client, never()).upload(anyString());
 
-    ArgumentCaptor<String> json = captureJson();
-    assertThat(json.getValue()).contains(id, version);
-  }
-
-  @Test
-  public void send_server_installation_date_and_installation_version() throws IOException {
-    initTelemetrySettingsToDefaultValues();
-    when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
-    settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
-    String installationVersion = "7.9.BEST.LTS.EVER";
-    Long installationDate = 1546300800000L; // 2019/01/01
-    internalProperties.write(InternalProperties.INSTALLATION_DATE, String.valueOf(installationDate));
-    internalProperties.write(InternalProperties.INSTALLATION_VERSION, installationVersion);
-
-    communityUnderTest.start();
-
-    ArgumentCaptor<String> json = captureJson();
-    assertThat(json.getValue()).contains(installationVersion, installationDate.toString());
-  }
-
-  @Test
-  public void do_not_send_server_installation_details_if_missing_property() throws IOException {
-    initTelemetrySettingsToDefaultValues();
-    when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
-    settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
-
-    communityUnderTest.start();
+    internalProperties.write("telemetry.lastPing", String.valueOf(sevenDaysAgo));
 
-    ArgumentCaptor<String> json = captureJson();
-    assertThat(json.getValue()).doesNotContain("installationVersion", "installationDate");
+    verify(dataJsonWriter, timeout(2_000)).writeTelemetryData(any(JsonWriter.class), same(SOME_TELEMETRY_DATA));
+    verify(client).upload(anyString());
   }
 
   @Test
@@ -324,9 +143,10 @@ public class TelemetryDaemonTest {
     settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
     long now = system2.now();
     long sixDaysAgo = now - (ONE_DAY * 6L);
+    mockDataJsonWriterDoingSomething();
 
     internalProperties.write("telemetry.lastPing", String.valueOf(sixDaysAgo));
-    communityUnderTest.start();
+    underTest.start();
 
     verify(client, after(2_000).never()).upload(anyString());
   }
@@ -341,8 +161,9 @@ public class TelemetryDaemonTest {
     long sevenDaysAgo = today - (ONE_DAY * 7L);
     internalProperties.write("telemetry.lastPing", String.valueOf(sevenDaysAgo));
     reset(internalProperties);
+    mockDataJsonWriterDoingSomething();
 
-    communityUnderTest.start();
+    underTest.start();
 
     verify(internalProperties, timeout(4_000)).write("telemetry.lastPing", String.valueOf(today));
     verify(client).upload(anyString());
@@ -354,28 +175,20 @@ public class TelemetryDaemonTest {
     when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
     settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
     settings.setProperty("sonar.telemetry.enable", "false");
-    communityUnderTest.start();
-    communityUnderTest.start();
+    mockDataJsonWriterDoingSomething();
+
+    underTest.start();
+    underTest.start();
 
     verify(client, after(2_000).never()).upload(anyString());
     verify(client, timeout(2_000).times(1)).optOut(anyString());
     assertThat(logger.logs(LoggerLevel.INFO)).contains("Sharing of SonarQube statistics is disabled.");
   }
 
-  private PluginInfo newPlugin(String key, String version) {
-    return new PluginInfo(key)
-      .setVersion(Version.create(version));
-  }
-
   private void initTelemetrySettingsToDefaultValues() {
     settings.setProperty(SONAR_TELEMETRY_ENABLE.getKey(), SONAR_TELEMETRY_ENABLE.getDefaultValue());
     settings.setProperty(SONAR_TELEMETRY_URL.getKey(), SONAR_TELEMETRY_URL.getDefaultValue());
     settings.setProperty(SONAR_TELEMETRY_FREQUENCY_IN_SECONDS.getKey(), SONAR_TELEMETRY_FREQUENCY_IN_SECONDS.getDefaultValue());
   }
 
-  private ArgumentCaptor<String> captureJson() throws IOException {
-    ArgumentCaptor<String> jsonCaptor = ArgumentCaptor.forClass(String.class);
-    verify(client, timeout(2_000).atLeastOnce()).upload(jsonCaptor.capture());
-    return jsonCaptor;
-  }
 }
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java
new file mode 100644 (file)
index 0000000..a448275
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.telemetry;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.core.platform.PlatformEditionProvider;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.measure.index.ProjectMeasuresIndex;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
+import org.sonar.server.organization.DefaultOrganizationProviderImpl;
+import org.sonar.server.platform.DockerSupport;
+import org.sonar.server.property.InternalProperties;
+import org.sonar.server.property.MapInternalProperties;
+import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.user.index.UserIndexer;
+import org.sonar.updatecenter.common.Version;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptySet;
+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.assertj.core.api.Assertions.entry;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.measures.CoreMetrics.COVERAGE_KEY;
+import static org.sonar.api.measures.CoreMetrics.LINES_KEY;
+import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
+import static org.sonar.api.measures.CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY;
+import static org.sonar.core.platform.EditionProvider.Edition.DEVELOPER;
+import static org.sonar.db.component.BranchType.LONG;
+import static org.sonar.db.component.BranchType.SHORT;
+
+public class TelemetryDataLoaderImplTest {
+  @Rule
+  public DbTester db = DbTester.create();
+  @Rule
+  public EsTester es = EsTester.create();
+
+  private final FakeServer server = new FakeServer();
+  private final PluginRepository pluginRepository = mock(PluginRepository.class);
+  private final TestSystem2 system2 = new TestSystem2().setNow(System.currentTimeMillis());
+  private final PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class);
+  private final DockerSupport dockerSupport = mock(DockerSupport.class);
+  private final InternalProperties internalProperties = spy(new MapInternalProperties());
+  private final ProjectMeasuresIndexer projectMeasuresIndexer = new ProjectMeasuresIndexer(db.getDbClient(), es.client());
+  private final UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client());
+  private final LicenseReader licenseReader = mock(LicenseReader.class);
+
+  private final TelemetryDataLoader communityUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, new UserIndex(es.client(), system2),
+    new ProjectMeasuresIndex(es.client(), null, system2), editionProvider, new DefaultOrganizationProviderImpl(db.getDbClient()), internalProperties, dockerSupport, null);
+  private final TelemetryDataLoader commercialUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, new UserIndex(es.client(), system2),
+    new ProjectMeasuresIndex(es.client(), null, system2), editionProvider, new DefaultOrganizationProviderImpl(db.getDbClient()), internalProperties, dockerSupport, licenseReader);
+
+  @Test
+  public void send_telemetry_data() {
+    String serverId = "AU-TpxcB-iU5OvuD2FL7";
+    String version = "7.5.4";
+    server.setId(serverId);
+    server.setVersion(version);
+    List<PluginInfo> plugins = asList(newPlugin("java", "4.12.0.11033"), newPlugin("scmgit", "1.2"), new PluginInfo("other"));
+    when(pluginRepository.getPluginInfos()).thenReturn(plugins);
+    when(editionProvider.get()).thenReturn(Optional.of(DEVELOPER));
+
+    int userCount = 3;
+    IntStream.range(0, userCount).forEach(i -> db.users().insertUser());
+    db.users().insertUser(u -> u.setActive(false));
+    userIndexer.indexOnStartup(emptySet());
+
+    MetricDto lines = db.measures().insertMetric(m -> m.setKey(LINES_KEY));
+    MetricDto ncloc = db.measures().insertMetric(m -> m.setKey(NCLOC_KEY));
+    MetricDto coverage = db.measures().insertMetric(m -> m.setKey(COVERAGE_KEY));
+    MetricDto nclocDistrib = db.measures().insertMetric(m -> m.setKey(NCLOC_LANGUAGE_DISTRIBUTION_KEY));
+
+    ComponentDto project1 = db.components().insertMainBranch(db.getDefaultOrganization());
+    ComponentDto project1Branch = db.components().insertProjectBranch(project1);
+    db.measures().insertLiveMeasure(project1, lines, m -> m.setValue(200d));
+    db.measures().insertLiveMeasure(project1, ncloc, m -> m.setValue(100d));
+    db.measures().insertLiveMeasure(project1, coverage, m -> m.setValue(80d));
+    db.measures().insertLiveMeasure(project1, nclocDistrib, m -> m.setValue(null).setData("java=200;js=50"));
+
+    ComponentDto project2 = db.components().insertMainBranch(db.getDefaultOrganization());
+    db.measures().insertLiveMeasure(project2, lines, m -> m.setValue(300d));
+    db.measures().insertLiveMeasure(project2, ncloc, m -> m.setValue(200d));
+    db.measures().insertLiveMeasure(project2, coverage, m -> m.setValue(80d));
+    db.measures().insertLiveMeasure(project2, nclocDistrib, m -> m.setValue(null).setData("java=300;kotlin=2500"));
+    projectMeasuresIndexer.indexOnStartup(emptySet());
+
+    TelemetryData data = communityUnderTest.load();
+    assertThat(data.getServerId()).isEqualTo(serverId);
+    assertThat(data.getVersion()).isEqualTo(version);
+    assertThat(data.getEdition()).contains(DEVELOPER);
+    assertDatabaseMetadata(data.getDatabase());
+    assertThat(data.getPlugins()).containsOnly(
+      entry("java", "4.12.0.11033"), entry("scmgit", "1.2"), entry("other", "undefined"));
+    assertThat(data.getUserCount()).isEqualTo(userCount);
+    assertThat(data.getProjectCount()).isEqualTo(2L);
+    assertThat(data.getNcloc()).isEqualTo(300L);
+    assertThat(data.getProjectCountByLanguage()).containsOnly(
+      entry("java", 2L), entry("kotlin", 1L), entry("js", 1L));
+    assertThat(data.getNclocByLanguage()).containsOnly(
+      entry("java", 500L), entry("kotlin", 2500L), entry("js", 50L));
+    assertThat(data.isInDocker()).isFalse();
+  }
+
+  private void assertDatabaseMetadata(TelemetryData.Database database) {
+    try (DbSession dbSession = db.getDbClient().openSession(false)) {
+      DatabaseMetaData metadata = dbSession.getConnection().getMetaData();
+      assertThat(database.getName()).isEqualTo("H2");
+      assertThat(database.getVersion()).isEqualTo(metadata.getDatabaseProductVersion());
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Test
+  public void take_biggest_long_living_branches() {
+    server.setId("AU-TpxcB-iU5OvuD2FL7").setVersion("7.5.4");
+    MetricDto ncloc = db.measures().insertMetric(m -> m.setKey(NCLOC_KEY));
+    ComponentDto project = db.components().insertMainBranch(db.getDefaultOrganization());
+    ComponentDto longBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(LONG));
+    ComponentDto shortBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(SHORT));
+    db.measures().insertLiveMeasure(project, ncloc, m -> m.setValue(10d));
+    db.measures().insertLiveMeasure(longBranch, ncloc, m -> m.setValue(20d));
+    db.measures().insertLiveMeasure(shortBranch, ncloc, m -> m.setValue(30d));
+    projectMeasuresIndexer.indexOnStartup(emptySet());
+
+    TelemetryData data = communityUnderTest.load();
+
+    assertThat(data.getNcloc()).isEqualTo(20l);
+  }
+
+  @Test
+  public void data_contains_no_license_type_on_community_edition() {
+    TelemetryData data = communityUnderTest.load();
+
+    assertThat(data.getLicenseType()).isEmpty();
+  }
+
+  @Test
+  public void data_contains_no_license_type_on_commercial_edition_if_no_license() {
+    when(licenseReader.read()).thenReturn(Optional.empty());
+
+    TelemetryData data = commercialUnderTest.load();
+
+    assertThat(data.getLicenseType()).isEmpty();
+  }
+
+  @Test
+  public void data_has_license_type_on_commercial_edition_if_no_license() {
+    String licenseType = randomAlphabetic(12);
+    LicenseReader.License license = mock(LicenseReader.License.class);
+    when(license.getType()).thenReturn(licenseType);
+    when(licenseReader.read()).thenReturn(Optional.of(license));
+
+    TelemetryData data = commercialUnderTest.load();
+
+    assertThat(data.getLicenseType()).contains(licenseType);
+  }
+
+  @Test
+  public void send_server_id_and_version() {
+    String id = randomAlphanumeric(40);
+    String version = randomAlphanumeric(10);
+    server.setId(id);
+    server.setVersion(version);
+
+    TelemetryData data = communityUnderTest.load();
+    assertThat(data.getServerId()).isEqualTo(id);
+    assertThat(data.getVersion()).isEqualTo(version);
+
+    data = commercialUnderTest.load();
+    assertThat(data.getServerId()).isEqualTo(id);
+    assertThat(data.getVersion()).isEqualTo(version);
+  }
+
+  @Test
+  public void send_server_installation_date_and_installation_version() {
+    String installationVersion = "7.9.BEST.LTS.EVER";
+    Long installationDate = 1546300800000L; // 2019/01/01
+    internalProperties.write(InternalProperties.INSTALLATION_DATE, String.valueOf(installationDate));
+    internalProperties.write(InternalProperties.INSTALLATION_VERSION, installationVersion);
+
+    TelemetryData data = communityUnderTest.load();
+
+    assertThat(data.getInstallationDate()).isEqualTo(installationDate);
+    assertThat(data.getInstallationVersion()).isEqualTo(installationVersion);
+  }
+
+  @Test
+  public void do_not_send_server_installation_details_if_missing_property() {
+    TelemetryData data = communityUnderTest.load();
+    assertThat(data.getInstallationDate()).isNull();
+    assertThat(data.getInstallationVersion()).isNull();
+
+    data = commercialUnderTest.load();
+    assertThat(data.getInstallationDate()).isNull();
+    assertThat(data.getInstallationVersion()).isNull();
+  }
+
+  private PluginInfo newPlugin(String key, String version) {
+    return new PluginInfo(key)
+      .setVersion(Version.create(version));
+  }
+
+}
index 47176b4daf638f3ba929f194f7b9b25fb3117eb4..3fd80ccb9e181422650c1d69bc958483342590d8 100644 (file)
@@ -193,6 +193,7 @@ import org.sonar.server.source.ws.SourceWsModule;
 import org.sonar.server.startup.LogServerId;
 import org.sonar.server.telemetry.TelemetryClient;
 import org.sonar.server.telemetry.TelemetryDaemon;
+import org.sonar.server.telemetry.TelemetryDataJsonWriter;
 import org.sonar.server.telemetry.TelemetryDataLoaderImpl;
 import org.sonar.server.text.MacroInterpreter;
 import org.sonar.server.ui.DeprecatedViews;
@@ -539,6 +540,7 @@ public class PlatformLevel4 extends PlatformLevel {
 
       // telemetry
       TelemetryDataLoaderImpl.class,
+      TelemetryDataJsonWriter.class,
       TelemetryDaemon.class,
       TelemetryClient.class