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

Add Recursive Arbs #1515

Merged
merged 14 commits into from
Jan 18, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@ import {
recursiveCandidDefinitionMemo
} from './recursive_candid_definition_memo';
import { DEFAULT_DEFINITION_MAX_DEPTH } from '../../config';
import { RecursiveShapes } from '../recursive';

export function candidDefinitionArb(
maxDepth: number = DEFAULT_DEFINITION_MAX_DEPTH,
recursiveShapes: RecursiveShapes,
parents: RecursiveCandidName[] = [],
constraints: DefinitionConstraints = {}
): CandidDefinitionArb {
return candidDefinitionMemo(parents, constraints)(maxDepth);
return candidDefinitionMemo(
parents,
constraints
)(constraints.depthLevel ?? DEFAULT_DEFINITION_MAX_DEPTH);
}

export function candidDefinitionMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { NatDefinitionArb } from '../primitive/nats/nat_arb';
import { NullDefinitionArb } from '../primitive/null';
import { TextDefinitionArb } from '../primitive/text';
import { PrincipalDefinitionArb } from '../reference/principal_arb';
import { CandidDefinitionArb } from './types';
import { PrimitiveDefinition, WithShapesArb } from './types';

export type PrimitiveDefinitionWeights = Partial<{
bool: number;
Expand Down Expand Up @@ -43,7 +43,7 @@ const PRIM_DEF_WEIGHTS_DEFAULT = {};

export function primitiveCandidDefinitionArb(
constraints: PrimitiveDefinitionWeights = PRIM_DEF_WEIGHTS_DEFAULT
): CandidDefinitionArb {
): WithShapesArb<PrimitiveDefinition> {
return fc.oneof(
{ arbitrary: BoolDefinitionArb(), weight: constraints.bool ?? 1 },
{ arbitrary: Float32DefinitionArb(), weight: constraints.float32 ?? 1 },
Expand Down
10 changes: 8 additions & 2 deletions property_tests/arbitraries/candid/candid_definition_arb/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import { CandidType as RuntimeCandidType } from '../../../../src/lib/candid/cand
import { ServiceMethodDefinition } from '../reference/service_arb/service_method_arb';
import { PrimitiveDefinitionWeights } from './simple_candid_definition_arb';
import { ComplexDefinitionWeights } from './complex_candid_definition_memo';
import { RecursiveShapes } from '../recursive';

export type CandidDefinitionMemo = (depthLevel: number) => CandidDefinitionArb;
export type CandidDefinitionMemo = (
depthLevel: number
) => WithShapesArb<CandidDefinition>;
export type RecursiveCandidDefinitionMemo = (
parents: RecursiveCandidName[],
constraints?: DefinitionConstraints
Expand All @@ -24,7 +27,10 @@ export type CandidDefinitionWeights = Partial<
>
>;

export type CandidDefinitionArb = fc.Arbitrary<CandidDefinition>;
export type WithShapes<T> = { definition: T; recursiveShapes: RecursiveShapes };
export type WithShapesArb<T> = fc.Arbitrary<WithShapes<T>>;

export type CandidDefinitionArb = WithShapesArb<CandidDefinition>;

export type CandidDefinition =
| MultiTypeConstructedDefinition
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
import fc from 'fast-check';
import { CorrespondingJSType } from './corresponding_js_type';
import { CandidDefinition } from './candid_definition_arb/types';
import { CandidDefinition, WithShapes } from './candid_definition_arb/types';
import { CandidValueAndMeta } from './candid_value_and_meta_arb';
import { CandidValues } from './candid_values_arb';
import { RecursiveShapes } from './recursive';

export function CandidValueAndMetaArbGenerator<
T extends CorrespondingJSType,
D extends CandidDefinition,
V extends CandidValues<T>
>(
DefinitionArb: fc.Arbitrary<D>,
ValueArb: (arb: D, constraints?: any) => fc.Arbitrary<V>,
DefinitionArb: fc.Arbitrary<WithShapes<D>>,
ValueArb: (
arb: D,
recursiveShapes: RecursiveShapes,
constraints?: any
) => fc.Arbitrary<V>,
valueConstraints?: any
): fc.Arbitrary<CandidValueAndMeta<any>> {
return DefinitionArb.chain((candidDefinition) =>
fc.tuple(
return DefinitionArb.chain((candidDefinitionAndShapes) => {
const candidDefinition = candidDefinitionAndShapes.definition;
const recursiveShape = candidDefinitionAndShapes.recursiveShapes;
bdemann marked this conversation as resolved.
Show resolved Hide resolved
return fc.tuple(
fc.constant(candidDefinition),
ValueArb(candidDefinition, valueConstraints)
)
).map(
ValueArb(candidDefinition, recursiveShape, valueConstraints)
);
}).map(
([
{
candidMeta: {
Expand Down
18 changes: 16 additions & 2 deletions property_tests/arbitraries/candid/candid_values_arb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
import { BlobValuesArb } from './constructed/blob_arb/values_arb';
import { CorrespondingJSType } from './corresponding_js_type';
import { RecursiveNameValuesArb } from './recursive/values_arb';
import { RecursiveShapes } from './recursive';

export type CandidValues<T extends CorrespondingJSType, E = T> = {
agentArgumentValue: T;
Expand All @@ -46,35 +47,47 @@ export type CandidValues<T extends CorrespondingJSType, E = T> = {

export function CandidValueArb(
candidTypeMeta: CandidDefinition,
recursiveShapes: RecursiveShapes,
depthLevel: number
): fc.Arbitrary<CandidValues<CorrespondingJSType>> {
const candidType = candidTypeMeta.candidMeta.candidType;
if (candidType === 'blob') {
return BlobValuesArb();
}
if (candidType === 'Opt') {
return OptValuesArb(candidTypeMeta as OptCandidDefinition, depthLevel);
return OptValuesArb(
candidTypeMeta as OptCandidDefinition,
recursiveShapes,
depthLevel
);
}
if (candidType === 'Record') {
return RecordValuesArb(
candidTypeMeta as RecordCandidDefinition,
recursiveShapes,
depthLevel
);
}
if (candidType === 'Tuple') {
return TupleValuesArb(
candidTypeMeta as TupleCandidDefinition,
recursiveShapes,
depthLevel
);
}
if (candidType === 'Variant') {
return VariantValuesArb(
candidTypeMeta as VariantCandidDefinition,
recursiveShapes,
depthLevel
);
}
if (candidType === 'Vec') {
return VecValuesArb(candidTypeMeta as VecCandidDefinition, depthLevel);
return VecValuesArb(
candidTypeMeta as VecCandidDefinition,
recursiveShapes,
depthLevel
);
}
if (candidType === 'bool') {
return BoolValueArb();
Expand Down Expand Up @@ -136,6 +149,7 @@ export function CandidValueArb(
if (candidType === 'Recursive') {
return RecursiveNameValuesArb(
candidTypeMeta as RecursiveCandidName | RecursiveCandidDefinition,
recursiveShapes,
depthLevel
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
import fc from 'fast-check';
import { UniqueIdentifierArb } from '../../../unique_identifier_arb';
import { BlobCandidDefinition } from '../../candid_definition_arb/types';
import {
BlobCandidDefinition,
WithShapes,
WithShapesArb
} from '../../candid_definition_arb/types';
import { SimpleCandidDefinitionArb } from '../../simple_type_arbs/definition_arb';
import { blob } from '../../../../../src/lib';

export function BlobDefinitionArb(): fc.Arbitrary<BlobCandidDefinition> {
export function BlobDefinitionArb(): WithShapesArb<BlobCandidDefinition> {
return fc.oneof(SimpleCandidDefinitionArb('blob'), _VecNat8DefinitionArb());
}

export function _VecNat8DefinitionArb(): fc.Arbitrary<BlobCandidDefinition> {
export function _VecNat8DefinitionArb(): WithShapesArb<BlobCandidDefinition> {
return fc
.tuple(UniqueIdentifierArb('typeDeclaration'), fc.boolean())
.map(([name, useTypeDeclaration]): BlobCandidDefinition => {
.map(([name, useTypeDeclaration]): WithShapes<BlobCandidDefinition> => {
const candidTypeAnnotation = 'Vec<nat8>';
const candidTypeObject = 'Vec(nat8)';
const variableAliasDeclarations = useTypeDeclaration
? [`const ${name} = ${candidTypeObject}`]
: [];
const imports = new Set(['Vec', 'nat8']);
return {
candidMeta: {
runtimeCandidTypeObject: blob,
candidTypeAnnotation,
candidTypeObject,
variableAliasDeclarations,
imports,
candidType: 'blob'
}
definition: {
candidMeta: {
runtimeCandidTypeObject: blob,
candidTypeAnnotation,
candidTypeObject,
variableAliasDeclarations,
imports,
candidType: 'blob'
}
},
recursiveShapes: {}
};
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ import {
DefinitionConstraints,
OptCandidDefinition,
RecursiveCandidDefinitionMemo,
RecursiveCandidName
RecursiveCandidName,
WithShapes,
WithShapesArb
} from '../../candid_definition_arb/types';
import { CandidType, Opt } from '../../../../../src/lib';
import { RecursiveShapes } from '../../recursive';

export function OptDefinitionArb(
candidTypeArbForInnerType: RecursiveCandidDefinitionMemo,
parents: RecursiveCandidName[],
constraints: DefinitionConstraints
): fc.Arbitrary<OptCandidDefinition> {
): WithShapesArb<OptCandidDefinition> {
return fc
.tuple(
UniqueIdentifierArb('typeDeclaration'),
Expand All @@ -24,49 +27,61 @@ export function OptDefinitionArb(
),
fc.boolean()
)
.map(([name, innerType, useTypeDeclaration]): OptCandidDefinition => {
const candidTypeAnnotation = generateCandidTypeAnnotation(
useTypeDeclaration,
.map(
([
name,
innerType
);
innerTypeAndShapes,
useTypeDeclaration
]): WithShapes<OptCandidDefinition> => {
const { definition: innerType, recursiveShapes } =
innerTypeAndShapes;
const candidTypeAnnotation = generateCandidTypeAnnotation(
useTypeDeclaration,
name,
innerType
);

const candidTypeObject = generateCandidTypeObject(
useTypeDeclaration,
name,
innerType
);
const candidTypeObject = generateCandidTypeObject(
useTypeDeclaration,
name,
innerType
);

const runtimeCandidTypeObject =
generateRuntimeCandidTypeObject(innerType);
const runtimeCandidTypeObject =
generateRuntimeCandidTypeObject(innerType);

const variableAliasDeclarations = generateVariableAliasDeclarations(
useTypeDeclaration,
name,
innerType
);
const variableAliasDeclarations =
generateVariableAliasDeclarations(
useTypeDeclaration,
name,
innerType
);

const imports = generateImports(innerType);
const imports = generateImports(innerType);

return {
candidMeta: {
candidTypeAnnotation,
candidTypeObject,
runtimeCandidTypeObject,
variableAliasDeclarations,
imports,
candidType: 'Opt'
},
innerType
};
});
return {
definition: {
candidMeta: {
candidTypeAnnotation,
candidTypeObject,
runtimeCandidTypeObject,
variableAliasDeclarations,
imports,
candidType: 'Opt'
},
innerType
},
recursiveShapes
};
}
);
}

function possiblyRecursiveArb(
candidArb: RecursiveCandidDefinitionMemo,
parents: RecursiveCandidName[],
constraints: DefinitionConstraints
): fc.Arbitrary<CandidDefinition> {
): WithShapesArb<CandidDefinition> {
const depthLevel = constraints.depthLevel ?? 0;
return fc.nat(Math.max(parents.length - 1, 0)).chain((randomIndex) => {
if (parents.length === 0 || depthLevel < 1) {
Expand All @@ -75,8 +90,17 @@ function possiblyRecursiveArb(
return candidArb(parents)(depthLevel);
}
return fc.oneof(
{ arbitrary: fc.constant(parents[randomIndex]), weight: 1 },
{ arbitrary: candidArb(parents)(depthLevel), weight: 1 }
{
arbitrary: fc.constant({
definition: parents[randomIndex],
recursiveShapes: {}
}),
weight: 1
},
{
arbitrary: candidArb(parents)(depthLevel),
weight: 1
}
);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@ import { Opt } from '.';
import { CorrespondingJSType } from '../../corresponding_js_type';
import { OptCandidDefinition } from '../../candid_definition_arb/types';
import { CandidValues, CandidValueArb } from '../../candid_values_arb';
import { RecursiveShapes } from '../../recursive';

type SomeOrNone = 'Some' | 'None';

export function OptValuesArb(
optDefinition: OptCandidDefinition,
recursiveShapes: RecursiveShapes,
depthLevel: number
): fc.Arbitrary<CandidValues<Opt>> {
if (depthLevel < 1) {
return fc.constant(generateNoneValue());
}
const innerValue = fc.tuple(
fc.constantFrom('Some', 'None') as fc.Arbitrary<SomeOrNone>,
CandidValueArb(optDefinition.innerType, depthLevel - 1)
CandidValueArb(optDefinition.innerType, recursiveShapes, depthLevel - 1)
);

return innerValue.map(([someOrNone, innerType]) => {
Expand Down
Loading
Loading