Skip to content

Commit

Permalink
Run native tests in Docker in a GH Actions workflow.
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamn committed Jan 23, 2023
1 parent d1c42e4 commit 536cfb8
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 1 deletion.
15 changes: 15 additions & 0 deletions .github/workflows/docker-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Docker Image CI

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run AsyncContext tests with benjamn/deno:async-context Docker image
run: docker/tests/docker-run.sh benjamn/deno:async-context
2 changes: 1 addition & 1 deletion .mocharc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"require": "@esbuild-kit/cjs-loader",
"loader": "@esbuild-kit/esm-loader",
"spec": ["tests/**"],
"spec": ["tests/*.test.ts"],
"extensions": ["ts"],
"watch-files": ["src"],
"node-option": ["no-warnings"]
Expand Down
11 changes: 11 additions & 0 deletions tests/docker-native/docker-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "$0")"

DENO_IMAGE=${1:-benjamn/deno:async-context}
echo "Running tests using $DENO_IMAGE"

# Try changing :async-context to :unmodified to observe the tests fail, most
# immediately because AsyncContext is not defined globally.
exec docker run -v $(pwd):/deno --rm $DENO_IMAGE test --allow-read --trace-ops *.ts
254 changes: 254 additions & 0 deletions tests/docker-native/polyfillable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
// These tests were directly adapted from ../async-context.test.ts, with
// different imports and no Promise.prototype.then wrapping. These tests are
// "polyfillable" in the sense that they could pass using a polyfill
// implementation like the one from PR #14, but no polyfill is needed when the
// tests are executed using benjamn/deno:async-context.

import * as assert from "https://deno.land/[email protected]/node/assert.ts";
import { describe, it } from "https://deno.land/[email protected]/testing/bdd.ts";

type Value = { id: number };
function sleep(ms: number): Promise<void> {
return new Promise((r) => setTimeout(r, ms));
}

describe("sync", () => {
describe("run and get", () => {
it("has initial undefined state", () => {
const ctx = new AsyncContext<Value>();

const actual = ctx.get();

assert.equal(actual, undefined);
});

it("return value", () => {
const ctx = new AsyncContext<Value>();
const expected = { id: 1 };

const actual = ctx.run({ id: 2 }, () => expected);

assert.equal(actual, expected);
});

it("get returns current context value", () => {
const ctx = new AsyncContext<Value>();
const expected = { id: 1 };

const actual = ctx.run(expected, () => ctx.get());

assert.equal(actual, expected);
});

it("get within nesting contexts", () => {
const ctx = new AsyncContext<Value>();
const first = { id: 1 };
const second = { id: 2 };

const actual = ctx.run(first, () => {
return [ctx.get(), ctx.run(second, () => ctx.get()), ctx.get()];
});

assert.deepStrictEqual(actual, [first, second, first]);
});

it("get within nesting different contexts", () => {
const a = new AsyncContext<Value>();
const b = new AsyncContext<Value>();
const first = { id: 1 };
const second = { id: 2 };

const actual = a.run(first, () => {
return [
a.get(),
b.get(),
...b.run(second, () => [a.get(), b.get()]),
a.get(),
b.get(),
];
});

assert.deepStrictEqual(actual, [
first,
undefined,
first,
second,
first,
undefined,
]);
});
});

describe("wrap", () => {
it("stores initial undefined state", () => {
const ctx = new AsyncContext<Value>();
const wrapped = AsyncContext.wrap(() => ctx.get());

const actual = ctx.run({ id: 1 }, () => wrapped());

assert.equal(actual, undefined);
});

it("stores current state", () => {
const ctx = new AsyncContext<Value>();
const expected = { id: 1 };

const wrapped = ctx.run(expected, () => {
return AsyncContext.wrap(() => ctx.get());
});

const actual = wrapped();

assert.equal(actual, expected);
});

it("wrap within nesting contexts", () => {
const ctx = new AsyncContext<Value>();
const first = { id: 1 };
const second = { id: 2 };

const actual = ctx.run(first, () => {
const wrapped = ctx.run(second, () => {
return AsyncContext.wrap(() => ctx.get());
});
return [ctx.get(), wrapped(), ctx.get()];
});

assert.deepStrictEqual(actual, [first, second, first]);
});

it("wrap out of order", () => {
const ctx = new AsyncContext<Value>();
const first = { id: 1 };
const second = { id: 2 };

const firstWrap = ctx.run(first, () => {
return AsyncContext.wrap(() => ctx.get());
});
const secondWrap = ctx.run(second, () => {
return AsyncContext.wrap(() => ctx.get());
});
const actual = [firstWrap(), secondWrap(), firstWrap(), secondWrap()];

assert.deepStrictEqual(actual, [first, second, first, second]);
});
});
});

describe("async via promises", () => {
// beforeEach(() => {
// Promise.prototype.then = then;
// });
// afterEach(() => {
// Promise.prototype.then = nativeThen;
// });

describe("run and get", () => {
it("get returns current context value", async () => {
const ctx = new AsyncContext<Value>();
const expected = { id: 1 };

const actual = await ctx.run(expected, () => {
return Promise.resolve().then(() => ctx.get());
});

assert.equal(actual, expected);
});

it("get within nesting contexts", async () => {
const ctx = new AsyncContext<Value>();
const first = { id: 1 };
const second = { id: 2 };

const actual = await ctx.run(first, () => {
return Promise.resolve<Value[]>([])
.then((temp) => {
temp.push(ctx.get());
return temp;
})
.then((temp) => {
return ctx.run(second, () => {
return Promise.resolve().then(() => {
temp.push(ctx.get());
return temp;
});
});
})
.then((temp) => {
temp.push(ctx.get());
return temp;
});
});

assert.deepStrictEqual(actual, [first, second, first]);
});

it("get within nesting different contexts", async () => {
const a = new AsyncContext<Value>();
const b = new AsyncContext<Value>();
const first = { id: 1 };
const second = { id: 2 };

const actual = await a.run(first, () => {
return Promise.resolve<Value[]>([])
.then((temp) => {
temp.push(a.get(), b.get());
return temp;
})
.then((temp) => {
return b.run(second, () => {
return Promise.resolve().then(() => {
temp.push(a.get(), b.get());
return temp;
});
});
})
.then((temp) => {
temp.push(a.get(), b.get());
return temp;
});
});

assert.deepStrictEqual(actual, [
first,
undefined,
first,
second,
first,
undefined,
]);
});

it("get out of order", async () => {
const ctx = new AsyncContext<Value>();
const first = { id: 1 };
const second = { id: 2 };

const firstRun = ctx.run(first, () => {
return [
sleep(10).then(() => ctx.get()),
sleep(20).then(() => ctx.get()),
sleep(30).then(() => ctx.get()),
];
});
const secondRun = ctx.run(second, () => {
return [
sleep(25).then(() => ctx.get()),
sleep(15).then(() => ctx.get()),
sleep(5).then(() => ctx.get()),
];
});

const actual = await Promise.all(firstRun.concat(secondRun));

assert.deepStrictEqual(actual, [
first,
first,
first,
second,
second,
second,
]);
});
});
});
28 changes: 28 additions & 0 deletions tests/docker-native/unpolyfillable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as assert from "https://deno.land/[email protected]/node/assert.ts";
import { describe, it } from "https://deno.land/[email protected]/testing/bdd.ts";

describe("async via native async/await", () => {
it("works after awaited setTimeout result", async () => {
const ctx = new AsyncContext<number>();
const ctxRunResult = await ctx.run(1234, async () => {
assert.strictEqual(ctx.get(), 1234);
const setTimeoutResult = await ctx.run(
2345,
() => new Promise(resolve => {
setTimeout(() => resolve(ctx.get()), 20);
}),
);
assert.strictEqual(setTimeoutResult, 2345);
assert.strictEqual(ctx.get(), 1234);
return "final result";
}).then(result => {
assert.strictEqual(result, "final result");
// The code that generated the Promise has access to the 1234 value
// provided to ctx.run above, but consumers of the Promise do not
// automatically inherit it.
assert.strictEqual(ctx.get(), void 0);
return "ctx.run result 👋";
});
assert.strictEqual(ctxRunResult, "ctx.run result 👋");
});
});

0 comments on commit 536cfb8

Please sign in to comment.