UNPKG

fast-check

Version:

Property based testing framework for JavaScript (like QuickCheck)

167 lines (166 loc) 7.39 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FrequencyArbitrary = void 0; const Stream_1 = require("../../stream/Stream"); const Arbitrary_1 = require("../../check/arbitrary/definition/Arbitrary"); const Value_1 = require("../../check/arbitrary/definition/Value"); const DepthContext_1 = require("./helpers/DepthContext"); const MaxLengthFromMinLength_1 = require("./helpers/MaxLengthFromMinLength"); const globals_1 = require("../../utils/globals"); const safePositiveInfinity = Number.POSITIVE_INFINITY; const safeMaxSafeInteger = Number.MAX_SAFE_INTEGER; const safeNumberIsInteger = Number.isInteger; const safeMathFloor = Math.floor; const safeMathPow = Math.pow; const safeMathMin = Math.min; class FrequencyArbitrary extends Arbitrary_1.Arbitrary { static from(warbs, constraints, label) { if (warbs.length === 0) { throw new Error(`${label} expects at least one weighted arbitrary`); } let totalWeight = 0; for (let idx = 0; idx !== warbs.length; ++idx) { const currentArbitrary = warbs[idx].arbitrary; if (currentArbitrary === undefined) { throw new Error(`${label} expects arbitraries to be specified`); } const currentWeight = warbs[idx].weight; totalWeight += currentWeight; if (!safeNumberIsInteger(currentWeight)) { throw new Error(`${label} expects weights to be integer values`); } if (currentWeight < 0) { throw new Error(`${label} expects weights to be superior or equal to 0`); } } if (totalWeight <= 0) { throw new Error(`${label} expects the sum of weights to be strictly superior to 0`); } const sanitizedConstraints = { depthBias: (0, MaxLengthFromMinLength_1.depthBiasFromSizeForArbitrary)(constraints.depthSize, constraints.maxDepth !== undefined), maxDepth: constraints.maxDepth != undefined ? constraints.maxDepth : safePositiveInfinity, withCrossShrink: !!constraints.withCrossShrink, }; return new FrequencyArbitrary(warbs, sanitizedConstraints, (0, DepthContext_1.getDepthContextFor)(constraints.depthIdentifier)); } constructor(warbs, constraints, context) { super(); this.warbs = warbs; this.constraints = constraints; this.context = context; let currentWeight = 0; this.cumulatedWeights = []; for (let idx = 0; idx !== warbs.length; ++idx) { currentWeight += warbs[idx].weight; (0, globals_1.safePush)(this.cumulatedWeights, currentWeight); } this.totalWeight = currentWeight; } generate(mrng, biasFactor) { if (this.mustGenerateFirst()) { return this.safeGenerateForIndex(mrng, 0, biasFactor); } const selected = mrng.nextInt(this.computeNegDepthBenefit(), this.totalWeight - 1); for (let idx = 0; idx !== this.cumulatedWeights.length; ++idx) { if (selected < this.cumulatedWeights[idx]) { return this.safeGenerateForIndex(mrng, idx, biasFactor); } } throw new Error(`Unable to generate from fc.frequency`); } canShrinkWithoutContext(value) { return this.canShrinkWithoutContextIndex(value) !== -1; } shrink(value, context) { if (context !== undefined) { const safeContext = context; const selectedIndex = safeContext.selectedIndex; const originalBias = safeContext.originalBias; const originalArbitrary = this.warbs[selectedIndex].arbitrary; const originalShrinks = originalArbitrary .shrink(value, safeContext.originalContext) .map((v) => this.mapIntoValue(selectedIndex, v, null, originalBias)); if (safeContext.clonedMrngForFallbackFirst !== null) { if (safeContext.cachedGeneratedForFirst === undefined) { safeContext.cachedGeneratedForFirst = this.safeGenerateForIndex(safeContext.clonedMrngForFallbackFirst, 0, originalBias); } const valueFromFirst = safeContext.cachedGeneratedForFirst; return Stream_1.Stream.of(valueFromFirst).join(originalShrinks); } return originalShrinks; } const potentialSelectedIndex = this.canShrinkWithoutContextIndex(value); if (potentialSelectedIndex === -1) { return Stream_1.Stream.nil(); } return this.defaultShrinkForFirst(potentialSelectedIndex).join(this.warbs[potentialSelectedIndex].arbitrary .shrink(value, undefined) .map((v) => this.mapIntoValue(potentialSelectedIndex, v, null, undefined))); } defaultShrinkForFirst(selectedIndex) { ++this.context.depth; try { if (!this.mustFallbackToFirstInShrink(selectedIndex) || this.warbs[0].fallbackValue === undefined) { return Stream_1.Stream.nil(); } } finally { --this.context.depth; } const rawShrinkValue = new Value_1.Value(this.warbs[0].fallbackValue.default, undefined); return Stream_1.Stream.of(this.mapIntoValue(0, rawShrinkValue, null, undefined)); } canShrinkWithoutContextIndex(value) { if (this.mustGenerateFirst()) { return this.warbs[0].arbitrary.canShrinkWithoutContext(value) ? 0 : -1; } try { ++this.context.depth; for (let idx = 0; idx !== this.warbs.length; ++idx) { const warb = this.warbs[idx]; if (warb.weight !== 0 && warb.arbitrary.canShrinkWithoutContext(value)) { return idx; } } return -1; } finally { --this.context.depth; } } mapIntoValue(idx, value, clonedMrngForFallbackFirst, biasFactor) { const context = { selectedIndex: idx, originalBias: biasFactor, originalContext: value.context, clonedMrngForFallbackFirst, }; return new Value_1.Value(value.value, context); } safeGenerateForIndex(mrng, idx, biasFactor) { ++this.context.depth; try { const value = this.warbs[idx].arbitrary.generate(mrng, biasFactor); const clonedMrngForFallbackFirst = this.mustFallbackToFirstInShrink(idx) ? mrng.clone() : null; return this.mapIntoValue(idx, value, clonedMrngForFallbackFirst, biasFactor); } finally { --this.context.depth; } } mustGenerateFirst() { return this.constraints.maxDepth <= this.context.depth; } mustFallbackToFirstInShrink(idx) { return idx !== 0 && this.constraints.withCrossShrink && this.warbs[0].weight !== 0; } computeNegDepthBenefit() { const depthBias = this.constraints.depthBias; if (depthBias <= 0 || this.warbs[0].weight === 0) { return 0; } const depthBenefit = safeMathFloor(safeMathPow(1 + depthBias, this.context.depth)) - 1; return -safeMathMin(this.totalWeight * depthBenefit, safeMaxSafeInteger) || 0; } } exports.FrequencyArbitrary = FrequencyArbitrary;