fast-check
Version:
Property based testing framework for JavaScript (like QuickCheck)
86 lines (85 loc) • 3.28 kB
JavaScript
import { bigUintN } from './BigIntArbitrary.js';
import { Arbitrary } from './definition/Arbitrary.js';
import { Shrinkable } from './definition/Shrinkable.js';
export function countToggledBits(n) {
let count = 0;
while (n > BigInt(0)) {
if (n & BigInt(1))
++count;
n >>= BigInt(1);
}
return count;
}
export function computeNextFlags(flags, nextSize) {
const allowedMask = (BigInt(1) << BigInt(nextSize)) - BigInt(1);
const preservedFlags = flags & allowedMask;
let numMissingFlags = countToggledBits(flags - preservedFlags);
let nFlags = preservedFlags;
for (let mask = BigInt(1); mask <= allowedMask && numMissingFlags !== 0; mask <<= BigInt(1)) {
if (!(nFlags & mask)) {
nFlags |= mask;
--numMissingFlags;
}
}
return nFlags;
}
class MixedCaseArbitrary extends Arbitrary {
constructor(stringArb, toggleCase) {
super();
this.stringArb = stringArb;
this.toggleCase = toggleCase;
}
computeTogglePositions(chars) {
const positions = [];
for (let idx = 0; idx !== chars.length; ++idx) {
if (this.toggleCase(chars[idx]) !== chars[idx])
positions.push(idx);
}
return positions;
}
wrapper(rawCase, chars, togglePositions, flags) {
const newChars = chars.slice();
for (let idx = 0, mask = BigInt(1); idx !== togglePositions.length; ++idx, mask <<= BigInt(1)) {
if (flags & mask)
newChars[togglePositions[idx]] = this.toggleCase(newChars[togglePositions[idx]]);
}
return new Shrinkable(newChars.join(''), () => this.shrinkImpl(rawCase, chars, togglePositions, flags));
}
shrinkImpl(rawCase, chars, togglePositions, flags) {
return rawCase
.shrink()
.map((s) => {
const nChars = [...s.value_];
const nTogglePositions = this.computeTogglePositions(nChars);
const nFlags = computeNextFlags(flags, nTogglePositions.length);
return this.wrapper(s, nChars, nTogglePositions, nFlags);
})
.join(bigUintN(togglePositions.length)
.shrinkableFor(flags)
.shrink()
.map((nFlags) => {
return this.wrapper(new Shrinkable(rawCase.value), chars, togglePositions, nFlags.value_);
}));
}
generate(mrng) {
const rawCaseShrinkable = this.stringArb.generate(mrng);
const chars = [...rawCaseShrinkable.value_];
const togglePositions = this.computeTogglePositions(chars);
const flagsArb = bigUintN(togglePositions.length);
const flags = flagsArb.generate(mrng).value_;
return this.wrapper(rawCaseShrinkable, chars, togglePositions, flags);
}
}
function defaultToggleCase(rawChar) {
const upper = rawChar.toUpperCase();
if (upper !== rawChar)
return upper;
return rawChar.toLowerCase();
}
export function mixedCase(stringArb, constraints) {
if (typeof BigInt === 'undefined') {
throw new Error(`mixedCase requires BigInt support`);
}
const toggleCase = (constraints && constraints.toggleCase) || defaultToggleCase;
return new MixedCaseArbitrary(stringArb, toggleCase);
}