Skip to content

Commit

Permalink
add constraints to the candid arbs
Browse files Browse the repository at this point in the history
  • Loading branch information
bdemann committed Jan 15, 2024
1 parent 488b057 commit 50a380b
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import { VariantDefinitionArb } from '../constructed/variant_arb/definition_arbs
import { VecDefinitionArb } from '../constructed/vec_arb/definition_arb';
import { FuncDefinitionArb } from '../reference/func_arb/definition_arb';
import { ServiceDefinitionArb } from '../reference/service_arb/definition_arb';
import { CandidDefinitionMemo, RecursiveCandidDefinition } from './types';
import {
CandidDefinitionMemo,
DefinitionConstraints,
RecursiveCandidDefinition
} from './types';

export type ComplexDefinitionWeights = Partial<{
blob: number;
Expand All @@ -22,48 +26,73 @@ export type ComplexDefinitionWeights = Partial<{
}>;

// The number of options below (blob, func, opt, etc)
export const COMPLEX_ARB_COUNT = 9; // TODO change this to 8 after we debug the current issue
export const COMPLEX_ARB_COUNT = 8;

export function complexCandidDefinitionMemo(
parents: RecursiveCandidDefinition[],
weights: ComplexDefinitionWeights = {}
constraints: DefinitionConstraints = {}
): CandidDefinitionMemo {
const weights = constraints.weights ?? {};
const newConstraints: DefinitionConstraints = {
n: constraints.n,
weights:
constraints.recursive_weights ?? false
? constraints.weights ?? {}
: {},
recursive_weights: constraints.recursive_weights
};
return fc.memo((n) => {
return fc.oneof(
{
arbitrary: BlobDefinitionArb(),
weight: weights.blob ?? 1
},
{
arbitrary: FuncDefinitionArb(candidDefinitionMemo([])(n)),
arbitrary: FuncDefinitionArb(
candidDefinitionMemo([], newConstraints)(n)
),
weight: weights.func ?? 1
},
{
arbitrary: OptDefinitionArb(candidDefinitionMemo, parents, n),
arbitrary: OptDefinitionArb(
candidDefinitionMemo,
parents,
newConstraints
),
weight: weights.opt ?? 1
},
{
arbitrary: RecordDefinitionArb(candidDefinitionMemo([])(n)),
arbitrary: RecordDefinitionArb(
candidDefinitionMemo([], newConstraints)(n)
),
weight: weights.record ?? 1
},
{
arbitrary: TupleDefinitionArb(candidDefinitionMemo([])(n)),
arbitrary: TupleDefinitionArb(
candidDefinitionMemo([], newConstraints)(n)
),
weight: weights.tuple ?? 1
},
{
arbitrary: VariantDefinitionArb(
candidDefinitionMemo,
parents,
n
newConstraints
),
weight: weights.variant ?? 1
},
{
arbitrary: VecDefinitionArb(candidDefinitionMemo, parents, n),
arbitrary: VecDefinitionArb(
candidDefinitionMemo,
parents,
constraints
),
weight: weights.vec ?? 1
},
{
arbitrary: ServiceDefinitionArb(candidDefinitionMemo([])(n)),
arbitrary: ServiceDefinitionArb(
candidDefinitionMemo([], newConstraints)(n)
),
weight: weights.service ?? 0
// TODO Service is disabled until it is more refined. Maybe the
// only thing missing is deepEquals
Expand Down
15 changes: 9 additions & 6 deletions property_tests/arbitraries/candid/candid_definition_arb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
CandidDefinitionArb,
RecursiveCandidDefinition,
CandidDefinitionMemo,
CandidDefinitionWeights
DefinitionConstraints
} from './types';
import {
COMPLEX_ARB_COUNT,
Expand All @@ -22,26 +22,29 @@ import { DEFAULT_DEF_MAX_DEPTH } from '../../config';
export function candidDefinitionArb(
maxDepth: number = DEFAULT_DEF_MAX_DEPTH,
parents: RecursiveCandidDefinition[] = [],
weights: CandidDefinitionWeights = {}
constraints: DefinitionConstraints = {}
): CandidDefinitionArb {
return candidDefinitionMemo(parents, weights)(maxDepth);
return candidDefinitionMemo(parents, constraints)(maxDepth);
}

export function candidDefinitionMemo(
parents: RecursiveCandidDefinition[],
weights: CandidDefinitionWeights = {}
constraints: DefinitionConstraints = {}
): CandidDefinitionMemo {
return fc.memo((n) => {
if (n <= 1) {
return primitiveCandidDefinitionArb();
}
return fc.oneof(
{
arbitrary: primitiveCandidDefinitionArb(weights),
arbitrary: primitiveCandidDefinitionArb(constraints.weights),
weight: PRIM_ARB_COUNT
},
{
arbitrary: complexCandidDefinitionMemo(parents, weights)(n - 1),
arbitrary: complexCandidDefinitionMemo(
parents,
constraints
)(n - 1),
weight: COMPLEX_ARB_COUNT
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import { complexCandidDefinitionMemo } from './complex_candid_definition_memo';
import { RecursiveCandidDefinition, CandidDefinitionMemo } from './types';

// The number of options below (it's just recursive)
export const REC_ARB_COUNT = 1;
export const REC_ARB_COUNT = 0;
// TODO there are a lot of bugs with recursion so we are disabling the whole thing until these issues are resolved
// https://github.com/demergent-labs/azle/issues/1518
// https://github.com/demergent-labs/azle/issues/1513
// https://github.com/demergent-labs/azle/issues/1525

export function recursiveCandidDefinitionMemo(
parents: RecursiveCandidDefinition[]
): CandidDefinitionMemo {
return fc.memo((n) =>
RecursiveDefinitionArb(complexCandidDefinitionMemo, parents, n)
RecursiveDefinitionArb(complexCandidDefinitionMemo, parents, { n })
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import { PrimitiveDefinitionWeights } from './simple_candid_definition_arb';
import { ComplexDefinitionWeights } from './complex_candid_definition_memo';

export type CandidDefinitionMemo = (n: number) => CandidDefinitionArb;
export type RecCandidDefMemo = (
export type RecursiveCandidDefinitionMemo = (
parents: RecursiveCandidDefinition[],
constraints?: ComplexDefinitionWeights
constraints?: DefinitionConstraints
) => CandidDefinitionMemo;

export type DefinitionConstraints = {
export type DefinitionConstraints = Partial<{
n: number;
recursive_weights: boolean;
weights: CandidDefinitionWeights;
};
}>;

export type CandidDefinitionWeights = Partial<
Record<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,26 @@ import fc from 'fast-check';
import { UniqueIdentifierArb } from '../../../unique_identifier_arb';
import {
CandidDefinition,
DefinitionConstraints,
OptCandidDefinition,
RecCandidDefMemo,
RecursiveCandidDefinitionMemo,
RecursiveCandidDefinition
} from '../../candid_definition_arb/types';
import { CandidType, Opt } from '../../../../../src/lib';

function possiblyRecursiveArb(
candidArb: RecCandidDefMemo,
parents: RecursiveCandidDefinition[],
n: number
): fc.Arbitrary<CandidDefinition> {
return fc.nat(Math.max(parents.length - 1, 0)).chain((randomIndex) => {
if (parents.length === 0 || n < 1) {
// If there are no recursive parents or this is the first variant field just do a regular arb field
return candidArb(parents)(n);
}
return fc.oneof(
{ arbitrary: fc.constant(parents[randomIndex]), weight: 1 },
{ arbitrary: candidArb(parents)(n), weight: 1 }
);
});
}

export function OptDefinitionArb(
candidTypeArbForInnerType: RecCandidDefMemo,
candidTypeArbForInnerType: RecursiveCandidDefinitionMemo,
parents: RecursiveCandidDefinition[],
n: number
constraints: DefinitionConstraints
): fc.Arbitrary<OptCandidDefinition> {
return fc
.tuple(
UniqueIdentifierArb('typeDeclaration'),
possiblyRecursiveArb(candidTypeArbForInnerType, parents, n),
possiblyRecursiveArb(
candidTypeArbForInnerType,
parents,
constraints
),
fc.boolean()
)
.map(([name, innerType, useTypeDeclaration]): OptCandidDefinition => {
Expand Down Expand Up @@ -74,6 +62,24 @@ export function OptDefinitionArb(
});
}

function possiblyRecursiveArb(
candidArb: RecursiveCandidDefinitionMemo,
parents: RecursiveCandidDefinition[],
constraints: DefinitionConstraints
): fc.Arbitrary<CandidDefinition> {
const n = constraints.n ?? 0;
return fc.nat(Math.max(parents.length - 1, 0)).chain((randomIndex) => {
if (parents.length === 0 || n < 1) {
// If there are no recursive parents or this is the first variant field just do a regular arb field
return candidArb(parents)(n);
}
return fc.oneof(
{ arbitrary: fc.constant(parents[randomIndex]), weight: 1 },
{ arbitrary: candidArb(parents)(n), weight: 1 }
);
});
}

function generateVariableAliasDeclarations(
useTypeDeclaration: boolean,
name: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export type Opt = [CorrespondingJSType] | never[];

export function OptArb(): fc.Arbitrary<CandidValueAndMeta<Opt>> {
return CandidValueAndMetaArbGenerator(
OptDefinitionArb(candidDefinitionMemo, [], DEFAULT_DEF_MAX_DEPTH),
OptDefinitionArb(candidDefinitionMemo, [], {
n: DEFAULT_DEF_MAX_DEPTH
}),
OptValuesArb
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import fc from 'fast-check';
import { UniqueIdentifierArb } from '../../../unique_identifier_arb';
import {
CandidDefinition,
RecCandidDefMemo,
DefinitionConstraints,
RecursiveCandidDefinitionMemo,
RecursiveCandidDefinition,
VariantCandidDefinition
} from '../../candid_definition_arb/types';
Expand All @@ -16,14 +17,14 @@ type RuntimeVariant = {
};

export function VariantDefinitionArb(
candidTypeArbForFields: RecCandidDefMemo,
candidTypeArbForFields: RecursiveCandidDefinitionMemo,
parents: RecursiveCandidDefinition[],
n: number
constraints: DefinitionConstraints
): fc.Arbitrary<VariantCandidDefinition> {
return fc
.tuple(
UniqueIdentifierArb('typeDeclaration'),
VariantFieldsArb(candidTypeArbForFields, parents, n),
VariantFieldsArb(candidTypeArbForFields, parents, constraints),
fc.boolean()
)
.map(([name, fields, useTypeDeclaration]): VariantCandidDefinition => {
Expand Down Expand Up @@ -65,9 +66,9 @@ export function VariantDefinitionArb(
}

function VariantFieldsArb(
candidTypeArb: RecCandidDefMemo,
candidTypeArb: RecursiveCandidDefinitionMemo,
parents: RecursiveCandidDefinition[],
n: number
constraints: DefinitionConstraints
): fc.Arbitrary<Field[]> {
// Although no minLength is technically required (according to the
// spec), the DFX CLI itself currently errors out trying to pass
Expand All @@ -82,13 +83,46 @@ function VariantFieldsArb(
...fieldsNames.map((name, index) =>
fc.tuple(
fc.constant(name),
possiblyRecursiveArb(candidTypeArb, index, parents, n)
possiblyRecursiveArb(
candidTypeArb,
index,
parents,
constraints
)
)
)
)
);
}

// Recursion requires at least one of the fields to be a "base case", or in
// other words it needs to terminate or else we will get an infinitely deep
// shape. For simplicity we have made the first index always be a base case so
// we are guaranteed to have at least one. Consequently this function takes the
// index of the field for which it's generating an arbitrary. If it's 0 then we
// will not allow a recursive option to be picked for that field. A more
// complicated approach would involve the guaranteed base case being in any one
// of the fields, instead of always the first one.
// TODO rename and move to the bottom.
function possiblyRecursiveArb(
candidArb: RecursiveCandidDefinitionMemo,
index: number,
parents: RecursiveCandidDefinition[],
constraints: DefinitionConstraints
): fc.Arbitrary<CandidDefinition> {
const n = constraints.n ?? 0;
return fc.nat(Math.max(parents.length - 1, 0)).chain((randomIndex) => {
if (parents.length === 0 || index < 1) {
// If there are no recursive parents or this is the first variant field just do a regular arb field
return candidArb(parents)(n);
}
return fc.oneof(
{ arbitrary: fc.constant(parents[randomIndex]), weight: 1 },
{ arbitrary: candidArb(parents)(n), weight: 1 }
);
});
}

function generateImports(fields: Field[]): Set<string> {
const fieldImports = fields.flatMap((field) => [
...field[1].candidMeta.imports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export type Variant = {

export function VariantArb(): fc.Arbitrary<CandidValueAndMeta<Variant>> {
return CandidValueAndMetaArbGenerator(
VariantDefinitionArb(candidDefinitionMemo, [], DEFAULT_DEF_MAX_DEPTH),
VariantDefinitionArb(candidDefinitionMemo, [], {
n: DEFAULT_DEF_MAX_DEPTH
}),
VariantValuesArb
);
}
Loading

0 comments on commit 50a380b

Please sign in to comment.