Index: llvm/docs/CommandGuide/llvm-cov.rst =================================================================== --- llvm/docs/CommandGuide/llvm-cov.rst +++ llvm/docs/CommandGuide/llvm-cov.rst @@ -336,11 +336,14 @@ Show code coverage only for functions with region coverage less than the given threshold. -.. option:: -path-equivalence=, +.. option:: -path-equivalence=,[,,[,...]] 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 + comma separated pairs can be passed and 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=, Index: llvm/test/tools/llvm-cov/Inputs/multiple-path_equivalence.proftext =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/Inputs/multiple-path_equivalence.proftext @@ -0,0 +1,19 @@ +f1 +0x0 +1 +1 + +f2 +0x0 +1 +1 + +f3 +0x0 +1 +1 + +f4 +0x0 +1 +1 Index: llvm/test/tools/llvm-cov/multiple-path_equivalence.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/multiple-path_equivalence.test @@ -0,0 +1,29 @@ +# multiple-path_equivalence.covmapping contains references to 4 files: +# /tmp/coverage/main.c +# /tmp/coverage/f1.c +# /tmp/coverage/a/f2.c +# /tmp/coverage/b/f3.c +# /tmp/coverage/b/c/f4.c + +# Setup +// RUN: touch %/T/main.c; touch %/T/f1.c; touch %/T/f2.c; touch %/T/f3.c; touch %/T/f4.c +// RUN: llvm-profdata merge %S/Inputs/multiple-path_equivalence.proftext -o %t.profdata + +# Make sure that remapping follows the specified order with the first matching entry being used first (f4 comes before f3) +// RUN: llvm-cov show %S/Inputs/multiple-path_equivalence.covmapping -instr-profile=%t.profdata -path-equivalence=/tmp/coverage/a,%/T,/tmp/coverage/b/c,%/T,/tmp/coverage/b,%/T,/tmp/coverage,%/T 2>&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,/tmp/coverage/b,%/T,/tmp/coverage/b/c,%/T,/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. Index: llvm/test/tools/llvm-cov/path_equivalence.c =================================================================== --- llvm/test/tools/llvm-cov/path_equivalence.c +++ llvm/test/tools/llvm-cov/path_equivalence.c @@ -4,4 +4,4 @@ int main() {} // CHECK: [[@LINE]]| 1|int main() {} // 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' +// INVALID: error: -path-equivalence: invalid argument 'foo', must be a variable length sequence of comma separated path equivalence pairs ',[,,[,...]]' Index: llvm/tools/llvm-cov/CodeCoverage.cpp =================================================================== --- llvm/tools/llvm-cov/CodeCoverage.cpp +++ 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(); + } } } @@ -507,7 +514,6 @@ for (const auto &RemappedFilename : RemappedFilenames) InvRemappedFilenames[RemappedFilename.getValue()] = std::string(RemappedFilename.getKey()); - for (std::string &Filename : SourceFiles) { SmallString<128> NativeFilename; sys::path::native(Filename, NativeFilename); @@ -676,8 +682,8 @@ cl::opt PathRemap( "path-equivalence", cl::Optional, - cl::desc(", Map coverage data paths to local source file " - "paths")); + cl::desc(",[,,[,...]] Map coverage data paths to " + "local source file paths.")); cl::OptionCategory FilteringCategory("Function filtering options"); @@ -812,19 +818,34 @@ break; } - // If path-equivalence was given and is a comma seperated pair then set - // PathRemapping. + SmallVector EquivPairs; + + // If path-equivalence was given and is a sequence of comma + // separated pairs then set PathRemapping. if (!PathRemap.empty()) { - auto EquivPair = StringRef(PathRemap).split(','); - if (EquivPair.first.empty() || EquivPair.second.empty()) { + StringRef(PathRemap).split(EquivPairs, ','); + + if (EquivPairs.size() > 0 && EquivPairs.size() % 2 != 0) { error("invalid argument '" + PathRemap + - "', must be in format 'from,to'", + "', must be a variable length sequence of comma separated " + "path equivalence pairs ',[,,[,...]]'", "-path-equivalence"); return 1; } - PathRemapping = {std::string(EquivPair.first), - std::string(EquivPair.second)}; + std::vector> Remappings; + for (size_t I = 1; I < EquivPairs.size(); I += 2) { + if (std::string(EquivPairs[I - 1]).empty() || + std::string(EquivPairs[I]).empty()) { + return 1; + } + + Remappings.push_back( + {std::string(EquivPairs[I - 1]), std::string(EquivPairs[I])}); + } + if (!Remappings.empty()) { + PathRemappings = Remappings; + } } // If a demangler is supplied, check if it exists and register it.