You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PersistTestsStep.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. /*
  2. * SonarQube, open source software quality management tool.
  3. * Copyright (C) 2008-2014 SonarSource
  4. * mailto:contact AT sonarsource DOT com
  5. *
  6. * SonarQube 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. * SonarQube 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.step;
  21. import com.google.common.base.Joiner;
  22. import com.google.common.collect.ArrayListMultimap;
  23. import com.google.common.collect.HashBasedTable;
  24. import com.google.common.collect.ImmutableMap;
  25. import com.google.common.collect.Multimap;
  26. import com.google.common.collect.Table;
  27. import java.util.ArrayList;
  28. import java.util.Collection;
  29. import java.util.HashMap;
  30. import java.util.HashSet;
  31. import java.util.List;
  32. import java.util.Map;
  33. import java.util.Set;
  34. import org.apache.ibatis.session.ResultContext;
  35. import org.apache.ibatis.session.ResultHandler;
  36. import org.sonar.api.utils.System2;
  37. import org.sonar.api.utils.internal.Uuids;
  38. import org.sonar.api.utils.log.Logger;
  39. import org.sonar.api.utils.log.Loggers;
  40. import org.sonar.batch.protocol.output.BatchReport;
  41. import org.sonar.db.DbSession;
  42. import org.sonar.db.MyBatis;
  43. import org.sonar.db.source.FileSourceDto;
  44. import org.sonar.db.source.FileSourceDto.Type;
  45. import org.sonar.server.computation.batch.BatchReportReader;
  46. import org.sonar.server.computation.component.Component;
  47. import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor;
  48. import org.sonar.server.computation.component.TreeRootHolder;
  49. import org.sonar.server.db.DbClient;
  50. import org.sonar.server.source.db.FileSourceDb;
  51. import org.sonar.server.source.db.FileSourceDb.Test.TestStatus;
  52. import org.sonar.server.util.CloseableIterator;
  53. public class PersistTestsStep implements ComputationStep {
  54. private static final Logger LOG = Loggers.get(PersistTestsStep.class);
  55. private final DbClient dbClient;
  56. private final System2 system;
  57. private final BatchReportReader reportReader;
  58. private final TreeRootHolder treeRootHolder;
  59. public PersistTestsStep(DbClient dbClient, System2 system, BatchReportReader reportReader, TreeRootHolder treeRootHolder) {
  60. this.dbClient = dbClient;
  61. this.system = system;
  62. this.reportReader = reportReader;
  63. this.treeRootHolder = treeRootHolder;
  64. }
  65. @Override
  66. public void execute() {
  67. DbSession session = dbClient.openSession(true);
  68. try {
  69. TestDepthTraversalTypeAwareVisitor visitor = new TestDepthTraversalTypeAwareVisitor(session);
  70. visitor.visit(treeRootHolder.getRoot());
  71. session.commit();
  72. if (visitor.hasUnprocessedCoverageDetails) {
  73. LOG.warn("Some coverage tests are not taken into account during analysis of project '{}'", visitor.getProjectKey());
  74. }
  75. } finally {
  76. MyBatis.closeQuietly(session);
  77. }
  78. }
  79. @Override
  80. public String getDescription() {
  81. return "Persist tests";
  82. }
  83. private class TestDepthTraversalTypeAwareVisitor extends DepthTraversalTypeAwareVisitor {
  84. final DbSession session;
  85. final Map<String, FileSourceDto> existingFileSourcesByUuid;
  86. final String projectUuid;
  87. final String projectKey;
  88. boolean hasUnprocessedCoverageDetails = false;
  89. public TestDepthTraversalTypeAwareVisitor(DbSession session) {
  90. super(Component.Type.FILE, Order.PRE_ORDER);
  91. this.session = session;
  92. this.existingFileSourcesByUuid = new HashMap<>();
  93. this.projectUuid = treeRootHolder.getRoot().getUuid();
  94. this.projectKey = treeRootHolder.getRoot().getKey();
  95. session.select("org.sonar.db.source.FileSourceMapper.selectHashesForProject",
  96. ImmutableMap.of("projectUuid", treeRootHolder.getRoot().getUuid(), "dataType", Type.TEST),
  97. new ResultHandler() {
  98. @Override
  99. public void handleResult(ResultContext context) {
  100. FileSourceDto dto = (FileSourceDto) context.getResultObject();
  101. existingFileSourcesByUuid.put(dto.getFileUuid(), dto);
  102. }
  103. });
  104. }
  105. @Override
  106. public void visitFile(Component file) {
  107. if (file.getFileAttributes().isUnitTest()) {
  108. persistTestResults(file);
  109. }
  110. }
  111. private void persistTestResults(Component component) {
  112. Multimap<String, FileSourceDb.Test.Builder> testsByName = buildDbTests(component.getRef());
  113. Table<String, String, FileSourceDb.Test.CoveredFile.Builder> coveredFilesByName = loadCoverageDetails(component.getRef());
  114. List<FileSourceDb.Test> tests = addCoveredFilesToTests(testsByName, coveredFilesByName);
  115. if (checkIfThereAreUnprocessedCoverageDetails(testsByName, coveredFilesByName, component.getKey())) {
  116. hasUnprocessedCoverageDetails = true;
  117. }
  118. if (tests.isEmpty()) {
  119. return;
  120. }
  121. String componentUuid = getUuid(component.getRef());
  122. FileSourceDto existingDto = existingFileSourcesByUuid.get(componentUuid);
  123. long now = system.now();
  124. if (existingDto != null) {
  125. // update
  126. existingDto
  127. .setTestData(tests)
  128. .setUpdatedAt(now);
  129. dbClient.fileSourceDao().update(session, existingDto);
  130. } else {
  131. // insert
  132. FileSourceDto newDto = new FileSourceDto()
  133. .setTestData(tests)
  134. .setFileUuid(componentUuid)
  135. .setProjectUuid(projectUuid)
  136. .setDataType(Type.TEST)
  137. .setCreatedAt(now)
  138. .setUpdatedAt(now);
  139. dbClient.fileSourceDao().insert(session, newDto);
  140. }
  141. }
  142. private boolean checkIfThereAreUnprocessedCoverageDetails(Multimap<String, FileSourceDb.Test.Builder> testsByName,
  143. Table<String, String, FileSourceDb.Test.CoveredFile.Builder> coveredFilesByName, String componentKey) {
  144. Set<String> unprocessedCoverageDetailNames = new HashSet<>(coveredFilesByName.rowKeySet());
  145. unprocessedCoverageDetailNames.removeAll(testsByName.keySet());
  146. boolean hasUnprocessedCoverage = !unprocessedCoverageDetailNames.isEmpty();
  147. if (hasUnprocessedCoverage) {
  148. LOG.trace("The following test coverages for file '{}' have not been taken into account: {}", componentKey, Joiner.on(", ").join(unprocessedCoverageDetailNames));
  149. }
  150. return hasUnprocessedCoverage;
  151. }
  152. private List<FileSourceDb.Test> addCoveredFilesToTests(Multimap<String, FileSourceDb.Test.Builder> testsByName,
  153. Table<String, String, FileSourceDb.Test.CoveredFile.Builder> coveredFilesByName) {
  154. List<FileSourceDb.Test> tests = new ArrayList<>();
  155. for (FileSourceDb.Test.Builder test : testsByName.values()) {
  156. Collection<FileSourceDb.Test.CoveredFile.Builder> coveredFiles = coveredFilesByName.row(test.getName()).values();
  157. if (!coveredFiles.isEmpty()) {
  158. for (FileSourceDb.Test.CoveredFile.Builder coveredFile : coveredFiles) {
  159. test.addCoveredFile(coveredFile);
  160. }
  161. }
  162. tests.add(test.build());
  163. }
  164. return tests;
  165. }
  166. private Multimap<String, FileSourceDb.Test.Builder> buildDbTests(int componentRed) {
  167. Multimap<String, FileSourceDb.Test.Builder> tests = ArrayListMultimap.create();
  168. try (CloseableIterator<BatchReport.Test> testIterator = reportReader.readTests(componentRed)) {
  169. while (testIterator.hasNext()) {
  170. BatchReport.Test batchTest = testIterator.next();
  171. FileSourceDb.Test.Builder dbTest = FileSourceDb.Test.newBuilder();
  172. dbTest.setUuid(Uuids.create());
  173. dbTest.setName(batchTest.getName());
  174. if (batchTest.hasStacktrace()) {
  175. dbTest.setStacktrace(batchTest.getStacktrace());
  176. }
  177. if (batchTest.hasStatus()) {
  178. dbTest.setStatus(TestStatus.valueOf(batchTest.getStatus().name()));
  179. }
  180. if (batchTest.hasMsg()) {
  181. dbTest.setMsg(batchTest.getMsg());
  182. }
  183. if (batchTest.hasDurationInMs()) {
  184. dbTest.setExecutionTimeMs(batchTest.getDurationInMs());
  185. }
  186. tests.put(dbTest.getName(), dbTest);
  187. }
  188. }
  189. return tests;
  190. }
  191. /**
  192. * returns a Table of (test name, main file uuid, covered file)
  193. */
  194. private Table<String, String, FileSourceDb.Test.CoveredFile.Builder> loadCoverageDetails(int testFileRef) {
  195. Table<String, String, FileSourceDb.Test.CoveredFile.Builder> nameToCoveredFiles = HashBasedTable.create();
  196. try (CloseableIterator<BatchReport.CoverageDetail> coverageIterator = reportReader.readCoverageDetails(testFileRef)) {
  197. while (coverageIterator.hasNext()) {
  198. BatchReport.CoverageDetail batchCoverageDetail = coverageIterator.next();
  199. for (BatchReport.CoverageDetail.CoveredFile batchCoveredFile : batchCoverageDetail.getCoveredFileList()) {
  200. String testName = batchCoverageDetail.getTestName();
  201. String mainFileUuid = getUuid(batchCoveredFile.getFileRef());
  202. FileSourceDb.Test.CoveredFile.Builder existingDbCoveredFile = nameToCoveredFiles.get(testName, mainFileUuid);
  203. List<Integer> batchCoveredLines = batchCoveredFile.getCoveredLineList();
  204. if (existingDbCoveredFile == null) {
  205. FileSourceDb.Test.CoveredFile.Builder dbCoveredFile = FileSourceDb.Test.CoveredFile.newBuilder()
  206. .setFileUuid(getUuid(batchCoveredFile.getFileRef()))
  207. .addAllCoveredLine(batchCoveredLines);
  208. nameToCoveredFiles.put(testName, mainFileUuid, dbCoveredFile);
  209. } else {
  210. List<Integer> remainingBatchCoveredLines = new ArrayList<>(batchCoveredLines);
  211. remainingBatchCoveredLines.removeAll(existingDbCoveredFile.getCoveredLineList());
  212. existingDbCoveredFile.addAllCoveredLine(batchCoveredLines);
  213. }
  214. }
  215. }
  216. }
  217. return nameToCoveredFiles;
  218. }
  219. private String getUuid(int fileRef) {
  220. return treeRootHolder.getComponentByRef(fileRef).getUuid();
  221. }
  222. public String getProjectKey() {
  223. return projectKey;
  224. }
  225. }
  226. }