diff --git a/examples/tests/sdk_tests/function/timeout.test.w b/examples/tests/sdk_tests/function/timeout.test.w index 7f4eb9c1c9d..8bf2df09b3e 100644 --- a/examples/tests/sdk_tests/function/timeout.test.w +++ b/examples/tests/sdk_tests/function/timeout.test.w @@ -35,9 +35,7 @@ new std.Test(inflight () => { assert(e.contains("Task timed out after")); } - if (util.env("WING_TARGET") != "sim") { - assert(c.peek() == 0); - } + assert(c.peek() == 0); try { f2.invoke(""); @@ -49,8 +47,6 @@ new std.Test(inflight () => { assert(e.contains("Task timed out after")); } - if (util.env("WING_TARGET") != "sim") { - assert(c.peek() == 1); - } + assert(c.peek() == 1); }, std.TestProps {timeout: 2m}) as "timeout"; diff --git a/examples/tests/valid/deep_equality.test.w b/examples/tests/valid/deep_equality.test.w index bc35235a234..172a2f6562d 100644 --- a/examples/tests/valid/deep_equality.test.w +++ b/examples/tests/valid/deep_equality.test.w @@ -39,6 +39,11 @@ test "Json with different values" { assert(!(jsonA != jsonB)); } +test "Json.values equality" { + let j = Json { hello: 123, world: [1, 2, 3] }; + assert(Json.values(j) == [Json 123, Json [1, 2, 3]]); +} + //----------------------------------------------------------------------------- // Set //----------------------------------------------------------------------------- diff --git a/examples/tests/valid/inflight_handler_singleton.test.w b/examples/tests/valid/inflight_handler_singleton.test.w index f9901e146d1..d5d526a2c28 100644 --- a/examples/tests/valid/inflight_handler_singleton.test.w +++ b/examples/tests/valid/inflight_handler_singleton.test.w @@ -36,11 +36,7 @@ test "single instance of Foo" { expect.equal(x, "100"); expect.equal(z, "100-fn2"); // fn2 should have a separate instance - // the simulator intentionally reuses the sandbox across invocations - // but we can't trust that this will always happen on the cloud - if sim { - expect.equal(y, "101"); - expect.equal(z, "100-fn2"); // fn2 should have a separate instance - log("client has been reused"); - } + // y could be 100 or 101 depending on whether the execution environment + // was reused or not between the two calls. + assert(y == "100" || y == "101"); } diff --git a/libs/wingsdk/.projen/deps.json b/libs/wingsdk/.projen/deps.json index 4a2a14462d0..9fd500c9965 100644 --- a/libs/wingsdk/.projen/deps.json +++ b/libs/wingsdk/.projen/deps.json @@ -317,10 +317,6 @@ "name": "ulid", "type": "bundled" }, - { - "name": "undici", - "type": "bundled" - }, { "name": "uuid", "type": "bundled" diff --git a/libs/wingsdk/.projenrc.ts b/libs/wingsdk/.projenrc.ts index 4521f9817ad..8c7642eab67 100644 --- a/libs/wingsdk/.projenrc.ts +++ b/libs/wingsdk/.projenrc.ts @@ -75,7 +75,6 @@ const project = new cdk.JsiiProject({ // simulator dependencies "express", "uuid", - "undici", // using version 3 because starting from version 4, it no longer works with CommonJS. "nanoid@^3.3.6", "cron-parser", diff --git a/libs/wingsdk/package.json b/libs/wingsdk/package.json index ade15c1fc9d..4c21da8e180 100644 --- a/libs/wingsdk/package.json +++ b/libs/wingsdk/package.json @@ -112,7 +112,6 @@ "stacktracey": "^2.1.8", "toml": "^3.0.0", "ulid": "^2.3.0", - "undici": "^6.6.2", "uuid": "^8.3.2", "yaml": "^2.3.2" }, @@ -152,7 +151,6 @@ "stacktracey", "toml", "ulid", - "undici", "uuid", "yaml" ], diff --git a/libs/wingsdk/src/shared/bundling.ts b/libs/wingsdk/src/shared/bundling.ts index 146466c794e..e8ffbba8b50 100644 --- a/libs/wingsdk/src/shared/bundling.ts +++ b/libs/wingsdk/src/shared/bundling.ts @@ -1,6 +1,6 @@ import * as crypto from "crypto"; -import { mkdirSync, writeFileSync } from "fs"; -import { join, resolve } from "path"; +import { mkdirSync, realpathSync, writeFileSync } from "fs"; +import { posix, resolve } from "path"; import { normalPath } from "./misc"; const SDK_PATH = normalPath(resolve(__dirname, "..", "..")); @@ -25,21 +25,24 @@ export function createBundle( external: string[] = [], outputDir?: string ): Bundle { - const outdir = resolve(outputDir ?? entrypoint + ".bundle"); + const normalEntrypoint = normalPath(realpathSync(entrypoint)); + const outdir = outputDir + ? normalPath(realpathSync(outputDir)) + : `${normalEntrypoint}.bundle`; mkdirSync(outdir, { recursive: true }); const outfileName = "index.js"; const soucemapFilename = `${outfileName}.map`; - const outfile = join(outdir, outfileName); - const outfileMap = join(outdir, soucemapFilename); + const outfile = posix.join(outdir, outfileName); + const outfileMap = posix.join(outdir, soucemapFilename); // eslint-disable-next-line import/no-extraneous-dependencies,@typescript-eslint/no-require-imports const esbuilder: typeof import("esbuild") = require("esbuild"); let esbuild = esbuilder.buildSync({ bundle: true, - entryPoints: [normalPath(resolve(entrypoint))], + entryPoints: [normalEntrypoint], outfile, // otherwise there are problems with running azure cloud functions: // https://stackoverflow.com/questions/70332883/webpack-azure-storage-blob-node-fetch-abortsignal-issue diff --git a/libs/wingsdk/src/shared/legacy-sandbox.ts b/libs/wingsdk/src/shared/legacy-sandbox.ts new file mode 100644 index 00000000000..f7f3dcadaf8 --- /dev/null +++ b/libs/wingsdk/src/shared/legacy-sandbox.ts @@ -0,0 +1,143 @@ +import { mkdtemp, readFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import * as util from "node:util"; +import * as vm from "node:vm"; +import { createBundle } from "./bundling"; +import { SandboxOptions } from "./sandbox"; + +export class LegacySandbox { + private createBundlePromise: Promise; + private entrypoint: string; + private code: string | undefined; + private readonly options: SandboxOptions; + private readonly context: any = {}; + + constructor(entrypoint: string, options: SandboxOptions = {}) { + this.entrypoint = entrypoint; + this.options = options; + this.context = this.createContext(); + this.createBundlePromise = this.createBundle(); + } + + private createContext() { + const sandboxProcess = { + ...process, + + // override process.exit to throw an exception instead of exiting the process + exit: (exitCode: number) => { + throw new Error("process.exit() was called with exit code " + exitCode); + }, + + env: this.options.env, + }; + + const sandboxConsole: any = {}; + const levels = ["debug", "info", "log", "warn", "error"]; + for (const level of levels) { + sandboxConsole[level] = (...args: any[]) => { + const message = util.format(...args); + this.options.log?.(false, level, message); + // also log to stderr if DEBUG is set + if (process.env.DEBUG) { + console.error(message); + } + }; + } + + const ctx: any = {}; + + // create a copy of all the globals from our current context. + for (const k of Object.getOwnPropertyNames(global)) { + try { + ctx[k] = (global as any)[k]; + } catch { + // ignore unresolvable globals (see https://github.com/winglang/wing/pull/1923) + } + } + + // append the user's context + for (const [k, v] of Object.entries(this.options.context ?? {})) { + ctx[k] = v; + } + + const context = vm.createContext({ + ...ctx, + process: sandboxProcess, + console: sandboxConsole, + exports: {}, + require, // to support requiring node.js sdk modules (others will be bundled) + }); + + // emit an explicit error when trying to access `__dirname` and `__filename` because we cannot + // resolve these when bundling (this is true both for simulator and the cloud since we are + // bundling there as well). + const forbidGlobal = (name: string) => { + Object.defineProperty(context, name, { + get: () => { + throw new Error( + `${name} cannot be used within bundled cloud functions` + ); + }, + }); + }; + + forbidGlobal("__dirname"); + forbidGlobal("__filename"); + + return context; + } + + private async createBundle() { + // load bundle into context on first run + const workdir = await mkdtemp(path.join(tmpdir(), "wing-bundles-")); + const bundle = createBundle(this.entrypoint, [], workdir); + this.entrypoint = bundle.entrypointPath; + + this.code = await readFile(this.entrypoint, "utf-8"); + + if (process.env.DEBUG) { + const bundleSize = Buffer.byteLength(this.code, "utf-8"); + this.options.log?.(true, "log", `Bundled code (${bundleSize} bytes).`); + } + } + + public async call(fn: string, ...args: any[]): Promise { + // wait for the bundle to finish creation + await this.createBundlePromise; + + if (!this.code) { + throw new Error("Bundle not created yet - please report this as a bug"); + } + + // this will add stuff to the "exports" object within our context + vm.runInContext(this.code!, this.context, { + filename: this.entrypoint, + }); + + return new Promise(($resolve, $reject) => { + const cleanup = () => { + delete this.context.$resolve; + delete this.context.$reject; + }; + + this.context.$resolve = (value: any) => { + cleanup(); + $resolve(value); + }; + + this.context.$reject = (reason?: any) => { + cleanup(); + $reject(reason); + }; + + const code = `exports.${fn}(${args + .map((arg) => JSON.stringify(arg)) + .join(",")}).then($resolve).catch($reject);`; + vm.runInContext(code, this.context, { + filename: this.entrypoint, + timeout: this.options.timeout, + }); + }); + } +} diff --git a/libs/wingsdk/src/shared/sandbox.ts b/libs/wingsdk/src/shared/sandbox.ts index a726acbe7af..9d065bfc91a 100644 --- a/libs/wingsdk/src/shared/sandbox.ts +++ b/libs/wingsdk/src/shared/sandbox.ts @@ -1,9 +1,10 @@ -import { mkdtemp, readFile } from "fs/promises"; +import * as cp from "child_process"; +import { writeFileSync } from "fs"; +import { mkdtemp, readFile, stat } from "fs/promises"; import { tmpdir } from "os"; import * as path from "path"; -import * as util from "util"; -import * as vm from "vm"; import { createBundle } from "./bundling"; +import { processStream } from "./stream-processor"; export interface SandboxOptions { readonly env?: { [key: string]: string }; @@ -12,152 +13,196 @@ export interface SandboxOptions { readonly log?: (internal: boolean, level: string, message: string) => void; } +/** + * Shape of the messages sent to the child process. + */ +type ProcessRequest = { + fn: string; + args: any[]; +}; + +/** + * Shape of the messages returned by the child process. + */ +type ProcessResponse = + | { + type: "resolve"; + value: any; + } + | { + type: "reject"; + reason: Error; + }; + export class Sandbox { - private loaded = false; // "true" after first run (module is loaded into context) private createBundlePromise: Promise; private entrypoint: string; - private code: string | undefined; + private readonly exitingChildren: Promise[] = []; + private readonly timeouts: NodeJS.Timeout[] = []; private readonly options: SandboxOptions; - private readonly context: any = {}; constructor(entrypoint: string, options: SandboxOptions = {}) { this.entrypoint = entrypoint; this.options = options; - this.context = this.createContext(); this.createBundlePromise = this.createBundle(); } - private createContext() { - const sandboxProcess = { - ...process, - - // override process.exit to throw an exception instead of exiting the process - exit: (exitCode: number) => { - throw new Error("process.exit() was called with exit code " + exitCode); - }, - - env: this.options.env, - }; - - const sandboxConsole: any = {}; - const levels = ["debug", "info", "log", "warn", "error"]; - for (const level of levels) { - sandboxConsole[level] = (...args: any[]) => { - const message = util.format(...args); - this.options.log?.(false, level, message); - // also log to stderr if DEBUG is set - if (process.env.DEBUG) { - console.error(message); - } - }; + public async cleanup() { + await this.createBundlePromise; + for (const timeout of this.timeouts) { + clearTimeout(timeout); } - - const ctx: any = {}; - - // create a copy of all the globals from our current context. - for (const k of Object.getOwnPropertyNames(global)) { - try { - ctx[k] = (global as any)[k]; - } catch { - // ignore unresolvable globals (see https://github.com/winglang/wing/pull/1923) - } + // Make sure all child processes have exited before cleaning up the sandbox. + for (const exitingChild of this.exitingChildren) { + await exitingChild; } - - // append the user's context - for (const [k, v] of Object.entries(this.options.context ?? {})) { - ctx[k] = v; - } - - const context = vm.createContext({ - ...ctx, - process: sandboxProcess, - console: sandboxConsole, - exports: {}, - require, // to support requiring node.js sdk modules (others will be bundled) - }); - - // emit an explicit error when trying to access `__dirname` and `__filename` because we cannot - // resolve these when bundling (this is true both for simulator and the cloud since we are - // bundling there as well). - const forbidGlobal = (name: string) => { - Object.defineProperty(context, name, { - get: () => { - throw new Error( - `${name} cannot be used within bundled cloud functions` - ); - }, - }); - }; - - forbidGlobal("__dirname"); - forbidGlobal("__filename"); - - return context; } private async createBundle() { - // load bundle into context on first run const workdir = await mkdtemp(path.join(tmpdir(), "wing-bundles-")); - const bundle = createBundle(this.entrypoint, [], workdir); - this.entrypoint = bundle.entrypointPath; - this.code = await readFile(this.entrypoint, "utf-8"); + // wrap contents with a shim that handles the communication with the parent process + // we insert this shim before bundling to ensure source maps are generated correctly + let contents = (await readFile(this.entrypoint)).toString(); + + // log a warning if contents includes __dirname or __filename + if (contents.includes("__dirname") || contents.includes("__filename")) { + this.options.log?.( + false, + "warn", + `Warning: __dirname and __filename cannot be used within bundled cloud functions. There may be unexpected behavior.` + ); + } + + contents = ` +"use strict"; +${contents} +process.on("message", async (message) => { + const { fn, args } = message; + try { + const value = await exports[fn](...args); + process.send({ type: "resolve", value }); + } catch (err) { + process.send({ type: "reject", reason: err }); + } +}); +`; + const wrappedPath = this.entrypoint.replace(/\.js$/, ".sandbox.js"); + writeFileSync(wrappedPath, contents); // async fsPromises.writeFile "flush" option is not available in Node 20 + const bundle = createBundle(wrappedPath, [], workdir); + this.entrypoint = bundle.entrypointPath; if (process.env.DEBUG) { - const bundleSize = Buffer.byteLength(this.code, "utf-8"); - this.options.log?.(true, "log", `Bundled code (${bundleSize} bytes).`); + const bundleSize = (await stat(bundle.entrypointPath)).size; + this.debugLog(`Bundled code (${bundleSize} bytes).`); } } - private loadBundleOnce() { - if (this.loaded) { - return; - } + public async call(fn: string, ...args: any[]): Promise { + // wait for the bundle to finish creation + await this.createBundlePromise; - if (!this.code) { - throw new Error("Bundle not created yet - please report this as a bug"); - } + this.debugLog("Forking process to run bundled code."); - // this will add stuff to the "exports" object within our context - vm.runInContext(this.code!, this.context, { - filename: this.entrypoint, + // start a Node.js process that runs the bundled code + // note: unlike the fork(2) POSIX system call, child_process.fork() + // does not clone the current process + const child = cp.fork(this.entrypoint, [], { + env: this.options.env, + stdio: "pipe", + // execArgv: ["--enable-source-maps"], + // this option allows complex objects like Error to be sent from the child process to the parent + serialization: "advanced", }); - this.loaded = true; - } + const log = (message: string) => this.options.log?.(false, "log", message); + const logError = (message: string) => + this.options.log?.(false, "error", message); - public async call(fn: string, ...args: any[]): Promise { - // wait for the bundle to finish creation - await this.createBundlePromise; + // pipe stdout and stderr from the child process + if (child.stdout) { + processStream(child.stdout, log); + } + if (child.stderr) { + processStream(child.stderr, logError); + } - // load the bundle into context on the first run - // we don't do this earlier because bundled code may have side effects - // and we want to simulate that a function is "cold" on the first run - this.loadBundleOnce(); - - return new Promise(($resolve, $reject) => { - const cleanup = () => { - delete this.context.$resolve; - delete this.context.$reject; - }; - - this.context.$resolve = (value: any) => { - cleanup(); - $resolve(value); - }; - - this.context.$reject = (reason?: any) => { - cleanup(); - $reject(reason); - }; - - const code = `exports.${fn}(${args - .map((a) => JSON.stringify(a)) - .join(",")}).then($resolve).catch($reject);`; - vm.runInContext(code, this.context, { - filename: this.entrypoint, - timeout: this.options.timeout, + // Keep track of when the child process exits so that the simulator doesn't try + // to clean up the sandbox before the child process has exited. + // NOTE: If child processes are taking too long to exit (preventing simulator cleanups), + // we could add a mechanism to send SIGKILL to the child process after a certain amount of time. + let childExited: (value?: any) => void; + this.exitingChildren.push( + new Promise((resolve) => { + childExited = resolve; + }) + ); + + // Send the function name and arguments to the child process. + // When a message is received, kill the child process. + // Once the child process is killed (by the parent process or because the user code + // exited on its own), resolve or reject the promise. + return new Promise((resolve, reject) => { + child.send({ fn, args } as ProcessRequest); + + let result: any; + let status: "resolve" | "reject" | "pending" = "pending"; + + child.on("message", (message: ProcessResponse) => { + this.debugLog("Received a message, killing child process."); + child.kill(); + if (message.type === "resolve") { + result = message.value; + status = "resolve"; + } else if (message.type === "reject") { + result = message.reason; + status = "reject"; + } else { + result = `Parent received unexpected message from child process: ${message}`; + status = "reject"; + } + }); + + // "error" could be emitted for any number of reasons + // (e.g. the process couldn't be spawned or killed, or a message couldn't be sent). + // Since this is unexpected, we kill the process with SIGKILL to ensure it's dead, and reject the promise. + child.on("error", (error) => { + this.debugLog("Killing process after error."); + child.kill("SIGKILL"); + childExited(); + reject(`Unexpected error: ${error}`); }); + + child.on("exit", (code, _signal) => { + this.debugLog("Child processed stopped."); + childExited(); + if (status === "pending") { + // If the child process stopped without sending us back a message, reject the promise. + reject(new Error(`Process exited with code ${code}`)); + } else if (status === "resolve") { + resolve(result); + } else if (status === "reject") { + reject(result); + } + }); + + if (this.options.timeout) { + const timeout = setTimeout(() => { + this.debugLog("Killing process after timeout."); + child.kill(); + status = "reject"; + result = new Error( + `Function timed out (it was configured to only run for ${this.options.timeout}ms)` + ); + }, this.options.timeout); + this.timeouts.push(timeout); + } }); } + + private debugLog(message: string) { + if (process.env.DEBUG) { + this.options.log?.(true, "log", message); + } + } } diff --git a/libs/wingsdk/src/shared/stream-processor.ts b/libs/wingsdk/src/shared/stream-processor.ts new file mode 100644 index 00000000000..d65c34096d5 --- /dev/null +++ b/libs/wingsdk/src/shared/stream-processor.ts @@ -0,0 +1,47 @@ +import { Readable } from "stream"; +import { StringDecoder } from "string_decoder"; + +/** + * Processes a stream, invoking a callback function for each line of data. + * + * @param stream - The readable stream to process. + * @param callback - The function to invoke for each line of data. + */ +export function processStream( + stream: Readable, + callback: (message: string) => void +) { + let remainder = ""; + // StringDecoder to handle potentially incomplete multi-byte characters + const decoder = new StringDecoder(); + + stream.on("data", (data) => { + remainder = processStreamData(data, remainder, decoder, callback); + }); + + // Handle any remaining data when the streams are closed + stream.on("end", () => { + if (remainder) callback(remainder + decoder.end()); + }); + + stream.on("error", (error) => { + console.error(`Error occurred: ${error.message}`); + }); +} + +function processStreamData( + data: Buffer, + remainder: string, + decoder: StringDecoder, + log: (message: string) => void +): string { + let str = decoder.write(data); + let lines = (remainder + str).split("\n"); + + // Process all lines except the last one, which might be incomplete + for (let i = 0; i < lines.length - 1; i++) { + log(lines[i]); + } + + return lines.pop() ?? ""; // Return the last (potentially incomplete) line +} diff --git a/libs/wingsdk/src/simulator/client.ts b/libs/wingsdk/src/simulator/client.ts index dce5ba370bc..a46262b7f05 100644 --- a/libs/wingsdk/src/simulator/client.ts +++ b/libs/wingsdk/src/simulator/client.ts @@ -1,9 +1,40 @@ +import * as http from "http"; import { deserialize } from "./serialization"; import type { SimulatorServerRequest, SimulatorServerResponse, } from "./simulator"; +interface HttpRequestOptions extends http.RequestOptions { + body?: string; +} + +function makeHttpRequest(options: HttpRequestOptions): Promise { + return new Promise((resolve, reject) => { + const req = http.request(options, (res) => { + let data = ""; + + res.on("data", (chunk) => { + data += chunk; + }); + + res.on("end", () => { + resolve(data); + }); + }); + + req.on("error", (e) => { + reject(e); + }); + + if (options.body !== undefined) { + req.write(options.body); + } + + req.end(); + }); +} + export function makeSimulatorClient(url: string, handle: string) { let proxy: any; let hasThenMethod = true; // assume that the object has a "then" method until proven otherwise @@ -15,24 +46,19 @@ export function makeSimulatorClient(url: string, handle: string) { return async function (...args: any[]) { const body: SimulatorServerRequest = { handle, method, args }; - - // import undici dynamically to reduce the time it takes to load Wing SDK - // undici is used instead of the built-in fetch so that we can customize the - // headers timeouts to be 10 minutes instead of the default 5 seconds - const { fetch, Agent } = await import("undici"); - const resp = await fetch(url + "/v1/call", { + const parsedUrl = new URL(url); + const resp = await makeHttpRequest({ + hostname: parsedUrl.hostname, + port: parsedUrl.port, + path: "/v1/call", method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { + "Content-Type": "application/json", + }, body: JSON.stringify(body), - dispatcher: new Agent({ - keepAliveTimeout: 15 * 60 * 1000, - keepAliveMaxTimeout: 15 * 60 * 1000, - headersTimeout: 15 * 60 * 1000, - bodyTimeout: 15 * 60 * 1000, - }), }); - let parsed: SimulatorServerResponse = deserialize(await resp.text()); + let parsed: SimulatorServerResponse = deserialize(resp); if (parsed.error) { // objects with "then" methods are special-cased by the JS runtime diff --git a/libs/wingsdk/src/target-sim/function.inflight.ts b/libs/wingsdk/src/target-sim/function.inflight.ts index 049ff99a24b..bc5918b49e2 100644 --- a/libs/wingsdk/src/target-sim/function.inflight.ts +++ b/libs/wingsdk/src/target-sim/function.inflight.ts @@ -46,7 +46,7 @@ export class Function implements IFunctionClient, ISimulatorResourceInstance { } public async cleanup(): Promise { - return; + await this.sandbox.cleanup(); } public async save(): Promise {} diff --git a/libs/wingsdk/src/target-sim/redis.inflight.ts b/libs/wingsdk/src/target-sim/redis.inflight.ts index 4519198984a..fe06cc1b349 100644 --- a/libs/wingsdk/src/target-sim/redis.inflight.ts +++ b/libs/wingsdk/src/target-sim/redis.inflight.ts @@ -58,6 +58,7 @@ export class Redis public async cleanup(): Promise { this.isCleanedUp = true; // disconnect from the redis server + await this.connection?.quit(); this.connection?.disconnect(); // stop the redis container await runCommand("docker", ["rm", "-f", this.containerName]); diff --git a/libs/wingsdk/src/target-sim/service.inflight.ts b/libs/wingsdk/src/target-sim/service.inflight.ts index 3124ff0fe79..4795f6521b4 100644 --- a/libs/wingsdk/src/target-sim/service.inflight.ts +++ b/libs/wingsdk/src/target-sim/service.inflight.ts @@ -5,7 +5,7 @@ import { IServiceStopHandlerClient, SERVICE_FQN, } from "../cloud"; -import { Sandbox } from "../shared/sandbox"; +import { LegacySandbox } from "../shared/legacy-sandbox"; import { ISimulatorContext, ISimulatorResourceInstance } from "../simulator"; import { TraceType } from "../std"; @@ -13,7 +13,7 @@ export class Service implements IServiceClient, ISimulatorResourceInstance { private readonly context: ISimulatorContext; private readonly entrypoint: string; private readonly autoStart: boolean; - private readonly sandbox: Sandbox; + private readonly sandbox: LegacySandbox; private running: boolean = false; private onStop?: IServiceStopHandlerClient; @@ -21,7 +21,7 @@ export class Service implements IServiceClient, ISimulatorResourceInstance { this.context = context; this.entrypoint = resolve(context.simdir, props.sourceCodeFile); this.autoStart = props.autoStart; - this.sandbox = new Sandbox(this.entrypoint, { + this.sandbox = new LegacySandbox(this.entrypoint, { env: { ...props.environmentVariables, WING_SIMULATOR_URL: this.context.serverUrl, diff --git a/libs/wingsdk/src/util/enhanced-error.ts b/libs/wingsdk/src/util/enhanced-error.ts index 4591918a351..e3dbe7a37ca 100644 --- a/libs/wingsdk/src/util/enhanced-error.ts +++ b/libs/wingsdk/src/util/enhanced-error.ts @@ -78,7 +78,10 @@ export async function prettyPrintError( !normalPath(item.file).includes("/@winglang/sdk/") ) // special: remove the handler wrapper (See `cloud.Function` entrypoint for where this comes from) - .filter((item) => !normalPath(item.file).match(/\.wing\/handler_\w+\.js$/)) + .filter( + (item) => + !normalPath(item.file).match(/\.wing\/handler_\w+(\.sandbox)?\.js$/) + ) .withSourcesAsync(); let traceWithSources = st.items; diff --git a/libs/wingsdk/test/simulator/__snapshots__/simulator.test.ts.snap b/libs/wingsdk/test/simulator/__snapshots__/simulator.test.ts.snap index 4f0b3de17cc..e875ea04279 100644 --- a/libs/wingsdk/test/simulator/__snapshots__/simulator.test.ts.snap +++ b/libs/wingsdk/test/simulator/__snapshots__/simulator.test.ts.snap @@ -93,15 +93,11 @@ exports[`run single test > test failure 1`] = ` { "error": "Error: test failed at Handler.handle ([abs]) - at Object.exports.handler ([abs]) - at [abs] - at Script.runInContext (node:vm:) - at Object.runInContext (node:vm:) - at [src]/shared/sandbox.ts: - at new Promise () - at Sandbox.call ([src]/shared/sandbox.ts:) - at Object.withTrace ([src]/simulator/simulator.ts:) - at TestRunner.runTest ([src]/target-sim/test-runner.inflight.ts:)", + at exports.handler ([abs]) + at process. ([abs]) + at process.emit (node:events:) + at emit (node:internal/child_process:) + at process.processTicksAndRejections (node:internal/process/task_queues:)", "pass": false, "path": "root/test", "traces": [ diff --git a/libs/wingsdk/test/target-sim/__snapshots__/function.test.ts.snap b/libs/wingsdk/test/target-sim/__snapshots__/function.test.ts.snap index 2216d7f4e23..9a0a47fcef6 100644 --- a/libs/wingsdk/test/target-sim/__snapshots__/function.test.ts.snap +++ b/libs/wingsdk/test/target-sim/__snapshots__/function.test.ts.snap @@ -1,5 +1,16 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`__dirname and __filename cannot be used within inflight code 1`] = ` +[ + "@winglang/sdk.cloud.Function created.", + "@winglang/sdk.cloud.Function created.", + "Warning: __dirname and __filename cannot be used within bundled cloud functions. There may be unexpected behavior.", + "Warning: __dirname and __filename cannot be used within bundled cloud functions. There may be unexpected behavior.", + "Invoke (payload=undefined).", + "Invoke (payload=undefined).", +] +`; + exports[`create a function 1`] = ` { ".wing/my_function_c85c4e0e.js": ""use strict"; diff --git a/libs/wingsdk/test/target-sim/function.test.ts b/libs/wingsdk/test/target-sim/function.test.ts index edf3f474c5c..b32ac6a9e2e 100644 --- a/libs/wingsdk/test/target-sim/function.test.ts +++ b/libs/wingsdk/test/target-sim/function.test.ts @@ -189,13 +189,13 @@ test("invoke function with process.exit(1)", async () => { // WHEN const PAYLOAD = {}; await expect(client.invoke(JSON.stringify(PAYLOAD))).rejects.toThrow( - "process.exit() was called with exit code 1" + "Process exited with code 1" ); // THEN await s.stop(); expect(listMessages(s)).toMatchSnapshot(); expect(s.listTraces()[1].data.error).toMatchObject({ - message: "process.exit() was called with exit code 1", + message: "Process exited with code 1", }); expect(app.snapshot()).toMatchSnapshot(); }); @@ -243,11 +243,8 @@ test("__dirname and __filename cannot be used within inflight code", async () => const s = await app.startSimulator(); - await expect(dirnameInvoker(s)).rejects.toThrow( - "__dirname cannot be used within bundled cloud functions" - ); + await dirnameInvoker(s); + await filenameInvoker(s); - await expect(filenameInvoker(s)).rejects.toThrow( - "__filename cannot be used within bundled cloud functions" - ); + expect(listMessages(s)).toMatchSnapshot(); }); diff --git a/libs/wingsdk/test/target-sim/stream-processor.test.ts b/libs/wingsdk/test/target-sim/stream-processor.test.ts new file mode 100644 index 00000000000..de19ce606a4 --- /dev/null +++ b/libs/wingsdk/test/target-sim/stream-processor.test.ts @@ -0,0 +1,60 @@ +import { Readable } from "stream"; +import { describe, expect, it, vi } from "vitest"; +import { processStream } from "../../src/shared/stream-processor"; + +describe("processStream", () => { + it("should process data events correctly", () => + new Promise((done) => { + const mockData = Buffer.from("Hello\nWorld\n"); + const mockStream = new Readable(); + mockStream.push(mockData); + mockStream.push(null); // Indicates end of stream + + const mockCallback = vi.fn(); + + processStream(mockStream, mockCallback); + + setImmediate(() => { + expect(mockCallback).toHaveBeenCalledTimes(2); + expect(mockCallback).toHaveBeenNthCalledWith(1, "Hello"); + expect(mockCallback).toHaveBeenNthCalledWith(2, "World"); + done(undefined); + }); + })); + + it("should process lines split among multiple chunks", () => + new Promise((done) => { + const mockData1 = Buffer.from("Hello "); + const mockData2 = Buffer.from("world\n"); + const mockStream = new Readable(); + mockStream.push(mockData1); + mockStream.push(mockData2); + mockStream.push(null); // Indicates end of stream + + const mockCallback = vi.fn(); + + processStream(mockStream, mockCallback); + + setImmediate(() => { + expect(mockCallback).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenNthCalledWith(1, "Hello world"); + done(undefined); + }); + })); + + it("should handle error events", () => + new Promise((done) => { + const mockStream = new Readable(); + const mockCallback = vi.fn(); + const consoleSpy = vi.spyOn(console, "error"); + + processStream(mockStream, mockCallback); + + mockStream.emit("error", new Error("Test error")); + + setImmediate(() => { + expect(consoleSpy).toHaveBeenCalledWith("Error occurred: Test error"); + done(undefined); + }); + })); +}); diff --git a/libs/wingsdk/test/util.ts b/libs/wingsdk/test/util.ts index a214f895bc6..eb211185981 100644 --- a/libs/wingsdk/test/util.ts +++ b/libs/wingsdk/test/util.ts @@ -149,6 +149,9 @@ export function directorySnapshot(initialRoot: string) { break; case ".js": + if (f.endsWith(".sandbox.js")) { + continue; + } const code = readFileSync(abspath, "utf-8"); snapshot[key] = sanitizeCode(code); break; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8eeffc36dd3..31a1cf4a417 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1365,9 +1365,6 @@ importers: ulid: specifier: ^2.3.0 version: 2.3.0 - undici: - specifier: ^6.6.2 - version: 6.6.2 uuid: specifier: ^8.3.2 version: 8.3.2 @@ -14361,7 +14358,7 @@ packages: dependencies: semver: 7.5.4 shelljs: 0.8.5 - typescript: 5.5.0-dev.20240304 + typescript: 5.5.0-dev.20240305 dev: true /dset@3.1.2: @@ -22936,8 +22933,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - /typescript@5.5.0-dev.20240304: - resolution: {integrity: sha512-nu7H0CgsNe6Q9dJLsRRKB4E7KNBHuhJVJtC6vwC0GNnunJLGh8ZGtEZzcFx+feNnpAC/TWLs/H5EAea1wruBUg==} + /typescript@5.5.0-dev.20240305: + resolution: {integrity: sha512-ERkltjdukpttYHdXPg6cQAeuQrK1BIZ/QnNXLljBx5r90B+8lZ7pZ1qCL/XHHsUEi0lkVLpf9LPfwEYxie3qcA==} engines: {node: '>=14.17'} hasBin: true dev: true @@ -22988,13 +22985,6 @@ packages: '@fastify/busboy': 2.0.0 dev: false - /undici@6.6.2: - resolution: {integrity: sha512-vSqvUE5skSxQJ5sztTZ/CdeJb1Wq0Hf44hlYMciqHghvz+K88U0l7D6u1VsndoFgskDcnU+nG3gYmMzJVzd9Qg==} - engines: {node: '>=18.0'} - dependencies: - '@fastify/busboy': 2.0.0 - dev: false - /unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/function/invoke.test.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/function/invoke.test.w_test_sim.md index 0f478ad24ae..f46ae02feeb 100644 --- a/tools/hangar/__snapshots__/test_corpus/sdk_tests/function/invoke.test.w_test_sim.md +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/function/invoke.test.w_test_sim.md @@ -6,8 +6,8 @@ log preflight log preflight pass ┌ invoke.test.wsim » root/env0/test:invoke │ log inside test - └ log inside function -contains 2 lines + │ log inside function + └ contains 2 lines pass ┌ invoke.test.wsim » root/env1/test:invoke without inputs and outputs │ no event, no return! └ bang! diff --git a/tools/hangar/__snapshots__/test_corpus/valid/deep_equality.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/deep_equality.test.w_compile_tf-aws.md index 5f08a9977f6..d090b6259a2 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/deep_equality.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/deep_equality.test.w_compile_tf-aws.md @@ -27,7 +27,7 @@ module.exports = function({ $numA, $numB, $strA, $strB }) { ```js "use strict"; const $helpers = require("@winglang/sdk/lib/helpers"); -module.exports = function({ $arrayA, $arrayB, $arrayC }) { +module.exports = function({ $arrayA, $arrayB }) { class $Closure10 { constructor({ }) { const $obj = (...args) => this.handle(...args); @@ -35,8 +35,8 @@ module.exports = function({ $arrayA, $arrayB, $arrayC }) { return $obj; } async handle() { - $helpers.assert($helpers.neq($arrayA, $arrayC), "arrayA != arrayC"); - $helpers.assert((!$helpers.neq($arrayA, $arrayB)), "!(arrayA != arrayB)"); + $helpers.assert($helpers.eq($arrayA, $arrayA), "arrayA == arrayA"); + $helpers.assert($helpers.eq($arrayA, $arrayB), "arrayA == arrayB"); } } return $Closure10; @@ -48,7 +48,7 @@ module.exports = function({ $arrayA, $arrayB, $arrayC }) { ```js "use strict"; const $helpers = require("@winglang/sdk/lib/helpers"); -module.exports = function({ $cat1, $cat2 }) { +module.exports = function({ $arrayA, $arrayB, $arrayC }) { class $Closure11 { constructor({ }) { const $obj = (...args) => this.handle(...args); @@ -56,8 +56,8 @@ module.exports = function({ $cat1, $cat2 }) { return $obj; } async handle() { - $helpers.assert($helpers.eq($cat1, $cat1), "cat1 == cat1"); - $helpers.assert($helpers.eq($cat1, $cat2), "cat1 == cat2"); + $helpers.assert($helpers.neq($arrayA, $arrayC), "arrayA != arrayC"); + $helpers.assert((!$helpers.neq($arrayA, $arrayB)), "!(arrayA != arrayB)"); } } return $Closure11; @@ -69,7 +69,7 @@ module.exports = function({ $cat1, $cat2 }) { ```js "use strict"; const $helpers = require("@winglang/sdk/lib/helpers"); -module.exports = function({ $cat1, $cat2, $cat3 }) { +module.exports = function({ $cat1, $cat2 }) { class $Closure12 { constructor({ }) { const $obj = (...args) => this.handle(...args); @@ -77,8 +77,8 @@ module.exports = function({ $cat1, $cat2, $cat3 }) { return $obj; } async handle() { - $helpers.assert($helpers.neq($cat1, $cat3), "cat1 != cat3"); - $helpers.assert((!$helpers.neq($cat1, $cat2)), "!(cat1 != cat2)"); + $helpers.assert($helpers.eq($cat1, $cat1), "cat1 == cat1"); + $helpers.assert($helpers.eq($cat1, $cat2), "cat1 == cat2"); } } return $Closure12; @@ -86,6 +86,27 @@ module.exports = function({ $cat1, $cat2, $cat3 }) { //# sourceMappingURL=inflight.$Closure12-1.js.map ``` +## inflight.$Closure13-1.js +```js +"use strict"; +const $helpers = require("@winglang/sdk/lib/helpers"); +module.exports = function({ $cat1, $cat2, $cat3 }) { + class $Closure13 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async handle() { + $helpers.assert($helpers.neq($cat1, $cat3), "cat1 != cat3"); + $helpers.assert((!$helpers.neq($cat1, $cat2)), "!(cat1 != cat2)"); + } + } + return $Closure13; +} +//# sourceMappingURL=inflight.$Closure13-1.js.map +``` + ## inflight.$Closure2-1.js ```js "use strict"; @@ -153,7 +174,7 @@ module.exports = function({ $jsonA, $jsonB, $jsonC }) { ```js "use strict"; const $helpers = require("@winglang/sdk/lib/helpers"); -module.exports = function({ $setA, $setB }) { +module.exports = function({ $std_Json }) { class $Closure5 { constructor({ }) { const $obj = (...args) => this.handle(...args); @@ -161,8 +182,8 @@ module.exports = function({ $setA, $setB }) { return $obj; } async handle() { - $helpers.assert($helpers.eq($setA, $setA), "setA == setA"); - $helpers.assert($helpers.eq($setA, $setB), "setA == setB"); + const j = ({"hello": 123, "world": [1, 2, 3]}); + $helpers.assert($helpers.eq(Object.values(j), [123, [1, 2, 3]]), "Json.values(j) == [Json 123, Json [1, 2, 3]]"); } } return $Closure5; @@ -174,7 +195,7 @@ module.exports = function({ $setA, $setB }) { ```js "use strict"; const $helpers = require("@winglang/sdk/lib/helpers"); -module.exports = function({ $setA, $setB, $setC }) { +module.exports = function({ $setA, $setB }) { class $Closure6 { constructor({ }) { const $obj = (...args) => this.handle(...args); @@ -182,8 +203,8 @@ module.exports = function({ $setA, $setB, $setC }) { return $obj; } async handle() { - $helpers.assert($helpers.neq($setA, $setC), "setA != setC"); - $helpers.assert((!$helpers.neq($setA, $setB)), "!(setA != setB)"); + $helpers.assert($helpers.eq($setA, $setA), "setA == setA"); + $helpers.assert($helpers.eq($setA, $setB), "setA == setB"); } } return $Closure6; @@ -195,7 +216,7 @@ module.exports = function({ $setA, $setB, $setC }) { ```js "use strict"; const $helpers = require("@winglang/sdk/lib/helpers"); -module.exports = function({ $mapA, $mapB }) { +module.exports = function({ $setA, $setB, $setC }) { class $Closure7 { constructor({ }) { const $obj = (...args) => this.handle(...args); @@ -203,8 +224,8 @@ module.exports = function({ $mapA, $mapB }) { return $obj; } async handle() { - $helpers.assert($helpers.eq($mapA, $mapA), "mapA == mapA"); - $helpers.assert($helpers.eq($mapA, $mapB), "mapA == mapB"); + $helpers.assert($helpers.neq($setA, $setC), "setA != setC"); + $helpers.assert((!$helpers.neq($setA, $setB)), "!(setA != setB)"); } } return $Closure7; @@ -216,7 +237,7 @@ module.exports = function({ $mapA, $mapB }) { ```js "use strict"; const $helpers = require("@winglang/sdk/lib/helpers"); -module.exports = function({ $mapA, $mapB, $mapC }) { +module.exports = function({ $mapA, $mapB }) { class $Closure8 { constructor({ }) { const $obj = (...args) => this.handle(...args); @@ -224,8 +245,8 @@ module.exports = function({ $mapA, $mapB, $mapC }) { return $obj; } async handle() { - $helpers.assert($helpers.neq($mapA, $mapC), "mapA != mapC"); - $helpers.assert((!$helpers.neq($mapA, $mapB)), "!(mapA != mapB)"); + $helpers.assert($helpers.eq($mapA, $mapA), "mapA == mapA"); + $helpers.assert($helpers.eq($mapA, $mapB), "mapA == mapB"); } } return $Closure8; @@ -237,7 +258,7 @@ module.exports = function({ $mapA, $mapB, $mapC }) { ```js "use strict"; const $helpers = require("@winglang/sdk/lib/helpers"); -module.exports = function({ $arrayA, $arrayB }) { +module.exports = function({ $mapA, $mapB, $mapC }) { class $Closure9 { constructor({ }) { const $obj = (...args) => this.handle(...args); @@ -245,8 +266,8 @@ module.exports = function({ $arrayA, $arrayB }) { return $obj; } async handle() { - $helpers.assert($helpers.eq($arrayA, $arrayA), "arrayA == arrayA"); - $helpers.assert($helpers.eq($arrayA, $arrayB), "arrayA == arrayB"); + $helpers.assert($helpers.neq($mapA, $mapC), "mapA != mapC"); + $helpers.assert((!$helpers.neq($mapA, $mapB)), "!(mapA != mapB)"); } } return $Closure9; @@ -461,8 +482,7 @@ class $Root extends $stdlib.std.Resource { static _toInflightType() { return ` require("${$helpers.normalPath(__dirname)}/inflight.$Closure5-1.js")({ - $setA: ${$stdlib.core.liftObject(setA)}, - $setB: ${$stdlib.core.liftObject(setB)}, + $std_Json: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(std.Json, "@winglang/sdk/std", "Json"))}, }) `; } @@ -477,6 +497,40 @@ class $Root extends $stdlib.std.Resource { })()) `; } + get _liftMap() { + return ({ + "handle": [ + ], + "$inflight_init": [ + ], + }); + } + } + class $Closure6 extends $stdlib.std.AutoIdResource { + _id = $stdlib.core.closureId(); + constructor($scope, $id, ) { + super($scope, $id); + $helpers.nodeof(this).hidden = true; + } + static _toInflightType() { + return ` + require("${$helpers.normalPath(__dirname)}/inflight.$Closure6-1.js")({ + $setA: ${$stdlib.core.liftObject(setA)}, + $setB: ${$stdlib.core.liftObject(setB)}, + }) + `; + } + _toInflight() { + return ` + (await (async () => { + const $Closure6Client = ${$Closure6._toInflightType()}; + const client = new $Closure6Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `; + } get _liftMap() { return ({ "handle": [ @@ -490,7 +544,7 @@ class $Root extends $stdlib.std.Resource { }); } } - class $Closure6 extends $stdlib.std.AutoIdResource { + class $Closure7 extends $stdlib.std.AutoIdResource { _id = $stdlib.core.closureId(); constructor($scope, $id, ) { super($scope, $id); @@ -498,7 +552,7 @@ class $Root extends $stdlib.std.Resource { } static _toInflightType() { return ` - require("${$helpers.normalPath(__dirname)}/inflight.$Closure6-1.js")({ + require("${$helpers.normalPath(__dirname)}/inflight.$Closure7-1.js")({ $setA: ${$stdlib.core.liftObject(setA)}, $setB: ${$stdlib.core.liftObject(setB)}, $setC: ${$stdlib.core.liftObject(setC)}, @@ -508,8 +562,8 @@ class $Root extends $stdlib.std.Resource { _toInflight() { return ` (await (async () => { - const $Closure6Client = ${$Closure6._toInflightType()}; - const client = new $Closure6Client({ + const $Closure7Client = ${$Closure7._toInflightType()}; + const client = new $Closure7Client({ }); if (client.$inflight_init) { await client.$inflight_init(); } return client; @@ -531,7 +585,7 @@ class $Root extends $stdlib.std.Resource { }); } } - class $Closure7 extends $stdlib.std.AutoIdResource { + class $Closure8 extends $stdlib.std.AutoIdResource { _id = $stdlib.core.closureId(); constructor($scope, $id, ) { super($scope, $id); @@ -539,7 +593,7 @@ class $Root extends $stdlib.std.Resource { } static _toInflightType() { return ` - require("${$helpers.normalPath(__dirname)}/inflight.$Closure7-1.js")({ + require("${$helpers.normalPath(__dirname)}/inflight.$Closure8-1.js")({ $mapA: ${$stdlib.core.liftObject(mapA)}, $mapB: ${$stdlib.core.liftObject(mapB)}, }) @@ -548,8 +602,8 @@ class $Root extends $stdlib.std.Resource { _toInflight() { return ` (await (async () => { - const $Closure7Client = ${$Closure7._toInflightType()}; - const client = new $Closure7Client({ + const $Closure8Client = ${$Closure8._toInflightType()}; + const client = new $Closure8Client({ }); if (client.$inflight_init) { await client.$inflight_init(); } return client; @@ -569,7 +623,7 @@ class $Root extends $stdlib.std.Resource { }); } } - class $Closure8 extends $stdlib.std.AutoIdResource { + class $Closure9 extends $stdlib.std.AutoIdResource { _id = $stdlib.core.closureId(); constructor($scope, $id, ) { super($scope, $id); @@ -577,7 +631,7 @@ class $Root extends $stdlib.std.Resource { } static _toInflightType() { return ` - require("${$helpers.normalPath(__dirname)}/inflight.$Closure8-1.js")({ + require("${$helpers.normalPath(__dirname)}/inflight.$Closure9-1.js")({ $mapA: ${$stdlib.core.liftObject(mapA)}, $mapB: ${$stdlib.core.liftObject(mapB)}, $mapC: ${$stdlib.core.liftObject(mapC)}, @@ -587,8 +641,8 @@ class $Root extends $stdlib.std.Resource { _toInflight() { return ` (await (async () => { - const $Closure8Client = ${$Closure8._toInflightType()}; - const client = new $Closure8Client({ + const $Closure9Client = ${$Closure9._toInflightType()}; + const client = new $Closure9Client({ }); if (client.$inflight_init) { await client.$inflight_init(); } return client; @@ -610,7 +664,7 @@ class $Root extends $stdlib.std.Resource { }); } } - class $Closure9 extends $stdlib.std.AutoIdResource { + class $Closure10 extends $stdlib.std.AutoIdResource { _id = $stdlib.core.closureId(); constructor($scope, $id, ) { super($scope, $id); @@ -618,7 +672,7 @@ class $Root extends $stdlib.std.Resource { } static _toInflightType() { return ` - require("${$helpers.normalPath(__dirname)}/inflight.$Closure9-1.js")({ + require("${$helpers.normalPath(__dirname)}/inflight.$Closure10-1.js")({ $arrayA: ${$stdlib.core.liftObject(arrayA)}, $arrayB: ${$stdlib.core.liftObject(arrayB)}, }) @@ -627,8 +681,8 @@ class $Root extends $stdlib.std.Resource { _toInflight() { return ` (await (async () => { - const $Closure9Client = ${$Closure9._toInflightType()}; - const client = new $Closure9Client({ + const $Closure10Client = ${$Closure10._toInflightType()}; + const client = new $Closure10Client({ }); if (client.$inflight_init) { await client.$inflight_init(); } return client; @@ -648,7 +702,7 @@ class $Root extends $stdlib.std.Resource { }); } } - class $Closure10 extends $stdlib.std.AutoIdResource { + class $Closure11 extends $stdlib.std.AutoIdResource { _id = $stdlib.core.closureId(); constructor($scope, $id, ) { super($scope, $id); @@ -656,7 +710,7 @@ class $Root extends $stdlib.std.Resource { } static _toInflightType() { return ` - require("${$helpers.normalPath(__dirname)}/inflight.$Closure10-1.js")({ + require("${$helpers.normalPath(__dirname)}/inflight.$Closure11-1.js")({ $arrayA: ${$stdlib.core.liftObject(arrayA)}, $arrayB: ${$stdlib.core.liftObject(arrayB)}, $arrayC: ${$stdlib.core.liftObject(arrayC)}, @@ -666,8 +720,8 @@ class $Root extends $stdlib.std.Resource { _toInflight() { return ` (await (async () => { - const $Closure10Client = ${$Closure10._toInflightType()}; - const client = new $Closure10Client({ + const $Closure11Client = ${$Closure11._toInflightType()}; + const client = new $Closure11Client({ }); if (client.$inflight_init) { await client.$inflight_init(); } return client; @@ -689,7 +743,7 @@ class $Root extends $stdlib.std.Resource { }); } } - class $Closure11 extends $stdlib.std.AutoIdResource { + class $Closure12 extends $stdlib.std.AutoIdResource { _id = $stdlib.core.closureId(); constructor($scope, $id, ) { super($scope, $id); @@ -697,7 +751,7 @@ class $Root extends $stdlib.std.Resource { } static _toInflightType() { return ` - require("${$helpers.normalPath(__dirname)}/inflight.$Closure11-1.js")({ + require("${$helpers.normalPath(__dirname)}/inflight.$Closure12-1.js")({ $cat1: ${$stdlib.core.liftObject(cat1)}, $cat2: ${$stdlib.core.liftObject(cat2)}, }) @@ -706,8 +760,8 @@ class $Root extends $stdlib.std.Resource { _toInflight() { return ` (await (async () => { - const $Closure11Client = ${$Closure11._toInflightType()}; - const client = new $Closure11Client({ + const $Closure12Client = ${$Closure12._toInflightType()}; + const client = new $Closure12Client({ }); if (client.$inflight_init) { await client.$inflight_init(); } return client; @@ -727,7 +781,7 @@ class $Root extends $stdlib.std.Resource { }); } } - class $Closure12 extends $stdlib.std.AutoIdResource { + class $Closure13 extends $stdlib.std.AutoIdResource { _id = $stdlib.core.closureId(); constructor($scope, $id, ) { super($scope, $id); @@ -735,7 +789,7 @@ class $Root extends $stdlib.std.Resource { } static _toInflightType() { return ` - require("${$helpers.normalPath(__dirname)}/inflight.$Closure12-1.js")({ + require("${$helpers.normalPath(__dirname)}/inflight.$Closure13-1.js")({ $cat1: ${$stdlib.core.liftObject(cat1)}, $cat2: ${$stdlib.core.liftObject(cat2)}, $cat3: ${$stdlib.core.liftObject(cat3)}, @@ -745,8 +799,8 @@ class $Root extends $stdlib.std.Resource { _toInflight() { return ` (await (async () => { - const $Closure12Client = ${$Closure12._toInflightType()}; - const client = new $Closure12Client({ + const $Closure13Client = ${$Closure13._toInflightType()}; + const client = new $Closure13Client({ }); if (client.$inflight_init) { await client.$inflight_init(); } return client; @@ -781,26 +835,27 @@ class $Root extends $stdlib.std.Resource { const jsonC = [1, 2, 3]; this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Json with the same value", new $Closure3(this, "$Closure3")); this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Json with different values", new $Closure4(this, "$Closure4")); + this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Json.values equality", new $Closure5(this, "$Closure5")); const setA = new Set([1, 2, 3]); const setB = new Set([1, 2, 3]); const setC = new Set([4, 5, 6]); - this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Set types with the same value", new $Closure5(this, "$Closure5")); - this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Set types with different values", new $Closure6(this, "$Closure6")); + this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Set types with the same value", new $Closure6(this, "$Closure6")); + this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Set types with different values", new $Closure7(this, "$Closure7")); const mapA = ({["a"]: 1, ["b"]: 2}); const mapB = ({["a"]: 1, ["b"]: 2}); const mapC = ({["c"]: 10, ["b"]: 2}); - this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Map with the same value", new $Closure7(this, "$Closure7")); - this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Map with different values", new $Closure8(this, "$Closure8")); + this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Map with the same value", new $Closure8(this, "$Closure8")); + this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Map with different values", new $Closure9(this, "$Closure9")); const arrayA = [1, 2, 3]; const arrayB = [1, 2, 3]; const arrayC = [4, 5, 6]; - this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Array with the same value", new $Closure9(this, "$Closure9")); - this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Array with different values", new $Closure10(this, "$Closure10")); + this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Array with the same value", new $Closure10(this, "$Closure10")); + this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Array with different values", new $Closure11(this, "$Closure11")); const cat1 = ({"name": "Mittens", "age": 3}); const cat2 = ({"name": "Mittens", "age": 3}); const cat3 = ({"name": "Simba", "age": 5}); - this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Struct with the same value", new $Closure11(this, "$Closure11")); - this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Struct with different values", new $Closure12(this, "$Closure12")); + this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Struct with the same value", new $Closure12(this, "$Closure12")); + this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:Struct with different values", new $Closure13(this, "$Closure13")); } } const $PlatformManager = new $stdlib.platform.PlatformManager({platformPaths: $platforms}); diff --git a/tools/hangar/__snapshots__/test_corpus/valid/deep_equality.test.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/valid/deep_equality.test.w_test_sim.md index 687fd06e3d5..aac00f0fc34 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/deep_equality.test.w_test_sim.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/deep_equality.test.w_test_sim.md @@ -4,19 +4,20 @@ ```log pass ─ deep_equality.test.wsim » root/env0/test:Primitive types with the same value pass ─ deep_equality.test.wsim » root/env1/test:Primitive types with different values -pass ─ deep_equality.test.wsim » root/env10/test:Struct with the same value -pass ─ deep_equality.test.wsim » root/env11/test:Struct with different values +pass ─ deep_equality.test.wsim » root/env10/test:Array with different values +pass ─ deep_equality.test.wsim » root/env11/test:Struct with the same value +pass ─ deep_equality.test.wsim » root/env12/test:Struct with different values pass ─ deep_equality.test.wsim » root/env2/test:Json with the same value pass ─ deep_equality.test.wsim » root/env3/test:Json with different values -pass ─ deep_equality.test.wsim » root/env4/test:Set types with the same value -pass ─ deep_equality.test.wsim » root/env5/test:Set types with different values -pass ─ deep_equality.test.wsim » root/env6/test:Map with the same value -pass ─ deep_equality.test.wsim » root/env7/test:Map with different values -pass ─ deep_equality.test.wsim » root/env8/test:Array with the same value -pass ─ deep_equality.test.wsim » root/env9/test:Array with different values +pass ─ deep_equality.test.wsim » root/env4/test:Json.values equality +pass ─ deep_equality.test.wsim » root/env5/test:Set types with the same value +pass ─ deep_equality.test.wsim » root/env6/test:Set types with different values +pass ─ deep_equality.test.wsim » root/env7/test:Map with the same value +pass ─ deep_equality.test.wsim » root/env8/test:Map with different values +pass ─ deep_equality.test.wsim » root/env9/test:Array with the same value -Tests 12 passed (12) +Tests 13 passed (13) Test Files 1 passed (1) Duration ``` diff --git a/tools/hangar/__snapshots__/test_corpus/valid/inflight_handler_singleton.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/inflight_handler_singleton.test.w_compile_tf-aws.md index 34c3103b360..3e87c254ad7 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/inflight_handler_singleton.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/inflight_handler_singleton.test.w_compile_tf-aws.md @@ -46,7 +46,7 @@ module.exports = function({ $foo }) { ```js "use strict"; const $helpers = require("@winglang/sdk/lib/helpers"); -module.exports = function({ $expect_Util, $fn, $fn2, $sim }) { +module.exports = function({ $expect_Util, $fn, $fn2 }) { class $Closure3 { constructor({ }) { const $obj = (...args) => this.handle(...args); @@ -59,11 +59,7 @@ module.exports = function({ $expect_Util, $fn, $fn2, $sim }) { const z = (await $fn2.invoke("")); (await $expect_Util.equal(x, "100")); (await $expect_Util.equal(z, "100-fn2")); - if ($sim) { - (await $expect_Util.equal(y, "101")); - (await $expect_Util.equal(z, "100-fn2")); - console.log("client has been reused"); - } + $helpers.assert(($helpers.eq(y, "100") || $helpers.eq(y, "101")), "y == \"100\" || y == \"101\""); } } return $Closure3; @@ -427,7 +423,6 @@ class $Root extends $stdlib.std.Resource { $expect_Util: ${$stdlib.core.liftObject($stdlib.core.toLiftableModuleType(expect.Util, "@winglang/sdk/expect", "Util"))}, $fn: ${$stdlib.core.liftObject(fn)}, $fn2: ${$stdlib.core.liftObject(fn2)}, - $sim: ${$stdlib.core.liftObject(sim)}, }) `; } @@ -447,12 +442,10 @@ class $Root extends $stdlib.std.Resource { "handle": [ [fn, ["invoke"]], [fn2, ["invoke"]], - [sim, []], ], "$inflight_init": [ [fn, []], [fn2, []], - [sim, []], ], }); } diff --git a/tools/hangar/__snapshots__/test_corpus/valid/inflight_handler_singleton.test.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/valid/inflight_handler_singleton.test.w_test_sim.md index 94cccf3d190..8c3752eb01a 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/inflight_handler_singleton.test.w_test_sim.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/inflight_handler_singleton.test.w_test_sim.md @@ -2,8 +2,7 @@ ## stdout.log ```log -pass ┌ inflight_handler_singleton.test.wsim » root/env0/test:single instance of Foo - └ client has been reused +pass ─ inflight_handler_singleton.test.wsim » root/env0/test:single instance of Foo Tests 1 passed (1)