Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
99e70d01c9 repro 2025-08-11 20:22:49 +00:00
17 changed files with 832 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
{
"targets": [
{
"target_name": "verify_node_behavior",
"sources": ["verify_node_behavior.cpp"],
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ]
}
]
}

View File

@@ -0,0 +1,8 @@
{
"targets": [
{
"target_name": "test_c_version",
"sources": ["test_c_version.c"]
}
]
}

View File

@@ -0,0 +1,11 @@
{
"targets": [
{
"target_name": "test_comprehensive",
"sources": ["test_comprehensive.cpp"],
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ]
}
]
}

View File

@@ -0,0 +1,11 @@
{
"targets": [
{
"target_name": "test_rapid_throws",
"sources": ["test_rapid_throws.cpp"],
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ]
}
]
}

View File

@@ -0,0 +1,11 @@
{
"targets": [
{
"target_name": "verify_node_behavior",
"sources": ["verify_node_behavior.cpp"],
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ]
}
]
}

View File

@@ -0,0 +1,9 @@
{
"name": "test-throw-exception-segfault",
"version": "1.0.0",
"main": "test.js",
"gypfile": true,
"dependencies": {
"node-addon-api": "^8.5.0"
}
}

View File

@@ -0,0 +1,24 @@
const assert = require('assert');
const testModule = require('./build/Release/test_throw_exception_segfault.node');
// Test the segfault case
console.log("Testing ThrowAsJavaScriptException (expected to segfault in Bun)...");
try {
testModule.throwException();
assert.fail("Expected exception to be thrown");
} catch (error) {
console.log("Caught exception:", error.message);
assert.strictEqual(error.message, "Test error message");
}
// Test the workaround case
console.log("Testing workaround (throw in C++ space)...");
try {
testModule.throwExceptionWorkaround();
assert.fail("Expected exception to be thrown");
} catch (error) {
console.log("Caught exception:", error.message);
assert.strictEqual(error.message, "Test error message");
}
console.log("All tests passed!");

View File

@@ -0,0 +1,29 @@
const assert = require('assert');
try {
const testModule = require('./build/Release/test_c_version.node');
// Test throwing with napi_throw
console.log("Testing napi_throw...");
try {
testModule.throwError();
assert.fail("Expected exception to be thrown");
} catch (error) {
console.log("Caught exception:", error.message);
}
// Test throwing with napi_throw_error
console.log("Testing napi_throw_error...");
try {
testModule.throwErrorString();
assert.fail("Expected exception to be thrown");
} catch (error) {
console.log("Caught exception:", error.message);
assert.strictEqual(error.message, "Test error string from C NAPI");
}
console.log("All C NAPI tests passed!");
} catch (loadError) {
console.error("Failed to load C module:", loadError.message);
console.log("Building C module...");
}

View File

@@ -0,0 +1,48 @@
#include <assert.h>
#include <js_native_api.h>
#include <stdlib.h>
// This reproduces the issue from https://github.com/oven-sh/bun/issues/4526
// using C NAPI to avoid any C++ wrapper issues
static napi_value ThrowError(napi_env env, napi_callback_info info) {
napi_value error;
napi_value message;
napi_status status;
// Create message string
status = napi_create_string_utf8(env, "Test error from C NAPI", NAPI_AUTO_LENGTH, &message);
if (status != napi_ok) return NULL;
// Create an error
status = napi_create_error(env, NULL, message, &error);
if (status != napi_ok) return NULL;
// Try to throw it
status = napi_throw(env, error);
if (status != napi_ok) return NULL;
return NULL;
}
static napi_value ThrowErrorString(napi_env env, napi_callback_info info) {
// Use napi_throw_error directly
napi_status status = napi_throw_error(env, NULL, "Test error string from C NAPI");
if (status != napi_ok) return NULL;
return NULL;
}
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{ "throwError", NULL, ThrowError, NULL, NULL, NULL, napi_default, NULL },
{ "throwErrorString", NULL, ThrowErrorString, NULL, NULL, NULL, napi_default, NULL },
};
napi_status status = napi_define_properties(env, exports, 2, desc);
assert(status == napi_ok);
return exports;
}
NAPI_MODULE(test_c_version, Init)

View File

@@ -0,0 +1,188 @@
#include <napi.h>
#include <iostream>
// This reproduces issue #4526 more comprehensively using various C++ NAPI patterns
// Test 1: Direct ThrowAsJavaScriptException (the original issue)
Napi::Value DirectThrowException(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing direct ThrowAsJavaScriptException..." << std::endl;
// This was the problematic line from the original issue
Napi::Error::New(env, "Direct throw error message").ThrowAsJavaScriptException();
// This should never be reached if the exception is thrown properly
return env.Null();
}
// Test 2: Create error then throw separately
Napi::Value CreateThenThrow(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing create then throw..." << std::endl;
auto error = Napi::Error::New(env, "Created then thrown error");
error.ThrowAsJavaScriptException();
return env.Null();
}
// Test 3: Multiple exception types
Napi::Value ThrowTypeError(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing TypeError throw..." << std::endl;
Napi::TypeError::New(env, "Type error message").ThrowAsJavaScriptException();
return env.Null();
}
Napi::Value ThrowRangeError(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing RangeError throw..." << std::endl;
Napi::RangeError::New(env, "Range error message").ThrowAsJavaScriptException();
return env.Null();
}
// Test 4: C++ exception (the workaround from original issue)
Napi::Value ThrowCppException(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing C++ throw (workaround)..." << std::endl;
// This was mentioned as working in the original issue
throw Napi::Error::New(env, "C++ thrown error");
return env.Null();
}
// Test 5: Throw with error code
Napi::Value ThrowWithCode(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing throw with error code..." << std::endl;
auto error = Napi::Error::New(env, "Error with code");
error.Set("code", Napi::String::New(env, "TEST_ERROR_CODE"));
error.ThrowAsJavaScriptException();
return env.Null();
}
// Test 6: Nested function call with exception
Napi::Value NestedThrow(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing nested function throw..." << std::endl;
auto throwError = [&env]() {
Napi::Error::New(env, "Nested error").ThrowAsJavaScriptException();
};
throwError();
return env.Null();
}
// Test 7: Exception in callback
Napi::Value ThrowInCallback(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing throw in callback..." << std::endl;
if (info.Length() > 0 && info[0].IsFunction()) {
auto callback = info[0].As<Napi::Function>();
// Call the callback which should trigger an exception
try {
callback.Call({});
} catch (const Napi::Error& e) {
// Re-throw using ThrowAsJavaScriptException
Napi::Error::New(env, "Callback error: " + std::string(e.Message())).ThrowAsJavaScriptException();
}
} else {
Napi::Error::New(env, "Callback required").ThrowAsJavaScriptException();
}
return env.Null();
}
// Test 8: Multiple rapid throws (stress test)
Napi::Value RapidThrows(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing rapid throws..." << std::endl;
for (int i = 0; i < 100; ++i) {
try {
std::string message = "Rapid throw #" + std::to_string(i);
Napi::Error::New(env, message).ThrowAsJavaScriptException();
} catch (...) {
// Continue throwing
}
}
// Final throw
Napi::Error::New(env, "Final rapid throw").ThrowAsJavaScriptException();
return env.Null();
}
// Test 9: Empty/null message handling
Napi::Value ThrowEmptyMessage(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing empty message throw..." << std::endl;
Napi::Error::New(env, "").ThrowAsJavaScriptException();
return env.Null();
}
// Test 10: Very long message
Napi::Value ThrowLongMessage(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing long message throw..." << std::endl;
std::string longMessage(10000, 'A');
longMessage += " - End of long message";
Napi::Error::New(env, longMessage).ThrowAsJavaScriptException();
return env.Null();
}
// Initialize the module
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "directThrow"),
Napi::Function::New(env, DirectThrowException));
exports.Set(Napi::String::New(env, "createThenThrow"),
Napi::Function::New(env, CreateThenThrow));
exports.Set(Napi::String::New(env, "throwTypeError"),
Napi::Function::New(env, ThrowTypeError));
exports.Set(Napi::String::New(env, "throwRangeError"),
Napi::Function::New(env, ThrowRangeError));
exports.Set(Napi::String::New(env, "throwCppException"),
Napi::Function::New(env, ThrowCppException));
exports.Set(Napi::String::New(env, "throwWithCode"),
Napi::Function::New(env, ThrowWithCode));
exports.Set(Napi::String::New(env, "nestedThrow"),
Napi::Function::New(env, NestedThrow));
exports.Set(Napi::String::New(env, "throwInCallback"),
Napi::Function::New(env, ThrowInCallback));
exports.Set(Napi::String::New(env, "rapidThrows"),
Napi::Function::New(env, RapidThrows));
exports.Set(Napi::String::New(env, "throwEmptyMessage"),
Napi::Function::New(env, ThrowEmptyMessage));
exports.Set(Napi::String::New(env, "throwLongMessage"),
Napi::Function::New(env, ThrowLongMessage));
return exports;
}
NODE_API_MODULE(test_comprehensive, Init)

View File

@@ -0,0 +1,147 @@
const assert = require('assert');
// Build and load the comprehensive test module
let testModule;
try {
testModule = require('./build/Release/test_comprehensive.node');
} catch (e) {
console.error("Failed to load comprehensive test module:", e.message);
console.log("Please build with: cp binding_comprehensive.gyp binding.gyp && node-gyp rebuild");
process.exit(1);
}
const tests = [
{
name: "Direct ThrowAsJavaScriptException (original issue)",
func: testModule.directThrow,
expectedMessage: "Direct throw error message"
},
{
name: "Create then throw separately",
func: testModule.createThenThrow,
expectedMessage: "Created then thrown error"
},
{
name: "TypeError throw",
func: testModule.throwTypeError,
expectedMessage: "Type error message",
expectedType: TypeError
},
{
name: "RangeError throw",
func: testModule.throwRangeError,
expectedMessage: "Range error message",
expectedType: RangeError
},
{
name: "C++ exception (workaround)",
func: testModule.throwCppException,
expectedMessage: "C++ thrown error"
},
{
name: "Throw with error code",
func: testModule.throwWithCode,
expectedMessage: "Error with code",
expectCode: "TEST_ERROR_CODE"
},
{
name: "Nested function throw",
func: testModule.nestedThrow,
expectedMessage: "Nested error"
},
{
name: "Throw in callback",
func: () => testModule.throwInCallback(() => { throw new Error("Callback error"); }),
expectedMessage: /Callback error/
},
{
name: "Rapid throws (stress test)",
func: testModule.rapidThrows,
expectedMessage: "Final rapid throw"
},
{
name: "Empty message throw",
func: testModule.throwEmptyMessage,
expectedMessage: ""
},
{
name: "Long message throw",
func: testModule.throwLongMessage,
expectedMessage: /End of long message$/
}
];
let passedTests = 0;
let totalTests = tests.length;
console.log(`Running ${totalTests} comprehensive C++ NAPI exception tests...\n`);
for (const test of tests) {
try {
console.log(`Testing: ${test.name}`);
test.func();
// If we get here, the function didn't throw
console.log(`❌ FAILED: ${test.name} - Expected exception but none was thrown`);
} catch (error) {
// Verify the exception properties
let passed = true;
let details = [];
// Check error type
if (test.expectedType) {
if (!(error instanceof test.expectedType)) {
passed = false;
details.push(`Expected ${test.expectedType.name} but got ${error.constructor.name}`);
}
}
// Check error message
if (test.expectedMessage instanceof RegExp) {
if (!test.expectedMessage.test(error.message)) {
passed = false;
details.push(`Message doesn't match pattern: got "${error.message}"`);
}
} else if (typeof test.expectedMessage === 'string') {
if (error.message !== test.expectedMessage) {
passed = false;
details.push(`Expected message "${test.expectedMessage}" but got "${error.message}"`);
}
}
// Check error code if expected
if (test.expectCode) {
if (error.code !== test.expectCode) {
passed = false;
details.push(`Expected code "${test.expectCode}" but got "${error.code}"`);
}
}
if (passed) {
console.log(`✅ PASSED: ${test.name}`);
passedTests++;
} else {
console.log(`❌ FAILED: ${test.name}`);
details.forEach(detail => console.log(` ${detail}`));
}
console.log(` Caught: ${error.constructor.name}: ${error.message}`);
if (error.code) console.log(` Code: ${error.code}`);
}
console.log();
}
console.log(`\n=== Test Summary ===`);
console.log(`Passed: ${passedTests}/${totalTests}`);
console.log(`Failed: ${totalTests - passedTests}/${totalTests}`);
if (passedTests === totalTests) {
console.log("🎉 All comprehensive C++ NAPI exception tests passed!");
console.log("The original issue #4526 appears to be FIXED ✅");
} else {
console.log("⚠️ Some tests failed - there may still be issues with NAPI exception handling");
process.exit(1);
}

View File

@@ -0,0 +1,39 @@
// Test the rapid throws issue found in Bun
let testModule;
try {
testModule = require('./build/Release/test_rapid_throws.node');
} catch (e) {
console.error("Failed to load test module:", e.message);
console.log("Please build with: cp binding_rapid.gyp binding.gyp && node-gyp rebuild");
process.exit(1);
}
console.log("=== Testing Rapid Throws Issue ===\n");
// Test 1: Single throw (should work)
console.log("Test 1: Single throw");
try {
testModule.singleThrow();
console.log("❌ Expected exception");
} catch (e) {
console.log("✅ Caught:", e.message);
}
console.log("\nTest 2: Throw after catch");
try {
testModule.throwAfterCatch();
console.log("❌ Expected exception");
} catch (e) {
console.log("✅ Caught:", e.message);
}
console.log("\nTest 3: Simple rapid throws (this may crash Bun)");
try {
testModule.simpleRapidThrows();
console.log("❌ Expected exception");
} catch (e) {
console.log("✅ Caught:", e.message);
}
console.log("\n=== Test completed ===");

View File

@@ -0,0 +1,64 @@
#include <napi.h>
#include <iostream>
// Reproduces a new issue found in Bun's NAPI exception handling
// The rapid throws test reveals an assertion failure in JavaScriptCore
Napi::Value SimpleRapidThrows(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing simple rapid throws..." << std::endl;
// Try multiple throws in a loop - this causes assertion failure in Bun
for (int i = 0; i < 10; ++i) {
try {
std::string message = "Rapid throw #" + std::to_string(i);
std::cout << "Throwing: " << message << std::endl;
Napi::Error::New(env, message).ThrowAsJavaScriptException();
std::cout << "After throw (should not see this)" << std::endl;
} catch (...) {
std::cout << "Caught C++ exception for #" << i << std::endl;
// Continue to next iteration
}
}
return env.Null();
}
Napi::Value SingleThrow(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing single throw..." << std::endl;
Napi::Error::New(env, "Single throw").ThrowAsJavaScriptException();
return env.Null();
}
Napi::Value ThrowAfterCatch(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "Testing throw after catch..." << std::endl;
try {
Napi::Error::New(env, "First throw").ThrowAsJavaScriptException();
} catch (...) {
std::cout << "Caught first exception, throwing second..." << std::endl;
}
// Second throw after catching the first
Napi::Error::New(env, "Second throw").ThrowAsJavaScriptException();
return env.Null();
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "simpleRapidThrows"),
Napi::Function::New(env, SimpleRapidThrows));
exports.Set(Napi::String::New(env, "singleThrow"),
Napi::Function::New(env, SingleThrow));
exports.Set(Napi::String::New(env, "throwAfterCatch"),
Napi::Function::New(env, ThrowAfterCatch));
return exports;
}
NODE_API_MODULE(test_rapid_throws, Init)

View File

@@ -0,0 +1,32 @@
#include <napi.h>
// This reproduces the issue from https://github.com/oven-sh/bun/issues/4526
// where Napi::Error::New(env, "MESSAGE").ThrowAsJavaScriptException() causes SIGSEGV
Napi::Value ThrowException(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
// This should cause a SIGSEGV in Bun (but works fine in Node.js)
Napi::Error::New(env, "Test error message").ThrowAsJavaScriptException();
return env.Null();
}
Napi::Value ThrowExceptionWorkaround(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
// This is the workaround mentioned in the issue - throwing in C++ space
throw Napi::Error::New(env, "Test error message");
return env.Null();
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "throwException"),
Napi::Function::New(env, ThrowException));
exports.Set(Napi::String::New(env, "throwExceptionWorkaround"),
Napi::Function::New(env, ThrowExceptionWorkaround));
return exports;
}
NODE_API_MODULE(test_throw_exception_segfault, Init)

View File

@@ -0,0 +1,33 @@
// Verify that Node.js handles "throw after catch" correctly
let testModule;
try {
testModule = require('./build/Release/verify_node_behavior.node');
} catch (e) {
console.error("Failed to load test module:", e.message);
console.log("Please build with: cp binding_verify.gyp binding.gyp && node-gyp rebuild");
process.exit(1);
}
console.log("=== Verifying Node.js Behavior ===\n");
// Test 1: Simple throw (baseline - should work in both Node.js and Bun)
console.log("Test 1: Simple throw (baseline)");
try {
testModule.simpleThrow();
console.log("❌ ERROR: Expected exception from simple throw");
} catch (e) {
console.log("✅ SUCCESS: Caught simple exception:", e.message);
}
console.log("\nTest 2: Throw after catch (the issue)");
try {
testModule.throwAfterCatchClean();
console.log("❌ ERROR: Expected exception from throw after catch");
} catch (e) {
console.log("✅ SUCCESS: Caught exception after catch:", e.message);
}
console.log("\n=== Node.js Verification Complete ===");
console.log("If both tests show SUCCESS, then Node.js handles this correctly");
console.log("and the issue is Bun-specific.");

View File

@@ -0,0 +1,48 @@
#include <napi.h>
#include <iostream>
// Clean test to verify Node.js handles "throw after catch" correctly
// This should work fine in Node.js but crash in Bun
Napi::Value ThrowAfterCatchClean(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "[C++] Starting throw after catch test..." << std::endl;
try {
std::cout << "[C++] Throwing first exception..." << std::endl;
Napi::Error::New(env, "First exception").ThrowAsJavaScriptException();
std::cout << "[C++] ERROR: Should not reach here after first throw!" << std::endl;
} catch (const Napi::Error& e) {
std::cout << "[C++] Caught first Napi::Error: " << e.Message() << std::endl;
} catch (...) {
std::cout << "[C++] Caught first unknown exception" << std::endl;
}
std::cout << "[C++] Now throwing second exception..." << std::endl;
// This second throw should work in Node.js but causes assertion failure in Bun
Napi::Error::New(env, "Second exception after catch").ThrowAsJavaScriptException();
std::cout << "[C++] ERROR: Should not reach here after second throw!" << std::endl;
return env.Null();
}
Napi::Value SimpleThrow(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "[C++] Simple throw test..." << std::endl;
Napi::Error::New(env, "Simple exception").ThrowAsJavaScriptException();
return env.Null();
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "throwAfterCatchClean"),
Napi::Function::New(env, ThrowAfterCatchClean));
exports.Set(Napi::String::New(env, "simpleThrow"),
Napi::Function::New(env, SimpleThrow));
return exports;
}
NODE_API_MODULE(verify_node_behavior, Init)

View File

@@ -0,0 +1,119 @@
import { test, expect } from "bun:test";
import { bunExe } from "harness";
import { mkdirSync, writeFileSync } from "fs";
import { tmpdir } from "os";
import { join } from "path";
test("NAPI exception after catch should not cause assertion failure", async () => {
// Create a temporary directory for our test
const testDir = join(tmpdir(), "napi-exception-after-catch-test");
mkdirSync(testDir, { recursive: true });
// Create package.json
writeFileSync(join(testDir, "package.json"), JSON.stringify({
name: "napi-exception-after-catch-test",
version: "1.0.0",
gypfile: true
}));
// Create C++ source file
const cppSource = `
#include <napi.h>
Napi::Value ThrowAfterCatch(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
try {
Napi::Error::New(env, "First throw").ThrowAsJavaScriptException();
} catch (...) {
// Caught the first exception in C++
}
// This second throw causes assertion failure in Bun
Napi::Error::New(env, "Second throw").ThrowAsJavaScriptException();
return env.Null();
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "throwAfterCatch"),
Napi::Function::New(env, ThrowAfterCatch));
return exports;
}
NODE_API_MODULE(test_module, Init)
`;
writeFileSync(join(testDir, "test.cpp"), cppSource);
// Create binding.gyp
const bindingGyp = `
{
"targets": [
{
"target_name": "test_module",
"sources": ["test.cpp"],
"include_dirs": ["<!@(node -p \\"require('node-addon-api').include\\")"],
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ]
}
]
}
`;
writeFileSync(join(testDir, "binding.gyp"), bindingGyp);
// Create test JavaScript file
const jsTest = `
const testModule = require('./build/Release/test_module.node');
try {
testModule.throwAfterCatch();
console.log("ERROR: Expected exception to be thrown");
process.exit(1);
} catch (e) {
console.log("SUCCESS: Caught exception:", e.message);
process.exit(0);
}
`;
writeFileSync(join(testDir, "test.js"), jsTest);
// Install node-addon-api and build the module
await using proc1 = Bun.spawn({
cmd: ["npm", "install", "node-addon-api"],
cwd: testDir,
});
await proc1.exited;
await using proc2 = Bun.spawn({
cmd: ["node-gyp", "configure", "build"],
cwd: testDir,
});
await proc2.exited;
// Test with Bun - this should not crash
await using proc3 = Bun.spawn({
cmd: [bunExe(), "test.js"],
cwd: testDir,
stdout: "pipe",
stderr: "pipe"
});
const [stdout, stderr, exitCode] = await Promise.all([
proc3.stdout.text(),
proc3.stderr.text(),
proc3.exited
]);
// The bug causes an assertion failure (abort), so we expect exit code 134 or similar
// When the bug is fixed, we expect exit code 0 and "SUCCESS" message
console.log("stdout:", stdout);
console.log("stderr:", stderr);
console.log("exitCode:", exitCode);
// For now, we expect this to fail with assertion error until the bug is fixed
// When this test starts passing, the bug has been fixed!
expect(exitCode).toBe(0); // This will fail until the bug is fixed
expect(stdout).toContain("SUCCESS: Caught exception: Second throw");
}, 30000);