{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import fs from 'node:fs';\n\nexport interface ReporterOptions {\n  /** Path of the JSON report file to write. Defaults to `jasmine.json`. */\n  file?: string;\n  /** Pretty-print the JSON output. Defaults to `true`. */\n  beautify?: boolean;\n  /** Indentation width used when `beautify` is enabled. Defaults to `4`. */\n  indentationLevel?: number;\n}\n\ninterface FailedExpectation {\n  message: string;\n}\n\ninterface SpecResult {\n  status: string;\n  fullName: string;\n  description: string;\n  failedExpectations: FailedExpectation[];\n}\n\ninterface SpecEntry {\n  duration: number;\n  time: number;\n  title: string;\n  fullTitle: string;\n  error?: string;\n}\n\ninterface Stats {\n  suites: number;\n  tests: number;\n  passes: number;\n  pending: number;\n  failures: number;\n  start: number | Date;\n  end: number | Date;\n  duration: number;\n  time: number;\n}\n\ninterface Report {\n  stats: Stats;\n  failures: SpecEntry[];\n  passes: SpecEntry[];\n  skipped: SpecEntry[];\n  start?: unknown;\n}\n\nfunction format(result: SpecResult): string {\n  const msg: string[] = [];\n  let counter = 1;\n  let formatted: string;\n\n  if (result.failedExpectations.length === 1) {\n    formatted = '1 Failure: ';\n  } else {\n    formatted = `${result.failedExpectations.length} Failures: `;\n  }\n\n  result.failedExpectations.forEach(function iterator(expectation) {\n    msg.push(`${counter}. : ${expectation.message}`);\n    counter++;\n  });\n\n  formatted += msg.join('\\n');\n\n  return formatted;\n}\n\nfunction shallowMerge(\n  obj1: Record<string, unknown>,\n  obj2: Record<string, unknown>,\n): Record<string, unknown> {\n  const mergedObj: Record<string, unknown> = {};\n\n  Object.keys(obj1).forEach(function iterator(key) {\n    if (!obj2[key]) {\n      mergedObj[key] = obj1[key];\n    } else {\n      mergedObj[key] = obj2[key];\n    }\n  });\n\n  return mergedObj;\n}\n\nfunction sleep(ms: number): void {\n  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n}\n\n// Cross-process advisory lock using an exclusive-create lock file (O_EXCL), the\n// same primitive the former `lockfile` dependency used internally. Polls until\n// the lock is acquired or `waitMs` elapses.\nfunction acquireLock(lockPath: string, waitMs: number): boolean {\n  const deadline = Date.now() + waitMs;\n  for (;;) {\n    try {\n      fs.closeSync(fs.openSync(lockPath, 'wx'));\n      return true;\n    } catch (err) {\n      if ((err as NodeJS.ErrnoException).code !== 'EEXIST') {\n        throw err;\n      }\n      if (Date.now() >= deadline) {\n        return false;\n      }\n      sleep(100);\n    }\n  }\n}\n\nfunction releaseLock(lockPath: string): void {\n  try {\n    fs.unlinkSync(lockPath);\n  } catch (err) {\n    if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n      throw err;\n    }\n  }\n}\n\nexport default class Reporter {\n  private options: Required<ReporterOptions>;\n  private spec: Partial<SpecEntry>;\n  private specStart: number | Date;\n  private output: Report;\n\n  constructor(opts?: ReporterOptions) {\n    const defaultOpts = {\n      file: 'jasmine.json',\n      beautify: true,\n      indentationLevel: 4,\n    };\n    this.options = shallowMerge(\n      defaultOpts,\n      typeof opts === 'object' ? (opts as Record<string, unknown>) : {},\n    ) as Required<ReporterOptions>;\n\n    this.spec = {};\n    this.specStart = 0;\n    this.output = {\n      stats: {\n        suites: 0,\n        tests: 0,\n\n        passes: 0,\n        pending: 0,\n        failures: 0,\n\n        start: 0, // this is the overall time for everything. - all suites.\n        end: 0,\n        duration: 0,\n        time: 0, // bamboo + mocha seem to be using \"time\", not duration.\n      },\n      failures: [],\n      passes: [],\n      skipped: [],\n    };\n  }\n\n  suiteDone(): void {\n    this.output.stats.suites++;\n  }\n\n  specStarted(): void {\n    this.specStart = new Date();\n  }\n\n  specDone(result: SpecResult): void {\n    this.spec.duration = Math.floor(\n      (Date.now() - (this.specStart as Date).getTime()) / 1000,\n    );\n    this.spec.time = this.spec.duration;\n    this.spec.title = result.fullName;\n    this.spec.fullTitle = result.description;\n\n    this.output.stats.tests++;\n\n    if (result.status === 'passed') {\n      this.output.stats.passes++;\n      this.output.passes.push(this.spec as SpecEntry);\n    } else if (result.status === 'failed') {\n      this.output.stats.failures++;\n      this.spec.error = format(result);\n      this.output.failures.push(this.spec as SpecEntry);\n    } else {\n      this.output.stats.pending++;\n      this.output.skipped.push(this.spec as SpecEntry);\n    }\n\n    this.spec = {};\n  }\n\n  jasmineStarted(): void {\n    this.output.stats.start = new Date();\n  }\n\n  mergeOutput(previous: Report): void {\n    this.output.stats.suites = this.output.stats.suites + previous.stats.suites;\n    this.output.stats.tests = this.output.stats.tests + previous.stats.tests;\n\n    this.output.stats.passes = this.output.stats.passes + previous.stats.passes;\n    this.output.stats.pending =\n      this.output.stats.pending + previous.stats.pending;\n    this.output.stats.failures =\n      this.output.stats.failures + previous.stats.failures;\n\n    this.output.start = previous.start;\n    // retain output.end from this run, not the previous run.\n    // math on duration will be computed elsewhere\n    this.output.failures = this.output.failures.concat(previous.failures);\n    this.output.passes = this.output.passes.concat(previous.passes);\n    this.output.skipped = this.output.skipped.concat(previous.skipped);\n  }\n\n  // jasmineDone gets called multiple times, once for each process (thread) we are executing. Normally this would\n  // just be one thread, but protractor can \"shard\" tests which really just runs multiple processes with one\n  // to rule them all.\n  //\n  // Since that code is running in separate processes, it isn't easy to use a global/static class variable to\n  // handle a single instance of the jasmine reporter - nor is it easy to aggregate the results. So the trick here\n  // is to lock, read any file that exists, merge, write results to file, and then release lock.\n  jasmineDone(): void {\n    const lockname = `${this.options.file}.lock`;\n\n    this.output.stats.end = new Date();\n\n    if (!acquireLock(lockname, 2000)) {\n      console.error(\n        `Jasmine bamboo reporter unable to acquire a file lock for ${lockname}`,\n      );\n      return;\n    }\n    // we can ensure that no other process will update the results causing a write-afte-read hazard.\n\n    try {\n      if (fs.existsSync(this.options.file)) {\n        const raw = fs.readFileSync(this.options.file, { encoding: 'utf8' });\n        const previous = JSON.parse(raw);\n        this.mergeOutput(previous);\n      }\n      this.output.stats.duration = Math.floor(\n        ((this.output.stats.end as Date).getTime() -\n          (this.output.stats.start as Date).getTime()) /\n          1000,\n      );\n      this.output.stats.time = this.output.stats.duration;\n      const resultsOutput = this.options.beautify\n        ? JSON.stringify(this.output, null, this.options.indentationLevel)\n        : JSON.stringify(this.output);\n      fs.writeFileSync(this.options.file, resultsOutput);\n    } finally {\n      try {\n        releaseLock(lockname);\n      } catch {\n        console.error(\n          `Jasmine bamboo reporter could not unlock file ${lockname}`,\n        );\n      }\n    }\n  }\n}\n"],"mappings":"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","names":[]}