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

⚡️ Faster instantiation of internet-related arbitraries #5402

Merged
merged 1 commit into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { fullUnicode } from '../../fullUnicode';
import type { Arbitrary } from '../../../check/arbitrary/definition/Arbitrary';
import { oneof } from '../../oneof';
import { mapToConstant } from '../../mapToConstant';
import { safeCharCodeAt, safeNumberToString, encodeURIComponent } from '../../../utils/globals';
import { safeCharCodeAt, safeNumberToString, encodeURIComponent, safeMapGet, safeMapSet } from '../../../utils/globals';

const SMap = Map;
const safeStringFromCharCode = String.fromCharCode;

/** @internal */
Expand Down Expand Up @@ -32,18 +33,56 @@ function percentCharArbUnmapper(value: unknown): string {
/** @internal */
const percentCharArb = fullUnicode().map(percentCharArbMapper, percentCharArbUnmapper);

let lowerAlphaArbitrary: Arbitrary<string> | undefined = undefined;

/** @internal */
export const buildLowerAlphaArbitrary = (others: string[]): Arbitrary<string> =>
mapToConstant(lowerCaseMapper, { num: others.length, build: (v) => others[v] });
export function getOrCreateLowerAlphaArbitrary(): Arbitrary<string> {
if (lowerAlphaArbitrary === undefined) {
lowerAlphaArbitrary = mapToConstant(lowerCaseMapper);
}
return lowerAlphaArbitrary;
}

let lowerAlphaNumericArbitraries: Map<string, Arbitrary<string>> | undefined = undefined;

/** @internal */
export const buildLowerAlphaNumericArbitrary = (others: string[]): Arbitrary<string> =>
mapToConstant(lowerCaseMapper, numericMapper, { num: others.length, build: (v) => others[v] });
export function getOrCreateLowerAlphaNumericArbitrary(others: string): Arbitrary<string> {
if (lowerAlphaNumericArbitraries === undefined) {
lowerAlphaNumericArbitraries = new SMap();
}
let match = safeMapGet(lowerAlphaNumericArbitraries, others);
if (match === undefined) {
match = mapToConstant(lowerCaseMapper, numericMapper, {
num: others.length,
build: (v) => others[v],
});
safeMapSet(lowerAlphaNumericArbitraries, others, match);
}
return match;
}

/** @internal */
export const buildAlphaNumericArbitrary = (others: string[]): Arbitrary<string> =>
mapToConstant(lowerCaseMapper, upperCaseMapper, numericMapper, { num: others.length, build: (v) => others[v] });
function buildAlphaNumericArbitrary(others: string): Arbitrary<string> {
return mapToConstant(lowerCaseMapper, upperCaseMapper, numericMapper, {
num: others.length,
build: (v) => others[v],
});
}

let alphaNumericPercentArbitraries: Map<string, Arbitrary<string>> | undefined = undefined;

/** @internal */
export const buildAlphaNumericPercentArbitrary = (others: string[]): Arbitrary<string> =>
oneof({ weight: 10, arbitrary: buildAlphaNumericArbitrary(others) }, { weight: 1, arbitrary: percentCharArb });
export function getOrCreateAlphaNumericPercentArbitrary(others: string): Arbitrary<string> {
if (alphaNumericPercentArbitraries === undefined) {
alphaNumericPercentArbitraries = new SMap();
}
let match = safeMapGet(alphaNumericPercentArbitraries, others);
if (match === undefined) {
match = oneof(
{ weight: 10, arbitrary: buildAlphaNumericArbitrary(others) },
{ weight: 1, arbitrary: percentCharArb },
);
safeMapSet(alphaNumericPercentArbitraries, others, match);
}
return match;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import type { Arbitrary } from '../../../check/arbitrary/definition/Arbitrary';
import { buildAlphaNumericPercentArbitrary } from './CharacterRangeArbitraryBuilder';
import { stringOf } from '../../stringOf';
import { getOrCreateAlphaNumericPercentArbitrary } from './CharacterRangeArbitraryBuilder';
import { string } from '../../string';
import type { SizeForArbitrary } from '../helpers/MaxLengthFromMinLength';

/** @internal */
export function buildUriQueryOrFragmentArbitrary(size: Exclude<SizeForArbitrary, 'max'>): Arbitrary<string> {
// query = *( pchar / "/" / "?" )
// fragment = *( pchar / "/" / "?" )
const others = ['-', '.', '_', '~', '!', '$', '&', "'", '(', ')', '*', '+', ',', ';', '=', ':', '@', '/', '?'];
return stringOf(buildAlphaNumericPercentArbitrary(others), { size });
return string({ unit: getOrCreateAlphaNumericPercentArbitrary("-._~!$&'()*+,;=:@/?"), size });
}
16 changes: 8 additions & 8 deletions packages/fast-check/src/arbitrary/domain.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { array } from './array';
import {
buildLowerAlphaArbitrary,
buildLowerAlphaNumericArbitrary,
getOrCreateLowerAlphaArbitrary,
getOrCreateLowerAlphaNumericArbitrary,
} from './_internals/builders/CharacterRangeArbitraryBuilder';
import { option } from './option';
import { stringOf } from './stringOf';
import { string } from './string';
import { tuple } from './tuple';
import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary';
import { filterInvalidSubdomainLabel } from './_internals/helpers/InvalidSubdomainLabelFiIter';
Expand All @@ -31,8 +31,8 @@ function toSubdomainLabelUnmapper(value: unknown): [string, [string, string] | n

/** @internal */
function subdomainLabel(size: Size) {
const alphaNumericArb = buildLowerAlphaNumericArbitrary([]);
const alphaNumericHyphenArb = buildLowerAlphaNumericArbitrary(['-']);
const alphaNumericArb = getOrCreateLowerAlphaNumericArbitrary('');
const alphaNumericHyphenArb = getOrCreateLowerAlphaNumericArbitrary('-');
// Rq: maxLength = 61 because max length of a label is 63 according to RFC 1034
// and we add 2 characters to this generated value
// According to RFC 1034 (confirmed by RFC 1035):
Expand All @@ -48,7 +48,7 @@ function subdomainLabel(size: Size) {
// restriction on the first character is relaxed to allow either a letter or a digit. Host software MUST support this more liberal syntax."
return tuple(
alphaNumericArb,
option(tuple(stringOf(alphaNumericHyphenArb, { size, maxLength: 61 }), alphaNumericArb)),
option(tuple(string({ unit: alphaNumericHyphenArb, size, maxLength: 61 }), alphaNumericArb)),
)
.map(toSubdomainLabelMapper, toSubdomainLabelUnmapper)
.filter(filterInvalidSubdomainLabel);
Expand Down Expand Up @@ -118,8 +118,8 @@ export function domain(constraints: DomainConstraints = {}): Arbitrary<string> {
// A list of public suffixes can be found here: https://publicsuffix.org/list/public_suffix_list.dat
// our current implementation does not follow this list and generate a fully randomized suffix
// which is probably not in this list (probability would be low)
const alphaNumericArb = buildLowerAlphaArbitrary([]);
const publicSuffixArb = stringOf(alphaNumericArb, { minLength: 2, maxLength: 63, size: resolvedSizeMinusOne });
const lowerAlphaArb = getOrCreateLowerAlphaArbitrary();
const publicSuffixArb = string({ unit: lowerAlphaArb, minLength: 2, maxLength: 63, size: resolvedSizeMinusOne });
return (
// labels have between 1 and 63 characters
// domains are made of dot-separated labels and have up to 255 characters so that are made of up-to 128 labels
Expand Down
10 changes: 5 additions & 5 deletions packages/fast-check/src/arbitrary/emailAddress.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { array } from './array';
import { buildLowerAlphaNumericArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder';
import { getOrCreateLowerAlphaNumericArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder';
import { domain } from './domain';
import { stringOf } from './stringOf';
import { string } from './string';
import { tuple } from './tuple';
import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary';
import type { SizeForArbitrary } from './_internals/helpers/MaxLengthFromMinLength';
Expand Down Expand Up @@ -71,15 +71,15 @@ export interface EmailAddressConstraints {
* @public
*/
export function emailAddress(constraints: EmailAddressConstraints = {}): Arbitrary<string> {
const others = ['!', '#', '$', '%', '&', "'", '*', '+', '-', '/', '=', '?', '^', '_', '`', '{', '|', '}', '~'];
const atextArb = buildLowerAlphaNumericArbitrary(others);
const atextArb = getOrCreateLowerAlphaNumericArbitrary("!#$%&'*+-/=?^_`{|}~");
const localPartArb = adapter(
// Maximal length for the output of dotMapper is 64,
// In other words:
// - `stringOf(atextArb, ...)` cannot produce values having more than 64 characters
// - `array(...)` cannot produce more than 32 values
array(
stringOf(atextArb, {
string({
unit: atextArb,
minLength: 1,
maxLength: 64,
size: constraints.size,
Expand Down
7 changes: 3 additions & 4 deletions packages/fast-check/src/arbitrary/webAuthority.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary';
import { buildAlphaNumericPercentArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder';
import { getOrCreateAlphaNumericPercentArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder';
import { constant } from './constant';
import { domain } from './domain';
import { ipV4 } from './ipV4';
Expand All @@ -8,14 +8,13 @@ import { ipV6 } from './ipV6';
import { nat } from './nat';
import { oneof } from './oneof';
import { option } from './option';
import { stringOf } from './stringOf';
import { string } from './string';
import { tuple } from './tuple';
import type { SizeForArbitrary } from './_internals/helpers/MaxLengthFromMinLength';

/** @internal */
function hostUserInfo(size: SizeForArbitrary): Arbitrary<string> {
const others = ['-', '.', '_', '~', '!', '$', '&', "'", '(', ')', '*', '+', ',', ';', '=', ':'];
return stringOf(buildAlphaNumericPercentArbitrary(others), { size });
return string({ unit: getOrCreateAlphaNumericPercentArbitrary("-._~!$&'()*+,;=:"), size });
}

/** @internal */
Expand Down
7 changes: 3 additions & 4 deletions packages/fast-check/src/arbitrary/webSegment.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary';
import { buildAlphaNumericPercentArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder';
import { stringOf } from './stringOf';
import { getOrCreateAlphaNumericPercentArbitrary } from './_internals/builders/CharacterRangeArbitraryBuilder';
import { string } from './string';
import type { SizeForArbitrary } from './_internals/helpers/MaxLengthFromMinLength';

/**
Expand Down Expand Up @@ -31,6 +31,5 @@ export interface WebSegmentConstraints {
export function webSegment(constraints: WebSegmentConstraints = {}): Arbitrary<string> {
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
// segment = *pchar
const others = ['-', '.', '_', '~', '!', '$', '&', "'", '(', ')', '*', '+', ',', ';', '=', ':', '@'];
return stringOf(buildAlphaNumericPercentArbitrary(others), { size: constraints.size });
return string({ unit: getOrCreateAlphaNumericPercentArbitrary("-._~!$&'()*+,;=:@"), size: constraints.size });
}
31 changes: 31 additions & 0 deletions packages/fast-check/src/utils/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,37 @@
return safeApply(untouchedGet, instance, [key]);
}

// Map

const untouchedMapSet = Map.prototype.set;
const untouchedMapGet = Map.prototype.get;
function extractMapSet(instance: Map<unknown, unknown>) {
try {
return instance.set;
} catch (err) {
return undefined;
}

Check warning on line 339 in packages/fast-check/src/utils/globals.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/utils/globals.ts#L338-L339

Added lines #L338 - L339 were not covered by tests
}
function extractMapGet(instance: Map<unknown, unknown>) {
try {
return instance.get;
} catch (err) {
return undefined;
}

Check warning on line 346 in packages/fast-check/src/utils/globals.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/utils/globals.ts#L345-L346

Added lines #L345 - L346 were not covered by tests
}
export function safeMapSet<T, U>(instance: Map<T, U>, key: T, value: U): Map<T, U> {
if (extractMapSet(instance) === untouchedMapSet) {
return instance.set(key, value);
}
return safeApply(untouchedMapSet, instance, [key, value]);
}

Check warning on line 353 in packages/fast-check/src/utils/globals.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/utils/globals.ts#L352-L353

Added lines #L352 - L353 were not covered by tests
export function safeMapGet<T, U>(instance: Map<T, U>, key: T): U | undefined {
if (extractMapGet(instance) === untouchedMapGet) {
return instance.get(key);
}
return safeApply(untouchedMapGet, instance, [key]);
}

Check warning on line 359 in packages/fast-check/src/utils/globals.ts

View check run for this annotation

Codecov / codecov/patch

packages/fast-check/src/utils/globals.ts#L358-L359

Added lines #L358 - L359 were not covered by tests

// String

const untouchedSplit: (separator: string | RegExp, limit?: number) => string[] = String.prototype.split;
Expand Down
Loading