4044: [code coverage] Merge script changes for separated test types.

- When merging profraw to profdata at shard_merge: Accept the test
target name arg from input and name the profdata file as
{target_name}.profdata.
- When merging profdata from different target to one profdata, add
support to filter the input with a regex pattern. This will be used to
merge only profdata of needed test type.

(cherry picked from commit 0c2093e58c56ebfd9dbf3a359f7df4da223a22a9)

Bug: 1064785
Change-Id: I4c8ff800c7b40adef2cfe8f905204d6d3ec87ae8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2121087
Commit-Queue: Zhaoyang Li <zhaoyangli@chromium.org>
Reviewed-by: Sajjad Mirza <sajjadm@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#754564}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2135160
Cr-Commit-Position: refs/branch-heads/4044@{#893}
Cr-Branched-From: a6d9daf149a473ceea37f629c41d4527bf2055bd-refs/heads/master@{#737173}
diff --git a/testing/merge_scripts/code_coverage/merge_lib.py b/testing/merge_scripts/code_coverage/merge_lib.py
index 03fa159..52b783f 100644
--- a/testing/merge_scripts/code_coverage/merge_lib.py
+++ b/testing/merge_scripts/code_coverage/merge_lib.py
@@ -6,6 +6,7 @@
 import logging
 import multiprocessing
 import os
+import re
 import shutil
 import subprocess
 
@@ -87,14 +88,16 @@
   return []
 
 
-def _get_profile_paths(input_dir, input_extension):
+def _get_profile_paths(input_dir,
+                       input_extension,
+                       input_filename_pattern='.*'):
   """Finds all the profiles in the given directory (recursively)."""
   paths = []
   for dir_path, _sub_dirs, file_names in os.walk(input_dir):
     paths.extend([
         os.path.join(dir_path, fn)
         for fn in file_names
-        if fn.endswith(input_extension)
+        if fn.endswith(input_extension) and re.search(input_filename_pattern,fn)
     ])
   return paths
 
@@ -224,7 +227,11 @@
   logging.info('Merge succeeded with output: %r', output)
 
 
-def merge_profiles(input_dir, output_file, input_extension, profdata_tool_path):
+def merge_profiles(input_dir,
+                   output_file,
+                   input_extension,
+                   profdata_tool_path,
+                   input_filename_pattern='.*'):
   """Merges the profiles produced by the shards using llvm-profdata.
 
   Args:
@@ -233,11 +240,15 @@
     input_extension (str): File extension to look for in the input_dir.
         e.g. '.profdata' or '.profraw'
     profdata_tool_path: The path to the llvm-profdata executable.
+    input_filename_pattern (str): The regex pattern of input filename. Should be
+        a valid regex pattern if present.
   Returns:
     The list of profiles that had to be excluded to get the merge to
     succeed and a list of profiles that had a counter overflow.
   """
-  profile_input_file_paths = _get_profile_paths(input_dir, input_extension)
+  profile_input_file_paths = _get_profile_paths(input_dir,
+                                                input_extension,
+                                                input_filename_pattern)
   invalid_profraw_files = []
   counter_overflows = []
   if input_extension == '.profraw':
diff --git a/testing/merge_scripts/code_coverage/merge_results.py b/testing/merge_scripts/code_coverage/merge_results.py
index 92f446ff..dd6b94c9 100755
--- a/testing/merge_scripts/code_coverage/merge_results.py
+++ b/testing/merge_scripts/code_coverage/merge_results.py
@@ -43,6 +43,7 @@
       '--profdata-dir', required=True, help='where to store the merged data')
   parser.add_argument(
       '--llvm-profdata', required=True, help='path to llvm-profdata executable')
+  parser.add_argument('--test-target-name', help='test target name')
   parser.add_argument(
       '--java-coverage-dir', help='directory for Java coverage data')
   parser.add_argument(
@@ -75,6 +76,10 @@
     coverage_merger.merge_java_exec_files(
         params.task_output_dir, output_path, params.jacococli_path)
 
+  # Name the output profdata file name as {test_target}.profdata or
+  # default.profdata.
+  output_prodata_filename = (params.test_target_name or 'default') + '.profdata'
+
   # NOTE: The coverage data merge script must make sure that the profraw files
   # are deleted from the task output directory after merging, otherwise, other
   # test results merge script such as layout tests will treat them as json test
@@ -82,7 +87,7 @@
   logging.info('Merging code coverage profraw data')
   invalid_profiles, counter_overflows = coverage_merger.merge_profiles(
       params.task_output_dir,
-      os.path.join(params.profdata_dir, 'default.profdata'), '.profraw',
+      os.path.join(params.profdata_dir, output_prodata_filename), '.profraw',
       params.llvm_profdata)
 
   # At the moment counter overflows overlap with invalid profiles, but this is
diff --git a/testing/merge_scripts/code_coverage/merge_results_test.py b/testing/merge_scripts/code_coverage/merge_results_test.py
index fe521ec0..91e5e30 100755
--- a/testing/merge_scripts/code_coverage/merge_results_test.py
+++ b/testing/merge_scripts/code_coverage/merge_results_test.py
@@ -39,12 +39,13 @@
     })
     task_output_dir = 'some/task/output/dir'
     profdata_dir = '/some/different/path/to/profdata/default.profdata'
-    profdata_file = os.path.join(profdata_dir, 'default.profdata')
+    profdata_file = os.path.join(profdata_dir, 'base_unittests.profdata')
     args = [
         'script_name', '--output-json', 'output.json', '--build-properties',
         build_properties, '--summary-json', 'summary.json', '--task-output-dir',
         task_output_dir, '--profdata-dir', profdata_dir, '--llvm-profdata',
-        'llvm-profdata', 'a.json', 'b.json', 'c.json'
+        'llvm-profdata', 'a.json', 'b.json', 'c.json', '--test-target-name',
+        'base_unittests'
     ]
     with mock.patch.object(merger, 'merge_profiles') as mock_merge:
       mock_merge.return_value = None, None
@@ -53,7 +54,7 @@
         self.assertEqual(
             mock_merge.call_args,
             mock.call(task_output_dir, profdata_file, '.profraw',
-                      'llvm-profdata'))
+                      'llvm-profdata'), None)
 
   def test_merge_steps_parameters(self):
     """Test the build-level merge front-end."""
@@ -67,6 +68,8 @@
         output_file,
         '--llvm-profdata',
         'llvm-profdata',
+        '--profdata-filename-pattern',
+        '.*'
     ]
     with mock.patch.object(merger, 'merge_profiles') as mock_merge:
       mock_merge.return_value = None
@@ -74,7 +77,8 @@
         merge_steps.main()
         self.assertEqual(
             mock_merge.call_args,
-            mock.call(input_dir, output_file, '.profdata', 'llvm-profdata'))
+            mock.call(input_dir, output_file, '.profdata', 'llvm-profdata',
+                '.*'))
 
   @mock.patch.object(merger, '_validate_and_convert_profraws')
   def test_merge_profraw(self, mock_validate_and_convert_profraws):
@@ -163,6 +167,44 @@
     # The mock method should only apply when merging .profraw files.
     self.assertFalse(mock_validate_and_convert_profraws.called)
 
+  @mock.patch.object(merger, '_validate_and_convert_profraws')
+  def test_merge_profdata_pattern(self, mock_validate_and_convert_profraws):
+    mock_input_dir_walk = [
+        ('/b/some/path', ['base_unittests', 'url_unittests'], ['summary.json']),
+        ('/b/some/path/base_unittests', [], ['output.json',
+                                             'base_unittests.profdata']),
+        ('/b/some/path/url_unittests', [], ['output.json',
+                                            'url_unittests.profdata'],),
+        ('/b/some/path/ios_chrome_smoke_eg2tests',
+            [], ['output.json','ios_chrome_smoke_eg2tests.profdata'],),
+    ]
+    with mock.patch.object(os, 'walk') as mock_walk:
+      with mock.patch.object(os, 'remove'):
+        mock_walk.return_value = mock_input_dir_walk
+        with mock.patch.object(subprocess, 'check_output') as mock_exec_cmd:
+          input_profdata_filename_pattern = '.+_unittests\.profdata'
+          merger.merge_profiles('/b/some/path',
+                                'output/dir/default.profdata',
+                                '.profdata',
+                                'llvm-profdata',
+                                input_profdata_filename_pattern)
+          self.assertEqual(
+              mock.call(
+                  [
+                      'llvm-profdata',
+                      'merge',
+                      '-o',
+                      'output/dir/default.profdata',
+                      '-sparse=true',
+                      '/b/some/path/base_unittests/base_unittests.profdata',
+                      '/b/some/path/url_unittests/url_unittests.profdata',
+                  ],
+                  stderr=-2,
+              ), mock_exec_cmd.call_args)
+
+    # The mock method should only apply when merging .profraw files.
+    self.assertFalse(mock_validate_and_convert_profraws.called)
+
   def test_retry_profdata_merge_failures(self):
     mock_input_dir_walk = [
         ('/b/some/path', ['0', '1'], ['summary.json']),
@@ -228,6 +270,7 @@
     self.assertEqual(set(['44b643576cf39f10', '44b1234567890123']),
                      merger.get_shards_to_retry(bad_profiles))
 
+  @mock.patch('merge_lib._JAVA_PATH', 'java')
   def test_merge_java_exec_files(self):
     mock_input_dir_walk = [
         ('/b/some/path', ['0', '1', '2', '3'], ['summary.json']),
diff --git a/testing/merge_scripts/code_coverage/merge_steps.py b/testing/merge_scripts/code_coverage/merge_steps.py
index c0d5d4e..544edd2 100755
--- a/testing/merge_scripts/code_coverage/merge_steps.py
+++ b/testing/merge_scripts/code_coverage/merge_steps.py
@@ -18,6 +18,11 @@
       '--output-file', required=True, help='where to store the merged data')
   parser.add_argument(
       '--llvm-profdata', required=True, help='path to llvm-profdata executable')
+  parser.add_argument(
+      '--profdata-filename-pattern',
+      default='.*',
+      help='regex pattern of profdata filename to merge for current test type. '
+          'If not present, all profdata files will be merged.')
   return parser
 
 
@@ -26,7 +31,7 @@
   parser = _merge_steps_argument_parser(description=desc)
   params = parser.parse_args()
   merger.merge_profiles(params.input_dir, params.output_file, '.profdata',
-                        params.llvm_profdata)
+                        params.llvm_profdata, params.profdata_filename_pattern)
 
 
 if __name__ == '__main__':