Skip to content

Commit

Permalink
Create precise bisection tasks on completion of regression/progressio…
Browse files Browse the repository at this point in the history
…n. (#1779)
  • Loading branch information
oliverchang authored May 20, 2020
1 parent 1f971ce commit 3115846
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 1 deletion.
3 changes: 3 additions & 0 deletions configs/test/project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,6 @@ env:
# REVISION_VARS_URL:

# Any of the above variables can be overridden in a job definition.

bisect_service:
#pubsub_topic: /projects/project/topics/topic
5 changes: 4 additions & 1 deletion src/python/bot/tasks/progression_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ def _save_fixed_range(testcase_id, min_revision, max_revision):
testcase, max_revision, message='fixed in range r%s' % testcase.fixed)
_write_to_bigquery(testcase, min_revision, max_revision)

# If there is a fine grained bisection service available, request it.
task_creation.request_bisection(testcase, 'fixed')


def find_fixed_range(testcase_id, job_type):
"""Attempt to find the revision range where a testcase was fixed."""
Expand Down Expand Up @@ -349,7 +352,7 @@ def find_fixed_range(testcase_id, job_type):

# Occasionally, we get into this bad state. It seems to be related to test
# cases with flaky stacks, but the exact cause is unknown.
elif max_index - min_index < 1:
if max_index - min_index < 1:
testcase = data_handler.get_testcase_by_id(testcase_id)
testcase.fixed = 'NA'
testcase.open = False
Expand Down
3 changes: 3 additions & 0 deletions src/python/bot/tasks/regression_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ def save_regression_range(testcase_id, regression_range_start,
# Get blame information using the regression range result.
task_creation.create_blame_task_if_needed(testcase)

# If there is a fine grained bisection service available, request it.
task_creation.request_bisection(testcase, 'regressed')


def _testcase_reproduces_in_revision(testcase,
testcase_file_path,
Expand Down
64 changes: 64 additions & 0 deletions src/python/bot/tasks/task_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
from base import tasks
from base import utils
from build_management import build_manager
from build_management import revisions
from config import local_config
from datastore import data_handler
from datastore import data_types
from datastore import ndb_utils
from google_cloud_utils import blobs
from google_cloud_utils import pubsub
from system import environment


Expand Down Expand Up @@ -227,3 +231,63 @@ def create_tasks(testcase):
# Just create the minimize task for now. Once minimization is complete, it
# automatically created the rest of the needed tasks.
create_minimize_task_if_needed(testcase)


def _get_commits(commit_range, job_type):
"""Get commits from range."""
start, end = revisions.get_start_and_end_revision(commit_range)
components = revisions.get_component_range_list(start, end, job_type)

commits = components[0]['link_text']

if ':' not in commits:
return commits, commits

old_commit, new_commit = commits.split(':')
if old_commit == '0':
old_commit = ''

return old_commit, new_commit


def request_bisection(testcase, bisect_type):
"""Request precise bisection."""
pubsub_topic = local_config.ProjectConfig().get('bisect_service.pubsub_topic')
if not pubsub_topic:
return

target = testcase.get_fuzz_target()
if not target:
return

if bisect_type == 'fixed':
old_commit, new_commit = _get_commits(testcase.fixed, testcase.job_type)
elif bisect_type == 'regressed':
old_commit, new_commit = _get_commits(testcase.regression,
testcase.job_type)
else:
raise ValueError('Invalid bisection type: ' + bisect_type)

reproducer = blobs.read_key(testcase.minimized_keys or testcase.fuzzed_keys)
pubsub_client = pubsub.PubSubClient()
pubsub_client.publish(
pubsub_topic,
pubsub.Message(
reproducer, {
'type':
bisect_type,
'project_name':
target.project,
'sanitizer':
environment.SANITIZER_NAME_MAP[
environment.get_memory_tool_name(testcase.job_type)
],
'fuzz_target':
target.binary,
'old_commit':
old_commit,
'new_commit':
new_commit,
'testcase_id':
testcase.key.id(),
}))
103 changes: 103 additions & 0 deletions src/python/tests/core/bot/tasks/task_creation_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for task_creation."""
import unittest

from bot.tasks import task_creation
from datastore import data_types
from tests.test_libs import helpers
from tests.test_libs import mock_config
from tests.test_libs import test_utils


@test_utils.with_cloud_emulators('datastore')
class RequestBisectionTest(unittest.TestCase):
"""Tests request_bisection."""

def setUp(self):
helpers.patch(self, [
'build_management.revisions.get_component_range_list',
'config.local_config.ProjectConfig',
'google_cloud_utils.blobs.read_key',
'google_cloud_utils.pubsub.PubSubClient.publish',
])

data_types.FuzzTarget(
id='libFuzzer_proj_target',
engine='libFuzzer',
project='proj',
binary='target').put()

self.testcase = data_types.Testcase(
job_type='libfuzzer_asan_proj',
fuzzer_name='libFuzzer',
overridden_fuzzer_name='libFuzzer_proj_target',
regression='123:456',
fixed='123:456')
self.testcase.put()

self.mock.read_key.return_value = b'reproducer'
self.mock.get_component_range_list.return_value = [
{
'link_text': 'old:new',
},
]

self.mock.ProjectConfig.return_value = mock_config.MockConfig({
'bisect_service': {
'pubsub_topic': '/projects/project/topics/topic',
}
})

def _test(self, sanitizer, bisect_type):
task_creation.request_bisection(self.testcase, bisect_type)
publish_call = self.mock.publish.call_args[0]
topic = publish_call[1]
message = publish_call[2]
self.assertEqual('/projects/project/topics/topic', topic)
self.assertEqual(b'reproducer', message.data)
self.assertDictEqual({
'fuzz_target': 'target',
'new_commit': 'new',
'old_commit': 'old',
'project_name': 'proj',
'sanitizer': sanitizer,
'testcase_id': 1,
'type': bisect_type,
}, message.attributes)

def test_request_bisection_regressed(self):
"""Basic regressed test."""
self.testcase.job_type = 'libfuzzer_asan_proj'
self._test('address', 'regressed')
self.testcase.job_type = 'libfuzzer_msan_proj'
self._test('memory', 'regressed')
self.testcase.job_type = 'libfuzzer_ubsan_proj'
self._test('undefined', 'regressed')

def test_request_bisection_fixed(self):
"""Basic fixed test."""
self.testcase.job_type = 'libfuzzer_asan_proj'
self._test('address', 'fixed')
self.testcase.job_type = 'libfuzzer_msan_proj'
self._test('memory', 'fixed')
self.testcase.job_type = 'libfuzzer_ubsan_proj'
self._test('undefined', 'fixed')

def test_request_bisection_blackbox(self):
"""Test request bisection for blackbox."""
self.testcase.job_type = 'blackbox'
self.testcase.overridden_fuzzer_name = None
task_creation.request_bisection(self.testcase, 'regressed')
self.assertEqual(0, self.mock.publish.call_count)

0 comments on commit 3115846

Please sign in to comment.