chromium/third_party/blink/renderer/modules/bluetooth/testing/clusterfuzz/fuzz_main_run.py

# Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Script to generate Web Bluetooth web tests that can be run in ClusterFuzz.

This script uses templates in the templates/ directory to generate html files
that can be run as web tests. The script reads a template, includes files
necessary to run as a web test, fuzzes its parameters and saves the result in
a new file in the directory specified when running the script.
"""

import argparse
import glob
import os
import sys
import tempfile
import time

from fuzzer_helpers import FillInParameter
import parameter_fuzzer
import test_case_fuzzer

JS_FILES_AND_PARAMETERS = (('testharness.js', 'INCLUDE_TESTHARNESS'),
                           ('testharnessreport.js', 'INCLUDE_REPORT'),
                           ('bluetooth-test.js', 'INCLUDE_BLUETOOTH_TEST'),
                           ('bluetooth-fake-devices.js',
                            'INCLUDE_BLUETOOTH_FAKE_DEVICES'))

SCRIPT_PREFIX = '<script type="text/javascript">\n'
SCRIPT_SUFFIX = '\n</script>\n'


def _GetArguments():
    """Parses the arguments passed when running this script.

    Returns:
      An argparse.Namespace object containing the arguments in sys.argv.
    """
    parser = argparse.ArgumentParser()

    # Arguments used by ClusterFuzz:
    parser.add_argument(
        '-n',
        '--no_of_files',
        type=int,
        required=True,
        help='The number of test cases that the fuzzer is '
        'expected to generate')
    parser.add_argument(
        '-i',
        '--input_dir',
        help='The directory containing the fuzzer\'s data '
        'bundle.')
    parser.add_argument(
        '-o',
        '--output_dir',
        required=True,
        help='The directory where test case files should be '
        'written to.')

    parser.add_argument(
        '--content_shell_dir',
        help='The directory of content shell. If present the '
        'program will print a command to run the '
        'generated test file.')

    return parser.parse_args()


def FuzzTemplate(template_path, resources_path):
    """Uses a template to return a test case that can be run as a web test.

    This functions reads the template in |template_path|, injects the necessary
    js files to run as a web test and fuzzes the template's parameters to
    generate a test case.

    Args:
      template_path: The path to the template that will be used to generate
          a new test case.
      resources_path: Path to the js files that need to be included.

    Returns:
      A binary string containing the test case.
    """
    print('Generating test file based on {}'.format(template_path))

    # Read the template.
    with open(template_path, 'r', encoding='utf-8') as template_in:
        template_file_data = template_in.read()

    # Generate a test file based on the template.
    generated_test = test_case_fuzzer.GenerateTestFile(template_file_data)
    # Fuzz parameters.
    fuzzed_file_data = parameter_fuzzer.FuzzParameters(generated_test)

    # Add includes
    for (js_file_name, include_parameter) in JS_FILES_AND_PARAMETERS:
        # Read js file.
        js_file_path = os.path.join(resources_path, js_file_name)
        with open(js_file_path, 'r', encoding='utf-8') as js_in:
            js_file_data = js_in.read()

        js_file_data = (SCRIPT_PREFIX + js_file_data + SCRIPT_SUFFIX)

        fuzzed_file_data = FillInParameter(
            include_parameter,
            lambda data=js_file_data: data,
            fuzzed_file_data)

    return fuzzed_file_data.encode('utf-8')


def WriteTestFile(test_file_data, test_file_prefix, output_dir):
    """Creates a new file with a unique name and writes the test case to it.

    Args:
      test_file_data: The data to be included in the new file.
      test_file_prefix: Used as a prefix when generating a new file.
      output_dir: The directory where the new file should be created.

    Returns:
      A string representing the file path to access the new file.
    """

    file_descriptor, file_path = tempfile.mkstemp(
        prefix=test_file_prefix, suffix='.html', dir=output_dir)

    with os.fdopen(file_descriptor, 'wb') as output:
        print('Writing {} bytes to \'{}\''.format(len(test_file_data),
                                                  file_path))
        output.write(test_file_data)

    return file_path


def main():
    args = _GetArguments()

    print('Generating {} test file(s).'.format(args.no_of_files))
    print('Writing test files to: \'{}\''.format(args.output_dir))
    if args.input_dir:
        print('Reading data bundle from: \'{}\''.format(args.input_dir))

    # Get Templates
    current_path = os.path.dirname(os.path.realpath(__file__))
    available_templates = glob.glob(
        os.path.join(current_path, 'templates', '*.html'))

    # Generate Test Files
    resources_path = os.path.join(current_path, 'resources')
    start_time = time.time()
    for file_no in range(args.no_of_files):
        template_path = available_templates[file_no % len(available_templates)]

        test_file_data = FuzzTemplate(template_path, resources_path)

        # Get Test File
        template_name = os.path.splitext(os.path.basename(template_path))[0]
        test_file_name = 'fuzz-{}-{}-{}'.format(template_name, int(start_time),
                                                int(file_no))

        test_file_path = WriteTestFile(test_file_data, test_file_name,
                                       args.output_dir)

        if args.content_shell_dir:
            print('{} --run-web-tests {}'.format(args.content_shell_dir,
                                                 test_file_path))


if __name__ == '__main__':
    sys.exit(main())