Repository: google/clusterfuzz

Issue:
Don't use REVISION_VARS_URL on custom binary jobs
For internal, i had to do "REVISION_VARS_URL = " on all experimental jobs. we need to fix this.


Discussion:

Resolving patch:
diff --git a/src/python/build_management/revisions.py b/src/python/build_management/revisions.py
--- a/src/python/build_management/revisions.py
+++ b/src/python/build_management/revisions.py
@@ -318,6 +318,23 @@ def get_components_list(component_revisions_dict, job_type):
   return components
 
 
+def _get_revision_vars_url_format(job_type):
+  """Return REVISION_VARS_URL from job environment if available. Otherwise,
+  default to one set in project.yaml. For custom binary jobs, this is not
+  applicable."""
+  if job_type is None:
+    # Force it to use env attribute in project.yaml.
+    return local_config.ProjectConfig().get('env.REVISION_VARS_URL')
+
+  custom_binary = data_handler.get_value_from_job_definition(
+      job_type, 'CUSTOM_BINARY')
+  if utils.string_is_true(custom_binary):
+    return None
+
+  return data_handler.get_value_from_job_definition_or_environment(
+      job_type, 'REVISION_VARS_URL')
+
+
 @memoize.wrap(memoize.FifoOnDisk(DISK_CACHE_SIZE))
 @memoize.wrap(memoize.Memcache(60 * 60 * 24 * 30))  # 30 day TTL
 def get_component_revisions_dict(revision, job_type):
@@ -326,15 +343,8 @@ def get_component_revisions_dict(revision, job_type):
     # Return empty dict for zero start revision.
     return {}
 
-  if job_type is None:
-    # Force it to use env attribute in project.yaml.
-    revision_info_url_format = local_config.ProjectConfig().get(
-        'env.REVISION_VARS_URL')
-  else:
-    revision_info_url_format = (
-        data_handler.get_value_from_job_definition_or_environment(
-            job_type, 'REVISION_VARS_URL'))
-  if not revision_info_url_format:
+  revision_vars_url_format = _get_revision_vars_url_format(job_type)
+  if not revision_vars_url_format:
     return None
 
   project_name = data_handler.get_project_name(job_type)
@@ -343,7 +353,7 @@ def get_component_revisions_dict(revision, job_type):
   if utils.is_chromium():
     component = data_handler.get_component_name(job_type)
     repository = data_handler.get_repository_for_component(component)
-    if repository and not _is_clank(revision_info_url_format):
+    if repository and not _is_clank(revision_vars_url_format):
       revision_hash = _git_commit_position_to_git_hash_for_chromium(
           revision, repository)
       if revision_hash is None:
@@ -363,15 +373,15 @@ def get_component_revisions_dict(revision, job_type):
       # Use revision hash for info url later.
       revision = revision_hash
 
-  revision_info_url = revision_info_url_format % revision
-  url_content = _get_url_content(revision_info_url)
+  revision_vars_url = revision_vars_url_format % revision
+  url_content = _get_url_content(revision_vars_url)
   if not url_content:
     logs.log_error(
-        'Failed to get component revisions from %s.' % revision_info_url)
+        'Failed to get component revisions from %s.' % revision_vars_url)
     return None
 
   # Parse as per DEPS format.
-  if _is_deps(revision_info_url):
+  if _is_deps(revision_vars_url):
     deps_revisions_dict = deps_to_revisions_dict(url_content)
     if not deps_revisions_dict:
       return None
@@ -380,18 +390,18 @@ def get_component_revisions_dict(revision, job_type):
     return revisions_dict
 
   # Parse as per Clank DEPS format.
-  if _is_clank(revision_info_url):
+  if _is_clank(revision_vars_url):
     return _clank_revision_file_to_revisions_dict(url_content)
 
   # Default case: parse content as yaml.
   revisions_dict = _to_dict(url_content)
   if not revisions_dict:
     logs.log_error(
-        'Failed to parse component revisions from %s.' % revision_info_url)
+        'Failed to parse component revisions from %s.' % revision_vars_url)
     return None
 
   # Parse as per source map format.
-  if revision_info_url.endswith(SOURCE_MAP_EXTENSION):
+  if revision_vars_url.endswith(SOURCE_MAP_EXTENSION):
     revisions_dict = _src_map_to_revisions_dict(revisions_dict, project_name)
 
   return revisions_dict


Test patch:
diff --git a/src/python/tests/appengine/handlers/testcase_detail/show_test.py b/src/python/tests/appengine/handlers/testcase_detail/show_test.py
--- a/src/python/tests/appengine/handlers/testcase_detail/show_test.py
+++ b/src/python/tests/appengine/handlers/testcase_detail/show_test.py
@@ -14,6 +14,7 @@
 """show tests."""
 # pylint: disable=protected-access
 from past.builtins import basestring
+import collections
 import datetime
 import os
 import unittest
@@ -485,6 +486,7 @@ def test_reproducible_get(self):
     testcase.timestamp = datetime.datetime(1970, 1, 1)
     testcase.project_name = 'chromium'
     testcase.one_time_crasher_flag = False
+    testcase.fuzzer_name = 'fuzzer1'
     testcase.put()
 
     job = data_types.Job()
@@ -501,34 +503,64 @@ def test_reproducible_get(self):
 
     result = show.get_testcase_detail_by_id(2)
     expected_subset = {
-        'id': 2,
-        'crash_type': 'crash_type1 crash_type2',
-        'crash_address': 'crash_address',
-        'crash_state': 'crash_state',
+        'id':
+            2,
+        'crash_type':
+            'crash_type1 crash_type2',
+        'crash_address':
+            'crash_address',
+        'crash_state':
+            'crash_state',
         'crash_state_lines': ['crash_state'],
-        'crash_revision': 123,
-        'csrf_token': 'csrf',
-        'external_user': True,
-        'footer': '',
-        'fixed': 'NO',
-        'issue_url': 'issue_url',
+        'crash_revision':
+            123,
+        'csrf_token':
+            'csrf',
+        'external_user':
+            True,
+        'footer':
+            '',
+        'fixed':
+            'NO',
+        'issue_url':
+            'issue_url',
         'metadata': {},
-        'minimized_testcase_size': None,
-        'needs_refresh': True,
-        'original_testcase_size': None,
-        'privileged_user': False,
-        'regression': 'Pending',
-        'security_severity': None,
-        'show_impact': True,
-        'show_blame': True,
-        'auto_delete_timestamp': None,
-        'auto_close_timestamp': None,
-        'memory_tool_display_label': 'Sanitizer',
-        'memory_tool_display_value': 'address (ASAN)',
-        'last_tested': 'name: 0:revision<br />',
-        'is_admin_or_not_oss_fuzz': True,
-        'has_issue_tracker': True,
-        'reproduction_help_url': 'help_url',
+        'minimized_testcase_size':
+            None,
+        'needs_refresh':
+            True,
+        'original_testcase_size':
+            None,
+        'privileged_user':
+            False,
+        'regression':
+            'Pending',
+        'security_severity':
+            None,
+        'show_impact':
+            True,
+        'show_blame':
+            True,
+        'auto_delete_timestamp':
+            None,
+        'auto_close_timestamp':
+            None,
+        'memory_tool_display_label':
+            'Sanitizer',
+        'memory_tool_display_value':
+            'address (ASAN)',
+        'last_tested':
+            'name: 0:revision<br />',
+        'is_admin_or_not_oss_fuzz':
+            True,
+        'has_issue_tracker':
+            True,
+        'reproduction_help_url':
+            'help_url',
+        'fuzzer_display':
+            collections.OrderedDict([('engine', None), ('target', None),
+                                     ('name', 'fuzzer1'),
+                                     ('fully_qualified_name', 'fuzzer1')]),
     }
 
     self.maxDiff = None  # pylint: disable=invalid-name
@@ -559,7 +591,10 @@ def test_unreproducible_get(self):
     testcase.timestamp = datetime.datetime(1970, 1, 1)
     testcase.project_name = 'chromium'
     testcase.one_time_crasher_flag = True
+    testcase.fuzzer_name = 'libFuzzer'
+    testcase.overridden_fuzzer_name = 'libFuzzer_test_fuzzer'
     testcase.put()
+    testcase.set_metadata('fuzzer_binary_name', 'test_fuzzer')
 
     job = data_types.Job()
     job.name = 'windows_asan_chrome'
@@ -575,34 +610,67 @@ def test_unreproducible_get(self):
 
     result = show.get_testcase_detail_by_id(2)
     expected_subset = {
-        'id': 2,
-        'crash_type': 'crash_type1 crash_type2',
-        'crash_address': 'crash_address',
-        'crash_state': 'crash_state',
+        'id':
+            2,
+        'crash_type':
+            'crash_type1 crash_type2',
+        'crash_address':
+            'crash_address',
+        'crash_state':
+            'crash_state',
         'crash_state_lines': ['crash_state'],
-        'crash_revision': 123,
-        'csrf_token': 'csrf',
-        'external_user': True,
-        'footer': '',
-        'fixed': 'NO',
-        'issue_url': 'issue_url',
-        'metadata': {},
-        'minimized_testcase_size': None,
-        'needs_refresh': True,
-        'original_testcase_size': None,
-        'privileged_user': False,
-        'regression': 'Pending',
-        'security_severity': None,
-        'show_impact': False,
-        'show_blame': True,
-        'auto_delete_timestamp': 947289600.0,
-        'auto_close_timestamp': None,
-        'memory_tool_display_label': 'Sanitizer',
-        'memory_tool_display_value': 'address (ASAN)',
-        'last_tested': 'name: 0:revision<br />',
-        'is_admin_or_not_oss_fuzz': True,
-        'has_issue_tracker': True,
-        'reproduction_help_url': 'help_url',
+        'crash_revision':
+            123,
+        'csrf_token':
+            'csrf',
+        'external_user':
+            True,
+        'footer':
+            '',
+        'fixed':
+            'NO',
+        'issue_url':
+            'issue_url',
+        'metadata': {
+            'fuzzer_binary_name': 'test_fuzzer'
+        },
+        'minimized_testcase_size':
+            None,
+        'needs_refresh':
+            True,
+        'original_testcase_size':
+            None,
+        'privileged_user':
+            False,
+        'regression':
+            'Pending',
+        'security_severity':
+            None,
+        'show_impact':
+            False,
+        'show_blame':
+            True,
+        'auto_delete_timestamp':
+            947289600.0,
+        'auto_close_timestamp':
+            None,
+        'memory_tool_display_label':
+            'Sanitizer',
+        'memory_tool_display_value':
+            'address (ASAN)',
+        'last_tested':
+            'name: 0:revision<br />',
+        'is_admin_or_not_oss_fuzz':
+            True,
+        'has_issue_tracker':
+            True,
+        'reproduction_help_url':
+            'help_url',
+        'fuzzer_display':
+            collections.OrderedDict(
+                [('engine', 'libFuzzer'), ('target', 'test_fuzzer'),
+                 ('name', 'libFuzzer'), ('fully_qualified_name',
+                                         'libFuzzer_test_fuzzer')]),
     }
 
     self.maxDiff = None  # pylint: disable=invalid-name
diff --git a/src/python/tests/core/build_management/revisions_test.py b/src/python/tests/core/build_management/revisions_test.py
--- a/src/python/tests/core/build_management/revisions_test.py
+++ b/src/python/tests/core/build_management/revisions_test.py
@@ -29,6 +29,7 @@
 ANDROID_JOB_TYPE = 'android_asan_chrome'
 BASIC_JOB_TYPE = 'linux_asan_chrome_mp'
 SRCMAP_JOB_TYPE = 'linux_asan_libass'
+CUSTOM_BINARY_JOB_TYPE = 'linux_asan_custom'
 DATA_DIRECTORY = os.path.join(os.path.dirname(__file__), 'revisions_data')
 
 # For simplicity, the mocked URLs all end with the revision. They do not match
@@ -83,6 +84,9 @@ def setUp(self):
             'HELP_URL = help_url\n'
             'REVISION_VARS_URL = https://commondatastorage.googleapis.com/'
             'blah-%s.srcmap.json')).put()
+    data_types.Job(
+        name=CUSTOM_BINARY_JOB_TYPE,
+        environment_string=('CUSTOM_BINARY = True\n')).put()
 
   # General helper functions.
   @staticmethod
@@ -325,6 +329,19 @@ def test_get_component_range_list_0_start_custom(self, mock_get_url_content,
     expected_html = self._read_data_file('srcmap_expected_html_3.txt')
     self.assertEqual(result_as_html, expected_html)
 
+  @mock.patch('config.db_config.get')
+  @mock.patch('build_management.revisions._get_url_content')
+  def test_get_component_range_list_custom_binary_job(
+      self, mock_get_url_content, mock_get_config):
+    """Test get_component_range_list with a custom binary job."""
+    mock_get_config.return_value = self.MockConfigChromium()
+    self.mock.default_project_name.return_value = 'chromium'
+    mock_get_url_content.side_effect = self.mock_get_url_content
+
+    self.assertEqual([],
+                     revisions.get_component_range_list(1337, 1338,
+                                                        CUSTOM_BINARY_JOB_TYPE))
+
   def test_find_min_revision_index(self):
     """Tests find_min_revision_index()."""
     revisions_list = [1000, 2000, 3000, 4000]