|
| 1 | +# JS examples against the local runner |
| 2 | + |
| 3 | +The JS SDK repository ([aws/aws-durable-execution-sdk-js]) has about 125 |
| 4 | +example tests. Each test runs a durable function and checks its result and |
| 5 | +history. The JS repository runs them against real Lambda. This directory runs |
| 6 | +the same tests against the Python local runner in |
| 7 | +`packages/aws-durable-execution-sdk-python-testing`. Everything runs on your |
| 8 | +machine. No AWS account or credentials are needed. |
| 9 | + |
| 10 | +Run it after you change the testing package. The same script runs in CI |
| 11 | +(`.github/workflows/js-examples.yml`) on pull requests that touch the testing |
| 12 | +package. |
| 13 | + |
| 14 | +[aws/aws-durable-execution-sdk-js]: https://github.com/aws/aws-durable-execution-sdk-js |
| 15 | + |
| 16 | +## Quick start |
| 17 | + |
| 18 | +You need `git`, [hatch](https://hatch.pypa.io/), and Node.js 22 or newer with |
| 19 | +`npm`. Run from the repository root: |
| 20 | + |
| 21 | +```sh |
| 22 | +hatch run dev-testing:js-examples # every example, about 2.5 minutes |
| 23 | +hatch run dev-testing:js-examples invoke # only tests whose path matches "invoke" |
| 24 | +``` |
| 25 | + |
| 26 | +`dev-testing` is the hatch environment for the testing package. It has the SDK |
| 27 | +and the testing package from your checkout, installed in editable mode, so a |
| 28 | +run always tests your current source. |
| 29 | + |
| 30 | +To use a JS SDK checkout you already have, pass it with `--js-dir`, or set |
| 31 | +`JS_SDK_DIR` once in your shell: |
| 32 | + |
| 33 | +```sh |
| 34 | +export JS_SDK_DIR=~/github/aws-durable-execution-sdk-js |
| 35 | +hatch run dev-testing:js-examples invoke |
| 36 | +``` |
| 37 | + |
| 38 | +The harness uses that checkout as it is. It does not fetch, switch branches or |
| 39 | +clone. It runs `npm ci` and builds the example bundles when the checkout's |
| 40 | +source differs from the last build: a new commit, or an uncommitted change |
| 41 | +to a tracked or untracked file. |
| 42 | + |
| 43 | +Without `--js-dir`, the harness clones the JS SDK's `main` branch into |
| 44 | +`~/.cache/dex-js-examples/js-sdk` on the first run, and builds it. Each later |
| 45 | +run fetches `main` again, and rebuilds only when `main` has moved. |
| 46 | + |
| 47 | +The last lines of the output give the result: |
| 48 | + |
| 49 | +```text |
| 50 | +[js-examples] selected 125 test files: 121 passed (0 on retry), 0 failed, 4 have no cloud tests, 0 not run |
| 51 | +``` |
| 52 | + |
| 53 | +The script exits 0 when every selected test passes and 1 otherwise. It exits |
| 54 | +2 when setup fails, for example when the interpreter running it does not have |
| 55 | +the testing package from this checkout. |
| 56 | + |
| 57 | +## Common tasks |
| 58 | + |
| 59 | +Each command below follows `hatch run dev-testing:js-examples`. |
| 60 | + |
| 61 | +| Task | Options | |
| 62 | +| --- | --- | |
| 63 | +| Run tests whose path matches a regex | `step/ 'wait-for-callback/.*heartbeat'` | |
| 64 | +| Test against another JS SDK branch, tag or commit | `--js-ref v1.2.0` | |
| 65 | +| Use your own JS SDK checkout | `--js-dir ~/github/aws-durable-execution-sdk-js` | |
| 66 | +| Skip the JS build for that checkout | `--js-dir ~/github/aws-durable-execution-sdk-js --no-build` | |
| 67 | +| Force a JS rebuild | `--rebuild` | |
| 68 | +| See runner debug logs | `--log-level DEBUG hello-world` | |
| 69 | +| Pass options to jest | `invoke --jest-args='--verbose'` | |
| 70 | +| All options | `--help` | |
| 71 | + |
| 72 | +`--js-ref` works only with the cloned JS SDK, so it cannot be combined with |
| 73 | +`--js-dir` or `JS_SDK_DIR`. With `--js-dir`, the harness writes only build |
| 74 | +output into your checkout. Its lock and build stamp live in |
| 75 | +`~/.cache/dex-js-examples`. |
| 76 | + |
| 77 | +## When a test fails |
| 78 | + |
| 79 | +Each run writes its files to `~/.cache/dex-js-examples/out/`, or to the |
| 80 | +directory given with `--out`. The run empties that directory first. So it |
| 81 | +refuses a directory that has files in it and was not created by an earlier |
| 82 | +run: |
| 83 | + |
| 84 | +| File | Contents | |
| 85 | +| --- | --- | |
| 86 | +| `report.json` | Passed, failed, flaky and skipped test files | |
| 87 | +| `runner.log` | The Python local runner, which is the code under test | |
| 88 | +| `shim.log` | One line per handler invocation: request ID, function, duration, outcome | |
| 89 | +| `jest.json`, `retry1.json` | Raw jest results, for the first run and the retry | |
| 90 | +| `maps/` | The function maps generated from the examples' `template.yml` | |
| 91 | + |
| 92 | +To investigate one example, run it alone with debug logs: |
| 93 | + |
| 94 | +```sh |
| 95 | +hatch run dev-testing:js-examples --log-level DEBUG --retries 0 --jest-args='--verbose' step/interrupted-no-retry |
| 96 | +``` |
| 97 | + |
| 98 | +Add `--dump-dir /tmp/dump` to also save every history, state and checkpoint |
| 99 | +response the tests read. |
| 100 | + |
| 101 | +A test file that fails is run once more on fresh servers. If it then passes, |
| 102 | +the run succeeds and the file is reported as `FLAKY`. A few examples race real |
| 103 | +timers, for example parallel branches that must checkpoint within |
| 104 | +milliseconds of each other, and they can fail on a loaded machine. Use |
| 105 | +`--retries 0` to see every failure. |
| 106 | + |
| 107 | +## How it works |
| 108 | + |
| 109 | +The Python local runner is the backend, in place of the Lambda service. Three |
| 110 | +helpers run around it, on the loopback, on free ports chosen for each run: |
| 111 | + |
| 112 | +```text |
| 113 | +jest ----> invoke_proxy.py ----> local runner ----> lambda-shim.cjs |
| 114 | +(test (starts executions) (the code under (runs the JS handlers, |
| 115 | + driver) test) like the Lambda runtime) |
| 116 | +``` |
| 117 | + |
| 118 | +1. **jest** runs the JS example tests unchanged, in the mode the JS repository |
| 119 | + uses against real Lambda (`NODE_ENV=integration`). Each test is a client: |
| 120 | + it starts an execution, then reads history and state, and some tests send |
| 121 | + callbacks. `LAMBDA_ENDPOINT` points the tests' Lambda client at the proxy. |
| 122 | +2. **invoke_proxy.py** exists because the tests start an execution with Lambda |
| 123 | + `Invoke`, and the local runner has no `Invoke` route. The proxy turns that |
| 124 | + call into the runner's `POST /start-durable-execution`, and returns the |
| 125 | + execution ARN in the header the JS SDK reads. It forwards every other |
| 126 | + request to the runner unchanged. |
| 127 | +3. **The local runner** runs the executions. To run a handler, it calls |
| 128 | + Lambda `Invoke` on its Lambda endpoint, which is the shim. It resolves |
| 129 | + chained-invoke targets from the function configs `run.py` gives it. |
| 130 | +4. **lambda-shim.cjs** hosts the built example bundles, as the Lambda runtime |
| 131 | + does in AWS. It runs each function on worker threads. A worker handles one |
| 132 | + invocation at a time and is reused afterwards, like a warm Lambda sandbox. |
| 133 | + When an invocation runs past the function's `Timeout`, the shim returns |
| 134 | + Lambda's `Sandbox.Timedout` error and terminates the worker. |
| 135 | + `step/interrupted-no-retry` depends on this. Inside the handler, the durable |
| 136 | + SDK checkpoints straight to the runner, because the shim sets |
| 137 | + `AWS_ENDPOINT_URL_LAMBDA` to the runner's address. |
| 138 | + |
| 139 | +`run.py` starts the runner with `WebRunner` from the testing package, in a |
| 140 | +child process, so each server session gets a runner with no state left from |
| 141 | +an earlier one. A run has one session, plus one per retry. The proxy runs on a |
| 142 | +thread in `run.py`, and the shim and jest run as separate Node processes. |
| 143 | +Before the run, `run.py` reads the examples' `template.yml` and writes three maps: test file to |
| 144 | +function name (for jest), function name to bundle, timeout and environment |
| 145 | +(for the shim), and function name to `DurableConfig` (for the runner). |
| 146 | + |
| 147 | +The runner emulates region `us-west-2` and account `123456789012`. The proxy |
| 148 | +and the shim use the same values. If they differ, the runner rejects chained |
| 149 | +invokes whose target ARN names another region or account. |
| 150 | + |
| 151 | +## Which tests run |
| 152 | + |
| 153 | +Every `*.test.ts` under the examples' `src/examples` is selected, except for |
| 154 | +three groups: |
| 155 | + |
| 156 | +- `otel/` examples. They export spans to an OpenTelemetry collector, which the |
| 157 | + harness does not run. |
| 158 | +- Examples marked `localOnly` in the JS catalog. `template.yml` omits them, |
| 159 | + and the JS repository's integration run skips them too. The output reports |
| 160 | + how many were skipped. |
| 161 | +- Tests without "cloud" in their name. The integration jest config runs only |
| 162 | + those. A file with no such test is reported as "no cloud tests". |
| 163 | + |
| 164 | +## Known failures |
| 165 | + |
| 166 | +`known-failures.txt` lists test files that are expected to fail, one per line, |
| 167 | +with the reason after `#`. It is empty today. A listed file that fails does |
| 168 | +not fail the run. A listed file that passes does fail the run, so that the |
| 169 | +list stays accurate. |
| 170 | + |
| 171 | +## A failure caused by a JS change |
| 172 | + |
| 173 | +Every run tests against the JS SDK's `main` branch. So a run can fail because |
| 174 | +of a new JS commit, with no Python change. To check, run the failing test |
| 175 | +against an older JS commit: |
| 176 | + |
| 177 | +```sh |
| 178 | +hatch run dev-testing:js-examples --js-ref <older JS commit> <failing test> |
| 179 | +``` |
| 180 | + |
| 181 | +If it passes there, the new JS commit exposed something the runner does not |
| 182 | +handle yet. |
| 183 | + |
| 184 | +## Harness tests |
| 185 | + |
| 186 | +`.github/scripts/tests/test_js_examples.py` tests the template parsing, test |
| 187 | +selection and result handling in `run.py`. It needs no JS SDK: |
| 188 | + |
| 189 | +```sh |
| 190 | +hatch run dev-testing:python -m pytest .github/scripts/tests/test_js_examples.py |
| 191 | +``` |
0 commit comments