]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10647 Add ITs
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Tue, 1 May 2018 15:34:04 +0000 (17:34 +0200)
committerSonarTech <sonartech@sonarsource.com>
Wed, 9 May 2018 18:20:46 +0000 (20:20 +0200)
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SignificantCodeSensor.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/lang/SignificantCodeSensorTest.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/source/SourceLinesHashRepository.java
tests/projects/significantCode/sample-xoo/file.xoo [new file with mode: 0644]
tests/projects/significantCode/sample-xoo/file_additional_line.xoo [new file with mode: 0644]
tests/projects/significantCode/sample-xoo/file_changed.xoo [new file with mode: 0644]
tests/src/test/java/org/sonarqube/tests/source/SignificantCodeTest.java [new file with mode: 0644]

index 9cb185a943e811cc09600e9c5d076e263d51c04c..3b626b8ed9f8f8e3511df82de69c9a5c0f00ba78 100644 (file)
@@ -34,6 +34,7 @@ import org.sonar.xoo.global.GlobalSensor;
 import org.sonar.xoo.lang.CpdTokenizerSensor;
 import org.sonar.xoo.lang.LineMeasureSensor;
 import org.sonar.xoo.lang.MeasureSensor;
+import org.sonar.xoo.lang.SignificantCodeSensor;
 import org.sonar.xoo.lang.SymbolReferencesSensor;
 import org.sonar.xoo.lang.SyntaxHighlightingSensor;
 import org.sonar.xoo.lang.XooCpdMapping;
@@ -173,7 +174,9 @@ public class XooPlugin implements Plugin {
       context.addExtension(GlobalSensor.class);
     }
     if (context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(7, 2))) {
-      context.addExtension(OneExternalIssuePerLineSensor.class);
+      context.addExtensions(
+        OneExternalIssuePerLineSensor.class,
+        SignificantCodeSensor.class);
     }
   }
 
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SignificantCodeSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SignificantCodeSensor.java
new file mode 100644 (file)
index 0000000..108d232
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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.xoo.lang;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+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.code.NewSignificantCode;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.xoo.Xoo;
+
+public class SignificantCodeSensor implements Sensor {
+  private static final Logger LOG = Loggers.get(SignificantCodeSensor.class);
+  private static final String FILE_EXTENSION = ".significantCode";
+
+  @Override
+  public void describe(SensorDescriptor descriptor) {
+    descriptor
+      .name("Xoo Significant Code Ranges Sensor")
+      .onlyOnLanguages(Xoo.KEY);
+  }
+
+  @Override
+  public void execute(SensorContext context) {
+    for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) {
+      processSignificantCodeFile(file, context);
+    }
+  }
+
+  private void processSignificantCodeFile(InputFile inputFile, SensorContext context) {
+    Path ioFile = inputFile.path();
+    Path significantCodeFile = ioFile.resolveSibling(ioFile.getFileName() + FILE_EXTENSION).toAbsolutePath();
+    if (Files.exists(significantCodeFile) && Files.isRegularFile(significantCodeFile)) {
+      LOG.debug("Processing " + significantCodeFile.toString());
+      try {
+        List<String> lines = Files.readAllLines(significantCodeFile, context.fileSystem().encoding());
+        NewSignificantCode significantCode = context.newSignificantCode()
+          .onFile(inputFile);
+        for (String line : lines) {
+          if (StringUtils.isBlank(line) || line.startsWith("#")) {
+            continue;
+          }
+          processLine(line, inputFile, significantCode);
+        }
+        significantCode.save();
+      } catch (IOException e) {
+        throw new IllegalStateException(e);
+      }
+    }
+  }
+
+  private void processLine(String fileLine, InputFile inputFile, NewSignificantCode significantCode) {
+    String[] textPointer = fileLine.split(",");
+    if (textPointer.length != 3) {
+      throw new IllegalStateException("Invalid format in error file");
+    }
+
+    try {
+      int line = Integer.parseInt(textPointer[0]);
+      int startLineOffset = Integer.parseInt(textPointer[1]);
+      int endLineOffset = Integer.parseInt(textPointer[2]);
+
+      significantCode.addRange(inputFile.newRange(line, startLineOffset, line, endLineOffset));
+
+    } catch (NumberFormatException e) {
+      throw new IllegalStateException("Invalid format in error file", e);
+    }
+  }
+
+}
index 9be43f0b4f366ee2c21f730d5ed6a37de185cf9d..dacd5b3f575aa932a7c50316c648817b21bc84e3 100644 (file)
@@ -62,6 +62,6 @@ public class XooPluginTest {
     SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("7.2"), SonarQubeSide.SCANNER);
     Plugin.Context context = new PluginContextImpl.Builder().setSonarRuntime(runtime).build();
     new XooPlugin().define(context);
-    assertThat(context.getExtensions()).hasSize(53).contains(OneExternalIssuePerLineSensor.class);
+    assertThat(context.getExtensions()).hasSize(54).contains(OneExternalIssuePerLineSensor.class);
   }
 }
diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/lang/SignificantCodeSensorTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/lang/SignificantCodeSensorTest.java
new file mode 100644 (file)
index 0000000..115c1c0
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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.xoo.lang;
+
+import java.io.File;
+import java.io.IOException;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.TextRange;
+import org.sonar.api.batch.fs.internal.DefaultTextPointer;
+import org.sonar.api.batch.fs.internal.DefaultTextRange;
+import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
+import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
+import org.sonar.api.batch.sensor.internal.SensorContextTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SignificantCodeSensorTest {
+  private SignificantCodeSensor sensor;
+  private SensorContextTester context;
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private File baseDir;
+
+  private InputFile inputFile;
+
+  @Before
+  public void prepare() throws IOException {
+    baseDir = temp.newFolder();
+    sensor = new SignificantCodeSensor();
+    context = SensorContextTester.create(baseDir);
+    inputFile = new TestInputFileBuilder("foo", baseDir, new File(baseDir, "src/foo.xoo"))
+      .setLanguage("xoo")
+      .setContents("some lines\nof code\nyet another line")
+      .build();
+  }
+
+  @Test
+  public void testDescriptor() {
+    sensor.describe(new DefaultSensorDescriptor());
+  }
+
+  @Test
+  public void testNoExceptionIfNoFileWithOffsets() {
+    context.fileSystem().add(inputFile);
+    sensor.execute(context);
+  }
+
+  @Test
+  public void testExecution() throws IOException {
+    File significantCode = new File(baseDir, "src/foo.xoo.significantCode");
+    FileUtils.write(significantCode, "1,1,4\n2,2,5");
+    context.fileSystem().add(inputFile);
+
+    sensor.execute(context);
+
+    assertThat(context.significantCodeTextRange("foo:src/foo.xoo", 1)).isEqualTo(range(1, 1, 4));
+    assertThat(context.significantCodeTextRange("foo:src/foo.xoo", 2)).isEqualTo(range(2, 2, 5));
+  }
+
+  private static TextRange range(int line, int startOffset, int endOffset) {
+    return new DefaultTextRange(new DefaultTextPointer(line, startOffset), new DefaultTextPointer(line, endOffset));
+  }
+}
index 60fc808f39325ecf09f74b36bf338919d4f9a267..8c616407a4e307ab510d0f980553e4b0139c2b44 100644 (file)
@@ -31,7 +31,7 @@ public interface SourceLinesHashRepository {
   List<String> getMatchingDB(Component component);
 
   /**
-   * The line processor will return line hashes taking into account significant code (if it was provided by a code analyzer).
+   * The line computer will compute line hashes taking into account significant code (if it was provided by a code analyzer).
    * It will use a cached value, if possible. If it's generated, it's not cached since it's assumed that it won't be 
    * needed again after it is persisted.
    */
diff --git a/tests/projects/significantCode/sample-xoo/file.xoo b/tests/projects/significantCode/sample-xoo/file.xoo
new file mode 100644 (file)
index 0000000..5c50979
--- /dev/null
@@ -0,0 +1,11 @@
+000100******************************************************************00010000
+000200 IDENTIFICATION DIVISION.                                         00020000
+000300******************************************************************00030000
+000400 PROGRAM-ID.    TEST-1.                                           00040000
+000500 AUTHOR.        SONARSOURCE.                                      00050000
+000600 DATE-COMPILED. APRIL 2018.                                       00060000
+000700 (this line has no significant code)                              00070009
+000800                                                                  00080000
+000900******************************************************************00090000
+001000*                                                                *00100000
+001100     MOVE WS-NB1       TO   WS-NB2.                               00110000
\ No newline at end of file
diff --git a/tests/projects/significantCode/sample-xoo/file_additional_line.xoo b/tests/projects/significantCode/sample-xoo/file_additional_line.xoo
new file mode 100644 (file)
index 0000000..5f3293b
--- /dev/null
@@ -0,0 +1,12 @@
+000100******************************************************************00010000
+000200 IDENTIFICATION DIVISION.                                         00020000
+000300******************************************************************00030000
+000400 PROGRAM-ID.    TEST-1.                                           00040000
+000500 AUTHOR.        SONARSOURCE.                                      00050000
+000600 DATE-COMPILED. APRIL 2018.                                       00060000
+000700 (this line has no significant code)                              00070009
+000800                                                                  00080000
+000800       THIS LINE WAS ADDED                                        00080000
+000900******************************************************************00090000
+001000*                                                                *00100000
+001100     MOVE WS-NB1       TO   WS-NB2.                               00110000
\ No newline at end of file
diff --git a/tests/projects/significantCode/sample-xoo/file_changed.xoo b/tests/projects/significantCode/sample-xoo/file_changed.xoo
new file mode 100644 (file)
index 0000000..aeb1f6f
--- /dev/null
@@ -0,0 +1,11 @@
+000109******************************************************************00010000
+000290 IDENTIFICATION DIVISION.                                         00020000
+000900******************************************************************00030000
+009400 PROGRAM-ID.    TEST-1.  - line changed                           00040000
+090500 AUTHOR.        SONARSOURCE.                                      00050000
+900600 DATE-COMPILED. APRIL 2018.                                       00060000
+000700 (this line has no significant code) - line changed               00070009
+000800                                                                  00080090
+000900******************************************************************00090900
+001000*                                                                *00109000
+001100     MOVE WS-NB1       TO   WS-NB2.                               00190000
\ No newline at end of file
diff --git a/tests/src/test/java/org/sonarqube/tests/source/SignificantCodeTest.java b/tests/src/test/java/org/sonarqube/tests/source/SignificantCodeTest.java
new file mode 100644 (file)
index 0000000..833e57e
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * 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.sonarqube.tests.source;
+
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.build.SonarScanner;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonarqube.qa.util.Tester;
+import org.sonarqube.ws.Issues.Issue;
+import org.sonarqube.ws.Issues.SearchWsResponse;
+import org.sonarqube.ws.client.issues.SearchRequest;
+import org.sonarqube.ws.client.sources.HashRequest;
+import util.ItUtils;
+
+import static org.apache.commons.io.FileUtils.copyDirectory;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonarqube.tests.source.SourceSuite.ORCHESTRATOR;
+import static util.ItUtils.projectDir;
+
+public class SignificantCodeTest {
+  private final String PROJECT_DIRECTORY = "sample-xoo";
+  private final String PROJECT_NAME = "sample-xoo";
+  private final String FILE_BASE = "file.xoo";
+  private final String FILE_CHANGED = "file_changed.xoo";
+  private final String FILE_ADDITIONAL_LINE = "file_additional_line.xoo";
+  private final String FILE_TO_ANALYSE = PROJECT_NAME + ":" + FILE_BASE;
+
+  @ClassRule
+  public static Orchestrator orchestrator = ORCHESTRATOR;
+
+  private SourceScmWS ws;
+
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Rule
+  public Tester tester = new Tester(orchestrator);
+
+  @Before
+  public void setUp() {
+    ORCHESTRATOR.resetData();
+    ws = new SourceScmWS(tester);
+  }
+
+  @Test
+  public void changes_in_margins_should_not_change_line_date() throws ParseException, IOException {
+    Path projectDirectory = disposableWorkspaceFor(PROJECT_DIRECTORY);
+    deployFileWithsignificantCodeRanges(projectDirectory, 7);
+
+    // 1st run
+    SonarScanner scanner = SonarScanner.create(projectDirectory.toFile())
+      .setProjectKey(PROJECT_NAME)
+      .setSourceDirs(FILE_BASE);
+    orchestrator.executeBuild(scanner);
+    Map<Integer, LineData> scmData1 = ws.getScmData(FILE_TO_ANALYSE);
+
+    // 2nd run
+    deployChangedFile(projectDirectory, FILE_CHANGED);
+
+    scanner = SonarScanner.create(projectDirectory.toFile())
+      .setProjectKey(PROJECT_NAME)
+      .setSourceDirs(FILE_BASE);
+    orchestrator.executeBuild(scanner);
+
+    // Check that only line 4 is considered as changed
+    Map<Integer, LineData> scmData2 = ws.getScmData(FILE_TO_ANALYSE);
+
+    for (Map.Entry<Integer, LineData> e : scmData2.entrySet()) {
+      if (e.getKey() == 4) {
+        assertThat(e.getValue().date).isAfter(scmData1.get(1).date);
+      } else {
+        assertThat(e.getValue().date).isEqualTo(scmData1.get(1).date);
+      }
+    }
+  }
+
+  @Test
+  public void migration_should_not_affect_unchanged_lines() throws IOException, ParseException {
+    Path projectDirectory = disposableWorkspaceFor(PROJECT_DIRECTORY);
+
+    // 1st run
+    SonarScanner scanner = SonarScanner.create(projectDirectory.toFile())
+      .setProjectKey(PROJECT_NAME)
+      .setSourceDirs(FILE_BASE);
+    orchestrator.executeBuild(scanner);
+    Map<Integer, LineData> scmData1 = ws.getScmData(FILE_TO_ANALYSE);
+    String[] lineHashes1 = getLineHashes();
+
+    // 2nd run
+    deployFileWithsignificantCodeRanges(projectDirectory, 7);
+    scanner = SonarScanner.create(projectDirectory.toFile())
+      .setProjectKey(PROJECT_NAME)
+      .setSourceDirs(FILE_BASE);
+    orchestrator.executeBuild(scanner);
+
+    // Check that no line was modified
+    Map<Integer, LineData> scmData2 = ws.getScmData(FILE_TO_ANALYSE);
+
+    for (Map.Entry<Integer, LineData> e : scmData2.entrySet()) {
+      assertThat(e.getValue().date).isEqualTo(scmData1.get(1).date);
+    }
+
+    // Check that line hashes changed for all lines, even though the file didn't change
+    String[] lineHashes2 = getLineHashes();
+
+    for (int i = 0; i < lineHashes2.length; i++) {
+      assertThat(lineHashes2[i]).isNotEqualTo(lineHashes1[i]);
+    }
+  }
+
+  @Test
+  public void issue_tracking() throws Exception {
+    ORCHESTRATOR.getServer().provisionProject(PROJECT_NAME, PROJECT_NAME);
+    ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/one-xoo-issue-per-line.xml"));
+    ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_NAME, "xoo", "one-xoo-issue-per-line");
+
+    Path projectDirectory = disposableWorkspaceFor(PROJECT_DIRECTORY);
+
+    // 1st run
+    deployFileWithsignificantCodeRanges(projectDirectory, 7);
+    SonarScanner scanner = SonarScanner.create(projectDirectory.toFile())
+      .setProjectKey(PROJECT_NAME)
+      .setSourceDirs(FILE_BASE);
+    orchestrator.executeBuild(scanner);
+
+    Map<Integer, Issue> issues1 = getIssues().stream().collect(Collectors.toMap(i -> i.getLine(), i -> i));
+
+    // 2nd run
+    deployChangedFile(projectDirectory, FILE_ADDITIONAL_LINE);
+    deployFileWithsignificantCodeRanges(projectDirectory, 7);
+
+    scanner = SonarScanner.create(projectDirectory.toFile())
+      .setProjectKey(PROJECT_NAME)
+      .setSourceDirs(FILE_BASE);
+    orchestrator.executeBuild(scanner);
+
+    List<Issue> issues2 = getIssues();
+
+    // Check that all issues were tracking except the issue on the new line
+    assertThat(issues1.size()).isEqualTo(11);
+    assertThat(issues2.size()).isEqualTo(12);
+
+    for (Issue issue : issues2) {
+      if (issue.getLine() < 9) {
+        assertThat(issue.getKey()).isEqualTo(issues1.get(issue.getLine()).getKey());
+      } else if (issue.getLine() == 9) {
+        // this is the new issue
+        List<String> keys = issues1.values().stream().map(i -> i.getKey()).collect(Collectors.toList());
+        assertThat(issue.getKey()).isNotIn(keys);
+      } else {
+        assertThat(issue.getKey()).isEqualTo(issues1.get(issue.getLine() - 1).getKey());
+      }
+    }
+
+  }
+
+  private String[] getLineHashes() {
+    String hashes = tester.wsClient().sources().hash(new HashRequest().setKey(FILE_TO_ANALYSE));
+    return StringUtils.split(hashes, "\n");
+  }
+
+  private List<Issue> getIssues() {
+    SearchWsResponse response = tester.wsClient().issues().search(new SearchRequest().setComponentKeys(Collections.singletonList(FILE_TO_ANALYSE)));
+    return response.getIssuesList();
+  }
+
+  private void deployFileWithsignificantCodeRanges(Path projectBaseDir, int lineWithoutSignificantCode) throws IOException {
+    Path file = projectBaseDir.resolve("file.xoo");
+    Path significantLineRangesFile = projectBaseDir.resolve("file.xoo.significantCode");
+
+    int numLines = Files.readAllLines(file).size();
+    List<String> lines = IntStream.rangeClosed(1, numLines)
+      .mapToObj(i -> i == lineWithoutSignificantCode ? "" : i + ",6,72")
+      .collect(Collectors.toList());
+
+    Files.write(significantLineRangesFile, lines);
+  }
+
+  private void deployChangedFile(Path projectBaseDir, String fileName) throws IOException {
+    Path file = projectBaseDir.resolve(FILE_BASE);
+    Path file_changed = projectBaseDir.resolve(fileName);
+    Files.move(file_changed, file, StandardCopyOption.REPLACE_EXISTING);
+  }
+
+  private Path disposableWorkspaceFor(String project) throws IOException {
+    File origin = projectDir("significantCode/" + project);
+    copyDirectory(origin.getParentFile(), temporaryFolder.getRoot());
+    return temporaryFolder.getRoot().toPath().resolve(project);
+  }
+}