UNPKG

jasmine-bamboo-reporter

Version:

A reporter for Jasmine which produces a report compatible with Atlassian Bamboo Mocha Test Parser.

1 lines 11.3 kB
{"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":[]}