diff --git a/src/convert.test.ts b/src/convert.test.ts deleted file mode 100644 index 535f371..0000000 --- a/src/convert.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import * as Codegen from "./convert"; -import * as Types from "./types"; - -const stable: Types.Output = { - kind: "Object", - name: "Stable" as Types.TypeName, - output: "fc.record({horse: arbitraryHorse })" as Types.Generated, - deps: ["Horse" as Types.TypeName] -}; -const horse: Types.Output = { - kind: "Object", - name: "Horse" as Types.TypeName, - output: "fc.record({saddle: arbitrarySaddle})" as Types.Generated, - deps: ["Saddle" as Types.TypeName] -}; -const saddle: Types.Output = { - kind: "Object", - name: "Saddle" as Types.TypeName, - output: "fc.record({chair: arbitraryChair})" as Types.Generated, - deps: [] -}; -const things: Types.Output = { - kind: "Union", - name: "Things" as Types.TypeName, - output: "fc.oneof(arbitraryStable, arbitrarySaddle)" as Types.Generated, - deps: ["Stable" as Types.TypeName, "Saddle" as Types.TypeName] -}; - -describe("sortASTs", () => { - it("Puts mentions later", () => { - const expectedOrder = [ - "Saddle" as Types.TypeName, - "Horse" as Types.TypeName, - "Stable" as Types.TypeName, - "Things" as Types.TypeName - ]; - expect( - Codegen.sortASTs([things, stable, horse, saddle]).map(a => a.name) - ).toStrictEqual(expectedOrder); - expect( - Codegen.sortASTs([stable, horse, saddle, things]).map(a => a.name) - ).toStrictEqual(expectedOrder); - }); -}); - -describe("moveASTs", () => { - it("Moves everything with zero deps", () => { - const used: Types.Output[] = []; - const remaining = [stable, horse, saddle, things]; - const result = Codegen.moveASTs(used, remaining); - if (result._tag === "Right") { - expect(result.payload.used.length).toEqual(1); - expect(result.payload.remaining.length).toEqual(3); - } else { - throw "moveASTs failed"; - } - }); - it("Moves everything with deps that have already been moved across", () => { - const used = [saddle]; - const remaining = [horse, stable, things]; - const result = Codegen.moveASTs(used, remaining); - if (result._tag === "Right") { - expect(result.payload.used.length).toEqual(2); - expect(result.payload.remaining.length).toEqual(2); - } else { - throw "moveASTs failed"; - } - }); - it("Bruce forces it to a satifying conclusion", () => { - const remaining = [stable, horse, saddle, things]; - const result = Codegen.magicSort(remaining, 100); - if (result._tag === "Right") { - expect(result && result.payload.length).toEqual(4); - } else { - throw "fail!"; - } - }); -}); diff --git a/src/convert.ts b/src/convert.ts index 1a1e8b7..87d582a 100644 --- a/src/convert.ts +++ b/src/convert.ts @@ -30,7 +30,9 @@ type FieldReturn = { const withNamedFieldType = (namedTypeNode: NamedTypeNode): FieldReturn => { const name = namedTypeNode.name.value as TypeName; - const output = `${getArbitraryName(name)}()` as Generated; + const output = `(limit > 0) ? ${getArbitraryName( + name + )}(limit - 1) : fc.constant(null)` as Generated; return { output, deps: [name] }; }; @@ -193,107 +195,15 @@ const removeKind = (k: Kind) => (a: Output) => a.kind !== k; const render = (val: Output) => { const { name, output } = val; + const recurseLimit = 10; return `export const ${getArbitraryName( name - )} = (): fc.Arbitrary => ${output}`; + )} = (limit = ${recurseLimit}): fc.Arbitrary => ${output}`; }; export const getSchemaDeclarations = (schema: GraphQLSchema): string => - sortASTs2(getNamedTypes(schema).map(withNamedType).filter(notNull)) + getNamedTypes(schema) + .map(withNamedType) + .filter(notNull) .map(render) .join("\n\n"); - -const sortASTs2 = (a: A): A => a; - -const filterSplit = ( - as: A[], - f: (a: A) => boolean -): { yes: A[]; no: A[] } => ({ - yes: as.filter(f), - no: as.filter(a => !f(a)) -}); - -export const sortASTs = (as: Output[]): Output[] => { - const limit = 10000; - return caseEither(magicSort(as, limit), { - onRight: payload => payload, - onLeft: err => { - throw err; - return []; - } - }); -}; - -const showProgress = (used: Output[], remaining: Output[]): void => { - const unresolved = [ - ...new Set(remaining.map(a => a.deps).reduce((as, a) => as.concat(a), [])) - ]; - const resolved = used.map(a => a.name); - console.log("RESOLVED", resolved); - console.log("UNRESOLVED", unresolved); -}; - -export const magicSort = ( - as: Output[], - startingLimit: number -): Either => { - let limit = startingLimit; - let newRemaining = as; - let newUsed: Output[] = []; - let error = `Could not resolve ordering within ${startingLimit} tries`; - while (newRemaining.length > 0 && limit > 0) { - const succeeded = caseEither(moveASTs(newUsed, newRemaining), { - onRight: payload => { - newUsed = payload.used; - newRemaining = payload.remaining; - limit = limit - 1; - return true; - }, - onLeft: err => { - error = err; - return false; - } - }); - // showProgress(newUsed, newRemaining); - if (!succeeded) { - break; - } - } - if (newRemaining.length > 0) { - return left(error); - } - return right(newUsed); -}; - -type ASTReturn = Either< - string, - { - used: Output[]; - remaining: Output[]; - } ->; - -export const moveASTs = (used: Output[], remaining: Output[]): ASTReturn => { - // remove everything in used from deps list - const usedDeps = used.map(a => a.name); - - const remainingFiltered = remaining.map(a => ({ - ...a, - deps: a.deps.filter(dep => !usedDeps.includes(dep)) - })); - - // move everything with empty deps list left - const { yes: moved, no: keep } = filterSplit( - remainingFiltered, - a => a.deps.length === 0 - ); - - // done - const newUsed = [...used, ...moved]; - if (newUsed.length === used.length) { - return left( - `No changes made, unresolvable. ${newUsed.length} moved, ${keep.length} remaining to move.` - ); - } - return right({ used: newUsed, remaining: keep }); -}; diff --git a/src/index.ts b/src/index.ts index 756b829..be27d5c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { getSchemaDeclarations } from "./convert"; module.exports = { plugin: (schema: GraphQLSchema, documents: any, config: any) => { const declarations = getSchemaDeclarations(schema); - const output = `import * as fc from 'fast-check'\n${declarations}`; + const output = `import * as fc from 'fast-check'\nconst RECURSE_LIMIT=10\n${declarations}`; return format(output, { parser: "typescript" }); diff --git a/test/test-output.ts b/test/test-output.ts index 2f6698d..8ecbe1c 100644 --- a/test/test-output.ts +++ b/test/test-output.ts @@ -1,16 +1,32 @@ -import * as generated from "../output/twitter/output"; +import * as twitter from "../output/twitter/output"; +import * as github from "../output/github/output"; import * as fc from "fast-check"; -const items: any = { ...generated }; - -Object.keys(items).forEach((key: string) => { - const func = items[key]; - console.log(`---------------${key}-----------------`); - const arb = (func as any)(); - fc.assert( - fc.property(arb, a => { - console.log(a); - return true; - }) - ); -}); +const twitterItems: any = { ...twitter }; + +Object.keys(twitterItems) + .map((key: string) => { + const func = twitterItems[key]; + const arb = (func as any)(); + return [key, fc.sample(arb, 1)[0]]; + }) + .map(console.log); + +const githubItems: any = { ...github }; + +console.log("github has ", Object.keys(githubItems).length, " items"); + +const someKeys = Object.keys(githubItems); // .slice(0, 110); + +console.log("lets try out ", someKeys.length, " first"); + +// we can override this for fucking huge things that blow the stack +const recurseLimit = 4; + +someKeys + .map((key: string) => { + const func = githubItems[key]; + const arb = (func as any)(recurseLimit); + return [key, fc.sample(arb, 1)[0]]; + }) + .map(console.log);