diff options
Diffstat (limited to 'sonar-scanner-protocol')
28 files changed, 3128 insertions, 0 deletions
diff --git a/sonar-scanner-protocol/pom.xml b/sonar-scanner-protocol/pom.xml new file mode 100644 index 00000000000..ed015f33bc7 --- /dev/null +++ b/sonar-scanner-protocol/pom.xml @@ -0,0 +1,111 @@ +<?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>5.5-SNAPSHOT</version> + </parent> + + <artifactId>sonar-scanner-protocol</artifactId> + <name>SonarQube :: Scanner :: Protocol</name> + + <description>Classes used for communication between scanner and server</description> + + <properties> + <!-- Viewer is for our internal use. This is not production code and mostly generated with Eclipse GUI builder --> + <sonar.exclusions>target/generated-sources/**/*,src/main/java/org/sonar/scanner/protocol/viewer/**</sonar.exclusions> + <sonar.test.exclusions>target/generated-test-sources/**/*</sonar.test.exclusions> + </properties> + + <dependencies> + <dependency> + <groupId>net.jpountz.lz4</groupId> + <artifactId>lz4</artifactId> + </dependency> + <dependency> + <groupId>com.google.protobuf</groupId> + <artifactId>protobuf-java</artifactId> + </dependency> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sonar-core</artifactId> + </dependency> + + <!-- unit tests --> + <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> + <dependency> + <groupId>net.javacrumbs.json-unit</groupId> + <artifactId>json-unit-assertj</artifactId> + <version>0.0.15</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <skipTests>${skipBatchTests}</skipTests> + </configuration> + </plugin> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <finalName>scanner-report-viewer-${project.version}</finalName> + <archive> + <manifest> + <mainClass>org.sonar.scanner.protocol.viewer.ScannerReportViewerApp</mainClass> + </manifest> + </archive> + <descriptors> + <descriptor>src/main/assembly/viewer.xml</descriptor> + </descriptors> + </configuration> + <executions> + <execution> + <id>make-assembly</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/sonar-scanner-protocol/src/main/assembly/viewer.xml b/sonar-scanner-protocol/src/main/assembly/viewer.xml new file mode 100644 index 00000000000..dcb1ad07c78 --- /dev/null +++ b/sonar-scanner-protocol/src/main/assembly/viewer.xml @@ -0,0 +1,22 @@ +<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd"> + <id>full</id> + <formats> + <format>jar</format> + </formats> + <includeBaseDirectory>false</includeBaseDirectory> + <dependencySets> + <dependencySet> + <outputDirectory>/</outputDirectory> + <useProjectArtifact>true</useProjectArtifact> + <unpack>true</unpack> + <scope>runtime</scope> + </dependencySet> + </dependencySets> + <fileSets> + <fileSet> + <directory>${project.build.outputDirectory}</directory> + </fileSet> + </fileSets> +</assembly>
\ No newline at end of file diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/GsonHelper.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/GsonHelper.java new file mode 100644 index 00000000000..890db7e52e1 --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/GsonHelper.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class GsonHelper { + + private GsonHelper() { + // Utility class + } + + public static Gson create() { + return new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").setPrettyPrinting().create(); + } + +} diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/FileData.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/FileData.java new file mode 100644 index 00000000000..be9d993b5c3 --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/FileData.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.input; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +public class FileData { + + private final String hash; + private final String revision; + + public FileData(@Nullable String hash, @Nullable String revision) { + this.hash = hash; + this.revision = revision; + } + + @CheckForNull + public String hash() { + return hash; + } + + @CheckForNull + public String revision() { + return revision; + } +} diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/GlobalRepositories.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/GlobalRepositories.java new file mode 100644 index 00000000000..7bf3bdb643a --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/GlobalRepositories.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.input; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.sonar.scanner.protocol.GsonHelper; + +/** + * Container for all global data going from server to batch. + * This is not an API since server and batch always share the same version. + */ +public class GlobalRepositories { + + private long timestamp; + private Collection<Metric> metrics = new ArrayList<>(); + private Map<String, String> globalSettings = new HashMap<>(); + + public Map<String, String> globalSettings() { + return globalSettings; + } + + public GlobalRepositories addGlobalSetting(String key, String value) { + globalSettings.put(key, value); + return this; + } + + public Collection<Metric> metrics() { + return metrics; + } + + public GlobalRepositories addMetric(Metric metric) { + metrics.add(metric); + return this; + } + + public long timestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public String toJson() { + return GsonHelper.create().toJson(this); + } + + public static GlobalRepositories fromJson(String json) { + return GsonHelper.create().fromJson(json, GlobalRepositories.class); + } + +} diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/Metric.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/Metric.java new file mode 100644 index 00000000000..24c68c76414 --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/Metric.java @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.input; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +public class Metric { + + private final int id; + + private final String key; + + private final String valueType; + + private final String description; + + private final int direction; + + private final String name; + + private final boolean qualitative; + + private final boolean userManaged; + + private final Double worstValue; + + private final Double bestValue; + + private final boolean optimizedBestValue; + + public Metric(int id, + String key, + String valueType, + @Nullable String description, + int direction, + String name, + boolean qualitative, + boolean userManaged, + @Nullable Double worstValue, + @Nullable Double bestValue, + boolean optimizedBestValue) { + this.id = id; + this.key = key; + this.valueType = valueType; + this.description = description; + this.direction = direction; + this.name = name; + this.qualitative = qualitative; + this.userManaged = userManaged; + this.worstValue = worstValue; + this.bestValue = bestValue; + this.optimizedBestValue = optimizedBestValue; + } + + public int id() { + return id; + } + + public String key() { + return key; + } + + public String valueType() { + return valueType; + } + + @CheckForNull + public String description() { + return description; + } + + public int direction() { + return direction; + } + + public String name() { + return name; + } + + public boolean isQualitative() { + return qualitative; + } + + public boolean isUserManaged() { + return userManaged; + } + + @CheckForNull + public Double worstValue() { + return worstValue; + } + + @CheckForNull + public Double bestValue() { + return bestValue; + } + + public boolean isOptimizedBestValue() { + return optimizedBestValue; + } + +} diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/ProjectRepositories.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/ProjectRepositories.java new file mode 100644 index 00000000000..d248e2ec290 --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/ProjectRepositories.java @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.input; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.scanner.protocol.GsonHelper; + +/** + * Container for all project data going from server to batch. + * This is not an API since server and batch always share the same version. + */ +public class ProjectRepositories { + + private long timestamp; + private boolean exists; + private Map<String, Map<String, String>> settingsByModule = new HashMap<>(); + private Map<String, Map<String, FileData>> fileDataByModuleAndPath = new HashMap<>(); + private Date lastAnalysisDate; + + public Map<String, String> settings(String moduleKey) { + return settingsByModule.containsKey(moduleKey) ? settingsByModule.get(moduleKey) : Collections.<String, String>emptyMap(); + } + + public Map<String, Map<String, String>> settings() { + return settingsByModule; + } + + public ProjectRepositories addSettings(String moduleKey, Map<String, String> settings) { + Map<String, String> existingSettings = settingsByModule.get(moduleKey); + if (existingSettings == null) { + existingSettings = new HashMap<>(); + settingsByModule.put(moduleKey, existingSettings); + } + existingSettings.putAll(settings); + return this; + } + + public boolean exists() { + return exists; + } + + public Map<String, Map<String, FileData>> fileDataByModuleAndPath() { + return fileDataByModuleAndPath; + } + + public Map<String, FileData> fileDataByPath(String moduleKey) { + return fileDataByModuleAndPath.containsKey(moduleKey) ? fileDataByModuleAndPath.get(moduleKey) : Collections.<String, FileData>emptyMap(); + } + + public ProjectRepositories addFileData(String moduleKey, @Nullable String path, FileData fileData) { + if (path == null || (fileData.hash() == null && fileData.revision() == null)) { + return this; + } + + Map<String, FileData> existingFileDataByPath = fileDataByModuleAndPath.get(moduleKey); + if (existingFileDataByPath == null) { + existingFileDataByPath = new HashMap<>(); + fileDataByModuleAndPath.put(moduleKey, existingFileDataByPath); + } + existingFileDataByPath.put(path, fileData); + return this; + } + + @CheckForNull + public FileData fileData(String projectKey, String path) { + return fileDataByPath(projectKey).get(path); + } + + public long timestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @CheckForNull + public Date lastAnalysisDate() { + return lastAnalysisDate; + } + + public void setLastAnalysisDate(@Nullable Date lastAnalysisDate) { + this.lastAnalysisDate = lastAnalysisDate; + } + + public String toJson() { + return GsonHelper.create().toJson(this); + } + + public static ProjectRepositories fromJson(String json) { + return GsonHelper.create().fromJson(json, ProjectRepositories.class); + } +} diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/package-info.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/package-info.java new file mode 100644 index 00000000000..8df79ed33e1 --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.input; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java new file mode 100644 index 00000000000..c4d3197cb43 --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.output; + +import java.io.File; + +/** + * Structure of files in the zipped report + */ +public class FileStructure { + + public enum Domain { + ISSUES("issues-", Domain.PB), + COMPONENT("component-", Domain.PB), + MEASURES("measures-", Domain.PB), + DUPLICATIONS("duplications-", Domain.PB), + CPD_TEXT_BLOCKS("cpd-text-block-", Domain.PB), + SYNTAX_HIGHLIGHTINGS("syntax-highlightings-", Domain.PB), + CHANGESETS("changesets-", Domain.PB), + SYMBOLS("symbols-", Domain.PB), + COVERAGES("coverages-", Domain.PB), + TESTS("tests-", Domain.PB), + COVERAGE_DETAILS("coverage-details-", Domain.PB), + SOURCE("source-", ".txt"); + + private static final String PB = ".pb"; + private final String filePrefix; + private final String fileSuffix; + + Domain(String filePrefix, String fileSuffix) { + this.filePrefix = filePrefix; + this.fileSuffix = fileSuffix; + } + } + + private final File dir; + + public FileStructure(File dir) { + if (!dir.exists() || !dir.isDirectory()) { + throw new IllegalArgumentException("Directory of analysis report does not exist: " + dir); + } + this.dir = dir; + } + + public File metadataFile() { + return new File(dir, "metadata.pb"); + } + + public File analysisLog() { + return new File(dir, "analysis.log"); + } + + public File activeRules() { + return new File(dir, "activerules.pb"); + } + + public File fileFor(Domain domain, int componentRef) { + return new File(dir, domain.filePrefix + componentRef + domain.fileSuffix); + } + +} diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java new file mode 100644 index 00000000000..d47b94225ef --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java @@ -0,0 +1,172 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.output; + +import java.io.File; +import javax.annotation.CheckForNull; +import org.sonar.core.util.CloseableIterator; +import org.sonar.core.util.Protobuf; + +import static org.sonar.core.util.CloseableIterator.emptyCloseableIterator; + +public class ScannerReportReader { + + private final FileStructure fileStructure; + + public ScannerReportReader(File dir) { + this.fileStructure = new FileStructure(dir); + } + + public ScannerReport.Metadata readMetadata() { + File file = fileStructure.metadataFile(); + if (!fileExists(file)) { + throw new IllegalStateException("Metadata file is missing in analysis report: " + file); + } + return Protobuf.read(file, ScannerReport.Metadata.PARSER); + } + + public CloseableIterator<ScannerReport.ActiveRule> readActiveRules() { + File file = fileStructure.activeRules(); + if (!fileExists(file)) { + return emptyCloseableIterator(); + } + return Protobuf.readStream(file, ScannerReport.ActiveRule.PARSER); + } + + public CloseableIterator<ScannerReport.Measure> readComponentMeasures(int componentRef) { + File file = fileStructure.fileFor(FileStructure.Domain.MEASURES, componentRef); + if (fileExists(file)) { + return Protobuf.readStream(file, ScannerReport.Measure.PARSER); + } + return emptyCloseableIterator(); + } + + @CheckForNull + public ScannerReport.Changesets readChangesets(int componentRef) { + File file = fileStructure.fileFor(FileStructure.Domain.CHANGESETS, componentRef); + if (fileExists(file)) { + return Protobuf.read(file, ScannerReport.Changesets.PARSER); + } + return null; + } + + public ScannerReport.Component readComponent(int componentRef) { + File file = fileStructure.fileFor(FileStructure.Domain.COMPONENT, componentRef); + if (!fileExists(file)) { + throw new IllegalStateException("Unable to find report for component #" + componentRef + ". File does not exist: " + file); + } + return Protobuf.read(file, ScannerReport.Component.PARSER); + } + + public CloseableIterator<ScannerReport.Issue> readComponentIssues(int componentRef) { + File file = fileStructure.fileFor(FileStructure.Domain.ISSUES, componentRef); + if (fileExists(file)) { + return Protobuf.readStream(file, ScannerReport.Issue.PARSER); + } + return emptyCloseableIterator(); + } + + public CloseableIterator<ScannerReport.Duplication> readComponentDuplications(int componentRef) { + File file = fileStructure.fileFor(FileStructure.Domain.DUPLICATIONS, componentRef); + if (fileExists(file)) { + return Protobuf.readStream(file, ScannerReport.Duplication.PARSER); + } + return emptyCloseableIterator(); + } + + public CloseableIterator<ScannerReport.CpdTextBlock> readCpdTextBlocks(int componentRef) { + File file = fileStructure.fileFor(FileStructure.Domain.CPD_TEXT_BLOCKS, componentRef); + if (fileExists(file)) { + return Protobuf.readStream(file, ScannerReport.CpdTextBlock.parser()); + } + return emptyCloseableIterator(); + } + + public CloseableIterator<ScannerReport.Symbol> readComponentSymbols(int componentRef) { + File file = fileStructure.fileFor(FileStructure.Domain.SYMBOLS, componentRef); + if (fileExists(file)) { + return Protobuf.readStream(file, ScannerReport.Symbol.PARSER); + } + return emptyCloseableIterator(); + } + + public boolean hasSyntaxHighlighting(int componentRef) { + File file = fileStructure.fileFor(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, componentRef); + return file.exists(); + } + + public CloseableIterator<ScannerReport.SyntaxHighlighting> readComponentSyntaxHighlighting(int fileRef) { + File file = fileStructure.fileFor(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, fileRef); + if (fileExists(file)) { + return Protobuf.readStream(file, ScannerReport.SyntaxHighlighting.PARSER); + } + return emptyCloseableIterator(); + } + + public boolean hasCoverage(int componentRef) { + File file = fileStructure.fileFor(FileStructure.Domain.COVERAGES, componentRef); + return file.exists(); + } + + public CloseableIterator<ScannerReport.Coverage> readComponentCoverage(int fileRef) { + File file = fileStructure.fileFor(FileStructure.Domain.COVERAGES, fileRef); + if (fileExists(file)) { + return Protobuf.readStream(file, ScannerReport.Coverage.PARSER); + } + return emptyCloseableIterator(); + } + + @CheckForNull + public File readFileSource(int fileRef) { + File file = fileStructure.fileFor(FileStructure.Domain.SOURCE, fileRef); + if (fileExists(file)) { + return file; + } + return null; + } + + @CheckForNull + public File readTests(int testFileRef) { + File file = fileStructure.fileFor(FileStructure.Domain.TESTS, testFileRef); + if (fileExists(file)) { + return file; + } + + return null; + } + + @CheckForNull + public File readCoverageDetails(int testFileRef) { + File file = fileStructure.fileFor(FileStructure.Domain.COVERAGE_DETAILS, testFileRef); + if (fileExists(file)) { + return file; + } + + return null; + } + + private static boolean fileExists(File file) { + return file.exists() && file.isFile(); + } + + public FileStructure getFileStructure() { + return fileStructure; + } +} diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java new file mode 100644 index 00000000000..5cbba578c2a --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java @@ -0,0 +1,141 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.output; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import org.sonar.core.util.ContextException; +import org.sonar.core.util.Protobuf; + +public class ScannerReportWriter { + + private final FileStructure fileStructure; + + public ScannerReportWriter(File dir) { + if (!dir.exists() && !dir.mkdirs()) { + throw new IllegalStateException("Unable to create directory: " + dir); + } + this.fileStructure = new FileStructure(dir); + } + + public FileStructure getFileStructure() { + return fileStructure; + } + + public boolean hasComponentData(FileStructure.Domain domain, int componentRef) { + File file = fileStructure.fileFor(domain, componentRef); + return file.exists() && file.isFile(); + } + + /** + * Metadata is mandatory + */ + public File writeMetadata(ScannerReport.Metadata metadata) { + Protobuf.write(metadata, fileStructure.metadataFile()); + return fileStructure.metadataFile(); + } + + public File writeActiveRules(Iterable<ScannerReport.ActiveRule> activeRules) { + Protobuf.writeStream(activeRules, fileStructure.activeRules(), false); + return fileStructure.metadataFile(); + } + + public File writeComponent(ScannerReport.Component component) { + File file = fileStructure.fileFor(FileStructure.Domain.COMPONENT, component.getRef()); + Protobuf.write(component, file); + return file; + } + + public File writeComponentIssues(int componentRef, Iterable<ScannerReport.Issue> issues) { + File file = fileStructure.fileFor(FileStructure.Domain.ISSUES, componentRef); + Protobuf.writeStream(issues, file, false); + return file; + } + + public void appendComponentIssue(int componentRef, ScannerReport.Issue issue) { + File file = fileStructure.fileFor(FileStructure.Domain.ISSUES, componentRef); + try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file, true))) { + issue.writeDelimitedTo(out); + } catch (Exception e) { + throw ContextException.of("Unable to write issue", e).addContext("file", file); + } + } + + public File writeComponentMeasures(int componentRef, Iterable<ScannerReport.Measure> measures) { + File file = fileStructure.fileFor(FileStructure.Domain.MEASURES, componentRef); + Protobuf.writeStream(measures, file, false); + return file; + } + + public File writeComponentChangesets(ScannerReport.Changesets changesets) { + File file = fileStructure.fileFor(FileStructure.Domain.CHANGESETS, changesets.getComponentRef()); + Protobuf.write(changesets, file); + return file; + } + + public File writeComponentDuplications(int componentRef, Iterable<ScannerReport.Duplication> duplications) { + File file = fileStructure.fileFor(FileStructure.Domain.DUPLICATIONS, componentRef); + Protobuf.writeStream(duplications, file, false); + return file; + } + + public File writeCpdTextBlocks(int componentRef, Iterable<ScannerReport.CpdTextBlock> blocks) { + File file = fileStructure.fileFor(FileStructure.Domain.CPD_TEXT_BLOCKS, componentRef); + Protobuf.writeStream(blocks, file, false); + return file; + } + + public File writeComponentSymbols(int componentRef, Iterable<ScannerReport.Symbol> symbols) { + File file = fileStructure.fileFor(FileStructure.Domain.SYMBOLS, componentRef); + Protobuf.writeStream(symbols, file, false); + return file; + } + + public File writeComponentSyntaxHighlighting(int componentRef, Iterable<ScannerReport.SyntaxHighlighting> syntaxHighlightingRules) { + File file = fileStructure.fileFor(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, componentRef); + Protobuf.writeStream(syntaxHighlightingRules, file, false); + return file; + } + + public File writeComponentCoverage(int componentRef, Iterable<ScannerReport.Coverage> coverageList) { + File file = fileStructure.fileFor(FileStructure.Domain.COVERAGES, componentRef); + Protobuf.writeStream(coverageList, file, false); + return file; + } + + public File writeTests(int componentRef, Iterable<ScannerReport.Test> tests) { + File file = fileStructure.fileFor(FileStructure.Domain.TESTS, componentRef); + Protobuf.writeStream(tests, file, false); + return file; + } + + public File writeCoverageDetails(int componentRef, Iterable<ScannerReport.CoverageDetail> tests) { + File file = fileStructure.fileFor(FileStructure.Domain.COVERAGE_DETAILS, componentRef); + Protobuf.writeStream(tests, file, false); + return file; + } + + public File getSourceFile(int componentRef) { + return fileStructure.fileFor(FileStructure.Domain.SOURCE, componentRef); + } + +} diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/package-info.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/package-info.java new file mode 100644 index 00000000000..7a1b7b47a3e --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.output; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/package-info.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/package-info.java new file mode 100644 index 00000000000..be3678faecd --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/ScannerReportViewerApp.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/ScannerReportViewerApp.java new file mode 100644 index 00000000000..3768c1ca0d2 --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/ScannerReportViewerApp.java @@ -0,0 +1,388 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.viewer; + +import java.awt.BorderLayout; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.sql.Date; +import java.text.SimpleDateFormat; +import java.util.Scanner; +import javax.swing.JEditorPane; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JTree; +import javax.swing.UIManager; +import javax.swing.UIManager.LookAndFeelInfo; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeSelectionModel; +import org.sonar.core.util.CloseableIterator; +import org.sonar.scanner.protocol.output.ScannerReportReader; +import org.sonar.scanner.protocol.output.FileStructure.Domain; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReport.Component; +import org.sonar.scanner.protocol.output.ScannerReport.Metadata; + +public class ScannerReportViewerApp { + + private JFrame frame; + private ScannerReportReader reader; + private Metadata metadata; + private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); + private JTree componentTree; + private JSplitPane splitPane; + private JTabbedPane tabbedPane; + private JScrollPane treeScrollPane; + private JScrollPane componentDetailsTab; + private JScrollPane highlightingTab; + private JScrollPane symbolTab; + private JEditorPane componentEditor; + private JEditorPane highlightingEditor; + private JEditorPane symbolEditor; + private JScrollPane sourceTab; + private JEditorPane sourceEditor; + private JScrollPane coverageTab; + private JEditorPane coverageEditor; + private TextLineNumber textLineNumber; + private JScrollPane duplicationTab; + private JEditorPane duplicationEditor; + + /** + * Create the application. + */ + public ScannerReportViewerApp() { + initialize(); + } + + /** + * Launch the application. + */ + public static void main(String[] args) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + try { + ScannerReportViewerApp window = new ScannerReportViewerApp(); + window.frame.setVisible(true); + + window.loadReport(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + }); + } + + private void loadReport() { + final JFileChooser fc = new JFileChooser(); + fc.setDialogTitle("Choose scanner report directory"); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fc.setFileHidingEnabled(false); + fc.setApproveButtonText("Open scanner report"); + int returnVal = fc.showOpenDialog(frame); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + try { + loadReport(file); + } catch (Exception e) { + JOptionPane.showMessageDialog(frame, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + exit(); + } + } else { + exit(); + } + + } + + private void exit() { + frame.setVisible(false); + frame.dispose(); + } + + private void loadReport(File file) { + reader = new ScannerReportReader(file); + metadata = reader.readMetadata(); + updateTitle(); + loadComponents(); + } + + private void loadComponents() { + int rootComponentRef = metadata.getRootComponentRef(); + Component component = reader.readComponent(rootComponentRef); + DefaultMutableTreeNode project = createNode(component); + loadChildren(component, project); + getComponentTree().setModel(new DefaultTreeModel(project)); + } + + private static DefaultMutableTreeNode createNode(Component component) { + return new DefaultMutableTreeNode(component) { + @Override + public String toString() { + return getNodeName((Component) getUserObject()); + } + }; + } + + private static String getNodeName(Component component) { + switch (component.getType()) { + case PROJECT: + case MODULE: + return component.getName(); + case DIRECTORY: + case FILE: + return component.getPath(); + default: + throw new IllegalArgumentException("Unknow component type: " + component.getType()); + } + } + + private void loadChildren(Component parentComponent, DefaultMutableTreeNode parentNode) { + for (int ref : parentComponent.getChildRefList()) { + Component child = reader.readComponent(ref); + DefaultMutableTreeNode childNode = createNode(child); + parentNode.add(childNode); + loadChildren(child, childNode); + } + + } + + private void updateTitle() { + frame.setTitle(metadata.getProjectKey() + (metadata.hasBranch() ? (" (" + metadata.getBranch() + ")") : "") + " " + sdf.format(new Date(metadata.getAnalysisDate()))); + } + + private void updateDetails(Component component) { + componentEditor.setText(component.toString()); + updateHighlighting(component); + updateSymbols(component); + updateSource(component); + updateCoverage(component); + updateDuplications(component); + } + + private void updateDuplications(Component component) { + duplicationEditor.setText(""); + if (reader.hasCoverage(component.getRef())) { + try (CloseableIterator<ScannerReport.Duplication> it = reader.readComponentDuplications(component.getRef())) { + while (it.hasNext()) { + ScannerReport.Duplication dup = it.next(); + duplicationEditor.getDocument().insertString(duplicationEditor.getDocument().getEndPosition().getOffset(), dup.toString() + "\n", null); + } + } catch (Exception e) { + throw new IllegalStateException("Can't read duplications for " + getNodeName(component), e); + } + } + } + + private void updateCoverage(Component component) { + coverageEditor.setText(""); + try (CloseableIterator<ScannerReport.Coverage> it = reader.readComponentCoverage(component.getRef())) { + while (it.hasNext()) { + ScannerReport.Coverage coverage = it.next(); + coverageEditor.getDocument().insertString(coverageEditor.getDocument().getEndPosition().getOffset(), coverage.toString() + "\n", null); + } + } catch (Exception e) { + throw new IllegalStateException("Can't read code coverage for " + getNodeName(component), e); + } + } + + private void updateSource(Component component) { + File sourceFile = reader.getFileStructure().fileFor(Domain.SOURCE, component.getRef()); + sourceEditor.setText(""); + + if (sourceFile.exists()) { + try (Scanner s = new Scanner(sourceFile, StandardCharsets.UTF_8.name()).useDelimiter("\\Z")) { + if (s.hasNext()) { + sourceEditor.setText(s.next()); + } + } catch (IOException ex) { + StringWriter errors = new StringWriter(); + ex.printStackTrace(new PrintWriter(errors)); + sourceEditor.setText(errors.toString()); + } + } + } + + private void updateHighlighting(Component component) { + highlightingEditor.setText(""); + try (CloseableIterator<ScannerReport.SyntaxHighlighting> it = reader.readComponentSyntaxHighlighting(component.getRef())) { + while (it.hasNext()) { + ScannerReport.SyntaxHighlighting rule = it.next(); + highlightingEditor.getDocument().insertString(highlightingEditor.getDocument().getEndPosition().getOffset(), rule.toString() + "\n", null); + } + } catch (Exception e) { + throw new IllegalStateException("Can't read syntax highlighting for " + getNodeName(component), e); + } + } + + private void updateSymbols(Component component) { + symbolEditor.setText(""); + try (CloseableIterator<ScannerReport.Symbol> it = reader.readComponentSymbols(component.getRef())) { + while (it.hasNext()) { + ScannerReport.Symbol symbol = it.next(); + symbolEditor.getDocument().insertString(symbolEditor.getDocument().getEndPosition().getOffset(), symbol.toString() + "\n", null); + } + } catch (Exception e) { + throw new IllegalStateException("Can't read symbol references for " + getNodeName(component), e); + } + } + + /** + * Initialize the contents of the frame. + */ + private void initialize() { + try { + for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } catch (Exception e) { + // If Nimbus is not available, you can set the GUI to another look and feel. + } + frame = new JFrame(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + splitPane = new JSplitPane(); + frame.getContentPane().add(splitPane, BorderLayout.CENTER); + + tabbedPane = new JTabbedPane(JTabbedPane.TOP); + tabbedPane.setPreferredSize(new Dimension(500, 7)); + splitPane.setRightComponent(tabbedPane); + + componentDetailsTab = new JScrollPane(); + tabbedPane.addTab("Component details", null, componentDetailsTab, null); + + componentEditor = new JEditorPane(); + componentDetailsTab.setViewportView(componentEditor); + + sourceTab = new JScrollPane(); + tabbedPane.addTab("Source", null, sourceTab, null); + + sourceEditor = createSourceEditor(); + sourceEditor.setEditable(false); + sourceTab.setViewportView(sourceEditor); + + textLineNumber = createTextLineNumber(); + sourceTab.setRowHeaderView(textLineNumber); + + highlightingTab = new JScrollPane(); + tabbedPane.addTab("Highlighting", null, highlightingTab, null); + + highlightingEditor = new JEditorPane(); + highlightingTab.setViewportView(highlightingEditor); + + symbolTab = new JScrollPane(); + tabbedPane.addTab("Symbol references", null, symbolTab, null); + + symbolEditor = new JEditorPane(); + symbolTab.setViewportView(symbolEditor); + + coverageTab = new JScrollPane(); + tabbedPane.addTab("Coverage", null, coverageTab, null); + + coverageEditor = new JEditorPane(); + coverageTab.setViewportView(coverageEditor); + + duplicationTab = new JScrollPane(); + tabbedPane.addTab("Duplications", null, duplicationTab, null); + + duplicationEditor = new JEditorPane(); + duplicationTab.setViewportView(duplicationEditor); + + treeScrollPane = new JScrollPane(); + treeScrollPane.setPreferredSize(new Dimension(200, 400)); + splitPane.setLeftComponent(treeScrollPane); + + componentTree = new JTree(); + componentTree.setModel(new DefaultTreeModel( + new DefaultMutableTreeNode("empty") { + { + } + })); + treeScrollPane.setViewportView(componentTree); + componentTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + componentTree.addTreeSelectionListener(new TreeSelectionListener() { + @Override + public void valueChanged(TreeSelectionEvent e) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) componentTree.getLastSelectedPathComponent(); + + if (node == null) { + // Nothing is selected. + return; + } + + frame.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + updateDetails((Component) node.getUserObject()); + frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + + } + + }); + frame.pack(); + } + + public JTree getComponentTree() { + return componentTree; + } + + /** + * @wbp.factory + */ + public static JPanel createComponentPanel() { + JPanel panel = new JPanel(); + return panel; + } + + protected JEditorPane getComponentEditor() { + return componentEditor; + } + + /** + * @wbp.factory + */ + public static JEditorPane createSourceEditor() { + JEditorPane editorPane = new JEditorPane(); + return editorPane; + } + + /** + * @wbp.factory + */ + public TextLineNumber createTextLineNumber() { + TextLineNumber textLineNumber = new TextLineNumber(sourceEditor); + return textLineNumber; + } +} diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/TextLineNumber.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/TextLineNumber.java new file mode 100644 index 00000000000..138b8e3a0e5 --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/TextLineNumber.java @@ -0,0 +1,457 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.viewer; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashMap; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Element; +import javax.swing.text.JTextComponent; +import javax.swing.text.StyleConstants; +import javax.swing.text.Utilities; + +/** + * This class will display line numbers for a related text component. The text + * component must use the same line height for each line. TextLineNumber + * supports wrapped lines and will highlight the line number of the current + * line in the text component. + * + * This class was designed to be used as a component added to the row header + * of a JScrollPane. + */ +public class TextLineNumber extends JPanel implements CaretListener, DocumentListener, PropertyChangeListener { + public static final float LEFT = 0.0f; + public static final float CENTER = 0.5f; + public static final float RIGHT = 1.0f; + + private static final Border OUTER = new MatteBorder(0, 0, 0, 2, Color.GRAY); + + private static final int HEIGHT = Integer.MAX_VALUE - 1000000; + + // Text component this TextTextLineNumber component is in sync with + + private JTextComponent component; + + // Properties that can be changed + + private boolean updateFont; + private int borderGap; + private Color currentLineForeground; + private float digitAlignment; + private int minimumDisplayDigits; + + // Keep history information to reduce the number of times the component + // needs to be repainted + + private int lastDigits; + private int lastHeight; + private int lastLine; + + private HashMap<String, FontMetrics> fonts; + + /** + * Create a line number component for a text component. This minimum + * display width will be based on 3 digits. + * + * @param component the related text component + */ + public TextLineNumber(JTextComponent component) { + this(component, 3); + } + + /** + * Create a line number component for a text component. + * + * @param component the related text component + * @param minimumDisplayDigits the number of digits used to calculate + * the minimum width of the component + */ + public TextLineNumber(JTextComponent component, int minimumDisplayDigits) { + this.component = component; + + setFont(component.getFont()); + + setBorderGap(5); + setCurrentLineForeground(Color.RED); + setDigitAlignment(RIGHT); + setMinimumDisplayDigits(minimumDisplayDigits); + + component.getDocument().addDocumentListener(this); + component.addCaretListener(this); + component.addPropertyChangeListener("font", this); + } + + /** + * Gets the update font property + * + * @return the update font property + */ + public boolean getUpdateFont() { + return updateFont; + } + + /** + * Set the update font property. Indicates whether this Font should be + * updated automatically when the Font of the related text component + * is changed. + * + * @param updateFont when true update the Font and repaint the line + * numbers, otherwise just repaint the line numbers. + */ + public void setUpdateFont(boolean updateFont) { + this.updateFont = updateFont; + } + + /** + * Gets the border gap + * + * @return the border gap in pixels + */ + public int getBorderGap() { + return borderGap; + } + + /** + * The border gap is used in calculating the left and right insets of the + * border. Default value is 5. + * + * @param borderGap the gap in pixels + */ + public void setBorderGap(int borderGap) { + this.borderGap = borderGap; + Border inner = new EmptyBorder(0, borderGap, 0, borderGap); + setBorder(new CompoundBorder(OUTER, inner)); + lastDigits = 0; + setPreferredWidth(); + } + + /** + * Gets the current line rendering Color + * + * @return the Color used to render the current line number + */ + public Color getCurrentLineForeground() { + return currentLineForeground == null ? getForeground() : currentLineForeground; + } + + /** + * The Color used to render the current line digits. Default is Coolor.RED. + * + * @param currentLineForeground the Color used to render the current line + */ + public void setCurrentLineForeground(Color currentLineForeground) { + this.currentLineForeground = currentLineForeground; + } + + /** + * Gets the digit alignment + * + * @return the alignment of the painted digits + */ + public float getDigitAlignment() { + return digitAlignment; + } + + /** + * Specify the horizontal alignment of the digits within the component. + * Common values would be: + * <ul> + * <li>TextLineNumber.LEFT + * <li>TextLineNumber.CENTER + * <li>TextLineNumber.RIGHT (default) + * </ul> + * @param currentLineForeground the Color used to render the current line + */ + public void setDigitAlignment(float digitAlignment) { + this.digitAlignment = digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment; + } + + /** + * Gets the minimum display digits + * + * @return the minimum display digits + */ + public int getMinimumDisplayDigits() { + return minimumDisplayDigits; + } + + /** + * Specify the mimimum number of digits used to calculate the preferred + * width of the component. Default is 3. + * + * @param minimumDisplayDigits the number digits used in the preferred + * width calculation + */ + public void setMinimumDisplayDigits(int minimumDisplayDigits) { + this.minimumDisplayDigits = minimumDisplayDigits; + setPreferredWidth(); + } + + /** + * Calculate the width needed to display the maximum line number + */ + private void setPreferredWidth() { + Element root = component.getDocument().getDefaultRootElement(); + int lines = root.getElementCount(); + int digits = Math.max(String.valueOf(lines).length(), minimumDisplayDigits); + + // Update sizes when number of digits in the line number changes + + if (lastDigits != digits) { + lastDigits = digits; + FontMetrics fontMetrics = getFontMetrics(getFont()); + int width = fontMetrics.charWidth('0') * digits; + Insets insets = getInsets(); + int preferredWidth = insets.left + insets.right + width; + + Dimension d = getPreferredSize(); + d.setSize(preferredWidth, HEIGHT); + setPreferredSize(d); + setSize(d); + } + } + + /** + * Draw the line numbers + */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + + // Determine the width of the space available to draw the line number + + FontMetrics fontMetrics = component.getFontMetrics(component.getFont()); + Insets insets = getInsets(); + int availableWidth = getSize().width - insets.left - insets.right; + + // Determine the rows to draw within the clipped bounds. + + Rectangle clip = g.getClipBounds(); + int rowStartOffset = component.viewToModel(new Point(0, clip.y)); + int endOffset = component.viewToModel(new Point(0, clip.y + clip.height)); + + while (rowStartOffset <= endOffset) { + try { + if (isCurrentLine(rowStartOffset)) + g.setColor(getCurrentLineForeground()); + else + g.setColor(getForeground()); + + // Get the line number as a string and then determine the + // "X" and "Y" offsets for drawing the string. + + String lineNumber = getTextLineNumber(rowStartOffset); + int stringWidth = fontMetrics.stringWidth(lineNumber); + int x = getOffsetX(availableWidth, stringWidth) + insets.left; + int y = getOffsetY(rowStartOffset, fontMetrics); + g.drawString(lineNumber, x, y); + + // Move to the next row + + rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1; + } catch (Exception e) { + break; + } + } + } + + /* + * We need to know if the caret is currently positioned on the line we + * are about to paint so the line number can be highlighted. + */ + private boolean isCurrentLine(int rowStartOffset) { + int caretPosition = component.getCaretPosition(); + Element root = component.getDocument().getDefaultRootElement(); + + return root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition); + } + + /* + * Get the line number to be drawn. The empty string will be returned + * when a line of text has wrapped. + */ + protected String getTextLineNumber(int rowStartOffset) { + Element root = component.getDocument().getDefaultRootElement(); + int index = root.getElementIndex(rowStartOffset); + Element line = root.getElement(index); + + if (line.getStartOffset() == rowStartOffset) + return String.valueOf(index + 1); + else + return ""; + } + + /* + * Determine the X offset to properly align the line number when drawn + */ + private int getOffsetX(int availableWidth, int stringWidth) { + return (int) ((availableWidth - stringWidth) * digitAlignment); + } + + /* + * Determine the Y offset for the current row + */ + private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics) + throws BadLocationException { + // Get the bounding rectangle of the row + + Rectangle r = component.modelToView(rowStartOffset); + int lineHeight = fontMetrics.getHeight(); + int y = r.y + r.height; + int descent = 0; + + // The text needs to be positioned above the bottom of the bounding + // rectangle based on the descent of the font(s) contained on the row. + + if (r.height == lineHeight) // default font is being used + { + descent = fontMetrics.getDescent(); + } else // We need to check all the attributes for font changes + { + if (fonts == null) + fonts = new HashMap<>(); + + Element root = component.getDocument().getDefaultRootElement(); + int index = root.getElementIndex(rowStartOffset); + Element line = root.getElement(index); + + for (int i = 0; i < line.getElementCount(); i++) { + Element child = line.getElement(i); + AttributeSet as = child.getAttributes(); + String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily); + Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize); + String key = fontFamily + fontSize; + + FontMetrics fm = fonts.get(key); + + if (fm == null) { + Font font = new Font(fontFamily, Font.PLAIN, fontSize); + fm = component.getFontMetrics(font); + fonts.put(key, fm); + } + + descent = Math.max(descent, fm.getDescent()); + } + } + + return y - descent; + } + + // + // Implement CaretListener interface + // + @Override + public void caretUpdate(CaretEvent e) { + // Get the line the caret is positioned on + + int caretPosition = component.getCaretPosition(); + Element root = component.getDocument().getDefaultRootElement(); + int currentLine = root.getElementIndex(caretPosition); + + // Need to repaint so the correct line number can be highlighted + + if (lastLine != currentLine) { + repaint(); + lastLine = currentLine; + } + } + + // + // Implement DocumentListener interface + // + @Override + public void changedUpdate(DocumentEvent e) { + documentChanged(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + documentChanged(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + documentChanged(); + } + + /* + * A document change may affect the number of displayed lines of text. + * Therefore the lines numbers will also change. + */ + private void documentChanged() { + // View of the component has not been updated at the time + // the DocumentEvent is fired + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + try { + int endPos = component.getDocument().getLength(); + Rectangle rect = component.modelToView(endPos); + + if (rect != null && rect.y != lastHeight) { + setPreferredWidth(); + repaint(); + lastHeight = rect.y; + } + } catch (BadLocationException ex) { + /* nothing to do */ + } + } + }); + } + + // + // Implement PropertyChangeListener interface + // + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getNewValue() instanceof Font) { + if (updateFont) { + Font newFont = (Font) evt.getNewValue(); + setFont(newFont); + lastDigits = 0; + setPreferredWidth(); + } else { + repaint(); + } + } + } +} diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/package-info.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/package-info.java new file mode 100644 index 00000000000..17684b0c408 --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.viewer; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-scanner-protocol/src/main/protobuf/constants.proto b/sonar-scanner-protocol/src/main/protobuf/constants.proto new file mode 100644 index 00000000000..862b4afa54c --- /dev/null +++ b/sonar-scanner-protocol/src/main/protobuf/constants.proto @@ -0,0 +1,79 @@ +// SonarQube, open source software quality management tool. +// Copyright (C) 2008-2015 SonarSource +// mailto:contact AT sonarsource DOT com +// +// SonarQube 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. +// +// SonarQube 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. + +syntax = "proto2"; + +option java_package = "org.sonar.scanner.protocol"; + +option optimize_for = SPEED; + +enum Severity { + INFO = 0; + MINOR = 1; + MAJOR = 2; + CRITICAL = 3; + BLOCKER = 4; +} + +enum ComponentType { + PROJECT = 0; + MODULE = 1; + DIRECTORY = 2; + FILE = 3; +} + +enum MeasureValueType { + INT = 0; + LONG = 1; + DOUBLE = 2; + BOOLEAN = 3; + STRING = 4; +} + +// temporary enum during development of computation stack +enum EventCategory { + ALERT = 0; + PROFILE = 1; +} + +enum ComponentLinkType { + HOME = 0; + SCM = 1; + SCM_DEV = 2; + ISSUE = 3; + CI = 4; +} + +enum HighlightingType { + ANNOTATION = 0; + CONSTANT = 1; + COMMENT = 2; + CPP_DOC = 3; + STRUCTURED_COMMENT = 4; + KEYWORD = 5; + HIGHLIGHTING_STRING = 6; + KEYWORD_LIGHT = 7; + PREPROCESS_DIRECTIVE = 8; +} + +enum TestStatus { + OK = 1; + FAILURE = 2; + ERROR = 3; + SKIPPED = 4; +} diff --git a/sonar-scanner-protocol/src/main/protobuf/scanner_input.proto b/sonar-scanner-protocol/src/main/protobuf/scanner_input.proto new file mode 100644 index 00000000000..db66fe50101 --- /dev/null +++ b/sonar-scanner-protocol/src/main/protobuf/scanner_input.proto @@ -0,0 +1,56 @@ +// SonarQube, open source software quality management tool. +// Copyright (C) 2008-2015 SonarSource +// mailto:contact AT sonarsource DOT com +// +// SonarQube 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. +// +// SonarQube 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. + +/* +Notes + + - "required" fields are not used as recommended by Google to keep forward-compatibility: + https://developers.google.com/protocol-buffers/docs/proto#simple + + - this is beta version of specification. It will evolve during next releases and is + not forward-compatible yet. +*/ + +syntax = "proto2"; + +import "constants.proto"; + +option java_package = "org.sonar.scanner.protocol.input"; +option optimize_for = SPEED; + +message ServerIssue { + optional string key = 1; + optional string module_key = 2; + optional string path = 3; + optional string rule_repository = 4; + optional string rule_key = 5; + optional int32 line = 6; + optional string msg = 7; + optional Severity severity = 8; + optional bool manual_severity = 9; + optional string resolution = 10; + optional string status = 11; + optional string checksum = 12; + optional string assignee_login = 13; + optional int64 creation_date = 14; +} + +message User { + optional string login = 1; + optional string name = 2; +} diff --git a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto new file mode 100644 index 00000000000..3df7f4343a6 --- /dev/null +++ b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto @@ -0,0 +1,213 @@ +// SonarQube, open source software quality management tool. +// Copyright (C) 2008-2015 SonarSource +// mailto:contact AT sonarsource DOT com +// +// SonarQube 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. +// +// SonarQube 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. + +/* +Notes + + - "required" fields are not used as recommended by Google to keep forward-compatibility: + https://developers.google.com/protocol-buffers/docs/proto#simple + + - this is beta version of specification. It will evolve during next releases and is + not forward-compatible yet. +*/ + +syntax = "proto2"; + +import "constants.proto"; + +option java_package = "org.sonar.scanner.protocol.output"; + +option optimize_for = SPEED; + +message Metadata { + optional int64 analysis_date = 1; + // TODO should we keep this project_key here or not ? Because it's a duplication of Component.key + optional string project_key = 2; + optional string branch = 3; + optional int32 root_component_ref = 4; + optional bool cross_project_duplication_activated = 5; +} + +message ActiveRule { + optional string rule_repository = 1; + optional string rule_key = 2; + optional Severity severity = 3; + repeated ActiveRuleParam param = 4; + + // TODO replace by map + message ActiveRuleParam { + optional string key = 1; + optional string value = 2; + } +} + +message ComponentLink { + optional ComponentLinkType type = 1; + optional string href = 2; +} + +message Component { + optional int32 ref = 1; + optional string path = 2; + optional string name = 3; + optional ComponentType type = 4; + optional bool is_test = 5; + optional string language = 6; + repeated int32 child_ref = 7 [packed = true]; + repeated ComponentLink link = 8; + // Only available on PROJECT and MODULE types + optional string version = 9; + // Only available on PROJECT and MODULE types + // TODO rename this property -> batchKey ? moduleKey ? + optional string key = 10; + // Only available on FILE type + optional int32 lines = 11; + // Only available on PROJECT and MODULE types + optional string description = 12; +} + +message Measure { + optional MeasureValueType value_type = 1; + // all values may be unset for "new_xxx" measures (only variations are populated) + optional bool boolean_value = 2; + optional int32 int_value = 3; + optional int64 long_value = 4; + optional double double_value = 5; + optional string string_value = 6; + optional string metric_key = 7; +} + +message Issue { + optional string rule_repository = 1; + optional string rule_key = 2; + // Only when issue component is a file. Can also be empty for a file if this is an issue global to the file. + //TODO To be removed. Use first line of text_range instead + optional int32 line = 3; + optional string msg = 4; + optional Severity severity = 5; + optional double gap = 6; + // Only when issue component is a file. Can also be empty for a file if this is an issue global to the file. + optional TextRange text_range = 7; + repeated Flow flow = 8; +} + +message IssueLocation { + optional int32 component_ref = 1; + // Only when component is a file. Can be empty for a file if this is an issue global to the file. + optional TextRange text_range = 2; + optional string msg = 3; +} + +message Flow { + repeated IssueLocation location = 1; +} + +message Changesets { + optional int32 component_ref = 1; + repeated Changeset changeset = 2; + // if changesetIndexByLine[5] = 2 then it means that changeset[2] is the last one on line 6 + repeated int32 changesetIndexByLine = 3 [packed = true]; + + message Changeset { + optional string revision = 1; + optional string author = 2; + optional int64 date = 3; + } +} + +message Duplicate { + // Will be null when duplicate is in the same file + optional int32 other_file_ref = 1; + optional TextRange range = 2; +} + +message Duplication { + // Origin position in current file + optional TextRange origin_position = 1; + repeated Duplicate duplicate = 2; +} + +// Used for cross project duplication +message CpdTextBlock { + optional string hash = 1; + optional int32 start_line = 2; + optional int32 end_line = 3; + optional int32 start_token_index = 4; + optional int32 end_token_index = 5; +} + +// Lines start at 1 and line offsets start at 0 +message TextRange { + // Should never be null + optional int32 start_line = 1; + // End line (inclusive) + optional int32 end_line = 2; + // If null it means range starts at the first offset of start line + optional int32 start_offset = 3; + // If null it means range ends at the last offset of end line + optional int32 end_offset = 4; +} + +message Symbol { + optional TextRange declaration = 1; + repeated TextRange reference = 2; +} + +// Only FILE component has coverage information, and only executable lines should contains this information. +// TODO rename it LineCoverage ? +message Coverage { + optional int32 line = 1; + + // Number of conditions to cover (if set, the value must be greater than 0) + optional int32 conditions = 2; + // Is the line has been touched by a unit test ? Returning false means that no test has touched this executable line. + optional bool ut_hits = 3; + // Is the line has been touched by a integration test ? Returning false means that no test has touched this executable line. + optional bool it_hits = 4; + // Number of conditions covered by unit tests + optional int32 ut_covered_conditions = 5; + // Number of conditions covered by integration tests + optional int32 it_covered_conditions = 6; + // Number of conditions covered by overall tests + optional int32 overall_covered_conditions = 7; +} + +// Must be sorted by line and start offset +// TODO rename it SyntaxHighlightingRule ? +message SyntaxHighlighting { + optional TextRange range = 1; + optional HighlightingType type = 2; +} + +message Test { + optional string name = 1; + optional TestStatus status = 2; + optional int64 duration_in_ms = 3; + optional string stacktrace = 4; + optional string msg = 5; +} + +message CoverageDetail { + optional string test_name = 1; + repeated CoveredFile covered_file = 2; + + message CoveredFile { + optional int32 file_ref = 1; + repeated int32 covered_line = 2 [packed = true]; + } +} diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/GlobalRepositoriesTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/GlobalRepositoriesTest.java new file mode 100644 index 00000000000..703e182ea8d --- /dev/null +++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/GlobalRepositoriesTest.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.input; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; +import org.sonar.scanner.protocol.input.GlobalRepositories; +import org.sonar.scanner.protocol.input.Metric; + +import static net.javacrumbs.jsonunit.assertj.JsonAssert.assertThatJson; +import static org.assertj.core.api.Assertions.assertThat; + +public class GlobalRepositoriesTest { + + @Test + public void to_json() throws Exception { + GlobalRepositories ref = new GlobalRepositories(); + ref.addMetric(new Metric(1, "ncloc", "INT", "Description", -1, "NCLOC", true, false, 2.0, 1.0, true)); + ref.addGlobalSetting("prop", "value"); + ref.setTimestamp(10); + + assertThatJson(ref.toJson()) + .isEqualTo(IOUtils.toString(getClass().getResource("GlobalRepositoriesTest/expected.json"))); + } + + @Test + public void from_json() { + GlobalRepositories ref = GlobalRepositories + .fromJson( + "{timestamp:1," + + "metrics:[{id:1,key:ncloc,valueType:DATA,description:Description,direction:-1,name:NCLOC,qualitative:true,userManaged:false,worstValue:2.0,bestValue:1.0,optimizedBestValue:true}]," + + "globalSettings:{prop:value}}"); + + assertThat(ref.timestamp()).isEqualTo(1); + Metric metric = ref.metrics().iterator().next(); + assertThat(metric.id()).isEqualTo(1); + assertThat(metric.key()).isEqualTo("ncloc"); + assertThat(metric.valueType()).isEqualTo("DATA"); + assertThat(metric.description()).isEqualTo("Description"); + assertThat(metric.direction()).isEqualTo(-1); + assertThat(metric.name()).isEqualTo("NCLOC"); + assertThat(metric.isQualitative()).isTrue(); + assertThat(metric.isUserManaged()).isFalse(); + assertThat(metric.worstValue()).isEqualTo(2.0); + assertThat(metric.bestValue()).isEqualTo(1.0); + assertThat(metric.isOptimizedBestValue()).isTrue(); + + assertThat(ref.globalSettings()).containsEntry("prop", "value"); + } +} diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/FileStructureTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/FileStructureTest.java new file mode 100644 index 00000000000..69d5266cf50 --- /dev/null +++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/FileStructureTest.java @@ -0,0 +1,74 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.output; + +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.scanner.protocol.output.FileStructure; +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class FileStructureTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void fail_if_dir_does_not_exist() throws Exception { + File dir = temp.newFolder(); + FileUtils.deleteQuietly(dir); + try { + new FileStructure(dir); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageContaining("Directory of analysis report does not exist"); + } + } + + @Test + public void fail_if_invalid_dir() throws Exception { + // not a dir but a file + File dir = temp.newFile(); + try { + new FileStructure(dir); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageContaining("Directory of analysis report does not exist"); + } + } + + @Test + public void locate_files() throws Exception { + File dir = temp.newFolder(); + FileUtils.write(new File(dir, "metadata.pb"), "metadata content"); + FileUtils.write(new File(dir, "issues-3.pb"), "issues of component 3"); + FileUtils.write(new File(dir, "component-42.pb"), "details of component 42"); + + FileStructure structure = new FileStructure(dir); + assertThat(structure.metadataFile()).exists().isFile(); + assertThat(structure.fileFor(FileStructure.Domain.COMPONENT, 42)).exists().isFile(); + assertThat(structure.fileFor(FileStructure.Domain.ISSUES, 3)).exists().isFile(); + assertThat(structure.fileFor(FileStructure.Domain.ISSUES, 42)).doesNotExist(); + } +} diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportReaderTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportReaderTest.java new file mode 100644 index 00000000000..ce2a6e86149 --- /dev/null +++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportReaderTest.java @@ -0,0 +1,391 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.output; + +import com.google.common.collect.Lists; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.core.util.CloseableIterator; +import org.sonar.scanner.protocol.Constants; +import org.sonar.scanner.protocol.output.ScannerReportReader; +import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.scanner.protocol.output.FileStructure; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +public class ScannerReportReaderTest { + + private static int UNKNOWN_COMPONENT_REF = 123; + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + File dir; + + ScannerReportReader underTest; + + @Before + public void setUp() throws Exception { + dir = temp.newFolder(); + underTest = new ScannerReportReader(dir); + } + + @Test + public void read_metadata() { + ScannerReportWriter writer = new ScannerReportWriter(dir); + ScannerReport.Metadata.Builder metadata = ScannerReport.Metadata.newBuilder() + .setAnalysisDate(15000000L) + .setProjectKey("PROJECT_A") + .setRootComponentRef(1) + .setCrossProjectDuplicationActivated(true); + writer.writeMetadata(metadata.build()); + + ScannerReport.Metadata readMetadata = underTest.readMetadata(); + assertThat(readMetadata.getAnalysisDate()).isEqualTo(15000000L); + assertThat(readMetadata.getProjectKey()).isEqualTo("PROJECT_A"); + assertThat(readMetadata.getRootComponentRef()).isEqualTo(1); + assertThat(readMetadata.getCrossProjectDuplicationActivated()).isTrue(); + } + + @Test(expected = IllegalStateException.class) + public void fail_if_missing_metadata_file() { + underTest.readMetadata(); + } + + @Test + public void read_components() { + ScannerReportWriter writer = new ScannerReportWriter(dir); + ScannerReport.Component.Builder component = ScannerReport.Component.newBuilder() + .setRef(1) + .setPath("src/main/java/Foo.java"); + writer.writeComponent(component.build()); + + assertThat(underTest.readComponent(1).getPath()).isEqualTo("src/main/java/Foo.java"); + } + + @Test(expected = IllegalStateException.class) + public void fail_if_missing_file_on_component() { + underTest.readComponent(UNKNOWN_COMPONENT_REF); + } + + @Test + public void read_issues() { + ScannerReportWriter writer = new ScannerReportWriter(dir); + ScannerReport.Issue issue = ScannerReport.Issue.newBuilder() + .setLine(50) + .build(); + writer.writeComponentIssues(1, asList(issue)); + + assertThat(underTest.readComponentIssues(1)).hasSize(1); + assertThat(underTest.readComponentIssues(200)).isEmpty(); + } + + @Test + public void empty_list_if_no_issue_found() { + assertThat(underTest.readComponentIssues(UNKNOWN_COMPONENT_REF)).isEmpty(); + } + + @Test + public void read_measures() { + ScannerReportWriter writer = new ScannerReportWriter(dir); + ScannerReport.Measure.Builder measure = ScannerReport.Measure.newBuilder() + .setStringValue("value_a"); + writer.writeComponentMeasures(1, asList(measure.build())); + + assertThat(underTest.readComponentMeasures(1)).hasSize(1); + } + + @Test + public void empty_list_if_no_measure_found() { + assertThat(underTest.readComponentMeasures(UNKNOWN_COMPONENT_REF)).isEmpty(); + } + + @Test + public void read_changesets() { + ScannerReportWriter writer = new ScannerReportWriter(dir); + ScannerReport.Changesets.Builder scm = ScannerReport.Changesets.newBuilder() + .setComponentRef(1) + .addChangeset(ScannerReport.Changesets.Changeset.newBuilder().setDate(123_456_789).setAuthor("jack.daniels").setRevision("123-456-789")); + writer.writeComponentChangesets(scm.build()); + + assertThat(underTest.readChangesets(1).getChangesetList()).hasSize(1); + assertThat(underTest.readChangesets(1).getChangeset(0).getDate()).isEqualTo(123_456_789L); + } + + @Test + public void null_if_no_changeset_found() { + assertThat(underTest.readChangesets(UNKNOWN_COMPONENT_REF)).isNull(); + } + + @Test + public void read_duplications() { + ScannerReportWriter writer = new ScannerReportWriter(dir); + writer.writeMetadata(ScannerReport.Metadata.newBuilder() + .setRootComponentRef(1).build()); + writer.writeComponent(ScannerReport.Component.newBuilder() + .setRef(1).build()); + + ScannerReport.Duplication duplication = ScannerReport.Duplication.newBuilder() + .setOriginPosition(ScannerReport.TextRange.newBuilder() + .setStartLine(1) + .setEndLine(5) + .build()) + .addDuplicate(ScannerReport.Duplicate.newBuilder() + .setOtherFileRef(2) + .setRange(ScannerReport.TextRange.newBuilder() + .setStartLine(6) + .setEndLine(10) + .build()) + .build()) + .build(); + writer.writeComponentDuplications(1, asList(duplication)); + + ScannerReportReader sut = new ScannerReportReader(dir); + assertThat(sut.readComponentDuplications(1)).hasSize(1); + } + + @Test + public void empty_list_if_no_duplication_found() { + assertThat(underTest.readComponentDuplications(UNKNOWN_COMPONENT_REF)).isEmpty(); + } + + @Test + public void read_duplication_blocks() { + ScannerReportWriter writer = new ScannerReportWriter(dir); + writer.writeMetadata(ScannerReport.Metadata.newBuilder() + .setRootComponentRef(1).build()); + writer.writeComponent(ScannerReport.Component.newBuilder() + .setRef(1).build()); + + ScannerReport.CpdTextBlock duplicationBlock = ScannerReport.CpdTextBlock.newBuilder() + .setHash("abcdefghijklmnop") + .setStartLine(1) + .setEndLine(2) + .setStartTokenIndex(10) + .setEndTokenIndex(15) + .build(); + writer.writeCpdTextBlocks(1, singletonList(duplicationBlock)); + + ScannerReportReader sut = new ScannerReportReader(dir); + assertThat(sut.readCpdTextBlocks(1)).hasSize(1); + } + + @Test + public void empty_list_if_no_duplication_block_found() { + assertThat(underTest.readComponentDuplications(UNKNOWN_COMPONENT_REF)).isEmpty(); + } + + @Test + public void read_syntax_highlighting() throws Exception { + ScannerReportWriter writer = new ScannerReportWriter(dir); + writer.writeMetadata(ScannerReport.Metadata.newBuilder() + .setRootComponentRef(1) + .build()); + writer.writeComponent(ScannerReport.Component.newBuilder() + .setRef(1).build()); + + writer.writeComponentSyntaxHighlighting(1, asList( + ScannerReport.SyntaxHighlighting.newBuilder() + .setRange(ScannerReport.TextRange.newBuilder() + .setStartLine(1) + .setEndLine(10) + .build()) + .setType(Constants.HighlightingType.ANNOTATION) + .build())); + + try (CloseableIterator<ScannerReport.SyntaxHighlighting> it = underTest.readComponentSyntaxHighlighting(1)) { + ScannerReport.SyntaxHighlighting syntaxHighlighting = it.next(); + assertThat(syntaxHighlighting.getRange()).isNotNull(); + assertThat(syntaxHighlighting.getRange().getStartLine()).isEqualTo(1); + assertThat(syntaxHighlighting.getRange().getEndLine()).isEqualTo(10); + assertThat(syntaxHighlighting.getType()).isEqualTo(Constants.HighlightingType.ANNOTATION); + } + } + + @Test + public void return_empty_if_no_highlighting_found() { + assertThat(underTest.readComponentSyntaxHighlighting(UNKNOWN_COMPONENT_REF)).isEmpty(); + } + + @Test + public void read_symbols() { + ScannerReportWriter writer = new ScannerReportWriter(dir); + writer.writeMetadata(ScannerReport.Metadata.newBuilder() + .setRootComponentRef(1) + .build()); + writer.writeComponent(ScannerReport.Component.newBuilder() + .setRef(1).build()); + + writer.writeComponentSymbols(1, asList(ScannerReport.Symbol.newBuilder() + .setDeclaration(ScannerReport.TextRange.newBuilder() + .setStartLine(1) + .setStartOffset(3) + .setEndLine(1) + .setEndOffset(5) + .build()) + .addReference(ScannerReport.TextRange.newBuilder() + .setStartLine(10) + .setStartOffset(15) + .setEndLine(11) + .setEndOffset(2) + .build()) + .build())); + + underTest = new ScannerReportReader(dir); + assertThat(underTest.readComponentSymbols(1)).hasSize(1); + } + + @Test + public void empty_list_if_no_symbol_found() { + assertThat(underTest.readComponentSymbols(UNKNOWN_COMPONENT_REF)).isEmpty(); + } + + @Test + public void read_coverage() throws Exception { + ScannerReportWriter writer = new ScannerReportWriter(dir); + writer.writeMetadata(ScannerReport.Metadata.newBuilder() + .setRootComponentRef(1) + .build()); + writer.writeComponent(ScannerReport.Component.newBuilder() + .setRef(1).build()); + + writer.writeComponentCoverage(1, asList( + ScannerReport.Coverage.newBuilder() + .setLine(1) + .setConditions(1) + .setUtHits(true) + .setItHits(false) + .setUtCoveredConditions(1) + .setItCoveredConditions(1) + .setOverallCoveredConditions(1) + .build(), + ScannerReport.Coverage.newBuilder() + .setLine(2) + .setConditions(5) + .setUtHits(false) + .setItHits(false) + .setUtCoveredConditions(4) + .setItCoveredConditions(5) + .setOverallCoveredConditions(5) + .build())); + + underTest = new ScannerReportReader(dir); + try (CloseableIterator<ScannerReport.Coverage> it = new ScannerReportReader(dir).readComponentCoverage(1)) { + ScannerReport.Coverage coverage = it.next(); + assertThat(coverage.getLine()).isEqualTo(1); + assertThat(coverage.getConditions()).isEqualTo(1); + assertThat(coverage.getUtHits()).isTrue(); + assertThat(coverage.getItHits()).isFalse(); + assertThat(coverage.getUtCoveredConditions()).isEqualTo(1); + assertThat(coverage.getItCoveredConditions()).isEqualTo(1); + assertThat(coverage.getOverallCoveredConditions()).isEqualTo(1); + } + } + + @Test + public void return_empty_iterator_if_no_coverage_found() { + assertThat(underTest.readComponentCoverage(UNKNOWN_COMPONENT_REF)).isEmpty(); + } + + @Test + public void read_source_lines() throws Exception { + ScannerReportWriter writer = new ScannerReportWriter(dir); + File file = writer.getFileStructure().fileFor(FileStructure.Domain.SOURCE, 1); + FileUtils.writeLines(file, Lists.newArrayList("line1", "line2")); + + File sourceFile = new ScannerReportReader(dir).readFileSource(1); + assertThat(sourceFile).isEqualTo(file); + } + + @Test + public void read_tests() throws Exception { + ScannerReportWriter writer = new ScannerReportWriter(dir); + writer.writeTests(1, asList( + ScannerReport.Test.newBuilder() + .setDurationInMs(60_000) + .setStacktrace("stacktrace") + .setMsg("message") + .setStatus(Constants.TestStatus.OK) + .build())); + + try (InputStream inputStream = FileUtils.openInputStream(underTest.readTests(1))) { + ScannerReport.Test testResult = ScannerReport.Test.PARSER.parseDelimitedFrom(inputStream); + assertThat(testResult.getDurationInMs()).isEqualTo(60_000); + assertThat(testResult.getStacktrace()).isEqualTo("stacktrace"); + assertThat(testResult.getMsg()).isEqualTo("message"); + assertThat(testResult.getStatus()).isEqualTo(Constants.TestStatus.OK); + } + } + + @Test + public void null_if_no_test_found() { + assertThat(underTest.readTests(UNKNOWN_COMPONENT_REF)).isNull(); + } + + @Test + public void read_coverage_details() throws Exception { + ScannerReportWriter writer = new ScannerReportWriter(dir); + writer.writeCoverageDetails(1, asList( + ScannerReport.CoverageDetail.newBuilder() + .setTestName("test-name") + .addCoveredFile(ScannerReport.CoverageDetail.CoveredFile.newBuilder() + .addAllCoveredLine(asList(1, 2, 3, 5, 7)) + .setFileRef(2)) + .build())); + + try (InputStream inputStream = FileUtils.openInputStream(underTest.readCoverageDetails(1))) { + ScannerReport.CoverageDetail coverageDetail = ScannerReport.CoverageDetail.PARSER.parseDelimitedFrom(inputStream); + assertThat(coverageDetail.getTestName()).isEqualTo("test-name"); + assertThat(coverageDetail.getCoveredFile(0).getFileRef()).isEqualTo(2); + assertThat(coverageDetail.getCoveredFile(0).getCoveredLineList()).containsExactly(1, 2, 3, 5, 7); + } + } + + @Test + public void null_if_no_coverage_detail_found() { + assertThat(underTest.readCoverageDetails(UNKNOWN_COMPONENT_REF)).isNull(); + } + + @Test + public void read_file_source() throws Exception { + ScannerReportWriter writer = new ScannerReportWriter(dir); + try (FileOutputStream outputStream = new FileOutputStream(writer.getSourceFile(1))) { + IOUtils.write("line1\nline2", outputStream); + } + + try (InputStream inputStream = FileUtils.openInputStream(underTest.readFileSource(1))) { + assertThat(IOUtils.readLines(inputStream)).containsOnly("line1", "line2"); + } + } + + @Test + public void return_null_when_no_file_source() throws Exception { + assertThat(underTest.readFileSource(UNKNOWN_COMPONENT_REF)).isNull(); + } +} diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java new file mode 100644 index 00000000000..9a08cd726ac --- /dev/null +++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java @@ -0,0 +1,308 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.scanner.protocol.output; + +import com.google.common.collect.Iterators; +import java.io.File; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.core.util.CloseableIterator; +import org.sonar.core.util.Protobuf; +import org.sonar.scanner.protocol.Constants; +import org.sonar.scanner.protocol.output.ScannerReportWriter; +import org.sonar.scanner.protocol.output.FileStructure; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +public class ScannerReportWriterTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + File dir; + ScannerReportWriter underTest; + + @Before + public void setUp() throws Exception { + dir = temp.newFolder(); + underTest = new ScannerReportWriter(dir); + } + + @Test + public void create_dir_if_does_not_exist() { + FileUtils.deleteQuietly(dir); + underTest = new ScannerReportWriter(dir); + + assertThat(dir).isDirectory().exists(); + } + + @Test + public void write_metadata() { + ScannerReport.Metadata.Builder metadata = ScannerReport.Metadata.newBuilder() + .setAnalysisDate(15000000L) + .setProjectKey("PROJECT_A") + .setRootComponentRef(1); + underTest.writeMetadata(metadata.build()); + + ScannerReport.Metadata read = Protobuf.read(underTest.getFileStructure().metadataFile(), ScannerReport.Metadata.PARSER); + assertThat(read.getAnalysisDate()).isEqualTo(15000000L); + assertThat(read.getProjectKey()).isEqualTo("PROJECT_A"); + assertThat(read.getRootComponentRef()).isEqualTo(1); + } + + @Test + public void write_component() { + // no data yet + assertThat(underTest.hasComponentData(FileStructure.Domain.COMPONENT, 1)).isFalse(); + + // write data + ScannerReport.Component.Builder component = ScannerReport.Component.newBuilder() + .setRef(1) + .setLanguage("java") + .setPath("src/Foo.java") + .setType(Constants.ComponentType.FILE) + .setIsTest(false) + .addChildRef(5) + .addChildRef(42); + underTest.writeComponent(component.build()); + + assertThat(underTest.hasComponentData(FileStructure.Domain.COMPONENT, 1)).isTrue(); + File file = underTest.getFileStructure().fileFor(FileStructure.Domain.COMPONENT, 1); + assertThat(file).exists().isFile(); + ScannerReport.Component read = Protobuf.read(file, ScannerReport.Component.PARSER); + assertThat(read.getRef()).isEqualTo(1); + assertThat(read.getChildRefList()).containsOnly(5, 42); + assertThat(read.hasName()).isFalse(); + assertThat(read.getIsTest()).isFalse(); + } + + @Test + public void write_issues() { + // no data yet + assertThat(underTest.hasComponentData(FileStructure.Domain.ISSUES, 1)).isFalse(); + + // write data + ScannerReport.Issue issue = ScannerReport.Issue.newBuilder() + .setLine(50) + .setMsg("the message") + .build(); + + underTest.writeComponentIssues(1, asList(issue)); + + assertThat(underTest.hasComponentData(FileStructure.Domain.ISSUES, 1)).isTrue(); + File file = underTest.getFileStructure().fileFor(FileStructure.Domain.ISSUES, 1); + assertThat(file).exists().isFile(); + try (CloseableIterator<ScannerReport.Issue> read = Protobuf.readStream(file, ScannerReport.Issue.PARSER)) { + assertThat(Iterators.size(read)).isEqualTo(1); + } + } + + @Test + public void write_measures() { + assertThat(underTest.hasComponentData(FileStructure.Domain.MEASURES, 1)).isFalse(); + + ScannerReport.Measure measure = ScannerReport.Measure.newBuilder() + .setStringValue("text-value") + .setDoubleValue(2.5d) + .setValueType(Constants.MeasureValueType.DOUBLE) + .build(); + + underTest.writeComponentMeasures(1, asList(measure)); + + assertThat(underTest.hasComponentData(FileStructure.Domain.MEASURES, 1)).isTrue(); + File file = underTest.getFileStructure().fileFor(FileStructure.Domain.MEASURES, 1); + assertThat(file).exists().isFile(); + try (CloseableIterator<ScannerReport.Measure> read = Protobuf.readStream(file, ScannerReport.Measure.PARSER)) { + assertThat(Iterators.size(read)).isEqualTo(1); + } + } + + @Test + public void write_scm() { + assertThat(underTest.hasComponentData(FileStructure.Domain.CHANGESETS, 1)).isFalse(); + + ScannerReport.Changesets scm = ScannerReport.Changesets.newBuilder() + .setComponentRef(1) + .addChangesetIndexByLine(0) + .addChangeset(ScannerReport.Changesets.Changeset.newBuilder() + .setRevision("123-456-789") + .setAuthor("author") + .setDate(123_456_789L)) + .build(); + + underTest.writeComponentChangesets(scm); + + assertThat(underTest.hasComponentData(FileStructure.Domain.CHANGESETS, 1)).isTrue(); + File file = underTest.getFileStructure().fileFor(FileStructure.Domain.CHANGESETS, 1); + assertThat(file).exists().isFile(); + ScannerReport.Changesets read = Protobuf.read(file, ScannerReport.Changesets.PARSER); + assertThat(read.getComponentRef()).isEqualTo(1); + assertThat(read.getChangesetCount()).isEqualTo(1); + assertThat(read.getChangesetList()).hasSize(1); + assertThat(read.getChangeset(0).getDate()).isEqualTo(123_456_789L); + } + + @Test + public void write_duplications() { + assertThat(underTest.hasComponentData(FileStructure.Domain.DUPLICATIONS, 1)).isFalse(); + + ScannerReport.Duplication duplication = ScannerReport.Duplication.newBuilder() + .setOriginPosition(ScannerReport.TextRange.newBuilder() + .setStartLine(1) + .setEndLine(5) + .build()) + .addDuplicate(ScannerReport.Duplicate.newBuilder() + .setOtherFileRef(2) + .setRange(ScannerReport.TextRange.newBuilder() + .setStartLine(6) + .setEndLine(10) + .build()) + .build()) + .build(); + underTest.writeComponentDuplications(1, asList(duplication)); + + assertThat(underTest.hasComponentData(FileStructure.Domain.DUPLICATIONS, 1)).isTrue(); + File file = underTest.getFileStructure().fileFor(FileStructure.Domain.DUPLICATIONS, 1); + assertThat(file).exists().isFile(); + try (CloseableIterator<ScannerReport.Duplication> duplications = Protobuf.readStream(file, ScannerReport.Duplication.PARSER)) { + ScannerReport.Duplication dup = duplications.next(); + assertThat(dup.getOriginPosition()).isNotNull(); + assertThat(dup.getDuplicateList()).hasSize(1); + } + } + + @Test + public void write_duplication_blocks() { + assertThat(underTest.hasComponentData(FileStructure.Domain.CPD_TEXT_BLOCKS, 1)).isFalse(); + + ScannerReport.CpdTextBlock duplicationBlock = ScannerReport.CpdTextBlock.newBuilder() + .setHash("abcdefghijklmnop") + .setStartLine(1) + .setEndLine(2) + .setStartTokenIndex(10) + .setEndTokenIndex(15) + .build(); + underTest.writeCpdTextBlocks(1, asList(duplicationBlock)); + + assertThat(underTest.hasComponentData(FileStructure.Domain.CPD_TEXT_BLOCKS, 1)).isTrue(); + File file = underTest.getFileStructure().fileFor(FileStructure.Domain.CPD_TEXT_BLOCKS, 1); + assertThat(file).exists().isFile(); + try (CloseableIterator<ScannerReport.CpdTextBlock> duplicationBlocks = Protobuf.readStream(file, ScannerReport.CpdTextBlock.parser())) { + ScannerReport.CpdTextBlock duplicationBlockResult = duplicationBlocks.next(); + assertThat(duplicationBlockResult.getHash()).isEqualTo("abcdefghijklmnop"); + assertThat(duplicationBlockResult.getStartLine()).isEqualTo(1); + assertThat(duplicationBlockResult.getEndLine()).isEqualTo(2); + assertThat(duplicationBlockResult.getStartTokenIndex()).isEqualTo(10); + assertThat(duplicationBlockResult.getEndTokenIndex()).isEqualTo(15); + } + } + + @Test + public void write_symbols() { + // no data yet + assertThat(underTest.hasComponentData(FileStructure.Domain.SYMBOLS, 1)).isFalse(); + + // write data + ScannerReport.Symbol symbol = ScannerReport.Symbol.newBuilder() + .setDeclaration(ScannerReport.TextRange.newBuilder() + .setStartLine(1) + .setStartOffset(3) + .setEndLine(1) + .setEndOffset(5) + .build()) + .addReference(ScannerReport.TextRange.newBuilder() + .setStartLine(10) + .setStartOffset(15) + .setEndLine(11) + .setEndOffset(2) + .build()) + .build(); + + underTest.writeComponentSymbols(1, asList(symbol)); + + assertThat(underTest.hasComponentData(FileStructure.Domain.SYMBOLS, 1)).isTrue(); + + File file = underTest.getFileStructure().fileFor(FileStructure.Domain.SYMBOLS, 1); + assertThat(file).exists().isFile(); + try (CloseableIterator<ScannerReport.Symbol> read = Protobuf.readStream(file, ScannerReport.Symbol.PARSER)) { + assertThat(read).hasSize(1); + } + } + + @Test + public void write_syntax_highlighting() { + // no data yet + assertThat(underTest.hasComponentData(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, 1)).isFalse(); + + underTest.writeComponentSyntaxHighlighting(1, asList( + ScannerReport.SyntaxHighlighting.newBuilder() + .setRange(ScannerReport.TextRange.newBuilder() + .setStartLine(1) + .setEndLine(1) + .build()) + .setType(Constants.HighlightingType.ANNOTATION) + .build())); + + assertThat(underTest.hasComponentData(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, 1)).isTrue(); + } + + @Test + public void write_coverage() { + // no data yet + assertThat(underTest.hasComponentData(FileStructure.Domain.COVERAGES, 1)).isFalse(); + + underTest.writeComponentCoverage(1, asList( + ScannerReport.Coverage.newBuilder() + .setLine(1) + .setConditions(1) + .setUtHits(true) + .setItHits(false) + .setUtCoveredConditions(1) + .setItCoveredConditions(1) + .setOverallCoveredConditions(1) + .build())); + + assertThat(underTest.hasComponentData(FileStructure.Domain.COVERAGES, 1)).isTrue(); + } + + @Test + public void write_tests() { + assertThat(underTest.hasComponentData(FileStructure.Domain.TESTS, 1)).isFalse(); + + underTest.writeTests(1, asList( + ScannerReport.Test.getDefaultInstance())); + + assertThat(underTest.hasComponentData(FileStructure.Domain.TESTS, 1)).isTrue(); + + } + + @Test + public void write_coverage_details() { + assertThat(underTest.hasComponentData(FileStructure.Domain.COVERAGE_DETAILS, 1)).isFalse(); + + underTest.writeCoverageDetails(1, asList( + ScannerReport.CoverageDetail.getDefaultInstance())); + + assertThat(underTest.hasComponentData(FileStructure.Domain.COVERAGE_DETAILS, 1)).isTrue(); + } +} diff --git a/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/GlobalRepositoriesTest/expected.json b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/GlobalRepositoriesTest/expected.json new file mode 100644 index 00000000000..de38ae0cb18 --- /dev/null +++ b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/GlobalRepositoriesTest/expected.json @@ -0,0 +1,21 @@ +{ + "timestamp": 10, + "metrics": [ + { + "id": 1, + "key": "ncloc", + "valueType": "INT", + "description": "Description", + "direction": -1, + "name": "NCLOC", + "qualitative": true, + "userManaged": false, + "worstValue": 2.0, + "bestValue": 1.0, + "optimizedBestValue": true + } + ], + "globalSettings": { + "prop": "value" + } +} diff --git a/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/ProjectRepositoriesTest/testToJson.json b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/ProjectRepositoriesTest/testToJson.json new file mode 100644 index 00000000000..ba7489143a6 --- /dev/null +++ b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/ProjectRepositoriesTest/testToJson.json @@ -0,0 +1,23 @@ +{ + "timestamp": 10, + "settingsByModule": { + "foo": { + "prop1": "value1", + "prop2": "value2", + "prop": "value" + } + }, + "fileDataByModuleAndPath": { + "foo": { + "src/main/java/Foo.java": { + "hash": "xyz", + "needBlame": true + }, + "src/main/java/Foo2.java": { + "hash": "xyz", + "needBlame": false + } + } + }, + "lastAnalysisDate": "2014-05-18T15:50:45+0100" +} diff --git a/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/empty.json b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/empty.json new file mode 100644 index 00000000000..055fe8b8d63 --- /dev/null +++ b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/empty.json @@ -0,0 +1 @@ +{"total":3225,"p":30,"ps":500,"rules":[]}
\ No newline at end of file diff --git a/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/expected.json b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/expected.json new file mode 100644 index 00000000000..89350a7e331 --- /dev/null +++ b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/input/RulesSearchTest/expected.json @@ -0,0 +1 @@ +{"total":290,"p":1,"ps":2,"rules":[{"key":"squid:S1194","internalKey":"S1194","repo":"squid","name":"\"java.lang.Error\" should not be extended","severity":"MAJOR","lang":"java"},{"key":"squid:ObjectFinalizeOverridenCallsSuperFinalizeCheck","internalKey":"ObjectFinalizeOverridenCallsSuperFinalizeCheck","repo":"squid","name":"super.finalize() should be called at the end of Object.finalize() implementations","severity":"BLOCKER","lang":"java"}]}
\ No newline at end of file diff --git a/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/output/component/ReportComponentsTest/expected.json b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/output/component/ReportComponentsTest/expected.json new file mode 100644 index 00000000000..581bbc5ea23 --- /dev/null +++ b/sonar-scanner-protocol/src/test/resources/org/sonar/scanner/protocol/output/component/ReportComponentsTest/expected.json @@ -0,0 +1,43 @@ +{ + "analysisDate": "2012-12-12T00:00:00+0100", + "root": { + "batchId": 1, + "id": 11, + "snapshotId": 111, + "name": "Root project", + "type": "PRJ", + "children": [ + { + "batchId": 2, + "id": 22, + "snapshotId": 222, + "path": "module1", + "name": "Module", + "type": "MOD", + "children": [ + { + "batchId": 3, + "id": 33, + "snapshotId": 333, + "path": "src", + "name": "src", + "type": "DIR", + "children": [ + { + "batchId": 4, + "id": 44, + "snapshotId": 444, + "path": "Foo.java", + "name": "Foo.java", + "type": "FIL", + "languageKey": "java", + "isTest": true, + "children": [] + } + ] + } + ] + } + ] + } +}
\ No newline at end of file |