Skip to content

Commit 6470ce2

Browse files
committed
Merge branch 'release/7.47.0'
2 parents efeb00e + 2611cc6 commit 6470ce2

36 files changed

+3839
-344
lines changed

.nycrc.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ function configOverrides(testType) {
2222
};
2323
case 'integration-legacy':
2424
return {
25-
statements: 45,
26-
branches: 33.5,
27-
functions: 42.5,
28-
lines: 45
25+
statements: 43,
26+
branches: 32,
27+
functions: 40,
28+
lines: 44
2929
};
3030
default:
3131
return {}

CHANGELOG.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
7.47.0:
2+
date: 2025-09-18
3+
new features:
4+
- GH-1525 Added iterations opimization
5+
16
7.46.1:
27
date: 2025-09-15
38
fixed bugs:

lib/runner/cursor.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,21 @@ var _ = require('lodash'),
88
* @param {Number} [position=0] -
99
* @param {Number} [iteration=0] -
1010
* @param {String} [ref] -
11+
* @param {Number} [partitionIndex=0] -
12+
* @param {Number} [partitionCycles=0] -
1113
* @constructor
1214
*/
13-
Cursor = function RunCursor (length, cycles, position, iteration, ref) { // eslint-disable-line func-name-matching
15+
Cursor = function RunCursor (length, cycles, position, iteration, // eslint-disable-line func-name-matching
16+
ref, partitionIndex, partitionCycles) {
1417
this.length = Cursor.validate(length, 0);
1518
this.position = Cursor.validate(position, 0, this.length);
1619

1720
this.cycles = Cursor.validate(cycles, 1, 1);
1821
this.iteration = Cursor.validate(iteration, 0, this.cycles);
1922

23+
this.partitionIndex = Cursor.validate(partitionIndex, 0, this.cycles);
24+
this.partitionCycles = Cursor.validate(partitionCycles, 0, this.cycles);
25+
2026
this.ref = ref || uuid.v4();
2127
};
2228

@@ -202,7 +208,9 @@ _.assign(Cursor.prototype, {
202208
var base = {
203209
ref: this.ref,
204210
length: this.length,
205-
cycles: this.cycles
211+
cycles: this.cycles,
212+
partitionIndex: this.partitionIndex,
213+
partitionCycles: this.partitionCycles
206214
},
207215
position,
208216
iteration;
@@ -265,6 +273,8 @@ _.assign(Cursor.prototype, {
265273
iteration: this.iteration,
266274
length: this.length,
267275
cycles: this.cycles,
276+
partitionIndex: this.partitionIndex,
277+
partitionCycles: this.partitionCycles,
268278
empty: this.empty(),
269279
eof: this.eof(),
270280
bof: this.bof(),
@@ -349,7 +359,8 @@ _.assign(Cursor, {
349359
if (!_.isObject(obj)) { return new Cursor(bounds && bounds.length, bounds && bounds.cycles); }
350360

351361
// load Cursor values from object
352-
return new Cursor((bounds || obj).length, (bounds || obj).cycles, obj.position, obj.iteration, obj.ref);
362+
return new Cursor((bounds || obj).length, (bounds || obj).cycles, obj.position,
363+
obj.iteration, obj.ref, obj.partitionIndex, obj.partitionCycles);
353364
},
354365

355366
/**

lib/runner/extensions/control.command.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,20 @@ module.exports = {
9494
abort (userback, payload, next) {
9595
// clear instruction pool and as such there will be nothing next to execute
9696
this.pool.clear();
97-
this.triggers.abort(null, this.state.cursor.current());
9897

99-
// execute the userback sent as part of the command and do so in a try block to ensure it does not hamper
100-
// the process tick
101-
backpack.ensure(userback, this) && userback();
98+
// clear all partition pools, if exist
99+
this.partitionManager.dispose();
100+
101+
if (!this.aborted) {
102+
this.aborted = true;
103+
104+
// always trigger abort event here to ensure it's called even if host has been disposed
105+
this.triggers.abort(null, this.state.cursor.current());
106+
107+
// execute the userback sent as part of the command and
108+
// do so in a try block to ensure it does not hamper the process tick
109+
backpack.ensure(userback, this) && userback();
110+
}
102111

103112
next(null);
104113
}

lib/runner/extensions/event.command.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,11 @@ module.exports = {
621621
result && result._variables &&
622622
(this.state._variables = new sdk.VariableScope(result._variables));
623623

624+
if (this.areIterationsParallelized) {
625+
// persist the pm.variables for the next request in the current partition
626+
this.partitionManager.updatePartitionVariables(payload.coords.partitionIndex, result);
627+
}
628+
624629
// persist the mutated request in payload context,
625630
// @note this will be used for the next prerequest script or
626631
// upcoming commands(request, httprequest).
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
var _ = require('lodash'),
2+
{ prepareVaultVariableScope, prepareVariablesScope,
3+
getIterationData, processExecutionResult
4+
} = require('../util');
5+
6+
/**
7+
* Adds options
8+
* disableSNR:Boolean
9+
*
10+
* @type {Object}
11+
*/
12+
module.exports = {
13+
init: function (done) {
14+
// bail out if iterations are not parallelized
15+
if (!this.areIterationsParallelized) {
16+
return done();
17+
}
18+
19+
var state = this.state;
20+
21+
// ensure that the environment, globals and collectionVariables are in VariableScope instance format
22+
prepareVariablesScope(state);
23+
// prepare the vault variable scope
24+
prepareVaultVariableScope(state.vaultSecrets);
25+
26+
// create the partition manager and partition the iterations
27+
this.partitionManager.createPartitions();
28+
const { partitions } = this.partitionManager;
29+
30+
// queue a parallel command for each of our partitions
31+
partitions.forEach((partition) => {
32+
this.queue('parallel', {
33+
coords: partition.cursor.current(),
34+
static: true,
35+
start: true
36+
});
37+
});
38+
39+
done();
40+
},
41+
42+
triggers: ['beforeIteration', 'iteration'],
43+
44+
prototype: {
45+
/**
46+
* Starts a parallel iteration
47+
*
48+
* @param {Number} index - The index of the partition to run
49+
* @param {Object} localVariables - Local variables for the iteration
50+
* @param {Function} callback - The callback to call when the iteration is complete
51+
*/
52+
startParallelIteration (index, localVariables, callback) {
53+
this.partitionManager.runSinglePartition(index, localVariables, callback);
54+
},
55+
56+
/**
57+
* Stops a parallel iteration
58+
*
59+
* @param {Number} index - The index of the partition to stop
60+
* @param {Function} callback - The callback to call when the iteration is complete
61+
*/
62+
stopParallelIteration (index, callback) {
63+
this.partitionManager.stopSinglePartition(index, callback);
64+
}
65+
},
66+
67+
process: {
68+
/**
69+
* This processor queues partitions in parallel.
70+
*
71+
* @param {Object} payload -
72+
* @param {Object} payload.coords -
73+
* @param {Boolean} [payload.static=false] -
74+
* @param {Function} next -
75+
*/
76+
parallel (payload, next) {
77+
var partitionIndex = payload.coords.partitionIndex,
78+
partition = this.partitionManager.partitions[partitionIndex],
79+
coords = payload.static ? payload.coords : partition.cursor.whatnext(payload.coords),
80+
item = this.state.items[coords.position],
81+
delay;
82+
83+
84+
if (coords.empty) {
85+
return next();
86+
}
87+
88+
if (payload.stopRunNow) {
89+
this.triggers.iteration(null, coords);
90+
91+
return next();
92+
}
93+
94+
// if it is a beginning of a run, we need to raise events for iteration start
95+
if (payload.start) {
96+
this.triggers.beforeIteration(null, coords);
97+
}
98+
99+
// since we will never reach coords.eof for some partitions because each cursor
100+
// contains cycles for the entire run, we are breaking off early here.
101+
// this has been done to keep the contract of a cursor intact.
102+
// cycles is defined as "number of iterations in the run"
103+
if (coords.iteration === partition.startIndex + coords.partitionCycles) {
104+
this.triggers.iteration(null, payload.coords);
105+
106+
return next();
107+
}
108+
109+
if (coords.cr) {
110+
delay = _.get(this.options, 'delay.iteration', 0);
111+
112+
this.triggers.iteration(null, payload.coords);
113+
this.triggers.beforeIteration(null, coords);
114+
}
115+
116+
117+
if (coords.eof) {
118+
this.triggers.iteration(null, coords);
119+
120+
return next();
121+
}
122+
123+
this.queueDelay(function () {
124+
this.queue('item', {
125+
item: item,
126+
coords: coords,
127+
data: getIterationData(this.state.data, coords.iteration + partition.startIndex),
128+
environment: partition.variables.environment,
129+
globals: partition.variables.globals,
130+
vaultSecrets: this.state.vaultSecrets,
131+
collectionVariables: partition.variables.collectionVariables,
132+
_variables: partition.variables._variables
133+
}, function (executionError, executions) {
134+
// Use shared utility function to process execution results and handle SNR logic
135+
var result = processExecutionResult({
136+
coords: coords,
137+
executions: executions,
138+
executionError: executionError,
139+
runnerOptions: this.options,
140+
snrHash: this.snrHash,
141+
items: this.state.items
142+
}),
143+
nextCoords,
144+
seekingToStart,
145+
stopRunNow;
146+
147+
// Update the snrHash if it was created/updated by the utility function
148+
this.snrHash = result.snrHash;
149+
150+
nextCoords = result.nextCoords;
151+
seekingToStart = result.seekingToStart;
152+
stopRunNow = result.stopRunNow;
153+
154+
155+
partition.cursor.seek(nextCoords.position, nextCoords.iteration, function (err, chngd, coords) {
156+
// this condition should never arise, so better throw error when this happens
157+
if (err) {
158+
throw err;
159+
}
160+
161+
this.queue('parallel', {
162+
coords: {
163+
...coords,
164+
partitionIndex
165+
},
166+
static: seekingToStart,
167+
stopRunNow: stopRunNow
168+
});
169+
}, this);
170+
});
171+
}.bind(this), {
172+
time: delay,
173+
source: 'iteration',
174+
cursor: coords
175+
}, next);
176+
}
177+
}
178+
};

0 commit comments

Comments
 (0)