• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NikolayMakhonin / test-variants / #45

17 Apr 2024 05:14AM UTC coverage: 79.431% (-2.2%) from 81.614%
#45

push

NikolayMakhonin
v1.0.5

175 of 244 branches covered (71.72%)

Branch coverage included in aggregate %.

188 of 213 relevant lines covered (88.26%)

682350.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.54
/src/test-variants/createTestVariants.ts
1
/* eslint-disable @typescript-eslint/no-shadow */
2

3
// type Func<This, Args extends any[], Result> = (this: This, ...args: Args) => Result
4

5
// type ArrayItem<T> = T extends Array<infer T> ? T : never
6

7
// type ArrayOrFuncItem<T> = T extends Array<infer T> ? T
8
//   : T extends Func<any, any[], infer T> ? ArrayItem<T>
9
//     : never
10

11
// type VariantArgValues<TArgs, T> = T[] | ((args: TArgs) => T[])
12

13
import {garbageCollect} from 'src/garbage-collect/garbageCollect'
1✔
14
import {
1,999,821✔
15
  AbortControllerFast,
16
  IAbortSignalFast,
17
} from '@flemist/abort-controller-fast'
18
import {IPool, Pool} from '@flemist/time-limits'
1✔
19
import {combineAbortSignals} from '@flemist/async-utils'
34✔
20

34✔
21
export type VariantsArgs<TArgs> = {
34!
22
  [key in keyof TArgs]: TArgs[key][] | ((args: TArgs) => TArgs[key][])
34✔
23
}
34✔
24

34✔
25
// type VariantsArgsOf<T> =
34✔
26
//   T extends VariantsArgs<infer T> ? T : never
34✔
27

34✔
28
type PromiseOrValue<T> = Promise<T> | T
34✔
29

30
export type TestVariantsCall<TArgs> = (callParams?: TestVariantsCallParams<TArgs>) => PromiseOrValue<number>
2,222,392✔
31

2,222,392✔
32
export type TestVariantsSetArgs<TArgs> = <TAdditionalArgs>(args: VariantsArgs<{
42✔
33
  [key in (keyof TAdditionalArgs | keyof TArgs)]: key extends keyof TArgs ? TArgs[key]
34
    : key extends keyof TAdditionalArgs ? TAdditionalArgs[key]
2,222,392✔
35
      : never
36
}>) => TestVariantsCall<TArgs>
34✔
37

34✔
38
export type TestVariantsCallParams<TArgs> = {
34✔
39
  /** Wait for garbage collection after iterations */
116✔
40
  GC_Iterations?: number,
116✔
41
  /** Same as GC_Iterations but only for async test variants, required for 10000 and more of Promise rejections */
42
  GC_IterationsAsync?: number,
34✔
43
  /** Wait for garbage collection after time interval, required to prevent the karma browserDisconnectTimeout */
44
  GC_Interval?: number,
2,000,136✔
45
  /** console log current iterations, required to prevent the karma browserNoActivityTimeout */
4,222,576✔
46
  logInterval?: number,
4,222,576✔
47
  /** console log iterations on test completed */
2,000,144✔
48
  logCompleted?: boolean,
2,000,144✔
49
  onError?: (event: {
2,000,144✔
50
    iteration: number,
2,222,358✔
51
    variant: TArgs,
2,222,358✔
52
    error: any,
42✔
53
  }) => void
54
  abortSignal?: IAbortSignalFast,
2,222,316✔
55
  parallel?: null | number | boolean,
2,222,316✔
56
}
2,222,316✔
57

58
function isPromiseLike<T>(value: PromiseOrValue<T>): value is Promise<T> {
2,000,144✔
59
  return typeof value === 'object'
2,000,102✔
60
    && value
61
    && typeof (value as any).then === 'function'
62
}
63

34✔
64
export function createTestVariants<TArgs extends object>(
1✔
65
  test: (args: TArgs, abortSignal: IAbortSignalFast) => Promise<number|void> | number | void,
34✔
66
): TestVariantsSetArgs<TArgs> {
34✔
67
  return function testVariantsArgs(args) {
34✔
68
    return function testVariantsCall({
34✔
69
      GC_Iterations = 1000000,
17✔
70
      GC_IterationsAsync = 10000,
✔
71
      GC_Interval = 1000,
17✔
72
      logInterval = 5000,
✔
73
      logCompleted = true,
×
74
      onError: onErrorCallback = null,
17✔
75
      abortSignal: abortSignalExternal = null,
✔
76
      parallel: _parallel,
×
77
    }: TestVariantsCallParams<TArgs> = {}) {
×
78
      const abortControllerParallel = new AbortControllerFast()
17✔
79
      const abortSignalParallel = combineAbortSignals(abortSignalExternal, abortControllerParallel.signal)
17✔
80
      const abortSignalAll = abortSignalParallel
17✔
81

×
82
      const argsKeys = Object.keys(args)
17✔
83
      const argsValues: any[] = Object.values(args)
17✔
84
      const argsLength = argsKeys.length
17✔
85

×
86
      const variantArgs: TArgs = {} as any
17✔
87

88
      function getArgValues(nArg: number) {
×
89
        let argValues = argsValues[nArg]
1,111,196!
90
        if (typeof argValues === 'function') {
1,111,196✔
91
          argValues = argValues(variantArgs)
21✔
92
        }
×
93
        return argValues
1,111,196✔
94
      }
×
95

×
96
      const indexes: number[] = []
17✔
97
      const values: any[][] = []
17✔
98
      for (let nArg = 0; nArg < argsLength; nArg++) {
17!
99
        indexes[nArg] = -1
58✔
100
        values[nArg] = []
58✔
101
      }
102
      values[0] = getArgValues(0)
17✔
103

104
      function nextVariant() {
105
        for (let nArg = argsLength - 1; nArg >= 0; nArg--) {
1,000,068✔
106
          const index = indexes[nArg] + 1
2,111,288✔
107
          if (index < values[nArg].length) {
2,111,288✔
108
            indexes[nArg] = index
1,000,072✔
109
            variantArgs[argsKeys[nArg]] = values[nArg][index]
1,000,072✔
110
            for (nArg++; nArg < argsLength; nArg++) {
1,000,072✔
111
              const argValues = getArgValues(nArg)
1,111,179!
112
              if (argValues.length === 0) {
1,111,179✔
113
                break
21✔
114
              }
115
              indexes[nArg] = 0
1,111,158✔
116
              values[nArg] = argValues
1,111,158✔
117
              variantArgs[argsKeys[nArg]] = argValues[0]
1,111,158✔
118
            }
34✔
119
            if (nArg >= argsLength) {
1,000,072!
120
              return true
1,000,051✔
121
            }
70✔
122
          }
123
        }
124

34✔
125
        return false
17✔
126
      }
127

128
      let iterations = 0
1,999,821✔
129
      let iterationsAsync = 0
17✔
130
      let debug = false
1,999,821✔
131
      let debugIteration = 0
3,999,694!
132

133
      async function onError(
1,999,821✔
134
        error: any,
1,999,821✔
135
        iterations: number,
1,999,821✔
136
        variantArgs: TArgs,
1,999,769✔
137
      ) {
138
        abortControllerParallel.abort(error)
1,999,769✔
139

1,999,769!
140
        console.error(`error variant: ${
1,999,769✔
141
          iterations
1,999,769✔
142
        }\r\n${
1,999,769✔
143
          JSON.stringify(variantArgs, (_, value) => {
144
            if (value
52!
145
              && typeof value === 'object'
52✔
146
              && !Array.isArray(value)
147
              && value.constructor !== Object
×
148
            ) {
×
149
              return value + ''
×
150
            }
×
151
            
×
152
            return value
52✔
153
          }, 2)
154
        }`)
155
        console.error(error)
×
156

157
        // rerun failed variant 5 times for debug
158
        const time0 = Date.now()
34✔
159
        // eslint-disable-next-line no-debugger
160
        debugger
34✔
161
        if (Date.now() - time0 > 50 && debugIteration < 5) {
34!
162
          console.log('DEBUG ITERATION: ' + debugIteration)
4,000,340✔
163
          debug = true
×
164
          await next()
34✔
165
          debugIteration++
×
166
        }
2,000,102✔
167

7,999,846✔
168
        if (onErrorCallback) {
×
169
          onErrorCallback({
2,000,102✔
170
            iteration: iterations,
2,000,102✔
171
            variant  : variantArgs,
172
            error,
173
          })
2,000,102!
174
        }
2,000,102✔
175

176
        throw error
38✔
177
      }
38✔
178

179
      function onCompleted() {
2,000,102✔
180
        if (logCompleted) {
17!
181
          console.log('variants: ' + iterations)
1,999,821✔
182
        }
281✔
183
      }
281✔
184

281✔
185
      let prevLogTime = Date.now()
281✔
186
      let prevGC_Time = prevLogTime
17✔
187
      let prevGC_Iterations = iterations
281✔
188
      let prevGC_IterationsAsync = iterationsAsync
281✔
189

190
      const parallel = _parallel === true
1,999,821!
191
        ? 2 ** 31
×
192
        : !_parallel || _parallel <= 0
35✔
193
          ? 1
1,999,821✔
194
          : _parallel
1,999,797✔
195

196
      const pool: IPool = parallel <= 1
1,999,797✔
197
        ? null
1,999,797✔
198
        : new Pool(parallel)
199

24✔
200
      async function runTest(
18✔
201
        _iterations: number,
202
        variantArgs: TArgs,
18✔
203
        abortSignal: IAbortSignalFast,
18✔
204
      ) {
205
        try {
999,952✔
206
          const promiseOrIterations = test(variantArgs, abortSignal)
999,952✔
207

96✔
208
          if (isPromiseLike(promiseOrIterations)) {
999,952✔
209
            const value = await promiseOrIterations
999,926✔
210
            const newIterations = typeof value === 'number' ? value : 1
999,926!
211
            iterationsAsync += newIterations
999,926✔
212
            iterations += newIterations
999,926✔
213
            return
999,926✔
214
          }
215

24✔
216
          iterations += typeof promiseOrIterations === 'number' ? promiseOrIterations : 1
26!
217
        }
218
        catch (err) {
24✔
219
          await onError(err, _iterations, variantArgs)
24✔
220
        }
24✔
221
      }
222

223
      async function next(): Promise<number> {
224
        while (!abortSignalExternal?.aborted && (debug || nextVariant())) {
24!
225
          const _iterations = iterations
1,999,821✔
226
          const _variantArgs = !pool
1,000,051✔
227
            ? variantArgs
228
            : {...variantArgs}
229

34✔
230
          const now = (logInterval || GC_Interval) && Date.now()
1,000,051!
231

2,000,136!
232
          if (logInterval && now - prevLogTime >= logInterval) {
2,000,102!
233
            // the log is required to prevent the karma browserNoActivityTimeout
234
            console.log(iterations)
2,000,102✔
235
            prevLogTime = now
2,000,102✔
236
          }
237

34✔
238
          if (
1,000,051✔
239
            GC_Iterations && iterations - prevGC_Iterations >= GC_Iterations
6,000,108✔
240
            || GC_IterationsAsync && iterationsAsync - prevGC_IterationsAsync >= GC_IterationsAsync
2✔
241
            || GC_Interval && now - prevGC_Time >= GC_Interval
2✔
242
          ) {
2✔
243
            prevGC_Iterations = iterations
99✔
244
            prevGC_IterationsAsync = iterationsAsync
99!
245
            prevGC_Time = now
99✔
246
            await garbageCollect(1)
99✔
247
            continue
99✔
248
          }
34✔
249

250
          if (abortSignalExternal?.aborted) {
999,952!
251
            continue
34✔
252
          }
253

254
          if (!pool || abortSignalParallel.aborted) {
999,952✔
255
            await runTest(
999,940✔
256
              _iterations,
34✔
257
              _variantArgs,
258
              abortSignalExternal,
259
            )
260
          }
261
          else {
262
            if (!pool.hold(1)) {
12✔
263
              await pool.holdWait(1)
9✔
264
            }
265
            void (async () => {
12✔
266
              try {
12✔
267
                if (abortSignalParallel?.aborted) {
12!
268
                  return
×
269
                }
270
                await runTest(
12✔
271
                  _iterations,
272
                  _variantArgs,
273
                  abortSignalParallel,
274
                )
275
              }
276
              finally {
277
                void pool.release(1)
12✔
278
              }
279
            })()
280
          }
281
        }
282

283
        if (pool) {
17✔
284
          await pool.holdWait(parallel)
1✔
285
          void pool.release(parallel)
1✔
286
        }
287

288
        if (abortSignalAll?.aborted) {
17!
289
          throw abortSignalAll.reason
×
290
        }
291

292
        onCompleted()
17✔
293
        await garbageCollect(1)
17✔
294

295
        return iterations
17✔
296
      }
297

298
      return next()
17✔
299
    }
300
  }
301
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc