Bug 1524965 - Part 3: Add a script to generate testcases for BinAST with invalid content. r=Yoric
authorTooru Fujisawa <arai_a@mac.com>
Tue, 05 Feb 2019 00:34:08 +0900
changeset 516095 1143fee910c70ae6ea25e325b7908f38c8cfeba6
parent 516094 30e403f80bfd23dae0b7e416f7772fe72157e01f
child 516096 21aedfe62db37db8ee18416c62863b4df28c8327
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersYoric
bugs1524965
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1524965 - Part 3: Add a script to generate testcases for BinAST with invalid content. r=Yoric Integrated binjs_convert_from_json into encode.py. encode.py now generates invalid BinAST file if --binjs_convert_from_json option is provided. Source of test files are located inside js/src/jsapi-tests/binast/invalid/tests, with .js file as a source of JSON, and .py file for filtering the JSON. The generated tests are located inside js/src/jit-test/tests/binast/invalid. filter_utils.py provides some utility functions that is used by filter script, to handle tagged tuple/list structure in the JSON encoded AST. Actual testcases are added in Part 4. Differential Revision: https://phabricator.services.mozilla.com/D18560
js/src/jsapi-tests/binast/encode.py
js/src/jsapi-tests/binast/invalid/lib/filter_runner.py
js/src/jsapi-tests/binast/invalid/lib/filter_utils.py
js/src/tests/lib/jittests.py
--- a/js/src/jsapi-tests/binast/encode.py
+++ b/js/src/jsapi-tests/binast/encode.py
@@ -12,16 +12,19 @@ from sets import Set
 
 parser = OptionParser()
 parser.add_option('--topsrcdir', dest='topsrcdir',
                   help='path to mozilla-central')
 parser.add_option('--binjsdir', dest='binjsdir',
                   help='cwd when running binjs_encode')
 parser.add_option('--binjs_encode', dest='binjs_encode',
                   help='path to binjs_encode commad')
+parser.add_option('--binjs_convert_from_json', dest='binjs_convert_from_json',
+                  default="",
+                  help='path to binjs_convert_from_json commad (optional)')
 (options, filters) = parser.parse_args()
 
 
 def ensure_dir(path, name):
     """ Ensure the given directory exists.
     If the directory doesn't exist, exits with non-zero status.
 
     :param path (string)
@@ -48,20 +51,40 @@ def ensure_file(path, name):
         print('{} {} does not exit'.format(name, path),
               file=sys.stderr)
         sys.exit(1)
 
 
 ensure_dir(options.topsrcdir, 'topsrcdir')
 ensure_dir(options.binjsdir, 'binjsdir')
 ensure_file(options.binjs_encode, 'binjs_encode command')
+if options.binjs_convert_from_json:
+    ensure_file(options.binjs_convert_from_json, 'binjs_convert_from_json command')
 
 jittest_dir = os.path.join(options.topsrcdir, 'js', 'src', 'jit-test', 'tests')
 ensure_dir(jittest_dir, 'jit-test')
 
+jsapi_tests_dir = os.path.join(options.topsrcdir, 'js', 'src', 'jsapi-tests')
+ensure_dir(jsapi_tests_dir, 'binast in jsapi-tests')
+
+jsapi_binast_dir = os.path.join(jsapi_tests_dir, 'binast')
+ensure_dir(jsapi_binast_dir, 'binast in jsapi-tests')
+
+invalid_tests_dir = os.path.join(jsapi_binast_dir, 'invalid', 'tests')
+ensure_dir(invalid_tests_dir, 'invalid tests')
+
+invalid_lib_dir = os.path.join(jsapi_binast_dir, 'invalid', 'lib')
+ensure_dir(invalid_lib_dir, 'library for libvalid tests')
+sys.path.insert(0, os.path.join(invalid_lib_dir))
+
+invalid_tests_output_dir = os.path.join(jittest_dir, 'binast', 'invalid')
+ensure_dir(invalid_tests_output_dir, 'invalid tests output')
+
+import filter_runner
+
 
 def check_filter(outfile_path):
     """ Check if the output file is the target.
 
     :return (bool)
             True if the file is target and should be written.
     """
     if len(filters) == 0:
@@ -340,19 +363,67 @@ def convert_wpt():
            os.path.join(wpt_binast_dir, 'large.binjs'))
     encode(os.path.join(wpt_binast_dir, 'small-binjs.js'),
            os.path.join(wpt_binast_dir, 'small.binjs'))
 
 
 def convert_jsapi_test():
     """ Convert jsapi-test files into .binjs.
     """
-    binast_test_dir = os.path.join(options.topsrcdir, 'js', 'src', 'jsapi-tests', 'binast')
-    ensure_dir(binast_test_dir, 'binast in jsapi-tests')
+    encode_inplace(os.path.join(jsapi_binast_dir, 'parser', 'multipart'))
+
+
+def convert(infile_path, filter_path, outfile_path, binjs_convert_from_json_args=[]):
+    """ Convert the given .js file into .binjs, with filter applied.
+
+    :param infile_path (string)
+           The path to the input .js file.
+    :param filter_path (string)
+           The path to the filter .py file.
+    :param outfile_path (string)
+           The path to the output .binjs file.
+
+    :param binjs_convert_from_json_args (list)
+           The command line arguments passed to binjs_convert_from_json command.
+    """
+
+    if not check_filter(outfile_path):
+        return
+
+    print(' converting', infile_path)
+    print('         to', outfile_path)
+    print('with filter', filter_path)
 
-    encode_inplace(os.path.join(binast_test_dir, 'parser', 'multipart'))
+    infile = open(infile_path)
+    outfile = open(outfile_path, 'w')
+
+    source = subprocess.check_output(
+        [options.binjs_encode, 'advanced', 'json'],
+        cwd=options.binjsdir, stdin=infile)
+
+    filtered_source = filter_runner.run(filter_path, source)
+
+    convert_from_json = subprocess.Popen(
+        [options.binjs_convert_from_json] + binjs_convert_from_json_args,
+        cwd=options.binjsdir, stdin=subprocess.PIPE, stdout=outfile)
+    convert_from_json.stdin.write(filtered_source)
+    convert_from_json.stdin.close()
+
+
+def convert_invalid_test():
+    for root, dirs, files in os.walk(invalid_tests_dir):
+        for filename in files:
+            if filename.endswith('.js'):
+                infile_path = os.path.join(root, filename)
+                filter_path = os.path.join(root, filename.replace('.js', '.py'))
+                outfile_path = os.path.join(invalid_tests_output_dir,
+                                            filename.replace('.js', '.binjs'))
+                convert(infile_path, filter_path, outfile_path)
 
 
 convert_jittest()
 
 convert_wpt()
 
 convert_jsapi_test()
+
+if options.binjs_convert_from_json:
+    convert_invalid_test()
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/binast/invalid/lib/filter_runner.py
@@ -0,0 +1,26 @@
+# A utility to run filter program for given JSON format BinAST.
+
+import json
+import runpy
+
+
+def run(filter_path, source):
+    """
+    Run filter script against JSON source.
+
+    :param filter_path(string)
+           The path to the filter script (python script)
+    :param source(string)
+           The string representation of JSON format of BinAST file
+    :returns (string)
+             The string representation of filtered JSON
+    """
+    ast = json.loads(source)
+
+    # The filter script is executed with sys.path that has this directory in
+    # the first element, so that it can import filter_utils.
+    filter_global = runpy.run_path(filter_path)
+
+    filtered_ast = filter_global['filter_ast'](ast)
+
+    return json.dumps(filtered_ast)
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/binast/invalid/lib/filter_utils.py
@@ -0,0 +1,180 @@
+# Utilities to modify JSON encoded AST
+#
+# All functions raise exception on unexpected case, to avoid overlooking AST
+# change.
+
+
+def assert_tagged_tuple(obj):
+    """
+    Assert that the object is a tagged tuple
+
+    :param obj (dict)
+           The tagged tuple
+    """
+    type_ = obj['@TYPE']
+    if type_ != 'tagged tuple':
+        raise Exception('expected a tagged tuple, got {}'.format(type_))
+
+
+def assert_list(obj):
+    """
+    Assert that the object is a list
+
+    :param obj (dict)
+           The list
+    """
+    type_ = obj['@TYPE']
+    if type_ != 'list':
+        raise Exception('expected a list, got {}'.format(type_))
+
+
+def assert_interface(obj, expected_name):
+    """
+    Assert that the object is a tagged tuple for given interface
+
+    :param obj (dict)
+           The tagged tuple
+    :param expected_name (string)
+           The name of the interface
+    """
+    assert_tagged_tuple(obj)
+    actual_name = obj['@INTERFACE']
+    if actual_name != expected_name:
+        raise Exception('expected {}, got {}'.format(expected_name, actual_name))
+
+
+def get_field(obj, name):
+    """
+    Returns the field of the tagged tuple.
+
+    :param obj (dict)
+           The tagged tuple
+    :param name (string)
+           The name of the field to get
+    :return (dict)
+            The field value
+    :raises Exception
+            If there's no such field
+    """
+    assert_tagged_tuple(obj)
+    fields = obj['@FIELDS']
+    for field in fields:
+        if field['@FIELD_NAME'] == name:
+            return field['@FIELD_VALUE']
+    raise Exception('No such field: {}'.format(name))
+
+
+def replace_field(obj, name, value):
+    """
+    Replaces the field of the tagged tuple.
+
+    :param obj (dict)
+           the tagged tuple
+    :param name (string)
+           the name of the field to replace
+    :param value (dict)
+           the value of the field
+    :raises Exception
+            If there's no such field
+    """
+    assert_tagged_tuple(obj)
+    fields = obj['@FIELDS']
+    for field in fields:
+        if field['@FIELD_NAME'] == name:
+            field['@FIELD_VALUE'] = value
+            return
+    raise Exception('No such field: {}'.format(name))
+
+
+def remove_field(obj, name):
+    """
+    Removes the field from the tagged tuple
+
+    :param obj (dict)
+           the tagged tuple
+    :param name (string)
+           the name of the field to remove
+    :raises Exception
+            If there's no such field
+    """
+    assert_tagged_tuple(obj)
+    i = 0
+    fields = obj['@FIELDS']
+    for field in fields:
+        if field['@FIELD_NAME'] == name:
+            del fields[i]
+            return
+        i += 1
+    raise Exception('No such field: {}'.format(name))
+
+
+def get_element(obj, i):
+    """
+    Returns the element of the list.
+
+    :param obj (dict)
+           The list
+    :param i (int)
+           The indef of the element to get
+    :return (dict)
+            The element value
+    :raises Exception
+            On out of bound access
+    """
+    assert_list(obj)
+    elements = obj['@VALUE']
+    if i >= len(elements):
+        raise Exception('Out of bound: {} < {}'.format(i, len(elements)))
+    return elements[i]
+
+
+def replace_element(obj, i, value):
+    """
+    Replaces the element of the list.
+
+    :param obj (dict)
+           the list
+    :param i (int)
+           The indef of the element to replace
+    :param value (dict)
+           the value of the element
+    :raises Exception
+            On out of bound access
+    """
+    assert_list(obj)
+    elements = obj['@VALUE']
+    if i >= len(elements):
+        raise Exception('Out of bound: {} < {}'.format(i, len(elements)))
+    elements[i] = value
+
+
+def append_element(obj, value):
+    """
+    Appends the element to the list.
+
+    :param obj (dict)
+           the list
+    :param value (dict)
+           the value to be added to the list
+    """
+    assert_list(obj)
+    elements = obj['@VALUE']
+    elements.append(value)
+
+
+def remove_element(obj, i):
+    """
+    Removes the element from the list
+
+    :param obj (dict)
+           the list
+    :param i (int)
+           The indef of the element to remove
+    :raises Exception
+            On out of bound access
+    """
+    assert_list(obj)
+    elements = obj['@VALUE']
+    if i >= len(elements):
+        raise Exception('Out of bound: {} < {}'.format(i, len(elements)))
+    del elements[i]
--- a/js/src/tests/lib/jittests.py
+++ b/js/src/tests/lib/jittests.py
@@ -388,16 +388,18 @@ def find_tests(substring=None, run_binas
         if dirpath == '.':
             continue
 
         if not run_binast:
             if os.path.join('binast', 'lazy') in dirpath:
                 continue
             if os.path.join('binast', 'nonlazy') in dirpath:
                 continue
+            if os.path.join('binast', 'invalid') in dirpath:
+                continue
 
         for filename in filenames:
             if not (filename.endswith('.js') or filename.endswith('.binjs')):
                 continue
             if filename in ('shell.js', 'browser.js'):
                 continue
             test = os.path.join(dirpath, filename)
             if substring is None \