chromium/third_party/blink/renderer/modules/bluetooth/testing/clusterfuzz/test_case_fuzzer.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.
"""Module to generate a test file with random calls to the Web Bluetooth API."""

import random
from fuzzer_helpers import FillInParameter

# Contains the different types of base tokens used when generating a test case.
BASE_TOKENS = [
    'TRANSFORM_BASIC_BASE',
    'TRANSFORM_DEVICE_DISCOVERY_BASE',
    'TRANSFORM_CONNECTABLE_BASE',
    'TRANSFORM_SERVICES_RETRIEVED_BASE',
    'TRANSFORM_CHARACTERISTICS_RETRIEVED_BASE',
]

# Contains strings that represent calls to the Web Bluetooth API. These
# strings can be sequentially placed together to generate sequences of
# calls to the API. These strings are separated by line and placed so
# that indentation can be performed more easily.
TOKENS = [
    [
        '})',
        '.catch(e => console.log(e.name + \': \' + e.message))',
        '.then(() => {',
    ],
    # Request Device Tokens
    ['  requestDeviceWithKeyDown(TRANSFORM_REQUEST_DEVICE_OPTIONS);'],
    [
        '  return requestDeviceWithKeyDown(TRANSFORM_REQUEST_DEVICE_OPTIONS);',
        '})',
        '.then(device => {',
    ],
    # Connect Tokens
    [
        '  device.gatt.connect();',
    ],
    [
        '  return device.gatt.connect();',
        '})'
        '.then(gatt => {',
    ],
    [
        '  gatt.connect();',
    ],
    [
        '  return gatt.connect();',
        '})'
        '.then(gatt => {',
    ],
    # GetPrimaryService(s) Tokens
    [
        '  device.gatt.TRANSFORM_GET_PRIMARY_SERVICES;',
    ],
    [
        '  gatt.TRANSFORM_GET_PRIMARY_SERVICES;',
    ],
    [
        '  return device.gatt.TRANSFORM_GET_PRIMARY_SERVICES;',
        '})'
        '.then(services => {',
    ],
    [
        '  return gatt.TRANSFORM_GET_PRIMARY_SERVICES;',
        '})'
        '.then(services => {',
    ],
    # GetCharacteristic(s) Tokens
    [
        '  TRANSFORM_PICK_A_SERVICE;',
        '  service.TRANSFORM_GET_CHARACTERISTICS;',
    ],
    [
        '  TRANSFORM_PICK_A_SERVICE;',
        '  return service.TRANSFORM_GET_CHARACTERISTICS;',
    ],
    # ReadValue Tokens
    [
        '  TRANSFORM_PICK_A_CHARACTERISTIC;',
        '  characteristic.readValue();',
    ],
    [
        '  TRANSFORM_PICK_A_CHARACTERISTIC;',
        '  return characteristic.readValue().then(_ => characteristics);',
        '})',
        '.then(characteristics => {',
    ],
    # WriteValue Tokens
    [
        '  TRANSFORM_PICK_A_CHARACTERISTIC;',
        '  characteristic.writeValue(TRANSFORM_VALUE);',
    ],
    [
        '  TRANSFORM_PICK_A_CHARACTERISTIC;',
        '  return characteristic.writeValue(TRANSFORM_VALUE).then(_ => characteristics);',
        '})',
        '.then(characteristics => {',
    ],
    # Start Notifications Tokens
    [
        '  TRANSFORM_PICK_A_CHARACTERISTIC;',
        '  characteristic.startNotifications();',
    ],
    [
        '  TRANSFORM_PICK_A_CHARACTERISTIC;',
        '  return characteristic.startNotitications().then(_ => characteristics);',
        '})',
        '.then(characteristics => {',
    ],
    # Stop Notifications Tokens
    [
        '  TRANSFORM_PICK_A_CHARACTERISTIC;',
        '  characteristic.stopNotifications();',
    ],
    [
        '  TRANSFORM_PICK_A_CHARACTERISTIC;',
        '  return characteristic.stopNotitications().then(() => characteristics);',
        '})',
        '.then(characteristics => {',
    ],
    # Garbage Collection
    [
        '  runGarbageCollection();',
    ],
    [
        '})',
        '.then(runGarbageCollection)',
        '.then(() => {',
    ],
    # Reload Tokens
    # We generate a unique id for each reload and save it in sessionStorage.
    # This ensures that the reload is only executed once and that if test cases
    # share the same sessionStorage all their reloads are executed.
    [
        '  (() => {',
        '    let reload_id = TRANSFORM_RELOAD_ID;',
        '    if (!sessionStorage.getItem(reload_id)) {',
        '      sessionStorage.setItem(reload_id, true);',
        '      location.reload();',
        '    }',
        '  })();',
    ],
]

INDENT = '    '
BREAK = '\n'
END_TOKEN = '});'

# Maximum number of tokens that will be inserted in the generated
# test case.
MAX_NUM_OF_TOKENS = 100


def _GenerateSequenceOfRandomTokens():
    """Generates a sequence of calls to the Web Bluetooth API.

    Uses the arrays of strings in TOKENS and randomly picks a number between
    [1, 100] to generate a random sequence of calls to the Web Bluetooth API,
    calls to reload the page, and calls to perform  garbage collection.

    Returns:
      A string containing a sequence of calls to the Web Bluetooth API.
    """
    result = random.choice(BASE_TOKENS)

    for _ in range(random.randint(1, MAX_NUM_OF_TOKENS)):
        # Get random token.
        token = random.choice(TOKENS)

        # Indent and break line.
        for line in token:
            result += INDENT + line + BREAK

    result += INDENT + END_TOKEN

    return result


def GenerateTestFile(template_file_data):
    """Inserts a sequence of calls to the Web Bluetooth API into a template.

    Args:
      template_file_data: A template containing the 'TRANSFORM_RANDOM_TOKENS'
          string.
    Returns:
      A string consisting of template_file_data with the string
        'TRANSFORM_RANDOM_TOKENS' replaced with a sequence of calls to the Web
        Bluetooth API and calls to reload the page and perform garbage
        collection.
    """

    return FillInParameter('TRANSFORM_RANDOM_TOKENS',
                           _GenerateSequenceOfRandomTokens, template_file_data)