Skip to content

Expose harness seams so SDK repos can build their own black-box tests #18

Description

@TomChv

Context

In dagger/typescript-sdk we moved module codegen out of the engine and into the SDK module, so the SDK now writes the tree the engine runtime executes. That made "does a generated module actually run?" the contract worth testing, and it needs cases sdk-sdk's own check set doesn't cover — notably one per TypeScript runtime, since node, bun and deno each execute the entrypoint in a different container with a different interpreter.

Depending on sdk-sdk to build those was the obvious move: the harness already does the hard part, vendoring the SDK into a scratch git workspace and driving a release CLI through sdk install → module init → generate.

Part of it worked. SdkSdk.target(workspaceView, sourceRootPath) is public, and target.run([...]) runs an arbitrary CLI command against the generated state, so invoking a function on a scaffolded module is a one-liner:

let target = sdkSdk.target(ws.directory("/"), ".")
let run = target.run(["call", target.moduleName, "base-image-address"])
run.assertSuccess

Two things stopped us going further.

1. No seam for SDK-specific init arguments

initArgs is private and fixed (sdk-target.dang):

let initArgs: [String!]! {
  ["module", "init", sdkInstallName, moduleName]
}

A TypeScript module's runtime is chosen at init (--runtime bun), and that choice changes nothing about generation but everything about execution. There's no way to reach it: the field is private, and SdkTarget's constructor fields (workspaceView, sourceRootPath) are private too, so the target can't be rebuilt with different args from a dependent module.

Every SDK with variants has this shape — Python's package manager, Go's toolchain, our three runtimes.

Suggestion: a builder alongside the existing withTimeout, which already sets the convention:

pub withInitArgs(args: [String!]!): SdkTarget!    # extra args appended to `module init`

so a dependent can write target.withInitArgs(["--runtime", "bun"]).run([...]).

2. Runs don't compose

SdkRun.container is private (sdk-run.dang), so a run is terminal: you get stdout/stderr/exitCode but not the state it produced. runInstalled, run and runInModule each start from a state the harness picked, so a custom sequence — init with a flag, then generate, then call — can't be expressed.

Suggestion: either make the resulting state reachable (pub state: Container! on SdkRun, or pub then(args: [String!]!): SdkRun!), or expose the intermediate states (pub installedState / initializedState / generatedState) so callers can pipe their own commands.

What we did instead

Built the per-runtime checks with our own fixtures, loading each generated module to force the runtime to execute its entrypoint, and used sdk-sdk only for the invocation case: dagger/typescript-sdk#31

That works, but it reimplements scaffolding sdk-sdk already does well, and it means our runtime coverage drifts from the contract suite instead of extending it.

Why it seems worth doing

sdk-sdk is already the place SDK authors go for black-box coverage. Right now that's all-or-nothing: you get the built-in checks, or you rebuild the harness. A couple of small seams would make it a library SDK repos extend with their own language-specific cases, which is where most of the interesting failures live.

Happy to send a PR if the shape above sounds right — in particular whether you'd prefer withInitArgs or something more structured like a moduleOptions map.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions