Compare commits

...

3 Commits

Author SHA1 Message Date
Claude Bot
d6e74e4e14 Improve test coverage based on CodeRabbit feedback
- Add exit code assertions to all test cases (0 for passing, non-zero for failing)
- Add async test coverage: describe() blocked after await inside test
- Add describe.each() coverage: blocked when called inside test
- Add comprehensive lifecycle hook coverage: beforeAll, afterEach, afterAll
- Follow testing best practices with proper error assertions

Addresses CodeRabbit review suggestions for more robust test coverage.
2025-08-31 01:25:55 +00:00
autofix-ci[bot]
6d9595d32c [autofix.ci] apply automated fixes 2025-08-31 00:57:17 +00:00
Claude Bot
68e1a48d27 Prevent describe() and lifecycle hooks from being called inside test() blocks
Use simple approach checking parent.current_test_id instead of complex thread-local storage:
- Check if parent.current_test_id != null_id to detect test execution
- Block describe() calls during test execution to prevent nesting
- Block lifecycle hooks (beforeEach, afterEach, beforeAll, afterAll) inside tests
- Add comprehensive tests for both restrictions
- Normal describe nesting (describe inside describe) still works
- Hooks and describes work properly at the top level

This approach is much simpler, uses existing infrastructure, and avoids
concurrency complexity while still solving the core problem.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-31 00:49:29 +00:00
2 changed files with 207 additions and 0 deletions

View File

@@ -974,6 +974,11 @@ pub const DescribeScope = struct {
return globalThis.throwInvalidArgumentType(@tagName(hook), "callback", "function");
}
// Prevent hooks from being called inside test blocks
if (DescribeScope.active.?.current_test_id != TestRunner.Test.null_id) {
return globalThis.throwPretty(@tagName(hook) ++ "() cannot be called inside a test(). Lifecycle hooks must be called at the top level.", .{});
}
cb.protect();
@field(DescribeScope.active.?, @tagName(hook) ++ "s").append(bun.default_allocator, cb) catch unreachable;
return .true;
@@ -1927,6 +1932,12 @@ inline fn createScope(
const allocator = bun.default_allocator;
const parent = DescribeScope.active.?;
// Prevent describe blocks from being nested inside test blocks
if (!is_test and parent.current_test_id != TestRunner.Test.null_id) {
return globalThis.throwPretty("describe() cannot be called inside a test(). Test structure functions must be called at the top level.", .{});
}
const label = brk: {
if (description == .zero) {
break :brk "";

View File

@@ -223,3 +223,199 @@ describe("passing arrow function as args", () => {
expect(fullOutput).toInclude("1 fail");
});
});
describe("test structure nesting restrictions", () => {
test("describe blocks cannot be nested inside test blocks", async () => {
const test_dir = tempDirWithFiles(".", {
"nested-describe-test.test.js": `
import { describe, test, expect } from "bun:test";
test("should fail when describe is nested inside", () => {
describe("nested describe", () => {
test("should not run", () => {
expect(true).toBe(true);
});
});
});
`,
});
const proc = spawnSync({
cmd: [bunExe(), "test", "nested-describe-test.test.js"],
cwd: test_dir,
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const fullOutput = proc.stdout.toString() + proc.stderr.toString();
expect(fullOutput).toInclude("describe() cannot be called inside a test()");
expect(fullOutput).toInclude("0 pass");
expect(fullOutput).toInclude("1 fail");
expect(proc.exitCode).not.toBe(0);
});
test("lifecycle hooks cannot be called inside test blocks", async () => {
const test_dir = tempDirWithFiles(".", {
"hook-in-test.test.js": `
import { test, expect, beforeEach } from "bun:test";
test("should fail when beforeEach is called inside", () => {
beforeEach(() => {
console.log("This should not work");
});
expect(true).toBe(true);
});
`,
});
const proc = spawnSync({
cmd: [bunExe(), "test", "hook-in-test.test.js"],
cwd: test_dir,
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const fullOutput = proc.stdout.toString() + proc.stderr.toString();
expect(fullOutput).toInclude("beforeEach() cannot be called inside a test()");
expect(fullOutput).toInclude("0 pass");
expect(fullOutput).toInclude("1 fail");
expect(proc.exitCode).not.toBe(0);
});
test("describe blocks can still be nested inside other describe blocks", async () => {
const test_dir = tempDirWithFiles(".", {
"nested-describe-valid.test.js": `
import { describe, test, expect } from "bun:test";
describe("outer describe", () => {
describe("inner describe", () => {
test("should pass", () => {
expect(true).toBe(true);
});
});
});
`,
});
const proc = spawnSync({
cmd: [bunExe(), "test", "nested-describe-valid.test.js"],
cwd: test_dir,
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const fullOutput = proc.stdout.toString() + proc.stderr.toString();
expect(fullOutput).toInclude("should pass");
expect(fullOutput).toInclude("1 pass");
expect(fullOutput).toInclude("0 fail");
expect(proc.exitCode).toBe(0);
});
test("describe cannot be called after await inside async test", async () => {
const test_dir = tempDirWithFiles(".", {
"async-describe-test.test.js": `
import { describe, test, expect } from "bun:test";
test("async test with describe after await", async () => {
await Promise.resolve();
describe("should not be allowed", () => {
test("nested test", () => {});
});
});
`,
});
const proc = spawnSync({
cmd: [bunExe(), "test", "async-describe-test.test.js"],
cwd: test_dir,
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const fullOutput = proc.stdout.toString() + proc.stderr.toString();
expect(fullOutput).toInclude("describe() cannot be called inside a test()");
expect(fullOutput).toInclude("0 pass");
expect(fullOutput).toInclude("1 fail");
expect(proc.exitCode).not.toBe(0);
});
test("describe.each cannot be called inside test blocks", async () => {
const test_dir = tempDirWithFiles(".", {
"describe-each-test.test.js": `
import { describe, test, expect } from "bun:test";
test("should fail when describe.each is nested inside", () => {
describe.each([
[1, 2, 3],
[4, 5, 9]
])("add %i + %i equals %i", (a, b, expected) => {
test("adds correctly", () => {
expect(a + b).toBe(expected);
});
});
});
`,
});
const proc = spawnSync({
cmd: [bunExe(), "test", "describe-each-test.test.js"],
cwd: test_dir,
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const fullOutput = proc.stdout.toString() + proc.stderr.toString();
expect(fullOutput).toInclude("describe() cannot be called inside a test()");
expect(fullOutput).toInclude("0 pass");
expect(fullOutput).toInclude("1 fail");
expect(proc.exitCode).not.toBe(0);
});
test("all lifecycle hooks are blocked inside test blocks", async () => {
const test_dir = tempDirWithFiles(".", {
"all-hooks-test.test.js": `
import { test, beforeEach, afterEach, beforeAll, afterAll } from "bun:test";
test("should fail with beforeAll", () => {
beforeAll(() => {});
});
test("should fail with afterEach", () => {
afterEach(() => {});
});
test("should fail with afterAll", () => {
afterAll(() => {});
});
`,
});
const proc = spawnSync({
cmd: [bunExe(), "test", "all-hooks-test.test.js"],
cwd: test_dir,
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const fullOutput = proc.stdout.toString() + proc.stderr.toString();
// Should show errors for all different hooks
expect(fullOutput).toInclude("beforeAll() cannot be called inside a test()");
expect(fullOutput).toInclude("afterEach() cannot be called inside a test()");
expect(fullOutput).toInclude("afterAll() cannot be called inside a test()");
expect(fullOutput).toInclude("0 pass");
expect(fullOutput).toInclude("3 fail");
expect(proc.exitCode).not.toBe(0);
});
});