postman-runtime 7.46.1 → 7.47.1
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/CHANGELOG.yaml +10 -0
- package/dist/index.js +1 -1
- package/lib/runner/cursor.js +14 -3
- package/lib/runner/extensions/control.command.js +13 -4
- package/lib/runner/extensions/event.command.js +5 -0
- package/lib/runner/extensions/parallel.command.js +178 -0
- package/lib/runner/extensions/waterfall.command.js +33 -128
- package/lib/runner/partition-manager.js +324 -0
- package/lib/runner/partition.js +114 -0
- package/lib/runner/run.js +26 -0
- package/lib/runner/util.js +160 -1
- package/package.json +3 -3
package/lib/runner/cursor.js
CHANGED
|
@@ -8,15 +8,21 @@ var _ = require('lodash'),
|
|
|
8
8
|
* @param {Number} [position=0] -
|
|
9
9
|
* @param {Number} [iteration=0] -
|
|
10
10
|
* @param {String} [ref] -
|
|
11
|
+
* @param {Number} [partitionIndex=0] -
|
|
12
|
+
* @param {Number} [partitionCycles=0] -
|
|
11
13
|
* @constructor
|
|
12
14
|
*/
|
|
13
|
-
Cursor = function RunCursor (length, cycles, position, iteration,
|
|
15
|
+
Cursor = function RunCursor (length, cycles, position, iteration, // eslint-disable-line func-name-matching
|
|
16
|
+
ref, partitionIndex, partitionCycles) {
|
|
14
17
|
this.length = Cursor.validate(length, 0);
|
|
15
18
|
this.position = Cursor.validate(position, 0, this.length);
|
|
16
19
|
|
|
17
20
|
this.cycles = Cursor.validate(cycles, 1, 1);
|
|
18
21
|
this.iteration = Cursor.validate(iteration, 0, this.cycles);
|
|
19
22
|
|
|
23
|
+
this.partitionIndex = Cursor.validate(partitionIndex, 0, this.cycles);
|
|
24
|
+
this.partitionCycles = Cursor.validate(partitionCycles, 0, this.cycles);
|
|
25
|
+
|
|
20
26
|
this.ref = ref || uuid.v4();
|
|
21
27
|
};
|
|
22
28
|
|
|
@@ -202,7 +208,9 @@ _.assign(Cursor.prototype, {
|
|
|
202
208
|
var base = {
|
|
203
209
|
ref: this.ref,
|
|
204
210
|
length: this.length,
|
|
205
|
-
cycles: this.cycles
|
|
211
|
+
cycles: this.cycles,
|
|
212
|
+
partitionIndex: this.partitionIndex,
|
|
213
|
+
partitionCycles: this.partitionCycles
|
|
206
214
|
},
|
|
207
215
|
position,
|
|
208
216
|
iteration;
|
|
@@ -265,6 +273,8 @@ _.assign(Cursor.prototype, {
|
|
|
265
273
|
iteration: this.iteration,
|
|
266
274
|
length: this.length,
|
|
267
275
|
cycles: this.cycles,
|
|
276
|
+
partitionIndex: this.partitionIndex,
|
|
277
|
+
partitionCycles: this.partitionCycles,
|
|
268
278
|
empty: this.empty(),
|
|
269
279
|
eof: this.eof(),
|
|
270
280
|
bof: this.bof(),
|
|
@@ -349,7 +359,8 @@ _.assign(Cursor, {
|
|
|
349
359
|
if (!_.isObject(obj)) { return new Cursor(bounds && bounds.length, bounds && bounds.cycles); }
|
|
350
360
|
|
|
351
361
|
// load Cursor values from object
|
|
352
|
-
return new Cursor((bounds || obj).length, (bounds || obj).cycles, obj.position,
|
|
362
|
+
return new Cursor((bounds || obj).length, (bounds || obj).cycles, obj.position,
|
|
363
|
+
obj.iteration, obj.ref, obj.partitionIndex, obj.partitionCycles);
|
|
353
364
|
},
|
|
354
365
|
|
|
355
366
|
/**
|
|
@@ -94,11 +94,20 @@ module.exports = {
|
|
|
94
94
|
abort (userback, payload, next) {
|
|
95
95
|
// clear instruction pool and as such there will be nothing next to execute
|
|
96
96
|
this.pool.clear();
|
|
97
|
-
this.triggers.abort(null, this.state.cursor.current());
|
|
98
97
|
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
}
|
|
102
111
|
|
|
103
112
|
next(null);
|
|
104
113
|
}
|
|
@@ -621,6 +621,11 @@ module.exports = {
|
|
|
621
621
|
result && result._variables &&
|
|
622
622
|
(this.state._variables = new sdk.VariableScope(result._variables));
|
|
623
623
|
|
|
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
|
+
|
|
624
629
|
// persist the mutated request in payload context,
|
|
625
630
|
// @note this will be used for the next prerequest script or
|
|
626
631
|
// upcoming commands(request, httprequest).
|
|
@@ -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
|
+
};
|
|
@@ -1,65 +1,11 @@
|
|
|
1
1
|
var _ = require('lodash'),
|
|
2
2
|
Cursor = require('../cursor'),
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Returns a hash of IDs and Names of items in an array
|
|
12
|
-
*
|
|
13
|
-
* @param {Array} items -
|
|
14
|
-
* @returns {Object}
|
|
15
|
-
*/
|
|
16
|
-
prepareLookupHash = function (items) {
|
|
17
|
-
var hash = {
|
|
18
|
-
ids: {},
|
|
19
|
-
names: {},
|
|
20
|
-
obj: {}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
_.forEach(items, function (item, index) {
|
|
24
|
-
if (item) {
|
|
25
|
-
item.id && (hash.ids[item.id] = index);
|
|
26
|
-
item.name && (hash.names[item.name] = index);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
return hash;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
extractSNR = function (executions, previous) {
|
|
34
|
-
var snr = previous || {};
|
|
35
|
-
|
|
36
|
-
_.isArray(executions) && executions.forEach(function (execution) {
|
|
37
|
-
_.has(_.get(execution, 'result.return'), 'nextRequest') && (
|
|
38
|
-
(snr.defined = true),
|
|
39
|
-
(snr.value = execution.result.return.nextRequest)
|
|
40
|
-
);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
return snr;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Returns the data for the given iteration
|
|
48
|
-
*
|
|
49
|
-
* @function getIterationData
|
|
50
|
-
* @param {Array} data - The data array containing all iterations' data
|
|
51
|
-
* @param {Number} iteration - The iteration to get data for
|
|
52
|
-
* @return {Any} - The data for the iteration
|
|
53
|
-
*/
|
|
54
|
-
getIterationData = function (data, iteration) {
|
|
55
|
-
// if iteration has a corresponding data element use that
|
|
56
|
-
if (iteration < data.length) {
|
|
57
|
-
return data[iteration];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// otherwise use the last data element
|
|
61
|
-
return data[data.length - 1];
|
|
62
|
-
};
|
|
3
|
+
{
|
|
4
|
+
getIterationData,
|
|
5
|
+
prepareVariablesScope,
|
|
6
|
+
processExecutionResult,
|
|
7
|
+
prepareVaultVariableScope
|
|
8
|
+
} = require('../util');
|
|
63
9
|
|
|
64
10
|
/**
|
|
65
11
|
* Adds options
|
|
@@ -71,19 +17,8 @@ module.exports = {
|
|
|
71
17
|
init: function (done) {
|
|
72
18
|
var state = this.state;
|
|
73
19
|
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
new VariableScope(state.environment);
|
|
77
|
-
state.globals = VariableScope.isVariableScope(state.globals) ? state.globals :
|
|
78
|
-
new VariableScope(state.globals);
|
|
79
|
-
state.vaultSecrets = VariableScope.isVariableScope(state.vaultSecrets) ? state.vaultSecrets :
|
|
80
|
-
new VariableScope(state.vaultSecrets);
|
|
81
|
-
state.collectionVariables = VariableScope.isVariableScope(state.collectionVariables) ?
|
|
82
|
-
state.collectionVariables : new VariableScope(state.collectionVariables);
|
|
83
|
-
state._variables = VariableScope.isVariableScope(state.localVariables) ?
|
|
84
|
-
state.localVariables : new VariableScope(state.localVariables);
|
|
85
|
-
|
|
86
|
-
// prepare the vault variable scope
|
|
20
|
+
// prepare the vault variable scope and other variables
|
|
21
|
+
prepareVariablesScope(state);
|
|
87
22
|
prepareVaultVariableScope(state.vaultSecrets);
|
|
88
23
|
|
|
89
24
|
// ensure that the items and iteration data set is in place
|
|
@@ -99,12 +34,14 @@ module.exports = {
|
|
|
99
34
|
});
|
|
100
35
|
this.waterfall = state.cursor; // copy the location object to instance for quick access
|
|
101
36
|
|
|
102
|
-
// queue the
|
|
103
|
-
this.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
37
|
+
// queue the waterfall command if iterations are not parallelized
|
|
38
|
+
if (!this.areIterationsParallelized) {
|
|
39
|
+
this.queue('waterfall', {
|
|
40
|
+
coords: this.waterfall.current(),
|
|
41
|
+
static: true,
|
|
42
|
+
start: true
|
|
43
|
+
});
|
|
44
|
+
}
|
|
108
45
|
|
|
109
46
|
// clear the variable that is supposed to store item name and id lookup hash for easy setNextRequest
|
|
110
47
|
this.snrHash = null; // we populate it in the first SNR call
|
|
@@ -175,57 +112,25 @@ module.exports = {
|
|
|
175
112
|
collectionVariables: this.state.collectionVariables,
|
|
176
113
|
_variables: this.state._variables
|
|
177
114
|
}, function (executionError, executions) {
|
|
178
|
-
|
|
115
|
+
// Use shared utility function to process execution results and handle SNR logic
|
|
116
|
+
var result = processExecutionResult({
|
|
117
|
+
coords: coords,
|
|
118
|
+
executions: executions,
|
|
119
|
+
executionError: executionError,
|
|
120
|
+
runnerOptions: this.options,
|
|
121
|
+
snrHash: this.snrHash,
|
|
122
|
+
items: this.state.items
|
|
123
|
+
}),
|
|
179
124
|
nextCoords,
|
|
180
125
|
seekingToStart,
|
|
181
|
-
stopRunNow
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (!this.options.disableSNR && snr.defined) {
|
|
192
|
-
// prepare the snr lookup hash if it is not already provided
|
|
193
|
-
// @todo - figure out a way to reset this post run complete
|
|
194
|
-
!this.snrHash && (this.snrHash = prepareLookupHash(this.state.items));
|
|
195
|
-
|
|
196
|
-
// if it is null, we do not proceed further and move on
|
|
197
|
-
// see if a request is found in the hash and then reset the coords position to the lookup
|
|
198
|
-
// value.
|
|
199
|
-
(snr.value !== null) && (snr.position = // eslint-disable-next-line no-nested-ternary
|
|
200
|
-
this.snrHash[_.has(this.snrHash.ids, snr.value) ? 'ids' :
|
|
201
|
-
(_.has(this.snrHash.names, snr.value) ? 'names' : 'obj')][snr.value]);
|
|
202
|
-
|
|
203
|
-
snr.valid = _.isNumber(snr.position);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
nextCoords = _.clone(coords);
|
|
207
|
-
|
|
208
|
-
if (snr.valid) {
|
|
209
|
-
// if the position was detected, we set the position to the one previous to the desired location
|
|
210
|
-
// this ensures that the next call to .whatnext() will return the desired position.
|
|
211
|
-
nextCoords.position = snr.position - 1;
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
// if snr was requested, but not valid, we stop this iteration.
|
|
215
|
-
// stopping an iteration is equivalent to seeking the last position of the current
|
|
216
|
-
// iteration, so that the next call to .whatnext() will automatically move to the next
|
|
217
|
-
// iteration.
|
|
218
|
-
(snr.defined || executionError) && (nextCoords.position = nextCoords.length - 1);
|
|
219
|
-
|
|
220
|
-
// If we need to stop on a run, we set the stop flag to true.
|
|
221
|
-
(stopOnFailure && executionError) && (stopRunNow = true);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// @todo - do this in unhacky way
|
|
225
|
-
if (nextCoords.position === -1) {
|
|
226
|
-
nextCoords.position = 0;
|
|
227
|
-
seekingToStart = true;
|
|
228
|
-
}
|
|
126
|
+
stopRunNow;
|
|
127
|
+
|
|
128
|
+
// Update the snrHash if it was created/updated by the utility function
|
|
129
|
+
this.snrHash = result.snrHash;
|
|
130
|
+
|
|
131
|
+
nextCoords = result.nextCoords;
|
|
132
|
+
seekingToStart = result.seekingToStart;
|
|
133
|
+
stopRunNow = result.stopRunNow;
|
|
229
134
|
|
|
230
135
|
this.waterfall.seek(nextCoords.position, nextCoords.iteration, function (err, chngd, coords) {
|
|
231
136
|
// this condition should never arise, so better throw error when this happens
|