How to use warbs method in fast-check-monorepo

Best JavaScript code snippet using fast-check-monorepo

FrequencyArbitrary.spec.ts

Source:FrequencyArbitrary.spec.ts Github

copy

Full Screen

1import * as fc from 'fast-check';2import { FrequencyArbitrary, _Constraints } from '../../../../src/arbitrary/_internals/FrequencyArbitrary';3import { Value } from '../../../../src/check/arbitrary/definition/Value';4import { fakeRandom } from '../__test-helpers__/RandomHelpers';5import { FakeIntegerArbitrary, fakeArbitrary } from '../__test-helpers__/ArbitraryHelpers';6import {7 assertProduceSameValueGivenSameSeed,8 assertProduceValuesShrinkableWithoutContext,9 assertProduceCorrectValues,10 assertShrinkProducesStrictlySmallerValue,11} from '../__test-helpers__/ArbitraryAssertions';12import * as DepthContextMock from '../../../../src/arbitrary/_internals/helpers/DepthContext';13import { Stream } from '../../../../src/stream/Stream';14import { sizeArb } from '../__test-helpers__/SizeHelpers';15function beforeEachHook() {16 jest.resetModules();17 jest.restoreAllMocks();18 fc.configureGlobal({ beforeEach: beforeEachHook });19}20beforeEach(beforeEachHook);21const frequencyValidInputsArb = fc22 .tuple(23 fc.record({ weight: fc.integer({ min: 1 }), arbitraryValue: fc.integer() }),24 fc.array(fc.record({ weight: fc.integer({ min: 1 }), arbitraryValue: fc.integer() })),25 fc.array(fc.record({ weight: fc.integer({ min: 1 }), arbitraryValue: fc.integer() }))26 )27 .map(([positiveWeightMeta, headingWeightsMeta, traillingWeightsMeta]) => [28 ...headingWeightsMeta,29 positiveWeightMeta,30 ...traillingWeightsMeta,31 ]);32const fromValidInputs = (metas: { weight: number; arbitraryValue: number }[]) =>33 metas.map((meta) => {34 const expectedContext = Symbol();35 const arbitraryMeta = fakeArbitrary<number>();36 arbitraryMeta.generate.mockReturnValue(new Value(meta.arbitraryValue, expectedContext));37 return {38 weight: meta.weight,39 arbitraryMeta,40 arbitrary: arbitraryMeta.instance,41 expectedValue: meta.arbitraryValue,42 expectedContext,43 fallbackValue: undefined as { default: number } | undefined,44 };45 });46const frequencyConstraintsArbFor = (keys: {47 forbidden?: (keyof _Constraints)[];48 required?: (keyof _Constraints)[];49}): fc.Arbitrary<_Constraints> => {50 const { forbidden = [], required = [] } = keys;51 return fc.record(52 {53 ...(!forbidden.includes('depthIdentifier') ? { depthIdentifier: fc.string() } : {}),54 ...(!forbidden.includes('depthSize')55 ? { depthSize: fc.oneof(fc.double({ min: 0, max: 100, noNaN: true }), sizeArb) }56 : {}),57 ...(!forbidden.includes('maxDepth') ? { maxDepth: fc.nat() } : {}),58 ...(!forbidden.includes('withCrossShrink') ? { withCrossShrink: fc.boolean() } : {}),59 },60 { requiredKeys: required }61 );62};63describe('FrequencyArbitrary', () => {64 describe('from', () => {65 it('should build instances of FrequencyArbitrary', () =>66 fc.assert(67 fc.property(68 frequencyValidInputsArb,69 frequencyConstraintsArbFor({}),70 fc.nat(),71 (validInputs, constraints, depth) => {72 // Arrange73 const warbs = fromValidInputs(validInputs);74 const depthContext = { depth };75 const getDepthContextFor = jest.spyOn(DepthContextMock, 'getDepthContextFor');76 getDepthContextFor.mockReturnValue(depthContext);77 // Act78 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');79 // Assert80 expect(arb).toBeInstanceOf(FrequencyArbitrary);81 }82 )83 ));84 it('should always use the context coming from getDepthContextFor', () =>85 fc.assert(86 fc.property(87 frequencyValidInputsArb,88 frequencyConstraintsArbFor({}),89 fc.nat(),90 (validInputs, constraints, depth) => {91 // Arrange92 const warbs = fromValidInputs(validInputs);93 const depthContext = { depth };94 const getDepthContextFor = jest.spyOn(DepthContextMock, 'getDepthContextFor');95 getDepthContextFor.mockReturnValue(depthContext);96 // Act97 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');98 const typedArb = arb as FrequencyArbitrary<number>;99 // Assert100 expect(getDepthContextFor).toHaveBeenCalledTimes(1);101 expect(getDepthContextFor).toHaveBeenCalledWith(constraints.depthIdentifier);102 expect(typedArb.context).toBe(depthContext);103 }104 )105 ));106 it('should reject calls without any weighted arbitraries', () => {107 // Arrange / Act / Assert108 expect(() => FrequencyArbitrary.from([], {}, 'test')).toThrowError();109 });110 it('should reject calls without weight', () => {111 // Arrange / Act / Assert112 expect(() =>113 FrequencyArbitrary.from([{ arbitrary: fakeArbitrary(), weight: undefined! }], {}, 'test')114 ).toThrowError(/expects weights to be integer values/);115 });116 it('should reject calls without arbitrary', () => {117 // Arrange / Act / Assert118 expect(() => FrequencyArbitrary.from([{ arbitrary: undefined!, weight: 1 }], {}, 'test')).toThrowError(119 /expects arbitraries to be specified/120 );121 });122 it('should reject calls including at least one strictly negative weight', () =>123 fc.assert(124 fc.property(125 fc.integer({ max: -1 }),126 fc.array(fc.nat()),127 fc.array(fc.nat()),128 (negativeWeight, headingWeights, traillingWeights) => {129 // Arrange130 const weightedArbs = [...headingWeights, negativeWeight, ...traillingWeights].map((weight) => ({131 weight,132 arbitrary: fakeArbitrary(),133 }));134 // Act / Assert135 expect(() => FrequencyArbitrary.from(weightedArbs, {}, 'test')).toThrowError();136 }137 )138 ));139 it('should reject calls having a total weight of zero', () =>140 fc.assert(141 fc.property(fc.nat({ max: 1000 }), (numEntries) => {142 // Arrange143 const weightedArbs = [...Array(numEntries)].map(() => ({144 weight: 0,145 arbitrary: fakeArbitrary(),146 }));147 // Act / Assert148 // Combined with: 'Should reject calls including at one strictly negative weight'149 // it means that we have: 'Should reject calls having a total weight inferior or equal to zero'150 expect(() => FrequencyArbitrary.from(weightedArbs, {}, 'test')).toThrowError();151 })152 ));153 it('should not reject calls defining a strictly positive total weight without any negative weights', () =>154 fc.assert(155 fc.property(156 fc.integer({ min: 1 }),157 fc.array(fc.nat()),158 fc.array(fc.nat()),159 (positiveWeight, headingWeights, traillingWeights) => {160 // Arrange161 const weightedArbs = [...headingWeights, positiveWeight, ...traillingWeights].map((weight) => ({162 weight,163 arbitrary: fakeArbitrary(),164 }));165 // Act / Assert166 expect(() => FrequencyArbitrary.from(weightedArbs, {}, 'test')).not.toThrowError();167 }168 )169 ));170 });171 describe('generate', () => {172 it('should call Random generator to generate values between 0 and total weight (not included)', () =>173 fc.assert(174 fc.property(175 frequencyValidInputsArb,176 frequencyConstraintsArbFor({}),177 fc.option(fc.integer({ min: 2 }), { nil: undefined }),178 fc.nat(),179 (validInputs, constraints, biasFactor, generateSeed) => {180 // Arrange181 fc.pre(constraints.maxDepth !== 0);182 const warbs = fromValidInputs(validInputs);183 const totalWeight = warbs.reduce((acc, cur) => acc + cur.weight, 0);184 const { instance: mrng, nextInt } = fakeRandom();185 nextInt.mockImplementation((a = 0, b = 0) => a + (generateSeed % (b - a + 1)));186 // Act187 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');188 arb.generate(mrng, biasFactor);189 // Assert190 expect(nextInt).toHaveBeenCalledTimes(1);191 expect(nextInt).toHaveBeenCalledWith(0, totalWeight - 1);192 }193 )194 ));195 it('should call the right arbitrary to generate the value', () =>196 fc.assert(197 fc.property(198 frequencyValidInputsArb,199 frequencyConstraintsArbFor({}),200 fc.option(fc.integer({ min: 2 }), { nil: undefined }),201 fc.nat(),202 fc.nat(),203 (validInputs, constraints, biasFactor, arbitrarySelectionSeed, generateSeed) => {204 // Arrange205 fc.pre(constraints.maxDepth !== 0);206 const warbs = fromValidInputs(validInputs);207 const selectedArbitraryIndex = arbitrarySelectionSeed % warbs.length;208 const selectedArbitrary = warbs[selectedArbitraryIndex];209 fc.pre(selectedArbitrary.weight > 0);210 const totalWeightBefore = warbs.slice(0, selectedArbitraryIndex).reduce((acc, cur) => acc + cur.weight, 0);211 const { instance: mrng, nextInt } = fakeRandom();212 nextInt.mockImplementation(() => totalWeightBefore + (generateSeed % selectedArbitrary.weight));213 // Act214 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');215 const g = arb.generate(mrng, biasFactor).value;216 // Assert217 expect(g).toBe(selectedArbitrary.expectedValue);218 expect(selectedArbitrary.arbitraryMeta.generate).toHaveBeenCalledTimes(1);219 expect(selectedArbitrary.arbitraryMeta.generate).toHaveBeenCalledWith(mrng, biasFactor);220 }221 )222 ));223 it('should always call the first arbitrary to generate the value when maxDepth has been reached', () =>224 fc.assert(225 fc.property(226 frequencyValidInputsArb,227 frequencyConstraintsArbFor({ required: ['maxDepth'] }),228 fc.option(fc.integer({ min: 2 }), { nil: undefined }),229 fc.nat(),230 (validInputs, constraints, biasFactor, overflowFromMaxDepth) => {231 // Arrange232 const warbs = fromValidInputs(validInputs);233 const requestedMaxDepth = constraints.maxDepth!;234 const { instance: mrng, nextInt } = fakeRandom();235 const getDepthContextFor = jest.spyOn(DepthContextMock, 'getDepthContextFor');236 getDepthContextFor.mockReturnValue({ depth: requestedMaxDepth + overflowFromMaxDepth });237 // Act238 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');239 const g = arb.generate(mrng, biasFactor).value;240 // Assert241 expect(nextInt).not.toHaveBeenCalled();242 expect(g).toBe(warbs[0].expectedValue);243 }244 )245 ));246 it('should increment received depth context when going deeper in the generate-tree then reset it', () =>247 fc.assert(248 fc.property(249 frequencyValidInputsArb,250 frequencyConstraintsArbFor({}),251 fc.option(fc.integer({ min: 2 }), { nil: undefined }),252 fc.nat(),253 fc.nat(),254 (validInputs, constraints, biasFactor, initialDepth, generateSeed) => {255 // Arrange256 let calledOnce = false;257 const warbs = fromValidInputs(validInputs);258 const { instance: mrng, nextInt } = fakeRandom();259 nextInt.mockImplementation((a = 0, b = 0) => a + (generateSeed % (b - a + 1)));260 const depthContext = { depth: initialDepth };261 const getDepthContextFor = jest.spyOn(DepthContextMock, 'getDepthContextFor');262 getDepthContextFor.mockReturnValue(depthContext);263 for (const { arbitraryMeta, expectedValue } of warbs) {264 arbitraryMeta.generate.mockReset();265 arbitraryMeta.generate.mockImplementation(() => {266 calledOnce = true;267 expect(depthContext).toEqual({ depth: initialDepth + 1 });268 return new Value(expectedValue, undefined);269 });270 }271 // Act272 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');273 arb.generate(mrng, biasFactor);274 // Assert275 expect(calledOnce).toBe(true);276 expect(depthContext).toEqual({ depth: initialDepth });277 }278 )279 ));280 it('should ask ranges containing negative values as we go deeper in the structure if depthSize and first arbitrary has weight >0', () =>281 fc.assert(282 fc.property(283 frequencyValidInputsArb,284 frequencyConstraintsArbFor({ forbidden: ['maxDepth'], required: ['depthSize'] }),285 fc.option(fc.integer({ min: 2 }), { nil: undefined }),286 (validInputs, constraints, biasFactor) => {287 // Arrange288 const warbs = fromValidInputs(validInputs);289 fc.pre(warbs[0].weight > 0);290 const { instance: mrng, nextInt } = fakeRandom();291 nextInt.mockReturnValue(0);292 const depthContext = { depth: 0 };293 const getDepthContextFor = jest.spyOn(DepthContextMock, 'getDepthContextFor');294 getDepthContextFor.mockReturnValueOnce(depthContext);295 // Act / Assert296 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');297 let currentDepth = 0;298 // eslint-disable-next-line no-constant-condition299 while (true) {300 depthContext.depth = currentDepth;301 arb.generate(mrng, biasFactor);302 expect(depthContext.depth).toBe(currentDepth); // ensures the depth was properly reset303 if (nextInt.mock.calls[nextInt.mock.calls.length - 1][0] < 0) {304 break; // we have been called with a negative value once305 }306 ++currentDepth;307 }308 // first run (depth=0) will always call us with 0->?309 // subsequent calls (depth>0) may call us with negative boundaries310 expect(nextInt).toHaveBeenCalledWith(0, expect.any(Number));311 // but all the runs will call us with the same max312 const distinctMax = new Set(nextInt.mock.calls.map(([_min, max]) => max));313 expect([...distinctMax]).toHaveLength(1);314 const distinctMin = new Set(nextInt.mock.calls.map(([min, _max]) => min));315 expect([...distinctMin]).toHaveLength(2);316 }317 )318 ));319 it('should never ask ranges containing negative values as we go deeper in the structure if first arbitrary has weight of zero', () =>320 fc.assert(321 fc.property(322 frequencyValidInputsArb,323 frequencyConstraintsArbFor({ forbidden: ['maxDepth'], required: ['depthSize'] }),324 fc.option(fc.integer({ min: 2 }), { nil: undefined }),325 (validInputs, constraints, biasFactor) => {326 // Arrange327 const warbs = fromValidInputs(validInputs);328 const { instance: mrng, nextInt } = fakeRandom();329 nextInt.mockReturnValue(0);330 const depthContext = { depth: 0 };331 const getDepthContextFor = jest.spyOn(DepthContextMock, 'getDepthContextFor');332 getDepthContextFor.mockReturnValueOnce(depthContext);333 // Act / Assert334 const arb = FrequencyArbitrary.from([{ ...warbs[0], weight: 0 }, ...warbs], constraints, 'test');335 for (let currentDepth = 0; currentDepth !== 100; ++currentDepth) {336 depthContext.depth = currentDepth;337 arb.generate(mrng, biasFactor);338 expect(depthContext.depth).toBe(currentDepth); // ensures the depth was properly reset339 }340 const distinctMin = new Set(nextInt.mock.calls.map(([min, _max]) => min));341 expect([...distinctMin]).toHaveLength(1);342 const distinctMax = new Set(nextInt.mock.calls.map(([_min, max]) => max));343 expect([...distinctMax]).toHaveLength(1);344 }345 )346 ));347 });348 describe('canShrinkWithoutContext', () => {349 it('should tell it cannot generate the value if no sub-arbitrary can generate the value', () =>350 fc.assert(351 fc.property(frequencyValidInputsArb, frequencyConstraintsArbFor({}), (validInputs, constraints) => {352 // Arrange353 const warbs = fromValidInputs(validInputs);354 const depthContext = { depth: 0 };355 const getDepthContextFor = jest.spyOn(DepthContextMock, 'getDepthContextFor');356 getDepthContextFor.mockReturnValue(depthContext);357 const value = Symbol();358 for (const input of warbs) {359 input.arbitraryMeta.canShrinkWithoutContext.mockReturnValue(false);360 }361 // Act362 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');363 // Assert364 expect(arb.canShrinkWithoutContext(value)).toBe(false);365 })366 ));367 it('should ignore arbitraries with weight of zero when maxDepth not reached', () =>368 fc.assert(369 fc.property(370 frequencyValidInputsArb,371 frequencyConstraintsArbFor({}),372 fc.nat(),373 (validInputs, constraints, position) => {374 // Arrange375 fc.pre(constraints.maxDepth !== 0);376 const warbs = fromValidInputs([377 ...validInputs.slice(0, position % validInputs.length),378 { arbitraryValue: -1, weight: 0 },379 ...validInputs.slice(position % validInputs.length),380 ]);381 const depthContext = { depth: 0 };382 const getDepthContextFor = jest.spyOn(DepthContextMock, 'getDepthContextFor');383 getDepthContextFor.mockReturnValue(depthContext);384 const value = Symbol();385 for (const input of warbs) {386 input.arbitraryMeta.canShrinkWithoutContext.mockReturnValue(false);387 }388 warbs[position % validInputs.length].arbitraryMeta.canShrinkWithoutContext.mockReturnValue(true);389 // Act390 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');391 // Assert392 expect(arb.canShrinkWithoutContext(value)).toBe(false);393 }394 )395 ));396 it('should tell it can generate the value if one of the sub-arbitraries can generate the value (maxDepth not reached)', () =>397 fc.assert(398 fc.property(399 frequencyValidInputsArb,400 frequencyConstraintsArbFor({}),401 fc.nat(),402 (validInputs, constraints, mod) => {403 // Arrange404 fc.pre(constraints.maxDepth !== 0);405 const warbs = fromValidInputs(validInputs);406 const depthContext = { depth: 0 };407 const getDepthContextFor = jest.spyOn(DepthContextMock, 'getDepthContextFor');408 getDepthContextFor.mockReturnValue(depthContext);409 const value = Symbol();410 const selectedIndex = mod % warbs.length;411 for (let index = 0; index !== warbs.length; ++index) {412 const input = warbs[index];413 const can = index === selectedIndex;414 input.arbitraryMeta.canShrinkWithoutContext.mockReturnValue(can);415 }416 // Act417 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');418 // Assert419 expect(arb.canShrinkWithoutContext(value)).toBe(true);420 expect(warbs[selectedIndex].arbitraryMeta.canShrinkWithoutContext).toHaveBeenCalledWith(value);421 }422 )423 ));424 it('should only consider the first arbitrary when maxDepth has been reached', () =>425 fc.assert(426 fc.property(427 frequencyValidInputsArb,428 frequencyConstraintsArbFor({ required: ['maxDepth'] }),429 fc.nat(),430 fc.boolean(),431 fc.boolean(),432 (validInputs, constraints, overflowFromMaxDepth, firstCanOrNot, allOthersCanOrNot) => {433 // Arrange434 const warbs = fromValidInputs(validInputs);435 const maxDepth = constraints.maxDepth!;436 const depthContext = { depth: maxDepth + overflowFromMaxDepth };437 const getDepthContextFor = jest.spyOn(DepthContextMock, 'getDepthContextFor');438 getDepthContextFor.mockReturnValue(depthContext);439 const value = Symbol();440 for (let index = 0; index !== warbs.length; ++index) {441 warbs[index].arbitraryMeta.canShrinkWithoutContext.mockReturnValue(442 index === 0 ? firstCanOrNot : allOthersCanOrNot443 );444 }445 // Act446 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');447 // Assert448 expect(arb.canShrinkWithoutContext(value)).toBe(firstCanOrNot);449 expect(warbs[0].arbitraryMeta.canShrinkWithoutContext).toHaveBeenCalledWith(value);450 for (let index = 1; index < warbs.length; ++index) {451 expect(warbs[index].arbitraryMeta.canShrinkWithoutContext).not.toHaveBeenCalled();452 }453 }454 )455 ));456 });457 describe('shrink', () => {458 it('should call the right arbitrary to shrink a value generated by itself', () =>459 fc.assert(460 fc.property(461 frequencyValidInputsArb,462 frequencyConstraintsArbFor({ forbidden: ['withCrossShrink'] }),463 fc.option(fc.integer({ min: 2 }), { nil: undefined }),464 fc.nat(),465 fc.nat(),466 (validInputs, constraints, biasFactor, arbitrarySelectionSeed, generateSeed) => {467 // Arrange468 fc.pre(constraints.maxDepth !== 0);469 const warbs = fromValidInputs(validInputs);470 const selectedArbitraryIndex = arbitrarySelectionSeed % warbs.length;471 const selectedArbitrary = warbs[selectedArbitraryIndex];472 fc.pre(selectedArbitrary.weight > 0);473 const totalWeightBefore = warbs.slice(0, selectedArbitraryIndex).reduce((acc, cur) => acc + cur.weight, 0);474 const { instance: mrng, nextInt } = fakeRandom();475 nextInt.mockImplementation(() => totalWeightBefore + (generateSeed % selectedArbitrary.weight));476 selectedArbitrary.arbitraryMeta.shrink.mockReturnValue(477 Stream.of(new Value(1, undefined), new Value(42, undefined))478 );479 // Act480 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');481 const value = arb.generate(mrng, biasFactor);482 const shrinks = [...arb.shrink(value.value, value.context)];483 // Assert484 expect(shrinks.map((v) => v.value)).toEqual([1, 42]);485 expect(selectedArbitrary.arbitraryMeta.shrink).toHaveBeenCalledTimes(1);486 expect(selectedArbitrary.arbitraryMeta.shrink).toHaveBeenCalledWith(487 value.value,488 selectedArbitrary.expectedContext489 );490 }491 )492 ));493 it('should generate a new value using first arbitrary when cross-shrink enabled', () =>494 fc.assert(495 fc.property(496 frequencyValidInputsArb,497 frequencyConstraintsArbFor({ forbidden: ['withCrossShrink'] }),498 fc.option(fc.integer({ min: 2 }), { nil: undefined }),499 fc.nat(),500 fc.nat(),501 (validInputs, constraints, biasFactor, arbitrarySelectionSeed, generateSeed) => {502 // Arrange503 fc.pre(constraints.maxDepth !== 0);504 const warbs = fromValidInputs(validInputs);505 const selectedArbitraryIndex = arbitrarySelectionSeed % warbs.length;506 const selectedArbitrary = warbs[selectedArbitraryIndex];507 fc.pre(selectedArbitrary.weight > 0);508 fc.pre(warbs[0].weight > 0);509 fc.pre(selectedArbitraryIndex !== 0);510 const totalWeightBefore = warbs.slice(0, selectedArbitraryIndex).reduce((acc, cur) => acc + cur.weight, 0);511 const { instance: mrng, nextInt, clone } = fakeRandom();512 const { instance: anotherMrng } = fakeRandom();513 clone.mockReturnValue(anotherMrng);514 nextInt.mockImplementation(() => totalWeightBefore + (generateSeed % selectedArbitrary.weight));515 selectedArbitrary.arbitraryMeta.shrink.mockReturnValue(516 Stream.of(new Value(1, undefined), new Value(42, undefined))517 );518 // Act519 const arb = FrequencyArbitrary.from(warbs, { ...constraints, withCrossShrink: true }, 'test');520 const value = arb.generate(mrng, biasFactor);521 const shrinks = [...arb.shrink(value.value, value.context)];522 // Assert523 expect(shrinks.map((v) => v.value)).toEqual([warbs[0].expectedValue, 1, 42]);524 expect(warbs[0].arbitraryMeta.generate).toHaveBeenCalledWith(anotherMrng, biasFactor);525 expect(selectedArbitrary.arbitraryMeta.shrink).toHaveBeenCalledTimes(1);526 expect(selectedArbitrary.arbitraryMeta.shrink).toHaveBeenCalledWith(527 value.value,528 selectedArbitrary.expectedContext529 );530 }531 )532 ));533 it('should not call generate on first arbitrary when cross-shrink enabled and first generate already used it', () =>534 fc.assert(535 fc.property(536 frequencyValidInputsArb,537 frequencyConstraintsArbFor({ forbidden: ['withCrossShrink'] }),538 fc.option(fc.integer({ min: 2 }), { nil: undefined }),539 (validInputs, constraints, biasFactor) => {540 // Arrange541 fc.pre(constraints.maxDepth !== 0);542 const warbs = fromValidInputs(validInputs);543 fc.pre(warbs[0].weight > 0);544 const { instance: mrng, nextInt } = fakeRandom();545 nextInt.mockReturnValue(0);546 warbs[0].arbitraryMeta.shrink.mockReturnValue(Stream.of(new Value(1, undefined), new Value(42, undefined)));547 // Act548 const arb = FrequencyArbitrary.from(warbs, { ...constraints, withCrossShrink: true }, 'test');549 const value = arb.generate(mrng, biasFactor);550 const shrinks = [...arb.shrink(value.value, value.context)];551 // Assert552 expect(shrinks.map((v) => v.value)).toEqual([1, 42]);553 expect(warbs[0].arbitraryMeta.shrink).toHaveBeenCalledTimes(1);554 expect(warbs[0].arbitraryMeta.shrink).toHaveBeenCalledWith(value.value, warbs[0].expectedContext);555 }556 )557 ));558 it('should be able to shrink without context if one of the sub-arbitrary can generate the value', () =>559 fc.assert(560 fc.property(561 frequencyValidInputsArb,562 frequencyConstraintsArbFor({}),563 fc.nat(),564 (validInputs, constraints, mod) => {565 // Arrange566 fc.pre(constraints.maxDepth !== 0);567 const warbs = fromValidInputs(validInputs);568 const depthContext = { depth: 0 };569 const getDepthContextFor = jest.spyOn(DepthContextMock, 'getDepthContextFor');570 getDepthContextFor.mockReturnValue(depthContext);571 const value = Symbol();572 const selectedIndex = mod % warbs.length;573 fc.pre(warbs[selectedIndex].weight !== 0);574 for (let index = 0; index !== warbs.length; ++index) {575 const input = warbs[index];576 const can = index === selectedIndex;577 input.arbitraryMeta.canShrinkWithoutContext.mockReturnValue(can);578 input.arbitraryMeta.shrink.mockReturnValue(579 Stream.of(new Value(42, undefined), new Value(index, undefined))580 );581 }582 // Act583 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');584 const shrinks = [...arb.shrink(value as any, undefined)];585 // Assert586 expect(shrinks.map((v) => v.value)).toEqual([42, selectedIndex]);587 expect(warbs[selectedIndex].arbitraryMeta.canShrinkWithoutContext).toHaveBeenCalledWith(value);588 expect(warbs[selectedIndex].arbitraryMeta.shrink).toHaveBeenCalledWith(value, undefined);589 }590 )591 ));592 it('should be able to shrink without context if one of the sub-arbitrary can generate the value plus prepend fallback of first (whenever possible)', () =>593 fc.assert(594 fc.property(595 frequencyValidInputsArb,596 frequencyConstraintsArbFor({}),597 fc.nat(),598 (validInputs, constraints, mod) => {599 // Arrange600 fc.pre(constraints.maxDepth !== 0);601 const warbs = fromValidInputs(validInputs);602 const depthContext = { depth: 0 };603 const getDepthContextFor = jest.spyOn(DepthContextMock, 'getDepthContextFor');604 getDepthContextFor.mockReturnValue(depthContext);605 const value = Symbol();606 const selectedIndex = mod % warbs.length;607 fc.pre(warbs[selectedIndex].weight !== 0);608 for (let index = 0; index !== warbs.length; ++index) {609 const input = warbs[index];610 const can = index === selectedIndex;611 input.arbitraryMeta.canShrinkWithoutContext.mockReturnValue(can);612 input.arbitraryMeta.shrink.mockReturnValue(613 Stream.of(new Value(42, undefined), new Value(index, undefined))614 );615 }616 warbs[0].fallbackValue = { default: 48 };617 // Act618 const arb = FrequencyArbitrary.from(warbs, constraints, 'test');619 const shrinks = [...arb.shrink(value as any, undefined)];620 // Assert621 if (warbs[0].weight !== 0 && selectedIndex !== 0 && constraints.withCrossShrink) {622 // Can only prepend when applicable ie:623 // - weight of [0] >0624 // - not shrinking a value coming from [0]625 // - cross-shrink enabled626 expect(shrinks.map((v) => v.value)).toEqual([48, 42, selectedIndex]);627 } else {628 expect(shrinks.map((v) => v.value)).toEqual([42, selectedIndex]);629 }630 expect(warbs[selectedIndex].arbitraryMeta.canShrinkWithoutContext).toHaveBeenCalledWith(value);631 expect(warbs[selectedIndex].arbitraryMeta.shrink).toHaveBeenCalledWith(value, undefined);632 }633 )634 ));635 });636});637describe('FrequencyArbitrary (integration)', () => {638 type Extra = { data: { offset: number; weight: number }[]; constraints: Partial<_Constraints> };639 const maxRangeLength = 10;640 const extraParameters: fc.Arbitrary<Extra> = fc.record(641 {642 data: fc643 .array(644 fc.record(645 {646 offset: fc.nat(),647 weight: fc.nat(),648 },649 { requiredKeys: ['offset', 'weight'] }650 ),651 { minLength: 1 }652 )653 .filter((inputs) => inputs.reduce((summedWeight, current) => summedWeight + current.weight, 0) > 0),654 constraints: fc.record(655 {656 withCrossShrink: fc.boolean(),657 depthSize: fc.oneof(fc.double({ min: 0, max: Number.MAX_VALUE, noNaN: true }), sizeArb),658 maxDepth: fc.nat(),659 },660 { requiredKeys: [] }661 ),662 },663 { requiredKeys: ['data', 'constraints'] }664 );665 const isCorrect = (value: number, extra: Extra) =>666 typeof value === 'number' && extra.data.some((m) => m.offset <= value && value <= m.offset + maxRangeLength);667 const isStrictlySmaller = (v1: number, v2: number, extra: Extra) => {668 // When withCrossShrink is toggled, the shrinker can jump from one arbitrary to the first one on shrink669 // But only if the weight associated to the first arbitrary is strictly greater than 0670 if (extra.constraints.withCrossShrink && extra.data[0].weight > 0) {671 const canBeInFirstArbitrary = extra.data[0].offset <= v1 && v1 <= extra.data[0].offset + maxRangeLength;672 if (canBeInFirstArbitrary) {673 // `v1` is possibly coming from our first arbitrary674 return true;675 }676 }677 return Math.abs(v1) < Math.abs(v2);678 };679 const frequencyBuilder = (extra: Extra) =>680 FrequencyArbitrary.from(681 extra.data.map((m) => ({ weight: m.weight, arbitrary: new FakeIntegerArbitrary(m.offset, maxRangeLength) })),682 extra.constraints,683 'test'684 );685 it('should produce the same values given the same seed', () => {686 assertProduceSameValueGivenSameSeed(frequencyBuilder, { extraParameters });687 });688 it('should only produce correct values', () => {689 assertProduceCorrectValues(frequencyBuilder, isCorrect, { extraParameters });690 });691 it('should produce values seen as shrinkable without any context', () => {692 assertProduceValuesShrinkableWithoutContext(frequencyBuilder, { extraParameters });693 });694 it('should shrink towards strictly smaller values (if underlyings do)', () => {695 assertShrinkProducesStrictlySmallerValue(frequencyBuilder, isStrictlySmaller, { extraParameters });696 });...

Full Screen

Full Screen

FrequencyArbitrary.ts

Source:FrequencyArbitrary.ts Github

copy

Full Screen

1import { Random } from '../../random/generator/Random';2import { Stream } from '../../stream/Stream';3import { Arbitrary } from '../../check/arbitrary/definition/Arbitrary';4import { Value } from '../../check/arbitrary/definition/Value';5import { DepthContext, DepthIdentifier, getDepthContextFor } from './helpers/DepthContext';6import { depthBiasFromSizeForArbitrary, DepthSize } from './helpers/MaxLengthFromMinLength';7import { safePush } from '../../utils/globals';8const safePositiveInfinity = Number.POSITIVE_INFINITY;9const safeMaxSafeInteger = Number.MAX_SAFE_INTEGER;10const safeNumberIsInteger = Number.isInteger;11const safeMathFloor = Math.floor;12const safeMathPow = Math.pow;13const safeMathMin = Math.min;14/** @internal */15export class FrequencyArbitrary<T> extends Arbitrary<T> {16 readonly cumulatedWeights: number[];17 readonly totalWeight: number;18 static from<T>(warbs: _WeightedArbitrary<T>[], constraints: _Constraints, label: string): Arbitrary<T> {19 if (warbs.length === 0) {20 throw new Error(`${label} expects at least one weighted arbitrary`);21 }22 let totalWeight = 0;23 for (let idx = 0; idx !== warbs.length; ++idx) {24 const currentArbitrary = warbs[idx].arbitrary;25 if (currentArbitrary === undefined) {26 throw new Error(`${label} expects arbitraries to be specified`);27 }28 const currentWeight = warbs[idx].weight;29 totalWeight += currentWeight;30 if (!safeNumberIsInteger(currentWeight)) {31 throw new Error(`${label} expects weights to be integer values`);32 }33 if (currentWeight < 0) {34 throw new Error(`${label} expects weights to be superior or equal to 0`);35 }36 }37 if (totalWeight <= 0) {38 throw new Error(`${label} expects the sum of weights to be strictly superior to 0`);39 }40 const sanitizedConstraints: _SanitizedConstraints = {41 depthBias: depthBiasFromSizeForArbitrary(constraints.depthSize, constraints.maxDepth !== undefined),42 maxDepth: constraints.maxDepth != undefined ? constraints.maxDepth : safePositiveInfinity,43 withCrossShrink: !!constraints.withCrossShrink,44 };45 return new FrequencyArbitrary(warbs, sanitizedConstraints, getDepthContextFor(constraints.depthIdentifier));46 }47 private constructor(48 readonly warbs: _WeightedArbitrary<T>[],49 readonly constraints: _SanitizedConstraints,50 readonly context: DepthContext51 ) {52 super();53 let currentWeight = 0;54 this.cumulatedWeights = [];55 for (let idx = 0; idx !== warbs.length; ++idx) {56 currentWeight += warbs[idx].weight;57 safePush(this.cumulatedWeights, currentWeight);58 }59 this.totalWeight = currentWeight;60 }61 generate(mrng: Random, biasFactor: number | undefined): Value<T> {62 if (this.mustGenerateFirst()) {63 // index=0 can be selected even if it has a weight equal to zero64 return this.safeGenerateForIndex(mrng, 0, biasFactor);65 }66 const selected = mrng.nextInt(this.computeNegDepthBenefit(), this.totalWeight - 1);67 for (let idx = 0; idx !== this.cumulatedWeights.length; ++idx) {68 if (selected < this.cumulatedWeights[idx]) {69 return this.safeGenerateForIndex(mrng, idx, biasFactor);70 }71 }72 throw new Error(`Unable to generate from fc.frequency`);73 }74 canShrinkWithoutContext(value: unknown): value is T {75 return this.canShrinkWithoutContextIndex(value) !== -1;76 }77 shrink(value: T, context?: unknown): Stream<Value<T>> {78 if (context !== undefined) {79 const safeContext = context as _FrequencyArbitraryContext<T>;80 const selectedIndex = safeContext.selectedIndex;81 const originalBias = safeContext.originalBias;82 const originalArbitrary = this.warbs[selectedIndex].arbitrary;83 const originalShrinks = originalArbitrary84 .shrink(value, safeContext.originalContext)85 .map((v) => this.mapIntoValue(selectedIndex, v, null, originalBias));86 if (safeContext.clonedMrngForFallbackFirst !== null) {87 if (safeContext.cachedGeneratedForFirst === undefined) {88 safeContext.cachedGeneratedForFirst = this.safeGenerateForIndex(89 safeContext.clonedMrngForFallbackFirst,90 0,91 originalBias92 );93 }94 const valueFromFirst = safeContext.cachedGeneratedForFirst;95 return Stream.of(valueFromFirst).join(originalShrinks);96 }97 return originalShrinks;98 }99 const potentialSelectedIndex = this.canShrinkWithoutContextIndex(value);100 if (potentialSelectedIndex === -1) {101 return Stream.nil(); // No arbitrary found to accept this value102 }103 return this.defaultShrinkForFirst(potentialSelectedIndex).join(104 this.warbs[potentialSelectedIndex].arbitrary105 .shrink(value, undefined) // re-checked by canShrinkWithoutContextIndex106 .map((v) => this.mapIntoValue(potentialSelectedIndex, v, null, undefined))107 );108 }109 /** Generate shrink values for first arbitrary when no context and no value was provided */110 private defaultShrinkForFirst(selectedIndex: number): Stream<Value<T>> {111 ++this.context.depth; // increase depth112 try {113 if (!this.mustFallbackToFirstInShrink(selectedIndex) || this.warbs[0].fallbackValue === undefined) {114 // Not applicable: no fallback to first arbitrary on shrink OR no hint to shrink without an initial value and context115 return Stream.nil();116 }117 } finally {118 --this.context.depth; // decrease depth (reset depth)119 }120 // The arbitrary at [0] accepts to shrink fallbackValue.default without any context (context=undefined)121 const rawShrinkValue = new Value(this.warbs[0].fallbackValue.default, undefined);122 return Stream.of(this.mapIntoValue(0, rawShrinkValue, null, undefined));123 }124 /** Extract the index of the generator that would have been able to gennrate the value */125 private canShrinkWithoutContextIndex(value: unknown): number {126 if (this.mustGenerateFirst()) {127 return this.warbs[0].arbitrary.canShrinkWithoutContext(value) ? 0 : -1;128 }129 try {130 ++this.context.depth; // increase depth131 for (let idx = 0; idx !== this.warbs.length; ++idx) {132 const warb = this.warbs[idx];133 if (warb.weight !== 0 && warb.arbitrary.canShrinkWithoutContext(value)) {134 return idx;135 }136 }137 return -1;138 } finally {139 --this.context.depth; // decrease depth (reset depth)140 }141 }142 /** Map the output of one of the children with the context of frequency */143 private mapIntoValue(144 idx: number,145 value: Value<T>,146 clonedMrngForFallbackFirst: Random | null,147 biasFactor: number | undefined148 ): Value<T> {149 const context: _FrequencyArbitraryContext<T> = {150 selectedIndex: idx,151 originalBias: biasFactor,152 originalContext: value.context,153 clonedMrngForFallbackFirst,154 };155 return new Value(value.value, context);156 }157 /** Generate using Arbitrary at index idx and safely handle depth context */158 private safeGenerateForIndex(mrng: Random, idx: number, biasFactor: number | undefined): Value<T> {159 ++this.context.depth; // increase depth160 try {161 const value = this.warbs[idx].arbitrary.generate(mrng, biasFactor);162 const clonedMrngForFallbackFirst = this.mustFallbackToFirstInShrink(idx) ? mrng.clone() : null;163 return this.mapIntoValue(idx, value, clonedMrngForFallbackFirst, biasFactor);164 } finally {165 --this.context.depth; // decrease depth (reset depth)166 }167 }168 /** Check if generating a value based on the first arbitrary is compulsory */169 private mustGenerateFirst(): boolean {170 return this.constraints.maxDepth <= this.context.depth;171 }172 /** Check if fallback on first arbitrary during shrinking is required */173 private mustFallbackToFirstInShrink(idx: number): boolean {174 return idx !== 0 && this.constraints.withCrossShrink && this.warbs[0].weight !== 0;175 }176 /** Compute the benefit for the current depth */177 private computeNegDepthBenefit(): number {178 const depthBias = this.constraints.depthBias;179 if (depthBias <= 0 || this.warbs[0].weight === 0) {180 return 0;181 }182 // We use a pow-based biased benefit as the deeper we go the more chance we have183 // to encounter thousands of instances of the current arbitrary.184 const depthBenefit = safeMathFloor(safeMathPow(1 + depthBias, this.context.depth)) - 1;185 // -0 has to be converted into 0 thus we call ||0186 return -safeMathMin(this.totalWeight * depthBenefit, safeMaxSafeInteger) || 0;187 }188}189/** @internal */190export type _Constraints = {191 withCrossShrink?: boolean;192 depthSize?: DepthSize;193 maxDepth?: number;194 depthIdentifier?: DepthIdentifier | string;195};196/** @internal */197type _SanitizedConstraints = {198 withCrossShrink: boolean;199 depthBias: number;200 maxDepth: number;201};202/** @internal */203interface _WeightedArbitrary<T> {204 weight: number;205 arbitrary: Arbitrary<T>;206 // If specified, the arbitrary must accept to shrink fallbackValue.default without any context207 fallbackValue?: { default: T };208}209/** @internal */210type _FrequencyArbitraryContext<T> = {211 selectedIndex: number;212 originalBias: number | undefined;213 originalContext: unknown;214 clonedMrngForFallbackFirst: Random | null;215 cachedGeneratedForFirst?: Value<T>;...

Full Screen

Full Screen

profile-seeder.js

Source:profile-seeder.js Github

copy

Full Screen

1var Profile = require('../models/profile');2var mongoose = require('mongoose');3mongoose.connect('localhost:27017/shopping');4var profiles = [5 new Profile({6 imageProf:'https://yt3.ggpht.com/-zfUc8DPpBWs/AAAAAAAAAAI/AAAAAAAAAAA/DyRp7GoSxbo/s900-c-k-no-mo-rj-c0xffffff/photo.jpg',7 name:'Carl',8 hometown: 'Mollymook',9 instagram: 'https://www.instagram.com/trump/',10 twitter: 'https://twitter.com/CarlWarbs'11 }),12 new Profile({13 imageProf:'https://yt3.ggpht.com/-zfUc8DPpBWs/AAAAAAAAAAI/AAAAAAAAAAA/DyRp7GoSxbo/s900-c-k-no-mo-rj-c0xffffff/photo.jpg',14 name:'Robert',15 hometown: 'Sydney',16 instagram: 'https://www.instagram.com/stabmag/',17 twitter: 'https://twitter.com/CarlWarbs'18 }),19 new Profile({20 imageProf:'https://yt3.ggpht.com/-zfUc8DPpBWs/AAAAAAAAAAI/AAAAAAAAAAA/DyRp7GoSxbo/s900-c-k-no-mo-rj-c0xffffff/photo.jpg',21 name:'Jack',22 hometown: 'Milton',23 instagram: 'https://www.instagram.com/kanye/',24 twitter: 'https://twitter.com/CarlWarbs'25 }),26 new Profile({27 imageProf:'https://yt3.ggpht.com/-zfUc8DPpBWs/AAAAAAAAAAI/AAAAAAAAAAA/DyRp7GoSxbo/s900-c-k-no-mo-rj-c0xffffff/photo.jpg',28 name:'Charles',29 hometown: 'Arley',30 instagram: 'https://www.instagram.com/carlos/',31 twitter: 'https://twitter.com/CarlWarbs'32 }),33];34var done = 0;35for (var i = 0; i < profiles.length; i++) {36 profiles[i].save(function(err, result){37 done++;38 if (done === profiles.length){39 exit();40 }41 });42}43function exit(){44 mongoose.disconnect();...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1const fc = require('fast-check');2const arb = require('fast-check-monorepo');3const { gen } = arb;4const arb1 = gen.oneof(5 gen.integer({ min: 1, max: 100 }),6 gen.integer({ min: 100, max: 1000 }),7 gen.integer({ min: 1000, max: 10000 }),8 gen.integer({ min: 10000, max: 100000 }),9 gen.integer({ min: 100000, max: 1000000 }),10 gen.integer({ min: 1000000, max: 10000000 }),11 gen.integer({ min: 10000000, max: 100000000 }),12 gen.integer({ min: 100000000, max: 1000000000 }),13 gen.integer({ min: 1000000000, max: 10000000000 }),14 gen.integer({ min: 10000000000, max: 100000000000 }),15 gen.integer({ min: 100000000000, max: 1000000000000 }),16 gen.integer({ min: 1000000000000, max: 10000000000000 }),17 gen.integer({ min: 10000000000000, max: 100000000000000 }),18 gen.integer({ min: 100000000000000, max: 1000000000000000 }),19 gen.integer({ min: 1000000000000000, max: 10000000000000000 }),20 gen.integer({ min: 10000000000000000, max: 100000000000000000 }),21 gen.integer({ min: 100000000000000000, max: 1000000000000000000 }),22 gen.integer({ min: 1000000000000000000, max: 10000000000000000000 })23);24const arb2 = gen.oneof(25 gen.integer({ min: 1, max: 100 }),26 gen.integer({ min: 100, max: 1000 }),27 gen.integer({ min: 1000,

Full Screen

Using AI Code Generation

copy

Full Screen

1const fc = require('fast-check');2const { check } = fc;3check(4 fc.integer(0, 100),5 [fc.integer(0, 100), fc.integer(0, 100)],6 (a, b) => a + b <= 1007);8const fc = require('fast-check');9const { check } = fc;10check(11 fc.integer(0, 100),12 [fc.integer(0, 100), fc.integer(0, 100)],13 (a, b) => a + b <= 10014);15const fc = require('fast-check');16const { check } = fc;17check(18 fc.integer(0, 100),19 [fc.integer(0, 100), fc.integer(0, 100)],20 (a, b) => a + b <= 10021);22const fc = require('fast-check');23const { check } = fc;24check(25 fc.integer(0, 100),26 [fc.integer(0, 100), fc.integer(0

Full Screen

Using AI Code Generation

copy

Full Screen

1const warbs = require('@warbs/fast-check');2const fc = warbs.default;3const { property } = fc;4const fc = require('fast-check');5const { property } = fc;6const arb = fc.integer(0, 100);7const arb2 = fc.integer(0, 100);8property('test', arb, arb2, (a, b) => {9 return a + b === b + a;10});11property('test', arb, arb2, (a, b) => {12 return a + b === b + a;13});

Full Screen

Using AI Code Generation

copy

Full Screen

1const fc = require('fast-check');2const { warbs } = require('fast-check-monorepo');3const arb = warbs(['a', 'b', 'c']);4const arb1 = warbs(['a', 'b', 'c'], 2);5const arb2 = warbs(['a', 'b', 'c'], 2, 3);6console.log(arb);7console.log(arb1);8console.log(arb2);9fc.assert(10 fc.property(arb, (a) => {11 console.log(a);12 return true;13 })14);15fc.assert(16 fc.property(arb1, (a) => {17 console.log(a);18 return true;19 })20);21fc.assert(22 fc.property(arb2, (a) => {23 console.log(a);24 return true;25 })26);27import fc from 'fast-check';28import { warbs } from 'fast-check-monorepo';29const arb = warbs(['a', 'b', 'c']);30fc.assert(31 fc.property(arb, (a) => {32 console.log(a);33 return true;34 })35);36import fc from 'fast-check';37import { warbs } from 'fast-check-monorepo';38const arb = warbs(['a', 'b', 'c']);39fc.assert(40 fc.property(arb, (a) => {41 console.log(a);42 return true;43 })44);

Full Screen

Using AI Code Generation

copy

Full Screen

1const { check, gen } = require("fast-check");2function warbsMethodOfFastCheckMonorepo() {3 const { check, gen } = require("fast-check");4 const myGen = gen.array(gen.int, { minLength: 1, maxLength: 10 });5 check(myGen, (arr) => {6 return arr.length <= 10;7 });8}9warbsMethodOfFastCheckMonorepo();10{11 "scripts": {12 },13 "dependencies": {14 }15}

Full Screen

Using AI Code Generation

copy

Full Screen

1const fastCheck = require('fast-check');2const { warbs } = require('fast-check-monorepo');3const arbiter = warbs({4});5const property = fastCheck.property(6 fastCheck.integer(1, 100),7 fastCheck.integer(1, 100),8 (a, b) => a + b === b + a9);10property(arbiter);11const fastCheck = require('fast-check');12const { warbs } = require('fast-check-monorepo');13const arbiter = warbs({14});15const property = fastCheck.property(16 fastCheck.integer(1, 100),17 fastCheck.integer(1, 100),18 (a, b) => a + b === b + a19);20property(arbiter);21function warbs(options: WarbsOptions): Arbiter;

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run fast-check-monorepo automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful