github.com/nuvolaris/goja@v0.0.0-20230825100449-967811910c6d/tc39_test.go (about)

     1  package goja
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path"
     9  	"sort"
    10  	"strings"
    11  	"sync"
    12  	"testing"
    13  	"time"
    14  
    15  	"gopkg.in/yaml.v2"
    16  )
    17  
    18  const (
    19  	tc39BASE = "testdata/test262"
    20  )
    21  
    22  var (
    23  	invalidFormatError = errors.New("Invalid file format")
    24  
    25  	ignorableTestError = newSymbol(stringEmpty)
    26  )
    27  
    28  var (
    29  	skipPrefixes prefixList
    30  
    31  	skipList = map[string]bool{
    32  
    33  		// out-of-date (https://github.com/tc39/test262/issues/3407)
    34  		"test/language/expressions/prefix-increment/S11.4.4_A6_T3.js":        true,
    35  		"test/language/expressions/prefix-increment/S11.4.4_A6_T2.js":        true,
    36  		"test/language/expressions/prefix-increment/S11.4.4_A6_T1.js":        true,
    37  		"test/language/expressions/prefix-decrement/S11.4.5_A6_T3.js":        true,
    38  		"test/language/expressions/prefix-decrement/S11.4.5_A6_T2.js":        true,
    39  		"test/language/expressions/prefix-decrement/S11.4.5_A6_T1.js":        true,
    40  		"test/language/expressions/postfix-increment/S11.3.1_A6_T3.js":       true,
    41  		"test/language/expressions/postfix-increment/S11.3.1_A6_T2.js":       true,
    42  		"test/language/expressions/postfix-increment/S11.3.1_A6_T1.js":       true,
    43  		"test/language/expressions/postfix-decrement/S11.3.2_A6_T3.js":       true,
    44  		"test/language/expressions/postfix-decrement/S11.3.2_A6_T2.js":       true,
    45  		"test/language/expressions/postfix-decrement/S11.3.2_A6_T1.js":       true,
    46  		"test/language/expressions/compound-assignment/S11.13.2_A7.1_T4.js":  true,
    47  		"test/language/expressions/compound-assignment/S11.13.2_A7.1_T2.js":  true,
    48  		"test/language/expressions/compound-assignment/S11.13.2_A7.1_T1.js":  true,
    49  		"test/language/expressions/compound-assignment/S11.13.2_A7.11_T4.js": true,
    50  		"test/language/expressions/compound-assignment/S11.13.2_A7.11_T2.js": true,
    51  		"test/language/expressions/compound-assignment/S11.13.2_A7.11_T1.js": true,
    52  		"test/language/expressions/compound-assignment/S11.13.2_A7.10_T4.js": true,
    53  		"test/language/expressions/compound-assignment/S11.13.2_A7.10_T2.js": true,
    54  		"test/language/expressions/compound-assignment/S11.13.2_A7.10_T1.js": true,
    55  		"test/language/expressions/compound-assignment/S11.13.2_A7.9_T4.js":  true,
    56  		"test/language/expressions/compound-assignment/S11.13.2_A7.9_T2.js":  true,
    57  		"test/language/expressions/compound-assignment/S11.13.2_A7.9_T1.js":  true,
    58  		"test/language/expressions/compound-assignment/S11.13.2_A7.8_T4.js":  true,
    59  		"test/language/expressions/compound-assignment/S11.13.2_A7.8_T2.js":  true,
    60  		"test/language/expressions/compound-assignment/S11.13.2_A7.8_T1.js":  true,
    61  		"test/language/expressions/compound-assignment/S11.13.2_A7.7_T4.js":  true,
    62  		"test/language/expressions/compound-assignment/S11.13.2_A7.7_T2.js":  true,
    63  		"test/language/expressions/compound-assignment/S11.13.2_A7.7_T1.js":  true,
    64  		"test/language/expressions/compound-assignment/S11.13.2_A7.6_T4.js":  true,
    65  		"test/language/expressions/compound-assignment/S11.13.2_A7.6_T2.js":  true,
    66  		"test/language/expressions/compound-assignment/S11.13.2_A7.6_T1.js":  true,
    67  		"test/language/expressions/compound-assignment/S11.13.2_A7.5_T4.js":  true,
    68  		"test/language/expressions/compound-assignment/S11.13.2_A7.5_T2.js":  true,
    69  		"test/language/expressions/compound-assignment/S11.13.2_A7.5_T1.js":  true,
    70  		"test/language/expressions/compound-assignment/S11.13.2_A7.4_T4.js":  true,
    71  		"test/language/expressions/compound-assignment/S11.13.2_A7.4_T2.js":  true,
    72  		"test/language/expressions/compound-assignment/S11.13.2_A7.4_T1.js":  true,
    73  		"test/language/expressions/compound-assignment/S11.13.2_A7.3_T4.js":  true,
    74  		"test/language/expressions/compound-assignment/S11.13.2_A7.3_T2.js":  true,
    75  		"test/language/expressions/compound-assignment/S11.13.2_A7.3_T1.js":  true,
    76  		"test/language/expressions/compound-assignment/S11.13.2_A7.2_T4.js":  true,
    77  		"test/language/expressions/compound-assignment/S11.13.2_A7.2_T2.js":  true,
    78  		"test/language/expressions/compound-assignment/S11.13.2_A7.2_T1.js":  true,
    79  		"test/language/expressions/assignment/S11.13.1_A7_T3.js":             true,
    80  
    81  		// timezone
    82  		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js":  true,
    83  		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js":  true,
    84  		"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true,
    85  
    86  		// floating point date calculations
    87  		"test/built-ins/Date/UTC/fp-evaluation-order.js": true,
    88  
    89  		// quantifier integer limit in regexp
    90  		"test/built-ins/RegExp/quantifier-integer-limit.js": true,
    91  
    92  		// GetFunctionRealm
    93  		"test/built-ins/Function/internals/Construct/base-ctor-revoked-proxy.js": true,
    94  
    95  		// Uses deprecated __lookupGetter__/__lookupSetter__
    96  		"test/language/expressions/class/elements/private-getter-is-not-a-own-property.js": true,
    97  		"test/language/expressions/class/elements/private-setter-is-not-a-own-property.js": true,
    98  		"test/language/statements/class/elements/private-setter-is-not-a-own-property.js":  true,
    99  		"test/language/statements/class/elements/private-getter-is-not-a-own-property.js":  true,
   100  
   101  		// restricted unicode regexp syntax
   102  		"test/built-ins/RegExp/unicode_restricted_quantifiable_assertion.js":         true,
   103  		"test/built-ins/RegExp/unicode_restricted_octal_escape.js":                   true,
   104  		"test/built-ins/RegExp/unicode_restricted_incomple_quantifier.js":            true,
   105  		"test/built-ins/RegExp/unicode_restricted_incomplete_quantifier.js":          true,
   106  		"test/built-ins/RegExp/unicode_restricted_identity_escape_x.js":              true,
   107  		"test/built-ins/RegExp/unicode_restricted_identity_escape_u.js":              true,
   108  		"test/built-ins/RegExp/unicode_restricted_identity_escape_c.js":              true,
   109  		"test/built-ins/RegExp/unicode_restricted_identity_escape_alpha.js":          true,
   110  		"test/built-ins/RegExp/unicode_restricted_identity_escape.js":                true,
   111  		"test/built-ins/RegExp/unicode_restricted_brackets.js":                       true,
   112  		"test/built-ins/RegExp/unicode_restricted_character_class_escape.js":         true,
   113  		"test/annexB/built-ins/RegExp/prototype/compile/pattern-string-invalid-u.js": true,
   114  
   115  		// Because goja parser works in UTF-8 it is not possible to pass strings containing invalid UTF-16 code points.
   116  		// This is mitigated by escaping them as \uXXXX, however because of this the RegExp source becomes
   117  		// `\uXXXX` instead of `<the actual UTF-16 code point of XXXX>`.
   118  		// The resulting RegExp will work exactly the same, but it causes these two tests to fail.
   119  		"test/annexB/built-ins/RegExp/RegExp-leading-escape-BMP.js":  true,
   120  		"test/annexB/built-ins/RegExp/RegExp-trailing-escape-BMP.js": true,
   121  		"test/language/literals/regexp/S7.8.5_A1.4_T2.js":            true,
   122  		"test/language/literals/regexp/S7.8.5_A1.1_T2.js":            true,
   123  		"test/language/literals/regexp/S7.8.5_A2.1_T2.js":            true,
   124  		"test/language/literals/regexp/S7.8.5_A2.4_T2.js":            true,
   125  
   126  		// async generator
   127  		"test/language/expressions/optional-chaining/member-expression.js":                                                                            true,
   128  		"test/language/expressions/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier-alt.js":              true,
   129  		"test/language/expressions/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier.js":                  true,
   130  		"test/language/destructuring/binding/syntax/destructuring-object-parameters-function-arguments-length.js":                                     true,
   131  		"test/language/destructuring/binding/syntax/destructuring-array-parameters-function-arguments-length.js":                                      true,
   132  		"test/language/comments/hashbang/function-constructor.js":                                                                                     true,
   133  		"test/language/statements/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier.js":      true,
   134  		"test/language/statements/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier-alt.js":  true,
   135  		"test/language/statements/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier.js":                   true,
   136  		"test/language/statements/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier-alt.js":               true,
   137  		"test/language/expressions/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier-alt.js": true,
   138  		"test/language/expressions/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier.js":     true,
   139  		"test/built-ins/Object/seal/seal-asyncgeneratorfunction.js":                                                                                   true,
   140  		"test/language/statements/switch/scope-lex-async-generator.js":                                                                                true,
   141  		"test/language/statements/class/elements/private-async-generator-method-name.js":                                                              true,
   142  		"test/language/expressions/class/elements/private-async-generator-method-name.js":                                                             true,
   143  		"test/language/expressions/async-generator/name.js":                                                                                           true,
   144  		"test/language/statements/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier.js":                            true,
   145  		"test/language/statements/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js":                        true,
   146  		"test/language/statements/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier.js":                          true,
   147  		"test/language/statements/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js":                      true,
   148  		"test/language/statements/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier.js":               true,
   149  		"test/language/statements/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier-alt.js":           true,
   150  		"test/language/statements/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier.js":                      true,
   151  		"test/language/statements/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js":                  true,
   152  		"test/language/expressions/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier.js":                           true,
   153  		"test/language/expressions/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js":                       true,
   154  		"test/language/expressions/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier.js":                         true,
   155  		"test/language/expressions/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js":                     true,
   156  		"test/language/expressions/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier.js":              true,
   157  		"test/language/expressions/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier-alt.js":          true,
   158  		"test/language/expressions/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier.js":                     true,
   159  		"test/language/expressions/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js":                 true,
   160  		"test/built-ins/GeneratorFunction/is-a-constructor.js":                                                                                        true,
   161  
   162  		// async iterator
   163  		"test/language/expressions/optional-chaining/iteration-statement-for-await-of.js": true,
   164  
   165  		// legacy number literals
   166  		"test/language/literals/numeric/non-octal-decimal-integer.js": true,
   167  		"test/language/literals/string/S7.8.4_A4.3_T2.js":             true,
   168  		"test/language/literals/string/S7.8.4_A4.3_T1.js":             true,
   169  
   170  		// integer separators
   171  		"test/language/expressions/object/cpn-obj-lit-computed-property-name-from-integer-separators.js":                  true,
   172  		"test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-integer-separators.js":      true,
   173  		"test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-integer-separators.js":          true,
   174  		"test/language/statements/class/cpn-class-decl-computed-property-name-from-integer-separators.js":                 true,
   175  		"test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-integer-separators.js":       true,
   176  		"test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-integer-separators.js":  true,
   177  		"test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-integer-separators.js":         true,
   178  		"test/language/expressions/class/cpn-class-expr-computed-property-name-from-integer-separators.js":                true,
   179  		"test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-integer-separators.js": true,
   180  
   181  		// BigInt
   182  		"test/built-ins/Object/seal/seal-biguint64array.js": true,
   183  		"test/built-ins/Object/seal/seal-bigint64array.js":  true,
   184  
   185  		// Regexp
   186  		"test/language/literals/regexp/invalid-range-negative-lookbehind.js":    true,
   187  		"test/language/literals/regexp/invalid-range-lookbehind.js":             true,
   188  		"test/language/literals/regexp/invalid-optional-negative-lookbehind.js": true,
   189  		"test/language/literals/regexp/invalid-optional-lookbehind.js":          true,
   190  
   191  		// FIXME bugs
   192  
   193  		// Left-hand side as a CoverParenthesizedExpression
   194  		"test/language/expressions/assignment/fn-name-lhs-cover.js": true,
   195  
   196  		// Character \ missing from character class [\c]
   197  		"test/annexB/built-ins/RegExp/RegExp-invalid-control-escape-character-class.js": true,
   198  		"test/annexB/built-ins/RegExp/RegExp-control-escape-russian-letter.js":          true,
   199  
   200  		// Skip due to regexp named groups
   201  		"test/built-ins/String/prototype/replaceAll/searchValue-replacer-RegExp-call.js": true,
   202  	}
   203  
   204  	featuresBlackList = []string{
   205  		"async-iteration",
   206  		"Symbol.asyncIterator",
   207  		"BigInt",
   208  		"resizable-arraybuffer",
   209  		"regexp-named-groups",
   210  		"regexp-dotall",
   211  		"regexp-unicode-property-escapes",
   212  		"regexp-match-indices",
   213  		"legacy-regexp",
   214  		"tail-call-optimization",
   215  		"Temporal",
   216  		"import-assertions",
   217  		"dynamic-import",
   218  		"logical-assignment-operators",
   219  		"import.meta",
   220  		"Atomics",
   221  		"Atomics.waitAsync",
   222  		"FinalizationRegistry",
   223  		"WeakRef",
   224  		"numeric-separator-literal",
   225  		"__getter__",
   226  		"__setter__",
   227  		"ShadowRealm",
   228  		"SharedArrayBuffer",
   229  		"error-cause",
   230  		"decorators",
   231  		"regexp-v-flag",
   232  	}
   233  )
   234  
   235  func init() {
   236  
   237  	skip := func(prefixes ...string) {
   238  		for _, prefix := range prefixes {
   239  			skipPrefixes.Add(prefix)
   240  		}
   241  	}
   242  
   243  	skip(
   244  		// Go 1.16 only supports unicode 13
   245  		"test/language/identifiers/start-unicode-14.",
   246  		"test/language/identifiers/part-unicode-14.",
   247  
   248  		// generators and async generators (harness/hidden-constructors.js)
   249  		"test/built-ins/Async",
   250  
   251  		// async generators
   252  		"test/language/statements/class/elements/wrapped-in-sc-rs-static-async-generator-",
   253  		"test/language/statements/class/elements/same-line-method-rs-static-async-generator-",
   254  		"test/language/statements/class/elements/regular-definitions-rs-static-async-generator-",
   255  		"test/language/statements/class/elements/private-static-async-generator-",
   256  		"test/language/statements/class/elements/new-sc-line-method-rs-static-async-generator-",
   257  		"test/language/statements/class/elements/multiple-stacked-definitions-rs-static-async-generator-",
   258  		"test/language/statements/class/elements/new-no-sc-line-method-rs-static-async-generator-",
   259  		"test/language/statements/class/elements/multiple-definitions-rs-static-async-generator-",
   260  		"test/language/statements/class/elements/after-same-line-static-method-rs-static-async-generator-",
   261  		"test/language/statements/class/elements/after-same-line-method-rs-static-async-generator-",
   262  		"test/language/statements/class/elements/after-same-line-static-method-rs-static-async-generator-",
   263  
   264  		"test/language/expressions/class/elements/wrapped-in-sc-rs-static-async-generator-",
   265  		"test/language/expressions/class/elements/same-line-method-rs-static-async-generator-",
   266  		"test/language/expressions/class/elements/regular-definitions-rs-static-async-generator-",
   267  		"test/language/expressions/class/elements/private-static-async-generator-",
   268  		"test/language/expressions/class/elements/new-sc-line-method-rs-static-async-generator-",
   269  		"test/language/expressions/class/elements/multiple-stacked-definitions-rs-static-async-generator-",
   270  		"test/language/expressions/class/elements/new-no-sc-line-method-rs-static-async-generator-",
   271  		"test/language/expressions/class/elements/multiple-definitions-rs-static-async-generator-",
   272  		"test/language/expressions/class/elements/after-same-line-static-method-rs-static-async-generator-",
   273  		"test/language/expressions/class/elements/after-same-line-method-rs-static-async-generator-",
   274  		"test/language/expressions/class/elements/after-same-line-static-method-rs-static-async-generator-",
   275  
   276  		"test/language/eval-code/direct/async-gen-",
   277  
   278  		// BigInt
   279  		"test/built-ins/TypedArrayConstructors/BigUint64Array/",
   280  		"test/built-ins/TypedArrayConstructors/BigInt64Array/",
   281  
   282  		// restricted unicode regexp syntax
   283  		"test/language/literals/regexp/u-",
   284  
   285  		// legacy octal escape in strings in strict mode
   286  		"test/language/literals/string/legacy-octal-",
   287  		"test/language/literals/string/legacy-non-octal-",
   288  
   289  		// modules
   290  		"test/language/export/",
   291  		"test/language/import/",
   292  		"test/language/module-code/",
   293  	)
   294  
   295  }
   296  
   297  type tc39Test struct {
   298  	name string
   299  	f    func(t *testing.T)
   300  }
   301  
   302  type tc39BenchmarkItem struct {
   303  	name     string
   304  	duration time.Duration
   305  }
   306  
   307  type tc39BenchmarkData []tc39BenchmarkItem
   308  
   309  type tc39TestCtx struct {
   310  	base         string
   311  	t            *testing.T
   312  	prgCache     map[string]*Program
   313  	prgCacheLock sync.Mutex
   314  	enableBench  bool
   315  	benchmark    tc39BenchmarkData
   316  	benchLock    sync.Mutex
   317  	sabStub      *Program
   318  	//lint:ignore U1000 Only used with race
   319  	testQueue []tc39Test
   320  }
   321  
   322  type TC39MetaNegative struct {
   323  	Phase, Type string
   324  }
   325  
   326  type tc39Meta struct {
   327  	Negative TC39MetaNegative
   328  	Includes []string
   329  	Flags    []string
   330  	Features []string
   331  	Es5id    string
   332  	Es6id    string
   333  	Esid     string
   334  }
   335  
   336  type prefixList struct {
   337  	prefixes map[int]map[string]struct{}
   338  }
   339  
   340  func (pl *prefixList) Add(prefix string) {
   341  	l := pl.prefixes[len(prefix)]
   342  	if l == nil {
   343  		l = make(map[string]struct{})
   344  		if pl.prefixes == nil {
   345  			pl.prefixes = make(map[int]map[string]struct{})
   346  		}
   347  		pl.prefixes[len(prefix)] = l
   348  	}
   349  	l[prefix] = struct{}{}
   350  }
   351  
   352  func (pl *prefixList) Match(s string) bool {
   353  	for l, prefixes := range pl.prefixes {
   354  		if len(s) >= l {
   355  			if _, exists := prefixes[s[:l]]; exists {
   356  				return true
   357  			}
   358  		}
   359  	}
   360  	return false
   361  }
   362  
   363  func (m *tc39Meta) hasFlag(flag string) bool {
   364  	for _, f := range m.Flags {
   365  		if f == flag {
   366  			return true
   367  		}
   368  	}
   369  	return false
   370  }
   371  
   372  func parseTC39File(name string) (*tc39Meta, string, error) {
   373  	f, err := os.Open(name)
   374  	if err != nil {
   375  		return nil, "", err
   376  	}
   377  	defer f.Close()
   378  
   379  	b, err := io.ReadAll(f)
   380  	if err != nil {
   381  		return nil, "", err
   382  	}
   383  
   384  	str := string(b)
   385  	metaStart := strings.Index(str, "/*---")
   386  	if metaStart == -1 {
   387  		return nil, "", invalidFormatError
   388  	} else {
   389  		metaStart += 5
   390  	}
   391  	metaEnd := strings.Index(str, "---*/")
   392  	if metaEnd == -1 || metaEnd <= metaStart {
   393  		return nil, "", invalidFormatError
   394  	}
   395  
   396  	var meta tc39Meta
   397  	err = yaml.Unmarshal([]byte(str[metaStart:metaEnd]), &meta)
   398  	if err != nil {
   399  		return nil, "", err
   400  	}
   401  
   402  	if meta.Negative.Type != "" && meta.Negative.Phase == "" {
   403  		return nil, "", errors.New("negative type is set, but phase isn't")
   404  	}
   405  
   406  	return &meta, str, nil
   407  }
   408  
   409  func (*tc39TestCtx) detachArrayBuffer(call FunctionCall) Value {
   410  	if obj, ok := call.Argument(0).(*Object); ok {
   411  		if buf, ok := obj.self.(*arrayBufferObject); ok {
   412  			buf.detach()
   413  			return _undefined
   414  		}
   415  	}
   416  	panic(typeError("detachArrayBuffer() is called with incompatible argument"))
   417  }
   418  
   419  func (*tc39TestCtx) throwIgnorableTestError(FunctionCall) Value {
   420  	panic(ignorableTestError)
   421  }
   422  
   423  func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing.TB) {
   424  	defer func() {
   425  		if x := recover(); x != nil {
   426  			panic(fmt.Sprintf("panic while running %s: %v", name, x))
   427  		}
   428  	}()
   429  	vm := New()
   430  	_262 := vm.NewObject()
   431  	_262.Set("detachArrayBuffer", ctx.detachArrayBuffer)
   432  	_262.Set("createRealm", ctx.throwIgnorableTestError)
   433  	_262.Set("evalScript", func(call FunctionCall) Value {
   434  		script := call.Argument(0).String()
   435  		result, err := vm.RunString(script)
   436  		if err != nil {
   437  			panic(err)
   438  		}
   439  		return result
   440  	})
   441  	vm.Set("$262", _262)
   442  	vm.Set("IgnorableTestError", ignorableTestError)
   443  	vm.RunProgram(ctx.sabStub)
   444  	var out []string
   445  	async := meta.hasFlag("async")
   446  	if async {
   447  		err := ctx.runFile(ctx.base, path.Join("harness", "doneprintHandle.js"), vm)
   448  		if err != nil {
   449  			t.Fatal(err)
   450  		}
   451  		vm.Set("print", func(msg string) {
   452  			out = append(out, msg)
   453  		})
   454  	} else {
   455  		vm.Set("print", t.Log)
   456  	}
   457  
   458  	err, early := ctx.runTC39Script(name, src, meta.Includes, vm)
   459  
   460  	if err != nil {
   461  		if meta.Negative.Type == "" {
   462  			if err, ok := err.(*Exception); ok {
   463  				if err.Value() == ignorableTestError {
   464  					t.Skip("Test threw IgnorableTestError")
   465  				}
   466  			}
   467  			t.Fatalf("%s: %v", name, err)
   468  		} else {
   469  			if (meta.Negative.Phase == "early" || meta.Negative.Phase == "parse") && !early || meta.Negative.Phase == "runtime" && early {
   470  				t.Fatalf("%s: error %v happened at the wrong phase (expected %s)", name, err, meta.Negative.Phase)
   471  			}
   472  			var errType string
   473  
   474  			switch err := err.(type) {
   475  			case *Exception:
   476  				if o, ok := err.Value().(*Object); ok {
   477  					if c := o.Get("constructor"); c != nil {
   478  						if c, ok := c.(*Object); ok {
   479  							errType = c.Get("name").String()
   480  						} else {
   481  							t.Fatalf("%s: error constructor is not an object (%v)", name, o)
   482  						}
   483  					} else {
   484  						t.Fatalf("%s: error does not have a constructor (%v)", name, o)
   485  					}
   486  				} else {
   487  					t.Fatalf("%s: error is not an object (%v)", name, err.Value())
   488  				}
   489  			case *CompilerSyntaxError:
   490  				errType = "SyntaxError"
   491  			case *CompilerReferenceError:
   492  				errType = "ReferenceError"
   493  			default:
   494  				t.Fatalf("%s: error is not a JS error: %v", name, err)
   495  			}
   496  
   497  			if errType != meta.Negative.Type {
   498  				vm.vm.prg.dumpCode(t.Logf)
   499  				t.Fatalf("%s: unexpected error type (%s), expected (%s)", name, errType, meta.Negative.Type)
   500  			}
   501  		}
   502  	} else {
   503  		if meta.Negative.Type != "" {
   504  			vm.vm.prg.dumpCode(t.Logf)
   505  			t.Fatalf("%s: Expected error: %v", name, err)
   506  		}
   507  	}
   508  
   509  	if vm.vm.sp != 0 {
   510  		t.Fatalf("sp: %d", vm.vm.sp)
   511  	}
   512  
   513  	if l := len(vm.vm.iterStack); l > 0 {
   514  		t.Fatalf("iter stack is not empty: %d", l)
   515  	}
   516  	if async {
   517  		complete := false
   518  		for _, line := range out {
   519  			if strings.HasPrefix(line, "Test262:AsyncTestFailure:") {
   520  				t.Fatal(line)
   521  			} else if line == "Test262:AsyncTestComplete" {
   522  				complete = true
   523  			}
   524  		}
   525  		if !complete {
   526  			for _, line := range out {
   527  				t.Log(line)
   528  			}
   529  			t.Fatal("Test262:AsyncTestComplete was not printed")
   530  		}
   531  	}
   532  }
   533  
   534  func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) {
   535  	if skipList[name] {
   536  		t.Skip("Excluded")
   537  	}
   538  	if skipPrefixes.Match(name) {
   539  		t.Skip("Excluded")
   540  	}
   541  	p := path.Join(ctx.base, name)
   542  	meta, src, err := parseTC39File(p)
   543  	if err != nil {
   544  		//t.Fatalf("Could not parse %s: %v", name, err)
   545  		t.Errorf("Could not parse %s: %v", name, err)
   546  		return
   547  	}
   548  	if meta.hasFlag("module") {
   549  		t.Skip("module")
   550  	}
   551  	if meta.Es5id == "" {
   552  		for _, feature := range meta.Features {
   553  			for _, bl := range featuresBlackList {
   554  				if feature == bl {
   555  					t.Skip("Blacklisted feature")
   556  				}
   557  			}
   558  		}
   559  	}
   560  
   561  	var startTime time.Time
   562  	if ctx.enableBench {
   563  		startTime = time.Now()
   564  	}
   565  
   566  	hasRaw := meta.hasFlag("raw")
   567  
   568  	if hasRaw || !meta.hasFlag("onlyStrict") {
   569  		//log.Printf("Running normal test: %s", name)
   570  		t.Logf("Running normal test: %s", name)
   571  		ctx.runTC39Test(name, src, meta, t)
   572  	}
   573  
   574  	if !hasRaw && !meta.hasFlag("noStrict") {
   575  		//log.Printf("Running strict test: %s", name)
   576  		t.Logf("Running strict test: %s", name)
   577  		ctx.runTC39Test(name, "'use strict';\n"+src, meta, t)
   578  	}
   579  
   580  	if ctx.enableBench {
   581  		ctx.benchLock.Lock()
   582  		ctx.benchmark = append(ctx.benchmark, tc39BenchmarkItem{
   583  			name:     name,
   584  			duration: time.Since(startTime),
   585  		})
   586  		ctx.benchLock.Unlock()
   587  	}
   588  
   589  }
   590  
   591  func (ctx *tc39TestCtx) init() {
   592  	ctx.prgCache = make(map[string]*Program)
   593  	ctx.sabStub = MustCompile("sabStub.js", `
   594  		Object.defineProperty(this, "SharedArrayBuffer", {
   595  			get: function() {
   596  				throw IgnorableTestError;
   597  			}
   598  		});`,
   599  		false)
   600  }
   601  
   602  func (ctx *tc39TestCtx) compile(base, name string) (*Program, error) {
   603  	ctx.prgCacheLock.Lock()
   604  	defer ctx.prgCacheLock.Unlock()
   605  
   606  	prg := ctx.prgCache[name]
   607  	if prg == nil {
   608  		fname := path.Join(base, name)
   609  		f, err := os.Open(fname)
   610  		if err != nil {
   611  			return nil, err
   612  		}
   613  		defer f.Close()
   614  
   615  		b, err := io.ReadAll(f)
   616  		if err != nil {
   617  			return nil, err
   618  		}
   619  
   620  		str := string(b)
   621  		prg, err = Compile(name, str, false)
   622  		if err != nil {
   623  			return nil, err
   624  		}
   625  		ctx.prgCache[name] = prg
   626  	}
   627  
   628  	return prg, nil
   629  }
   630  
   631  func (ctx *tc39TestCtx) runFile(base, name string, vm *Runtime) error {
   632  	prg, err := ctx.compile(base, name)
   633  	if err != nil {
   634  		return err
   635  	}
   636  	_, err = vm.RunProgram(prg)
   637  	return err
   638  }
   639  
   640  func (ctx *tc39TestCtx) runTC39Script(name, src string, includes []string, vm *Runtime) (err error, early bool) {
   641  	early = true
   642  	err = ctx.runFile(ctx.base, path.Join("harness", "assert.js"), vm)
   643  	if err != nil {
   644  		return
   645  	}
   646  
   647  	err = ctx.runFile(ctx.base, path.Join("harness", "sta.js"), vm)
   648  	if err != nil {
   649  		return
   650  	}
   651  
   652  	for _, include := range includes {
   653  		err = ctx.runFile(ctx.base, path.Join("harness", include), vm)
   654  		if err != nil {
   655  			return
   656  		}
   657  	}
   658  
   659  	var p *Program
   660  	p, err = Compile(name, src, false)
   661  
   662  	if err != nil {
   663  		return
   664  	}
   665  
   666  	early = false
   667  	_, err = vm.RunProgram(p)
   668  
   669  	return
   670  }
   671  
   672  func (ctx *tc39TestCtx) runTC39Tests(name string) {
   673  	files, err := os.ReadDir(path.Join(ctx.base, name))
   674  	if err != nil {
   675  		ctx.t.Fatal(err)
   676  	}
   677  
   678  	for _, file := range files {
   679  		if file.Name()[0] == '.' {
   680  			continue
   681  		}
   682  		if file.IsDir() {
   683  			ctx.runTC39Tests(path.Join(name, file.Name()))
   684  		} else {
   685  			fileName := file.Name()
   686  			if strings.HasSuffix(fileName, ".js") && !strings.HasSuffix(fileName, "_FIXTURE.js") {
   687  				name := path.Join(name, fileName)
   688  				ctx.runTest(name, func(t *testing.T) {
   689  					ctx.runTC39File(name, t)
   690  				})
   691  			}
   692  		}
   693  	}
   694  
   695  }
   696  
   697  func TestTC39(t *testing.T) {
   698  	if testing.Short() {
   699  		t.Skip()
   700  	}
   701  
   702  	if _, err := os.Stat(tc39BASE); err != nil {
   703  		t.Skipf("If you want to run tc39 tests, download them from https://github.com/tc39/test262 and put into %s. See .tc39_test262_checkout.sh for the latest working commit id. (%v)", tc39BASE, err)
   704  	}
   705  
   706  	ctx := &tc39TestCtx{
   707  		base: tc39BASE,
   708  	}
   709  	ctx.init()
   710  	//ctx.enableBench = true
   711  
   712  	t.Run("tc39", func(t *testing.T) {
   713  		ctx.t = t
   714  		//ctx.runTC39File("test/language/types/number/8.5.1.js", t)
   715  		ctx.runTC39Tests("test/language")
   716  		ctx.runTC39Tests("test/built-ins")
   717  		ctx.runTC39Tests("test/annexB/built-ins/String/prototype/substr")
   718  		ctx.runTC39Tests("test/annexB/built-ins/String/prototype/trimLeft")
   719  		ctx.runTC39Tests("test/annexB/built-ins/String/prototype/trimRight")
   720  		ctx.runTC39Tests("test/annexB/built-ins/escape")
   721  		ctx.runTC39Tests("test/annexB/built-ins/unescape")
   722  		ctx.runTC39Tests("test/annexB/built-ins/RegExp")
   723  
   724  		ctx.flush()
   725  	})
   726  
   727  	if ctx.enableBench {
   728  		sort.Slice(ctx.benchmark, func(i, j int) bool {
   729  			return ctx.benchmark[i].duration > ctx.benchmark[j].duration
   730  		})
   731  		bench := ctx.benchmark
   732  		if len(bench) > 50 {
   733  			bench = bench[:50]
   734  		}
   735  		for _, item := range bench {
   736  			fmt.Printf("%s\t%d\n", item.name, item.duration/time.Millisecond)
   737  		}
   738  	}
   739  }