From 75a3416c74425a05ad1fe089c3967022efbd0309 Mon Sep 17 00:00:00 2001 From: Josh Wolfe Date: Sat, 4 Nov 2017 12:38:11 -0700 Subject: [PATCH] add messages to generated tests --- tools/generation/generate_type_coercion.py | 64 +-- tools/generation/lib/type_coercion.py | 462 ++++++++++++--------- 2 files changed, 298 insertions(+), 228 deletions(-) diff --git a/tools/generation/generate_type_coercion.py b/tools/generation/generate_type_coercion.py index c60eb3a1b13..818e9f14050 100755 --- a/tools/generation/generate_type_coercion.py +++ b/tools/generation/generate_type_coercion.py @@ -23,12 +23,12 @@ def main(): """, }, templates={ - '0': 'assert.sameValue("aaaa".indexOf("aa", %(value)s), 0);', - '1': 'assert.sameValue("aaaa".indexOf("aa", %(value)s), 1);', - "throws": 'assert.throws(%(error)s, function() { "".indexOf("", %(value)s); });', + '0': 'assert.sameValue("aaaa".indexOf("aa", %(value)s), 0, %(message)s);', + '1': 'assert.sameValue("aaaa".indexOf("aa", %(value)s), 1, %(message)s);', + "throws": 'assert.throws(%(error)s, function() { "".indexOf("", %(value)s); }, %(message)s);', }, nominal_value_cases=[ - (2, 'assert.sameValue("aaaa".indexOf("aa", %(value)s), 2);'), + (2, 'assert.sameValue("aaaa".indexOf("aa", %(value)s), 2, %(message)s);'), ], ) @@ -45,9 +45,9 @@ def main(): """, }, templates={ - '': 'assert.sameValue("foo".indexOf(%(value)s), 0);', - str: 'assert.sameValue("__%(expected_string_contents)s__".indexOf(%(value)s), 2);', - "throws": 'assert.throws(%(error)s, function() { "".indexOf(%(value)s); });', + '': 'assert.sameValue("foo".indexOf(%(value)s), 0, %(message)s);', + str: 'assert.sameValue("__%(expected_string_contents)s__".indexOf(%(value)s), 2, %(message)s);', + "throws": 'assert.throws(%(error)s, function() { "".indexOf(%(value)s); }, %(message)s);', }, ) @@ -66,12 +66,12 @@ def main(): "features": ["BigInt"], }, templates={ - '0': 'assert.sameValue(BigInt.asIntN(%(value)s, 1n), 0n);', - '1': 'assert.sameValue(BigInt.asIntN(%(value)s, 1n), -1n);', - "throws": 'assert.throws(%(error)s, function() { BigInt.asIntN(%(value)s, 0n); });', + '0': 'assert.sameValue(BigInt.asIntN(%(value)s, 1n), 0n, %(message)s);', + '1': 'assert.sameValue(BigInt.asIntN(%(value)s, 1n), -1n, %(message)s);', + "throws": 'assert.throws(%(error)s, function() { BigInt.asIntN(%(value)s, 0n); }, %(message)s);', }, nominal_value_cases=[ - (3, 'assert.sameValue(BigInt.asIntN(%(value)s, 10n), 2n);'), + (3, 'assert.sameValue(BigInt.asIntN(%(value)s, 10n), 2n, %(message)s);'), ], ) @@ -90,12 +90,12 @@ def main(): "features": ["BigInt"], }, templates={ - '0': 'assert.sameValue(BigInt.asUintN(%(value)s, 1n), 0n);', - '1': 'assert.sameValue(BigInt.asUintN(%(value)s, 1n), 1n);', - "throws": 'assert.throws(%(error)s, function() { BigInt.asUintN(%(value)s, 0n); });', + '0': 'assert.sameValue(BigInt.asUintN(%(value)s, 1n), 0n, %(message)s);', + '1': 'assert.sameValue(BigInt.asUintN(%(value)s, 1n), 1n, %(message)s);', + "throws": 'assert.throws(%(error)s, function() { BigInt.asUintN(%(value)s, 0n); }, %(message)s);', }, nominal_value_cases=[ - (3, 'assert.sameValue(BigInt.asUintN(%(value)s, 10n), 2n);'), + (3, 'assert.sameValue(BigInt.asUintN(%(value)s, 10n), 2n, %(message)s);'), ], ) @@ -114,13 +114,13 @@ def main(): "features": ["BigInt"], }, templates={ - '0n': 'assert.sameValue(BigInt.asIntN(2, %(value)s), 0n);', - '1n': 'assert.sameValue(BigInt.asIntN(2, %(value)s), 1n);', - "throws": 'assert.throws(%(error)s, function() { BigInt.asIntN(0, %(value)s); });', + '0n': 'assert.sameValue(BigInt.asIntN(2, %(value)s), 0n, %(message)s);', + '1n': 'assert.sameValue(BigInt.asIntN(2, %(value)s), 1n, %(message)s);', + "throws": 'assert.throws(%(error)s, function() { BigInt.asIntN(0, %(value)s); }, %(message)s);', }, nominal_value_cases=[ - (10, 'assert.sameValue(BigInt.asIntN(3, %(value)s), 2n);'), - (12345678901234567890003, 'assert.sameValue(BigInt.asIntN(4, %(value)s), 3n);'), + (10, 'assert.sameValue(BigInt.asIntN(3, %(value)s), 2n, %(message)s);'), + (12345678901234567890003, 'assert.sameValue(BigInt.asIntN(4, %(value)s), 3n, %(message)s);'), ], ) @@ -139,13 +139,13 @@ def main(): "features": ["BigInt"], }, templates={ - '0n': 'assert.sameValue(BigInt.asUintN(2, %(value)s), 0n);', - '1n': 'assert.sameValue(BigInt.asUintN(2, %(value)s), 1n);', - "throws": 'assert.throws(%(error)s, function() { BigInt.asUintN(0, %(value)s); });', + '0n': 'assert.sameValue(BigInt.asUintN(2, %(value)s), 0n, %(message)s);', + '1n': 'assert.sameValue(BigInt.asUintN(2, %(value)s), 1n, %(message)s);', + "throws": 'assert.throws(%(error)s, function() { BigInt.asUintN(0, %(value)s); }, %(message)s);', }, nominal_value_cases=[ - (10, 'assert.sameValue(BigInt.asUintN(3, %(value)s), 2n);'), - (12345678901234567890003, 'assert.sameValue(BigInt.asUintN(4, %(value)s), 3n);'), + (10, 'assert.sameValue(BigInt.asUintN(3, %(value)s), 2n, %(message)s);'), + (12345678901234567890003, 'assert.sameValue(BigInt.asUintN(4, %(value)s), 3n, %(message)s);'), ], ) @@ -194,8 +194,8 @@ def main(): '\n' ), templates={ - 'false': 'assert.sameValue(sample.getBigInt64(0, %(value)s), 0xffn);', - 'true': 'assert.sameValue(sample.getBigInt64(0, %(value)s), -0x100000000000000n);', + 'false': 'assert.sameValue(sample.getBigInt64(0, %(value)s), 0xffn, %(message)s);', + 'true': 'assert.sameValue(sample.getBigInt64(0, %(value)s), -0x100000000000000n, %(message)s);', }, ) @@ -239,13 +239,13 @@ def main(): '\n' ), templates={ - '0': 'assert.sameValue(sample.getBigInt64(%(value)s), 0x2702060280008001n);', - '1': 'assert.sameValue(sample.getBigInt64(%(value)s), 0x20602800080017fn);', - "throws": 'assert.throws(%(error)s, function() { sample.getBigInt64(%(value)s); });', + '0': 'assert.sameValue(sample.getBigInt64(%(value)s), 0x2702060280008001n, %(message)s);', + '1': 'assert.sameValue(sample.getBigInt64(%(value)s), 0x20602800080017fn, %(message)s);', + "throws": 'assert.throws(%(error)s, function() { sample.getBigInt64(%(value)s); }, %(message)s);', }, nominal_value_cases=[ - (2, 'assert.sameValue(sample.getBigInt64(%(value)s), 0x602800080017F00n);'), - (3, 'assert.sameValue(sample.getBigInt64(%(value)s), 0x2800080017F0001n);'), + (2, 'assert.sameValue(sample.getBigInt64(%(value)s), 0x602800080017F00n, %(message)s);'), + (3, 'assert.sameValue(sample.getBigInt64(%(value)s), 0x2800080017F0001n, %(message)s);'), ], ) diff --git a/tools/generation/lib/type_coercion.py b/tools/generation/lib/type_coercion.py index 338807f1d47..b0f9fd13572 100644 --- a/tools/generation/lib/type_coercion.py +++ b/tools/generation/lib/type_coercion.py @@ -16,6 +16,14 @@ def int2str(n, base=10): if s[-1:] == "L": s = s[:-1] return s +def js_repr(string): + string = string.replace("\\", "\\\\") + string = string.replace("\n", "\\n") + if '"' in string and "'" not in string: + return "'" + string + "'" + else: + string = string.replace("\"", "\\\"") + return "\"" + string + "\"" allowed_frontmatter_fields = frozenset([ "description", "esid", "info", "features", "includes" @@ -75,11 +83,11 @@ def append(self, test_case, templates): self.add_declaration('function MyError() {}') if "throws" in test_case.flags: - test_line = templates["throws"] % { - "error": test_case.expected_str, - "value": test_case.input_str, - "message": test_case.message, - } + test_line = format_test_line(templates["throws"], + error=test_case.expected_str, + value=test_case.input_str, + message=test_case.message, + ) elif self.conversion_str == "ToString": # some chars are guaranteed to never appear in expected strings for c in '\\_"': @@ -88,21 +96,21 @@ def append(self, test_case, templates): try: template = templates[test_case.expected_str] except KeyError: - test_line = templates[str] % { - "expected_string_contents": test_case.expected_str, - "value": test_case.input_str, - "message": test_case.message, - } + test_line = format_test_line(templates[str], + expected_string_contents=test_case.expected_str, + value=test_case.input_str, + message=test_case.message, + ) else: - test_line = template % { - "value": test_case.input_str, - "message": test_case.message, - } + test_line = format_test_line(template, + value=test_case.input_str, + message=test_case.message, + ) else: - test_line = templates[test_case.expected_str] % { - "value": test_case.input_str, - "message": test_case.message, - } + test_line = format_test_line(templates[test_case.expected_str], + value=test_case.input_str, + message=test_case.message, + ) self.append_test_line(test_line) @@ -180,6 +188,15 @@ def write(self, path_prefix): with open("test/" + path_prefix + self.file_name_suffix, "w") as f: f.write(contents) +def format_test_line(template, **kwargs): + message = kwargs.get("message", None) + if message == None: + # remove the message parameter and the comma before it + template = template.replace(", %(message)s", "") + else: + kwargs["message"] = js_repr(message) + return template % kwargs + def generate_tests(path_prefix, **kwargs): assert ( @@ -217,8 +234,8 @@ def add_test_case(test_case, templates, category_tag): assert inserted_nominal_values == False inserted_nominal_values = True for (nominal_value, template) in nominal_value_cases: - for value in conversion.from_nominal_value(nominal_value): - get_test_file(category_tag).append_test_line(template % {"value": value}) + for test_case in conversion.from_nominal_value(nominal_value): + get_test_file(category_tag).append_test_line(format_test_line(template, value=test_case.input_str, message=test_case.message)) continue if "BigInt" not in global_features and "BigInt" in test_case.flags: @@ -237,7 +254,7 @@ def add_test_case(test_case, templates, category_tag): if "toprimitive" in test_case.flags: assert toprimitive_case == None toprimitive_case = test_case - if test_case.primitive_hint != None: + if conversion.primitive_hint != None: assert toprimitive_case != None category_tag = "-toprimitive" for wrapped_case in advanced_primitive_wrappers(toprimitive_case, conversion.primitive_hint): @@ -248,31 +265,43 @@ def add_test_case(test_case, templates, category_tag): for test_file in test_files.values(): test_file.write(path_prefix) +def make_from_nominal_truncator(conversion_str): + def from_nominal_value(n): + yield TestCase(None, repr(n)) + yield TestCase(None, '"{}"'.format(n), message=conversion_str + ": parse Number") + offsets = [] + if n >= 0: offsets.append(0.9) + if n <= 0: offsets.append(-0.9) + for offset in offsets: + yield TestCase(None, repr(n + offset), message=conversion_str + ": truncate towards 0") + yield TestCase(None, '"{}"'.format(n + offset), message=conversion_str + ": parse Number => truncate towards 0") + return from_nominal_value + nominal_value_tests_go_here = "nominal value tests go here" conversions = { "ToBoolean": Conversion( tests=[ TestCase('false', 'false'), TestCase('true', 'true'), - TestCase('false', '0'), - TestCase('false', '-0'), - TestCase('true', '1'), - TestCase('true', '-1'), - TestCase('true', '0.1'), - TestCase('true', 'Infinity'), - TestCase('false', 'NaN'), - TestCase('false', 'undefined'), - TestCase('false', 'null'), - TestCase('false', '""'), - TestCase('true', '"string"'), - TestCase('true', '"false"'), - TestCase('true', '" "'), - TestCase('true', 'Symbol("1")', flags=["Symbol"]), - TestCase('false', '0n', flags=["BigInt"]), - TestCase('true', '1n', flags=["BigInt"]), - TestCase('true', '[]'), - TestCase('true', '{}'), - TestCase('true', 'Object(false)'), + TestCase('false', '0', message="ToBoolean: 0 => false"), + TestCase('false', '-0', message="ToBoolean: -0 => false"), + TestCase('true', '1', message="ToBoolean: Number != 0 => true"), + TestCase('true', '-1', message="ToBoolean: Number != 0 => true"), + TestCase('true', '0.1', message="ToBoolean: Number != 0 => true"), + TestCase('true', 'Infinity', message="ToBoolean: Number != 0 => true"), + TestCase('false', 'NaN', message="ToBoolean: NaN => false"), + TestCase('false', 'undefined', message="ToBoolean: undefined => false"), + TestCase('false', 'null', message="ToBoolean: null => false"), + TestCase('false', '""', message="ToBoolean: String .length == 0 => false"), + TestCase('true', '"string"', message="ToBoolean: String .length > 0 => true"), + TestCase('true', '"false"', message="ToBoolean: String .length > 0 => true"), + TestCase('true', '" "', message="ToBoolean: String .length > 0 => true"), + TestCase('true', 'Symbol("1")', flags=["Symbol"], message="ToBoolean: Symbol => true"), + TestCase('false', '0n', flags=["BigInt"], message="ToBoolean: 0n => false"), + TestCase('true', '1n', flags=["BigInt"], message="ToBoolean: BigInt != 0n => true"), + TestCase('true', '[]', message="ToBoolean: any object => true"), + TestCase('true', '{}', message="ToBoolean: any object => true"), + TestCase('true', 'Object(false)', message="ToBoolean: any object => true; no ToPrimitive"), ], # ToBoolean doesn't go through ToPrimitive, and can't throw. from_nominal_value=None, @@ -285,153 +314,154 @@ def add_test_case(test_case, templates, category_tag): TestCase('NaN', 'NaN', flags=["wrap"]), TestCase('Infinity', 'Infinity', flags=["wrap"]), TestCase('-Infinity', '-Infinity'), - TestCase('NaN', 'undefined', flags=["wrap"]), - TestCase('0', 'null', flags=["wrap"]), - TestCase('0', 'false'), - TestCase('1', 'true', flags=["wrap"]), - TestCase('1', '"1"', flags=["wrap"]), - TestCase('0', '"0"'), - TestCase('NaN', '""'), - TestCase('NaN', '"foo"'), - TestCase('NaN', '"true"'), + TestCase('NaN', 'undefined', flags=["wrap"], message="ToNumber: undefined => NaN"), + TestCase('0', 'null', flags=["wrap"], message="ToNumber: null => 0"), + TestCase('0', 'false', message="ToNumber: false => 0"), + TestCase('1', 'true', flags=["wrap"], message="ToNumber: true => 1"), + TestCase('0', '"0"', message="ToNumber: parse Number"), + TestCase('1', '"1"', flags=["wrap"], message="ToNumber: parse Number"), + TestCase('Infinity', '"Infinity"', message="ToNumber: parse Number"), + TestCase('-Infinity', '"-Infinity"', message="ToNumber: parse Number"), + TestCase('NaN', '""', message="ToNumber: unparseable string => NaN"), + TestCase('NaN', '"foo"', message="ToNumber: unparseable string => NaN"), + TestCase('NaN', '"true"', message="ToNumber: unparseable string => NaN"), nominal_value_tests_go_here, - TestCase('TypeError', '0n', flags=["throws", "wrap", "BigInt"]), - TestCase('TypeError', 'Symbol("1")', flags=["throws", "wrap", "Symbol"]), - TestCase('0', '[0]'), - TestCase('1', '["1"]'), - TestCase('NaN', '{}'), - TestCase('NaN', '[]'), + TestCase('TypeError', '0n', flags=["throws", "wrap", "BigInt"], message="ToNumber: BigInt => TypeError"), + TestCase('TypeError', 'Symbol("1")', flags=["throws", "wrap", "Symbol"], message="ToNumber: Symbol => TypeError"), + TestCase('0', '[0]', message='ToNumber: [0].toString() => "0" => 0'), + TestCase('1', '["1"]', message='ToNumber: ["1"].toString() => "1" => 1'), + TestCase('NaN', '{}', message='ToNumber: ({}).toString() => "[object Object]" => NaN'), + TestCase('NaN', '[]', message='ToNumber: [].toString() => "" => NaN'), + ], + from_nominal_value=lambda n: [ + TestCase(None, template.format(n), message=message) + for template, message in [ + ('{}', None), + ('"{}"', "ToNumber: parse Number"), + ] ], - from_nominal_value=lambda n: [template.format(n) for template in ['{}', '"{}"']], primitive_hint="number", ), "ToInteger": Conversion( tests=[ TestCase('0', '0', flags=["wrap"]), TestCase('1', '1', flags=["toprimitive"]), - TestCase('0', '-0.9'), - TestCase('0', '0.9'), - TestCase('0', 'NaN', flags=["wrap"]), - TestCase('0', 'undefined', flags=["wrap"]), - TestCase('0', 'null', flags=["wrap"]), - TestCase('1', 'true', flags=["wrap"]), - TestCase('0', 'false'), - TestCase('0', '"0"'), - TestCase('1', '"1"', flags=["wrap"]), - TestCase('0', '""'), - TestCase('0', '"foo"'), - TestCase('0', '"true"'), + TestCase('0', '-0.9', message="ToInteger: truncate towards 0"), + TestCase('0', '0.9', message="ToInteger: truncate towards 0"), + TestCase('1', '1.9', message="ToInteger: truncate towards 0"), + TestCase('0', 'NaN', flags=["wrap"], message="ToInteger: NaN => 0"), + TestCase('0', 'undefined', flags=["wrap"], message="ToInteger: undefined => NaN => 0"), + TestCase('0', 'null', flags=["wrap"], message="ToInteger: null => 0"), + TestCase('0', 'false', message="ToInteger: false => 0"), + TestCase('1', 'true', flags=["wrap"], message="ToInteger: true => 1"), + TestCase('0', '"0"', message="ToInteger: parse Number => 0"), + TestCase('1', '"1.9"', flags=["wrap"], message="ToInteger: parse Number => 1.9 => 1"), + TestCase('0', '""', message="ToInteger: unparseable string => NaN => 0"), + TestCase('0', '"foo"', message="ToInteger: unparseable string => NaN => 0"), + TestCase('0', '"true"', message="ToInteger: unparseable string => NaN => 0"), nominal_value_tests_go_here, - TestCase('TypeError', '0n', flags=["throws", "wrap", "BigInt"]), - TestCase('TypeError', 'Symbol("1")', flags=["throws", "wrap", "Symbol"]), - TestCase('0', '[0]'), - TestCase('1', '["1"]'), - TestCase('0', '{}'), - TestCase('0', '[]'), + TestCase('TypeError', '0n', flags=["throws", "wrap", "BigInt"], message="ToInteger: BigInt => TypeError"), + TestCase('TypeError', 'Symbol("1")', flags=["throws", "wrap", "Symbol"], message="ToInteger: Symbol => TypeError"), + TestCase('0', '[0]', message='ToInteger: [0].toString() => "0" => 0'), + TestCase('1', '["1"]', message='ToInteger: ["1"].toString() => "1" => 1'), + TestCase('0', '{}', message='ToInteger: ({}).toString() => "[object Object]" => NaN => 0'), + TestCase('0', '[]', message='ToInteger: [].toString() => "" => NaN => 0'), ], - from_nominal_value=lambda n: - [template.format(n) for template in ['{}', '"{}"']] + - [template.format(n + 0.9) for template in ['{}', '"{}"'] if n >= 0] + - [template.format(n - 0.9) for template in ['{}', '"{}"'] if n <= 0] + - [], + from_nominal_value=make_from_nominal_truncator("ToInteger"), primitive_hint="number", ), "ToIndex": Conversion( tests=[ TestCase('0', '0', flags=["wrap"]), TestCase('1', '1', flags=["toprimitive"]), - TestCase('0', '-0.9'), - TestCase('0', '0.9'), - TestCase('0', 'NaN', flags=["wrap"]), - TestCase('0', 'undefined', flags=["wrap"]), - TestCase('0', 'null', flags=["wrap"]), - TestCase('0', 'false'), - TestCase('1', 'true', flags=["wrap"]), - TestCase('0', '"0"'), - TestCase('1', '"1"', flags=["wrap"]), - TestCase('0', '""'), - TestCase('0', '"foo"'), - TestCase('0', '"true"'), + TestCase('0', '-0.9', message="ToIndex: truncate towards 0"), + TestCase('0', '0.9', message="ToIndex: truncate towards 0"), + TestCase('0', 'NaN', flags=["wrap"], message="ToIndex: NaN => 0"), + TestCase('0', 'undefined', flags=["wrap"], message="ToIndex: undefined => NaN => 0"), + TestCase('0', 'null', flags=["wrap"], message="ToIndex: null => 0"), + TestCase('0', 'false', message="ToIndex: false => 0"), + TestCase('1', 'true', flags=["wrap"], message="ToIndex: true => 1"), + TestCase('0', '"0"', message="ToIndex: parse Number"), + TestCase('1', '"1"', flags=["wrap"], message="ToIndex: parse Number"), + TestCase('0', '""', message="ToIndex: parse Number => NaN => 0"), + TestCase('0', '"foo"', message="ToIndex: parse Number => NaN => 0"), + TestCase('0', '"true"', message="ToIndex: parse Number => NaN => 0"), nominal_value_tests_go_here, - TestCase('RangeError', '-1', flags=["throws"]), - TestCase('RangeError', '-2.5', flags=["throws"]), - TestCase('RangeError', '"-2.5"', flags=["throws"]), - TestCase('RangeError', '-Infinity', flags=["throws"]), - TestCase('RangeError', int2str(2**53), flags=["throws"]), - TestCase('RangeError', 'Infinity', flags=["throws"]), - TestCase('TypeError', '0n', flags=["throws", "wrap", "BigInt"]), - TestCase('TypeError', 'Symbol("1")', flags=["throws", "wrap", "Symbol"]), - TestCase('0', '[0]'), - TestCase('1', '["1"]'), - TestCase('0', '{}'), - TestCase('0', '[]'), + TestCase('RangeError', '-1', flags=["throws"], message="ToIndex: throw when integerIndex < 0"), + TestCase('RangeError', '-2.5', flags=["throws"], message="ToIndex: throw when integerIndex < 0"), + TestCase('RangeError', '"-2.5"', flags=["throws"], message="ToIndex: parse Number => throw when integerIndex < 0"), + TestCase('RangeError', '-Infinity', flags=["throws"], message="ToIndex: throw when integerIndex < 0"), + TestCase('RangeError', int2str(2**53), flags=["throws"], message="ToIndex: throw when integerIndex > 2**53-1"), + TestCase('RangeError', 'Infinity', flags=["throws"], message="ToIndex: throw when integerIndex > 2**53-1"), + TestCase('TypeError', '0n', flags=["throws", "wrap", "BigInt"], message="ToIndex: BigInt => TypeError"), + TestCase('TypeError', 'Symbol("1")', flags=["throws", "wrap", "Symbol"], message="ToIndex: Symbol => TypeError"), + TestCase('0', '[0]', message='ToIndex: [0].toString() => "0" => 0'), + TestCase('1', '["1"]', message='ToIndex: ["1"].toString() => "1" => 1'), + TestCase('0', '{}', message='ToIndex: ({}).toString() => "[object Object]" => NaN => 0'), + TestCase('0', '[]', message='ToIndex: [].toString() => "" => NaN => 0'), ], - from_nominal_value=lambda n: - [template.format(n) for template in ['{}', '"{}"']] + - [template.format(n + 0.9) for template in ['{}', '"{}"'] if n >= 0] + - [template.format(n - 0.9) for template in ['{}', '"{}"'] if n <= 0] + - [], + from_nominal_value=make_from_nominal_truncator("toIndex"), primitive_hint="number", ), "ToBigInt": Conversion( tests=[ TestCase('0n', '0n', flags=["wrap"]), - TestCase('1n', 'true', flags=["wrap"]), - TestCase('1n', '"1"', flags=["wrap", "toprimitive"]), TestCase('0n', '-0n'), - TestCase('0n', '"-0"'), - TestCase('0n', 'false'), - TestCase('0n', '""'), - TestCase('0n', '" "'), - TestCase('0n', '[]'), - TestCase('1n', '[1]'), + TestCase('0n', 'false', message="ToBigInt: false => 0n"), + TestCase('1n', 'true', flags=["wrap"], message="ToBigInt: true => 1n"), + TestCase('1n', '"1"', flags=["wrap", "toprimitive"], message="ToBigInt: parse BigInt"), + TestCase('0n', '"-0"', message="ToBigInt: parse BigInt"), + TestCase('0n', '""', message="ToBigInt: empty String => 0n"), + TestCase('0n', '" "', message="ToBigInt: String with only whitespace => 0n"), + TestCase('0n', '[]', message="ToBigInt: .toString() => empty String => 0n"), + TestCase('1n', '[1]', message="ToBigInt: .toString() => parse BigInt"), nominal_value_tests_go_here, - TestCase('TypeError', 'undefined', flags=["throws", "wrap"]), - TestCase('TypeError', 'null', flags=["throws", "wrap"]), - TestCase('TypeError', '0', flags=["throws", "wrap"]), - TestCase('TypeError', 'NaN', flags=["throws"]), - TestCase('TypeError', 'Infinity', flags=["throws"]), - TestCase('TypeError', 'Symbol("1")', flags=["throws", "wrap", "Symbol"]), - TestCase('SyntaxError', '"a"', flags=["throws"]), - TestCase('SyntaxError', '"0b2"', flags=["throws", "wrap"]), - TestCase('SyntaxError', '" 0b2 "', flags=["throws"]), - TestCase('SyntaxError', '"0o8"', flags=["throws"]), - TestCase('SyntaxError', '"0xg"', flags=["throws"]), - TestCase('SyntaxError', '"1n"', flags=["throws"]), + TestCase('TypeError', 'undefined', flags=["throws", "wrap"], message="ToBigInt: undefined => TypeError"), + TestCase('TypeError', 'null', flags=["throws", "wrap"], message="ToBigInt: null => TypeError"), + TestCase('TypeError', '0', flags=["throws", "wrap"], message="ToBigInt: Number => TypeError"), + TestCase('TypeError', 'NaN', flags=["throws"], message="ToBigInt: Number => TypeError"), + TestCase('TypeError', 'Infinity', flags=["throws"], message="ToBigInt: Number => TypeError"), + TestCase('TypeError', 'Symbol("1")', flags=["throws", "wrap", "Symbol"], message="ToBigInt: Symbol => TypeError"), + TestCase('SyntaxError', '"a"', flags=["throws"], message="ToBigInt: unparseable BigInt"), + TestCase('SyntaxError', '"0b2"', flags=["throws", "wrap"], message="ToBigInt: unparseable BigInt binary"), + TestCase('SyntaxError', '" 0b2 "', flags=["throws"], message="ToBigInt: unparseable BigInt with leading/trailing whitespace"), + TestCase('SyntaxError', '"0o8"', flags=["throws"], message="ToBigInt: unparseable BigInt octal"), + TestCase('SyntaxError', '"0xg"', flags=["throws"], message="ToBigInt: unparseable BigInt hex"), + TestCase('SyntaxError', '"1n"', flags=["throws"], message="ToBigInt: unparseable BigInt due to literal suffix"), ], from_nominal_value=lambda n: [ - '{}n'.format(int2str(n)), - '"{}"'.format(int2str(n)), - '"0b{}"'.format(int2str(n, 2)), - '"0o{}"'.format(int2str(n, 8)), - '"0x{}"'.format(int2str(n, 16)), - '" 0x{} "'.format(int2str(n, 16)), - '" {} "'.format(int2str(n)), - '[{}n]'.format(int2str(n)), - '["{}"]'.format(int2str(n)), + TestCase(None, '{}n'.format(int2str(n))), + TestCase(None, '"{}"'.format(int2str(n)), message="ToBigInt: parse BigInt"), + TestCase(None, '"0b{}"'.format(int2str(n, 2)), message="ToBigInt: parse BigInt binary"), + TestCase(None, '"0o{}"'.format(int2str(n, 8)), message="ToBigInt: parse BigInt octal"), + TestCase(None, '"0x{}"'.format(int2str(n, 16)), message="ToBigInt: parse BigInt hex"), + TestCase(None, '" 0x{} "'.format(int2str(n, 16)), message="ToBigInt: parse BigInt ignore leading/trailing whitespace"), + TestCase(None, '" {} "'.format(int2str(n)), message="ToBigInt: parse BigInt ignore leading/trailing whitespace"), + TestCase(None, '[{}n]'.format(int2str(n)), message="ToBigInt: .toString() => parse BigInt"), + TestCase(None, '["{}"]'.format(int2str(n)), message="ToBigInt: .toString() => parse BigInt"), ], primitive_hint="number", ), "ToString": Conversion( tests=[ TestCase('', '""'), - TestCase('', '[]'), - TestCase('undefined', 'undefined', flags=["wrap"]), - TestCase('null', 'null', flags=["wrap"]), - TestCase('true', 'true'), - TestCase('false', 'false', flags=["wrap"]), - TestCase('0', '0', flags=["wrap"]), - TestCase('0', '-0'), - TestCase('Infinity', 'Infinity'), - TestCase('-Infinity', '-Infinity'), - TestCase('NaN', 'NaN'), - TestCase('123.456', '123.456'), - TestCase('-123.456', '-123.456'), TestCase('foo', '"foo"', flags=["wrap", "toprimitive"]), - TestCase('0', '0n', flags=["wrap", "BigInt"]), - TestCase('foo,bar', '["foo", "bar"]'), - TestCase('[object Object]', '{}'), - TestCase('TypeError', 'Symbol("1")', flags=["throws", "wrap", "Symbol"]), + TestCase('undefined', 'undefined', flags=["wrap"], message='ToString: undefined => "undefined"'), + TestCase('null', 'null', flags=["wrap"], message='ToString: null => "null"'), + TestCase('true', 'true', message='ToString: true => "true"'), + TestCase('false', 'false', flags=["wrap"], message='ToString: false => "false"'), + TestCase('0', '0', flags=["wrap"], message='ToString: Number to String'), + TestCase('0', '-0', message='ToString: -0 => "0"'), + TestCase('Infinity', 'Infinity', message='ToString: Infinity => "Infinity"'), + TestCase('-Infinity', '-Infinity', message='ToString: -Infinity => "-Infinity"'), + TestCase('NaN', 'NaN', message='ToString: NaN => "NaN"'), + TestCase('123.456', '123.456', message='ToString: Number to String'), + TestCase('-123.456', '-123.456', message='ToString: Number to String'), + TestCase('0', '0n', flags=["wrap", "BigInt"], message='ToString: BigInt to String'), + TestCase('', '[]', message='ToString: .toString()'), + TestCase('foo,bar', '["foo", "bar"]', message='ToString: .toString()'), + TestCase('[object Object]', '{}', message='ToString: .toString()'), + TestCase('TypeError', 'Symbol("1")', flags=["throws", "wrap", "Symbol"], message='ToString: Symbol => TypeError'), ], from_nominal_value=None, primitive_hint="string", @@ -439,35 +469,41 @@ def add_test_case(test_case, templates, category_tag): } def basic_primitive_wrappers(test_case, hint): - def test(input_str, flags=None): + def test(input_str, flags=None, message=None): if flags == None: flags = [] flags += test_case.flags if '[Symbol.toPrimitive]:' in input_str: flags += ["Symbol.toPrimitive", "computed-property-names"] - return TestCase(test_case.expected_str, input_str, flags=flags, message=test_case.message) + if test_case.message != None: + conversion_str, message_2 = test_case.message.split(": ", 1) + message = conversion_str + ": " + message + " => " + message_2 + else: + message = "ToPrimitive: " + message + return TestCase(test_case.expected_str, input_str, flags=flags, message=message) if test_case.input_str not in ('null', 'undefined'): # null and undefined result in {} rather than a proper wrapper, # so skip this case for those values. - yield test('Object(%s)' % test_case.input_str) + yield test('Object(%s)' % test_case.input_str, message="unbox object with internal slot") method = 'function() { return %s; }' % test_case.input_str - yield test('{[Symbol.toPrimitive]: %s}' % method, flags=["Symbol.toPrimitive", "computed-property-names"]) + yield test('{[Symbol.toPrimitive]: %s}' % method, flags=["Symbol.toPrimitive", "computed-property-names"], message="@@toPrimitive") if hint == "number": - yield test('{valueOf: %s}' % method) + yield test('{valueOf: %s}' % method, message="valueOf") else: # have to make toString get skipped to hit valueOf - yield test('{valueOf: %s, toString: null}' % method) - yield test('{toString: %s}' % method) + yield test('{valueOf: %s, toString: null}' % method, message="valueOf") + yield test('{toString: %s}' % method, message="toString") def advanced_primitive_wrappers(test_case, hint): - def test(input_str, flags=None): + def test(input_str, flags=None, message=None): if flags == None: flags = [] flags += test_case.flags if '[Symbol.toPrimitive]:' in input_str: flags += ["Symbol.toPrimitive", "computed-property-names"] - return TestCase(test_case.expected_str, input_str, flags=flags, message=test_case.message) + assert message != None + return TestCase(test_case.expected_str, input_str, flags=flags, message="ToPrimitive: " + message) method_names = { "number": ['valueOf', 'toString'], @@ -484,84 +520,118 @@ def test(input_str, flags=None): yield test( '{[Symbol.toPrimitive]: %(method)s, %(method_name_0)s: err, %(method_name_1)s: err}' % template_args, flags=["err"], + message="@@toPrimitive takes precedence", ) yield test( '{%(method_name_0)s: %(method)s, %(method_name_1)s: err}' % template_args, flags=["err"], + message="%(method_name_0)s takes precedence over %(method_name_1)s" % template_args, ) if hint == "number": # The default valueOf returns an object, which is unsuitable. # The default toString returns a String, which is suitable. # Therefore, a single overrided method will only be called if it's toString. - yield test('{toString: %(method)s}' % template_args) + yield test( + '{toString: %(method)s}' % template_args, + message="toString with no valueOf", + ) # GetMethod: if func is undefined or null, return undefined. - yield test('{[Symbol.toPrimitive]: undefined, %(method_name_0)s: %(method)s}' % template_args) - yield test('{[Symbol.toPrimitive]: null, %(method_name_0)s: %(method)s}' % template_args) + yield test( + '{[Symbol.toPrimitive]: undefined, %(method_name_0)s: %(method)s}' % template_args, + message="skip @@toPrimitive when it's undefined", + ) + yield test( + '{[Symbol.toPrimitive]: null, %(method_name_0)s: %(method)s}' % template_args, + message="skip @@toPrimitive when it's null", + ) # if method_names[0] is not callable, fallback to method_names[1] - yield test('{%(method_name_0)s: null, %(method_name_1)s: %(method)s}' % template_args) - yield test('{%(method_name_0)s: 1, %(method_name_1)s: %(method)s}' % template_args) - yield test('{%(method_name_0)s: {}, %(method_name_1)s: %(method)s}' % template_args) + yield test( + '{%(method_name_0)s: null, %(method_name_1)s: %(method)s}' % template_args, + message="skip %(method_name_0)s when it's not callable" % template_args, + ) + yield test( + '{%(method_name_0)s: 1, %(method_name_1)s: %(method)s}' % template_args, + message="skip %(method_name_0)s when it's not callable" % template_args, + ) + yield test( + '{%(method_name_0)s: {}, %(method_name_1)s: %(method)s}' % template_args, + message="skip %(method_name_0)s when it's not callable" % template_args, + ) # if method_names[0] returns an object, fallback to method_names[1] - yield test('{%(method_name_0)s: function() { return {}; }, %(method_name_1)s: %(method)s}' % template_args) - yield test('{%(method_name_0)s: function() { return Object(12345); }, %(method_name_1)s: %(method)s}' % template_args) + yield test( + '{%(method_name_0)s: function() { return {}; }, %(method_name_1)s: %(method)s}' % template_args, + message="skip %(method_name_0)s when it returns an object" % template_args, + ) + yield test( + '{%(method_name_0)s: function() { return Object(12345); }, %(method_name_1)s: %(method)s}' % template_args, + message="skip %(method_name_0)s when it returns an object" % template_args, + ) + def throws_test(expected_err, input_str, flags=[], message=None): + return TestCase(expected_err, input_str, flags=flags + ["throws"], message="ToPrimitive: " + message) # ToPrimitive: input[@@toPrimitive] is not callable (and non-null) - yield TestCase('TypeError', + yield throws_test('TypeError', '{[Symbol.toPrimitive]: 1}', - flags=["throws", "Symbol.toPrimitive", "computed-property-names"], + flags=["Symbol.toPrimitive", "computed-property-names"], + message="throw when @@toPrimitive is not callable", ) - yield TestCase('TypeError', + yield throws_test('TypeError', '{[Symbol.toPrimitive]: {}}', - flags=["throws", "Symbol.toPrimitive", "computed-property-names"], + flags=["Symbol.toPrimitive", "computed-property-names"], + message="throw when @@toPrimitive is not callable", ) # ToPrimitive: input[@@toPrimitive] returns object - yield TestCase('TypeError', + yield throws_test('TypeError', '{[Symbol.toPrimitive]: function() { return Object(1); }}', - flags=["throws", "Symbol.toPrimitive", "computed-property-names"], + flags=["Symbol.toPrimitive", "computed-property-names"], + message="throw when @@toPrimitive returns an object", ) - yield TestCase('TypeError', + yield throws_test('TypeError', '{[Symbol.toPrimitive]: function() { return {}; }}', - flags=["throws", "Symbol.toPrimitive", "computed-property-names"], + flags=["Symbol.toPrimitive", "computed-property-names"], + message="throw when @@toPrimitive returns an object", ) # ToPrimitive: input[@@toPrimitive] throws - yield TestCase('MyError', + yield throws_test('MyError', '{[Symbol.toPrimitive]: function() { throw new MyError(); }}', - flags=["MyError", "throws", "Symbol.toPrimitive", "computed-property-names"], + flags=["MyError", "Symbol.toPrimitive", "computed-property-names"], + message="propagate errors from @@toPrimitive", ) # OrdinaryToPrimitive: method throws if hint == "number": - yield TestCase('MyError', + yield throws_test('MyError', '{valueOf: function() { throw new MyError(); }}', - flags=["MyError", "throws"], + flags=["MyError"], + message="propagate errors from valueOf", ) else: # have to make toString get skipped to hit valueOf - yield TestCase('MyError', + yield throws_test('MyError', '{valueOf: function() { throw new MyError(); }, toString: null}', - flags=["MyError", "throws"], + flags=["MyError"], + message="propagate errors from valueOf", ) - yield TestCase('MyError', + yield throws_test('MyError', '{toString: function() { throw new MyError(); }}', - flags=["MyError", "throws"], + flags=["MyError"], + message="propagate errors from toString", ) # OrdinaryToPrimitive: both methods are unsuitable - def testUnsuitableMethod(method_str): - return TestCase('TypeError', + def unsuitable_method_test(method_str): + return throws_test('TypeError', '{valueOf: %(method)s, toString: %(method)s}' % {"method": method_str}, - flags=["throws"], + message="throw when skipping both valueOf and toString", + ) + for value in ['null', '1', '{}', 'function() { return Object(1); }', 'function() { return {}; }']: + yield throws_test('TypeError', + '{valueOf: %(method)s, toString: %(method)s}' % {"method": value}, + message="throw when skipping both valueOf and toString", ) - # not callable: - yield testUnsuitableMethod('null') - yield testUnsuitableMethod('1') - yield testUnsuitableMethod('{}') - # returns object: - yield testUnsuitableMethod('function() { return Object(1); }') - yield testUnsuitableMethod('function() { return {}; }')