3 * Copyright (C) 2009-2016 SonarSource SA
4 * mailto:contact AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.computation.task.projectanalysis.step;
22 import com.google.common.base.Joiner;
23 import com.google.common.collect.ArrayListMultimap;
24 import com.google.common.collect.HashBasedTable;
25 import com.google.common.collect.ImmutableMap;
26 import com.google.common.collect.Multimap;
27 import com.google.common.collect.Table;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.List;
35 import org.apache.ibatis.session.ResultContext;
36 import org.apache.ibatis.session.ResultHandler;
37 import org.sonar.api.utils.System2;
38 import org.sonar.api.utils.log.Logger;
39 import org.sonar.api.utils.log.Loggers;
40 import org.sonar.core.util.CloseableIterator;
41 import org.sonar.core.util.Uuids;
42 import org.sonar.db.DbClient;
43 import org.sonar.db.DbSession;
44 import org.sonar.db.MyBatis;
45 import org.sonar.db.protobuf.DbFileSources;
46 import org.sonar.db.source.FileSourceDto;
47 import org.sonar.db.source.FileSourceDto.Type;
48 import org.sonar.scanner.protocol.output.ScannerReport;
49 import org.sonar.scanner.protocol.output.ScannerReport.Test.TestStatus;
50 import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReader;
51 import org.sonar.server.computation.task.projectanalysis.component.Component;
52 import org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor;
53 import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
54 import org.sonar.server.computation.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
55 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
56 import org.sonar.server.computation.task.projectanalysis.component.TypeAwareVisitorAdapter;
57 import org.sonar.server.computation.task.step.ComputationStep;
59 public class PersistTestsStep implements ComputationStep {
61 private static final Logger LOG = Loggers.get(PersistTestsStep.class);
63 private final DbClient dbClient;
64 private final System2 system;
65 private final BatchReportReader reportReader;
66 private final TreeRootHolder treeRootHolder;
68 public PersistTestsStep(DbClient dbClient, System2 system, BatchReportReader reportReader, TreeRootHolder treeRootHolder) {
69 this.dbClient = dbClient;
71 this.reportReader = reportReader;
72 this.treeRootHolder = treeRootHolder;
76 public void execute() {
77 DbSession session = dbClient.openSession(true);
79 TestDepthTraversalTypeAwareVisitor visitor = new TestDepthTraversalTypeAwareVisitor(session);
80 new DepthTraversalTypeAwareCrawler(visitor).visit(treeRootHolder.getRoot());
82 if (visitor.hasUnprocessedCoverageDetails) {
83 LOG.warn("Some coverage tests are not taken into account during analysis of project '{}'", visitor.getProjectKey());
86 MyBatis.closeQuietly(session);
91 public String getDescription() {
92 return "Persist tests";
95 private class TestDepthTraversalTypeAwareVisitor extends TypeAwareVisitorAdapter {
96 final DbSession session;
97 final Map<String, FileSourceDto> existingFileSourcesByUuid;
98 final String projectUuid;
99 final String projectKey;
100 boolean hasUnprocessedCoverageDetails = false;
102 public TestDepthTraversalTypeAwareVisitor(DbSession session) {
103 super(CrawlerDepthLimit.FILE, ComponentVisitor.Order.PRE_ORDER);
104 this.session = session;
105 this.existingFileSourcesByUuid = new HashMap<>();
106 this.projectUuid = treeRootHolder.getRoot().getUuid();
107 this.projectKey = treeRootHolder.getRoot().getKey();
108 session.select("org.sonar.db.source.FileSourceMapper.selectHashesForProject",
109 ImmutableMap.of("projectUuid", treeRootHolder.getRoot().getUuid(), "dataType", Type.TEST),
110 new ResultHandler() {
112 public void handleResult(ResultContext context) {
113 FileSourceDto dto = (FileSourceDto) context.getResultObject();
114 existingFileSourcesByUuid.put(dto.getFileUuid(), dto);
120 public void visitFile(Component file) {
121 if (file.getFileAttributes().isUnitTest()) {
122 persistTestResults(file);
126 private void persistTestResults(Component component) {
127 Multimap<String, DbFileSources.Test.Builder> testsByName = buildDbTests(component.getReportAttributes().getRef());
128 Table<String, String, DbFileSources.Test.CoveredFile.Builder> coveredFilesByName = loadCoverageDetails(component.getReportAttributes().getRef());
129 List<DbFileSources.Test> tests = addCoveredFilesToTests(testsByName, coveredFilesByName);
130 if (checkIfThereAreUnprocessedCoverageDetails(testsByName, coveredFilesByName, component.getKey())) {
131 hasUnprocessedCoverageDetails = true;
134 if (tests.isEmpty()) {
138 String componentUuid = getUuid(component.getReportAttributes().getRef());
139 FileSourceDto existingDto = existingFileSourcesByUuid.get(componentUuid);
140 long now = system.now();
141 if (existingDto != null) {
146 dbClient.fileSourceDao().update(session, existingDto);
149 FileSourceDto newDto = new FileSourceDto()
151 .setFileUuid(componentUuid)
152 .setProjectUuid(projectUuid)
153 .setDataType(Type.TEST)
156 dbClient.fileSourceDao().insert(session, newDto);
160 private boolean checkIfThereAreUnprocessedCoverageDetails(Multimap<String, DbFileSources.Test.Builder> testsByName,
161 Table<String, String, DbFileSources.Test.CoveredFile.Builder> coveredFilesByName, String componentKey) {
162 Set<String> unprocessedCoverageDetailNames = new HashSet<>(coveredFilesByName.rowKeySet());
163 unprocessedCoverageDetailNames.removeAll(testsByName.keySet());
164 boolean hasUnprocessedCoverage = !unprocessedCoverageDetailNames.isEmpty();
165 if (hasUnprocessedCoverage) {
166 LOG.trace("The following test coverages for file '{}' have not been taken into account: {}", componentKey, Joiner.on(", ").join(unprocessedCoverageDetailNames));
168 return hasUnprocessedCoverage;
171 private List<DbFileSources.Test> addCoveredFilesToTests(Multimap<String, DbFileSources.Test.Builder> testsByName,
172 Table<String, String, DbFileSources.Test.CoveredFile.Builder> coveredFilesByName) {
173 List<DbFileSources.Test> tests = new ArrayList<>();
174 for (DbFileSources.Test.Builder test : testsByName.values()) {
175 Collection<DbFileSources.Test.CoveredFile.Builder> coveredFiles = coveredFilesByName.row(test.getName()).values();
176 if (!coveredFiles.isEmpty()) {
177 for (DbFileSources.Test.CoveredFile.Builder coveredFile : coveredFiles) {
178 test.addCoveredFile(coveredFile);
181 tests.add(test.build());
187 private Multimap<String, DbFileSources.Test.Builder> buildDbTests(int componentRed) {
188 Multimap<String, DbFileSources.Test.Builder> tests = ArrayListMultimap.create();
190 try (CloseableIterator<ScannerReport.Test> testIterator = reportReader.readTests(componentRed)) {
191 while (testIterator.hasNext()) {
192 ScannerReport.Test batchTest = testIterator.next();
193 DbFileSources.Test.Builder dbTest = DbFileSources.Test.newBuilder();
194 dbTest.setUuid(Uuids.create());
195 dbTest.setName(batchTest.getName());
196 if (!batchTest.getStacktrace().isEmpty()) {
197 dbTest.setStacktrace(batchTest.getStacktrace());
199 if (batchTest.getStatus() != TestStatus.UNSET) {
200 dbTest.setStatus(DbFileSources.Test.TestStatus.valueOf(batchTest.getStatus().name()));
202 if (!batchTest.getMsg().isEmpty()) {
203 dbTest.setMsg(batchTest.getMsg());
205 dbTest.setExecutionTimeMs(batchTest.getDurationInMs());
207 tests.put(dbTest.getName(), dbTest);
215 * returns a Table of (test name, main file uuid, covered file)
217 private Table<String, String, DbFileSources.Test.CoveredFile.Builder> loadCoverageDetails(int testFileRef) {
218 Table<String, String, DbFileSources.Test.CoveredFile.Builder> nameToCoveredFiles = HashBasedTable.create();
220 try (CloseableIterator<ScannerReport.CoverageDetail> coverageIterator = reportReader.readCoverageDetails(testFileRef)) {
221 while (coverageIterator.hasNext()) {
222 ScannerReport.CoverageDetail batchCoverageDetail = coverageIterator.next();
223 for (ScannerReport.CoverageDetail.CoveredFile batchCoveredFile : batchCoverageDetail.getCoveredFileList()) {
224 String testName = batchCoverageDetail.getTestName();
225 String mainFileUuid = getUuid(batchCoveredFile.getFileRef());
226 DbFileSources.Test.CoveredFile.Builder existingDbCoveredFile = nameToCoveredFiles.get(testName, mainFileUuid);
227 List<Integer> batchCoveredLines = batchCoveredFile.getCoveredLineList();
228 if (existingDbCoveredFile == null) {
229 DbFileSources.Test.CoveredFile.Builder dbCoveredFile = DbFileSources.Test.CoveredFile.newBuilder()
230 .setFileUuid(getUuid(batchCoveredFile.getFileRef()))
231 .addAllCoveredLine(batchCoveredLines);
232 nameToCoveredFiles.put(testName, mainFileUuid, dbCoveredFile);
234 List<Integer> remainingBatchCoveredLines = new ArrayList<>(batchCoveredLines);
235 remainingBatchCoveredLines.removeAll(existingDbCoveredFile.getCoveredLineList());
236 existingDbCoveredFile.addAllCoveredLine(batchCoveredLines);
241 return nameToCoveredFiles;
244 private String getUuid(int fileRef) {
245 return treeRootHolder.getComponentByRef(fileRef).getUuid();
248 public String getProjectKey() {