@loadstrike/loadstrike-sdk 1.0.10101 → 1.0.15201

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.
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.LoadStrikeRunner = exports.LoadStrikeScenario = exports.LoadStrikeContext = exports.ScenarioTrackingExtensions = exports.CrossPlatformScenarioConfigurator = exports.LoadStrikeThreshold = exports.LoadStrikeMetric = exports.LoadStrikeGauge = exports.LoadStrikeCounter = exports.LoadStrikeSimulation = exports.LoadStrikeStep = exports.LoadStrikeResponse = exports.LoadStrikeReportData = exports.LoadStrikePluginData = exports.LoadStrikePluginDataTable = exports.LoadStrikeOperationType = exports.LoadStrikeScenarioOperation = exports.LoadStrikeLogLevel = exports.LoadStrikeReportFormat = exports.LoadStrikeNodeType = void 0;
6
+ exports.__loadstrikeTestExports = exports.LoadStrikeRunner = exports.LoadStrikeScenario = exports.LoadStrikeContext = exports.ScenarioTrackingExtensions = exports.CrossPlatformScenarioConfigurator = exports.LoadStrikeThreshold = exports.LoadStrikeMetric = exports.LoadStrikeGauge = exports.LoadStrikeCounter = exports.LoadStrikeSimulation = exports.LoadStrikeStep = exports.LoadStrikeResponse = exports.LoadStrikePluginData = exports.LoadStrikePluginDataTable = exports.LoadStrikeOperationType = exports.LoadStrikeScenarioOperation = exports.LoadStrikeLogLevel = exports.LoadStrikeReportFormat = exports.LoadStrikeNodeType = void 0;
7
7
  const node_fs_1 = require("node:fs");
8
8
  const node_crypto_1 = require("node:crypto");
9
9
  const node_os_1 = __importDefault(require("node:os"));
@@ -93,43 +93,6 @@ class LoadStrikePluginData {
93
93
  }
94
94
  }
95
95
  exports.LoadStrikePluginData = LoadStrikePluginData;
96
- class LoadStrikeReportData {
97
- constructor(scenarioStats = []) {
98
- if (!Array.isArray(scenarioStats) || scenarioStats.some((value) => !value || typeof value !== "object")) {
99
- throw new TypeError("Scenario stats cannot contain null values.");
100
- }
101
- this.scenarioStats = scenarioStats.map((value, index) => attachScenarioStatsAliases(normalizeScenarioStatsValue(value, index)));
102
- }
103
- static create(...scenarioStats) {
104
- return new LoadStrikeReportData(scenarioStats);
105
- }
106
- static Create(...scenarioStats) {
107
- return LoadStrikeReportData.create(...scenarioStats);
108
- }
109
- static fromRunResult(result) {
110
- return new LoadStrikeReportData((result?.scenarioStats ?? result?.ScenarioStats ?? []));
111
- }
112
- findScenarioStats(scenarioName) {
113
- return this.scenarioStats.find((value) => value.scenarioName === scenarioName);
114
- }
115
- getScenarioStats(scenarioName) {
116
- const value = this.findScenarioStats(scenarioName);
117
- if (!value) {
118
- throw new Error(`Scenario stats not found: ${scenarioName}`);
119
- }
120
- return value;
121
- }
122
- FindScenarioStats(scenarioName) {
123
- return this.findScenarioStats(scenarioName);
124
- }
125
- GetScenarioStats(scenarioName) {
126
- return this.getScenarioStats(scenarioName);
127
- }
128
- get ScenarioStats() {
129
- return this.scenarioStats;
130
- }
131
- }
132
- exports.LoadStrikeReportData = LoadStrikeReportData;
133
96
  class MeasurementAccumulator {
134
97
  constructor() {
135
98
  this.count = 0;
@@ -749,28 +712,10 @@ class LoadStrikeContext {
749
712
  .configureContext(this);
750
713
  return runner.run();
751
714
  }
752
- async runDetailed(...argsOrSingleArray) {
753
- const args = normalizeRunArgsInput(argsOrSingleArray);
754
- if (!this.registeredScenarios.length) {
755
- throw new Error("At least one scenario must be added before running the context.");
756
- }
757
- if (args.length) {
758
- this.mergeValues(extractContextOverridesFromArgs(args), args);
759
- }
760
- const runner = LoadStrikeRunner
761
- .create()
762
- .addScenarios(...this.registeredScenarios)
763
- .configureContext(this);
764
- return runner.runDetailed();
765
- }
766
715
  async Run(...argsOrSingleArray) {
767
716
  const args = normalizeRunArgsInput(argsOrSingleArray);
768
717
  return this.run(args);
769
718
  }
770
- async RunDetailed(...argsOrSingleArray) {
771
- const args = normalizeRunArgsInput(argsOrSingleArray);
772
- return this.runDetailed(args);
773
- }
774
719
  toRunnerOptions() {
775
720
  const normalizedValues = normalizeRunContextCollectionShapes(this.values);
776
721
  return {
@@ -784,7 +729,6 @@ class LoadStrikeContext {
784
729
  coordinatorTargetScenarios: normalizedValues.CoordinatorTargetScenarios,
785
730
  natsServerUrl: normalizedValues.NatsServerUrl,
786
731
  runnerKey: normalizedValues.RunnerKey,
787
- licenseValidationServerUrl: normalizedValues.LicenseValidationServerUrl,
788
732
  licenseValidationTimeoutSeconds: normalizedValues.LicenseValidationTimeoutSeconds,
789
733
  configPath: normalizedValues.ConfigPath,
790
734
  infraConfigPath: normalizedValues.InfraConfigPath,
@@ -797,8 +741,6 @@ class LoadStrikeContext {
797
741
  reportFileName: normalizedValues.ReportFileName,
798
742
  reportFolderPath: normalizedValues.ReportFolderPath,
799
743
  reportFormats: normalizedValues.ReportFormats,
800
- reportFinalizer: normalizedValues.ReportFinalizer,
801
- detailedReportFinalizer: normalizedValues.DetailedReportFinalizer,
802
744
  reportingIntervalSeconds: normalizedValues.ReportingIntervalSeconds,
803
745
  minimumLogLevel: normalizedValues.MinimumLogLevel,
804
746
  loggerConfig: normalizedValues.LoggerConfig,
@@ -806,6 +748,7 @@ class LoadStrikeContext {
806
748
  sinkRetryCount: normalizedValues.SinkRetryCount,
807
749
  sinkRetryBackoffMs: normalizedValues.SinkRetryBackoffMs,
808
750
  runtimePolicies: normalizedValues.RuntimePolicies,
751
+ runtimePolicyErrorMode: normalizedRuntimePolicyErrorMode(normalizedValues.RuntimePolicyErrorMode),
809
752
  scenarioCompletionTimeoutSeconds: normalizedValues.ScenarioCompletionTimeoutSeconds,
810
753
  clusterCommandTimeoutSeconds: normalizedValues.ClusterCommandTimeoutSeconds,
811
754
  restartIterationMaxAttempts: normalizedValues.RestartIterationMaxAttempts,
@@ -851,28 +794,6 @@ class LoadStrikeContext {
851
794
  ConfigureContext(context) {
852
795
  return this.configureContext(context);
853
796
  }
854
- buildLicenseValidationPayload() {
855
- const scenarios = this.registeredScenarios.map((scenario) => ({
856
- Name: scenario.name,
857
- MaxFailCount: scenario.getMaxFailCount(),
858
- RestartIterationOnFail: scenario.shouldRestartIterationOnFail(),
859
- WithoutWarmUp: scenario.isWithoutWarmUp(),
860
- WarmUpDurationSeconds: scenario.getWarmUpDurationSeconds(),
861
- Weight: scenario.getWeight(),
862
- LoadSimulations: [...scenario.getSimulations()],
863
- Thresholds: [...scenario.getThresholds()],
864
- Tracking: scenario.getTrackingConfiguration() ?? {},
865
- LicenseFeatures: scenario.getLicenseFeatures()
866
- }));
867
- return {
868
- Context: this.toObject(),
869
- Scenarios: scenarios,
870
- RunArgs: [...this.runArgs]
871
- };
872
- }
873
- BuildLicenseValidationPayload() {
874
- return this.buildLicenseValidationPayload();
875
- }
876
797
  displayConsoleMetrics(enable) {
877
798
  return this.DisplayConsoleMetrics(enable);
878
799
  }
@@ -946,12 +867,6 @@ class LoadStrikeContext {
946
867
  WithRunnerKey(runnerKey) {
947
868
  return this.mergeValues({ RunnerKey: requireNonEmpty(runnerKey, "Runner key must be provided.") });
948
869
  }
949
- withLicenseValidationServerUrl(serverUrl) {
950
- return this.WithLicenseValidationServerUrl(serverUrl);
951
- }
952
- WithLicenseValidationServerUrl(serverUrl) {
953
- return this.mergeValues({ LicenseValidationServerUrl: requireNonEmpty(serverUrl, "License validation server URL must be provided.") });
954
- }
955
870
  withLicenseValidationTimeout(timeoutSeconds) {
956
871
  return this.WithLicenseValidationTimeout(timeoutSeconds);
957
872
  }
@@ -1004,24 +919,6 @@ class LoadStrikeContext {
1004
919
  }
1005
920
  return this.mergeValues({ ReportFormats: normalized });
1006
921
  }
1007
- withReportFinalizer(handler) {
1008
- return this.WithReportFinalizer(handler);
1009
- }
1010
- WithReportFinalizer(handler) {
1011
- if (typeof handler !== "function") {
1012
- throw new TypeError("Report finalizer must be provided.");
1013
- }
1014
- return this.mergeValues({ ReportFinalizer: handler });
1015
- }
1016
- withDetailedReportFinalizer(handler) {
1017
- return this.WithDetailedReportFinalizer(handler);
1018
- }
1019
- WithDetailedReportFinalizer(handler) {
1020
- if (typeof handler !== "function") {
1021
- throw new TypeError("Detailed report finalizer must be provided.");
1022
- }
1023
- return this.mergeValues({ DetailedReportFinalizer: handler });
1024
- }
1025
922
  withReportingInterval(intervalSeconds) {
1026
923
  return this.WithReportingInterval(intervalSeconds);
1027
924
  }
@@ -1044,6 +941,26 @@ class LoadStrikeContext {
1044
941
  validateNamedReportingSinks(sinks);
1045
942
  return this.mergeValues({ ReportingSinks: sinks });
1046
943
  }
944
+ withRuntimePolicies(...policies) {
945
+ return this.WithRuntimePolicies(...policies);
946
+ }
947
+ WithRuntimePolicies(...policies) {
948
+ if (!policies.length) {
949
+ throw new Error("At least one runtime policy should be provided.");
950
+ }
951
+ if (policies.some((policy) => policy == null)) {
952
+ throw new Error("Runtime policy collection cannot contain null values.");
953
+ }
954
+ return this.mergeValues({ RuntimePolicies: policies });
955
+ }
956
+ withRuntimePolicyErrorMode(mode) {
957
+ return this.WithRuntimePolicyErrorMode(mode);
958
+ }
959
+ WithRuntimePolicyErrorMode(mode) {
960
+ return this.mergeValues({
961
+ RuntimePolicyErrorMode: normalizedRuntimePolicyErrorMode(mode)
962
+ });
963
+ }
1047
964
  withWorkerPlugins(...plugins) {
1048
965
  return this.WithWorkerPlugins(...plugins);
1049
966
  }
@@ -1140,7 +1057,7 @@ class LoadStrikeContext {
1140
1057
  }
1141
1058
  exports.LoadStrikeContext = LoadStrikeContext;
1142
1059
  class LoadStrikeScenario {
1143
- constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail, licenseFeatures) {
1060
+ constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail) {
1144
1061
  this.name = name;
1145
1062
  this.runHandler = runHandler;
1146
1063
  this.initHandler = initHandler;
@@ -1153,18 +1070,23 @@ class LoadStrikeScenario {
1153
1070
  this.warmUpDurationSeconds = warmUpDurationSeconds;
1154
1071
  this.weight = weight;
1155
1072
  this.restartIterationOnFail = restartIterationOnFail;
1156
- this.licenseFeatures = [...licenseFeatures];
1157
1073
  }
1158
1074
  static create(name, runHandler) {
1159
1075
  const scenarioName = requireNonEmpty(name, "Scenario name must be provided.");
1160
1076
  if (typeof runHandler !== "function") {
1161
1077
  throw new TypeError("Scenario run handler must be provided.");
1162
1078
  }
1163
- return new LoadStrikeScenario(scenarioName, runHandler, undefined, undefined, [], [], undefined, 0, false, 0, 1, false, []);
1079
+ return new LoadStrikeScenario(scenarioName, runHandler, undefined, undefined, [], [], undefined, 0, false, 0, 1, false);
1164
1080
  }
1165
1081
  static Create(name, runHandler) {
1166
1082
  return LoadStrikeScenario.create(name, runHandler);
1167
1083
  }
1084
+ static createAsync(name, runHandler) {
1085
+ return LoadStrikeScenario.create(name, runHandler);
1086
+ }
1087
+ static CreateAsync(name, runHandler) {
1088
+ return LoadStrikeScenario.createAsync(name, runHandler);
1089
+ }
1168
1090
  static empty(name) {
1169
1091
  return LoadStrikeScenario.create(name, () => LoadStrikeResponse.ok());
1170
1092
  }
@@ -1178,37 +1100,43 @@ class LoadStrikeScenario {
1178
1100
  if (typeof handler !== "function") {
1179
1101
  throw new TypeError("Init handler must be provided.");
1180
1102
  }
1181
- return new LoadStrikeScenario(this.name, this.runHandler, handler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.licenseFeatures);
1103
+ return new LoadStrikeScenario(this.name, this.runHandler, handler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
1104
+ }
1105
+ withInitAsync(handler) {
1106
+ return this.withInit(handler);
1182
1107
  }
1183
1108
  withClean(handler) {
1184
1109
  if (typeof handler !== "function") {
1185
1110
  throw new TypeError("Clean handler must be provided.");
1186
1111
  }
1187
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, handler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.licenseFeatures);
1112
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, handler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
1113
+ }
1114
+ withCleanAsync(handler) {
1115
+ return this.withClean(handler);
1188
1116
  }
1189
1117
  withMaxFailCount(maxFailCount) {
1190
1118
  if (!Number.isFinite(maxFailCount)) {
1191
1119
  throw new RangeError("maxFailCount should be a finite number.");
1192
1120
  }
1193
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, Math.trunc(maxFailCount), this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.licenseFeatures);
1121
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, Math.trunc(maxFailCount), this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
1194
1122
  }
1195
1123
  withoutWarmUp() {
1196
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, true, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.licenseFeatures);
1124
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, true, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
1197
1125
  }
1198
1126
  withWarmUpDuration(durationSeconds) {
1199
1127
  if (!Number.isFinite(durationSeconds)) {
1200
1128
  throw new RangeError("Warmup duration should be a finite number.");
1201
1129
  }
1202
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, durationSeconds, this.weight, this.restartIterationOnFail, this.licenseFeatures);
1130
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, durationSeconds, this.weight, this.restartIterationOnFail);
1203
1131
  }
1204
1132
  withWeight(weight) {
1205
1133
  if (!Number.isFinite(weight)) {
1206
1134
  throw new RangeError("Weight should be a finite number.");
1207
1135
  }
1208
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, Math.trunc(weight), this.restartIterationOnFail, this.licenseFeatures);
1136
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, Math.trunc(weight), this.restartIterationOnFail);
1209
1137
  }
1210
1138
  withRestartIterationOnFail(shouldRestart) {
1211
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, Boolean(shouldRestart), this.licenseFeatures);
1139
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, Boolean(shouldRestart));
1212
1140
  }
1213
1141
  withCrossPlatformTracking(configuration) {
1214
1142
  if (!configuration || typeof configuration !== "object" || Array.isArray(configuration)) {
@@ -1225,25 +1153,19 @@ class LoadStrikeScenario {
1225
1153
  ? mapRuntimeTrackingEndpointSpec(destinationSpec)
1226
1154
  : null;
1227
1155
  validateRuntimeTrackingConfiguration(copied, sourceEndpoint, destinationEndpoint);
1228
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, copied, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.licenseFeatures);
1156
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, copied, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
1229
1157
  }
1230
1158
  withLoadSimulations(...simulations) {
1231
1159
  if (!simulations.length) {
1232
1160
  throw new Error("At least one load simulation should be provided.");
1233
1161
  }
1234
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, simulations.map((simulation) => attachLoadSimulationProjection({ ...simulation })), this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.licenseFeatures);
1162
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, simulations.map((simulation) => attachLoadSimulationProjection({ ...simulation })), this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
1235
1163
  }
1236
1164
  withThresholds(...thresholds) {
1237
1165
  if (!thresholds.length) {
1238
1166
  throw new Error("At least one threshold should be provided.");
1239
1167
  }
1240
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, thresholds.map((threshold) => ({ ...threshold })), this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.licenseFeatures);
1241
- }
1242
- withLicenseFeatures(...features) {
1243
- const normalized = features
1244
- .map((value) => String(value ?? "").trim())
1245
- .filter((value) => value.length > 0);
1246
- return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, [...this.licenseFeatures, ...normalized]);
1168
+ return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, thresholds.map((threshold) => ({ ...threshold })), this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail);
1247
1169
  }
1248
1170
  getSimulations() {
1249
1171
  return this.loadSimulations.map((simulation) => attachLoadSimulationProjection({ ...simulation }));
@@ -1269,9 +1191,6 @@ class LoadStrikeScenario {
1269
1191
  shouldRestartIterationOnFail() {
1270
1192
  return this.restartIterationOnFail;
1271
1193
  }
1272
- getLicenseFeatures() {
1273
- return [...this.licenseFeatures];
1274
- }
1275
1194
  async invokeInit(context) {
1276
1195
  if (this.initHandler) {
1277
1196
  await this.initHandler(context);
@@ -1300,9 +1219,15 @@ class LoadStrikeScenario {
1300
1219
  WithInit(handler) {
1301
1220
  return this.withInit(handler);
1302
1221
  }
1222
+ WithInitAsync(handler) {
1223
+ return this.withInitAsync(handler);
1224
+ }
1303
1225
  WithClean(handler) {
1304
1226
  return this.withClean(handler);
1305
1227
  }
1228
+ WithCleanAsync(handler) {
1229
+ return this.withCleanAsync(handler);
1230
+ }
1306
1231
  WithLoadSimulations(...simulations) {
1307
1232
  return this.withLoadSimulations(...simulations);
1308
1233
  }
@@ -1315,9 +1240,6 @@ class LoadStrikeScenario {
1315
1240
  WithRestartIterationOnFail(shouldRestart) {
1316
1241
  return this.withRestartIterationOnFail(shouldRestart);
1317
1242
  }
1318
- WithLicenseFeatures(...features) {
1319
- return this.withLicenseFeatures(...features);
1320
- }
1321
1243
  WithThresholds(...thresholds) {
1322
1244
  return this.withThresholds(...thresholds);
1323
1245
  }
@@ -1381,9 +1303,6 @@ class LoadStrikeRunner {
1381
1303
  static WithRunnerKey(context, runnerKey) {
1382
1304
  return context.WithRunnerKey(runnerKey);
1383
1305
  }
1384
- static WithLicenseValidationServerUrl(context, serverUrl) {
1385
- return context.WithLicenseValidationServerUrl(serverUrl);
1386
- }
1387
1306
  static WithLicenseValidationTimeout(context, timeoutSeconds) {
1388
1307
  return context.WithLicenseValidationTimeout(timeoutSeconds);
1389
1308
  }
@@ -1405,12 +1324,6 @@ class LoadStrikeRunner {
1405
1324
  static WithReportFileName(context, reportFileName) {
1406
1325
  return context.WithReportFileName(reportFileName);
1407
1326
  }
1408
- static WithReportFinalizer(context, handler) {
1409
- return context.WithReportFinalizer(handler);
1410
- }
1411
- static WithDetailedReportFinalizer(context, handler) {
1412
- return context.WithDetailedReportFinalizer(handler);
1413
- }
1414
1327
  static WithReportFolder(context, reportFolderPath) {
1415
1328
  return context.WithReportFolder(reportFolderPath);
1416
1329
  }
@@ -1423,6 +1336,12 @@ class LoadStrikeRunner {
1423
1336
  static WithReportingSinks(context, ...sinks) {
1424
1337
  return context.WithReportingSinks(...sinks);
1425
1338
  }
1339
+ static WithRuntimePolicies(context, ...policies) {
1340
+ return context.WithRuntimePolicies(...policies);
1341
+ }
1342
+ static WithRuntimePolicyErrorMode(context, mode) {
1343
+ return context.WithRuntimePolicyErrorMode(mode);
1344
+ }
1426
1345
  static WithScenarioCompletionTimeout(context, timeoutSeconds) {
1427
1346
  return context.WithScenarioCompletionTimeout(timeoutSeconds);
1428
1347
  }
@@ -1583,15 +1502,11 @@ class LoadStrikeRunner {
1583
1502
  return this.buildContext();
1584
1503
  }
1585
1504
  async run(args = []) {
1586
- const detailed = await this.runDetailed(args);
1587
- return detailedToNodeStats(detailed);
1588
- }
1589
- async runDetailed(args = []) {
1590
1505
  if (this.contextConfigurators.length) {
1591
- return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions()).runDetailed(args);
1506
+ return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions()).run(args);
1592
1507
  }
1593
1508
  if (args.length) {
1594
- return this.buildContext().runDetailed(args);
1509
+ return this.buildContext().run(args);
1595
1510
  }
1596
1511
  const started = new Date();
1597
1512
  const scenarioStats = new Map();
@@ -1605,14 +1520,15 @@ class LoadStrikeRunner {
1605
1520
  name: resolveSinkName(sink, index)
1606
1521
  }));
1607
1522
  const sinkErrors = [];
1523
+ const policyErrors = [];
1608
1524
  const sinkRetryCount = Math.max(this.options.sinkRetryCount ?? 2, 0);
1609
1525
  const sinkRetryBackoffMs = Math.max(this.options.sinkRetryBackoffMs ?? 25, 0);
1610
1526
  const policies = this.options.runtimePolicies ?? [];
1527
+ const runtimePolicyErrorMode = normalizedRuntimePolicyErrorMode(this.options.runtimePolicyErrorMode);
1611
1528
  const plugins = this.options.reportingSinks === undefined && this.options.workerPlugins === undefined &&
1612
1529
  this.options.runtimePolicies === undefined && this.options.customSettings === undefined &&
1613
1530
  this.options.globalCustomSettings === undefined && this.options.configPath === undefined &&
1614
1531
  this.options.infraConfigPath === undefined && this.options.infraConfig === undefined &&
1615
- this.options.reportFinalizer === undefined && this.options.detailedReportFinalizer === undefined &&
1616
1532
  this.options.loggerConfig === undefined && this.options.minimumLogLevel === undefined &&
1617
1533
  this.options.sinkRetryCount === undefined && this.options.sinkRetryBackoffMs === undefined &&
1618
1534
  this.options.reportsEnabled === false && this.options.displayConsoleMetrics === false &&
@@ -1627,7 +1543,6 @@ class LoadStrikeRunner {
1627
1543
  const createdUtc = started.toISOString();
1628
1544
  const testInfo = buildTestInfo(this.options, createdUtc);
1629
1545
  const nodeInfo = buildNodeInfo(this.options);
1630
- const runLogger = createLogger(this.options.loggerConfig, this.options.minimumLogLevel);
1631
1546
  let pluginsStopped = false;
1632
1547
  let sinksStopped = false;
1633
1548
  let realtimeTimer = null;
@@ -1637,18 +1552,18 @@ class LoadStrikeRunner {
1637
1552
  let licenseSession = null;
1638
1553
  const clusterMode = resolveClusterExecutionMode(this.options);
1639
1554
  const selectedScenarios = clusterMode === "local-coordinator" || clusterMode === "nats-coordinator"
1640
- ? await this.filterScenariosWithPolicies(this.scenarios, policies)
1641
- : await this.selectScenarios(policies);
1642
- const licenseValidationServerUrl = String(this.options.licenseValidationServerUrl ?? local_js_1.DEFAULT_LICENSING_API_BASE_URL).trim() || local_js_1.DEFAULT_LICENSING_API_BASE_URL;
1643
- licensePayload = this.buildLicenseValidationPayload();
1555
+ ? await this.filterScenariosWithPolicies(this.scenarios, policies, policyErrors, runtimePolicyErrorMode)
1556
+ : await this.selectScenarios(policies, policyErrors, runtimePolicyErrorMode);
1557
+ licensePayload = buildLicenseValidationPayload(this.options, this.scenarios);
1644
1558
  licenseClient = new local_js_1.LoadStrikeLocalClient({
1645
- licensingApiBaseUrl: licenseValidationServerUrl,
1646
1559
  licenseValidationTimeoutMs: Math.max((this.options.licenseValidationTimeoutSeconds ?? 10) * 1000, 1)
1647
1560
  });
1648
1561
  licenseSession = await licenseClient.acquireLicenseLease(licensePayload);
1649
1562
  if (clusterMode === "nats-agent") {
1650
1563
  return this.runAgentWithNats(createdUtc, testInfo, nodeInfo);
1651
1564
  }
1565
+ const loggerSetup = createLoggerSetup(this.options.loggerConfig, this.options.minimumLogLevel, this.options, testInfo, nodeInfo);
1566
+ const runLogger = loggerSetup.logger;
1652
1567
  const scenarioStartInfos = selectedScenarios.map((scenario, index) => {
1653
1568
  const startInfo = {
1654
1569
  scenarioName: scenario.name,
@@ -1715,7 +1630,7 @@ class LoadStrikeRunner {
1715
1630
  const okCount = snapshot.reduce((sum, value) => sum + value.allOkCount, 0);
1716
1631
  const failCount = snapshot.reduce((sum, value) => sum + value.allFailCount, 0);
1717
1632
  const stamp = new Date().toTimeString().slice(0, 8);
1718
- runLogger.info(`[${stamp}] requests=${requestCount} ok=${okCount} fail=${failCount}`);
1633
+ console.log(`[${stamp}] requests=${requestCount} ok=${okCount} fail=${failCount}`);
1719
1634
  }
1720
1635
  }
1721
1636
  finally {
@@ -1723,7 +1638,7 @@ class LoadStrikeRunner {
1723
1638
  }
1724
1639
  };
1725
1640
  const reportingIntervalMs = Math.max(Math.trunc((this.options.reportingIntervalSeconds ?? 5) * 1000), 1);
1726
- if (sinkStates.length > 0) {
1641
+ if (sinkStates.length > 0 || toBoolean(this.options.displayConsoleMetrics, true)) {
1727
1642
  realtimeTimer = setInterval(() => {
1728
1643
  void emitRealtimeSnapshot();
1729
1644
  }, reportingIntervalMs);
@@ -1738,12 +1653,12 @@ class LoadStrikeRunner {
1738
1653
  if (clusterMode === "local-coordinator") {
1739
1654
  const aggregated = await this.runCoordinatorWithLocalAgents(selectedScenarios, testInfo, nodeInfo);
1740
1655
  metricStats = aggregated.metrics;
1741
- result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors);
1656
+ result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors, policyErrors);
1742
1657
  }
1743
1658
  else if (clusterMode === "nats-coordinator") {
1744
1659
  const aggregated = await this.runCoordinatorWithNats(selectedScenarios, testInfo, nodeInfo);
1745
1660
  metricStats = aggregated.metrics;
1746
- result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors);
1661
+ result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors, policyErrors);
1747
1662
  }
1748
1663
  else {
1749
1664
  const testAbortController = new AbortController();
@@ -1766,21 +1681,15 @@ class LoadStrikeRunner {
1766
1681
  stopTestState,
1767
1682
  testAbortController,
1768
1683
  executeScenarioInvocation: (targetScenario, context, operation) => this.executeScenarioInvocation(targetScenario, context, operation),
1769
- invokeBeforeScenario: (runtimePolicies, scenarioName) => this.invokeBeforeScenario(runtimePolicies, scenarioName),
1770
- invokeAfterScenario: (runtimePolicies, scenarioName, stats) => this.invokeAfterScenario(runtimePolicies, scenarioName, stats)
1684
+ invokeBeforeScenario: (runtimePolicies, scenarioName) => this.invokeBeforeScenario(runtimePolicies, scenarioName, policyErrors, runtimePolicyErrorMode),
1685
+ invokeAfterScenario: (runtimePolicies, scenarioName, stats) => this.invokeAfterScenario(runtimePolicies, scenarioName, stats, policyErrors, runtimePolicyErrorMode),
1686
+ invokeBeforeStep: (runtimePolicies, scenarioName, stepName) => this.invokeBeforeStep(runtimePolicies, scenarioName, stepName, policyErrors, runtimePolicyErrorMode),
1687
+ invokeAfterStep: (runtimePolicies, scenarioName, stepName, reply) => this.invokeAfterStep(runtimePolicies, scenarioName, stepName, reply, policyErrors, runtimePolicyErrorMode)
1771
1688
  })));
1772
1689
  nodeInfo.currentOperation = stopTestState.value ? "Stop" : "Complete";
1773
- let scenarioStatList = Array.from(scenarioAccumulators.values())
1690
+ const scenarioStatList = Array.from(scenarioAccumulators.values())
1774
1691
  .map((value) => value.build(scenarioDurationsMs.get(value.scenarioName) ?? 0))
1775
1692
  .sort((left, right) => left.sortIndex - right.sortIndex);
1776
- const reportFinalizer = this.options.reportFinalizer;
1777
- if (typeof reportFinalizer === "function") {
1778
- const finalized = reportFinalizer(LoadStrikeReportData.create(...scenarioStatList));
1779
- if (!(finalized instanceof LoadStrikeReportData)) {
1780
- throw new TypeError("Report finalizer must return LoadStrikeReportData.");
1781
- }
1782
- scenarioStatList = [...finalized.scenarioStats];
1783
- }
1784
1693
  const metricValues = collectMetricValues(allRegisteredMetrics);
1785
1694
  metricStats = collectMetricStats(allRegisteredMetrics, Date.now() - started.getTime());
1786
1695
  const metricsByName = metricValues.reduce((accumulator, metric) => {
@@ -1794,35 +1703,42 @@ class LoadStrikeRunner {
1794
1703
  result = {
1795
1704
  startedUtc: started.toISOString(),
1796
1705
  completedUtc: new Date().toISOString(),
1706
+ allBytes: builtScenarioStats.reduce((sum, x) => sum + x.allBytes, 0),
1797
1707
  allRequestCount: scenarioStatList.reduce((sum, x) => sum + x.allRequestCount, 0),
1798
1708
  allOkCount: scenarioStatList.reduce((sum, x) => sum + x.allOkCount, 0),
1799
1709
  allFailCount: scenarioStatList.reduce((sum, x) => sum + x.allFailCount, 0),
1800
1710
  failedThresholds: thresholdEvaluation.failedCount,
1711
+ durationMs: Math.max(Date.now() - started.getTime(), 0),
1801
1712
  nodeInfo,
1802
1713
  testInfo,
1714
+ thresholds: thresholdEvaluation.results.map((value) => ({ ...value })),
1803
1715
  thresholdResults: thresholdEvaluation.results,
1716
+ metricStats,
1804
1717
  metrics: metricValues,
1805
1718
  scenarioStats: builtScenarioStats,
1806
1719
  stepStats: builtStepStats,
1720
+ scenarioDurationsMs: Object.fromEntries(scenarioDurationsMs.entries()),
1807
1721
  pluginsData: [],
1808
1722
  disabledSinks: [],
1809
1723
  sinkErrors,
1810
- reportFiles: []
1724
+ policyErrors,
1725
+ reportFiles: [],
1726
+ logFiles: [...loggerSetup.logFiles],
1727
+ correlationRows: buildDetailedCorrelationRows(),
1728
+ failedCorrelationRows: buildDetailedFailedCorrelationRows()
1811
1729
  };
1812
1730
  }
1813
- result.pluginsData = mergePluginData(result.pluginsData, await this.collectPluginData(plugins, detailedToNodeStats(result, metricStats), pluginLifecycleErrors));
1814
- const detailedReportFinalizer = this.options.detailedReportFinalizer;
1815
- const detailedResult = attachRunResultAliases(result);
1816
- const finalizedResult = attachRunResultAliases(typeof detailedReportFinalizer === "function"
1817
- ? requireDetailedReportFinalizerResult(detailedReportFinalizer(detailedResult))
1818
- : detailedResult);
1819
- await this.emitFinalStats(sinkStates, detailedToNodeStats(finalizedResult, metricStats), sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
1731
+ result.pluginsData = mergePluginData(result.pluginsData, await this.collectPluginData(plugins, attachRunResultAliases(result), pluginLifecycleErrors));
1732
+ const finalizedResult = attachRunResultAliases(result);
1733
+ finalizedResult.logFiles = mergeStringArrays(finalizedResult.logFiles, loggerSetup.logFiles);
1820
1734
  finalizedResult.reportFiles = this.writeReports(finalizedResult);
1821
1735
  finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
1822
1736
  await this.emitRunResult(sinkStates, finalizedResult, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
1823
1737
  await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
1824
1738
  sinksStopped = true;
1825
1739
  finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
1740
+ finalizedResult.sinkErrors = sinkErrors
1741
+ .map((value) => attachSinkErrorAliases(normalizeSinkErrorValue(value)));
1826
1742
  return finalizedResult;
1827
1743
  }
1828
1744
  finally {
@@ -1833,7 +1749,7 @@ class LoadStrikeRunner {
1833
1749
  await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
1834
1750
  }
1835
1751
  if (!pluginsStopped) {
1836
- await this.stopPlugins(plugins, pluginLifecycleErrors);
1752
+ await this.stopPlugins(plugins, pluginLifecycleErrors, runLogger);
1837
1753
  }
1838
1754
  if (licenseClient && licenseSession && licensePayload) {
1839
1755
  await licenseClient.releaseLicenseLease(licenseSession, licensePayload);
@@ -1843,10 +1759,7 @@ class LoadStrikeRunner {
1843
1759
  async Run(args = []) {
1844
1760
  return this.run(args);
1845
1761
  }
1846
- async RunDetailed(args = []) {
1847
- return this.runDetailed(args);
1848
- }
1849
- async filterScenariosWithPolicies(scenarios, policies) {
1762
+ async filterScenariosWithPolicies(scenarios, policies, policyErrors, mode) {
1850
1763
  if (!policies.length) {
1851
1764
  return [...scenarios];
1852
1765
  }
@@ -1855,7 +1768,7 @@ class LoadStrikeRunner {
1855
1768
  let allowed = true;
1856
1769
  for (const policy of policies) {
1857
1770
  if (policy.shouldRunScenario) {
1858
- const shouldRun = await policy.shouldRunScenario(scenario.name);
1771
+ const shouldRun = await this.invokePolicyCallback(policy, "shouldRunScenario", scenario.name, "", policyErrors, mode, () => policy.shouldRunScenario(scenario.name), true);
1859
1772
  if (!shouldRun) {
1860
1773
  allowed = false;
1861
1774
  break;
@@ -1888,6 +1801,7 @@ class LoadStrikeRunner {
1888
1801
  localDevClusterEnabled: false,
1889
1802
  nodeType,
1890
1803
  natsServerUrl: undefined,
1804
+ reportFolderPath: (0, node_path_1.resolve)(this.options.reportFolderPath ?? "./reports", sanitizeReportFileName(machineName)),
1891
1805
  targetScenarios,
1892
1806
  agentTargetScenarios: targetScenarios,
1893
1807
  coordinatorTargetScenarios: [],
@@ -1896,14 +1810,16 @@ class LoadStrikeRunner {
1896
1810
  reportingSinks: includeWorkerExtensions ? this.options.reportingSinks : [],
1897
1811
  workerPlugins: includeWorkerExtensions ? this.options.workerPlugins : []
1898
1812
  });
1899
- const childStats = await childRunner.run();
1813
+ const childResult = await childRunner.run();
1814
+ const childStats = detailedToNodeStats(childResult);
1900
1815
  return {
1901
1816
  ...childStats,
1902
1817
  nodeInfo: {
1903
1818
  ...childStats.nodeInfo,
1904
1819
  machineName,
1905
1820
  nodeType
1906
- }
1821
+ },
1822
+ logFiles: [...(childResult.logFiles ?? [])]
1907
1823
  };
1908
1824
  }
1909
1825
  async runCoordinatorWithLocalAgents(scenarios, testInfo, nodeInfo) {
@@ -1971,7 +1887,7 @@ class LoadStrikeRunner {
1971
1887
  };
1972
1888
  });
1973
1889
  if (handled && handledStats) {
1974
- return toDetailedRunResultFromNodeStats(handledStats, startedUtc, []);
1890
+ return toDetailedRunResultFromNodeStats(handledStats, startedUtc, [], []);
1975
1891
  }
1976
1892
  await sleep(5);
1977
1893
  }
@@ -1981,15 +1897,17 @@ class LoadStrikeRunner {
1981
1897
  await agent.dispose().catch(() => { });
1982
1898
  }
1983
1899
  }
1984
- async stopPlugins(plugins, pluginLifecycleErrors) {
1985
- void pluginLifecycleErrors;
1900
+ async stopPlugins(plugins, pluginLifecycleErrors, logger) {
1986
1901
  for (const plugin of plugins) {
1902
+ const pluginName = normalizePluginName(resolveWorkerPluginName(plugin));
1987
1903
  const stop = resolveWorkerPluginStop(plugin);
1988
1904
  if (stop) {
1989
1905
  try {
1990
1906
  await stop();
1991
1907
  }
1992
- catch {
1908
+ catch (error) {
1909
+ recordPluginLifecycleError(pluginLifecycleErrors, pluginName, "stop", error);
1910
+ logger?.warn?.(`Worker plugin ${pluginName} stop failed during shutdown: ${String(error ?? "unknown error")}`);
1993
1911
  }
1994
1912
  }
1995
1913
  const dispose = resolveWorkerPluginDispose(plugin);
@@ -1997,7 +1915,9 @@ class LoadStrikeRunner {
1997
1915
  try {
1998
1916
  await dispose();
1999
1917
  }
2000
- catch {
1918
+ catch (error) {
1919
+ recordPluginLifecycleError(pluginLifecycleErrors, pluginName, "dispose", error);
1920
+ logger?.warn?.(`Worker plugin ${pluginName} dispose failed during shutdown: ${String(error ?? "unknown error")}`);
2001
1921
  }
2002
1922
  }
2003
1923
  }
@@ -2009,7 +1929,7 @@ class LoadStrikeRunner {
2009
1929
  }
2010
1930
  return executeTrackedScenarioInvocation(tracking, context, () => scenario.execute(context), operation);
2011
1931
  }
2012
- async selectScenarios(policies) {
1932
+ async selectScenarios(policies, policyErrors, mode) {
2013
1933
  const scenarioMap = new Map(this.scenarios.map((x) => [x.name, x]));
2014
1934
  const nodeType = (this.options.nodeType ?? "SingleNode").toLowerCase();
2015
1935
  let selectedNames = this.options.targetScenarios;
@@ -2032,7 +1952,7 @@ class LoadStrikeRunner {
2032
1952
  let allowed = true;
2033
1953
  for (const policy of policies) {
2034
1954
  if (policy.shouldRunScenario) {
2035
- const shouldRun = await policy.shouldRunScenario(scenario.name);
1955
+ const shouldRun = await this.invokePolicyCallback(policy, "shouldRunScenario", scenario.name, "", policyErrors, mode, () => policy.shouldRunScenario(scenario.name), true);
2036
1956
  if (!shouldRun) {
2037
1957
  allowed = false;
2038
1958
  break;
@@ -2045,9 +1965,6 @@ class LoadStrikeRunner {
2045
1965
  }
2046
1966
  return filtered;
2047
1967
  }
2048
- buildLicenseValidationPayload() {
2049
- return buildLicenseValidationPayload(this.options, this.scenarios);
2050
- }
2051
1968
  async initializeSinks(sinkStates, context, infraConfig, retryCount, retryBackoffMs, sinkErrors) {
2052
1969
  for (const state of sinkStates) {
2053
1970
  await this.invokeSinkAction(state, "init", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
@@ -2082,19 +1999,10 @@ class LoadStrikeRunner {
2082
1999
  });
2083
2000
  }
2084
2001
  }
2085
- async emitFinalStats(sinkStates, result, retryCount, retryBackoffMs, sinkErrors) {
2086
- for (const state of sinkStates) {
2087
- await this.invokeSinkAction(state, "final", retryCount, retryBackoffMs, sinkErrors, false, false, async () => {
2088
- const saveFinalStats = resolveSinkSaveFinalStats(state.sink);
2089
- if (saveFinalStats) {
2090
- await saveFinalStats(result);
2091
- }
2092
- });
2093
- }
2094
- }
2095
2002
  async stopSinks(sinkStates, retryCount, retryBackoffMs, sinkErrors) {
2003
+ const shutdownRetryCount = 0;
2096
2004
  for (const state of sinkStates) {
2097
- await this.invokeSinkAction(state, "stop", retryCount, retryBackoffMs, sinkErrors, true, false, async () => {
2005
+ await this.invokeSinkAction(state, "stop", shutdownRetryCount, retryBackoffMs, sinkErrors, true, true, async () => {
2098
2006
  const stop = resolveSinkStop(state.sink);
2099
2007
  if (stop) {
2100
2008
  await stop();
@@ -2102,11 +2010,9 @@ class LoadStrikeRunner {
2102
2010
  });
2103
2011
  const dispose = resolveSinkDispose(state.sink);
2104
2012
  if (dispose) {
2105
- try {
2013
+ await this.invokeSinkAction(state, "dispose", shutdownRetryCount, retryBackoffMs, sinkErrors, true, true, async () => {
2106
2014
  await dispose();
2107
- }
2108
- catch {
2109
- }
2015
+ });
2110
2016
  }
2111
2017
  }
2112
2018
  }
@@ -2150,20 +2056,56 @@ class LoadStrikeRunner {
2150
2056
  }
2151
2057
  }
2152
2058
  }
2153
- async invokeBeforeScenario(policies, scenarioName) {
2059
+ async invokeBeforeScenario(policies, scenarioName, policyErrors, mode) {
2154
2060
  for (const policy of policies) {
2155
2061
  if (policy.beforeScenario) {
2156
- await policy.beforeScenario(scenarioName);
2062
+ await this.invokePolicyCallback(policy, "beforeScenario", scenarioName, "", policyErrors, mode, () => policy.beforeScenario(scenarioName));
2157
2063
  }
2158
2064
  }
2159
2065
  }
2160
- async invokeAfterScenario(policies, scenarioName, stats) {
2066
+ async invokeAfterScenario(policies, scenarioName, stats, policyErrors, mode) {
2161
2067
  for (const policy of policies) {
2162
2068
  if (policy.afterScenario) {
2163
- await policy.afterScenario(scenarioName, stats);
2069
+ await this.invokePolicyCallback(policy, "afterScenario", scenarioName, "", policyErrors, mode, () => policy.afterScenario(scenarioName, stats));
2070
+ }
2071
+ }
2072
+ }
2073
+ async invokeBeforeStep(policies, scenarioName, stepName, policyErrors, mode) {
2074
+ for (const policy of policies) {
2075
+ if (policy.beforeStep) {
2076
+ await this.invokePolicyCallback(policy, "beforeStep", scenarioName, stepName, policyErrors, mode, () => policy.beforeStep(scenarioName, stepName));
2164
2077
  }
2165
2078
  }
2166
2079
  }
2080
+ async invokeAfterStep(policies, scenarioName, stepName, reply, policyErrors, mode) {
2081
+ for (const policy of policies) {
2082
+ if (policy.afterStep) {
2083
+ await this.invokePolicyCallback(policy, "afterStep", scenarioName, stepName, policyErrors, mode, () => policy.afterStep(scenarioName, stepName, reply));
2084
+ }
2085
+ }
2086
+ }
2087
+ async invokePolicyCallback(policy, callbackName, scenarioName, stepName, policyErrors, mode, callback, continueFallback) {
2088
+ try {
2089
+ return await callback();
2090
+ }
2091
+ catch (error) {
2092
+ const entry = this.buildRuntimePolicyError(policy, callbackName, scenarioName, stepName, error);
2093
+ policyErrors.push(entry);
2094
+ if (mode === "fail") {
2095
+ throw new RuntimePolicyCallbackError(`Runtime policy '${entry.policyName}' failed during ${callbackName}: ${entry.message}`);
2096
+ }
2097
+ return continueFallback;
2098
+ }
2099
+ }
2100
+ buildRuntimePolicyError(policy, callbackName, scenarioName, stepName, error) {
2101
+ return attachRuntimePolicyErrorAliases({
2102
+ policyName: resolveRuntimePolicyName(policy),
2103
+ callbackName,
2104
+ scenarioName,
2105
+ stepName,
2106
+ message: String(error ?? "runtime policy callback failed")
2107
+ });
2108
+ }
2167
2109
  writeReports(result) {
2168
2110
  const reportsEnabled = this.options.reportsEnabled ?? true;
2169
2111
  if (!reportsEnabled) {
@@ -2172,7 +2114,7 @@ class LoadStrikeRunner {
2172
2114
  const reportFolder = (0, node_path_1.resolve)(this.options.reportFolderPath ?? "./reports");
2173
2115
  const reportFile = sanitizeReportFileName(this.options.reportFileName?.trim()
2174
2116
  ? this.options.reportFileName
2175
- : `${result.testInfo.testSuite}_${result.testInfo.testName}_${formatUtcReportTimestamp(new Date())}`);
2117
+ : `${result.testInfo.testSuite}_${result.testInfo.testName}_${resolveDefaultReportTimestamp(result)}`);
2176
2118
  const reportFormats = normalizeReportFormats(this.options.reportFormats ?? ["html", "txt", "csv", "md"]);
2177
2119
  (0, node_fs_1.mkdirSync)(reportFolder, { recursive: true });
2178
2120
  const nodeStats = detailedToNodeStats(result);
@@ -2224,7 +2166,7 @@ function hasPluginRows(value) {
2224
2166
  return value.tables.some((table) => Array.isArray(table.rows) && table.rows.length > 0);
2225
2167
  }
2226
2168
  async function executeScenarioRuntime(args) {
2227
- const { scenario, scenarioIndex, scenarioCount, options, logger, nodeInfo, testInfo, policies, restartIterationMaxAttempts, allRegisteredMetrics, scenarioRuntimes, stepRuntimes, scenarioAccumulators, scenarioDurationsMs, stopTestState, testAbortController, executeScenarioInvocation, invokeBeforeScenario, invokeAfterScenario } = args;
2169
+ const { scenario, scenarioIndex, scenarioCount, options, logger, nodeInfo, testInfo, policies, restartIterationMaxAttempts, allRegisteredMetrics, scenarioRuntimes, stepRuntimes, scenarioAccumulators, scenarioDurationsMs, stopTestState, testAbortController, executeScenarioInvocation, invokeBeforeScenario, invokeAfterScenario, invokeBeforeStep, invokeAfterStep } = args;
2228
2170
  const scenarioStartedMs = Date.now();
2229
2171
  const scenarioContextData = {};
2230
2172
  const registeredMetrics = [];
@@ -2337,20 +2279,8 @@ async function executeScenarioRuntime(args) {
2337
2279
  },
2338
2280
  shouldStopScenario: () => stopScenario || scenarioCancellationToken.aborted,
2339
2281
  shouldStopTest: () => stopTestState.value || scenarioCancellationToken.aborted,
2340
- invokeBeforeStep: async (stepName) => {
2341
- for (const policy of policies) {
2342
- if (policy.beforeStep) {
2343
- await policy.beforeStep(scenario.name, stepName);
2344
- }
2345
- }
2346
- },
2347
- invokeAfterStep: async (stepName, reply) => {
2348
- for (const policy of policies) {
2349
- if (policy.afterStep) {
2350
- await policy.afterStep(scenario.name, stepName, reply);
2351
- }
2352
- }
2353
- }
2282
+ invokeBeforeStep: async (stepName) => invokeBeforeStep(policies, scenario.name, stepName),
2283
+ invokeAfterStep: async (stepName, reply) => invokeAfterStep(policies, scenario.name, stepName, reply)
2354
2284
  };
2355
2285
  attachScenarioContextAliases(context);
2356
2286
  const startedAt = Date.now();
@@ -2589,6 +2519,10 @@ async function executeScenarioRuntime(args) {
2589
2519
  await invokeAfterScenario(policies, scenario.name, runtime);
2590
2520
  }
2591
2521
  catch (error) {
2522
+ if (error instanceof RuntimePolicyCallbackError) {
2523
+ accumulator.setCurrentOperation("Error");
2524
+ throw error;
2525
+ }
2592
2526
  if (scenarioCancellationToken.aborted || testAbortController.signal.aborted) {
2593
2527
  accumulator.setCurrentOperation("Stop");
2594
2528
  }
@@ -2986,6 +2920,16 @@ function normalizeSinkErrorValue(value) {
2986
2920
  attempts: pickAliasNumber(source, "attempts", "Attempts")
2987
2921
  };
2988
2922
  }
2923
+ function normalizeRuntimePolicyErrorValue(value) {
2924
+ const source = asAliasRecord(value);
2925
+ return {
2926
+ policyName: pickAliasString(source, "policyName", "PolicyName"),
2927
+ callbackName: pickAliasString(source, "callbackName", "CallbackName"),
2928
+ scenarioName: pickAliasString(source, "scenarioName", "ScenarioName"),
2929
+ stepName: pickAliasString(source, "stepName", "StepName"),
2930
+ message: pickAliasString(source, "message", "Message")
2931
+ };
2932
+ }
2989
2933
  function normalizeMetricValueProjection(value) {
2990
2934
  const source = asAliasRecord(value);
2991
2935
  const scenarioName = pickAliasString(source, "scenarioName", "ScenarioName");
@@ -3328,6 +3272,16 @@ function attachSinkErrorAliases(error) {
3328
3272
  Attempts: "attempts"
3329
3273
  });
3330
3274
  }
3275
+ function attachRuntimePolicyErrorAliases(error) {
3276
+ const projected = normalizeRuntimePolicyErrorValue(error);
3277
+ return attachAliasMap(projected, {
3278
+ PolicyName: "policyName",
3279
+ CallbackName: "callbackName",
3280
+ ScenarioName: "scenarioName",
3281
+ StepName: "stepName",
3282
+ Message: "message"
3283
+ });
3284
+ }
3331
3285
  function attachMetricStatsAliases(stats) {
3332
3286
  const projected = normalizeMetricStatsValue(stats);
3333
3287
  projected.counters = projected.counters.map((value) => attachCounterStatsAliases(value));
@@ -3486,7 +3440,8 @@ function attachNodeStatsAliases(stats) {
3486
3440
  PluginsData: "pluginsData",
3487
3441
  DisabledSinks: "disabledSinks",
3488
3442
  SinkErrors: "sinkErrors",
3489
- ReportFiles: "reportFiles"
3443
+ ReportFiles: "reportFiles",
3444
+ LogFiles: "logFiles"
3490
3445
  });
3491
3446
  defineAliasProperty(projected, "StartedUtc", () => parseAliasDate(stats.startedUtc));
3492
3447
  defineAliasProperty(projected, "CompletedUtc", () => parseAliasDate(stats.completedUtc));
@@ -3497,17 +3452,30 @@ function attachRunResultAliases(result) {
3497
3452
  const source = asAliasRecord(result);
3498
3453
  const scenarioStats = pickAliasArray(source, "scenarioStats", "ScenarioStats")
3499
3454
  .map((value, index) => attachScenarioStatsAliases(normalizeScenarioStatsValue(value, index)));
3455
+ const findScenarioStats = (scenarioName) => scenarioStats.find((value) => value.scenarioName === scenarioName);
3456
+ const getScenarioStats = (scenarioName) => {
3457
+ const value = findScenarioStats(scenarioName);
3458
+ if (!value) {
3459
+ throw new Error(`Scenario stats not found: ${scenarioName}`);
3460
+ }
3461
+ return value;
3462
+ };
3500
3463
  const projected = {
3501
3464
  startedUtc: pickAliasDateToken(source, "startedUtc", "StartedUtc"),
3502
3465
  completedUtc: pickAliasDateToken(source, "completedUtc", "CompletedUtc"),
3466
+ allBytes: pickAliasNumber(source, "allBytes", "AllBytes"),
3503
3467
  allRequestCount: pickAliasNumber(source, "allRequestCount", "AllRequestCount"),
3504
3468
  allOkCount: pickAliasNumber(source, "allOkCount", "AllOkCount"),
3505
3469
  allFailCount: pickAliasNumber(source, "allFailCount", "AllFailCount"),
3506
3470
  failedThresholds: pickAliasNumber(source, "failedThresholds", "FailedThresholds"),
3471
+ durationMs: pickAliasNumber(source, "durationMs", "DurationMs", "Duration"),
3507
3472
  nodeInfo: attachNodeInfoAliases(pickAliasValue(source, "nodeInfo", "NodeInfo")),
3508
3473
  testInfo: attachTestInfoAliases(pickAliasValue(source, "testInfo", "TestInfo")),
3474
+ thresholds: pickAliasArray(source, "thresholds", "Thresholds")
3475
+ .map((value) => attachThresholdResultAliases(normalizeThresholdResultValue(value))),
3509
3476
  thresholdResults: pickAliasArray(source, "thresholdResults", "ThresholdResults")
3510
3477
  .map((value) => attachThresholdResultAliases(normalizeThresholdResultValue(value))),
3478
+ metricStats: attachMetricStatsAliases(normalizeMetricStatsValue(pickAliasValue(source, "metricStats", "MetricStats"), pickAliasNumber(source, "durationMs", "DurationMs", "Duration"))),
3511
3479
  metrics: pickAliasArray(source, "metrics", "Metrics")
3512
3480
  .map((value) => attachMetricValueAliases(normalizeMetricValueProjection(value))),
3513
3481
  scenarioStats,
@@ -3517,37 +3485,53 @@ function attachRunResultAliases(result) {
3517
3485
  : scenarioStats.flatMap((value) => value.stepStats)),
3518
3486
  pluginsData: pickAliasArray(source, "pluginsData", "PluginsData")
3519
3487
  .map((value) => normalizePluginData(pickAliasString(asAliasRecord(value), "pluginName", "PluginName"), value)),
3488
+ scenarioDurationsMs: pickAliasValue(source, "scenarioDurationsMs", "ScenarioDurationsMs") ?? {},
3520
3489
  disabledSinks: normalizeAliasStringArray(pickAliasValue(source, "disabledSinks", "DisabledSinks")),
3521
3490
  sinkErrors: pickAliasArray(source, "sinkErrors", "SinkErrors")
3522
3491
  .map((value) => attachSinkErrorAliases(normalizeSinkErrorValue(value))),
3523
- reportFiles: normalizeAliasStringArray(pickAliasValue(source, "reportFiles", "ReportFiles"))
3492
+ policyErrors: pickAliasArray(source, "policyErrors", "PolicyErrors")
3493
+ .map((value) => attachRuntimePolicyErrorAliases(normalizeRuntimePolicyErrorValue(value))),
3494
+ reportFiles: normalizeAliasStringArray(pickAliasValue(source, "reportFiles", "ReportFiles")),
3495
+ logFiles: normalizeAliasStringArray(pickAliasValue(source, "logFiles", "LogFiles")),
3496
+ correlationRows: pickAliasArray(source, "correlationRows", "CorrelationRows")
3497
+ .map((value) => ({ ...asAliasRecord(value) })),
3498
+ failedCorrelationRows: pickAliasArray(source, "failedCorrelationRows", "FailedCorrelationRows")
3499
+ .map((value) => ({ ...asAliasRecord(value) })),
3500
+ findScenarioStats,
3501
+ getScenarioStats,
3502
+ FindScenarioStats: findScenarioStats,
3503
+ GetScenarioStats: getScenarioStats
3524
3504
  };
3525
3505
  attachAliasMap(projected, {
3506
+ AllBytes: "allBytes",
3526
3507
  AllRequestCount: "allRequestCount",
3527
3508
  AllOkCount: "allOkCount",
3528
3509
  AllFailCount: "allFailCount",
3529
3510
  FailedThresholds: "failedThresholds",
3511
+ DurationMs: "durationMs",
3530
3512
  NodeInfo: "nodeInfo",
3531
3513
  TestInfo: "testInfo",
3514
+ Thresholds: "thresholds",
3532
3515
  ThresholdResults: "thresholdResults",
3516
+ MetricStats: "metricStats",
3533
3517
  Metrics: "metrics",
3534
3518
  ScenarioStats: "scenarioStats",
3535
3519
  StepStats: "stepStats",
3520
+ ScenarioDurationsMs: "scenarioDurationsMs",
3536
3521
  PluginsData: "pluginsData",
3537
3522
  DisabledSinks: "disabledSinks",
3538
3523
  SinkErrors: "sinkErrors",
3539
- ReportFiles: "reportFiles"
3524
+ PolicyErrors: "policyErrors",
3525
+ ReportFiles: "reportFiles",
3526
+ LogFiles: "logFiles",
3527
+ CorrelationRows: "correlationRows",
3528
+ FailedCorrelationRows: "failedCorrelationRows"
3540
3529
  });
3541
3530
  defineAliasProperty(projected, "StartedUtc", () => parseAliasDate(projected.startedUtc));
3542
3531
  defineAliasProperty(projected, "CompletedUtc", () => parseAliasDate(projected.completedUtc));
3532
+ defineAliasProperty(projected, "Duration", () => projected.durationMs);
3543
3533
  return projected;
3544
3534
  }
3545
- function requireDetailedReportFinalizerResult(result) {
3546
- if (!result || typeof result !== "object" || Array.isArray(result)) {
3547
- throw new TypeError("Detailed report finalizer must return LoadStrikeRunResult.");
3548
- }
3549
- return result;
3550
- }
3551
3535
  function resolveResponseCustomLatency(customLatencyMs, usesOverloadDefaults) {
3552
3536
  if (!Number.isFinite(customLatencyMs)) {
3553
3537
  return usesOverloadDefaults ? -1 : 0;
@@ -4145,9 +4129,10 @@ function detailedToNodeStats(result, metricStats) {
4145
4129
  scenarioStats,
4146
4130
  stepStats,
4147
4131
  pluginsData,
4148
- disabledSinks: [...result.disabledSinks],
4149
- sinkErrors: result.sinkErrors.map((sinkError) => ({ ...sinkError })),
4150
- reportFiles: [...result.reportFiles],
4132
+ disabledSinks: [...(result.disabledSinks ?? [])],
4133
+ sinkErrors: (result.sinkErrors ?? []).map((sinkError) => ({ ...sinkError })),
4134
+ reportFiles: [...(result.reportFiles ?? [])],
4135
+ logFiles: [...(result.logFiles ?? [])],
4151
4136
  findScenarioStats: (scenarioName) => scenarioStats.find((scenario) => scenario.scenarioName === scenarioName),
4152
4137
  getScenarioStats: (scenarioName) => {
4153
4138
  const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
@@ -4214,6 +4199,7 @@ function buildEmptyNodeStats(args) {
4214
4199
  disabledSinks: [],
4215
4200
  sinkErrors: [],
4216
4201
  reportFiles: [],
4202
+ logFiles: [],
4217
4203
  findScenarioStats: (scenarioName) => undefined,
4218
4204
  getScenarioStats: (scenarioName) => {
4219
4205
  throw new Error(`Scenario stats not found: ${scenarioName}`);
@@ -4261,27 +4247,37 @@ function nodeStatsToClusterPayload(result) {
4261
4247
  thresholds: result.thresholds,
4262
4248
  pluginsData: result.pluginsData,
4263
4249
  nodeInfo: result.nodeInfo,
4264
- testInfo: result.testInfo
4250
+ testInfo: result.testInfo,
4251
+ logFiles: [...(result.logFiles ?? [])]
4265
4252
  };
4266
4253
  }
4267
- function toDetailedRunResultFromNodeStats(result, startedUtc, sinkErrors) {
4254
+ function toDetailedRunResultFromNodeStats(result, startedUtc, sinkErrors, policyErrors = []) {
4268
4255
  return attachRunResultAliases({
4269
4256
  startedUtc,
4270
4257
  completedUtc: result.completedUtc,
4258
+ allBytes: result.allBytes,
4271
4259
  allRequestCount: result.allRequestCount,
4272
4260
  allOkCount: result.allOkCount,
4273
4261
  allFailCount: result.allFailCount,
4274
4262
  failedThresholds: result.failedThresholds,
4263
+ durationMs: result.durationMs,
4275
4264
  nodeInfo: result.nodeInfo,
4276
4265
  testInfo: result.testInfo,
4266
+ thresholds: result.thresholds.map((value) => ({ ...value })),
4277
4267
  thresholdResults: result.thresholds.map((value) => ({ ...value })),
4268
+ metricStats: result.metrics,
4278
4269
  metrics: flattenMetricValues(result.metrics),
4279
4270
  scenarioStats: result.scenarioStats,
4280
4271
  stepStats: result.stepStats,
4272
+ scenarioDurationsMs: Object.fromEntries(result.scenarioStats.map((value) => [value.scenarioName, value.durationMs])),
4281
4273
  pluginsData: result.pluginsData.map((value) => normalizePluginData(value.pluginName, value)),
4282
4274
  disabledSinks: [...result.disabledSinks],
4283
4275
  sinkErrors: [...sinkErrors, ...result.sinkErrors],
4284
- reportFiles: [...result.reportFiles]
4276
+ policyErrors: policyErrors.map((value) => attachRuntimePolicyErrorAliases({ ...value })),
4277
+ reportFiles: [...result.reportFiles],
4278
+ logFiles: [...(result.logFiles ?? [])],
4279
+ correlationRows: buildDetailedCorrelationRows(),
4280
+ failedCorrelationRows: buildDetailedFailedCorrelationRows()
4285
4281
  });
4286
4282
  }
4287
4283
  function flattenMetricValues(metricStats) {
@@ -4374,6 +4370,7 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
4374
4370
  disabledSinks: [],
4375
4371
  sinkErrors: [],
4376
4372
  reportFiles: [],
4373
+ logFiles: normalizeAliasStringArray(result.stats.logFiles),
4377
4374
  findScenarioStats: (scenarioName) => scenarioStats.find((value) => value.scenarioName === scenarioName),
4378
4375
  getScenarioStats: (scenarioName) => {
4379
4376
  const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
@@ -4420,6 +4417,7 @@ function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes) {
4420
4417
  disabledSinks: [],
4421
4418
  sinkErrors: [],
4422
4419
  reportFiles: [],
4420
+ logFiles: mergeStringArrays(...nodes.map((value) => value.logFiles ?? [])),
4423
4421
  findScenarioStats: (scenarioName) => scenarioStats.find((value) => value.scenarioName === scenarioName),
4424
4422
  getScenarioStats: (scenarioName) => {
4425
4423
  const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
@@ -4812,6 +4810,8 @@ class ManagedScenarioTrackingRuntime {
4812
4810
  this.timeoutSweepIntervalMs = Math.trunc(pickTrackingNumber(tracking, ["TimeoutSweepIntervalMs", "timeoutSweepIntervalMs"], pickTrackingNumber(tracking, ["TimeoutSweepIntervalSeconds", "timeoutSweepIntervalSeconds"], 1) * 1000));
4813
4811
  this.timeoutBatchSize = Math.trunc(pickTrackingNumber(tracking, ["TimeoutBatchSize", "timeoutBatchSize"], 200));
4814
4812
  this.timeoutCountsAsFailure = pickTrackingBoolean(tracking, "TimeoutCountsAsFailure", "timeoutCountsAsFailure", true);
4813
+ const trackingFieldValueCaseSensitive = pickTrackingBoolean(tracking, "TrackingFieldValueCaseSensitive", "trackingFieldValueCaseSensitive", true);
4814
+ const gatherByFieldValueCaseSensitive = pickTrackingBoolean(tracking, "GatherByFieldValueCaseSensitive", "gatherByFieldValueCaseSensitive", true);
4815
4815
  this.sourceAdapter = transports_js_1.EndpointAdapterFactory.create(this.sourceEndpoint);
4816
4816
  this.destinationAdapter = this.destinationEndpoint ? transports_js_1.EndpointAdapterFactory.create(this.destinationEndpoint) : null;
4817
4817
  this.correlationStore = mapRuntimeCorrelationStore(tracking, runNamespace);
@@ -4819,18 +4819,20 @@ class ManagedScenarioTrackingRuntime {
4819
4819
  sourceTrackingField: this.sourceEndpoint.trackingField,
4820
4820
  destinationTrackingField: this.destinationEndpoint?.trackingField,
4821
4821
  destinationGatherByField: this.destinationEndpoint?.gatherByField,
4822
+ trackingFieldValueCaseSensitive,
4823
+ gatherByFieldValueCaseSensitive,
4822
4824
  correlationTimeoutMs: this.correlationTimeoutMs,
4823
4825
  timeoutCountsAsFailure: this.timeoutCountsAsFailure,
4824
4826
  store: this.correlationStore ?? undefined,
4825
4827
  plugins: [
4826
4828
  {
4827
- onMatched: async (trackingId, _source, destination, latencyMs) => {
4829
+ onMatched: async (trackingId, _source, destination, latencyMs, gatherByValue) => {
4828
4830
  const eventId = this.shiftPendingValue(this.pendingEventIds, trackingId);
4829
4831
  const sourceTimestampUtc = this.shiftPendingValue(this.pendingSourceTimestamps, trackingId);
4830
4832
  const destinationTimestampUtc = new Date().toISOString();
4831
- const gatherByValue = this.destinationEndpoint?.gatherByField
4833
+ const observedGatherByValue = gatherByValue ?? (this.destinationEndpoint?.gatherByField
4832
4834
  ? readRuntimeTrackingId(destination, this.destinationEndpoint.gatherByField)
4833
- : undefined;
4835
+ : undefined);
4834
4836
  addCorrelationRow({
4835
4837
  occurredUtc: new Date().toISOString(),
4836
4838
  scenarioName: this.scenarioName,
@@ -4846,7 +4848,7 @@ class ManagedScenarioTrackingRuntime {
4846
4848
  isSuccess: true,
4847
4849
  isFailure: false,
4848
4850
  gatherByField: this.gatherByFieldExpression,
4849
- gatherByValue: gatherByValue ?? undefined
4851
+ gatherByValue: observedGatherByValue ?? undefined
4850
4852
  });
4851
4853
  this.resolveTrackingWaiter(trackingId, {
4852
4854
  status: "matched",
@@ -5617,10 +5619,10 @@ function normalizeRuntimeObservedTrackingPayload(payload, endpoint) {
5617
5619
  function readRuntimeTrackingId(payload, selector) {
5618
5620
  const normalized = selector.trim().toLowerCase();
5619
5621
  if (normalized.startsWith("header:")) {
5620
- const headerName = selector.slice("header:".length).trim().toLowerCase();
5622
+ const headerName = selector.slice("header:".length).trim();
5621
5623
  for (const [key, value] of Object.entries(payload.headers ?? {})) {
5622
5624
  const resolved = String(value ?? "").trim();
5623
- if (key.toLowerCase() === headerName && resolved) {
5625
+ if (key === headerName && resolved) {
5624
5626
  return resolved;
5625
5627
  }
5626
5628
  }
@@ -5896,6 +5898,49 @@ function createLogger(loggerConfig, minimumLogLevel) {
5896
5898
  };
5897
5899
  }
5898
5900
  }
5901
+ return wrapLoggerWithMinimumLevel(baseLogger, minimumLogLevel);
5902
+ }
5903
+ function createLoggerSetup(loggerConfig, minimumLogLevel, options, testInfo, nodeInfo) {
5904
+ if (typeof loggerConfig === "function") {
5905
+ return {
5906
+ logger: createLogger(loggerConfig, minimumLogLevel),
5907
+ logFiles: []
5908
+ };
5909
+ }
5910
+ const logFilePath = resolveDefaultLogFilePath(options, testInfo, nodeInfo);
5911
+ (0, node_fs_1.mkdirSync)((0, node_path_1.resolve)(options.reportFolderPath ?? "./reports"), { recursive: true });
5912
+ (0, node_fs_1.writeFileSync)(logFilePath, "", "utf8");
5913
+ return {
5914
+ logger: wrapLoggerWithMinimumLevel(createDefaultLogger(logFilePath), minimumLogLevel),
5915
+ logFiles: [logFilePath]
5916
+ };
5917
+ }
5918
+ function createDefaultLogger(logFilePath) {
5919
+ const write = (level, message) => {
5920
+ const line = formatDefaultLoggerLine(level, message);
5921
+ const output = `${line}\n`;
5922
+ if (level === "debug") {
5923
+ console.debug(line);
5924
+ }
5925
+ else if (level === "info") {
5926
+ console.info(line);
5927
+ }
5928
+ else if (level === "warn") {
5929
+ console.warn(line);
5930
+ }
5931
+ else {
5932
+ console.error(line);
5933
+ }
5934
+ (0, node_fs_1.appendFileSync)(logFilePath, output, "utf8");
5935
+ };
5936
+ return {
5937
+ debug: (message) => write("debug", message),
5938
+ info: (message) => write("info", message),
5939
+ warn: (message) => write("warn", message),
5940
+ error: (message) => write("error", message)
5941
+ };
5942
+ }
5943
+ function wrapLoggerWithMinimumLevel(baseLogger, minimumLogLevel) {
5899
5944
  const threshold = logLevelOrder(minimumLogLevel);
5900
5945
  return {
5901
5946
  debug: (message) => {
@@ -5920,6 +5965,16 @@ function createLogger(loggerConfig, minimumLogLevel) {
5920
5965
  }
5921
5966
  };
5922
5967
  }
5968
+ function formatDefaultLoggerLine(level, message) {
5969
+ const code = level === "debug"
5970
+ ? "DBG"
5971
+ : level === "info"
5972
+ ? "INF"
5973
+ : level === "warn"
5974
+ ? "WRN"
5975
+ : "ERR";
5976
+ return `${new Date().toISOString()} [${code}] ${message}`;
5977
+ }
5923
5978
  function logLevelOrder(level) {
5924
5979
  const normalized = String(level ?? "").trim().toLowerCase();
5925
5980
  if (!normalized) {
@@ -5951,11 +6006,41 @@ function formatUtcReportTimestamp(value) {
5951
6006
  const second = String(value.getUTCSeconds()).padStart(2, "0");
5952
6007
  return `${year}${month}${day}_${hour}${minute}${second}`;
5953
6008
  }
6009
+ function resolveDefaultReportTimestamp(result) {
6010
+ const createdUtc = result.testInfo?.createdUtc ?? result.startedUtc;
6011
+ const parsed = createdUtc ? new Date(createdUtc) : undefined;
6012
+ return parsed && !Number.isNaN(parsed.getTime())
6013
+ ? formatUtcReportTimestamp(parsed)
6014
+ : formatUtcReportTimestamp(new Date());
6015
+ }
6016
+ function resolveDefaultLogFilePath(options, testInfo, nodeInfo) {
6017
+ const reportFolder = (0, node_path_1.resolve)(options.reportFolderPath ?? "./reports");
6018
+ const parsedCreatedUtc = testInfo.createdUtc ? new Date(testInfo.createdUtc) : undefined;
6019
+ const timestamp = parsedCreatedUtc && !Number.isNaN(parsedCreatedUtc.getTime())
6020
+ ? formatUtcReportTimestamp(parsedCreatedUtc)
6021
+ : formatUtcReportTimestamp(new Date());
6022
+ const suffixParts = [];
6023
+ if (nodeInfo.nodeType !== "SingleNode") {
6024
+ suffixParts.push(nodeInfo.nodeType === "Coordinator" ? "coordinator" : "agent");
6025
+ }
6026
+ const localMachineName = node_os_1.default.hostname().trim().toLowerCase();
6027
+ if (nodeInfo.machineName.trim() &&
6028
+ (nodeInfo.nodeType !== "SingleNode" || nodeInfo.machineName.trim().toLowerCase() !== localMachineName)) {
6029
+ suffixParts.push(nodeInfo.machineName);
6030
+ }
6031
+ const suffix = suffixParts.length
6032
+ ? `-${suffixParts.map((value) => sanitizeReportFileName(value)).join("-")}`
6033
+ : "";
6034
+ return (0, node_path_1.resolve)(reportFolder, sanitizeReportFileName(`loadstrike-log-${timestamp}${suffix}.txt`));
6035
+ }
5954
6036
  function sanitizeReportFileName(value) {
5955
6037
  const normalized = String(value ?? "");
5956
6038
  return normalized
5957
6039
  .replace(/[<>:"/\\|?*\u0000-\u001F]/g, "_") || "loadstrike-run";
5958
6040
  }
6041
+ function mergeStringArrays(...values) {
6042
+ return Array.from(new Set(values.flatMap((value) => (value ?? []).filter((entry) => entry.trim().length > 0))));
6043
+ }
5959
6044
  function loadJsonObject(path) {
5960
6045
  try {
5961
6046
  const raw = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(path), "utf8");
@@ -6192,6 +6277,16 @@ function normalizeRunnerOptionCollectionShapes(options) {
6192
6277
  validateNamedWorkerPlugins(normalized.workerPlugins ?? []);
6193
6278
  return normalized;
6194
6279
  }
6280
+ function normalizedRuntimePolicyErrorMode(value) {
6281
+ const normalized = String(value ?? "fail").trim().toLowerCase();
6282
+ if (normalized === "fail") {
6283
+ return "fail";
6284
+ }
6285
+ if (normalized === "continue") {
6286
+ return "continue";
6287
+ }
6288
+ throw new Error("Runtime policy error mode must be either Fail or Continue.");
6289
+ }
6195
6290
  function extractContextOverridesFromConfig(config) {
6196
6291
  const rootConfig = asRecord(config);
6197
6292
  const loadStrikeSection = asRecord(tryReadConfigValue(rootConfig, "LoadStrike"));
@@ -6245,6 +6340,7 @@ function extractContextOverridesFromConfig(config) {
6245
6340
  setString("AgentGroup", "AgentGroup", "LoadStrike:AgentGroup");
6246
6341
  setString("NatsServerUrl", "NatsServerUrl", "LoadStrike:NatsServerUrl");
6247
6342
  setString("RunnerKey", "RunnerKey", "LoadStrike:RunnerKey");
6343
+ setString("RuntimePolicyErrorMode", "RuntimePolicyErrorMode", "LoadStrike:RuntimePolicyErrorMode");
6248
6344
  const nodeType = pick("NodeType", "LoadStrike:NodeType");
6249
6345
  if (nodeType != null) {
6250
6346
  const parsed = tryParseNodeTypeToken(nodeType);
@@ -6259,7 +6355,6 @@ function extractContextOverridesFromConfig(config) {
6259
6355
  patch.MinimumLogLevel = parsed;
6260
6356
  }
6261
6357
  }
6262
- setString("LicenseValidationServerUrl", "LicenseValidationServerUrl", "LoadStrike:LicenseValidation:ServerUrl", "LicenseValidation:ServerUrl");
6263
6358
  const agentsCount = toInt(pick("AgentsCount", "LoadStrike:AgentsCount"));
6264
6359
  if (agentsCount > 0) {
6265
6360
  patch.AgentsCount = agentsCount;
@@ -6394,7 +6489,6 @@ function toRunContext(options) {
6394
6489
  CoordinatorTargetScenarios: normalized.coordinatorTargetScenarios,
6395
6490
  NatsServerUrl: normalized.natsServerUrl,
6396
6491
  RunnerKey: normalized.runnerKey,
6397
- LicenseValidationServerUrl: normalized.licenseValidationServerUrl,
6398
6492
  LicenseValidationTimeoutSeconds: normalized.licenseValidationTimeoutSeconds,
6399
6493
  ConfigPath: normalized.configPath,
6400
6494
  InfraConfigPath: normalized.infraConfigPath,
@@ -6407,8 +6501,6 @@ function toRunContext(options) {
6407
6501
  ReportFileName: normalized.reportFileName,
6408
6502
  ReportFolderPath: normalized.reportFolderPath,
6409
6503
  ReportFormats: normalized.reportFormats,
6410
- ReportFinalizer: normalized.reportFinalizer,
6411
- DetailedReportFinalizer: normalized.detailedReportFinalizer,
6412
6504
  ReportingIntervalSeconds: normalized.reportingIntervalSeconds,
6413
6505
  MinimumLogLevel: normalized.minimumLogLevel,
6414
6506
  LoggerConfig: normalized.loggerConfig,
@@ -6416,6 +6508,7 @@ function toRunContext(options) {
6416
6508
  SinkRetryCount: normalized.sinkRetryCount,
6417
6509
  SinkRetryBackoffMs: normalized.sinkRetryBackoffMs,
6418
6510
  RuntimePolicies: normalized.runtimePolicies,
6511
+ RuntimePolicyErrorMode: normalized.runtimePolicyErrorMode,
6419
6512
  ScenarioCompletionTimeoutSeconds: normalized.scenarioCompletionTimeoutSeconds,
6420
6513
  ClusterCommandTimeoutSeconds: normalized.clusterCommandTimeoutSeconds,
6421
6514
  RestartIterationMaxAttempts: normalized.restartIterationMaxAttempts,
@@ -6424,6 +6517,26 @@ function toRunContext(options) {
6424
6517
  GlobalCustomSettings: normalized.globalCustomSettings
6425
6518
  };
6426
6519
  }
6520
+ class RuntimePolicyCallbackError extends Error {
6521
+ constructor(message) {
6522
+ super(message);
6523
+ this.name = "RuntimePolicyCallbackError";
6524
+ }
6525
+ }
6526
+ function resolveRuntimePolicyName(policy) {
6527
+ const namedPolicy = policy.policyName ?? policy.PolicyName;
6528
+ if (typeof namedPolicy === "string" && namedPolicy.trim()) {
6529
+ return namedPolicy;
6530
+ }
6531
+ const constructorValue = Reflect.get(policy, "constructor");
6532
+ const constructorName = constructorValue && typeof constructorValue === "object"
6533
+ ? Reflect.get(constructorValue, "name")
6534
+ : typeof constructorValue === "function"
6535
+ ? Reflect.get(constructorValue, "name")
6536
+ : "";
6537
+ const fallback = typeof constructorName === "string" ? constructorName.trim() : "";
6538
+ return fallback || "runtime-policy";
6539
+ }
6427
6540
  function buildLicenseValidationPayload(options, scenarios) {
6428
6541
  const context = {
6429
6542
  ...toRunContext(options)
@@ -6437,8 +6550,7 @@ function buildLicenseValidationPayload(options, scenarios) {
6437
6550
  Weight: scenario.getWeight(),
6438
6551
  LoadSimulations: [...scenario.getSimulations()],
6439
6552
  Thresholds: [...scenario.getThresholds()],
6440
- Tracking: scenario.getTrackingConfiguration() ?? {},
6441
- LicenseFeatures: scenario.getLicenseFeatures()
6553
+ Tracking: scenario.getTrackingConfiguration() ?? {}
6442
6554
  }));
6443
6555
  return {
6444
6556
  Context: context,
@@ -6487,7 +6599,6 @@ function looksLikeRunContext(value) {
6487
6599
  "ClusterId",
6488
6600
  "CoordinatorTargetScenarios",
6489
6601
  "RunnerKey",
6490
- "LicenseValidationServerUrl",
6491
6602
  "LicenseValidationTimeoutSeconds",
6492
6603
  "MinimumLogLevel",
6493
6604
  "LoggerConfig",
@@ -6497,8 +6608,6 @@ function looksLikeRunContext(value) {
6497
6608
  "ReportFileName",
6498
6609
  "ReportFolderPath",
6499
6610
  "ReportFormats",
6500
- "ReportFinalizer",
6501
- "DetailedReportFinalizer",
6502
6611
  "ReportingIntervalSeconds",
6503
6612
  "ReportingSinks",
6504
6613
  "SinkRetryCount",
@@ -6635,12 +6744,6 @@ function resolveSinkSaveRealtimeMetrics(sink) {
6635
6744
  ? method.bind(sink)
6636
6745
  : undefined;
6637
6746
  }
6638
- function resolveSinkSaveFinalStats(sink) {
6639
- const method = sink.saveFinalStats ?? sink.SaveFinalStats;
6640
- return typeof method === "function"
6641
- ? method.bind(sink)
6642
- : undefined;
6643
- }
6644
6747
  function resolveSinkSaveRunResult(sink) {
6645
6748
  const method = sink.saveRunResult ?? sink.SaveRunResult;
6646
6749
  return typeof method === "function"
@@ -6679,6 +6782,46 @@ function addCorrelationRow(row) {
6679
6782
  correlationRows.splice(0, correlationRows.length - MAX_CORRELATION_ROWS);
6680
6783
  }
6681
6784
  }
6785
+ function buildDetailedFailedCorrelationRows() {
6786
+ return [...failedResponseRows]
6787
+ .reverse()
6788
+ .map((row) => ({
6789
+ OccurredUtc: row.occurredUtc,
6790
+ Scenario: row.scenarioName,
6791
+ Source: row.sourceEndpoint,
6792
+ Destination: row.destinationEndpoint,
6793
+ RunMode: row.runMode,
6794
+ StatusCode: row.statusCode,
6795
+ TrackingId: row.trackingId ?? "",
6796
+ EventId: row.eventId ?? "",
6797
+ SourceTimestampUtc: row.sourceTimestampUtc ?? "",
6798
+ DestinationTimestampUtc: row.destinationTimestampUtc ?? "",
6799
+ LatencyMs: formatOptionalLatency(row.latencyMs),
6800
+ Message: row.message ?? ""
6801
+ }));
6802
+ }
6803
+ function buildDetailedCorrelationRows() {
6804
+ return [...correlationRows]
6805
+ .reverse()
6806
+ .map((row) => ({
6807
+ OccurredUtc: row.occurredUtc,
6808
+ Scenario: row.scenarioName,
6809
+ Source: row.sourceEndpoint,
6810
+ Destination: row.destinationEndpoint,
6811
+ RunMode: row.runMode,
6812
+ StatusCode: row.statusCode,
6813
+ IsSuccess: row.isSuccess,
6814
+ IsFailure: row.isFailure,
6815
+ GatherByField: row.gatherByField ?? "",
6816
+ GatherByValue: row.gatherByValue ?? "",
6817
+ TrackingId: row.trackingId ?? "",
6818
+ EventId: row.eventId ?? "",
6819
+ SourceTimestampUtc: row.sourceTimestampUtc ?? "",
6820
+ DestinationTimestampUtc: row.destinationTimestampUtc ?? "",
6821
+ LatencyMs: formatOptionalLatency(row.latencyMs),
6822
+ Message: row.message ?? ""
6823
+ }));
6824
+ }
6682
6825
  function resolveWorkerPlugins(customPlugins) {
6683
6826
  const registry = new Map();
6684
6827
  for (const plugin of createBuiltInWorkerPlugins()) {
@@ -7011,3 +7154,122 @@ function readConfiguredSinkName(sink) {
7011
7154
  async function sleep(ms) {
7012
7155
  await new Promise((resolve) => setTimeout(resolve, Math.max(ms, 0)));
7013
7156
  }
7157
+ exports.__loadstrikeTestExports = {
7158
+ LoadStrikeContext,
7159
+ LoadStrikeCounter,
7160
+ LoadStrikeGauge,
7161
+ LoadStrikeMetric,
7162
+ LoadStrikeResponse,
7163
+ LoadStrikeRunner,
7164
+ LoadStrikeScenario,
7165
+ LoadStrikeSimulation,
7166
+ LoadStrikeStep,
7167
+ ManagedScenarioTrackingRuntime,
7168
+ ScenarioStatsAccumulator,
7169
+ StepStatsAccumulator,
7170
+ TrackingFieldSelector: correlation_js_1.TrackingFieldSelector,
7171
+ addCorrelationRow,
7172
+ addFailedResponseRow,
7173
+ aggregateNodeStats,
7174
+ asRecord,
7175
+ assertNoDisableLicenseEnforcementOption,
7176
+ buildEmptyNodeStats,
7177
+ buildGroupedCorrelationRows,
7178
+ buildMeasurementPlaceholder,
7179
+ buildRichHtmlReport,
7180
+ buildThresholdCheckExpression,
7181
+ buildTrackingLeaseKey,
7182
+ buildTrackingRunNamespace,
7183
+ clusterNodeResultToNodeStats,
7184
+ combineAbortSignals,
7185
+ computeScenarioRequestCount,
7186
+ computeWarmUpIterations,
7187
+ createBuiltInWorkerPlugins,
7188
+ createLogger,
7189
+ createRuntimeRandom,
7190
+ delayWithAbort,
7191
+ detailedToNodeStats,
7192
+ evaluateThresholdsForScenarios,
7193
+ executeTrackedScenarioInvocation,
7194
+ extractContextOverridesFromArgs,
7195
+ extractContextOverridesFromConfig,
7196
+ findCaseInsensitiveKey,
7197
+ formatOptionalLatency,
7198
+ formatUtcReportTimestamp,
7199
+ hasPluginRows,
7200
+ inferRuntimeLegacyHttpResponseSource,
7201
+ isComparisonFailed,
7202
+ loadJsonObject,
7203
+ logLevelOrder,
7204
+ looksLikeRunContext,
7205
+ mapRuntimeCorrelationStore,
7206
+ mapRuntimeTrackingEndpointSpec,
7207
+ mergeDefinedRecord,
7208
+ normalizeCliOverrideKey,
7209
+ normalizeMetricValue,
7210
+ normalizeOptionalReportFormats,
7211
+ normalizeOptionalStringArray,
7212
+ normalizeReplyArguments,
7213
+ normalizePluginData,
7214
+ normalizeReportFormats,
7215
+ normalizeRunArgsInput,
7216
+ normalizeRunContextCollectionShapes,
7217
+ normalizeRunnerOptionCollectionShapes,
7218
+ normalizeRuntimeHttpTrackingPayloadSource,
7219
+ normalizeRuntimeTrackingPayload,
7220
+ normalizeStringArray,
7221
+ normalizeFailureArguments,
7222
+ normalizeTrackingRunMode,
7223
+ normalizeThresholdScope,
7224
+ parseAliasDate,
7225
+ parseMinimumLogLevelToken,
7226
+ parseNodeTypeToken,
7227
+ parseStrictBooleanToken,
7228
+ percentile,
7229
+ pickOptionalTrackingSelectorString,
7230
+ pickTrackingNumber,
7231
+ produceOrConsumeTrackingPayload,
7232
+ readConfiguredSinkName,
7233
+ readRuntimeRedisCorrelationStoreOptions,
7234
+ readRuntimeTrackingId,
7235
+ recordPluginLifecycleError,
7236
+ requireNonEmpty,
7237
+ attachScenarioStatsAliases,
7238
+ attachNodeStatsAliases,
7239
+ attachRunResultAliases,
7240
+ resolveClusterExecutionMode,
7241
+ resolveThresholdActualValue,
7242
+ resetCrossPlatformReportRegistries,
7243
+ resolveSinkDispose,
7244
+ resolveSinkInit,
7245
+ resolveSinkName,
7246
+ resolveSinkSaveRealtimeMetrics,
7247
+ resolveSinkSaveRealtimeStats,
7248
+ resolveSinkSaveRunResult,
7249
+ resolveSinkStart,
7250
+ resolveSinkStop,
7251
+ resolveWorkerPlugins,
7252
+ runtimeParseBodyAsObject,
7253
+ sanitizeReportFileName,
7254
+ sanitizeTrackingNamespacePart,
7255
+ setRuntimeJsonPathValue,
7256
+ stripCaseInsensitivePrefix,
7257
+ toBoolean,
7258
+ toDetailedRunResultFromNodeStats,
7259
+ toInt,
7260
+ toNullableInt,
7261
+ toNumber,
7262
+ toRunContext,
7263
+ toTrackingStringMap,
7264
+ tryParseDotnetDurationMs,
7265
+ tryParseMinimumLogLevelToken,
7266
+ tryParseNodeTypeToken,
7267
+ tryReadConfigValue,
7268
+ validateNamedReportingSinks,
7269
+ validateRegisteredScenarios,
7270
+ validateRuntimeRedisCorrelationStoreConfiguration,
7271
+ validateRuntimeTrackingConfiguration,
7272
+ validateScenarioNames,
7273
+ waitForTrackingOutcome,
7274
+ withLifecycleErrors
7275
+ };