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

NikolayMakhonin / test-variants / #44

12 Apr 2024 08:49AM UTC coverage: 81.614% (-0.2%) from 81.818%
#44

push

NikolayMakhonin
v1.0.4

175 of 232 branches covered (75.43%)

Branch coverage included in aggregate %.

189 of 214 relevant lines covered (88.32%)

683835.83 hits per line

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

81.84
/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,841✔
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

34!
104
      function nextVariant() {
34✔
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
            }
119
            if (nArg >= argsLength) {
1,000,072✔
120
              return true
1,999,841✔
121
            }
122
          }
1,999,841✔
123
        }
3,999,734!
124

125
        return false
1,999,841✔
126
      }
1,999,841✔
127

1,999,841✔
128
      let iterations = 0
1,999,789✔
129
      let iterationsAsync = 0
17✔
130
      let debug = false
1,999,789✔
131
      let debugIteration = 0
1,999,789!
132

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

×
140
        console.error(`error variant: ${
×
141
          iterations
142
        }\r\n${
×
143
          JSON.stringify(variantArgs, null, 2)
×
144
        }`)
52✔
145
        console.error(error)
×
146

147
        // rerun failed variant 5 times for debug
148
        const time0 = Date.now()
×
149
        // eslint-disable-next-line no-debugger
150
        debugger
34✔
151
        if (Date.now() - time0 > 50 && debugIteration < 5) {
×
152
          console.log('DEBUG ITERATION: ' + debugIteration)
34✔
153
          debug = true
34✔
154
          await next()
4,000,340✔
155
          debugIteration++
×
156
        }
34✔
157

158
        if (onErrorCallback) {
2,000,102!
159
          onErrorCallback({
7,999,886✔
160
            iteration: iterations,
161
            variant  : variantArgs,
2,000,102✔
162
            error,
2,000,102✔
163
          })
164
        }
165

2,000,102!
166
        throw error
2,000,102✔
167
      }
168

33✔
169
      function onCompleted() {
33✔
170
        if (logCompleted) {
17!
171
          console.log('variants: ' + iterations)
2,000,102✔
172
        }
173
      }
1,999,841✔
174

261✔
175
      let prevLogTime = Date.now()
261✔
176
      let prevGC_Time = prevLogTime
261✔
177
      let prevGC_Iterations = iterations
261✔
178
      let prevGC_IterationsAsync = iterationsAsync
17✔
179

261✔
180
      const parallel = _parallel === true
261!
181
        ? 2 ** 31
182
        : !_parallel || _parallel <= 0
1,999,841!
183
          ? 1
×
184
          : _parallel
185

1,999,841✔
186
      const pool: IPool = parallel <= 1
1,999,817✔
187
        ? null
188
        : new Pool(parallel)
1,999,817✔
189

1,999,817✔
190
      async function runTest(
191
        _iterations: number,
24✔
192
        variantArgs: TArgs,
18✔
193
        abortSignal: IAbortSignalFast,
194
      ) {
18✔
195
        try {
999,952✔
196
          const promiseOrIterations = test(variantArgs, abortSignal)
999,952✔
197

24✔
198
          if (isPromiseLike(promiseOrIterations)) {
999,952✔
199
            const value = await promiseOrIterations
999,926✔
200
            const newIterations = typeof value === 'number' ? value : 1
999,926!
201
            iterationsAsync += newIterations
999,926✔
202
            iterations += newIterations
999,926!
203
            return
999,926✔
204
          }
205

24✔
206
          iterations += typeof promiseOrIterations === 'number' ? promiseOrIterations : 1
26!
207
        }
24✔
208
        catch (err) {
24✔
209
          await onError(err, _iterations, variantArgs)
×
210
        }
24✔
211
      }
24✔
212

24✔
213
      async function next(): Promise<number> {
214
        while (!abortSignalExternal?.aborted && (debug || nextVariant())) {
17!
215
          const _iterations = iterations
1,000,051✔
216
          const _variantArgs = !pool
1,000,051✔
217
            ? variantArgs
1,999,841✔
218
            : {...variantArgs}
219

220
          const now = (logInterval || GC_Interval) && Date.now()
1,000,051!
221

34✔
222
          if (logInterval && now - prevLogTime >= logInterval) {
1,000,051!
223
            // the log is required to prevent the karma browserNoActivityTimeout
2,000,136!
224
            console.log(iterations)
2,000,102✔
225
            prevLogTime = now
×
226
          }
2,000,102✔
227

2,000,102✔
228
          if (
1,000,051✔
229
            GC_Iterations && iterations - prevGC_Iterations >= GC_Iterations
34✔
230
            || GC_IterationsAsync && iterationsAsync - prevGC_IterationsAsync >= GC_IterationsAsync
2✔
231
            || GC_Interval && now - prevGC_Time >= GC_Interval
232
          ) {
2✔
233
            prevGC_Iterations = iterations
99✔
234
            prevGC_IterationsAsync = iterationsAsync
99✔
235
            prevGC_Time = now
99✔
236
            await garbageCollect(1)
99!
237
            continue
99✔
238
          }
239

34✔
240
          if (abortSignalExternal?.aborted) {
999,952!
241
            continue
×
242
          }
34✔
243

34✔
244
          if (!pool || abortSignalParallel.aborted) {
999,952✔
245
            await runTest(
999,940✔
246
              _iterations,
247
              _variantArgs,
248
              abortSignalExternal,
34✔
249
            )
250
          }
251
          else {
252
            if (!pool.hold(1)) {
12✔
253
              await pool.holdWait(1)
9✔
254
            }
255
            void (async () => {
12✔
256
              try {
12✔
257
                if (abortSignalParallel?.aborted) {
12!
258
                  return
×
259
                }
260
                await runTest(
12✔
261
                  _iterations,
262
                  _variantArgs,
263
                  abortSignalParallel,
264
                )
265
              }
266
              finally {
267
                void pool.release(1)
12✔
268
              }
269
            })()
270
          }
271
        }
272

273
        if (pool) {
17✔
274
          await pool.holdWait(parallel)
1✔
275
          void pool.release(parallel)
1✔
276
        }
277

278
        if (abortSignalAll?.aborted) {
17!
279
          throw abortSignalAll.reason
×
280
        }
281

282
        onCompleted()
17✔
283
        await garbageCollect(1)
17✔
284

285
        return iterations
17✔
286
      }
287

288
      return next()
17✔
289
    }
290
  }
291
}
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