Add Windows support to coverage.py.

With these fixes, coverage.py successfully generated coverage reports on
Windows for various browser_tests and interactive_ui_tests. I also tried
for unit_tests, but couldn't generate a coverage-enabled build--the
linker fails with OOM errors. The build machine has 64GB RAM, which
seems like it should be plenty. So it seems there is more work to be
done to fully enable coverage builds on Windows, perhaps already tracked
by http://crbug.com/846918? Building browser_tests also OOM'ed, but
finally succeeded after I set is_component_build to true.

gn gen D:\cr\src\out\coverage --root=D:\cr\src "--args=is_debug = false
is_component_build = true enable_nacl = false use_clang_coverage = true
 dcheck_always_on = true" --ide=vs

Example usage:
python tools/code_coverage/coverage.py browser_tests -b
D:/cr/src/out/coverage -o D:/cr/src/out/report  -c
"D:/cr/src/out/coverage/browser_tests.exe --gtest_filter=
SyncFileSystemTest.AuthorizationTest" -f
chrome/browser/apps/platform_apps/api/sync_file_system/

Bug: 809150
Change-Id: Ie2419aad80fbc1cca5c5f15257536c96ee25d77f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2189255
Reviewed-by: Bruce Dawson <brucedawson@chromium.org>
Reviewed-by: Yuke Liao <liaoyuke@chromium.org>
Commit-Queue: Brent McBride <brenmc@microsoft.com>
Cr-Original-Commit-Position: refs/heads/master@{#767396}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: b25b177a4bf22399ff06324747c34293fc37b7df
diff --git a/coverage.py b/coverage.py
index 8b3b7a4..a4b8f88 100755
--- a/coverage.py
+++ b/coverage.py
@@ -157,7 +157,11 @@
     LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
     LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
   else:
-    update.UpdatePackage('coverage_tools')
+    update.UpdatePackage('coverage_tools', coverage_utils.GetHostPlatform())
+
+  if coverage_utils.GetHostPlatform() == 'win':
+    LLVM_COV_PATH += '.exe'
+    LLVM_PROFDATA_PATH += '.exe'
 
   coverage_tools_exist = (
       os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
@@ -290,8 +294,11 @@
                 default value is derived based on CPUs availability.
   """
   logging.info('Building %s.', str(targets))
+  autoninja = 'autoninja'
+  if coverage_utils.GetHostPlatform() == 'win':
+    autoninja += '.bat'
 
-  subprocess_cmd = ['autoninja', '-C', BUILD_DIR]
+  subprocess_cmd = [autoninja, '-C', BUILD_DIR]
   if jobs_count is not None:
     subprocess_cmd.append('-j' + str(jobs_count))
 
@@ -690,8 +697,8 @@
     current_platform = coverage_utils.GetHostPlatform()
 
   assert current_platform in [
-      'linux', 'mac', 'chromeos', 'ios'
-  ], ('Coverage is only supported on linux, mac, chromeos and ios.')
+      'linux', 'mac', 'chromeos', 'ios', 'win'
+  ], ('Coverage is only supported on linux, mac, chromeos, ios and win.')
 
 
 def _GetBuildArgs():
@@ -944,7 +951,7 @@
   # Setup coverage binaries even when script is called with empty params. This
   # is used by coverage bot for initial setup.
   if len(sys.argv) == 1:
-    update.UpdatePackage('coverage_tools')
+    update.UpdatePackage('coverage_tools', coverage_utils.GetHostPlatform())
     print(__doc__)
     return
 
@@ -1019,8 +1026,9 @@
         'otool')
     if os.path.exists(hermetic_otool_path):
       otool_path = hermetic_otool_path
-  binary_paths.extend(
-      coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
+  if sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
+    binary_paths.extend(
+        coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
 
   assert args.format == 'html' or args.format == 'text', (
       '%s is not a valid output format for "llvm-cov show". Only "text" and '
diff --git a/coverage_utils.py b/coverage_utils.py
index 8fc69ad..e7f20ac 100644
--- a/coverage_utils.py
+++ b/coverage_utils.py
@@ -415,10 +415,7 @@
     html_report_path = os.path.join(
         GetFullPath(dir_path), DIRECTORY_COVERAGE_HTML_REPORT_NAME)
 
-    # '+' is used instead of os.path.join because both of them are absolute
-    # paths and os.path.join ignores the first path.
-    # TODO(crbug.com/809150): Think of a generic cross platform fix (Windows).
-    return self.report_root_dir + html_report_path
+    return self.CombineAbsolutePaths(self.report_root_dir, html_report_path)
 
   def GetCoverageHtmlReportPathForFile(self, file_path):
     """Given a file path, returns the corresponding html report path."""
@@ -426,10 +423,17 @@
         self._MapToLocal(file_path)), '"%s" is not a file.' % file_path
     html_report_path = os.extsep.join([GetFullPath(file_path), 'html'])
 
+    return self.CombineAbsolutePaths(self.report_root_dir, html_report_path)
+
+  def CombineAbsolutePaths(self, path1, path2):
+    if GetHostPlatform() == 'win':
+      # Absolute paths in Windows may start with a drive letter and colon.
+      # Remove them from the second path before appending to the first.
+      _, path2 = os.path.splitdrive(path2)
+
     # '+' is used instead of os.path.join because both of them are absolute
     # paths and os.path.join ignores the first path.
-    # TODO(crbug.com/809150): Think of a generic cross platform fix (Windows).
-    return self.report_root_dir + html_report_path
+    return path1 + path2
 
   def GenerateFileViewHtmlIndexFile(self, per_file_coverage_summary,
                                     file_view_index_file_path):