| // Copyright 2018 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.devtools.coverageoutputgenerator; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import com.google.common.collect.ImmutableList; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| /** |
| * Helper class for creating and parsing lcov tracefiles and the necessary data structured used by |
| * {@code LcovMerger}. |
| * |
| * <p>The tests are floating around 2 main tracefiles (numbered with 1 and 2 throughout the tests |
| * code base). The tracefiles refer the same source file with different coverage data, making them |
| * good candidates for being merged. |
| * |
| * <p>There are multiple static variables defined for information extracted from each of |
| * tracefile{1,2} (e.g. the number of lines found, the number of functions hit, lines execution). |
| * |
| * <p>Additionally, the 2 tracefiles may be used in tests multiple times with different source |
| * filenames so they could be considered as different entries in the merged tracefile. |
| */ |
| public class LcovMergerTestUtils { |
| |
| // The content of tracefile1. |
| static final ImmutableList<String> TRACEFILE1 = |
| ImmutableList.of( |
| "SF:SOURCE_FILENAME", |
| "FN:10,file1-func1", |
| "FN:20,file1-func2", |
| "FN:25,file1-func3", |
| "FNDA:3,file1-func1", |
| "FNDA:5,file1-func2", |
| "FNDA:0,file1-func3", |
| "FNF:3", |
| "FNH:2", |
| "DA:10,3", |
| "DA:11,3", |
| "DA:12,30", |
| "DA:13,30", |
| "DA:15,3", |
| "DA:16,0", |
| "DA:17,0", |
| "DA:19,3", |
| "DA:20,5", |
| "DA:21,5", |
| "DA:22,5", |
| "DA:23,5", |
| "DA:25,0", |
| "DA:26,0", |
| "LH:10", |
| "LF:14", |
| "end_of_record"); |
| |
| static final ImmutableList<String> TRACEFILE1_DIFFERENT_NAME = |
| ImmutableList.of( |
| "SF:DIFFERENT_SOURCE_FILENAME", |
| "FN:10,file1-func1", |
| "FN:20,file1-func2", |
| "FN:25,file1-func3", |
| "FNDA:3,file1-func1", |
| "FNDA:5,file1-func2", |
| "FNDA:0,file1-func3", |
| "FNF:3", |
| "FNH:2", |
| "DA:10,3", |
| "DA:11,3", |
| "DA:12,30", |
| "DA:13,30", |
| "DA:15,3", |
| "DA:16,0", |
| "DA:17,0", |
| "DA:19,3", |
| "DA:20,5", |
| "DA:21,5", |
| "DA:22,5", |
| "DA:23,5", |
| "DA:25,0", |
| "DA:26,0", |
| "LH:10", |
| "LF:14", |
| "end_of_record"); |
| |
| // The content of tracefile2. |
| static final ImmutableList<String> TRACEFILE2 = |
| ImmutableList.of( |
| "SF:SOURCE_FILENAME", |
| "FN:10,file1-func1", |
| "FN:20,file1-func2", |
| "FN:25,file1-func3", |
| "FNDA:2,file1-func1", |
| "FNDA:3,file1-func2", |
| "FNDA:2,file1-func3", |
| "FNF:3", |
| "FNH:3", |
| "DA:10,2", |
| "DA:11,2", |
| "DA:12,20", |
| "DA:13,20", |
| "DA:15,2", |
| "DA:16,2", |
| "DA:17,2", |
| "DA:19,0", |
| "DA:20,3", |
| "DA:21,3", |
| "DA:22,3", |
| "DA:23,3", |
| "DA:25,2", |
| "DA:26,2", |
| "LH:13", |
| "LF:14", |
| "end_of_record"); |
| |
| // The content of a tracefile after tracefile1 and tracefile2 were merged together. |
| static final ImmutableList<String> MERGED_TRACEFILE = |
| ImmutableList.of( |
| "SF:SOURCE_FILENAME", |
| "FN:10,file1-func1", |
| "FN:20,file1-func2", |
| "FN:25,file1-func3", |
| "FNDA:5,file1-func1", |
| "FNDA:8,file1-func2", |
| "FNDA:2,file1-func3", |
| "FNF:3", |
| "FNH:3", |
| "DA:10,5", |
| "DA:11,5", |
| "DA:12,50", |
| "DA:13,50", |
| "DA:15,5", |
| "DA:16,2", |
| "DA:17,2", |
| "DA:19,3", |
| "DA:20,8", |
| "DA:21,8", |
| "DA:22,8", |
| "DA:23,8", |
| "DA:25,2", |
| "DA:26,2", |
| "LH:14", |
| "LF:14", |
| "end_of_record"); |
| |
| static final String SOURCE_FILENAME = "SOURCE_FILENAME"; |
| static final int NR_FUNCTIONS_FOUND = 3; |
| static final int NR_FUNCTIONS_HIT_TRACEFILE1 = 2; |
| static final int NR_FUNCTIONS_HIT_TRACEFILE2 = 3; |
| |
| static final String FUNC_1 = "file1-func1"; |
| static final int FUNC_1_LINE_NR = 10; |
| static final int FUNC_1_NR_EXECUTED_LINES_TRACEFILE1 = 3; |
| static final int FUNC_1_NR_EXECUTED_LINES_TRACEFILE2 = 2; |
| |
| static final String FUNC_2 = "file1-func2"; |
| static final int FUNC_2_LINE_NR = 20; |
| static final int FUNC_2_NR_EXECUTED_LINES_TRACEFILE1 = 5; |
| static final int FUNC_2_NR_EXECECUTED_LINES_TRACEFILE2 = 3; |
| |
| static final String FUNC_3 = "file1-func3"; |
| static final int FUNC_3_LINE_NR = 25; |
| static final int FUNC_3_NR_EXECUTED_LINES_TRACEFILE1 = 0; |
| static final int FUNC_3_NR_EXECUTED_LINES_TRACEFILE2 = 2; |
| |
| static final int NR_LINES_FOUND = 14; |
| static final int NR_LINES_HIT_TRACEFILE1 = 10; |
| static final int NR_LINES_HIT_TRACEFILE2 = 13; |
| |
| static final int MAX_LINES_IN_FILE = 27; |
| |
| static int[] createLinesExecution1() { |
| int[] lineExecutionCountForTracefile = new int[MAX_LINES_IN_FILE]; |
| for (int i = 0; i < MAX_LINES_IN_FILE; ++i) { |
| lineExecutionCountForTracefile[i] = -1; // no corresponding DA line for line i |
| } |
| |
| lineExecutionCountForTracefile[10] = 3; // DA:10,3 |
| lineExecutionCountForTracefile[11] = 3; // DA:11,3 |
| lineExecutionCountForTracefile[12] = 30; // DA:12,30 |
| lineExecutionCountForTracefile[13] = 30; // DA:13,30 |
| lineExecutionCountForTracefile[15] = 3; // DA:15,3 |
| lineExecutionCountForTracefile[16] = 0; // DA:16,0 |
| lineExecutionCountForTracefile[17] = 0; // DA:17,0 |
| lineExecutionCountForTracefile[19] = 3; // DA:19,3 |
| lineExecutionCountForTracefile[20] = 5; // DA:20,5 |
| lineExecutionCountForTracefile[21] = 5; // DA:21,5 |
| lineExecutionCountForTracefile[22] = 5; // DA:22,5 |
| lineExecutionCountForTracefile[23] = 5; // DA:23,5 |
| lineExecutionCountForTracefile[25] = 0; // DA:25,0 |
| lineExecutionCountForTracefile[26] = 0; // DA:26,0 |
| return lineExecutionCountForTracefile; |
| } |
| |
| static int[] createLinesExecution2() { |
| int[] lineExecutionCountForTracefile = new int[MAX_LINES_IN_FILE]; |
| for (int i = 0; i < MAX_LINES_IN_FILE; ++i) { |
| lineExecutionCountForTracefile[i] = -1; // no corresponding DA line for line i |
| } |
| |
| lineExecutionCountForTracefile[10] = 2; // DA:10,2 |
| lineExecutionCountForTracefile[11] = 2; // DA:11,2 |
| lineExecutionCountForTracefile[12] = 20; // DA:12,20 |
| lineExecutionCountForTracefile[13] = 20; // DA:13,20 |
| lineExecutionCountForTracefile[15] = 2; // DA:15,2 |
| lineExecutionCountForTracefile[16] = 2; // DA:16,2 |
| lineExecutionCountForTracefile[17] = 2; // DA:17,2 |
| lineExecutionCountForTracefile[19] = 0; // DA:19,0 |
| lineExecutionCountForTracefile[20] = 3; // DA:20,3 |
| lineExecutionCountForTracefile[21] = 3; // DA:21,3 |
| lineExecutionCountForTracefile[22] = 3; // DA:22,3 |
| lineExecutionCountForTracefile[23] = 3; // DA:23,3 |
| lineExecutionCountForTracefile[25] = 2; // DA:25,2 |
| lineExecutionCountForTracefile[26] = 2; // DA:26,2 |
| return lineExecutionCountForTracefile; |
| } |
| |
| static SourceFileCoverage createSourceFile1(int[] lineExecutionCountForTracefile) { |
| return createSourceFile1(SOURCE_FILENAME, lineExecutionCountForTracefile); |
| } |
| |
| // Create source file coverage data, excluding branch coverage |
| static SourceFileCoverage createSourceFile1(String sourceFilename, int[] lineExecutionCount) { |
| SourceFileCoverage sourceFile = new SourceFileCoverage(sourceFilename); |
| |
| sourceFile.addLineNumber(FUNC_1, FUNC_1_LINE_NR); |
| sourceFile.addFunctionExecution(FUNC_1, FUNC_1_NR_EXECUTED_LINES_TRACEFILE1); |
| |
| sourceFile.addLineNumber(FUNC_2, FUNC_2_LINE_NR); |
| sourceFile.addFunctionExecution(FUNC_2, FUNC_2_NR_EXECUTED_LINES_TRACEFILE1); |
| |
| sourceFile.addLineNumber(FUNC_3, FUNC_3_LINE_NR); |
| sourceFile.addFunctionExecution(FUNC_3, FUNC_3_NR_EXECUTED_LINES_TRACEFILE1); |
| |
| for (int line = FUNC_1_LINE_NR; line < MAX_LINES_IN_FILE; line++) { |
| if (lineExecutionCount[line] >= 0) { |
| sourceFile.addLine(line, LineCoverage.create(line, lineExecutionCount[line], null)); |
| } |
| } |
| |
| return sourceFile; |
| } |
| |
| // Create source file coverage data, excluding branch coverage |
| static SourceFileCoverage createSourceFile2(int[] lineExecutionCount) { |
| SourceFileCoverage sourceFileCoverage = new SourceFileCoverage(SOURCE_FILENAME); |
| |
| sourceFileCoverage.addLineNumber(FUNC_1, FUNC_1_LINE_NR); |
| sourceFileCoverage.addFunctionExecution(FUNC_1, FUNC_1_NR_EXECUTED_LINES_TRACEFILE2); |
| |
| sourceFileCoverage.addLineNumber(FUNC_2, FUNC_2_LINE_NR); |
| sourceFileCoverage.addFunctionExecution(FUNC_2, FUNC_2_NR_EXECECUTED_LINES_TRACEFILE2); |
| |
| sourceFileCoverage.addLineNumber(FUNC_3, FUNC_3_LINE_NR); |
| sourceFileCoverage.addFunctionExecution(FUNC_3, FUNC_3_NR_EXECUTED_LINES_TRACEFILE2); |
| |
| for (int line = FUNC_1_LINE_NR; line < MAX_LINES_IN_FILE; line++) { |
| if (lineExecutionCount[line] >= 0) { |
| sourceFileCoverage.addLine(line, LineCoverage.create(line, lineExecutionCount[line], null)); |
| } |
| } |
| return sourceFileCoverage; |
| } |
| |
| private static void assertLinesExecution_tracefile1(Map<Integer, LineCoverage> lines) { |
| int[] lineExecution = createLinesExecution1(); |
| |
| assertThat(lines.size()).isEqualTo(NR_LINES_FOUND); |
| |
| for (int line = 10; line < lineExecution.length; line++) { |
| if (lineExecution[line] >= 0) { |
| LineCoverage lineCoverage = lines.get(line); |
| assertThat(lineCoverage.executionCount()).isEqualTo(lineExecution[line]); |
| assertThat(lineCoverage.lineNumber()).isEqualTo(line); |
| assertThat(lineCoverage.checksum()).isNull(); |
| } |
| } |
| } |
| |
| private static void assertLines_tracefile2(Map<Integer, LineCoverage> lines) { |
| int[] lineExecution = createLinesExecution2(); |
| |
| assertThat(lines.size()).isEqualTo(NR_LINES_FOUND); |
| |
| for (int lineIndex = 10; lineIndex < lineExecution.length; lineIndex++) { |
| if (lineExecution[lineIndex] >= 0) { |
| LineCoverage line = lines.get(lineIndex); |
| assertThat(line.executionCount()).isEqualTo(lineExecution[lineIndex]); |
| assertThat(line.lineNumber()).isEqualTo(lineIndex); |
| assertThat(line.checksum()).isNull(); |
| } |
| } |
| } |
| |
| static void assertTracefile1(SourceFileCoverage sourceFile) { |
| Map<String, Integer> lineNumbers = sourceFile.getLineNumbers(); |
| assertThat(lineNumbers.size()).isEqualTo(3); |
| assertThat(lineNumbers.keySet()).containsAllOf(FUNC_1, FUNC_2, FUNC_3); |
| assertThat(lineNumbers.get(FUNC_1)).isEqualTo(FUNC_1_LINE_NR); |
| assertThat(lineNumbers.get(FUNC_2)).isEqualTo(FUNC_2_LINE_NR); |
| assertThat(lineNumbers.get(FUNC_3)).isEqualTo(FUNC_3_LINE_NR); |
| |
| Map<String, Integer> functionsExecution = sourceFile.getFunctionsExecution(); |
| assertThat(functionsExecution.size()).isEqualTo(3); |
| assertThat(functionsExecution.keySet()).containsAllOf(FUNC_1, FUNC_2, FUNC_3); |
| assertThat(functionsExecution.get(FUNC_1)).isEqualTo(FUNC_1_NR_EXECUTED_LINES_TRACEFILE1); |
| assertThat(functionsExecution.get(FUNC_2)).isEqualTo(FUNC_2_NR_EXECUTED_LINES_TRACEFILE1); |
| assertThat(functionsExecution.get(FUNC_3)).isEqualTo(FUNC_3_NR_EXECUTED_LINES_TRACEFILE1); |
| |
| assertLinesExecution_tracefile1(sourceFile.getLines()); |
| |
| assertThat(sourceFile.nrOfInstrumentedLines()).isEqualTo(14); |
| assertThat(sourceFile.nrOfLinesWithNonZeroExecution()).isEqualTo(10); |
| } |
| |
| static void assertTracefile2(SourceFileCoverage sourceFile) { |
| Map<String, Integer> lineNumbers = sourceFile.getLineNumbers(); |
| assertThat(lineNumbers.size()).isEqualTo(3); |
| assertThat(lineNumbers.keySet()).containsAllOf(FUNC_1, FUNC_2, FUNC_3); |
| assertThat(lineNumbers.get(FUNC_1)).isEqualTo(FUNC_1_LINE_NR); |
| assertThat(lineNumbers.get(FUNC_2)).isEqualTo(FUNC_2_LINE_NR); |
| assertThat(lineNumbers.get(FUNC_3)).isEqualTo(FUNC_3_LINE_NR); |
| |
| Map<String, Integer> functionsExecution = sourceFile.getFunctionsExecution(); |
| assertThat(functionsExecution.size()).isEqualTo(3); |
| assertThat(functionsExecution.keySet()).containsAllOf(FUNC_1, FUNC_2, FUNC_3); |
| assertThat(functionsExecution.get(FUNC_1)).isEqualTo(FUNC_1_NR_EXECUTED_LINES_TRACEFILE2); |
| assertThat(functionsExecution.get(FUNC_2)).isEqualTo(FUNC_2_NR_EXECECUTED_LINES_TRACEFILE2); |
| assertThat(functionsExecution.get(FUNC_3)).isEqualTo(FUNC_3_NR_EXECUTED_LINES_TRACEFILE2); |
| |
| assertLines_tracefile2(sourceFile.getLines()); |
| |
| assertThat(sourceFile.nrOfInstrumentedLines()).isEqualTo(14); |
| assertThat(sourceFile.nrOfLinesWithNonZeroExecution()).isEqualTo(13); |
| } |
| |
| static void assertMergedLineNumbers(TreeMap<String, Integer> lineNumbers) { |
| assertThat(lineNumbers.size()).isEqualTo(3); |
| assertThat(lineNumbers.keySet()).containsAllOf(FUNC_1, FUNC_2, FUNC_3); |
| assertThat(lineNumbers.get(FUNC_1)).isEqualTo(FUNC_1_LINE_NR); |
| assertThat(lineNumbers.get(FUNC_2)).isEqualTo(FUNC_2_LINE_NR); |
| assertThat(lineNumbers.get(FUNC_3)).isEqualTo(FUNC_3_LINE_NR); |
| } |
| |
| static void assertMergedFunctionsExecution(TreeMap<String, Integer> functionsExecution) { |
| assertThat(functionsExecution.size()).isEqualTo(3); |
| assertThat(functionsExecution.keySet()).containsAllOf(FUNC_1, FUNC_2, FUNC_3); |
| assertThat(functionsExecution.get(FUNC_1)) |
| .isEqualTo(FUNC_1_NR_EXECUTED_LINES_TRACEFILE1 + FUNC_1_NR_EXECUTED_LINES_TRACEFILE2); |
| assertThat(functionsExecution.get(FUNC_2)) |
| .isEqualTo(FUNC_2_NR_EXECUTED_LINES_TRACEFILE1 + FUNC_2_NR_EXECECUTED_LINES_TRACEFILE2); |
| assertThat(functionsExecution.get(FUNC_3)) |
| .isEqualTo(FUNC_3_NR_EXECUTED_LINES_TRACEFILE1 + FUNC_3_NR_EXECUTED_LINES_TRACEFILE2); |
| } |
| |
| static void assertMergedLines( |
| Map<Integer, LineCoverage> lines, int[] linesExecution1, int[] linesExecution2) { |
| assertThat(lines.size()).isEqualTo(14); |
| |
| for (int lineIndex = 10; lineIndex < MAX_LINES_IN_FILE; ++lineIndex) { |
| int totalExecutionCount = 0; |
| boolean wasInstrumented = false; |
| if (linesExecution1[lineIndex] >= 0) { |
| totalExecutionCount += linesExecution1[lineIndex]; |
| wasInstrumented = true; |
| } |
| if (linesExecution2[lineIndex] >= 0) { |
| totalExecutionCount += linesExecution2[lineIndex]; |
| wasInstrumented = true; |
| } |
| if (wasInstrumented) { |
| LineCoverage line = lines.get(lineIndex); |
| assertThat(line.executionCount()).isEqualTo(totalExecutionCount); |
| assertThat(line.lineNumber()).isEqualTo(lineIndex); |
| assertThat(line.checksum()).isNull(); |
| } |
| } |
| } |
| |
| static void assertMergedSourceFile( |
| SourceFileCoverage merged, int[] linesExecution1, int[] linesExecution2) { |
| assertMergedLineNumbers(merged.getLineNumbers()); |
| assertMergedFunctionsExecution(merged.getFunctionsExecution()); |
| assertMergedLines(merged.getLines(), linesExecution1, linesExecution2); |
| |
| assertThat(merged.nrFunctionsFound()).isEqualTo(NR_FUNCTIONS_FOUND); |
| assertThat(merged.nrFunctionsHit()).isEqualTo(NR_FUNCTIONS_FOUND); |
| assertThat(merged.nrOfLinesWithNonZeroExecution()).isEqualTo(14); |
| assertThat(merged.nrOfInstrumentedLines()).isEqualTo(14); |
| } |
| } |