--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.sonarsource.sonarqube</groupId>
+ <artifactId>sonarqube</artifactId>
+ <version>6.6-SNAPSHOT</version>
+ <relativePath>../..</relativePath>
+ </parent>
+ <artifactId>backdating-plugin-v1</artifactId>
+ <name>SonarQube :: Plugins :: Backdating (V1)</name>
+ <packaging>sonar-plugin</packaging>
+ <description>Sample of plugin to test issue backdating</description>
+
+ <properties>
+ <sonar.skip>true</sonar.skip>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.sonarsource.sonarqube</groupId>
+ <artifactId>sonar-plugin-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- unit testing -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
+ <artifactId>sonar-packaging-maven-plugin</artifactId>
+ <configuration>
+ <pluginKey>backdating</pluginKey>
+ <pluginName>Foo</pluginName>
+ <pluginClass>org.sonar.backdating.BackdatingPlugin</pluginClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * 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.backdating;
+
+import org.sonar.api.Plugin;
+import org.sonar.backdating.rule.BackRulesDefinition;
+import org.sonar.backdating.rule.BackSensorV1;
+
+/**
+ * Plugin entry-point, as declared in pom.xml.
+ */
+public class BackdatingPlugin implements Plugin {
+
+ @Override
+ public void define(Context context) {
+ context.addExtensions(
+ BackRulesDefinition.class,
+ BackSensorV1.class);
+ }
+
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.backdating;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * 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.backdating.rule;
+
+import org.sonar.api.server.rule.RulesDefinition;
+
+public class BackRulesDefinition implements RulesDefinition {
+
+ public static final String RULE_KEY = "Rule";
+ public static final String BACK_REPOSITORY = "back";
+
+ @Override
+ public void define(Context context) {
+ defineRulesXoo(context);
+ }
+
+ private static void defineRulesXoo(Context context) {
+ NewRepository repo = context.createRepository(BACK_REPOSITORY, "xoo").setName("Back");
+ createRule(repo, RULE_KEY);
+ repo.done();
+ }
+
+ private static NewRule createRule(NewRepository repo, String key) {
+ return repo.createRule(key).setName(key).setHtmlDescription(key);
+ }
+
+}
--- /dev/null
+/*
+ * 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.backdating.rule;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.rule.RuleKey;
+
+public class BackSensorV1 implements Sensor {
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor.createIssuesForRuleRepositories(BackRulesDefinition.BACK_REPOSITORY)
+ .onlyOnLanguage("xoo")
+ .name("Back V1");
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ for (InputFile inputFile : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguage("xoo"))) {
+ int lineNb = 0;
+ try {
+ BufferedReader reader = new BufferedReader(new StringReader(inputFile.contents()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ lineNb++;
+ if (line.contains("BACKV1")) {
+ NewIssue newIssue = context.newIssue();
+ newIssue.at(newIssue.newLocation().on(inputFile).at(inputFile.selectLine(lineNb)))
+ .forRule(RuleKey.of(BackRulesDefinition.BACK_REPOSITORY, BackRulesDefinition.RULE_KEY))
+ .save();
+ }
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.backdating.rule;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.sonarsource.sonarqube</groupId>
+ <artifactId>sonarqube</artifactId>
+ <version>6.6-SNAPSHOT</version>
+ <relativePath>../..</relativePath>
+ </parent>
+ <artifactId>backdating-plugin-v2</artifactId>
+ <name>SonarQube :: Plugins :: Backdating (V2)</name>
+ <packaging>sonar-plugin</packaging>
+ <description>Sample of plugin to test issue backdating</description>
+
+ <properties>
+ <sonar.skip>true</sonar.skip>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.sonarsource.sonarqube</groupId>
+ <artifactId>sonar-plugin-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- unit testing -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
+ <artifactId>sonar-packaging-maven-plugin</artifactId>
+ <configuration>
+ <pluginKey>backdating</pluginKey>
+ <pluginName>Foo</pluginName>
+ <pluginClass>org.sonar.backdating.BackdatingPlugin</pluginClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * 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.backdating;
+
+import org.sonar.api.Plugin;
+import org.sonar.backdating.rule.BackRulesDefinition;
+import org.sonar.backdating.rule.BackSensorV2;
+
+/**
+ * Plugin entry-point, as declared in pom.xml.
+ */
+public class BackdatingPlugin implements Plugin {
+
+ @Override
+ public void define(Context context) {
+ context.addExtensions(
+ BackRulesDefinition.class,
+ BackSensorV2.class);
+ }
+
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.backdating;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * 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.backdating.rule;
+
+import org.sonar.api.server.rule.RulesDefinition;
+
+public class BackRulesDefinition implements RulesDefinition {
+
+ public static final String RULE_KEY = "Rule";
+ public static final String BACK_REPOSITORY = "back";
+
+ @Override
+ public void define(Context context) {
+ defineRulesXoo(context);
+ }
+
+ private static void defineRulesXoo(Context context) {
+ NewRepository repo = context.createRepository(BACK_REPOSITORY, "xoo").setName("Back");
+ createRule(repo, RULE_KEY);
+ repo.done();
+ }
+
+ private static NewRule createRule(NewRepository repo, String key) {
+ return repo.createRule(key).setName(key).setHtmlDescription(key);
+ }
+
+}
--- /dev/null
+/*
+ * 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.backdating.rule;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.rule.RuleKey;
+
+public class BackSensorV2 implements Sensor {
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor.createIssuesForRuleRepositories(BackRulesDefinition.BACK_REPOSITORY)
+ .onlyOnLanguage("xoo")
+ .name("Back V2");
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ for (InputFile inputFile : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguage("xoo"))) {
+ int lineNb = 0;
+ try {
+ BufferedReader reader = new BufferedReader(new StringReader(inputFile.contents()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ lineNb++;
+ if (line.contains("BACKV1") || line.contains("BACKV2")) {
+ NewIssue newIssue = context.newIssue();
+ newIssue.at(newIssue.newLocation().on(inputFile).at(inputFile.selectLine(lineNb)))
+ .forRule(RuleKey.of(BackRulesDefinition.BACK_REPOSITORY, BackRulesDefinition.RULE_KEY))
+ .save();
+ }
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.backdating.rule;
+
+import javax.annotation.ParametersAreNonnullByDefault;
<module>ui-extensions-plugin</module>
<module>posttask-plugin</module>
<module>ws-plugin</module>
+ <module>backdating-plugin-v1</module>
+ <module>backdating-plugin-v2</module>
</modules>
</project>
--- /dev/null
+sonar.projectKey=creation-date-sample
+sonar.projectName=Creation date sample
+sonar.projectVersion=SNAPSHOT
+sonar.sources=src/main/xoo
+sonar.language=xoo
--- /dev/null
+BACKV1
+BACKV2
\ No newline at end of file
--- /dev/null
+1,jhenry,2005-01-01T00:00:00+0000
+1,jhenry,2005-01-01T00:00:00+0000
package org.sonarqube.tests;
import com.sonar.orchestrator.Orchestrator;
+import org.junit.ClassRule;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
import org.sonarqube.tests.issue.AutoAssignTest;
import org.sonarqube.tests.issue.CommonRulesTest;
import org.sonarqube.tests.issue.CustomRulesTest;
import org.sonarqube.tests.issue.IssueActionTest;
import org.sonarqube.tests.issue.IssueBulkChangeTest;
import org.sonarqube.tests.issue.IssueChangelogTest;
+import org.sonarqube.tests.issue.IssueCreationDateQPChangedTest;
import org.sonarqube.tests.issue.IssueCreationTest;
import org.sonarqube.tests.issue.IssueFilterExtensionTest;
import org.sonarqube.tests.issue.IssueFilterOnCommonRulesTest;
import org.sonarqube.tests.test.CoverageTrackingTest;
import org.sonarqube.tests.test.NewCoverageTest;
import org.sonarqube.tests.test.TestExecutionTest;
-import org.junit.ClassRule;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
import static util.ItUtils.pluginArtifact;
import static util.ItUtils.xooPlugin;
IssueTrackingTest.class,
IssueWorkflowTest.class,
NewIssuesMeasureTest.class,
+ IssueCreationDateQPChangedTest.class,
// debt
MaintainabilityMeasureTest.class,
MaintainabilityRatingMeasureTest.class,
import org.junit.runners.Suite;
import org.sonarqube.tests.ce.CeShutdownTest;
import org.sonarqube.tests.ce.CeWorkersTest;
+import org.sonarqube.tests.cluster.ClusterTest;
+import org.sonarqube.tests.issue.IssueCreationDatePluginChangedTest;
import org.sonarqube.tests.qualityProfile.ActiveRuleEsResilienceTest;
import org.sonarqube.tests.qualityProfile.BuiltInQualityProfilesNotificationTest;
import org.sonarqube.tests.rule.RuleEsResilienceTest;
-import org.sonarqube.tests.cluster.ClusterTest;
import org.sonarqube.tests.serverSystem.RestartTest;
import org.sonarqube.tests.serverSystem.ServerSystemRestartingOrchestrator;
import org.sonarqube.tests.settings.ElasticsearchSettingsTest;
// ce
CeShutdownTest.class,
CeWorkersTest.class,
+ // issues
+ IssueCreationDatePluginChangedTest.class,
// elasticsearch
ElasticsearchSettingsTest.class
--- /dev/null
+/*
+ * 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.sonarqube.tests.issue;
+
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.build.SonarScanner;
+import java.io.File;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.wsclient.issue.Issue;
+import org.sonar.wsclient.issue.IssueQuery;
+import org.sonarqube.ws.client.PostRequest;
+import util.ItUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static util.ItUtils.pluginArtifact;
+import static util.ItUtils.projectDir;
+import static util.ItUtils.xooPlugin;
+
+/**
+ * @see <a href="https://jira.sonarsource.com/browse/MMF-766">MMF-766</a>
+ */
+public class IssueCreationDatePluginChangedTest {
+
+ private static final String ISSUE_STATUS_OPEN = "OPEN";
+
+ private static final String LANGUAGE_XOO = "xoo";
+
+ private static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
+
+ private static final String SAMPLE_PROJECT_KEY = "creation-date-sample";
+ private static final String SAMPLE_PROJECT_NAME = "Creation date sample";
+ private static final String SAMPLE_QUALITY_PROFILE_NAME = "creation-date-plugin";
+
+ @ClassRule
+ public static final Orchestrator ORCHESTRATOR = Orchestrator.builderEnv()
+ .addPlugin(xooPlugin())
+ .addPlugin(ItUtils.pluginArtifact("backdating-plugin-v1"))
+ .build();
+
+ @Before
+ public void cleanup() {
+ ORCHESTRATOR.resetData();
+ }
+
+ @Test
+ public void should_use_scm_date_for_new_issues_if_plugin_updated() {
+ ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/IssueCreationDatePluginChangedTest/one-rule.xml"));
+
+ ORCHESTRATOR.getServer().provisionProject(SAMPLE_PROJECT_KEY, SAMPLE_PROJECT_NAME);
+ ORCHESTRATOR.getServer().associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, LANGUAGE_XOO, SAMPLE_QUALITY_PROFILE_NAME);
+
+ // First analysis
+ SonarScanner scanner = SonarScanner.create(projectDir("issue/creationDatePluginChanged"))
+ .setProperty("sonar.scm.provider", "xoo")
+ .setProperty("sonar.scm.disabled", "false");
+ ORCHESTRATOR.executeBuild(scanner);
+
+ List<Issue> issues = getIssues(issueQuery().components("creation-date-sample:src/main/xoo/sample/Sample.xoo"));
+
+ // Check that issue is backdated to SCM (because it is the first analysis)
+ assertThat(issues)
+ .extracting(Issue::line, Issue::creationDate)
+ .containsExactly(tuple(1, dateTimeParse("2005-01-01T00:00:00+0000")));
+
+ // Update the plugin
+ // uninstall plugin V1
+ ItUtils.newAdminWsClient(ORCHESTRATOR).wsConnector().call(new PostRequest("api/plugins/uninstall").setParam("key", "backdating")).failIfNotSuccessful();
+ // install plugin V2
+ File pluginsDir = new File(ORCHESTRATOR.getServer().getHome() + "/extensions/plugins");
+ ORCHESTRATOR.getConfiguration().fileSystem().copyToDirectory(pluginArtifact("backdating-plugin-v2"), pluginsDir);
+
+ ORCHESTRATOR.restartServer();
+
+ // New analysis that should raise a new issue
+ ORCHESTRATOR.executeBuild(scanner);
+ issues = getIssues(issueQuery().components("creation-date-sample:src/main/xoo/sample/Sample.xoo"));
+ assertThat(issues)
+ .extracting(Issue::line, Issue::creationDate)
+ .containsExactly(tuple(1, dateTimeParse("2005-01-01T00:00:00+0000")),
+ tuple(2, dateTimeParse("2005-01-01T00:00:00+0000")));
+ }
+
+ private static List<Issue> getIssues(IssueQuery query) {
+ return ORCHESTRATOR.getServer().wsClient().issueClient().find(query).list();
+ }
+
+ private static IssueQuery issueQuery() {
+ return IssueQuery.create().statuses(ISSUE_STATUS_OPEN);
+ }
+
+ private static Date dateTimeParse(String expectedDate) {
+ try {
+ return new SimpleDateFormat(DATETIME_FORMAT).parse(expectedDate);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.sonarqube.tests.issue;
+
+import com.sonar.orchestrator.build.SonarScanner;
+import com.sonar.orchestrator.container.Server;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.wsclient.issue.Issue;
+import org.sonar.wsclient.issue.IssueQuery;
+import org.sonarqube.ws.ProjectAnalyses;
+import org.sonarqube.ws.client.projectanalysis.SearchRequest;
+import util.ItUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static util.ItUtils.projectDir;
+
+/**
+ * @see <a href="https://jira.sonarsource.com/browse/MMF-567">MMF-567</a>
+ */
+public class IssueCreationDateQPChangedTest extends AbstractIssueTest {
+
+ private static final String ISSUE_STATUS_OPEN = "OPEN";
+
+ private static final String LANGUAGE_XOO = "xoo";
+
+ private static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
+
+ private static final String SAMPLE_PROJECT_KEY = "creation-date-sample";
+ private static final String SAMPLE_PROJECT_NAME = "Creation date sample";
+ private static final String SAMPLE_QUALITY_PROFILE_NAME = "creation-date-quality-profile";
+ private static final String SAMPLE_EXPLICIT_DATE_1 = todayMinusDays(2);
+ private static final String SAMPLE_EXPLICIT_DATE_2 = todayMinusDays(1);
+
+ private Server server = ORCHESTRATOR.getServer();
+
+ @Before
+ public void resetData() {
+ ORCHESTRATOR.resetData();
+ server.provisionProject(SAMPLE_PROJECT_KEY, SAMPLE_PROJECT_NAME);
+ }
+
+ @Test
+ public void should_use_scm_date_for_new_issues_if_scm_is_available() {
+ analysis(QProfile.ONE_RULE, SourceCode.INITIAL, ScannerFeature.SCM);
+
+ assertNumberOfIssues(3);
+ assertIssueCreationDate(Component.OnlyInInitial, IssueCreationDate.OnlyInInitial_R1);
+ assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R1);
+ assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1);
+ }
+
+ @Test
+ public void should_use_analysis_date_for_new_issues_if_scm_is_not_available() {
+ analysis(QProfile.ONE_RULE, SourceCode.INITIAL);
+
+ assertNumberOfIssues(3);
+ assertIssueCreationDates(COMPONENTS_OF_SOURCE_INITIAL, IssueCreationDate.FIRST_ANALYSIS);
+ }
+
+ @Test
+ public void use_explicit_project_date_if_scm_is_not_available() {
+ analysis(QProfile.ONE_RULE, SourceCode.INITIAL, ScannerFeature.EXPLICIT_DATE_1);
+
+ assertNumberOfIssues(3);
+ assertIssueCreationDates(COMPONENTS_OF_SOURCE_INITIAL, IssueCreationDate.EXPLICIT_DATE_1);
+ }
+
+ @Test
+ public void use_scm_date_even_if_explicit_project_date_is_set() {
+ analysis(QProfile.ONE_RULE, SourceCode.INITIAL, ScannerFeature.SCM, ScannerFeature.EXPLICIT_DATE_1);
+
+ assertNumberOfIssues(3);
+ assertIssueCreationDate(Component.OnlyInInitial, IssueCreationDate.OnlyInInitial_R1);
+ assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R1);
+ assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1);
+ }
+
+ @Test
+ public void no_rules_no_issues_if_scm_is_available() {
+ analysis(QProfile.NO_RULES, SourceCode.INITIAL, ScannerFeature.SCM);
+
+ assertNoIssue();
+ }
+
+ @Test
+ public void no_rules_no_issues_if_scm_is_not_available() {
+ analysis(QProfile.NO_RULES, SourceCode.INITIAL);
+
+ assertNoIssue();
+ }
+
+ @Test
+ public void use_scm_date_for_issues_raised_by_new_rules_if_scm_is_newly_available() {
+ analysis(QProfile.NO_RULES, SourceCode.INITIAL);
+ analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.SCM);
+
+ assertNumberOfIssues(3);
+ assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R2);
+ assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1);
+ assertIssueCreationDate(Component.OnlyInChanged, IssueCreationDate.OnlyInChanged_R1);
+ }
+
+ @Test
+ public void use_scm_date_for_issues_raised_by_new_rules_if_scm_is_available_and_ever_has_been_available() {
+ analysis(QProfile.NO_RULES, SourceCode.INITIAL, ScannerFeature.SCM);
+ analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.SCM);
+
+ assertNumberOfIssues(3);
+ assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R2);
+ assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1);
+ assertIssueCreationDate(Component.OnlyInChanged, IssueCreationDate.OnlyInChanged_R1);
+ }
+
+ @Test
+ public void use_analysis_date_for_issues_raised_by_new_rules_if_scm_is_not_available() {
+ analysis(QProfile.NO_RULES, SourceCode.INITIAL);
+ analysis(QProfile.ONE_RULE, SourceCode.CHANGED);
+
+ assertNumberOfIssues(3);
+ Stream.of(COMPONENTS_OF_SOURCE_CHANGED)
+ .forEach(component -> {
+ assertIssueCreationDate(component, IssueCreationDate.LATEST_ANALYSIS);
+ });
+ }
+
+ @Test
+ public void keep_the_date_of_an_existing_issue_even_if_the_blame_information_changes() {
+ analysis(QProfile.ONE_RULE, SourceCode.INITIAL, ScannerFeature.SCM);
+ analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.SCM);
+
+ assertNumberOfIssues(3);
+ assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R1);
+ assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1);
+
+ // this file is new to the second analysis
+ assertIssueCreationDate(Component.OnlyInChanged, IssueCreationDate.LATEST_ANALYSIS);
+ }
+
+ @Test
+ public void ignore_explicit_date_for_issues_related_to_new_rules_if_scm_is_available() {
+ analysis(QProfile.NO_RULES, SourceCode.INITIAL, ScannerFeature.SCM, ScannerFeature.EXPLICIT_DATE_1);
+ analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.SCM, ScannerFeature.EXPLICIT_DATE_2);
+
+ assertNumberOfIssues(3);
+ assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R2);
+ assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1);
+ assertIssueCreationDate(Component.OnlyInChanged, IssueCreationDate.OnlyInChanged_R1);
+ }
+
+ @Test
+ public void use_explicit_date_for_issues_related_to_new_rules_if_scm_is_not_available() {
+ analysis(QProfile.NO_RULES, SourceCode.INITIAL, ScannerFeature.EXPLICIT_DATE_1);
+ analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.EXPLICIT_DATE_2);
+
+ assertNumberOfIssues(3);
+ assertIssueCreationDates(COMPONENTS_OF_SOURCE_CHANGED, IssueCreationDate.EXPLICIT_DATE_2);
+ }
+
+ private void analysis(QProfile qProfile, SourceCode sourceCode, ScannerFeature... scm) {
+ ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource(qProfile.path));
+ server.associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, LANGUAGE_XOO, SAMPLE_QUALITY_PROFILE_NAME);
+
+ SonarScanner scanner = SonarScanner.create(projectDir(sourceCode.path));
+ Arrays.stream(scm).forEach(s -> s.configure(scanner));
+ ORCHESTRATOR.executeBuild(scanner);
+ }
+
+ private static void assertNoIssue() {
+ assertNumberOfIssues(0);
+ }
+
+ private static void assertNumberOfIssues(int number) {
+ assertThat(getIssues(issueQuery())).hasSize(number);
+ }
+
+ private static void assertIssueCreationDate(Component component, IssueCreationDate expectedDate) {
+ assertIssueCreationDates(new Component[] {component}, expectedDate);
+ }
+
+ private static void assertIssueCreationDates(Component[] components, IssueCreationDate expectedDate) {
+ String[] keys = Arrays.stream(components).map(Component::getKey).toArray(String[]::new);
+ List<Issue> issues = getIssues(issueQuery().components(keys));
+ Date[] dates = Arrays.stream(components).map(x -> expectedDate.getDate()).toArray(Date[]::new);
+
+ assertThat(issues)
+ .extracting(Issue::creationDate)
+ .containsExactly(dates);
+ }
+
+ private static List<Issue> getIssues(IssueQuery query) {
+ return issueClient().find(query).list();
+ }
+
+ private static IssueQuery issueQuery() {
+ return IssueQuery.create().statuses(ISSUE_STATUS_OPEN);
+ }
+
+ private static Date dateTimeParse(String expectedDate) {
+ try {
+ return new SimpleDateFormat(DATETIME_FORMAT).parse(expectedDate);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static String todayMinusDays(int numberOfDays) {
+ return DateTimeFormatter.ofPattern(DATETIME_FORMAT).format(LocalDate.now().atStartOfDay().minusDays(numberOfDays).atZone(ZoneId.systemDefault()));
+ }
+
+ private enum SourceCode {
+ INITIAL("issue/creationDateSampleInitial"),
+ CHANGED("issue/creationDateSampleChanged"),
+ ;
+
+ private final String path;
+
+ private SourceCode(String path) {
+ this.path = path;
+ }
+ }
+
+ private enum Component {
+ OnlyInInitial("creation-date-sample:src/main/xoo/sample/OnlyInInitial.xoo"),
+ ForeverAndModified("creation-date-sample:src/main/xoo/sample/ForeverAndModified.xoo"),
+ ForeverAndUnmodified("creation-date-sample:src/main/xoo/sample/ForeverAndUnmodified.xoo"),
+ OnlyInChanged("creation-date-sample:src/main/xoo/sample/OnlyInChanged.xoo"),
+ ;
+ private final String key;
+
+ private Component(String key) {
+ this.key = key;
+ }
+
+ public String getKey() {
+ return key;
+ }
+ }
+
+ private static final Component[] COMPONENTS_OF_SOURCE_INITIAL = {Component.OnlyInInitial, Component.ForeverAndModified, Component.ForeverAndUnmodified};
+ private static final Component[] COMPONENTS_OF_SOURCE_CHANGED = {Component.ForeverAndModified, Component.ForeverAndUnmodified, Component.OnlyInChanged};
+
+ private enum QProfile {
+ ONE_RULE("/issue/IssueCreationDateQPChangedTest/one-rule.xml"),
+ NO_RULES("/issue/IssueCreationDateQPChangedTest/no-rules.xml"),
+ ;
+
+ private final String path;
+
+ private QProfile(String path) {
+ this.path = path;
+ }
+ }
+
+ private enum ScannerFeature {
+ SCM {
+ @Override
+ void configure(SonarScanner scanner) {
+ scanner
+ .setProperty("sonar.scm.provider", "xoo")
+ .setProperty("sonar.scm.disabled", "false");
+ }
+ },
+ EXPLICIT_DATE_1 {
+ @Override
+ void configure(SonarScanner scanner) {
+ scanner
+ .setProperty("sonar.projectDate", SAMPLE_EXPLICIT_DATE_1);
+ }
+ },
+ EXPLICIT_DATE_2 {
+ @Override
+ void configure(SonarScanner scanner) {
+ scanner
+ .setProperty("sonar.projectDate", SAMPLE_EXPLICIT_DATE_2);
+ }
+ },
+ ;
+
+ void configure(SonarScanner scanner) {
+ }
+ }
+
+ private enum IssueCreationDate {
+ OnlyInInitial_R1(dateTimeParse("2001-01-01T00:00:00+0000")),
+ ForeverAndUnmodified_R1(dateTimeParse("2002-01-01T00:00:00+0000")),
+ ForeverAndModified_R1(dateTimeParse("2003-01-01T00:00:00+0000")),
+ ForeverAndModified_R2(dateTimeParse("2004-01-01T00:00:00+0000")),
+ OnlyInChanged_R1(dateTimeParse("2005-01-01T00:00:00+0000")),
+ EXPLICIT_DATE_1(dateTimeParse(SAMPLE_EXPLICIT_DATE_1)),
+ EXPLICIT_DATE_2(dateTimeParse(SAMPLE_EXPLICIT_DATE_2)),
+ FIRST_ANALYSIS {
+ @Override
+ Date getDate() {
+ return getAnalysisDate(l -> {
+ if (l.isEmpty()) {
+ return Optional.empty();
+ }
+ return Optional.of(l.get(l.size() - 1));
+ });
+ }
+ },
+ LATEST_ANALYSIS {
+ @Override
+ Date getDate() {
+ return getAnalysisDate(l -> {
+ if (l.size() > 0) {
+ return Optional.of(l.get(0));
+ }
+ return Optional.empty();
+ });
+ }
+ },
+ ;
+
+ private final Date date;
+
+ private IssueCreationDate() {
+ this.date = null;
+ }
+
+ private IssueCreationDate(Date date) {
+ this.date = date;
+ }
+
+ Date getDate() {
+ return date;
+ }
+
+ private static Date getAnalysisDate(Function<List<ProjectAnalyses.Analysis>, Optional<ProjectAnalyses.Analysis>> chooseItem) {
+ return Optional.of(
+ ItUtils.newWsClient(ORCHESTRATOR)
+ .projectAnalysis()
+ .search(SearchRequest.builder().setProject(SAMPLE_PROJECT_KEY).build())
+ .getAnalysesList())
+ .flatMap(chooseItem)
+ .map(ProjectAnalyses.Analysis::getDate)
+ .map(IssueCreationDateQPChangedTest::dateTimeParse)
+ .orElseThrow(() -> new IllegalStateException("There is no analysis"));
+ }
+ }
+}
+++ /dev/null
-/*
- * 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.sonarqube.tests.issue;
-
-import com.sonar.orchestrator.build.SonarScanner;
-import com.sonar.orchestrator.container.Server;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.time.LocalDate;
-import java.time.format.DateTimeFormatter;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Function;
-import java.util.stream.Stream;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.wsclient.issue.Issue;
-import org.sonar.wsclient.issue.IssueQuery;
-import org.sonarqube.ws.ProjectAnalyses;
-import org.sonarqube.ws.client.projectanalysis.SearchRequest;
-import util.ItUtils;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static util.ItUtils.projectDir;
-
-/**
- * @see <a href="https://jira.sonarsource.com/browse/MMF-567">MMF-567</a>
- */
-public class IssueCreationDateTest extends AbstractIssueTest {
-
- private static final String ISSUE_STATUS_OPEN = "OPEN";
-
- private static final String LANGUAGE_XOO = "xoo";
-
- private static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
- private static final String DATE_FORMAT = "yyyy-MM-dd";
-
- private static final String SAMPLE_PROJECT_KEY = "creation-date-sample";
- private static final String SAMPLE_PROJECT_NAME = "Creation date sample";
- private static final String SAMPLE_QUALITY_PROFILE_NAME = "creation-date-quality-profile";
- private static final String SAMPLE_EXPLICIT_DATE_1 = todayMinusDays(2);
- private static final String SAMPLE_EXPLICIT_DATE_2 = todayMinusDays(1);
-
- private Server server = ORCHESTRATOR.getServer();
-
- @Before
- public void resetData() {
- ORCHESTRATOR.resetData();
- server.provisionProject(SAMPLE_PROJECT_KEY, SAMPLE_PROJECT_NAME);
- }
-
- @Test
- public void should_use_scm_date_for_new_issues_if_scm_is_available() {
- analysis(QProfile.ONE_RULE, SourceCode.INITIAL, ScannerFeature.SCM);
-
- assertNumberOfIssues(3);
- assertIssueCreationDate(Component.OnlyInInitial, IssueCreationDate.OnlyInInitial_R1);
- assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R1);
- assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1);
- }
-
- @Test
- public void should_use_analysis_date_for_new_issues_if_scm_is_not_available() {
- analysis(QProfile.ONE_RULE, SourceCode.INITIAL);
-
- assertNumberOfIssues(3);
- assertIssueCreationDates(COMPONENTS_OF_SOURCE_INITIAL, IssueCreationDate.FIRST_ANALYSIS);
- }
-
- @Test
- public void use_explicit_project_date_if_scm_is_not_available() {
- analysis(QProfile.ONE_RULE, SourceCode.INITIAL, ScannerFeature.EXPLICIT_DATE_1);
-
- assertNumberOfIssues(3);
- assertIssueCreationDates(COMPONENTS_OF_SOURCE_INITIAL, IssueCreationDate.EXPLICIT_DATE_1);
- }
-
- @Test
- public void use_scm_date_even_if_explicit_project_date_is_set() {
- analysis(QProfile.ONE_RULE, SourceCode.INITIAL, ScannerFeature.SCM, ScannerFeature.EXPLICIT_DATE_1);
-
- assertNumberOfIssues(3);
- assertIssueCreationDate(Component.OnlyInInitial, IssueCreationDate.OnlyInInitial_R1);
- assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R1);
- assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1);
- }
-
- @Test
- public void no_rules_no_issues_if_scm_is_available() {
- analysis(QProfile.NO_RULES, SourceCode.INITIAL, ScannerFeature.SCM);
-
- assertNoIssue();
- }
-
- @Test
- public void no_rules_no_issues_if_scm_is_not_available() {
- analysis(QProfile.NO_RULES, SourceCode.INITIAL);
-
- assertNoIssue();
- }
-
- @Test
- public void use_scm_date_for_issues_raised_by_new_rules_if_scm_is_newly_available() {
- analysis(QProfile.NO_RULES, SourceCode.INITIAL);
- analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.SCM);
-
- assertNumberOfIssues(3);
- assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R2);
- assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1);
- assertIssueCreationDate(Component.OnlyInChanged, IssueCreationDate.OnlyInChanged_R1);
- }
-
- @Test
- public void use_scm_date_for_issues_raised_by_new_rules_if_scm_is_available_and_ever_has_been_available() {
- analysis(QProfile.NO_RULES, SourceCode.INITIAL, ScannerFeature.SCM);
- analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.SCM);
-
- assertNumberOfIssues(3);
- assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R2);
- assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1);
- assertIssueCreationDate(Component.OnlyInChanged, IssueCreationDate.OnlyInChanged_R1);
- }
-
- @Test
- public void use_analysis_date_for_issues_raised_by_new_rules_if_scm_is_not_available() {
- analysis(QProfile.NO_RULES, SourceCode.INITIAL);
- analysis(QProfile.ONE_RULE, SourceCode.CHANGED);
-
- assertNumberOfIssues(3);
- Stream.of(COMPONENTS_OF_SOURCE_CHANGED)
- .forEach(component -> {
- assertIssueCreationDate(component, IssueCreationDate.LATEST_ANALYSIS);
- });
- }
-
- @Test
- public void keep_the_date_of_an_existing_issue_even_if_the_blame_information_changes() {
- analysis(QProfile.ONE_RULE, SourceCode.INITIAL, ScannerFeature.SCM);
- analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.SCM);
-
- assertNumberOfIssues(3);
- assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R1);
- assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1);
-
- // this file is new to the second analysis
- assertIssueCreationDate(Component.OnlyInChanged, IssueCreationDate.LATEST_ANALYSIS);
- }
-
- @Test
- public void ignore_explicit_date_for_issues_related_to_new_rules_if_scm_is_available() {
- analysis(QProfile.NO_RULES, SourceCode.INITIAL, ScannerFeature.SCM, ScannerFeature.EXPLICIT_DATE_1);
- analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.SCM, ScannerFeature.EXPLICIT_DATE_2);
-
- assertNumberOfIssues(3);
- assertIssueCreationDate(Component.ForeverAndModified, IssueCreationDate.ForeverAndModified_R2);
- assertIssueCreationDate(Component.ForeverAndUnmodified, IssueCreationDate.ForeverAndUnmodified_R1);
- assertIssueCreationDate(Component.OnlyInChanged, IssueCreationDate.OnlyInChanged_R1);
- }
-
- @Test
- public void use_explicit_date_for_issues_related_to_new_rules_if_scm_is_not_available() {
- analysis(QProfile.NO_RULES, SourceCode.INITIAL, ScannerFeature.EXPLICIT_DATE_1);
- analysis(QProfile.ONE_RULE, SourceCode.CHANGED, ScannerFeature.EXPLICIT_DATE_2);
-
- assertNumberOfIssues(3);
- assertIssueCreationDates(COMPONENTS_OF_SOURCE_CHANGED, IssueCreationDate.EXPLICIT_DATE_2);
- }
-
- private void analysis(QProfile qProfile, SourceCode sourceCode, ScannerFeature... scm) {
- ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource(qProfile.path));
- server.associateProjectToQualityProfile(SAMPLE_PROJECT_KEY, LANGUAGE_XOO, SAMPLE_QUALITY_PROFILE_NAME);
-
- SonarScanner scanner = SonarScanner.create(projectDir(sourceCode.path));
- Arrays.stream(scm).forEach(s -> s.configure(scanner));
- ORCHESTRATOR.executeBuild(scanner);
- }
-
- private static void assertNoIssue() {
- assertNumberOfIssues(0);
- }
-
- private static void assertNumberOfIssues(int number) {
- assertThat(getIssues(issueQuery())).hasSize(number);
- }
-
- private static void assertIssueCreationDate(Component component, IssueCreationDate expectedDate) {
- assertIssueCreationDates(new Component[] {component}, expectedDate);
- }
-
- private static void assertIssueCreationDates(Component[] components, IssueCreationDate expectedDate) {
- String[] keys = Arrays.stream(components).map(Component::getKey).toArray(String[]::new);
- List<Issue> issues = getIssues(issueQuery().components(keys));
- Date[] dates = Arrays.stream(components).map(x -> expectedDate.getDate()).toArray(Date[]::new);
-
- assertThat(issues)
- .extracting(Issue::creationDate)
- .containsExactly(dates);
- }
-
- private static List<Issue> getIssues(IssueQuery query) {
- return issueClient().find(query).list();
- }
-
- private static IssueQuery issueQuery() {
- return IssueQuery.create().statuses(ISSUE_STATUS_OPEN);
- }
-
- private static Date dateTimeParse(String expectedDate) {
- try {
- return new SimpleDateFormat(DATETIME_FORMAT).parse(expectedDate);
- } catch (ParseException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static Date dateParse(String expectedDate) {
- try {
- return new SimpleDateFormat(DATE_FORMAT).parse(expectedDate);
- } catch (ParseException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static String todayMinusDays(int numberOfDays) {
- return DateTimeFormatter.ofPattern(DATE_FORMAT).format(LocalDate.now().atStartOfDay().minusDays(numberOfDays));
- }
-
- private enum SourceCode {
- INITIAL("issue/creationDateSampleInitial"),
- CHANGED("issue/creationDateSampleChanged"),
- ;
-
- private final String path;
-
- private SourceCode(String path) {
- this.path = path;
- }
- }
-
- private enum Component {
- OnlyInInitial("creation-date-sample:src/main/xoo/sample/OnlyInInitial.xoo"),
- ForeverAndModified("creation-date-sample:src/main/xoo/sample/ForeverAndModified.xoo"),
- ForeverAndUnmodified("creation-date-sample:src/main/xoo/sample/ForeverAndUnmodified.xoo"),
- OnlyInChanged("creation-date-sample:src/main/xoo/sample/OnlyInChanged.xoo"),
- ;
- private final String key;
-
- private Component(String key) {
- this.key = key;
- }
-
- public String getKey() {
- return key;
- }
- }
-
- private static final Component[] COMPONENTS_OF_SOURCE_INITIAL = {Component.OnlyInInitial, Component.ForeverAndModified, Component.ForeverAndUnmodified};
- private static final Component[] COMPONENTS_OF_SOURCE_CHANGED = {Component.ForeverAndModified, Component.ForeverAndUnmodified, Component.OnlyInChanged};
-
- private enum QProfile {
- ONE_RULE("/issue/IssueCreationDateTest/one-rule.xml"),
- NO_RULES("/issue/IssueCreationDateTest/no-rules.xml"),
- ;
-
- private final String path;
-
- private QProfile(String path) {
- this.path = path;
- }
- }
-
- private enum ScannerFeature {
- SCM {
- @Override
- void configure(SonarScanner scanner) {
- scanner
- .setProperty("sonar.scm.provider", "xoo")
- .setProperty("sonar.scm.disabled", "false");
- }
- },
- EXPLICIT_DATE_1 {
- @Override
- void configure(SonarScanner scanner) {
- scanner
- .setProperty("sonar.projectDate", SAMPLE_EXPLICIT_DATE_1);
- }
- },
- EXPLICIT_DATE_2 {
- @Override
- void configure(SonarScanner scanner) {
- scanner
- .setProperty("sonar.projectDate", SAMPLE_EXPLICIT_DATE_2);
- }
- },
- ;
-
- void configure(SonarScanner scanner) {
- }
- }
-
- private enum IssueCreationDate {
- OnlyInInitial_R1(dateTimeParse("2001-01-01T00:00:00+0000")),
- ForeverAndUnmodified_R1(dateTimeParse("2002-01-01T00:00:00+0000")),
- ForeverAndModified_R1(dateTimeParse("2003-01-01T00:00:00+0000")),
- ForeverAndModified_R2(dateTimeParse("2004-01-01T00:00:00+0000")),
- OnlyInChanged_R1(dateTimeParse("2005-01-01T00:00:00+0000")),
- EXPLICIT_DATE_1(dateParse(SAMPLE_EXPLICIT_DATE_1)),
- EXPLICIT_DATE_2(dateParse(SAMPLE_EXPLICIT_DATE_2)),
- FIRST_ANALYSIS {
- @Override
- Date getDate() {
- return getAnalysisDate(l -> {
- if (l.isEmpty()) {
- return Optional.empty();
- }
- return Optional.of(l.get(l.size() - 1));
- });
- }
- },
- LATEST_ANALYSIS {
- @Override
- Date getDate() {
- return getAnalysisDate(l -> {
- if (l.size() > 0) {
- return Optional.of(l.get(0));
- }
- return Optional.empty();
- });
- }
- },
- ;
-
- private final Date date;
-
- private IssueCreationDate() {
- this.date = null;
- }
-
- private IssueCreationDate(Date date) {
- this.date = date;
- }
-
- Date getDate() {
- return date;
- }
-
- private static Date getAnalysisDate(Function<List<ProjectAnalyses.Analysis>, Optional<ProjectAnalyses.Analysis>> chooseItem) {
- return Optional.of(
- ItUtils.newWsClient(ORCHESTRATOR)
- .projectAnalysis()
- .search(SearchRequest.builder().setProject(SAMPLE_PROJECT_KEY).build())
- .getAnalysesList())
- .flatMap(chooseItem)
- .map(ProjectAnalyses.Analysis::getDate)
- .map(IssueCreationDateTest::dateTimeParse)
- .orElseThrow(() -> new IllegalStateException("There is no analysis"));
- }
- }
-}
--- /dev/null
+<profile>
+ <name>creation-date-plugin</name>
+ <language>xoo</language>
+ <rules>
+ <rule>
+ <repositoryKey>back</repositoryKey>
+ <key>Rule</key>
+ <priority>MAJOR</priority>
+ </rule>
+ </rules>
+</profile>
--- /dev/null
+<profile>
+ <name>creation-date-quality-profile</name>
+ <language>xoo</language>
+ <rules>
+ </rules>
+</profile>
--- /dev/null
+<profile>
+ <name>creation-date-quality-profile</name>
+ <language>xoo</language>
+ <rules>
+ <rule>
+ <repositoryKey>xoo</repositoryKey>
+ <key>OneIssuePerFile</key>
+ <priority>MAJOR</priority>
+ </rule>
+ </rules>
+</profile>
+++ /dev/null
-<profile>
- <name>creation-date-quality-profile</name>
- <language>xoo</language>
- <rules>
- </rules>
-</profile>
+++ /dev/null
-<profile>
- <name>creation-date-quality-profile</name>
- <language>xoo</language>
- <rules>
- <rule>
- <repositoryKey>xoo</repositoryKey>
- <key>OneIssuePerFile</key>
- <priority>MAJOR</priority>
- </rule>
- </rules>
-</profile>