Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Fuzzed string #4012

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions packages/fast-check/src/arbitrary/fuzzedString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Arbitrary } from '../check/arbitrary/definition/Arbitrary';
import { Value } from '../check/arbitrary/definition/Value';
import { Random } from '../random/generator/Random';
import { Stream } from '../stream/Stream';
import { patternsToStringUnmapperFor } from './_internals/mappers/PatternsToString';
import { char } from './char';

const startSymbol = Symbol('start');
const endSymbol = Symbol('end');

// from => { to => weight }
type TransitionMap = Map<string | typeof startSymbol, Map<string | typeof endSymbol, number>>;

function multiFromToSingleFrom(fromMulti: (string | typeof startSymbol)[]): string | typeof startSymbol {
const nonStart = fromMulti.filter((i) => i !== startSymbol) as string[];

Check warning on line 15 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L14-L15

Added lines #L14 - L15 were not covered by tests
if (nonStart.length === 0) {
return startSymbol;

Check warning on line 17 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L17

Added line #L17 was not covered by tests
}
return nonStart.join('');

Check warning on line 19 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L19

Added line #L19 was not covered by tests
dubzzz marked this conversation as resolved.
Show resolved Hide resolved
}

function incrementInTransitionMap(
transitionMap: TransitionMap,
from: string | typeof startSymbol,
to: string | typeof endSymbol

Check warning on line 25 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L25

Added line #L25 was not covered by tests
): void {
const transitionsFromPrevious = transitionMap.get(from);

Check warning on line 27 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L27

Added line #L27 was not covered by tests
if (transitionsFromPrevious !== undefined) {
const pastValue = transitionsFromPrevious.get(to) || 0;
transitionsFromPrevious.set(to, pastValue + 1);
} else {
transitionMap.set(from, new Map([[to, 1]]));

Check warning on line 32 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L30-L32

Added lines #L30 - L32 were not covered by tests
}
}

function addIntoTransitionMap(transitionMap: TransitionMap, tokenizedCorpusItem: string[], depth: number): void {
const previousItems: (string | typeof startSymbol)[] = Array(depth).fill(startSymbol);
for (let index = 0; index !== tokenizedCorpusItem.length; ++index) {
const currentItem = tokenizedCorpusItem[index];
incrementInTransitionMap(transitionMap, multiFromToSingleFrom(previousItems), currentItem);
previousItems.shift();
previousItems.push(currentItem);

Check warning on line 42 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L36-L42

Added lines #L36 - L42 were not covered by tests
}
incrementInTransitionMap(transitionMap, multiFromToSingleFrom(previousItems), endSymbol);

Check warning on line 44 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L44

Added line #L44 was not covered by tests
}

class FuzzedString extends Arbitrary<string> {
private readonly transitionMap: TransitionMap;

constructor(
corpus: string[],
private readonly charArb: Arbitrary<string>,
private readonly strictness: 0 | 1 | 2,
private readonly depth: number

Check warning on line 54 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L52-L54

Added lines #L52 - L54 were not covered by tests
) {
super();

Check warning on line 56 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L56

Added line #L56 was not covered by tests

const tokenizedCorpus: string[][] = [];
const unmapper = patternsToStringUnmapperFor(this.charArb, {});
for (const corpusItem of corpus) {
const tokenizedCorpusItem = unmapper(corpusItem); // implicit throw
tokenizedCorpus.push(tokenizedCorpusItem);

Check warning on line 62 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L58-L62

Added lines #L58 - L62 were not covered by tests
}
if (tokenizedCorpus.length === 0) {
throw new Error(`Do not support empty corpus`);

Check warning on line 65 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L65

Added line #L65 was not covered by tests
}

this.transitionMap = new Map();
for (let d = 1; d <= this.depth; ++d) {
for (const tokenizedCorpusItem of tokenizedCorpus) {
addIntoTransitionMap(this.transitionMap, tokenizedCorpusItem, d);

Check warning on line 71 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L68-L71

Added lines #L68 - L71 were not covered by tests
}
}
}

private generateInternal(mrng: Random): string {
const previousItems: (string | typeof startSymbol)[] = Array(this.depth).fill(startSymbol);
let stringValue = '';

Check warning on line 78 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L76-L78

Added lines #L76 - L78 were not covered by tests

if (this.strictness !== 2) {
throw new Error('Strictness not being 2, not implemented');

Check warning on line 81 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L81

Added line #L81 was not covered by tests
}

// eslint-disable-next-line no-constant-condition
while (true) {
const allTransitions: [string | typeof endSymbol, number][] = [];
for (let d = 1; d <= this.depth; ++d) {
const transitions = this.transitionMap.get(

Check warning on line 88 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L85-L88

Added lines #L85 - L88 were not covered by tests
multiFromToSingleFrom(previousItems.slice(previousItems.length - d, previousItems.length))
);
if (transitions !== undefined) {
allTransitions.push(...transitions.entries());

Check warning on line 92 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L92

Added line #L92 was not covered by tests
}
}
const totalWeight = allTransitions.reduce((acc, transition) => acc + transition[1], 0);
const selectedWeight = mrng.nextInt(0, totalWeight - 1);
let postSelected = 1;
let totalWeightUpToPostSelected = allTransitions[0][1];
for (

Check warning on line 99 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L95-L99

Added lines #L95 - L99 were not covered by tests
;
postSelected !== allTransitions.length && totalWeightUpToPostSelected <= selectedWeight;
totalWeightUpToPostSelected += allTransitions[postSelected][1], ++postSelected
) {
// no-op
}
const item = allTransitions[postSelected - 1][0];

Check warning on line 106 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L106

Added line #L106 was not covered by tests
if (item === endSymbol) {
return stringValue;

Check warning on line 108 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L108

Added line #L108 was not covered by tests
}
previousItems.shift();
previousItems.push(item);
stringValue += item;

Check warning on line 112 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L110-L112

Added lines #L110 - L112 were not covered by tests
}
}

generate(mrng: Random, _biasFactor: number | undefined): Value<string> {
return new Value(this.generateInternal(mrng), undefined);

Check warning on line 117 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L116-L117

Added lines #L116 - L117 were not covered by tests
}

canShrinkWithoutContext(value: unknown): value is string {
return false;

Check warning on line 121 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L120-L121

Added lines #L120 - L121 were not covered by tests
}

shrink(_value: string, _context: unknown): Stream<Value<string>> {
return Stream.nil();

Check warning on line 125 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L124-L125

Added lines #L124 - L125 were not covered by tests
}
}

export function fuzzedString(corpus: string[]): Arbitrary<string> {
return new FuzzedString(corpus, char(), 2, 1);

Check warning on line 130 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L130

Added line #L130 was not covered by tests
}

export function fuzzedString10(corpus: string[]): Arbitrary<string> {
return new FuzzedString(corpus, char(), 2, 10);

Check warning on line 134 in packages/fast-check/src/arbitrary/fuzzedString.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/arbitrary/fuzzedString.ts#L134

Added line #L134 was not covered by tests
}
3 changes: 3 additions & 0 deletions packages/fast-check/src/fast-check-default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ import { bigInt64Array, BigIntArrayConstraints } from './arbitrary/bigInt64Array
import { bigUint64Array } from './arbitrary/bigUint64Array';
import { SchedulerAct } from './arbitrary/_internals/interfaces/Scheduler';
import { stringMatching, StringMatchingConstraints } from './arbitrary/stringMatching';
import { fuzzedString, fuzzedString10 } from './arbitrary/fuzzedString';

// Explicit cast into string to avoid to have __type: "__PACKAGE_TYPE__"
/**
Expand Down Expand Up @@ -251,6 +252,8 @@ export {
hexa,
base64,
mixedCase,
fuzzedString,
fuzzedString10,
string,
asciiString,
string16bits,
Expand Down
9 changes: 7 additions & 2 deletions packages/fast-check/test/e2e/documentation/Docs.md.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,20 @@ function refreshContent(originalContent: string): { content: string; numExecuted
.trim()
.replace(/;$/, '')
.replace(/;\n\/\/.*$/m, '\n//');
const evalCode = `${preparationPart}\nfc.sample(${santitizeArbitraryPart}\n, { numRuns: ${numRuns}, seed: ${seed} }).map(v => fc.stringify(v))`;
const evalCode = `${preparationPart}\nfc.sample(${santitizeArbitraryPart}\n, { numRuns: ${
santitizeArbitraryPart.includes('fuzzedString') ? 10 * numRuns : numRuns
}, seed: ${seed} }).map(v => fc.stringify(v))`;
try {
return eval(evalCode);
} catch (err) {
throw new Error(`Failed to run code snippet:\n\n${evalCode}\n\nWith error message: ${err}`);
}
})(fc);

const uniqueGeneratedValues = Array.from(new Set(generatedValues)).slice(0, TargetNumExamples);
const uniqueGeneratedValues = Array.from(new Set(generatedValues)).slice(
0,
snippet.includes('fuzzedString') ? 10 * TargetNumExamples : TargetNumExamples
);
// If the display for generated values is too long, we split it into a list of items
if (
uniqueGeneratedValues.some((value) => value.includes('\n')) ||
Expand Down
Loading
Loading