org.sonar.server.telemetry.TelemetryDaemonTest.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.server.telemetry.TelemetryDaemonTest.java

Source

/*
 * 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.telemetry;

import java.io.IOException;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.List;
import java.util.stream.IntStream;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.sonar.api.config.Configuration;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.utils.internal.TestSystem2;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.core.config.TelemetryProperties;
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.ProjectMeasuresIndexDefinition;
import org.sonar.server.measure.index.ProjectMeasuresIndexer;
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.UserIndexDefinition;
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.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
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.test.JsonAssert.assertJson;

public class TelemetryDaemonTest {

    private static final long ONE_HOUR = 60 * 60 * 1_000L;
    private static final long ONE_DAY = 24 * ONE_HOUR;
    private static final Configuration emptyConfig = new MapSettings().asConfig();

    @Rule
    public UserSessionRule userSession = UserSessionRule.standalone();
    @Rule
    public DbTester db = DbTester.create();
    @Rule
    public EsTester es = new EsTester(new UserIndexDefinition(emptyConfig),
            new ProjectMeasuresIndexDefinition(emptyConfig));
    @Rule
    public LogTester logger = new LogTester().setLevel(LoggerLevel.DEBUG);

    private TelemetryClient client = mock(TelemetryClient.class);
    private InternalProperties internalProperties = spy(new MapInternalProperties());
    private FakeServer server = new FakeServer();
    private PluginRepository pluginRepository = mock(PluginRepository.class);
    private TestSystem2 system2 = new TestSystem2().setNow(System.currentTimeMillis());
    private MapSettings settings = new MapSettings(new PropertyDefinitions(TelemetryProperties.all()));
    private ProjectMeasuresIndexer projectMeasuresIndexer = new ProjectMeasuresIndexer(db.getDbClient(),
            es.client());
    private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client());

    private TelemetryDaemon underTest = new TelemetryDaemon(
            new TelemetryDataLoader(server, db.getDbClient(), pluginRepository, new UserIndex(es.client(), system2),
                    new ProjectMeasuresIndex(es.client(), null, system2)),
            client, settings.asConfig(), internalProperties, system2);

    @After
    public void tearDown() {
        underTest.stop();
    }

    @Test
    public void send_telemetry_data() throws IOException {
        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);

        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());

        underTest.start();

        ArgumentCaptor<String> jsonCaptor = ArgumentCaptor.forClass(String.class);
        verify(client, timeout(2_000).atLeastOnce()).upload(jsonCaptor.capture());
        String json = jsonCaptor.getValue();
        assertJson(json).ignoreFields("database").isSimilarTo(getClass().getResource("telemetry-example.json"));
        assertJson(getClass().getResource("telemetry-example.json")).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 exclude_branches() throws IOException {
        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());

        underTest.start();

        ArgumentCaptor<String> jsonCaptor = ArgumentCaptor.forClass(String.class);
        verify(client, timeout(2_000).atLeastOnce()).upload(jsonCaptor.capture());
        assertJson(jsonCaptor.getValue()).isSimilarTo("{\n" + "  \"ncloc\": 10\n" + "}\n");
    }

    @Test
    public void send_data_via_client_at_startup_after_initial_delay() throws IOException {
        settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
        underTest.start();

        verify(client, timeout(2_000).atLeastOnce()).upload(anyString());
    }

    @Test
    public void check_if_should_send_data_periodically() throws IOException {
        long now = system2.now();
        long sixDaysAgo = now - (ONE_DAY * 6L);
        long sevenDaysAgo = now - (ONE_DAY * 7L);
        internalProperties.write("telemetry.lastPing", String.valueOf(sixDaysAgo));
        settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
        underTest.start();
        verify(client, timeout(2_000).never()).upload(anyString());
        internalProperties.write("telemetry.lastPing", String.valueOf(sevenDaysAgo));

        verify(client, timeout(2_000).atLeastOnce()).upload(anyString());
    }

    @Test
    public void send_server_id_and_version() throws IOException {
        settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
        String id = randomAlphanumeric(40);
        String version = randomAlphanumeric(10);
        server.setId(id);
        server.setVersion(version);
        underTest.start();

        ArgumentCaptor<String> json = ArgumentCaptor.forClass(String.class);
        verify(client, timeout(2_000).atLeastOnce()).upload(json.capture());
        assertThat(json.getValue()).contains(id, version);
    }

    @Test
    public void do_not_send_data_if_last_ping_earlier_than_one_week_ago() throws IOException {
        settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
        long now = system2.now();
        long sixDaysAgo = now - (ONE_DAY * 6L);

        internalProperties.write("telemetry.lastPing", String.valueOf(sixDaysAgo));
        underTest.start();

        verify(client, timeout(2_000).never()).upload(anyString());
    }

    @Test
    public void send_data_if_last_ping_is_one_week_ago() throws IOException {
        settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
        long today = parseDate("2017-08-01").getTime();
        system2.setNow(today + 15 * ONE_HOUR);
        long sevenDaysAgo = today - (ONE_DAY * 7L);
        internalProperties.write("telemetry.lastPing", String.valueOf(sevenDaysAgo));
        reset(internalProperties);

        underTest.start();

        verify(internalProperties, timeout(4_000)).write("telemetry.lastPing", String.valueOf(today));
        verify(client).upload(anyString());
    }

    @Test
    public void opt_out_sent_once() throws IOException {
        settings.setProperty("sonar.telemetry.frequencyInSeconds", "1");
        settings.setProperty("sonar.telemetry.enable", "false");
        underTest.start();
        underTest.start();

        verify(client, timeout(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));
    }
}