summaryrefslogtreecommitdiff
path: root/buildscripts/evergreen_gen_fuzzer_tests.py
blob: eb7501493484c5e52f34cf46aa30d21f590a37bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#!/usr/bin/env python3
"""Generate fuzzer tests to run in evergreen in parallel."""
import argparse
from collections import namedtuple
from typing import Set

from shrub.v2 import ShrubProject, FunctionCall, Task, TaskDependency, BuildVariant, ExistingTask

from buildscripts.util.fileops import write_file_to_dir
import buildscripts.util.read_config as read_config
import buildscripts.util.taskname as taskname

CONFIG_DIRECTORY = "generated_resmoke_config"

ConfigOptions = namedtuple("ConfigOptions", [
    "num_files",
    "num_tasks",
    "resmoke_args",
    "npm_command",
    "jstestfuzz_vars",
    "name",
    "variant",
    "continue_on_failure",
    "resmoke_jobs_max",
    "should_shuffle",
    "timeout_secs",
    "use_multiversion",
    "suite",
])


def _get_config_options(cmd_line_options, config_file):  # pylint: disable=too-many-locals
    """
    Get the configuration to use.

    Command line options override config files options.

    :param cmd_line_options: Command line options specified.
    :param config_file: config file to use.
    :return: ConfigOptions to use.
    """
    config_file_data = read_config.read_config_file(config_file)

    num_files = int(
        read_config.get_config_value("num_files", cmd_line_options, config_file_data,
                                     required=True))
    num_tasks = int(
        read_config.get_config_value("num_tasks", cmd_line_options, config_file_data,
                                     required=True))
    resmoke_args = read_config.get_config_value("resmoke_args", cmd_line_options, config_file_data,
                                                default="")
    npm_command = read_config.get_config_value("npm_command", cmd_line_options, config_file_data,
                                               default="jstestfuzz")
    jstestfuzz_vars = read_config.get_config_value("jstestfuzz_vars", cmd_line_options,
                                                   config_file_data, default="")
    name = read_config.get_config_value("name", cmd_line_options, config_file_data, required=True)
    variant = read_config.get_config_value("build_variant", cmd_line_options, config_file_data,
                                           required=True)
    continue_on_failure = read_config.get_config_value("continue_on_failure", cmd_line_options,
                                                       config_file_data, default="false")
    resmoke_jobs_max = read_config.get_config_value("resmoke_jobs_max", cmd_line_options,
                                                    config_file_data, default="0")
    should_shuffle = read_config.get_config_value("should_shuffle", cmd_line_options,
                                                  config_file_data, default="false")
    timeout_secs = read_config.get_config_value("timeout_secs", cmd_line_options, config_file_data,
                                                default="1800")
    use_multiversion = read_config.get_config_value("task_path_suffix", cmd_line_options,
                                                    config_file_data, default=False)

    suite = read_config.get_config_value("suite", cmd_line_options, config_file_data, required=True)

    return ConfigOptions(num_files, num_tasks, resmoke_args, npm_command, jstestfuzz_vars, name,
                         variant, continue_on_failure, resmoke_jobs_max, should_shuffle,
                         timeout_secs, use_multiversion, suite)


def build_fuzzer_sub_task(task_name: str, task_index: int, options: ConfigOptions) -> Task:
    """
    Build a shrub task to run the fuzzer.

    :param task_name: Parent name of task.
    :param task_index: Index of sub task being generated.
    :param options: Options to use for task.
    :return: Shrub task to run the fuzzer.
    """
    sub_task_name = taskname.name_generated_task(task_name, task_index, options.num_tasks,
                                                 options.variant)

    run_jstestfuzz_vars = {
        "jstestfuzz_vars":
            "--numGeneratedFiles {0} {1}".format(options.num_files, options.jstestfuzz_vars),
        "npm_command":
            options.npm_command,
    }
    suite_arg = f"--suites={options.suite}"
    run_tests_vars = {
        "continue_on_failure": options.continue_on_failure,
        "resmoke_args": f"{suite_arg} {options.resmoke_args}",
        "resmoke_jobs_max": options.resmoke_jobs_max,
        "should_shuffle": options.should_shuffle,
        "task_path_suffix": options.use_multiversion,
        "timeout_secs": options.timeout_secs,
        "task": options.name
    }  # yapf: disable

    commands = [
        FunctionCall("do setup"),
        FunctionCall("do multiversion setup") if options.use_multiversion else None,
        FunctionCall("setup jstestfuzz"),
        FunctionCall("run jstestfuzz", run_jstestfuzz_vars),
        FunctionCall("run generated tests", run_tests_vars)
    ]
    commands = [command for command in commands if command is not None]

    return Task(sub_task_name, commands, {TaskDependency("compile")})


def generate_fuzzer_sub_tasks(task_name: str, options: ConfigOptions) -> Set[Task]:
    """
    Generate evergreen tasks for fuzzers based on the options given.

    :param task_name: Parent name for tasks being generated.
    :param options: task options.
    :return: Set of shrub tasks.
    """
    sub_tasks = {
        build_fuzzer_sub_task(task_name, index, options)
        for index in range(options.num_tasks)
    }
    return sub_tasks


def create_fuzzer_task(options: ConfigOptions, build_variant: BuildVariant) -> None:
    """
    Generate an evergreen configuration for fuzzers and add it to the given build variant.

    :param options: task options.
    :param build_variant: Build variant to add tasks to.
    """
    task_name = options.name
    sub_tasks = generate_fuzzer_sub_tasks(task_name, options)

    build_variant.display_task(task_name, sub_tasks,
                               execution_existing_tasks={ExistingTask(f"{options.name}_gen")})


def main():
    """Generate fuzzer tests to run in evergreen."""
    parser = argparse.ArgumentParser(description=main.__doc__)

    parser.add_argument("--expansion-file", dest="expansion_file", type=str,
                        help="Location of expansions file generated by evergreen.")
    parser.add_argument("--num-files", dest="num_files", type=int,
                        help="Number of files to generate per task.")
    parser.add_argument("--num-tasks", dest="num_tasks", type=int,
                        help="Number of tasks to generate.")
    parser.add_argument("--resmoke-args", dest="resmoke_args", help="Arguments to pass to resmoke.")
    parser.add_argument("--npm-command", dest="npm_command", help="npm command to run for fuzzer.")
    parser.add_argument("--jstestfuzz-vars", dest="jstestfuzz_vars",
                        help="options to pass to jstestfuzz.")
    parser.add_argument("--name", dest="name", help="name of task to generate.")
    parser.add_argument("--variant", dest="build_variant", help="build variant to generate.")
    parser.add_argument("--use-multiversion", dest="task_path_suffix",
                        help="Task path suffix for multiversion generated tasks.")
    parser.add_argument("--continue-on-failure", dest="continue_on_failure",
                        help="continue_on_failure value for generated tasks.")
    parser.add_argument("--resmoke-jobs-max", dest="resmoke_jobs_max",
                        help="resmoke_jobs_max value for generated tasks.")
    parser.add_argument("--should-shuffle", dest="should_shuffle",
                        help="should_shuffle value for generated tasks.")
    parser.add_argument("--timeout-secs", dest="timeout_secs",
                        help="timeout_secs value for generated tasks.")
    parser.add_argument("--suite", dest="suite", help="Suite to run using resmoke.")

    options = parser.parse_args()

    config_options = _get_config_options(options, options.expansion_file)
    build_variant = BuildVariant(config_options.variant)
    create_fuzzer_task(config_options, build_variant)

    shrub_project = ShrubProject.empty()
    shrub_project.add_build_variant(build_variant)

    write_file_to_dir(CONFIG_DIRECTORY, f"{config_options.name}.json", shrub_project.json())


if __name__ == '__main__':
    main()