Skip to content

Commit 509728a

Browse files
Add more validation (#1029)
* Add more validation Add validation to presubmit.py and run_experiment.py to ensure: 1. That experiments are only run with one type of benchmark. 2. That experiments are only run with valid benchmarks. 3. Valid benchmarks must have a valid type. 4. To pass presubmit, benchmarks must have a valid type. * fmt everywhere * fix test
1 parent 51cdf44 commit 509728a

File tree

8 files changed

+82
-20
lines changed

8 files changed

+82
-20
lines changed

analysis/benchmark_results.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from analysis import data_utils
2020
from analysis import coverage_data_utils
2121
from analysis import stat_tests
22-
from common import benchmark_config
22+
from common import benchmark_utils
2323

2424

2525
# pylint: disable=too-many-public-methods, too-many-arguments
@@ -71,15 +71,7 @@ def type(self):
7171
7272
Raises ValueError in case of invalid benchmark type in the config.
7373
"""
74-
try:
75-
benchmark_type = benchmark_config.get_config(self.name).get('type')
76-
except Exception: # pylint: disable=broad-except
77-
return 'code'
78-
if not benchmark_type:
79-
return 'code'
80-
if benchmark_type not in ['code', 'bug']:
81-
raise ValueError('Invalid benchmark type: ' + benchmark_type)
82-
return benchmark_type
74+
return benchmark_utils.get_type(self.name)
8375

8476
@property
8577
def _relevant_column(self):

analysis/test_experiment_results.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
# See the License for the specific language governing permissions andsss
1313
# limitations under the License.
1414
"""Tests for experiment_results.py"""
15+
from unittest import mock
1516

1617
from analysis import experiment_results
1718
from analysis import test_data_utils
1819

1920

20-
def test_linkify_fuzzer_names_in_ranking():
21+
@mock.patch('common.benchmark_config.get_config', return_value={})
22+
def test_linkify_fuzzer_names_in_ranking(_):
2123
"""Tests turning fuzzer names into links."""
2224
experiment_df = test_data_utils.create_experiment_data()
2325
results = experiment_results.ExperimentResults(experiment_df,

common/benchmark_utils.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
"""Code for dealing with benchmarks."""
15+
import enum
1516
import os
1617
import re
1718

@@ -27,6 +28,17 @@
2728
BENCHMARKS_DIR = os.path.join(utils.ROOT_DIR, 'benchmarks')
2829

2930

31+
class BenchmarkType(str, enum.Enum):
32+
"""Benchmark type."""
33+
CODE = 'code'
34+
BUG = 'bug'
35+
36+
37+
# pytype: disable=missing-parameter
38+
BENCHMARK_TYPE_STRS = {benchmark_type.value for benchmark_type in BenchmarkType}
39+
# pytype: enable=missing-parameter
40+
41+
3042
def get_fuzz_target(benchmark):
3143
"""Returns the fuzz target of |benchmark|"""
3244
return benchmark_config.get_config(benchmark)['fuzz_target']
@@ -37,6 +49,12 @@ def get_project(benchmark):
3749
return benchmark_config.get_config(benchmark)['project']
3850

3951

52+
def get_type(benchmark):
53+
"""Returns the type of |benchmark|"""
54+
return benchmark_config.get_config(benchmark).get('type',
55+
BenchmarkType.CODE.value)
56+
57+
4058
def get_runner_image_url(experiment, benchmark, fuzzer, docker_registry):
4159
"""Get the URL of the docker runner image for fuzzing the benchmark with
4260
fuzzer."""
@@ -64,6 +82,16 @@ def validate_name(benchmark):
6482
return True
6583

6684

85+
def validate_type(benchmark):
86+
"""Returns True if |benchmark| has a valid type."""
87+
benchmark_type = get_type(benchmark)
88+
if benchmark_type not in BENCHMARK_TYPE_STRS:
89+
logs.error('%s has an invalid benchmark type %s, must be one of %s',
90+
benchmark, benchmark_type, BENCHMARK_TYPE_STRS)
91+
return False
92+
return True
93+
94+
6795
def validate(benchmark):
6896
"""Returns True if |benchmark| is a valid fuzzbench benchmark."""
6997
if not validate_name(benchmark):
@@ -73,6 +101,7 @@ def validate(benchmark):
73101
logs.error('%s must have a benchmark.yaml.', benchmark)
74102
return False
75103

104+
# Validate config file can be parsed.
76105
try:
77106
get_fuzz_target(benchmark)
78107
except yaml.parser.ParserError:
@@ -84,7 +113,8 @@ def validate(benchmark):
84113
benchmark)
85114
return False
86115

87-
return True
116+
# Validate type.
117+
return validate_type(benchmark)
88118

89119

90120
def get_all_benchmarks():

common/test_benchmark_utils.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
"""Tests for benchmark_utils.py"""
15+
from unittest import mock
16+
1517
import pytest
1618

1719
from common import benchmark_utils
@@ -56,3 +58,26 @@ def test_validate_name_invalid(benchmark_name):
5658
def test_validate_name_valid(benchmark_name):
5759
"""Tests that validate_name returns True for a valid benchmark name."""
5860
assert benchmark_utils.validate_name(benchmark_name)
61+
62+
63+
@mock.patch('common.benchmark_utils.get_type', return_value='other-type')
64+
def test_validate_type_invalid(_):
65+
"""Tests that validate_type returns False for an invalid type."""
66+
assert not benchmark_utils.validate_type('benchmark')
67+
68+
69+
@pytest.mark.parametrize(('benchmark_type',), [
70+
('code',),
71+
('bug',),
72+
])
73+
def test_validate_type_valid(benchmark_type):
74+
"""Tests that validate_type returns True for an valid type."""
75+
with mock.patch('common.benchmark_utils.get_type',
76+
return_value=benchmark_type):
77+
assert benchmark_utils.validate_type('benchmark')
78+
79+
80+
@mock.patch('common.benchmark_config.get_config', return_value={})
81+
def test_get_default_type(_):
82+
"""Tests that get_type returns the correct default value."""
83+
assert benchmark_utils.get_type('benchmark') == 'code'

experiment/run_experiment.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,23 @@ def get_directories(parent_dir):
151151

152152
def validate_benchmarks(benchmarks: List[str]):
153153
"""Parses and validates list of benchmarks."""
154+
benchmark_types = set()
154155
for benchmark in set(benchmarks):
155156
if benchmarks.count(benchmark) > 1:
156157
raise Exception('Benchmark "%s" is included more than once.' %
157158
benchmark)
159+
# Validate benchmarks here. It's possible someone might run an
160+
# experiment without going through presubmit. Better to catch an invalid
161+
# benchmark than see it in production.
162+
if not benchmark_utils.validate(benchmark):
163+
raise Exception('Benchmark "%s" is invalid.' % benchmark)
164+
165+
benchmark_types.add(benchmark_utils.get_type(benchmark))
166+
167+
if (benchmark_utils.BenchmarkType.CODE.value in benchmark_types and
168+
benchmark_utils.BenchmarkType.BUG.value in benchmark_types):
169+
raise Exception(
170+
'Cannot mix bug benchmarks with code coverage benchmarks.')
158171

159172

160173
def validate_fuzzer(fuzzer: str):

fuzzbench/test_e2e/test_e2e_run.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ def test_jobs_dependency(self, experiment_config, redis_connection): # pylint:
5959
assert jobs[dep].ended_at <= jobs[name].started_at
6060

6161
def test_all_jobs_finished_successfully(
62-
self,
63-
experiment_config, # pylint: disable=redefined-outer-name
64-
redis_connection): # pylint: disable=redefined-outer-name
62+
self,
63+
experiment_config, # pylint: disable=redefined-outer-name
64+
redis_connection): # pylint: disable=redefined-outer-name
6565
"""Tests all jobs finished successully."""
6666
all_images = docker_images.get_images_to_build(
6767
experiment_config['fuzzers'], experiment_config['benchmarks'])

service/automatic_run_experiment.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323
import sys
2424
from typing import Optional
2525

26-
from common import logs
26+
from common import benchmark_utils
2727
from common import fuzzer_utils
28+
from common import logs
2829
from common import utils
2930
from common import yaml_utils
3031
from database import models
@@ -247,7 +248,7 @@ def run_requested_experiment(dry_run):
247248
fuzzers = requested_experiment['fuzzers']
248249

249250
benchmark_type = requested_experiment.get('type')
250-
if benchmark_type == 'bug':
251+
if benchmark_type == benchmark_utils.BenchmarkType.BUG.value:
251252
benchmarks = BUG_BENCHMARKS
252253
else:
253254
benchmarks = CODE_BENCHMARKS

test_libs/utils.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ def mock_popen_ctx_mgr(*args, **kwargs):
2525
yield mocked_popen
2626

2727

28-
def create_mock_popen(output=bytes('', 'utf-8'),
29-
err=bytes('', 'utf-8'),
30-
returncode=0):
28+
def create_mock_popen(
29+
output=bytes('', 'utf-8'), err=bytes('', 'utf-8'), returncode=0):
3130
"""Creates a mock subprocess.Popen."""
3231

3332
class MockPopen:

0 commit comments

Comments
 (0)