massping
Version:
Mass send http requests for test web application
216 lines (185 loc) • 7.54 kB
JavaScript
/**
* @file lib/generator.js
* @description some generators
*/
import { randomUUID } from 'crypto';
import fs from 'fs';
import { spawnSync, execSync } from 'child_process';
import { trimFalsy } from './helper.js';
/**
* @callback Ticker
* @returns {{ value: any, overflow: boolean }}
*/
/**
* Creates a sequence generator that produces values from `start` to `end` with a specified `step`.
* When the sequence overflows, it wraps around to the start.
*
* @param {number} start - The starting value of the sequence.
* @param {number} end - The ending value of the sequence (inclusive).
* @param {number} step - The step size for each iteration.
* @returns {Ticker} A function that returns the next value in the sequence and whether it overflowed.
*/
export function seq(start, end = Number.MAX_SAFE_INTEGER, step = 1) {
let current = start;
let nextOverflow = false; // 标记下一次调用是否应返回overflow
return function () {
const value = current;
const overflow = nextOverflow;
// 计算下一个值并检查是否需要重置
let next = current + step;
nextOverflow = false;
if (next > end) {
next = start;
nextOverflow = true;
}
current = next;
return { value, overflow };
};
}
/**
*
* @param {number} min - The min value
* @param {number} max - The max value (inclusive).
* @param {number} countdown - set overflow at after the countdown is completed
* @returns {Ticker} A function that returns the next value in the random and whether it overflowed.
*/export function rand(min, max, countdown) {
let count = 0;
return () => {
let value = Math.floor(Math.random() * (max + 1 - min)) + min;
let overflow = true;
if (countdown) {
overflow = (count === countdown);
count = overflow ? 1 : count + 1;
}
return { value, overflow };
};
}
export function choose(values, orderly) {
// const pool = values.filter(Boolean).map(v => v.trim());
const pool = trimFalsy(values.map(v => v.trim()));
const tick = orderly
? seq(0, pool.length - 1)
: rand(0, pool.length - 1, pool.length)
return () => {
let x = tick()
let value = pool[x.value]
let overflow = x.overflow
return { value, overflow }
}
}
export function chooseFromFile(file, orderly) {
const lines = fs.readFileSync(file, 'utf-8').trim().split('\n')
return choose(lines, orderly)
}
export function randText(chars, minLength, maxLength) {
const ntick = rand(minLength, maxLength)
const itick = rand(0, chars.length - 1)
return () => {
let arr = Array(ntick().value)
for (let i = 0; i < arr.length; i++) {
arr[i] = chars[itick().value]
}
return { value: arr.join(''), overflow: true }
}
}
export function time(secondsUnit = false) {
return secondsUnit
? (() => ({ value: parseInt(Date.now() / 1000), overflow: true }))
: () => ({ value: Date.now(), overflow: true })
}
/**
*
* @param {Iterable<Iterable>} iterables
* @param {string|null} separator
* @returns {Ticker}
*/
export function product(iterables, separator) {
// 如果任意集合为空,则笛卡尔积为空,返回恒定的空数据
if (iterables.some(iterable => iterable.length === 0)) {
return function ticker() {
return { value: typeof separator == 'string' ? '' : [], overflow: true };
};
}
// 初始化每个维度的索引(全0)和下次溢出的标志
let indices = iterables.map(() => 0);
let nextOverflow = false;
return () => {
// 根据当前索引获取各维度的值组成当前组合
const value = iterables.map((iterable, i) => iterable[indices[i]]);
const overflow = nextOverflow; // 本次返回的溢出标志是上次设置的
nextOverflow = false; // 重置为false,准备计算下次状态
// 进位更新:从最后一个维度开始向前进位
let carry = 1;
for (let i = indices.length - 1; i >= 0; i--) {
if (carry === 0) break; // 无进位时提前终止循环
indices[i] += carry; // 当前维度加进位
carry = 0; // 清除进位
// 检查是否超出当前维度的值池大小
if (indices[i] >= iterables[i].length) {
indices[i] = 0; // 重置当前维度索引
carry = 1; // 设置向前维度的进位
}
}
// 若最终仍有进位(所有维度都已重置),则设置下次溢出标志
if (carry === 1) {
nextOverflow = true;
}
return {
value: typeof separator == 'string'
? value.join(separator)
: value,
overflow
};
};
}
/**
* Creates a generator for the Cartesian power of an iterable, producing all combinations from `startExponent` to `endExponent`.
* When the last combination of the highest exponent is produced, the next call wraps around to the first combination of the start exponent and sets the overflow flag.
*
* @param {Iterable} iterable - The input iterable (e.g., string or array).
* @param {number} startExponent - The starting exponent (inclusive).
* @param {number} endExponent - The ending exponent (inclusive).
* @param {string|null} separator - If a string, combinations are joined by this separator; otherwise, arrays are returned.
* @returns {Ticker} A function that returns the next combination and whether it overflowed (due to reset).
*/
export function power(iterable, startExponent, endExponent, separator) {
// Convert iterable to an array to handle strings and check length
const length = iterable.length;
// Handle empty iterable: return constant empty value with overflow=true
if (length === 0) {
return () => {
return {
value: typeof separator === 'string' ? '' : [],
overflow: true
};
};
}
let currentExponent = startExponent;
// Create initial product ticker for the start exponent
let currentTicker = product(Array(currentExponent).fill(iterable), separator);
let nextOverflow = false; // Tracks if the next call should return overflow=true (for reset)
return function ticker() {
// Save overflow flag for this call and reset nextOverflow
let overflow = nextOverflow;
nextOverflow = false;
// Get the next combination from the current exponent's product
let result = currentTicker();
// If the current exponent's product overflowed (all combinations generated)
if (result.overflow) {
currentExponent++; // Move to next exponent
if (currentExponent > endExponent) {
// Reset to start exponent and mark that this call is a reset
currentExponent = startExponent;
overflow = true; // This call returns overflow=true due to reset
}
// Create new product ticker for the new exponent
currentTicker = product(Array(currentExponent).fill(iterable), separator);
// Get the first combination of the new exponent
result = currentTicker();
}
return {
value: result.value,
overflow,
};
};
}