Fix bugs in code coverage script.

- Use absolute path to make filters work.
- Fix description to account for fact that we just want developer
  to add GN configuration and we build the target for them.
- Fix missing target in pdfium command line.
- llvm directory can be cleared when target_os change, but
  cr_coverage_revision stays intact. So, check for coverage
  tool binaries, otherwise we will crash on file not found.

[email protected],[email protected]

Bug: 
Change-Id: I0ce23c09863323af2d03561a232cb9e614493506
Reviewed-on: https://chromium-review.googlesource.com/814495
Commit-Queue: Abhishek Arya <[email protected]>
Reviewed-by: Yuke Liao <[email protected]>
Reviewed-by: Max Moroz <[email protected]>
Cr-Commit-Position: refs/heads/master@{#522464}
diff --git a/tools/code_coverage/coverage.py b/tools/code_coverage/coverage.py
index 23124cb..211f011 100755
--- a/tools/code_coverage/coverage.py
+++ b/tools/code_coverage/coverage.py
@@ -7,35 +7,38 @@
   It uses Clang Source-based Code Coverage -
   https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
 
-  In order to generate code coverage report, you need to first build the target
-  program with "use_clang_coverage=true" GN flag.
+  In order to generate code coverage report, you need to first add
+  "use_clang_coverage=true" GN flag to args.gn file in your build
+  output directory (e.g. out/coverage).
 
-  It is recommended to set "is_component_build=false" flag explicitly in GN
-  configuration because:
+  It is recommended to add "is_component_build=false" flag as well because:
   1. It is incompatible with other sanitizer flags (like "is_asan", "is_msan")
      and others like "optimize_for_fuzzing".
   2. If it is not set explicitly, "is_debug" overrides it to true.
 
   Example usage:
 
+  gn gen out/coverage --args='use_clang_coverage=true is_component_build=false'
+  gclient runhooks
   python tools/code_coverage/coverage.py crypto_unittests url_unittests \\
-    -b out/coverage -o out/report -c 'out/coverage/crypto_unittests' \\
-    -c 'out/coverage/url_unittests --gtest_filter=URLParser.PathURL' \\
-    -f url/ -f crypto/
+      -b out/coverage -o out/report -c 'out/coverage/crypto_unittests' \\
+      -c 'out/coverage/url_unittests --gtest_filter=URLParser.PathURL' \\
+      -f url/ -f crypto/
 
-  The command above generates code coverage report for crypto_unittests and
-  url_unittests with only files under url/ and crypto/ directories are included
-  in the report, and all generated artifacts are stored in out/report.
-  For url_unittests, it only runs the test URLParser.PathURL.
+  The command above builds crypto_unittests and url_unittests targets and then
+  runs them with specified command line arguments. For url_unittests, it only
+  runs the test URLParser.PathURL. The coverage report is filtered to include
+  only files and sub-directories under url/ and crypto/ directories.
 
   If you are building a fuzz target, you need to add "use_libfuzzer=true" GN
   flag as well.
 
   Sample workflow for a fuzz target (e.g. pdfium_fuzzer):
 
-  python tools/code_coverage/coverage.py \\
-    -b out/coverage -o out/report \\
-    -c 'out/coverage/pdfium_fuzzer -runs=<runs> <corpus_dir>'
+  python tools/code_coverage/coverage.py pdfium_fuzzer \\
+      -b out/coverage -o out/report \\
+      -c 'out/coverage/pdfium_fuzzer -runs=<runs> <corpus_dir>' \\
+      -f third_party/pdfium
 
   where:
     <corpus_dir> - directory containing samples files for this format.
@@ -151,7 +154,11 @@
   coverage_revision, coverage_sub_revision = _GetRevisionFromStampFile(
       coverage_revision_stamp_file, platform)
 
-  if (coverage_revision == clang_revision and
+  has_coverage_tools = (os.path.exists(LLVM_COV_PATH) and
+                        os.path.exists(LLVM_PROFDATA_PATH))
+
+  if (has_coverage_tools and
+      coverage_revision == clang_revision and
       coverage_sub_revision == clang_sub_revision):
     # LLVM coverage tools are up to date, bail out.
     return clang_revision
@@ -192,7 +199,8 @@
     profdata_file_path: A path to the profdata file.
     filters: A list of directories and files to get coverage for.
   """
-  print('Generating per file line by line code coverage in html')
+  print('Generating per file line-by-line code coverage in html '
+        '(this can take a while depending on size of target!)')
 
   # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
   # [[-object BIN]] [SOURCES]
@@ -353,6 +361,7 @@
   print('Creating the profile data file')
 
   profdata_file_path = os.path.join(OUTPUT_DIR, PROFDATA_FILE_NAME)
+
   try:
     subprocess_cmd = [
         LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
@@ -448,15 +457,21 @@
   return build_args
 
 
-def _AssertPathsExist(paths):
-  """Asserts that the paths specified in |paths| exist.
+def _VerifyPathsAndReturnAbsolutes(paths):
+  """Verifies that the paths specified in |paths| exist and returns absolute
+  versions.
 
   Args:
     paths: A list of files or directories.
   """
+  absolute_paths = []
   for path in paths:
-    abspath = os.path.join(SRC_ROOT_PATH, path)
-    assert os.path.exists(abspath), ('Path: "%s" doesn\'t exist.' % path)
+    absolute_path = os.path.join(SRC_ROOT_PATH, path)
+    assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
+
+    absolute_paths.append(absolute_path)
+
+  return absolute_paths
 
 
 def _ParseCommandArguments():
@@ -496,7 +511,7 @@
       '-f',
       '--filters',
       action='append',
-      required=True,
+      required=False,
       help='Directories or files to get code coverage for, and all files under '
       'the directories are included recursively.')
 
@@ -539,8 +554,10 @@
       'Please run "gn gen" to generate.').format(BUILD_DIR)
   _ValidateBuildingWithClangCoverage()
   _ValidateCommandsAreRelativeToSrcRoot(args.command)
+
+  absolute_filter_paths = []
   if args.filters:
-    _AssertPathsExist(args.filters)
+    absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
 
   if not os.path.exists(OUTPUT_DIR):
     os.makedirs(OUTPUT_DIR)
@@ -550,11 +567,11 @@
 
   binary_paths = [_GetBinaryPath(command) for command in args.command]
   _GenerateLineByLineFileCoverageInHtml(binary_paths, profdata_file_path,
-                                        args.filters)
+                                        absolute_filter_paths)
   html_index_file_path = 'file://' + os.path.abspath(
       os.path.join(OUTPUT_DIR, 'index.html'))
   print('\nCode coverage profile data is created as: %s' % profdata_file_path)
-  print('index file for html report is generated as: %s' % html_index_file_path)
+  print('Index file for html report is generated as: %s' % html_index_file_path)
 
 
 if __name__ == '__main__':