]> source.dussan.org Git - sonarqube.git/blob
2f4925cd1347980bb69a132813c671897e52e0e0
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2016 SonarSource SA
4  * mailto:contact AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.server.computation.task.projectanalysis.step;
21
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;
33 import java.util.Map;
34 import java.util.Set;
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;
58
59 public class PersistTestsStep implements ComputationStep {
60
61   private static final Logger LOG = Loggers.get(PersistTestsStep.class);
62
63   private final DbClient dbClient;
64   private final System2 system;
65   private final BatchReportReader reportReader;
66   private final TreeRootHolder treeRootHolder;
67
68   public PersistTestsStep(DbClient dbClient, System2 system, BatchReportReader reportReader, TreeRootHolder treeRootHolder) {
69     this.dbClient = dbClient;
70     this.system = system;
71     this.reportReader = reportReader;
72     this.treeRootHolder = treeRootHolder;
73   }
74
75   @Override
76   public void execute() {
77     DbSession session = dbClient.openSession(true);
78     try {
79       TestDepthTraversalTypeAwareVisitor visitor = new TestDepthTraversalTypeAwareVisitor(session);
80       new DepthTraversalTypeAwareCrawler(visitor).visit(treeRootHolder.getRoot());
81       session.commit();
82       if (visitor.hasUnprocessedCoverageDetails) {
83         LOG.warn("Some coverage tests are not taken into account during analysis of project '{}'", visitor.getProjectKey());
84       }
85     } finally {
86       MyBatis.closeQuietly(session);
87     }
88   }
89
90   @Override
91   public String getDescription() {
92     return "Persist tests";
93   }
94
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;
101
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() {
111           @Override
112           public void handleResult(ResultContext context) {
113             FileSourceDto dto = (FileSourceDto) context.getResultObject();
114             existingFileSourcesByUuid.put(dto.getFileUuid(), dto);
115           }
116         });
117     }
118
119     @Override
120     public void visitFile(Component file) {
121       if (file.getFileAttributes().isUnitTest()) {
122         persistTestResults(file);
123       }
124     }
125
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;
132       }
133
134       if (tests.isEmpty()) {
135         return;
136       }
137
138       String componentUuid = getUuid(component.getReportAttributes().getRef());
139       FileSourceDto existingDto = existingFileSourcesByUuid.get(componentUuid);
140       long now = system.now();
141       if (existingDto != null) {
142         // update
143         existingDto
144           .setTestData(tests)
145           .setUpdatedAt(now);
146         dbClient.fileSourceDao().update(session, existingDto);
147       } else {
148         // insert
149         FileSourceDto newDto = new FileSourceDto()
150           .setTestData(tests)
151           .setFileUuid(componentUuid)
152           .setProjectUuid(projectUuid)
153           .setDataType(Type.TEST)
154           .setCreatedAt(now)
155           .setUpdatedAt(now);
156         dbClient.fileSourceDao().insert(session, newDto);
157       }
158     }
159
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));
167       }
168       return hasUnprocessedCoverage;
169     }
170
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);
179           }
180         }
181         tests.add(test.build());
182       }
183
184       return tests;
185     }
186
187     private Multimap<String, DbFileSources.Test.Builder> buildDbTests(int componentRed) {
188       Multimap<String, DbFileSources.Test.Builder> tests = ArrayListMultimap.create();
189
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());
198           }
199           if (batchTest.getStatus() != TestStatus.UNSET) {
200             dbTest.setStatus(DbFileSources.Test.TestStatus.valueOf(batchTest.getStatus().name()));
201           }
202           if (!batchTest.getMsg().isEmpty()) {
203             dbTest.setMsg(batchTest.getMsg());
204           }
205           dbTest.setExecutionTimeMs(batchTest.getDurationInMs());
206
207           tests.put(dbTest.getName(), dbTest);
208         }
209       }
210
211       return tests;
212     }
213
214     /**
215      * returns a Table of (test name, main file uuid, covered file)
216      */
217     private Table<String, String, DbFileSources.Test.CoveredFile.Builder> loadCoverageDetails(int testFileRef) {
218       Table<String, String, DbFileSources.Test.CoveredFile.Builder> nameToCoveredFiles = HashBasedTable.create();
219
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);
233             } else {
234               List<Integer> remainingBatchCoveredLines = new ArrayList<>(batchCoveredLines);
235               remainingBatchCoveredLines.removeAll(existingDbCoveredFile.getCoveredLineList());
236               existingDbCoveredFile.addAllCoveredLine(batchCoveredLines);
237             }
238           }
239         }
240       }
241       return nameToCoveredFiles;
242     }
243
244     private String getUuid(int fileRef) {
245       return treeRootHolder.getComponentByRef(fileRef).getUuid();
246     }
247
248     public String getProjectKey() {
249       return projectKey;
250     }
251   }
252
253 }