-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Fix emulator race condition in tests #4160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
## Problem Tests occasionally fail with: ``` Uncaught SyntaxError: Got [ERASED] expected EOF ``` Reported in espruino#4132 by @thyttan: tests fail initially but succeed on re-run. Example: https://github.com/espruino/BangleApps/actions/runs/21783161416/attempts/1 ## Cause The test runner sends commands without waiting for the emulator to finish processing. When one test ends and the next begins, `factoryReset()` can be called while the previous test's commands are still being processed, corrupting the input stream. ## Solution - Track emulator ready state (has it output a response?) - Add `waitForResponse()` to poll until command completes - Add `safeFactoryReset()` that ensures previous commands finished - Wait after each test step (`cmd`, `emit`, `load`, etc.) - Wait after app upload and reset in test setup ## Testing - All functional tests pass locally and in CI - Full test suite (all 4 apps): https://github.com/elijahr/BangleApps/actions/runs/21789313739 - No significant increase in test duration
When bin/runapptests.js, .github/workflows/nodejs.yml, or the core submodule changes, run all functional tests instead of only tests for changed apps. This ensures test infrastructure changes are properly validated against the full test suite.
bobrippling
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the rapid fix, this is great - I have some small questions, but nothing major about the design etc :)
| exit 0 | ||
| else | ||
| # Push to other branches: test only changed apps since master | ||
| CHANGED=$(git diff --name-only origin/master...HEAD) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep this comment in, as it's not clear what case the else handles
| CHANGED=$(git diff --name-only origin/master...HEAD) | |
| # Push to other branches: test only changed apps since master | |
| CHANGED=$(git diff --name-only origin/master...HEAD) |
| # Check if test infrastructure changed - if so, run all tests | ||
| RUN_ALL=false | ||
| for f in $TEST_INFRA_FILES; do | ||
| if echo "$CHANGED" | grep -q "^$f$"; then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's minor, but $f is treated as a regex when it isn't. We could:
| if echo "$CHANGED" | grep -q "^$f$"; then | |
| if echo "$CHANGED" | grep -qx "$f"; then |
Or we pull out the refspec above and one-shot this without needing to loop:
# e.g. refspec is origin/master...HEAD
if git diff --quiet "$refspec" -- $TEST_INFRA_FILES; then
...| fi | ||
| done | ||
|
|
||
| if [ "$RUN_ALL" = "true" ]; then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can shorten this:
| if [ "$RUN_ALL" = "true" ]; then | |
| if "$RUN_ALL"; then |
| waitForResponse(); | ||
| } | ||
| if (!emuReady) { | ||
| throw new Error( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose this is unreachable code as we can't leave waitForResponse() with emuReady === false
Perhaps we need a way of detecting if maxIterations has been reached?
| emu.tx(wrappingCode); | ||
| waitForResponse(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would using txAndWait() for this and other emu.tx(...); waitForResponse() be an approach to avoiding the bug sneaking in, in future?
Problem
Tests occasionally fail with:
Reported in #4132 by @thyttan: tests fail initially but succeed on re-run.
Example: https://github.com/espruino/BangleApps/actions/runs/21783161416/attempts/1
Cause (suspected)
The test runner sends commands without waiting for the emulator to finish processing. When one test ends and the next begins,
factoryReset()can be called while the previous test's commands are still being processed, corrupting the input stream.Solution
waitForResponse()to poll until command completessafeFactoryReset()that ensures previous commands finishedcmd,emit,load, etc.)Testing
bin/runapptests.js.github/workflows/nodejs.ymlandcoresubmodule).