diff --git a/llvm/docs/CommandGuide/llvm-cov.rst b/llvm/docs/CommandGuide/llvm-cov.rst --- a/llvm/docs/CommandGuide/llvm-cov.rst +++ b/llvm/docs/CommandGuide/llvm-cov.rst @@ -340,7 +340,11 @@ Map the paths in the coverage data to local source file paths. This allows you to generate the coverage data on one machine, and then use llvm-cov on a - different machine where you have the same files on a different path. + different machine where you have the same files on a different path. Multiple + `-path-equivalence` arguments can be passed to specify different mappings. Each + argument consists of a source path `` and its corresponding local path ``. + The mappings are applied in the order they are specified. If multiple mappings can + be applied to a single path, the first mapping encountered is used. .. option:: -coverage-watermark=, diff --git a/llvm/test/tools/llvm-cov/Inputs/multiple-path_equivalence.covmapping b/llvm/test/tools/llvm-cov/Inputs/multiple-path_equivalence.covmapping new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@&1 | FileCheck %s + +// CHECK-DAG: {{/|\\}}tmp{{/|\\}}coverage{{/|\\}}main.c: +// CHECK-DAG: {{/|\\}}tmp{{/|\\}}coverage{{/|\\}}f1.c: +// CHECK-DAG: {{/|\\}}tmp{{/|\\}}coverage{{/|\\}}a{{/|\\}}f2.c: +// CHECK-DAG: {{/|\\}}tmp{{/|\\}}coverage{{/|\\}}b{{/|\\}}f3.c: +// CHECK-DAG: {{/|\\}}tmp{{/|\\}}coverage{{/|\\}}b{{/|\\}}c{{/|\\}}f4.c: +// CHECK-NOT: isn't covered. + +# Make sure remapping follows the specified order by proving paths in an overriding order (f4 comes after f3) +// RUN: llvm-cov show %S/Inputs/multiple-path_equivalence.covmapping -instr-profile=%t.profdata -path-equivalence=/tmp/coverage/a,%/T -path-equivalence=/tmp/coverage/b,%/T -path-equivalence=/tmp/coverage/b/c,%/T -path-equivalence=/tmp/coverage,%/T 2>&1 | FileCheck %s -check-prefix=OVERRIDING_ORDER + +// OVERRIDING_ORDER-DAG: {{/|\\}}tmp{{/|\\}}coverage{{/|\\}}main.c: +// OVERRIDING_ORDER-DAG: {{/|\\}}tmp{{/|\\}}coverage{{/|\\}}f1.c: +// OVERRIDING_ORDER-DAG: {{/|\\}}tmp{{/|\\}}coverage{{/|\\}}a{{/|\\}}f2.c: +// OVERRIDING_ORDER-DAG: {{/|\\}}tmp{{/|\\}}coverage{{/|\\}}b{{/|\\}}f3.c: +// OVERRIDING_ORDER-DAG: warning: The file '{{/|\\}}tmp{{/|\\}}coverage{{/|\\}}b{{/|\\}}c{{/|\\}}f4.c' isn't covered. + +// RUN: not llvm-cov show %S/Inputs/multiple-path_equivalence.covmapping -instr-profile=%t.profdata -path-equivalence=/tmp/coverage/a,%/T -path-equivalence=/tmp/coverage/b, -path-equivalence=/tmp/coverage/b/c,%/T -path-equivalence=/tmp/coverage,%/T 2>&1 | FileCheck %s -check-prefix=EMPTY_PATH +// EMPTY_PATH: must be in format 'from,to' \ No newline at end of file diff --git a/llvm/test/tools/llvm-cov/path_equivalence.c b/llvm/test/tools/llvm-cov/path_equivalence.c --- a/llvm/test/tools/llvm-cov/path_equivalence.c +++ b/llvm/test/tools/llvm-cov/path_equivalence.c @@ -5,3 +5,6 @@ // RUN: not llvm-cov show --instr-profile=/dev/null -path-equivalence=foo /dev/null 2>&1 | FileCheck --check-prefix=INVALID %s // INVALID: error: -path-equivalence: invalid argument 'foo', must be in format 'from,to' + +// RUN: not llvm-cov show --instr-profile=/dev/null -path-equivalence=,foo /dev/null 2>&1 | FileCheck --check-prefix=EMPTY_PATH %s +// EMPTY_PATH: error: -path-equivalence: invalid argument ',foo', must be in format 'from,to' diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp --- a/llvm/tools/llvm-cov/CodeCoverage.cpp +++ b/llvm/tools/llvm-cov/CodeCoverage.cpp @@ -162,7 +162,8 @@ /// The coverage data path to be remapped from, and the source path to be /// remapped to, when using -path-equivalence. - std::optional> PathRemapping; + std::optional>> + PathRemappings; /// File status cache used when finding the same file. StringMap> FileStatusCache; @@ -228,7 +229,7 @@ llvm::sys::fs::file_status Status; llvm::sys::fs::status(Path, Status); if (!llvm::sys::fs::exists(Status)) { - if (PathRemapping) + if (PathRemappings) addCollectedPath(Path); else warning("Source file doesn't exist, proceeded by ignoring it.", Path); @@ -474,7 +475,7 @@ } void CodeCoverageTool::remapPathNames(const CoverageMapping &Coverage) { - if (!PathRemapping) + if (!PathRemappings) return; // Convert remapping paths to native paths with trailing seperators. @@ -488,17 +489,23 @@ NativePath += sys::path::get_separator(); return NativePath.c_str(); }; - std::string RemapFrom = nativeWithTrailing(PathRemapping->first); - std::string RemapTo = nativeWithTrailing(PathRemapping->second); - // Create a mapping from coverage data file paths to local paths. - for (StringRef Filename : Coverage.getUniqueSourceFiles()) { - SmallString<128> NativeFilename; - sys::path::native(Filename, NativeFilename); - sys::path::remove_dots(NativeFilename, true); - if (NativeFilename.startswith(RemapFrom)) { - RemappedFilenames[Filename] = - RemapTo + NativeFilename.substr(RemapFrom.size()).str(); + for (std::pair &PathRemapping : *PathRemappings) { + std::string RemapFrom = nativeWithTrailing(PathRemapping.first); + std::string RemapTo = nativeWithTrailing(PathRemapping.second); + + // Create a mapping from coverage data file paths to local paths. + for (StringRef Filename : Coverage.getUniqueSourceFiles()) { + if (RemappedFilenames.count(Filename) == 1) + continue; + + SmallString<128> NativeFilename; + sys::path::native(Filename, NativeFilename); + sys::path::remove_dots(NativeFilename, true); + if (NativeFilename.startswith(RemapFrom)) { + RemappedFilenames[Filename] = + RemapTo + NativeFilename.substr(RemapFrom.size()).str(); + } } } @@ -674,7 +681,7 @@ "lcov tracefile output")), cl::init(CoverageViewOptions::OutputFormat::Text)); - cl::opt PathRemap( + cl::list PathRemaps( "path-equivalence", cl::Optional, cl::desc(", Map coverage data paths to local source file " "paths")); @@ -812,19 +819,23 @@ break; } - // If path-equivalence was given and is a comma seperated pair then set - // PathRemapping. - if (!PathRemap.empty()) { - auto EquivPair = StringRef(PathRemap).split(','); - if (EquivPair.first.empty() || EquivPair.second.empty()) { - error("invalid argument '" + PathRemap + - "', must be in format 'from,to'", - "-path-equivalence"); - return 1; + if (!PathRemaps.empty()) { + std::vector> Remappings; + + for (const std::string &PathRemap : PathRemaps) { + auto EquivPair = StringRef(PathRemap).split(','); + if (EquivPair.first.empty() || EquivPair.second.empty()) { + error("invalid argument '" + PathRemap + + "', must be in format 'from,to'", + "-path-equivalence"); + return 1; + } + + Remappings.push_back( + {std::string(EquivPair.first), std::string(EquivPair.second)}); } - PathRemapping = {std::string(EquivPair.first), - std::string(EquivPair.second)}; + PathRemappings = Remappings; } // If a demangler is supplied, check if it exists and register it.