github.com/evanw/esbuild@v0.21.4/scripts/test262.js (about)

     1  const vm = require('vm')
     2  const fs = require('fs')
     3  const path = require('path')
     4  const jsYaml = require('js-yaml')
     5  const isatty = require('tty').isatty(process.stdout.fd)
     6  const { installForTests } = require('./esbuild')
     7  
     8  const testDir = path.join(__dirname, '..', 'demo', 'test262', 'test')
     9  const harnessDir = path.join(__dirname, '..', 'demo', 'test262', 'harness')
    10  
    11  const progressBarLength = 64
    12  const eraseProgressBar = () => {
    13    previousProgressBar = null
    14    let text = `\r${' '.repeat(progressBarLength)}\r`
    15    if (printNewlineWhenErasing) {
    16      printNewlineWhenErasing = false
    17      text += '\n'
    18    }
    19    return text
    20  }
    21  let previousProgressBar = null
    22  let printNewlineWhenErasing = false
    23  
    24  const resetColor = isatty ? `\x1b[0m` : ''
    25  const boldColor = isatty ? `\x1b[1m` : ''
    26  const dimColor = isatty ? `\x1b[37m` : ''
    27  const underlineColor = isatty ? `\x1b[4m` : ''
    28  
    29  const redColor = isatty ? `\x1b[31m` : ''
    30  const greenColor = isatty ? `\x1b[32m` : ''
    31  const blueColor = isatty ? `\x1b[34m` : ''
    32  
    33  const cyanColor = isatty ? `\x1b[36m` : ''
    34  const magentaColor = isatty ? `\x1b[35m` : ''
    35  const yellowColor = isatty ? `\x1b[33m` : ''
    36  
    37  const redBgRedColor = isatty ? `\x1b[41;31m` : ''
    38  const redBgWhiteColor = isatty ? `\x1b[41;97m` : ''
    39  const greenBgGreenColor = isatty ? `\x1b[42;32m` : ''
    40  const greenBgWhiteColor = isatty ? `\x1b[42;97m` : ''
    41  const blueBgBlueColor = isatty ? `\x1b[44;34m` : ''
    42  const blueBgWhiteColor = isatty ? `\x1b[44;97m` : ''
    43  
    44  const cyanBgCyanColor = isatty ? `\x1b[46;36m` : ''
    45  const cyanBgBlackColor = isatty ? `\x1b[46;30m` : ''
    46  const magentaBgMagentaColor = isatty ? `\x1b[45;35m` : ''
    47  const magentaBgBlackColor = isatty ? `\x1b[45;30m` : ''
    48  const yellowBgYellowColor = isatty ? `\x1b[43;33m` : ''
    49  const yellowBgBlackColor = isatty ? `\x1b[43;30m` : ''
    50  
    51  const whiteBgWhiteColor = isatty ? `\x1b[107;97m` : ''
    52  const whiteBgBlackColor = isatty ? `\x1b[107;30m` : ''
    53  
    54  const skipTheseFeatures = new Set([
    55    'regexp-v-flag',
    56    'regexp-match-indices',
    57    'regexp-named-groups',
    58    'regexp-unicode-property-escapes',
    59  ])
    60  
    61  const skipTheseTests = new Set([
    62    // Skip these tests because esbuild deliberately always supports ESM syntax
    63    // in all files. Also esbuild doesn't support the script goal at all.
    64    'language/expressions/import.meta/syntax/goal-script.js',
    65    'language/global-code/export.js',
    66    'language/global-code/import.js',
    67  
    68    // Skip these tests because we deliberately support top-level return (input
    69    // files are treated as CommonJS and/or ESM but never as global code, and
    70    // top-level return is allowed in CommonJS)
    71    'language/statements/return/S12.9_A1_T1.js',
    72    'language/statements/return/S12.9_A1_T10.js',
    73    'language/statements/return/S12.9_A1_T2.js',
    74    'language/statements/return/S12.9_A1_T3.js',
    75    'language/statements/return/S12.9_A1_T4.js',
    76    'language/statements/return/S12.9_A1_T5.js',
    77    'language/statements/return/S12.9_A1_T6.js',
    78    'language/statements/return/S12.9_A1_T7.js',
    79    'language/statements/return/S12.9_A1_T8.js',
    80    'language/statements/return/S12.9_A1_T9.js',
    81    'language/global-code/return.js',
    82  
    83    // Skip these tests because we deliberately support parsing top-level await
    84    // in all files. Files containing top-level await are always interpreted as
    85    // ESM, never as CommonJS.
    86    'language/expressions/assignmenttargettype/simple-basic-identifierreference-await.js',
    87    'language/expressions/await/await-BindingIdentifier-in-global.js',
    88    'language/expressions/await/await-in-global.js',
    89    'language/expressions/await/await-in-nested-function.js',
    90    'language/expressions/await/await-in-nested-generator.js',
    91    'language/expressions/class/class-name-ident-await-escaped.js',
    92    'language/expressions/class/class-name-ident-await.js',
    93    'language/expressions/class/static-init-await-reference.js',
    94    'language/expressions/dynamic-import/2nd-param-await-ident.js',
    95    'language/expressions/dynamic-import/assignment-expression/await-identifier.js',
    96    'language/expressions/function/static-init-await-reference.js',
    97    'language/expressions/generators/static-init-await-reference.js',
    98    'language/expressions/in/private-field-rhs-await-absent.js',
    99    'language/expressions/object/identifier-shorthand-await-strict-mode.js',
   100    'language/expressions/object/method-definition/static-init-await-reference-accessor.js',
   101    'language/expressions/object/method-definition/static-init-await-reference-generator.js',
   102    'language/expressions/object/method-definition/static-init-await-reference-normal.js',
   103    'language/module-code/top-level-await/new-await-script-code.js',
   104    'language/reserved-words/await-script.js',
   105    'language/statements/class/class-name-ident-await-escaped.js',
   106    'language/statements/class/class-name-ident-await.js',
   107    'language/statements/labeled/value-await-non-module-escaped.js',
   108    'language/statements/labeled/value-await-non-module.js',
   109  
   110    // Skip these tests because we don't currently validate the contents of
   111    // regular expressions. We could do this but it's not necessary to parse
   112    // JavaScript successfully since we parse enough of it to be able to
   113    // determine where the regular expression ends (just "\" and "[]" pairs).
   114    'language/literals/regexp/early-err-pattern.js',
   115    'language/literals/regexp/invalid-braced-quantifier-exact.js',
   116    'language/literals/regexp/invalid-braced-quantifier-lower.js',
   117    'language/literals/regexp/invalid-braced-quantifier-range.js',
   118    'language/literals/regexp/invalid-optional-lookbehind.js',
   119    'language/literals/regexp/invalid-optional-negative-lookbehind.js',
   120    'language/literals/regexp/invalid-range-lookbehind.js',
   121    'language/literals/regexp/invalid-range-negative-lookbehind.js',
   122    'language/literals/regexp/u-invalid-class-escape.js',
   123    'language/literals/regexp/u-invalid-extended-pattern-char.js',
   124    'language/literals/regexp/u-invalid-identity-escape.js',
   125    'language/literals/regexp/u-invalid-legacy-octal-escape.js',
   126    'language/literals/regexp/u-invalid-non-empty-class-ranges-no-dash-a.js',
   127    'language/literals/regexp/u-invalid-non-empty-class-ranges-no-dash-ab.js',
   128    'language/literals/regexp/u-invalid-non-empty-class-ranges-no-dash-b.js',
   129    'language/literals/regexp/u-invalid-non-empty-class-ranges.js',
   130    'language/literals/regexp/u-invalid-oob-decimal-escape.js',
   131    'language/literals/regexp/u-invalid-optional-lookahead.js',
   132    'language/literals/regexp/u-invalid-optional-lookbehind.js',
   133    'language/literals/regexp/u-invalid-optional-negative-lookahead.js',
   134    'language/literals/regexp/u-invalid-optional-negative-lookbehind.js',
   135    'language/literals/regexp/u-invalid-range-lookahead.js',
   136    'language/literals/regexp/u-invalid-range-lookbehind.js',
   137    'language/literals/regexp/u-invalid-range-negative-lookahead.js',
   138    'language/literals/regexp/u-invalid-range-negative-lookbehind.js',
   139    'language/literals/regexp/u-unicode-esc-bounds.js',
   140    'language/literals/regexp/u-unicode-esc-non-hex.js',
   141    'language/literals/regexp/unicode-escape-nls-err.js',
   142  ])
   143  
   144  const skipEvaluatingTheseIncludes = new Set([
   145    'nativeFunctionMatcher.js', // We don't preserve "toString()" on functions
   146  ])
   147  
   148  const skipEvaluatingTheseFeatures = new Set([
   149    // Node's version of V8 doesn't implement these
   150    'hashbang',
   151    'legacy-regexp',
   152    'regexp-duplicate-named-groups',
   153    'symbols-as-weakmap-keys',
   154    'tail-call-optimization',
   155  
   156    // We don't care about API-related things
   157    'ArrayBuffer',
   158    'change-array-by-copy',
   159    'DataView',
   160    'resizable-arraybuffer',
   161    'ShadowRealm',
   162    'SharedArrayBuffer',
   163    'String.prototype.toWellFormed',
   164    'Symbol.match',
   165    'Symbol.replace',
   166    'Symbol.unscopables',
   167    'Temporal',
   168    'TypedArray',
   169  ])
   170  
   171  const skipEvaluatingTheseTests = new Set([
   172    // This test is skipped because it crashes V8:
   173    //
   174    //   #
   175    //   # Fatal error in , line 0
   176    //   # Check failed: i < self->length().
   177    //   #
   178    //   #
   179    //   #
   180    //   #FailureMessage Object: 0x7fffac9a1c30
   181    //    1: 0xc2abf1  [node]
   182    //    2: 0x1f231d4 V8_Fatal(char const*, ...) [node]
   183    //    3: 0xdac209 v8::FixedArray::Get(v8::Local<v8::Context>, int) const [node]
   184    //    4: 0xb74ab7  [node]
   185    //    5: 0xb76a2d  [node]
   186    //    6: 0xf2436f v8::internal::Isolate::RunHostImportModuleDynamicallyCallback(v8::internal::MaybeHandle<v8::internal::Script>,
   187    //                v8::internal::Handle<v8::internal::Object>, v8::internal::MaybeHandle<v8::internal::Object>) [node]
   188    //    7: 0x137e3db v8::internal::Runtime_DynamicImportCall(int, unsigned long*, v8::internal::Isolate*) [node]
   189    //    8: 0x17fd4f4  [node]
   190    //
   191    'language/expressions/dynamic-import/import-assertions/2nd-param-assert-enumeration.js',
   192  ])
   193  
   194  function findFiles() {
   195    function visit(dir) {
   196      for (const entry of fs.readdirSync(dir)) {
   197        const fullEntry = path.join(dir, entry)
   198        const stats = fs.statSync(fullEntry)
   199        if (stats.isDirectory()) {
   200          visit(fullEntry)
   201        } else if (stats.isFile() && entry.endsWith('.js') && !entry.includes('_FIXTURE')) {
   202          files.push(fullEntry)
   203        }
   204      }
   205    }
   206  
   207    const files = []
   208    for (const entry of fs.readdirSync(testDir)) {
   209      if (entry === 'staging' || entry === 'intl402' || entry === 'built-ins') continue // We're not interested in these
   210      visit(path.join(testDir, entry))
   211    }
   212  
   213    // Reverse for faster iteration times because many of the more interesting tests come last
   214    return files.reverse()
   215  }
   216  
   217  async function checkTransformAPI({ esbuild, file, content, yaml }) {
   218    if (yaml.flags) {
   219      if (yaml.flags.includes('onlyStrict')) content = '"use strict";' + content
   220      if (yaml.flags.includes('module')) content += '\nexport {}'
   221    }
   222  
   223    // Step 1: Try transforming the file normally
   224    const shouldParse = !yaml.negative || yaml.negative.phase !== 'parse'
   225    let result
   226    try {
   227      result = await esbuild.transform(content, { sourcefile: file })
   228    } catch (error) {
   229      if (shouldParse) {
   230        error.kind = 'Transform'
   231        throw error
   232      }
   233      return // Stop now if this test is supposed to fail
   234    }
   235    if (!shouldParse) {
   236      const error = new Error('Unexpected successful transform')
   237      error.kind = 'Transform'
   238      throw error
   239    }
   240  
   241    // Step 2: Try transforming the output again (this should always succeed)
   242    let result2
   243    try {
   244      result2 = await esbuild.transform(result.code, { sourcefile: file })
   245    } catch (error) {
   246      error.kind = 'Reparse'
   247      throw error
   248    }
   249  
   250    // Step 3: The output should be the same the second time
   251    if (result2.code !== result.code) {
   252      const lines = result.code.split('\n')
   253      const lines2 = result2.code.split('\n')
   254      let i = 0
   255      while (i < lines.length && i < lines2.length && lines[i] === lines2[i]) i++
   256      const error = { toString: () => `${redColor}-${lines[i]}\n${greenColor}+${lines2[i]}${resetColor}` }
   257      error.kind = 'Reprint'
   258      throw error
   259    }
   260  
   261    // Step 4: Try minifying the output once
   262    let result4
   263    try {
   264      result4 = await esbuild.transform(result2.code, { sourcefile: file, minify: true })
   265    } catch (error) {
   266      error.kind = 'Minify'
   267      throw error
   268    }
   269  
   270    // Step 5: Try minifying the output again
   271    let result5
   272    try {
   273      result5 = await esbuild.transform(result4.code, { sourcefile: file, minify: true })
   274    } catch (error) {
   275      error.kind = 'Minify'
   276      throw error
   277    }
   278  }
   279  
   280  async function checkBuildAPI({ esbuild, file, content, yaml }) {
   281    const plugins = []
   282    if (yaml.flags) {
   283      const isOnlyStrict = yaml.flags.includes('onlyStrict')
   284      const isModule = yaml.flags.includes('module')
   285      if (isOnlyStrict || isModule) {
   286        plugins.push({
   287          name: 'modify',
   288          setup(build) {
   289            build.onLoad({ filter: /./ }, args => {
   290              if (args.path === file) {
   291                let loaded = content
   292                if (isOnlyStrict) loaded = '"use strict";' + loaded
   293                if (isModule) loaded += '\nexport {}'
   294                return { contents: loaded }
   295              }
   296            })
   297          },
   298        })
   299      }
   300    }
   301  
   302    // Step 1: Try building the file normally
   303    const isModule = yaml.flags && yaml.flags.includes('module')
   304    const isDynamicImport = yaml.flags && yaml.flags.includes('dynamic-import')
   305    const isAsync = yaml.flags && yaml.flags.includes('async')
   306    const shouldParse = !yaml.negative || yaml.negative.phase === 'runtime'
   307    let result
   308    try {
   309      const options = {
   310        entryPoints: [file],
   311        write: false,
   312        keepNames: true,
   313        logLevel: 'silent',
   314        plugins,
   315        target: 'node' + process.version.slice(1),
   316        logOverride: { 'direct-eval': 'warning' },
   317      }
   318      if (isModule || isDynamicImport || isAsync) {
   319        options.bundle = true
   320        options.format = isModule ? 'esm' : 'iife'
   321        options.external = [
   322          '', // Some tests use this as a dummy argument to an "import('')" that is never evaluated
   323        ]
   324      }
   325      result = await esbuild.build(options)
   326    } catch (error) {
   327      if (shouldParse) {
   328        error.kind = 'Build'
   329        throw error
   330      }
   331      return // Stop now if this test is supposed to fail
   332    }
   333    if (!shouldParse) {
   334      const error = new Error('Unexpected successful build')
   335      error.kind = 'Build'
   336      throw error
   337    }
   338  
   339    // Don't evaluate problematic files
   340    const hasDirectEval = result.warnings.some(msg => msg.id === 'direct-eval')
   341    if (
   342      hasDirectEval ||
   343      skipEvaluatingTheseTests.has(path.relative(testDir, file)) ||
   344      (yaml.includes && yaml.includes.some(include => skipEvaluatingTheseIncludes.has(include))) ||
   345      (yaml.features && yaml.features.some(feature => skipEvaluatingTheseFeatures.has(feature)))
   346    ) {
   347      return
   348    }
   349  
   350    // Step 3: Try evaluating the file using node
   351    const importDir = path.dirname(file)
   352    const shouldEvaluate = !yaml.negative
   353    try {
   354      await runCodeInHarness(yaml, content, importDir)
   355    } catch (error) {
   356      // Ignore tests that fail when run in node
   357      if (shouldEvaluate) console.log(eraseProgressBar() + dimColor + `IGNORING ${path.relative(testDir, file)}: ${error}` + resetColor)
   358      return
   359    }
   360    if (!shouldEvaluate) {
   361      // Ignore tests that incorrectly pass when run in node
   362      return
   363    }
   364  
   365    // Step 3: Try evaluating the file we generated
   366    const code = result.outputFiles[0].text
   367    try {
   368      await runCodeInHarness(yaml, code, importDir)
   369    } catch (error) {
   370      if (shouldEvaluate) {
   371        if (typeof error === 'string') error = new Error(error)
   372        error.kind = 'Evaluate'
   373        throw error
   374      }
   375      return // Stop now if this test is supposed to fail
   376    }
   377    if (!shouldEvaluate) {
   378      const error = new Error('Unexpected successful evaluation')
   379      error.kind = 'Evaluate'
   380      throw error
   381    }
   382  
   383    // Step 4: If evaluation worked and was supposed to work, check to see
   384    // if it still works after running esbuild's syntax lowering pass on it
   385    for (let version = 2015; version <= 2022; version++) {
   386      let result
   387      try {
   388        result = await esbuild.transform(code, { sourcefile: file, target: `es${version}` })
   389      } catch (error) {
   390        continue // This means esbuild doesn't support lowering this code to this old a version
   391      }
   392      try {
   393        await runCodeInHarness(yaml, code, importDir)
   394      } catch (error) {
   395        if (typeof error === 'string') error = new Error(error)
   396        error.kind = 'Lower'
   397        throw error
   398      }
   399      break
   400    }
   401  }
   402  
   403  async function main() {
   404    const startTime = Date.now()
   405  
   406    // Get this warning out of the way so it's not in the middle of our output.
   407    // Note: If this constructor is missing, you need to run node with the
   408    // "--experimental-vm-modules" flag (and use a version of node that has it).
   409    {
   410      const temp = new vm.SourceTextModule('')
   411      await temp.link(() => { throw new Error })
   412      await temp.evaluate()
   413    }
   414  
   415    console.log(`\n${dimColor}Finding tests...${resetColor}`)
   416    const files = findFiles()
   417    console.log(`Found ${files.length} test files`)
   418  
   419    console.log(`\n${dimColor}Installing esbuild...${resetColor}`)
   420    const esbuild = installForTests()
   421  
   422    console.log(`\n${dimColor}Running tests...${resetColor}\n`)
   423    const errorCounts = {}
   424    let skippedCount = 0
   425  
   426    await forEachInParallel(files, 32, async (file) => {
   427      // Don't parse files that esbuild deliberately handles differently
   428      if (skipTheseTests.has(path.relative(testDir, file))) {
   429        skippedCount++
   430        return
   431      }
   432  
   433      try {
   434        const content = fs.readFileSync(file, 'utf8')
   435        const start = content.indexOf('/*---')
   436        const end = content.indexOf('---*/')
   437        if (start < 0 || end < 0) throw new Error(`Missing YAML metadata`)
   438        const yaml = jsYaml.safeLoad(content.slice(start + 5, end))
   439  
   440        // Don't parse files that test things we don't care about
   441        if (yaml.features && yaml.features.some(feature => skipTheseFeatures.has(feature))) {
   442          skippedCount++
   443          return
   444        }
   445  
   446        await checkTransformAPI({ esbuild, file, content, yaml })
   447        await checkBuildAPI({ esbuild, file, content, yaml })
   448      }
   449  
   450      catch (error) {
   451        errorCounts[error.kind] = (errorCounts[error.kind] || 0) + 1
   452        printError(file, error)
   453      }
   454    })
   455  
   456    const table = []
   457    table.push(['Total tests', `${files.length}`])
   458    table.push(['Tests ran', `${files.length - skippedCount}`])
   459    table.push(['Tests skipped', `${skippedCount}`])
   460    for (const kind of Object.keys(errorCounts).sort()) {
   461      table.push([kind + ' errors', `${errorCounts[kind]}`])
   462    }
   463    const seconds = (Date.now() - startTime) / 1000
   464    const minutes = Math.floor(seconds / 60)
   465    table.push(['Time taken', `${minutes ? `${minutes} min ${+(seconds - minutes * 60).toFixed(1)} sec` : `${+seconds.toFixed(1)} sec`}`])
   466    const maxLength = Math.max(...table.map(x => x[0].length))
   467    printNewlineWhenErasing = true
   468    process.stdout.write(eraseProgressBar())
   469    for (const [key, value] of table) {
   470      console.log(`${boldColor}${(key + ':').padEnd(maxLength + 1)}${resetColor} ${value}`)
   471    }
   472  }
   473  
   474  function forEachInParallel(items, batchSize, callback) {
   475    return new Promise((resolve, reject) => {
   476      let inFlight = 0
   477      let i = 0
   478  
   479      function next() {
   480        if (i === items.length && inFlight === 0) {
   481          process.stdout.write(eraseProgressBar())
   482          return resolve()
   483        }
   484  
   485        const completed = Math.floor(progressBarLength * i / items.length)
   486        if (previousProgressBar !== completed) {
   487          previousProgressBar = completed
   488          const progressHead = '\u2501'.repeat(Math.max(0, completed - 1))
   489          const progressBoundary = completed ? '\u252B' : ''
   490          const progressTail = '\u2500'.repeat(progressBarLength - completed)
   491          process.stdout.write(`\r` + greenColor + progressHead + progressBoundary + dimColor + progressTail + resetColor)
   492        }
   493  
   494        while (i < items.length && inFlight < batchSize) {
   495          inFlight++
   496          callback(items[i++]).then(() => {
   497            inFlight--
   498            next()
   499          }, reject)
   500        }
   501      }
   502  
   503      next()
   504    })
   505  }
   506  
   507  const harnessFiles = new Map
   508  let defaultHarness = ''
   509  
   510  for (const entry of fs.readdirSync(harnessDir)) {
   511    if (entry.startsWith('.') || !entry.endsWith('.js')) {
   512      continue
   513    }
   514    const file = path.join(harnessDir, entry)
   515    const content = fs.readFileSync(file, 'utf8')
   516    if (entry === 'assert.js' || entry === 'sta.js') {
   517      defaultHarness += content
   518      continue
   519    }
   520    harnessFiles.set(entry, content)
   521  }
   522  
   523  function createHarnessForTest(yaml) {
   524    let harness = defaultHarness
   525  
   526    if (yaml.includes) {
   527      for (const include of yaml.includes) {
   528        const content = harnessFiles.get(include)
   529        if (!content) throw new Error(`Included file is missing: ${include}`)
   530        harness += content
   531      }
   532    }
   533  
   534    return harness
   535  }
   536  
   537  async function runCodeInHarness(yaml, code, importDir) {
   538    const context = {}
   539    const isAsync = yaml.flags && yaml.flags.includes('async')
   540    const isModule = yaml.flags && yaml.flags.includes('module')
   541    const isRaw = yaml.flags && yaml.flags.includes('raw')
   542  
   543    // See: https://github.com/nodejs/node/issues/36351
   544    const unique = () => '//' + Math.random()
   545  
   546    const runCode = async () => {
   547      const moduleCache = new Map
   548      const dynamicImportCache = new Map
   549  
   550      const findModule = (modulePath) => {
   551        let module = moduleCache.get(modulePath)
   552        if (!module) {
   553          const code = fs.readFileSync(modulePath, 'utf8')
   554          if (modulePath.endsWith('json')) {
   555            const evaluate = function () {
   556              this.setExport('default', vm.runInContext('JSON.parse', context)(code))
   557            }
   558            module = new vm.SyntheticModule(['default'], evaluate, { context })
   559          } else {
   560            module = new vm.SourceTextModule(code + unique(), { context, importModuleDynamically })
   561          }
   562          moduleCache.set(modulePath, module)
   563        }
   564        return module
   565      }
   566  
   567      const linker = (specifier, referencingModule) => {
   568        return findModule(path.join(importDir, specifier))
   569      }
   570  
   571      const importModuleDynamically = (specifier, script) => {
   572        const where = path.join(importDir, specifier)
   573        let promise = dynamicImportCache.get(where)
   574        if (!promise) {
   575          const module = findModule(where, context)
   576          if (module.status === 'unlinked') {
   577            promise = module.link(linker)
   578              .then(() => module.evaluate())
   579              .then(() => module)
   580          } else {
   581            promise = Promise.resolve(module)
   582          }
   583          dynamicImportCache.set(where, promise)
   584        }
   585        return promise
   586      }
   587  
   588      vm.createContext(context)
   589      if (!isRaw) vm.runInContext(createHarnessForTest(yaml), context)
   590  
   591      if (isModule) {
   592        const module = new vm.SourceTextModule(code + unique(), { context, importModuleDynamically })
   593        await module.link(linker)
   594        await module.evaluate()
   595      } else {
   596        const script = new vm.Script(code, { importModuleDynamically })
   597        script.runInContext(context)
   598      }
   599    }
   600  
   601    if (isAsync) {
   602      await new Promise((resolve, reject) => {
   603        context.$DONE = err => err ? reject(err) : resolve()
   604        runCode(code, context).catch(reject)
   605      })
   606    } else {
   607      await runCode(code, context)
   608    }
   609  }
   610  
   611  function printError(file, error) {
   612    let detail
   613  
   614    if (error.errors) {
   615      const { text, location } = error.errors[0]
   616      if (location) {
   617        const { file, line, column, lineText, length } = location
   618        detail = '  ' + dimColor + path.basename(file) + ':' + line + ':' + column + ': ' + resetColor + text + '\n' +
   619          '  ' + dimColor + lineText.slice(0, column) + greenColor + lineText.slice(column, column + length) + dimColor + lineText.slice(column + length) + resetColor + '\n' +
   620          '  ' + greenColor + ' '.repeat(column) + (length > 1 ? '~'.repeat(length) : '^') + resetColor
   621      } else {
   622        detail = dimColor + ('\n' + text).split('\n').join('\n  ').slice(1) + resetColor
   623      }
   624    } else {
   625      detail = dimColor + ('\n' + error).split('\n').join('\n  ').slice(1) + resetColor
   626    }
   627  
   628    const prettyPath = path.relative(testDir, file)
   629    printNewlineWhenErasing = true
   630    console.log(eraseProgressBar() + tagMap[error.kind] + ' ' + prettyPath + '\n' + detail)
   631    printNewlineWhenErasing = true
   632  }
   633  
   634  const tagMap = {
   635    Transform: redBgRedColor + `[` + redBgWhiteColor + `TRANSFORM ERROR` + redBgRedColor + `]` + resetColor,
   636    Build: magentaBgMagentaColor + `[` + magentaBgBlackColor + `BUILD ERROR` + magentaBgMagentaColor + `]` + resetColor,
   637    Reparse: yellowBgYellowColor + `[` + yellowBgBlackColor + `REPARSE ERROR` + yellowBgYellowColor + `]` + resetColor,
   638    Reprint: cyanBgCyanColor + `[` + cyanBgBlackColor + `REPRINT ERROR` + cyanBgCyanColor + `]` + resetColor,
   639    Minify: blueBgBlueColor + `[` + blueBgWhiteColor + `MINIFY ERROR` + blueBgBlueColor + `]` + resetColor,
   640    Evaluate: greenBgGreenColor + `[` + greenBgWhiteColor + `EVALUATE ERROR` + greenBgGreenColor + `]` + resetColor,
   641    Lower: whiteBgWhiteColor + `[` + whiteBgBlackColor + `LOWER ERROR` + whiteBgWhiteColor + `]` + resetColor,
   642  }
   643  
   644  process.on('unhandledRejection', () => {
   645    // Don't exit when a test does this
   646  })
   647  
   648  main().catch(e => setTimeout(() => {
   649    throw e
   650  }))