Skip to content

Commit 60a9948

Browse files
authored
Add default end to end test config file for testing; prepare for config validation refactoring (#550)
The initial version for enqueuing and working image building jobs in the new arch.
1 parent 8d4cb49 commit 60a9948

10 files changed

+120
-30
lines changed

.github/workflows/presubmit.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,7 @@ jobs:
3737
- name: Run presubmit checks
3838
run: |
3939
FUZZBENCH_TEST_INTEGRATION=1 make presubmit
40-
# TODO(zhichengcai): Add back end to end test.
40+
41+
- name: Run end to end CI test
42+
run: |
43+
make run-end-to-end-test

common/config_utils.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2020 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
"""Provides helper functions to obtain configurations."""
16+
17+
18+
def validate_and_expand(config):
19+
"""Validates |config| and returns the expanded configuration."""
20+
# TODO: move the logic from experiment/run_experiment.py to here.
21+
return config

compose/e2e-test.yaml

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ services:
88
- queue-server
99
environment:
1010
E2E_INTEGRATION_TEST: 1
11-
command: python3 -m pytest -vv fuzzbench/test_e2e_run.py
11+
command: python3 -m pytest -vv fuzzbench/test_e2e/test_e2e_run.py
12+
13+
run-experiment:
14+
environment:
15+
EXPERIMENT_CONFIG: fuzzbench/test_e2e/end-to-end-test-config.yaml

docker/fuzzbench/Dockerfile

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ COPY benchmarks benchmarks
3131
COPY common common
3232
COPY database database
3333
COPY docker docker
34+
COPY experiment/build experiment/build
35+
COPY experiment/*.py experiment/
3436
COPY fuzzbench fuzzbench
3537
COPY fuzzers fuzzers
3638

docker/image_types.yaml

+9-9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@
1515
tag: 'dispatcher-image'
1616
type: 'dispatcher'
1717

18+
# TODO: It would be better to call this benchmark builder. But that would be
19+
# confusing because this doesn't involve benchmark-builder/Dockerfile. Rename
20+
# that and then rename this.
21+
'{benchmark}-project-builder':
22+
dockerfile: 'benchmarks/{benchmark}/Dockerfile'
23+
context: 'benchmarks/{benchmark}'
24+
tag: 'builders/benchmark/{benchmark}'
25+
type: 'builder'
26+
1827
'coverage-{benchmark}-builder-intermediate':
1928
build_arg:
2029
- 'parent_image=gcr.io/fuzzbench/builders/benchmark/{benchmark}'
@@ -37,15 +46,6 @@
3746
tag: 'builders/coverage/{benchmark}'
3847
type: 'coverage'
3948

40-
# TODO: It would be better to call this benchmark builder. But that would be
41-
# confusing because this doesn't involve benchmark-builder/Dockerfile. Rename
42-
# that and then rename this.
43-
'{benchmark}-project-builder':
44-
dockerfile: 'benchmarks/{benchmark}/Dockerfile'
45-
context: 'benchmarks/{benchmark}'
46-
tag: 'builders/benchmark/{benchmark}'
47-
type: 'builder'
48-
4949
'{fuzzer}-{benchmark}-builder-intermediate':
5050
build_arg:
5151
- 'parent_image=gcr.io/fuzzbench/builders/benchmark/{benchmark}'

fuzzbench/jobs.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@
1919
BASE_TAG = 'gcr.io/fuzzbench'
2020

2121

22-
def build_image(name: str):
22+
def build_image(image):
2323
"""Builds a Docker image and returns whether it succeeds."""
24-
image_tag = os.path.join(BASE_TAG, name)
24+
image_tag = os.path.join(BASE_TAG, image['tag'])
2525
subprocess.run(['docker', 'pull', image_tag], check=True)
26-
subprocess.run(
27-
['docker', 'build', '--tag', image_tag,
28-
os.path.join('docker', name)],
29-
check=True)
26+
command = ['docker', 'build', '--tag', image_tag, image['context']]
27+
cpu_options = ['--cpu-period', '100000', '--cpu-quota', '100000']
28+
command.extend(cpu_options)
29+
if 'dockerfile' in image:
30+
command.extend(['--file', image['dockerfile']])
31+
if 'build_arg' in image:
32+
for arg in image['build_arg']:
33+
command.extend(['--build-arg', arg])
34+
subprocess.run(command, check=True)
3035
return True
3136

3237

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
benchmarks:
2+
- freetype2-2017
3+
- bloaty_fuzz_target
4+
fuzzers:
5+
- afl
6+
- libfuzzer

fuzzbench/run_experiment.py

+27-7
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,34 @@
1818
import redis
1919
import rq
2020

21+
from common import config_utils, environment, yaml_utils
22+
from experiment.build import docker_images
2123
from fuzzbench import jobs
2224

2325

24-
def run_experiment():
26+
def run_experiment(config):
2527
"""Main experiment logic."""
2628
print('Initializing the job queue.')
2729
# Create the queue for scheduling build jobs and run jobs.
2830
queue = rq.Queue('build_n_run_queue')
31+
32+
images_to_build = docker_images.get_images_to_build(config['fuzzers'],
33+
config['benchmarks'])
2934
jobs_list = []
30-
jobs_list.append(
31-
queue.enqueue(jobs.build_image,
32-
'base-image',
33-
job_timeout=600,
34-
job_id='base-image'))
35+
# TODO(#643): topological sort before enqueuing jobs.
36+
for name, image in images_to_build.items():
37+
depends = image.get('depends_on', None)
38+
if depends is not None:
39+
assert len(depends) == 1, 'image %s has %d dependencies. Multiple '\
40+
'dependencies are currently not supported.' % (name, len(depends))
41+
jobs_list.append(
42+
queue.enqueue(
43+
jobs.build_image,
44+
image=image,
45+
job_timeout=30 * 60,
46+
result_ttl=-1,
47+
job_id=name,
48+
depends_on=depends[0] if 'depends_on' in image else None))
3549

3650
while True:
3751
print('Current status of jobs:')
@@ -52,8 +66,14 @@ def run_experiment():
5266
def main():
5367
"""Set up Redis connection and start the experiment."""
5468
redis_connection = redis.Redis(host="queue-server")
69+
70+
config_path = environment.get('EXPERIMENT_CONFIG',
71+
'fuzzbench/local-experiment-config.yaml')
72+
config = yaml_utils.read(config_path)
73+
config = config_utils.validate_and_expand(config)
74+
5575
with rq.Connection(redis_connection):
56-
return run_experiment()
76+
return run_experiment(config)
5777

5878

5979
if __name__ == '__main__':
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
benchmarks:
2+
- bloaty_fuzz_target
3+
fuzzers:
4+
- libfuzzer

fuzzbench/test_e2e_run.py fuzzbench/test_e2e/test_e2e_run.py

+31-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,17 @@
1919

2020
import pytest
2121
import redis
22-
from rq.job import Job
22+
import rq
23+
24+
from common import config_utils, yaml_utils
25+
from experiment.build import docker_images
26+
27+
28+
@pytest.fixture(scope='class')
29+
def experiment_config():
30+
"""Returns the default configuration for end-to-end testing."""
31+
return config_utils.validate_and_expand(
32+
yaml_utils.read('fuzzbench/test_e2e/end-to-end-test-config.yaml'))
2333

2434

2535
@pytest.fixture(scope='class')
@@ -31,17 +41,32 @@ def redis_connection():
3141
# pylint: disable=no-self-use
3242
@pytest.mark.skipif('E2E_INTEGRATION_TEST' not in os.environ,
3343
reason='Not running end-to-end test.')
34-
@pytest.mark.usefixtures('redis_connection')
44+
@pytest.mark.usefixtures('redis_connection', 'experiment_config')
3545
class TestEndToEndRunResults:
3646
"""Checks the result of a test experiment run."""
3747

38-
def test_jobs_dependency(self): # pylint: disable=redefined-outer-name
48+
def test_jobs_dependency(self, experiment_config, redis_connection): # pylint: disable=redefined-outer-name
3949
"""Tests that jobs dependency preserves during working."""
40-
assert True
50+
all_images = docker_images.get_images_to_build(
51+
experiment_config['fuzzers'], experiment_config['benchmarks'])
52+
jobs = {
53+
name: rq.job.Job.fetch(name, connection=redis_connection)
54+
for name in all_images
55+
}
56+
for name, image in all_images.items():
57+
if 'depends_on' in image:
58+
for dep in image['depends_on']:
59+
assert jobs[dep].ended_at <= jobs[name].started_at
4160

42-
def test_all_jobs_finished_successfully(self, redis_connection): # pylint: disable=redefined-outer-name
61+
def test_all_jobs_finished_successfully(
62+
self,
63+
experiment_config, # pylint: disable=redefined-outer-name
64+
redis_connection): # pylint: disable=redefined-outer-name
4365
"""Tests all jobs finished successully."""
44-
jobs = Job.fetch_many(['base-image'], connection=redis_connection)
66+
all_images = docker_images.get_images_to_build(
67+
experiment_config['fuzzers'], experiment_config['benchmarks'])
68+
jobs = rq.job.Job.fetch_many(all_images.keys(),
69+
connection=redis_connection)
4570
for job in jobs:
4671
assert job.get_status() == 'finished'
4772

0 commit comments

Comments
 (0)