Closed gkiely closed 10 months ago
re: JSDOM, the next step here is to implement a vm
polyfill using ShadowRealm
The vm
polyfill can be done in JS.
Another option is to submit a PR to JSDOM that leverages ShadowRealm instead of the vm
polyfill.
We'll be tracking support for the vm
module here: #401
@Electroid Can we keep this open to confirm that react-testing-library works once the vm module work is completed? I am happy to do the testing.
Tested this with bun 0.5.8 and looks like the blocker isn't @testing-library
itself, but rather we need support for some underlying DOM implementation. Running @testing-library
tests directly gives you:
326 | } = _temp === void 0 ? {} : _temp;
327 |
328 | if (!baseElement) {
329 | // default to document.body instead of documentElement to avoid output of potentially-large
330 | // head elements (such as JSS style blocks) in debug output
331 | baseElement = document.body;
^
ReferenceError: Can't find variable: document
at render (/project/node_modules/@testing-library/react/dist/@testing-library/react.esm.js:331:18)
at /project/myfile.test.tsx:15:22
I think bun just needs a way to load some kind of DOM library like happy-dom
or jsdom
. At the moment they don't work.
Trying to import happy-dom
fails with:
25 | Object.defineProperty(exports, "__esModule", { value: true });
26 | const NodeFetch = __importStar(require("node-fetch"));
27 | /**
28 | * Fetch headers.
29 | */
30 | class Headers extends NodeFetch.Headers {
^
TypeError: The superclass is not a constructor.
at /project/node_modules/happy-dom/lib/fetch/Headers.js:30:0
at /project/node_modules/happy-dom/lib/window/Window.js:108:18
at /project/node_modules/happy-dom/lib/window/GlobalWindow.js:6:17
at /project/node_modules/happy-dom/lib/index.js:8:23
Trying to import jsdom
fails with:
error: Cannot find package "vm" from "/project/node_modules/jsdom/lib/api.js"
I'll be testing this out tmrw unless @EvHaus beats me to it.
It uses kind of an insane hack where the context object's prototype becomes a special global object proxy (like window in browsers)
Lmk if that breaks stuff
It works!
I am seeing an issue using screen.getByText
that I am investigating.
oh interesitng
@gkiely Can you share your code? I'm having a hard time figuring out how to get JSDom to be loaded in bun before any tests run.
@EvHaus can you try bun test --preload 'script-or-package-name'
I'm using a variation on this, will update shortly: https://twitter.com/jarredsumner/status/1659391501871513607/photo/1
JSDom is not working for me, using happy-dom.
Ok posted an update with a working version. Preload fixed the above error. https://github.com/gkiely/bun-rtl
bun test --preload './src/preload.js' App.test.jsx
I'll have a fix for the issue with node-fetch shortly. TLDR Is we polyfill node-fetch since in Bun it should just use the globalThis.fetch implementation, but our polyfill was only exporting fetch and not all the other things node-fetch exports.
@EvHaus I tried to get your commit running and there is definitely a bug with --preload
. Will investigate that.
In the meantime, if you make helpers/happydom.ts look more like this:
import { GlobalRegistrator } from "@happy-dom/global-registrator";
GlobalRegistrator.register();
and add a bunfig.toml
that looks like this:
[test]
preload = ["./helpers/happydom.ts"]
It gets a lot closer to working.
There are 218 test failures, but 20 pass and it console.log's a LOT of text
Nice! The main thing that was missing is a way for bun:test
to have a global afterEach
step because react-testing-library
requires that you run their cleanup
function after each test.
For now I manually added that to each test file and that got me a lot closer. All tests pass when run individually, but running them all causes bun to crash mid-way through with...
$ TZ=UTC bun test
bun test v0.6.2 (8d90d795)
StatusBadge.test.tsx:
✓ <StatusBadge /> > should render without failure [9.08ms]
Pagination.test.tsx:
✓ <Pagination /> > should render a button for each page [17.88ms]
✓ <Pagination /> > should render two ellipses if current page is far from both ends [5.68ms]
✓ <Pagination /> > should render an ellipsis if current page is far the end [4.57ms]
✓ <Pagination /> > should render an ellipsis if current page is far the start [2.86ms]
✓ <Pagination /> > should navigate to the page when clicking on that specific page [17.10ms]
error: script "test" exited with code 6 (SIGABRT)
...before all the tests have finshed.
You can try with latest commit in https://github.com/EvHaus/test-runner-benchmarks/commit/3d1305172843b5819fd990f8b1dfe9c4a57cde55
We will add support for registering hooks via bun:test in preload
Nice! The main thing that was missing is a way for
bun:test
to have a globalafterEach
step becausereact-testing-library
requires that you run theircleanup
function after each test.For now I manually added that to each test file and that got me a lot closer. All tests pass when run individually, but running them all causes bun to crash mid-way through with...
$ TZ=UTC bun test bun test v0.6.2 (8d90d795) StatusBadge.test.tsx: ✓ <StatusBadge /> > should render without failure [9.08ms] Pagination.test.tsx: ✓ <Pagination /> > should render a button for each page [17.88ms] ✓ <Pagination /> > should render two ellipses if current page is far from both ends [5.68ms] ✓ <Pagination /> > should render an ellipsis if current page is far the end [4.57ms] ✓ <Pagination /> > should render an ellipsis if current page is far the start [2.86ms] ✓ <Pagination /> > should navigate to the page when clicking on that specific page [17.10ms] error: script "test" exited with code 6 (SIGABRT)
...before all the tests have finshed.
You can try with latest commit in EvHaus/test-runner-benchmarks@3d13051
I can repro the crash.
Call stack:
WTFCrashWithInfo(int, char const*, char const*, int) (/Users/jarred/Code/bun/node_modules/bun-webkit-macos-arm64/include/wtf/Assertions.h:758)
JSC::LocalAllocator::allocateSlowCase(JSC::Heap&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode) (@JSC::LocalAllocator::allocateSlowCase(JSC::Heap&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode):67)
JSC::LocalAllocator::allocate(JSC::Heap&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode)::'lambda'()::operator()() const (/Users/jarred/Code/bun/node_modules/bun-webkit-macos-arm64/include/JavaScriptCore/LocalAllocatorInlines.h:41)
JSC::HeapCell* JSC::FreeList::allocateWithCellSize<JSC::LocalAllocator::allocate(JSC::Heap&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode)::'lambda'()>(JSC::LocalAllocator::allocate(JSC::Heap&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode)::'lambda'() const&, unsigned long) (/Users/jarred/Code/bun/node_modules/bun-webkit-macos-arm64/include/JavaScriptCore/FreeListInlines.h:44)
JSC::LocalAllocator::allocate(JSC::Heap&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode) (/Users/jarred/Code/bun/node_modules/bun-webkit-macos-arm64/include/JavaScriptCore/LocalAllocatorInlines.h:38)
JSC::GCClient::IsoSubspace::allocate(JSC::VM&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode) (/Users/jarred/Code/bun/node_modules/bun-webkit-macos-arm64/include/JavaScriptCore/IsoSubspaceInlines.h:34)
void* JSC::tryAllocateCellHelper<JSC::JSString, (JSC::AllocationFailureMode)0>(JSC::VM&, unsigned long, JSC::GCDeferralContext*) (/Users/jarred/Code/bun/node_modules/bun-webkit-macos-arm64/include/JavaScriptCore/JSCellInlines.h:190)
void* JSC::allocateCell<JSC::JSString>(JSC::VM&, unsigned long) (/Users/jarred/Code/bun/node_modules/bun-webkit-macos-arm64/include/JavaScriptCore/JSCellInlines.h:206)
JSC::JSString::create(JSC::VM&, WTF::Ref<WTF::StringImpl, WTF::RawPtrTraits<WTF::StringImpl>>&&) (/Users/jarred/Code/bun/node_modules/bun-webkit-macos-arm64/include/JavaScriptCore/JSString.h:187)
JSC::JSFunction::originalName(JSC::JSGlobalObject*) (@JSC::JSFunction::originalName(JSC::JSGlobalObject*):253)
JSC::JSBoundFunction::nameSlow(JSC::VM&) (@JSC::JSBoundFunction::nameSlow(JSC::VM&):47)
JSC::JSFunction::name(JSC::VM&) (@JSC::JSFunction::name(JSC::VM&):58)
JSC::getCalculatedDisplayName(JSC::VM&, JSC::JSObject*) (@JSC::getCalculatedDisplayName(JSC::VM&, JSC::JSObject*):65)
JSC::StackFrame::functionName(JSC::VM&) const (@JSC::StackFrame::functionName(JSC::VM&) const:43)
JSC::StackFrame::toString(JSC::VM&) const (@JSC::StackFrame::toString(JSC::VM&) const:13)
JSC::Interpreter::stackTraceAsString(JSC::VM&, WTF::Vector<JSC::StackFrame, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&) (@JSC::Interpreter::stackTraceAsString(JSC::VM&, WTF::Vector<JSC::StackFrame, 0ul, WTF::CrashOnOverflow, 16ul, WTF::FastMalloc> const&):35)
JSC::ErrorInstance::computeErrorInfo(JSC::VM&) (@JSC::ErrorInstance::computeErrorInfo(JSC::VM&):23)
JSC::Heap::finalizeUnconditionalFinalizers() (@JSC::Heap::finalizeUnconditionalFinalizers():306)
JSC::Heap::runEndPhase(JSC::GCConductor) (@JSC::Heap::runEndPhase(JSC::GCConductor):355)
JSC::Heap::runCurrentPhase(JSC::GCConductor, JSC::CurrentThreadState*) (@JSC::Heap::runCurrentPhase(JSC::GCConductor, JSC::CurrentThreadState*):97)
WTF::ScopedLambdaFunctor<void (JSC::CurrentThreadState&), JSC::Heap::collectInMutatorThread()::$_25>::implFunction(void*, JSC::CurrentThreadState&) (@WTF::ScopedLambdaFunctor<void (JSC::CurrentThreadState&), JSC::Heap::collectInMutatorThread()::$_25>::implFunction(void*, JSC::CurrentThreadState&):12)
JSC::callWithCurrentThreadState(WTF::ScopedLambda<void (JSC::CurrentThreadState&)> const&) (@JSC::callWithCurrentThreadState(WTF::ScopedLambda<void (JSC::CurrentThreadState&)> const&):45)
JSC::Heap::collectInMutatorThread() (@JSC::Heap::collectInMutatorThread():27)
JSC::Heap::waitForCollection(unsigned long long) (@JSC::Heap::waitForCollection(unsigned long long):42)
JSC::Heap::collectSync(JSC::GCRequest) (@JSC::Heap::collectSync(JSC::GCRequest):30)
::JSC__VM__runGC(JSC__VM *, bool) (/Users/jarred/Code/bun/src/bun.js/bindings/bindings.cpp:3453)
src.bun.js.bindings.shimmer.Shimmer("JSC","VM",src.bun.js.bindings.bindings.VM).cppFn (/Users/jarred/Code/bun/src/bun.js/bindings/shimmer.zig:186)
src.bun.js.bindings.bindings.VM.runGC (/Users/jarred/Code/bun/src/bun.js/bindings/bindings.zig:4683)
src.cli.test_command.TestCommand.run (/Users/jarred/Code/bun/src/cli/test_command.zig:720)
src.string_types.PathString.slice (/Users/jarred/Code/bun/src/string_types.zig:43)
src.cli.test_command.TestCommand.runAllTests.Context.begin (/Users/jarred/Code/bun/src/cli/test_command.zig:618)
src.bun.js.javascript.OpaqueWrap__anon_144322__struct_253123.callback (/Users/jarred/Code/bun/src/bun.js/javascript.zig:121)
::JSC__VM__holdAPILock(JSC__VM *, void *, void (*)(void *)) (/Users/jarred/Code/bun/src/bun.js/bindings/bindings.cpp:3492)
src.bun.js.bindings.shimmer.Shimmer("JSC","VM",src.bun.js.bindings.bindings.VM).cppFn (/Users/jarred/Code/bun/src/bun.js/bindings/shimmer.zig:186)
src.bun.js.bindings.bindings.VM.holdAPILock (/Users/jarred/Code/bun/src/bun.js/bindings/bindings.zig:4627)
src.bun.js.javascript.VirtualMachine.runWithAPILock (/Users/jarred/Code/bun/src/bun.js/javascript.zig:1710)
src.cli.test_command.TestCommand.runAllTests (/Users/jarred/Code/bun/src/cli/test_command.zig:632)
src.cli.test_command.TestCommand.exec (/Users/jarred/Code/bun/src/cli/test_command.zig:462)
src.cli.Command.start (/Users/jarred/Code/bun/src/cli.zig:1213)
We manually run the garbage collector after each file finishes executing and that seems to cause a crash due to a memory allocation failure happening inside the finalizer when getting function names from Error objects being finalized. I'm not sure yet if this is a bug in bun or in JSC (probably Bun)
After working around the JSC thing
The next crash happens when trying to diff a 2.7 GB string compared to null
. It occurs in the test following this one:
✓
> should render the children for the element
Current status:
beforeAll
etc in a --preload
scriptwaitFor
seems to have a fixed 500ms delay. We don't have a way to simulate timers yet. react-testing-library should work now, minus the above issue with the test simulation.
Confirmed. Working well for me in bun 0.6.3. 👍
I was able to add it to my test runner benchmarks. If you're curious, here are the results:
'yarn workspace bun test' ran
1.16 ± 0.02 times faster than 'yarn workspace vitest test --isolate=false'
1.32 ± 0.04 times faster than 'yarn workspace jasmine test'
4.46 ± 0.06 times faster than 'yarn workspace jest test'
9.08 ± 0.13 times faster than 'yarn workspace vitest test'
Looks like bun
is the new performance winner of my benchmark. 🎉
The very marginal improvement there over vitest, I think, is because of the timers. If you remove the waitFor
code or (make it so it only waits one tick), what does it look like? When I checked last, waitFor
was a hardcoded 500ms delay in each test that it was used.
ah there's only one usage of waitFor
, interesting
If I remove the 1 usage of waitFor
I get slightly better comparative results:
'yarn workspace bun test' ran
1.45 ± 0.04 times faster than 'yarn workspace vitest test --isolate=false'
1.98 ± 0.06 times faster than 'yarn workspace jasmine test'
7.96 ± 0.22 times faster than 'yarn workspace jest test'
17.32 ± 0.48 times faster than 'yarn workspace vitest test'
yeah i think our functions for printing the errors is pretty slow right now. There is a test failing in Bun's version due to lack of process.env.TZ
support. Adding that shortly.
alright give it another try in the canary build
should be about 80% faster
the main remaining TODOs here are:
1) defer generating the Error message to when the getter is called. This is what Jest & Vitest already do, but we do not. We eagerly generate error messages which is super expensive (why await waitFor ...
had such an impact)
2) run the transpiler in paralell. We are running it single-threaded. This is good for memory usage. But after a few files, it slows stuff down a bit
Confirmed. Latest canary is a bit better (results). Bun is now a clear winner 🎉
@EvHaus the waitFor
thing still needs to be addressed - Bun needs to implement Jest fake timers, I guess. In both Vite & Bun about 5 seconds of the total is just the hardcoded waitFor
delay. Vite is running multi-threaded so the delay is spread across the number of cores
@Jarred-Sumner Feel free to close this.
I try to switch from Jest+Testing Library to Bun+Testing Library and stumbled over this issue. It looks like I have to run afterEach(() => cleanup());
within each test file.
When I do it globally in a preload function like this:
import { cleanup } from '@testing-library/react';
import {
getIsReactActEnvironment,
setReactActEnvironment,
} from '@testing-library/react/dist/act-compat';
import { afterAll, afterEach, beforeAll } from 'bun:test';
let previousIsReactActEnvironment = getIsReactActEnvironment();
beforeAll(() => {
previousIsReactActEnvironment = getIsReactActEnvironment();
setReactActEnvironment(true);
});
afterEach(() => cleanup());
afterAll(() => {
setReactActEnvironment(previousIsReactActEnvironment);
});
then I got For queries bound to document.body a global document has to be available...
. Is someone running Bun with Testing Library without manually adding afterEach(() => cleanup());
in each test file?
@donaldpipowitch, I had the same issue, I opened an issue on react testing library repo https://github.com/testing-library/react-testing-library/issues/1348
@donaldpipowitch @hmidmrii You need to set up happy-dom before calling cleanup. https://github.com/oven-sh/bun/issues/198#issuecomment-1555565193
@gkiely If you check the issue I opened on react testing library repo in my previous commit, you will find that I did that in the preload file, I even proposed two workarounds,
but the casual way of using the exported screen
will not work.
here's the issue again: https://github.com/testing-library/react-testing-library/issues/1348
I opened an issue on their repo because I did some logs on both preload cleanup (placing the cleanup code in the preload file) and local cleanup (placing the cleanup code in each test file) and saw that both act the same, so I expected it the issue to be from cleanup
function from react testing library and not from afterEach
exported by Bun.
Allow running tests with @testing-library/react.
Example repo: https://github.com/gkiely/bun-rtl
Steps:
Error without environment:
Error after enabling jsdom: