@allurereport/core 3.19.0 → 3.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -11,4 +11,4 @@ export * from "./resolutions.js";
11
11
  export { resolveConfig, readConfig, readRawConfig, getPluginInstance } from "./config.js";
12
12
  export * from "./report.js";
13
13
  export * from "./plugin.js";
14
- export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, maxDurationRule, allTestsContainEnvRule, environmentsTestedRule, metricMaxRule, metricMinRule, metricMaxDeltaRule, metricMaxDeltaPercentRule, convertQualityGateResultsToTestErrors, filterFailedQualityGateResults, stringifyQualityGateResults, } from "./qualityGate/index.js";
14
+ export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, newTestsRule, successRateRule, maxDurationRule, allTestsContainEnvRule, environmentsTestedRule, metricMaxRule, metricMinRule, metricMaxDeltaRule, metricMaxDeltaPercentRule, convertQualityGateResultsToTestErrors, filterFailedQualityGateResults, stringifyQualityGateResults, } from "./qualityGate/index.js";
package/dist/index.js CHANGED
@@ -10,4 +10,4 @@ export * from "./resolutions.js";
10
10
  export { resolveConfig, readConfig, readRawConfig, getPluginInstance } from "./config.js";
11
11
  export * from "./report.js";
12
12
  export * from "./plugin.js";
13
- export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, maxDurationRule, allTestsContainEnvRule, environmentsTestedRule, metricMaxRule, metricMinRule, metricMaxDeltaRule, metricMaxDeltaPercentRule, convertQualityGateResultsToTestErrors, filterFailedQualityGateResults, stringifyQualityGateResults, } from "./qualityGate/index.js";
13
+ export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, newTestsRule, successRateRule, maxDurationRule, allTestsContainEnvRule, environmentsTestedRule, metricMaxRule, metricMinRule, metricMaxDeltaRule, metricMaxDeltaPercentRule, convertQualityGateResultsToTestErrors, filterFailedQualityGateResults, stringifyQualityGateResults, } from "./qualityGate/index.js";
@@ -7,6 +7,7 @@ type MetricRuleConfig = {
7
7
  };
8
8
  export declare const maxFailuresRule: QualityGateRule<number>;
9
9
  export declare const minTestsCountRule: QualityGateRule<number>;
10
+ export declare const newTestsRule: QualityGateRule<boolean, number>;
10
11
  export declare const successRateRule: QualityGateRule<number>;
11
12
  export declare const maxDurationRule: QualityGateRule<number>;
12
13
  export declare const allTestsContainEnvRule: QualityGateRule<string, number>;
@@ -15,5 +16,5 @@ export declare const metricMaxRule: QualityGateRule<MetricRuleConfig>;
15
16
  export declare const metricMinRule: QualityGateRule<MetricRuleConfig>;
16
17
  export declare const metricMaxDeltaRule: QualityGateRule<MetricRuleConfig>;
17
18
  export declare const metricMaxDeltaPercentRule: QualityGateRule<MetricRuleConfig>;
18
- export declare const qualityGateDefaultRules: (QualityGateRule<number> | QualityGateRule<string, number> | QualityGateRule<string[]> | QualityGateRule<MetricRuleConfig>)[];
19
+ export declare const qualityGateDefaultRules: (QualityGateRule<number> | QualityGateRule<boolean, number> | QualityGateRule<string, number> | QualityGateRule<string[]> | QualityGateRule<MetricRuleConfig>)[];
19
20
  export {};
@@ -106,6 +106,25 @@ export const minTestsCountRule = {
106
106
  };
107
107
  },
108
108
  };
109
+ export const newTestsRule = {
110
+ rule: "newTests",
111
+ message: ({ actual, expected }) => expected
112
+ ? `No new tests were found; expected at least one new test`
113
+ : `The number of new tests ${bold(String(actual))} does not match the expected policy`,
114
+ successMessage: ({ actual }) => `New tests were found: ${bold(String(actual))}`,
115
+ validate: async ({ trs, expected, state }) => {
116
+ const previous = numberStateValue(state.getResult());
117
+ const newTrs = trs.filter((tr) => tr.transition === "new");
118
+ const testResults = newTrs.map((tr) => tr.id);
119
+ const actual = previous + newTrs.length;
120
+ state.setResult(actual, testResults);
121
+ return {
122
+ success: !expected || actual > 0,
123
+ actual,
124
+ testResults,
125
+ };
126
+ },
127
+ };
109
128
  export const successRateRule = {
110
129
  rule: "successRate",
111
130
  message: ({ actual, expected }) => `Success rate ${bold(formatQualityGateNumber(actual))} is less, than expected ${bold(formatQualityGateNumber(expected))}`,
@@ -237,6 +256,7 @@ export const metricMaxDeltaPercentRule = {
237
256
  export const qualityGateDefaultRules = [
238
257
  maxFailuresRule,
239
258
  minTestsCountRule,
259
+ newTestsRule,
240
260
  successRateRule,
241
261
  maxDurationRule,
242
262
  allTestsContainEnvRule,
package/dist/report.js CHANGED
@@ -14,7 +14,7 @@ import console from "node:console";
14
14
  import { randomUUID } from "node:crypto";
15
15
  import { once } from "node:events";
16
16
  import { createReadStream, createWriteStream, existsSync, readFileSync } from "node:fs";
17
- import { lstat, mkdtemp, readdir, realpath, rename, rm, writeFile } from "node:fs/promises";
17
+ import { lstat, mkdtemp, readdir, rename, rm, writeFile } from "node:fs/promises";
18
18
  import { tmpdir } from "node:os";
19
19
  import { basename, dirname, join, resolve } from "node:path";
20
20
  import { promisify } from "node:util";
@@ -22,7 +22,7 @@ import { detect } from "@allurereport/ci";
22
22
  import { createProgressLogger } from "@allurereport/cli-commons";
23
23
  import { normalizeCategoriesConfig, parseIntegerConfigValue } from "@allurereport/core-api";
24
24
  import { AllureStoreDumpFiles, createTestResultRegistry, } from "@allurereport/plugin-api";
25
- import { allure1, allure2, attachments, cucumberjson, junitXml, readXcResultBundle } from "@allurereport/reader";
25
+ import { allure1, allure2, attachments, cucumberjson, cucumberMessages, junitXml, readXcResultBundle, } from "@allurereport/reader";
26
26
  import { PathResultFile } from "@allurereport/reader-api";
27
27
  import { AllureRemoteHistory, AllureServiceClient, AllureTestOpsClient, KnownError, UnknownError, } from "@allurereport/service";
28
28
  import { generateSummary } from "@allurereport/summary";
@@ -37,7 +37,7 @@ import { writeKnownIssues } from "./resolutions.js";
37
37
  import { DefaultAllureStore } from "./store/store.js";
38
38
  import { ARTIFACTS_MANIFEST_FILENAME, createReportArtifact, deduplicateDumpInputs, } from "./utils/artifacts.js";
39
39
  import { environmentIdentityById, environmentIdentityByName } from "./utils/environment.js";
40
- import { getPerfMetricsResults, isPerfMetricsEnabled, measurePerf, PERF_METRIC_NAMES, PERF_METRIC_PREFIXES, perfMetricsFileName, startPerfSpan, writePerfMetrics, } from "./utils/perf.js";
40
+ import { getPerfMetricsResults, incrementPerfCounter, isPerfMetricsEnabled, measurePerfAggregate, measurePerf, PERF_METRIC_NAMES, PERF_METRIC_PREFIXES, perfMetricsFileName, startPerfSpan, writePerfMetrics, } from "./utils/perf.js";
41
41
  import { RealtimeChannel } from "./utils/realtimeChannel.js";
42
42
  import { RealtimeUpdateScheduler } from "./utils/realtimeUpdateScheduler.js";
43
43
  import { resolveDumpAttachmentPath, UnsafeDumpPathError } from "./utils/safeDumpPath.js";
@@ -53,6 +53,11 @@ const ROOT_INTEGRATION_FILENAMES = new Set([
53
53
  QUALITY_GATE_RESULTS_FILENAME,
54
54
  ARTIFACTS_MANIFEST_FILENAME,
55
55
  ]);
56
+ const WORKLOAD_PERF_METRIC = {
57
+ group: "workload",
58
+ groupTitle: "Workload",
59
+ better: "neutral",
60
+ };
56
61
  const readConcurrency = () => {
57
62
  const parsed = Number.parseInt(process.env.ALLURE_READ_CONCURRENCY ?? "", 10);
58
63
  if (!Number.isFinite(parsed)) {
@@ -76,9 +81,7 @@ const getExecutorReportUrl = (executor) => {
76
81
  return undefined;
77
82
  }
78
83
  try {
79
- const navUrl = new URL(reportUrl);
80
- navUrl.pathname = navUrl.pathname.endsWith("/") ? navUrl.pathname : `${navUrl.pathname}/`;
81
- return navUrl.toString();
84
+ return new URL(reportUrl).toString();
82
85
  }
83
86
  catch {
84
87
  return undefined;
@@ -298,19 +301,23 @@ export class AllureReport {
298
301
  throw new Error(INIT_REQUIRED_ERROR_MESSAGE);
299
302
  }
300
303
  const resultsDirPath = resolve(resultsDir);
301
- if (await readXcResultBundle(__classPrivateFieldGet(this, _AllureReport_store, "f"), resultsDirPath)) {
304
+ if (await measurePerf(PERF_METRIC_NAMES.generateReadResultsXcresultCheck, () => readXcResultBundle(__classPrivateFieldGet(this, _AllureReport_store, "f"), resultsDirPath))) {
302
305
  __classPrivateFieldGet(this, _AllureReport_store, "f").updateHistoryFlags();
303
306
  return;
304
307
  }
305
308
  try {
306
- const entries = (await readdir(resultsDirPath, { withFileTypes: true }))
309
+ const entries = await measurePerf(PERF_METRIC_NAMES.generateReadResultsReaddir, async () => (await readdir(resultsDirPath, { withFileTypes: true }))
307
310
  .filter((dirent) => dirent.isFile() && !dirent.name.endsWith(".tmp"))
308
- .sort((a, b) => a.name.localeCompare(b.name));
311
+ .sort((a, b) => a.name.localeCompare(b.name)));
309
312
  const limit = pLimit(readConcurrency());
313
+ incrementPerfCounter(PERF_METRIC_NAMES.generateReadResultsFiles, entries.length, {
314
+ ...WORKLOAD_PERF_METRIC,
315
+ title: "Input files",
316
+ unit: "files",
317
+ });
310
318
  await Promise.all(entries.map((dirent) => limit(async () => {
311
319
  try {
312
- const path = await realpath(join(resultsDirPath, dirent.name));
313
- await this.readResult(new PathResultFile(path, dirent.name));
320
+ await this.readResult(new PathResultFile(join(resultsDirPath, dirent.name), dirent.name));
314
321
  }
315
322
  catch (e) {
316
323
  console.error(`can't read result file ${dirent.name}`, e);
@@ -338,7 +345,7 @@ export class AllureReport {
338
345
  if (reader.matches && !(await reader.matches(data))) {
339
346
  continue;
340
347
  }
341
- const processed = await reader.read(__classPrivateFieldGet(this, _AllureReport_store, "f"), data);
348
+ const processed = await measurePerfAggregate(PERF_METRIC_NAMES.generateReadResultsReaderRead, () => reader.read(__classPrivateFieldGet(this, _AllureReport_store, "f"), data));
342
349
  if (processed) {
343
350
  return;
344
351
  }
@@ -457,7 +464,7 @@ export class AllureReport {
457
464
  const addJsonDumpEntry = async (entryName, value) => {
458
465
  await addRequiredDumpEntry(Buffer.from(JSON.stringify(value)), entryName);
459
466
  };
460
- const dumpJsonEntries = ({ testResults, testCases, fixtures, attachments, environments, reportVariables, checkResults = {}, globalAttachmentIds = [], globalErrors = [], indexAttachmentByTestResult = {}, indexTestResultByRetryHash = {}, indexTestResultByTestCase = {}, indexTestResultByResolutionIssue = {}, indexAttachmentByFixture = {}, indexFixturesByTestResult = {}, resolutionIssues = {}, qualityGateResults = [], testResultIdsIngestOrder = [], metrics = [], }) => [
467
+ const dumpJsonEntries = ({ testResults, testCases, fixtures, attachments, environments, reportVariables, metadata = {}, checkResults = {}, globalAttachmentIds = [], globalErrors = [], indexAttachmentByTestResult = {}, indexTestResultByRetryHash = {}, indexTestResultByTestCase = {}, indexTestResultByResolutionIssue = {}, indexAttachmentByFixture = {}, indexFixturesByTestResult = {}, resolutionIssues = {}, qualityGateResults = [], testResultIdsIngestOrder = [], metrics = [], }) => [
461
468
  [AllureStoreDumpFiles.TestResults, testResults],
462
469
  [AllureStoreDumpFiles.TestCases, testCases],
463
470
  [AllureStoreDumpFiles.Fixtures, fixtures],
@@ -465,6 +472,7 @@ export class AllureReport {
465
472
  [AllureStoreDumpFiles.CheckResults, checkResults],
466
473
  [AllureStoreDumpFiles.Environments, environments],
467
474
  [AllureStoreDumpFiles.ReportVariables, reportVariables],
475
+ [AllureStoreDumpFiles.Metadata, metadata],
468
476
  [AllureStoreDumpFiles.ResolutionIssues, resolutionIssues],
469
477
  [AllureStoreDumpFiles.GlobalAttachments, globalAttachmentIds],
470
478
  [AllureStoreDumpFiles.GlobalErrors, globalErrors],
@@ -613,6 +621,7 @@ export class AllureReport {
613
621
  const checkResultsEntry = await optionalEntryData(AllureStoreDumpFiles.CheckResults);
614
622
  const environmentsEntry = await requiredEntryData(AllureStoreDumpFiles.Environments);
615
623
  const reportVariablesEntry = await requiredEntryData(AllureStoreDumpFiles.ReportVariables);
624
+ const metadataEntry = await optionalEntryData(AllureStoreDumpFiles.Metadata);
616
625
  const globalAttachmentsEntry = await requiredEntryData(AllureStoreDumpFiles.GlobalAttachments);
617
626
  const globalErrorsEntry = await requiredEntryData(AllureStoreDumpFiles.GlobalErrors);
618
627
  const indexAttachmentsEntry = await requiredEntryData(AllureStoreDumpFiles.IndexAttachmentsByTestResults);
@@ -633,6 +642,7 @@ export class AllureReport {
633
642
  case AllureStoreDumpFiles.Fixtures:
634
643
  case AllureStoreDumpFiles.Environments:
635
644
  case AllureStoreDumpFiles.ReportVariables:
645
+ case AllureStoreDumpFiles.Metadata:
636
646
  case AllureStoreDumpFiles.ResolutionIssues:
637
647
  case AllureStoreDumpFiles.GlobalAttachments:
638
648
  case AllureStoreDumpFiles.GlobalErrors:
@@ -663,6 +673,7 @@ export class AllureReport {
663
673
  checkResults: checkResultsEntry ? JSON.parse(checkResultsEntry.toString("utf8")) : [],
664
674
  environments: JSON.parse(environmentsEntry.toString("utf8")),
665
675
  reportVariables: JSON.parse(reportVariablesEntry.toString("utf8")),
676
+ metadata: metadataEntry ? JSON.parse(metadataEntry.toString("utf8")) : {},
666
677
  globalAttachmentIds: JSON.parse(globalAttachmentsEntry.toString("utf8")),
667
678
  globalErrors: JSON.parse(globalErrorsEntry.toString("utf8")),
668
679
  indexAttachmentByTestResult: JSON.parse(indexAttachmentsEntry.toString("utf8")),
@@ -978,6 +989,11 @@ export class AllureReport {
978
989
  await pluginState.set("files", {});
979
990
  }
980
991
  const pluginFiles = new PluginFiles(__classPrivateFieldGet(this, _AllureReport_reportFiles, "f"), id, async (key, filepath) => {
992
+ incrementPerfCounter(`${PERF_METRIC_PREFIXES.generatePlugin}${id}.generatedFiles`, 1, {
993
+ ...WORKLOAD_PERF_METRIC,
994
+ title: `Generated ${id} files`,
995
+ unit: "files",
996
+ });
981
997
  const currentPluginState = __classPrivateFieldGet(this, _AllureReport_instances, "m", _AllureReport_getPluginState).call(this, false, id);
982
998
  const files = await currentPluginState?.get("files");
983
999
  if (!files) {
@@ -994,6 +1010,14 @@ export class AllureReport {
994
1010
  hideLabels: __classPrivateFieldGet(this, _AllureReport_hideLabels, "f"),
995
1011
  state: pluginState,
996
1012
  reportFiles: pluginFiles,
1013
+ ...(isPerfMetricsEnabled()
1014
+ ? {
1015
+ perf: {
1016
+ measure: (name, fn) => measurePerf(`${PERF_METRIC_PREFIXES.generatePlugin}${id}.${name}`, fn),
1017
+ count: (name, value, metadata) => incrementPerfCounter(`${PERF_METRIC_PREFIXES.generatePlugin}${id}.${name}`, value, metadata),
1018
+ },
1019
+ }
1020
+ : {}),
997
1021
  reportUrl: this.reportUrl,
998
1022
  realTime: !!__classPrivateFieldGet(this, _AllureReport_realTime, "f"),
999
1023
  output: __classPrivateFieldGet(this, _AllureReport_output, "f"),
@@ -1013,7 +1037,7 @@ export class AllureReport {
1013
1037
  }
1014
1038
  }
1015
1039
  });
1016
- const { name, readers = [allure1, allure2, cucumberjson, junitXml, attachments], plugins = [], resolutions, reportFiles, realTime, historyPath, historyBaseUrl, historyLimit, flakyDetection, appendHistory, defaultLabels = {}, variables = {}, environment, allowedEnvironments, environments, output, hideLabels, qualityGate, performance, dump, categories, allureService, globalAttachments, cwd, } = opts;
1040
+ const { name, readers = [allure1, allure2, cucumberjson, cucumberMessages, junitXml, attachments], plugins = [], resolutions, reportFiles, realTime, historyPath, historyBaseUrl, historyLimit, flakyDetection, appendHistory, defaultLabels = {}, variables = {}, environment, allowedEnvironments, environments, output, hideLabels, qualityGate, performance, dump, categories, allureService, globalAttachments, cwd, } = opts;
1017
1041
  const allureServiceAccessToken = allureService?.accessToken;
1018
1042
  if (allureServiceAccessToken) {
1019
1043
  const allureServiceClientConfig = {
@@ -1,4 +1,4 @@
1
- import type { ResolutionRule, ResolutionsConfig, TestResult } from "@allurereport/core-api";
1
+ import type { IssueResolutionRule, ResolutionIssue, ResolutionRule, ResolutionsConfig, TestResult } from "@allurereport/core-api";
2
2
  import type { AllureStore } from "@allurereport/plugin-api";
3
3
  export declare const DEFAULT_KNOWN_ISSUES_PATH = "known-issues.json";
4
4
  export declare const validateResolutionsConfig: (config?: ResolutionsConfig) => void;
@@ -12,5 +12,6 @@ export declare const KNOWN_FROM_RESULT_RESOLUTION: {
12
12
  };
13
13
  export declare const getResolutionByRules: (testResult: TestResult, config?: ResolutionsConfig) => ResolutionRule | undefined;
14
14
  export declare const isIgnoredFailure: (testResult: TestResult) => boolean;
15
+ export declare const createResolutionIssue: (rule: IssueResolutionRule, config?: ResolutionsConfig) => ResolutionIssue;
15
16
  export declare const resolveExactIssuesFilePath: (pathOrDir: string | undefined, label: string) => Promise<string | undefined>;
16
17
  export declare const writeKnownIssues: (store: AllureStore, knownIssuesPath?: string) => Promise<void>;
@@ -52,7 +52,7 @@ const validateIssueUrl = (rule, template, errors) => {
52
52
  }
53
53
  let url;
54
54
  try {
55
- url = new URL(template.urlTemplate.replace("%s", encodeURIComponent(rule.issue.id)));
55
+ url = new URL(template.urlTemplate.replace("%s", () => encodeURIComponent(rule.issue.id)));
56
56
  }
57
57
  catch {
58
58
  errors.push(`resolutions.links.${rule.issue.type}.urlTemplate must resolve to an absolute URL`);
@@ -155,6 +155,21 @@ export const getResolutionByRules = (testResult, config) => {
155
155
  return undefined;
156
156
  };
157
157
  export const isIgnoredFailure = (testResult) => testResult.resolution === "muted" || testResult.resolution === "accepted";
158
+ export const createResolutionIssue = (rule, config) => {
159
+ const template = config?.links?.[rule.issue.type];
160
+ const issue = { ...rule.issue, comment: rule.comment };
161
+ if (!template) {
162
+ return issue;
163
+ }
164
+ return {
165
+ ...issue,
166
+ link: {
167
+ name: template.nameTemplate?.replace("%s", () => rule.issue.id) ?? rule.issue.id,
168
+ url: template.urlTemplate.replace("%s", () => encodeURIComponent(rule.issue.id)),
169
+ type: rule.issue.type,
170
+ },
171
+ };
172
+ };
158
173
  export const resolveExactIssuesFilePath = async (pathOrDir, label) => {
159
174
  if (!pathOrDir)
160
175
  return undefined;
@@ -48,15 +48,18 @@ export class RetrySubstore {
48
48
  });
49
49
  }
50
50
  retriesByTr(testResult) {
51
- if (!testResult.retryHash || testResult.isRetry) {
51
+ if (!testResult.retryHash) {
52
52
  return NO_RETRIES;
53
53
  }
54
54
  const attempts = __classPrivateFieldGet(this, _RetrySubstore_testResultsByRetryHash, "f").get(testResult.retryHash) ?? [];
55
+ if (attempts.length <= 1) {
56
+ return NO_RETRIES;
57
+ }
55
58
  const index = attempts.findIndex((attempt) => attempt.id === testResult.id);
56
- if (index !== 0) {
59
+ if (index === -1) {
57
60
  return NO_RETRIES;
58
61
  }
59
- return attempts.slice(1);
62
+ return attempts.filter((attempt) => attempt.id !== testResult.id);
60
63
  }
61
64
  reset() {
62
65
  __classPrivateFieldGet(this, _RetrySubstore_testResultsByRetryHash, "f").clear();
@@ -13,10 +13,11 @@ var _DefaultAllureStore_instances, _DefaultAllureStore_testResults, _DefaultAllu
13
13
  import { extname } from "node:path";
14
14
  import { DEFAULT_ENVIRONMENT, DEFAULT_ENVIRONMENT_IDENTITY, calculateEnvironmentHash, calculateParametersHash, calculateRetryHash, calculateTestCaseHash, compareBy, createDictionary, fallbackTestCaseIdLabelName, getWorstStatus, createHistoryTestResultLookup, normalizeHistoryDataPoint, ordinal, reverse, resolveMetricSamples, validateEnvironmentId, validateEnvironmentName, } from "@allurereport/core-api";
15
15
  import { md5, } from "@allurereport/plugin-api";
16
- import { getResolutionByRules, isIgnoredFailure } from "../resolutions.js";
16
+ import { createResolutionIssue, getResolutionByRules, isIgnoredFailure } from "../resolutions.js";
17
17
  import { environmentIdentityById, normalizeEnvironmentDescriptorMap, resolveEnvironmentIdentity, resolveStoredEnvironmentIdentity, validateAllowedEnvironmentId, } from "../utils/environment.js";
18
18
  import { createFlakyDetector } from "../utils/flaky.js";
19
19
  import { getStatusTransition } from "../utils/new.js";
20
+ import { measurePerfAggregateSync, PERF_METRIC_NAMES } from "../utils/perf.js";
20
21
  import { testFixtureResultRawToState, testResultRawToState } from "./convert.js";
21
22
  import { RetrySubstore } from "./retrySubstore.js";
22
23
  const index = (indexMap, key, ...items) => {
@@ -348,58 +349,71 @@ export class DefaultAllureStore {
348
349
  }
349
350
  async visitTestResult(raw, context) {
350
351
  const attachmentLinks = [];
351
- const testResult = testResultRawToState({
352
+ const testResult = measurePerfAggregateSync(PERF_METRIC_NAMES.storeVisitTestResultConvert, () => testResultRawToState({
352
353
  testCases: __classPrivateFieldGet(this, _DefaultAllureStore_testCases, "f"),
353
354
  attachments: __classPrivateFieldGet(this, _DefaultAllureStore_attachments, "f"),
354
355
  visitAttachmentLink: (link) => attachmentLinks.push(link),
355
- }, raw, context);
356
- const defaultLabelsNames = Object.keys(__classPrivateFieldGet(this, _DefaultAllureStore_defaultLabels, "f"));
357
- if (defaultLabelsNames.length) {
358
- defaultLabelsNames.forEach((labelName) => {
359
- if (!testResult.labels.find((label) => label.name === labelName)) {
360
- const defaultLabelValue = __classPrivateFieldGet(this, _DefaultAllureStore_defaultLabels, "f")[labelName];
361
- [].concat(defaultLabelValue).forEach((labelValue) => {
362
- testResult.labels.push({
363
- name: labelName,
364
- value: labelValue,
356
+ }, raw, context));
357
+ measurePerfAggregateSync(PERF_METRIC_NAMES.storeVisitTestResultDefaultLabels, () => {
358
+ const defaultLabelsNames = Object.keys(__classPrivateFieldGet(this, _DefaultAllureStore_defaultLabels, "f"));
359
+ if (defaultLabelsNames.length) {
360
+ defaultLabelsNames.forEach((labelName) => {
361
+ if (!testResult.labels.find((label) => label.name === labelName)) {
362
+ const defaultLabelValue = __classPrivateFieldGet(this, _DefaultAllureStore_defaultLabels, "f")[labelName];
363
+ [].concat(defaultLabelValue).forEach((labelValue) => {
364
+ testResult.labels.push({
365
+ name: labelName,
366
+ value: labelValue,
367
+ });
365
368
  });
366
- });
367
- }
369
+ }
370
+ });
371
+ }
372
+ });
373
+ const environmentIdentity = measurePerfAggregateSync(PERF_METRIC_NAMES.storeVisitTestResultEnvironment, () => {
374
+ const environmentMatcherLabels = testResult.labels.filter(({ name }) => name !== "ALLURE_ID" && name !== fallbackTestCaseIdLabelName);
375
+ const environmentMatch = __classPrivateFieldGet(this, _DefaultAllureStore_environment, "f")
376
+ ? undefined
377
+ : Object.entries(__classPrivateFieldGet(this, _DefaultAllureStore_environmentsConfig, "f")).find(([, { matcher }]) => matcher({ labels: environmentMatcherLabels }));
378
+ const namedEnvironmentIdentity = __classPrivateFieldGet(this, _DefaultAllureStore_environment, "f") ??
379
+ (environmentMatch
380
+ ? {
381
+ id: environmentMatch[0],
382
+ name: environmentMatch[1].name ?? environmentMatch[0],
383
+ }
384
+ : undefined);
385
+ const identity = namedEnvironmentIdentity ?? DEFAULT_ENVIRONMENT_IDENTITY;
386
+ testResult.environment = identity.id;
387
+ __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_addEnvironments).call(this, [identity]);
388
+ return identity;
389
+ });
390
+ measurePerfAggregateSync(PERF_METRIC_NAMES.storeVisitTestResultRetry, () => {
391
+ testResult.environmentHash = calculateEnvironmentHash(environmentIdentity.id);
392
+ testResult.retryHash = calculateRetryHash({
393
+ testCaseHash: testResult.testCaseHash,
394
+ parametersHash: testResult.parametersHash,
395
+ environmentHash: testResult.environmentHash,
368
396
  });
369
- }
370
- const environmentMatcherLabels = testResult.labels.filter(({ name }) => name !== "ALLURE_ID" && name !== fallbackTestCaseIdLabelName);
371
- const environmentMatch = __classPrivateFieldGet(this, _DefaultAllureStore_environment, "f")
372
- ? undefined
373
- : Object.entries(__classPrivateFieldGet(this, _DefaultAllureStore_environmentsConfig, "f")).find(([, { matcher }]) => matcher({ labels: environmentMatcherLabels }));
374
- const namedEnvironmentIdentity = __classPrivateFieldGet(this, _DefaultAllureStore_environment, "f") ??
375
- (environmentMatch
376
- ? {
377
- id: environmentMatch[0],
378
- name: environmentMatch[1].name ?? environmentMatch[0],
379
- }
380
- : undefined);
381
- const environmentIdentity = namedEnvironmentIdentity ?? DEFAULT_ENVIRONMENT_IDENTITY;
382
- testResult.environment = environmentIdentity.id;
383
- __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_addEnvironments).call(this, [environmentIdentity]);
384
- testResult.environmentHash = calculateEnvironmentHash(environmentIdentity.id);
385
- testResult.retryHash = calculateRetryHash({
386
- testCaseHash: testResult.testCaseHash,
387
- parametersHash: testResult.parametersHash,
388
- environmentHash: testResult.environmentHash,
389
397
  });
390
398
  if (__classPrivateFieldGet(this, _DefaultAllureStore_history, "f")) {
391
- const lookup = createHistoryTestResultLookup([testResult]);
392
- __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_applyHistoryFlags).call(this, testResult, __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_historyFor).call(this, testResult, lookup));
399
+ measurePerfAggregateSync(PERF_METRIC_NAMES.storeVisitTestResultHistory, () => {
400
+ const lookup = createHistoryTestResultLookup([testResult]);
401
+ __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_applyHistoryFlags).call(this, testResult, __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_historyFor).call(this, testResult, lookup));
402
+ });
393
403
  }
394
- __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_classifyResolution).call(this, testResult);
395
- __classPrivateFieldGet(this, _DefaultAllureStore_testResults, "f").set(testResult.id, testResult);
396
- __classPrivateFieldSet(this, _DefaultAllureStore_historyLookup, undefined, "f");
397
- __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_setTestResultEnvironmentId).call(this, testResult, environmentIdentity.id);
398
- __classPrivateFieldGet(this, _DefaultAllureStore_retrySubstore, "f").recordIngestOrder(testResult.id);
399
- __classPrivateFieldGet(this, _DefaultAllureStore_retrySubstore, "f").upsert(testResult);
400
- index(this.indexTestResultByTestCase, testResult.testCase?.id, testResult);
401
- index(this.indexTestResultByRetryHash, testResult.retryHash, testResult);
402
- index(this.indexAttachmentByTestResult, testResult.id, ...attachmentLinks);
404
+ measurePerfAggregateSync(PERF_METRIC_NAMES.storeVisitTestResultResolution, () => {
405
+ __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_classifyResolution).call(this, testResult);
406
+ });
407
+ measurePerfAggregateSync(PERF_METRIC_NAMES.storeVisitTestResultIndexes, () => {
408
+ __classPrivateFieldGet(this, _DefaultAllureStore_testResults, "f").set(testResult.id, testResult);
409
+ __classPrivateFieldSet(this, _DefaultAllureStore_historyLookup, undefined, "f");
410
+ __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_setTestResultEnvironmentId).call(this, testResult, environmentIdentity.id);
411
+ __classPrivateFieldGet(this, _DefaultAllureStore_retrySubstore, "f").recordIngestOrder(testResult.id);
412
+ __classPrivateFieldGet(this, _DefaultAllureStore_retrySubstore, "f").upsert(testResult);
413
+ index(this.indexTestResultByTestCase, testResult.testCase?.id, testResult);
414
+ index(this.indexTestResultByRetryHash, testResult.retryHash, testResult);
415
+ index(this.indexAttachmentByTestResult, testResult.id, ...attachmentLinks);
416
+ });
403
417
  __classPrivateFieldGet(this, _DefaultAllureStore_realtimeDispatcher, "f")?.sendTestResult(testResult.id);
404
418
  }
405
419
  async visitTestFixtureResult(result, context) {
@@ -416,28 +430,31 @@ export class DefaultAllureStore {
416
430
  __classPrivateFieldGet(this, _DefaultAllureStore_realtimeDispatcher, "f")?.sendTestFixtureResult(testFixtureResult.id);
417
431
  }
418
432
  async visitAttachmentFile(resultFile) {
419
- const originalFileName = resultFile.getOriginalFileName();
420
- const id = md5(originalFileName);
421
- __classPrivateFieldGet(this, _DefaultAllureStore_attachmentContents, "f").set(id, resultFile);
422
- const maybeLink = __classPrivateFieldGet(this, _DefaultAllureStore_attachments, "f").get(id);
423
- if (maybeLink) {
424
- const link = maybeLink;
425
- link.missed = false;
426
- link.ext = link.ext === undefined || link.ext === "" ? resultFile.getExtension() : link.ext;
427
- link.contentType = link.contentType ?? resultFile.getContentType();
428
- link.contentLength = resultFile.getContentLength();
429
- }
430
- else {
431
- __classPrivateFieldGet(this, _DefaultAllureStore_attachments, "f").set(id, {
432
- used: false,
433
- missed: false,
434
- id,
435
- originalFileName,
436
- ext: resultFile.getExtension(),
437
- contentType: resultFile.getContentType(),
438
- contentLength: resultFile.getContentLength(),
439
- });
440
- }
433
+ const { id, originalFileName } = measurePerfAggregateSync(PERF_METRIC_NAMES.storeVisitAttachmentFileMetadata, () => {
434
+ const originalFileName = resultFile.getOriginalFileName();
435
+ const id = md5(originalFileName);
436
+ __classPrivateFieldGet(this, _DefaultAllureStore_attachmentContents, "f").set(id, resultFile);
437
+ const maybeLink = __classPrivateFieldGet(this, _DefaultAllureStore_attachments, "f").get(id);
438
+ if (maybeLink) {
439
+ const link = maybeLink;
440
+ link.missed = false;
441
+ link.ext = link.ext === undefined || link.ext === "" ? resultFile.getExtension() : link.ext;
442
+ link.contentType = link.contentType ?? resultFile.getContentType();
443
+ link.contentLength = resultFile.getContentLength();
444
+ }
445
+ else {
446
+ __classPrivateFieldGet(this, _DefaultAllureStore_attachments, "f").set(id, {
447
+ used: false,
448
+ missed: false,
449
+ id,
450
+ originalFileName,
451
+ ext: resultFile.getExtension(),
452
+ contentType: resultFile.getContentType(),
453
+ contentLength: resultFile.getContentLength(),
454
+ });
455
+ }
456
+ return { id, originalFileName };
457
+ });
441
458
  for (const globalAttachmentId of [...__classPrivateFieldGet(this, _DefaultAllureStore_globalAttachmentIds, "f"), ...__classPrivateFieldGet(this, _DefaultAllureStore_processGlobalAttachmentIds, "f")]) {
442
459
  const globalAttachment = __classPrivateFieldGet(this, _DefaultAllureStore_attachments, "f").get(globalAttachmentId);
443
460
  if (!globalAttachment || globalAttachment.originalFileName !== originalFileName) {
@@ -874,6 +891,7 @@ export class DefaultAllureStore {
874
891
  fixtures: mapToObject(__classPrivateFieldGet(this, _DefaultAllureStore_fixtures, "f")),
875
892
  environments: __classPrivateFieldGet(this, _DefaultAllureStore_environments, "f"),
876
893
  reportVariables: __classPrivateFieldGet(this, _DefaultAllureStore_reportVariables, "f"),
894
+ metadata: mapToObject(__classPrivateFieldGet(this, _DefaultAllureStore_metadata, "f")),
877
895
  globalAttachmentIds: [...__classPrivateFieldGet(this, _DefaultAllureStore_globalAttachmentIds, "f"), ...__classPrivateFieldGet(this, _DefaultAllureStore_processGlobalAttachmentIds, "f")],
878
896
  globalErrors: [...__classPrivateFieldGet(this, _DefaultAllureStore_globalErrors, "f"), ...__classPrivateFieldGet(this, _DefaultAllureStore_processGlobalErrors, "f")],
879
897
  checkResults: mapToObject(__classPrivateFieldGet(this, _DefaultAllureStore_checkResultsById, "f")),
@@ -910,7 +928,7 @@ export class DefaultAllureStore {
910
928
  }
911
929
  async restoreState(stateDump, attachmentsContents = {}) {
912
930
  __classPrivateFieldSet(this, _DefaultAllureStore_historyLookup, undefined, "f");
913
- const { testResults, attachments, testCases, fixtures, reportVariables, environments, globalAttachmentIds = [], globalErrors = [], checkResults, indexAttachmentByTestResult = {}, indexAttachmentByFixture = {}, indexFixturesByTestResult = {}, qualityGateResults = [], metrics = [], testResultIdsIngestOrder = [], } = stateDump;
931
+ const { testResults, attachments, testCases, fixtures, reportVariables, metadata = {}, environments, globalAttachmentIds = [], globalErrors = [], checkResults, indexAttachmentByTestResult = {}, indexAttachmentByFixture = {}, indexFixturesByTestResult = {}, qualityGateResults = [], metrics = [], testResultIdsIngestOrder = [], } = stateDump;
914
932
  __classPrivateFieldGet(this, _DefaultAllureStore_resolutionIssues, "f").clear();
915
933
  __classPrivateFieldGet(this, _DefaultAllureStore_testResultIdsByResolutionIssueId, "f").clear();
916
934
  __classPrivateFieldGet(this, _DefaultAllureStore_resolutionIssueIdByTestResultId, "f").clear();
@@ -1021,6 +1039,7 @@ export class DefaultAllureStore {
1021
1039
  __classPrivateFieldGet(this, _DefaultAllureStore_globalErrors, "f").push(__classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_indexGlobalError).call(this, error));
1022
1040
  });
1023
1041
  Object.assign(__classPrivateFieldGet(this, _DefaultAllureStore_reportVariables, "f"), reportVariables);
1042
+ updateMapWithRecord(__classPrivateFieldGet(this, _DefaultAllureStore_metadata, "f"), metadata);
1024
1043
  Object.entries(indexAttachmentByTestResult).forEach(([trId, links]) => {
1025
1044
  const attachmentsLinks = links.map((id) => __classPrivateFieldGet(this, _DefaultAllureStore_attachments, "f").get(id)).filter(Boolean);
1026
1045
  if (attachmentsLinks.length === 0) {
@@ -1176,7 +1195,7 @@ _DefaultAllureStore_testResults = new WeakMap(), _DefaultAllureStore_attachments
1176
1195
  testResult.resolution = rule.resolution;
1177
1196
  testResult.resolutionComment = rule.comment;
1178
1197
  if (rule.resolution === "issue") {
1179
- __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_associateResolutionIssue).call(this, { ...rule.issue, comment: rule.comment }, testResult.id);
1198
+ __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_associateResolutionIssue).call(this, createResolutionIssue(rule, __classPrivateFieldGet(this, _DefaultAllureStore_resolutionsConfig, "f")), testResult.id);
1180
1199
  }
1181
1200
  }, _DefaultAllureStore_associateResolutionIssue = function _DefaultAllureStore_associateResolutionIssue(resolutionIssue, testResultId) {
1182
1201
  __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_removeResolutionIssueAssociation).call(this, testResultId);
@@ -1,18 +1,12 @@
1
1
  import type { AllurePerformanceResult } from "@allurereport/core-api";
2
2
  export declare const PERF_METRICS_FILE = "performance.json";
3
3
  export declare const perfMetricsFileName: (reportUuid: string) => string;
4
- export type PerfMetricSpan = {
5
- name: string;
6
- startTimeMs: number;
7
- durationMs: number;
8
- };
9
- export type PerfMetricSummary = {
10
- name: string;
11
- count: number;
12
- totalMs: number;
13
- minMs: number;
14
- maxMs: number;
15
- avgMs: number;
4
+ type PerfMetricMetadata = {
5
+ title?: string;
6
+ unit?: string;
7
+ group?: string;
8
+ groupTitle?: string;
9
+ better?: "neutral";
16
10
  };
17
11
  export declare const PERF_METRIC_NAMES: {
18
12
  readonly allureTotal: "allure.total";
@@ -22,17 +16,34 @@ export declare const PERF_METRIC_NAMES: {
22
16
  readonly restoreStateStoreRestore: "restoreState.storeRestore";
23
17
  readonly generateTotal: "generate.total";
24
18
  readonly generateReadResults: "generate.readResults";
19
+ readonly generateReadResultsXcresultCheck: "generate.readResults.xcresultCheck";
20
+ readonly generateReadResultsReaddir: "generate.readResults.readdir";
21
+ readonly generateReadResultsFiles: "generate.readResults.files";
22
+ readonly generateReadResultsReaderRead: "generate.readResults.reader.read";
25
23
  readonly generatePluginsDone: "generate.plugins.done";
24
+ readonly storeVisitTestResultConvert: "store.visitTestResult.convert";
25
+ readonly storeVisitTestResultDefaultLabels: "store.visitTestResult.defaultLabels";
26
+ readonly storeVisitTestResultEnvironment: "store.visitTestResult.environment";
27
+ readonly storeVisitTestResultRetry: "store.visitTestResult.retry";
28
+ readonly storeVisitTestResultHistory: "store.visitTestResult.history";
29
+ readonly storeVisitTestResultResolution: "store.visitTestResult.resolution";
30
+ readonly storeVisitTestResultIndexes: "store.visitTestResult.indexes";
31
+ readonly storeVisitAttachmentFileMetadata: "store.visitAttachmentFile.metadata";
26
32
  readonly publishUploadTotal: "publish.upload.total";
27
33
  readonly summaryGenerate: "summary.generate";
28
34
  };
29
35
  export declare const PERF_METRIC_PREFIXES: {
36
+ readonly generatePlugin: "generate.plugin.";
30
37
  readonly generatePluginDone: "generate.plugin.done.";
31
38
  readonly publishUploadPlugin: "publish.upload.plugin.";
32
39
  };
33
40
  export declare const isPerfMetricsEnabled: () => boolean;
34
41
  export declare const startPerfSpan: (name: string) => (() => void);
35
42
  export declare const measurePerf: <T>(name: string, fn: () => Promise<T>) => Promise<T>;
43
+ export declare const measurePerfAggregate: <T>(name: string, fn: () => Promise<T>) => Promise<T>;
44
+ export declare const measurePerfAggregateSync: <T>(name: string, fn: () => T) => T;
45
+ export declare const incrementPerfCounter: (name: string, value?: number, metadata?: PerfMetricMetadata) => void;
36
46
  export declare const getPerfMetricsResults: () => AllurePerformanceResult[];
37
47
  export declare const writePerfMetrics: (output: string, fileName?: string) => Promise<boolean>;
38
48
  export declare const resetPerfMetrics: () => void;
49
+ export {};
@@ -7,6 +7,8 @@ export const perfMetricsFileName = (reportUuid) => `${reportUuid}-perf.json`;
7
7
  const MARK_PREFIX = "allure:perf:";
8
8
  const ENABLED_VALUES = new Set(["1", "true", "yes", "on"]);
9
9
  const SPANS = [];
10
+ const AGGREGATES = new Map();
11
+ const COUNTERS = new Map();
10
12
  const MARKS = new Set();
11
13
  const MEASURES = new Set();
12
14
  let sequence = 0;
@@ -19,28 +21,45 @@ export const PERF_METRIC_NAMES = {
19
21
  restoreStateStoreRestore: "restoreState.storeRestore",
20
22
  generateTotal: "generate.total",
21
23
  generateReadResults: "generate.readResults",
24
+ generateReadResultsXcresultCheck: "generate.readResults.xcresultCheck",
25
+ generateReadResultsReaddir: "generate.readResults.readdir",
26
+ generateReadResultsFiles: "generate.readResults.files",
27
+ generateReadResultsReaderRead: "generate.readResults.reader.read",
22
28
  generatePluginsDone: "generate.plugins.done",
29
+ storeVisitTestResultConvert: "store.visitTestResult.convert",
30
+ storeVisitTestResultDefaultLabels: "store.visitTestResult.defaultLabels",
31
+ storeVisitTestResultEnvironment: "store.visitTestResult.environment",
32
+ storeVisitTestResultRetry: "store.visitTestResult.retry",
33
+ storeVisitTestResultHistory: "store.visitTestResult.history",
34
+ storeVisitTestResultResolution: "store.visitTestResult.resolution",
35
+ storeVisitTestResultIndexes: "store.visitTestResult.indexes",
36
+ storeVisitAttachmentFileMetadata: "store.visitAttachmentFile.metadata",
23
37
  publishUploadTotal: "publish.upload.total",
24
38
  summaryGenerate: "summary.generate",
25
39
  };
26
40
  export const PERF_METRIC_PREFIXES = {
41
+ generatePlugin: "generate.plugin.",
27
42
  generatePluginDone: "generate.plugin.done.",
28
43
  publishUploadPlugin: "publish.upload.plugin.",
29
44
  };
30
- const getCoveredDurationSummary = () => {
31
- if (SPANS.length === 0) {
45
+ const getCoveredTiming = () => {
46
+ const timings = [
47
+ ...SPANS.map(({ startTimeMs, durationMs }) => ({
48
+ startTimeMs,
49
+ stopTimeMs: startTimeMs + durationMs,
50
+ })),
51
+ ...AGGREGATES.values(),
52
+ ...COUNTERS.values(),
53
+ ];
54
+ if (timings.length === 0) {
32
55
  return undefined;
33
56
  }
34
- const startTimeMs = Math.min(...SPANS.map(({ startTimeMs }) => startTimeMs));
35
- const endTimeMs = Math.max(...SPANS.map(({ startTimeMs, durationMs }) => startTimeMs + durationMs));
36
- const durationMs = round(endTimeMs - startTimeMs);
57
+ const startTimeMs = Math.min(...timings.map(({ startTimeMs }) => startTimeMs));
58
+ const stopTimeMs = Math.max(...timings.map(({ stopTimeMs }) => stopTimeMs));
37
59
  return {
38
- name: PERF_METRIC_NAMES.allureTotal,
39
- count: 1,
40
- totalMs: durationMs,
41
- minMs: durationMs,
42
- maxMs: durationMs,
43
- avgMs: durationMs,
60
+ startTimeMs,
61
+ stopTimeMs,
62
+ durationMs: round(stopTimeMs - startTimeMs),
44
63
  };
45
64
  };
46
65
  export const isPerfMetricsEnabled = () => ENABLED_VALUES.has((process.env.ALLURE_PERF_METRICS ?? "").toLowerCase());
@@ -91,8 +110,80 @@ export const measurePerf = async (name, fn) => {
91
110
  end();
92
111
  }
93
112
  };
113
+ const recordPerfTiming = (name, durationMs, startTimeMs) => {
114
+ if (!isPerfMetricsEnabled()) {
115
+ return;
116
+ }
117
+ const current = AGGREGATES.get(name);
118
+ if (!current) {
119
+ AGGREGATES.set(name, {
120
+ name,
121
+ count: 1,
122
+ totalMs: durationMs,
123
+ startTimeMs,
124
+ stopTimeMs: startTimeMs + durationMs,
125
+ });
126
+ return;
127
+ }
128
+ current.count += 1;
129
+ current.totalMs += durationMs;
130
+ current.startTimeMs = Math.min(current.startTimeMs, startTimeMs);
131
+ current.stopTimeMs = Math.max(current.stopTimeMs, startTimeMs + durationMs);
132
+ };
133
+ export const measurePerfAggregate = async (name, fn) => {
134
+ if (!isPerfMetricsEnabled()) {
135
+ return fn();
136
+ }
137
+ const startTimeMs = performance.now();
138
+ try {
139
+ return await fn();
140
+ }
141
+ finally {
142
+ recordPerfTiming(name, performance.now() - startTimeMs, startTimeMs);
143
+ }
144
+ };
145
+ export const measurePerfAggregateSync = (name, fn) => {
146
+ if (!isPerfMetricsEnabled()) {
147
+ return fn();
148
+ }
149
+ const startTimeMs = performance.now();
150
+ try {
151
+ return fn();
152
+ }
153
+ finally {
154
+ recordPerfTiming(name, performance.now() - startTimeMs, startTimeMs);
155
+ }
156
+ };
157
+ export const incrementPerfCounter = (name, value = 1, metadata) => {
158
+ if (!isPerfMetricsEnabled()) {
159
+ return;
160
+ }
161
+ const timeMs = performance.now();
162
+ const current = COUNTERS.get(name);
163
+ if (!current) {
164
+ COUNTERS.set(name, {
165
+ name,
166
+ value,
167
+ startTimeMs: timeMs,
168
+ stopTimeMs: timeMs,
169
+ ...(metadata ? { metadata } : {}),
170
+ });
171
+ return;
172
+ }
173
+ current.value += value;
174
+ current.stopTimeMs = timeMs;
175
+ current.metadata ?? (current.metadata = metadata);
176
+ };
177
+ const metricResult = (key, value, startTimeMs, stopTimeMs, metadata) => ({
178
+ id: randomUUID(),
179
+ key,
180
+ value: round(value),
181
+ start: round(performance.timeOrigin + startTimeMs),
182
+ stop: round(performance.timeOrigin + stopTimeMs),
183
+ ...metadata,
184
+ });
94
185
  export const getPerfMetricsResults = () => {
95
- const totalSummary = getCoveredDurationSummary();
186
+ const coveredTiming = getCoveredTiming();
96
187
  const results = SPANS.map((span) => ({
97
188
  id: randomUUID(),
98
189
  key: span.name,
@@ -100,15 +191,21 @@ export const getPerfMetricsResults = () => {
100
191
  start: round(performance.timeOrigin + span.startTimeMs),
101
192
  stop: round(performance.timeOrigin + span.startTimeMs + span.durationMs),
102
193
  }));
103
- if (totalSummary) {
194
+ if (coveredTiming) {
104
195
  results.unshift({
105
196
  id: randomUUID(),
106
197
  key: PERF_METRIC_NAMES.allureTotal,
107
- value: totalSummary.avgMs,
108
- start: round(performance.timeOrigin + Math.min(...SPANS.map(({ startTimeMs }) => startTimeMs))),
109
- stop: round(performance.timeOrigin + Math.max(...SPANS.map(({ startTimeMs, durationMs }) => startTimeMs + durationMs))),
198
+ value: coveredTiming.durationMs,
199
+ start: round(performance.timeOrigin + coveredTiming.startTimeMs),
200
+ stop: round(performance.timeOrigin + coveredTiming.stopTimeMs),
110
201
  });
111
202
  }
203
+ for (const aggregate of AGGREGATES.values()) {
204
+ results.push(metricResult(`${aggregate.name}.totalMs`, aggregate.totalMs, aggregate.startTimeMs, aggregate.stopTimeMs), metricResult(`${aggregate.name}.avgMs`, aggregate.totalMs / aggregate.count, aggregate.startTimeMs, aggregate.stopTimeMs));
205
+ }
206
+ for (const counter of COUNTERS.values()) {
207
+ results.push(metricResult(counter.name, counter.value, counter.startTimeMs, counter.stopTimeMs, counter.metadata));
208
+ }
112
209
  return results;
113
210
  };
114
211
  export const writePerfMetrics = async (output, fileName = PERF_METRICS_FILE) => {
@@ -123,6 +220,8 @@ export const writePerfMetrics = async (output, fileName = PERF_METRICS_FILE) =>
123
220
  };
124
221
  export const resetPerfMetrics = () => {
125
222
  SPANS.length = 0;
223
+ AGGREGATES.clear();
224
+ COUNTERS.clear();
126
225
  sequence = 0;
127
226
  for (const mark of MARKS) {
128
227
  performance.clearMarks(mark);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allurereport/core",
3
- "version": "3.19.0",
3
+ "version": "3.20.0",
4
4
  "description": "Collection of generic Allure utilities used across the entire project",
5
5
  "keywords": [
6
6
  "allure"
@@ -25,26 +25,26 @@
25
25
  "lint:fix": "oxlint --import-plugin --fix src test features stories"
26
26
  },
27
27
  "dependencies": {
28
- "@allurereport/ci": "3.19.0",
29
- "@allurereport/cli-commons": "3.19.0",
30
- "@allurereport/core-api": "3.19.0",
31
- "@allurereport/plugin-agent": "3.19.0",
32
- "@allurereport/plugin-allure2": "3.19.0",
33
- "@allurereport/plugin-api": "3.19.0",
34
- "@allurereport/plugin-awesome": "3.19.0",
35
- "@allurereport/plugin-classic": "3.19.0",
36
- "@allurereport/plugin-csv": "3.19.0",
37
- "@allurereport/plugin-dashboard": "3.19.0",
38
- "@allurereport/plugin-jira": "3.19.0",
39
- "@allurereport/plugin-log": "3.19.0",
40
- "@allurereport/plugin-progress": "3.19.0",
41
- "@allurereport/plugin-slack": "3.19.0",
42
- "@allurereport/plugin-testops": "3.19.0",
43
- "@allurereport/plugin-testplan": "3.19.0",
44
- "@allurereport/reader": "3.19.0",
45
- "@allurereport/reader-api": "3.19.0",
46
- "@allurereport/service": "3.19.0",
47
- "@allurereport/summary": "3.19.0",
28
+ "@allurereport/ci": "3.20.0",
29
+ "@allurereport/cli-commons": "3.20.0",
30
+ "@allurereport/core-api": "3.20.0",
31
+ "@allurereport/plugin-agent": "3.20.0",
32
+ "@allurereport/plugin-allure2": "3.20.0",
33
+ "@allurereport/plugin-api": "3.20.0",
34
+ "@allurereport/plugin-awesome": "3.20.0",
35
+ "@allurereport/plugin-classic": "3.20.0",
36
+ "@allurereport/plugin-csv": "3.20.0",
37
+ "@allurereport/plugin-dashboard": "3.20.0",
38
+ "@allurereport/plugin-jira": "3.20.0",
39
+ "@allurereport/plugin-log": "3.20.0",
40
+ "@allurereport/plugin-progress": "3.20.0",
41
+ "@allurereport/plugin-slack": "3.20.0",
42
+ "@allurereport/plugin-testops": "3.20.0",
43
+ "@allurereport/plugin-testplan": "3.20.0",
44
+ "@allurereport/reader": "3.20.0",
45
+ "@allurereport/reader-api": "3.20.0",
46
+ "@allurereport/service": "3.20.0",
47
+ "@allurereport/summary": "3.20.0",
48
48
  "glob": "^13.0.6",
49
49
  "handlebars": "^4.7.9",
50
50
  "jiti": "^2.5.1",
@@ -58,7 +58,7 @@
58
58
  "@types/node": "^20",
59
59
  "@types/zip-stream": "^7.0.0",
60
60
  "@vitest/runner": "^2",
61
- "@vitest/snapshot": "^2.1.9",
61
+ "@vitest/snapshot": "^5.0.1",
62
62
  "allure-js-commons": "^3",
63
63
  "allure-vitest": "^3",
64
64
  "rimraf": "^6",