mirror of
https://github.com/oven-sh/bun
synced 2026-02-17 06:12:08 +00:00
Enable breaking_changes_1_3 (#23308)
Breaking changes: - bun:test: disallow creating snapshots or using .only() in ci - for users: hopefully this should only reveal existing bugs in tests, not cause failures. - general: enable calling unhandled rejection handlers for ErrorBuilder.reject() - for users: this might reveal some unhandled rejections that were not visible before.
This commit is contained in:
@@ -278,7 +278,6 @@ fn genericExtend(this: *ScopeFunctions, globalThis: *JSGlobalObject, cfg: bun_te
|
||||
}
|
||||
|
||||
fn errorInCI(globalThis: *jsc.JSGlobalObject, signature: []const u8) bun.JSError!void {
|
||||
if (!bun.FeatureFlags.breaking_changes_1_3) return; // this is a breaking change for version 1.3
|
||||
if (bun.detectCI()) |_| {
|
||||
return globalThis.throwPretty("{s} is not allowed in CI environments.\nIf this is not a CI environment, set the environment variable CI=false to force allow.", .{signature});
|
||||
}
|
||||
|
||||
@@ -744,12 +744,10 @@ pub const Expect = struct {
|
||||
}
|
||||
|
||||
if (needs_write) {
|
||||
if (bun.FeatureFlags.breaking_changes_1_3) {
|
||||
if (bun.detectCI()) |_| {
|
||||
if (!update) {
|
||||
const signature = comptime getSignature(fn_name, "", false);
|
||||
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Inline snapshot updates are not allowed in CI environments unless --update-snapshots is used\nIf this is not a CI environment, set the environment variable CI=false to force allow.", .{});
|
||||
}
|
||||
if (bun.detectCI()) |_| {
|
||||
if (!update) {
|
||||
const signature = comptime getSignature(fn_name, "", false);
|
||||
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Inline snapshot updates are not allowed in CI environments unless --update-snapshots is used\nIf this is not a CI environment, set the environment variable CI=false to force allow.", .{});
|
||||
}
|
||||
}
|
||||
var buntest_strong = this.bunTest() orelse {
|
||||
|
||||
@@ -484,7 +484,6 @@ pub fn captureTestLineNumber(callframe: *jsc.CallFrame, globalThis: *JSGlobalObj
|
||||
}
|
||||
|
||||
pub fn errorInCI(globalObject: *jsc.JSGlobalObject, message: []const u8) bun.JSError!void {
|
||||
if (!bun.FeatureFlags.breaking_changes_1_3) return; // this is a breaking change for version 1.3
|
||||
if (bun.detectCI()) |_| {
|
||||
return globalObject.throwPretty("{s}\nIf this is not a CI environment, set the environment variable CI=false to force allow.", .{message});
|
||||
}
|
||||
|
||||
@@ -85,11 +85,9 @@ pub const Snapshots = struct {
|
||||
|
||||
// doesn't exist. append to file bytes and add to hashmap.
|
||||
// Prevent snapshot creation in CI environments unless --update-snapshots is used
|
||||
if (bun.FeatureFlags.breaking_changes_1_3) {
|
||||
if (bun.detectCI()) |_| {
|
||||
if (!this.update_snapshots) {
|
||||
return error.SnapshotCreationNotAllowedInCI;
|
||||
}
|
||||
if (bun.detectCI()) |_| {
|
||||
if (!this.update_snapshots) {
|
||||
return error.SnapshotCreationNotAllowedInCI;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,11 +72,7 @@ pub fn ErrorBuilder(comptime code: Error, comptime fmt: [:0]const u8, Args: type
|
||||
|
||||
/// Turn this into a JSPromise that is already rejected.
|
||||
pub inline fn reject(this: @This()) jsc.JSValue {
|
||||
if (comptime bun.FeatureFlags.breaking_changes_1_3) {
|
||||
return jsc.JSPromise.rejectedPromise(this.globalThis, code.fmt(this.global, fmt, this.args)).toJS();
|
||||
} else {
|
||||
return jsc.JSPromise.dangerouslyCreateRejectedPromiseValueWithoutNotifyingVM(this.global, code.fmt(this.global, fmt, this.args));
|
||||
}
|
||||
return jsc.JSPromise.rejectedPromise(this.global, code.fmt(this.global, fmt, this.args)).toJS();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ pub const RuntimeFeatureFlag = enum {
|
||||
|
||||
/// Enable breaking changes for the next major release of Bun
|
||||
// TODO: Make this a CLI flag / runtime var so that we can verify disabled code paths can compile
|
||||
pub const breaking_changes_1_3 = false;
|
||||
pub const breaking_changes_1_4 = false;
|
||||
|
||||
/// Store and reuse file descriptors during module resolution
|
||||
/// This was a ~5% performance improvement
|
||||
|
||||
42
test/js/bun/test/concurrent-and-serial.fixture.ts
Normal file
42
test/js/bun/test/concurrent-and-serial.fixture.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { test, describe, beforeEach } from "bun:test";
|
||||
|
||||
let activeGroup: (() => void)[] = [];
|
||||
function tick() {
|
||||
const { resolve, reject, promise } = Promise.withResolvers();
|
||||
activeGroup.push(() => resolve());
|
||||
setTimeout(() => {
|
||||
activeGroup.shift()?.();
|
||||
}, 0);
|
||||
return promise;
|
||||
}
|
||||
|
||||
test("test default-1", async () => {
|
||||
console.log("[0] start test default-1");
|
||||
await tick();
|
||||
console.log("[1] end test default-1");
|
||||
});
|
||||
test("test default-2", async () => {
|
||||
console.log("[0] start test default-2");
|
||||
await tick();
|
||||
console.log("[1] end test default-2");
|
||||
});
|
||||
test.concurrent("test concurrent-1", async () => {
|
||||
console.log("[0] start test concurrent-1");
|
||||
await tick();
|
||||
console.log("[1] end test concurrent-1");
|
||||
});
|
||||
test.concurrent("test concurrent-2", async () => {
|
||||
console.log("[0] start test concurrent-2");
|
||||
await tick();
|
||||
console.log("[1] end test concurrent-2");
|
||||
});
|
||||
test.serial("test serial-1", async () => {
|
||||
console.log("[0] start test serial-1");
|
||||
await tick();
|
||||
console.log("[1] end test serial-1");
|
||||
});
|
||||
test.serial("test serial-2", async () => {
|
||||
console.log("[0] start test serial-2");
|
||||
await tick();
|
||||
console.log("[1] end test serial-2");
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, normalizeBunSnapshot } from "harness";
|
||||
|
||||
test("concurrent order", async () => {
|
||||
test.concurrent("concurrent order", async () => {
|
||||
const result = await Bun.spawn({
|
||||
cmd: [bunExe(), "test", import.meta.dir + "/concurrent.fixture.ts"],
|
||||
stdout: "pipe",
|
||||
@@ -58,7 +58,63 @@ test("concurrent order", async () => {
|
||||
`);
|
||||
});
|
||||
|
||||
test("max-concurrency limits concurrent tests", async () => {
|
||||
test.concurrent("concurrent-and-serial --concurrent", async () => {
|
||||
const result = await Bun.spawn({
|
||||
cmd: [bunExe(), "test", import.meta.dir + "/concurrent-and-serial.fixture.ts", "--concurrent"],
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: bunEnv,
|
||||
});
|
||||
const exitCode = await result.exited;
|
||||
const stdout = await result.stdout.text();
|
||||
const stderr = await result.stderr.text();
|
||||
expect(exitCode).toBe(0);
|
||||
expect(normalizeBunSnapshot(stdout)).toMatchInlineSnapshot(`
|
||||
"bun test <version> (<revision>)
|
||||
[0] start test default-1
|
||||
[0] start test default-2
|
||||
[0] start test concurrent-1
|
||||
[0] start test concurrent-2
|
||||
[1] end test default-1
|
||||
[1] end test default-2
|
||||
[1] end test concurrent-1
|
||||
[1] end test concurrent-2
|
||||
[0] start test serial-1
|
||||
[1] end test serial-1
|
||||
[0] start test serial-2
|
||||
[1] end test serial-2"
|
||||
`);
|
||||
});
|
||||
|
||||
test.concurrent("concurrent-and-serial, no flag", async () => {
|
||||
const result = await Bun.spawn({
|
||||
cmd: [bunExe(), "test", import.meta.dir + "/concurrent-and-serial.fixture.ts"],
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: bunEnv,
|
||||
});
|
||||
const exitCode = await result.exited;
|
||||
const stdout = await result.stdout.text();
|
||||
const stderr = await result.stderr.text();
|
||||
expect(exitCode).toBe(0);
|
||||
expect(normalizeBunSnapshot(stdout)).toMatchInlineSnapshot(`
|
||||
"bun test <version> (<revision>)
|
||||
[0] start test default-1
|
||||
[1] end test default-1
|
||||
[0] start test default-2
|
||||
[1] end test default-2
|
||||
[0] start test concurrent-1
|
||||
[0] start test concurrent-2
|
||||
[1] end test concurrent-1
|
||||
[1] end test concurrent-2
|
||||
[0] start test serial-1
|
||||
[1] end test serial-1
|
||||
[0] start test serial-2
|
||||
[1] end test serial-2"
|
||||
`);
|
||||
});
|
||||
|
||||
test.concurrent("max-concurrency limits concurrent tests", async () => {
|
||||
// Test with max-concurrency=3
|
||||
const result = await Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--max-concurrency", "3", import.meta.dir + "/concurrent-max.fixture.ts"],
|
||||
@@ -81,7 +137,7 @@ test("max-concurrency limits concurrent tests", async () => {
|
||||
expect(executionPattern).toEqual(expected);
|
||||
});
|
||||
|
||||
test("max-concurrency default is 20", async () => {
|
||||
test.concurrent("max-concurrency default is 20", async () => {
|
||||
const result = await Bun.spawn({
|
||||
cmd: [bunExe(), "test", import.meta.dir + "/concurrent-max.fixture.ts"],
|
||||
stdout: "pipe",
|
||||
@@ -103,7 +159,7 @@ test("max-concurrency default is 20", async () => {
|
||||
expect(executionPattern).toEqual(expected);
|
||||
});
|
||||
|
||||
test("zero removes max-concurrency", async () => {
|
||||
test.concurrent("zero removes max-concurrency", async () => {
|
||||
const result = await Bun.spawn({
|
||||
cmd: [bunExe(), "test", "--max-concurrency", "0", import.meta.dir + "/concurrent-max.fixture.ts"],
|
||||
stdout: "pipe",
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
// Test that test.serial() is available and works
|
||||
test("test.serial is a function", () => {
|
||||
expect(typeof test.serial).toBe("function");
|
||||
});
|
||||
|
||||
test("test.serial.if is a function", () => {
|
||||
expect(typeof test.serial.if).toBe("function");
|
||||
});
|
||||
|
||||
test("test.serial.skip is a function", () => {
|
||||
expect(typeof test.serial.skip).toBe("function");
|
||||
});
|
||||
|
||||
test("test.serial.todo is a function", () => {
|
||||
expect(typeof test.serial.todo).toBe("function");
|
||||
});
|
||||
|
||||
test("test.serial.each is a function", () => {
|
||||
expect(typeof test.serial.each).toBe("function");
|
||||
});
|
||||
|
||||
test("test.serial.only is a function", () => {
|
||||
expect(typeof test.serial.only).toBe("function");
|
||||
});
|
||||
|
||||
// Test describe.serial
|
||||
test("describe.serial is a function", () => {
|
||||
expect(typeof describe.serial).toBe("function");
|
||||
});
|
||||
|
||||
// Test serialIf function
|
||||
test("test.serial.if() works correctly", () => {
|
||||
const serialIf = test.serial.if(true);
|
||||
expect(typeof serialIf).toBe("function");
|
||||
|
||||
const notSerial = test.serial.if(false);
|
||||
expect(typeof notSerial).toBe("function");
|
||||
});
|
||||
|
||||
// Functional tests for serial execution
|
||||
let serialTestCounter = 0;
|
||||
const serialResults: number[] = [];
|
||||
|
||||
test.serial("serial execution test 1", async () => {
|
||||
const myIndex = serialTestCounter++;
|
||||
serialResults.push(myIndex);
|
||||
await Bun.sleep(10);
|
||||
expect(myIndex).toBe(0);
|
||||
});
|
||||
|
||||
test.serial("serial execution test 2", async () => {
|
||||
const myIndex = serialTestCounter++;
|
||||
serialResults.push(myIndex);
|
||||
await Bun.sleep(10);
|
||||
expect(myIndex).toBe(1);
|
||||
});
|
||||
|
||||
test.serial("serial execution test 3", async () => {
|
||||
const myIndex = serialTestCounter++;
|
||||
serialResults.push(myIndex);
|
||||
await Bun.sleep(10);
|
||||
expect(myIndex).toBe(2);
|
||||
});
|
||||
|
||||
// Verify serial execution happened
|
||||
test("verify serial execution order", () => {
|
||||
expect(serialResults).toEqual([0, 1, 2]);
|
||||
});
|
||||
|
||||
// Test describe.serial functionality
|
||||
describe.serial("serial describe block", () => {
|
||||
let describeCounter = 0;
|
||||
const describeResults: number[] = [];
|
||||
|
||||
test("nested test 1", async () => {
|
||||
const myIndex = describeCounter++;
|
||||
describeResults.push(myIndex);
|
||||
await Bun.sleep(10);
|
||||
expect(myIndex).toBe(0);
|
||||
});
|
||||
|
||||
test("nested test 2", async () => {
|
||||
const myIndex = describeCounter++;
|
||||
describeResults.push(myIndex);
|
||||
await Bun.sleep(10);
|
||||
expect(myIndex).toBe(1);
|
||||
});
|
||||
|
||||
test("verify nested serial execution", () => {
|
||||
expect(describeResults).toEqual([0, 1]);
|
||||
});
|
||||
});
|
||||
|
||||
// Test test.serial.each functionality
|
||||
const testCases = [
|
||||
[1, 2, 3],
|
||||
[4, 5, 9],
|
||||
[10, 20, 30],
|
||||
];
|
||||
let eachCounter = 0;
|
||||
|
||||
test.serial.each(testCases)("serial.each test %#", (a, b, expected) => {
|
||||
const myIndex = eachCounter++;
|
||||
expect(a + b).toBe(expected);
|
||||
// These should run serially, so counter should increment predictably
|
||||
expect(myIndex).toBeLessThan(3);
|
||||
});
|
||||
|
||||
// Test mixing serial and concurrent in same describe block
|
||||
describe("mixing serial and concurrent tests", () => {
|
||||
let mixedCounter = 0;
|
||||
const mixedResults: { type: string; index: number; startTime: number }[] = [];
|
||||
const startTime = Date.now();
|
||||
|
||||
test.serial("mixed serial 1", async () => {
|
||||
const myIndex = mixedCounter++;
|
||||
mixedResults.push({ type: "serial", index: myIndex, startTime: Date.now() - startTime });
|
||||
await Bun.sleep(20);
|
||||
});
|
||||
|
||||
test.concurrent("mixed concurrent 1", async () => {
|
||||
const myIndex = mixedCounter++;
|
||||
mixedResults.push({ type: "concurrent", index: myIndex, startTime: Date.now() - startTime });
|
||||
await Bun.sleep(20);
|
||||
});
|
||||
|
||||
test.concurrent("mixed concurrent 2", async () => {
|
||||
const myIndex = mixedCounter++;
|
||||
mixedResults.push({ type: "concurrent", index: myIndex, startTime: Date.now() - startTime });
|
||||
await Bun.sleep(20);
|
||||
});
|
||||
|
||||
test.serial("mixed serial 2", async () => {
|
||||
const myIndex = mixedCounter++;
|
||||
mixedResults.push({ type: "serial", index: myIndex, startTime: Date.now() - startTime });
|
||||
await Bun.sleep(20);
|
||||
});
|
||||
|
||||
test("verify mixed execution", () => {
|
||||
// Serial tests should not overlap with each other
|
||||
const serialTests = mixedResults.filter(r => r.type === "serial");
|
||||
for (let i = 1; i < serialTests.length; i++) {
|
||||
// Each serial test should start after the previous one (with at least 15ms gap for 20ms sleep)
|
||||
const gap = serialTests[i].startTime - serialTests[i - 1].startTime;
|
||||
expect(gap).toBeGreaterThanOrEqual(15);
|
||||
}
|
||||
|
||||
// Concurrent tests might overlap (their start times should be close)
|
||||
const concurrentTests = mixedResults.filter(r => r.type === "concurrent");
|
||||
if (concurrentTests.length > 1) {
|
||||
const gap = concurrentTests[1].startTime - concurrentTests[0].startTime;
|
||||
// Concurrent tests should start within a few ms of each other
|
||||
expect(gap).toBeLessThan(10);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Test nested describe blocks with conflicting settings
|
||||
describe.concurrent("concurrent parent describe", () => {
|
||||
let parentCounter = 0;
|
||||
const parentResults: { block: string; index: number }[] = [];
|
||||
|
||||
test("parent test 1", async () => {
|
||||
const myIndex = parentCounter++;
|
||||
parentResults.push({ block: "parent", index: myIndex });
|
||||
await Bun.sleep(10);
|
||||
});
|
||||
|
||||
describe.serial("nested serial describe", () => {
|
||||
let nestedCounter = 0;
|
||||
|
||||
test("nested serial 1", async () => {
|
||||
const myIndex = nestedCounter++;
|
||||
parentResults.push({ block: "nested-serial", index: myIndex });
|
||||
await Bun.sleep(10);
|
||||
expect(myIndex).toBe(0);
|
||||
});
|
||||
|
||||
test("nested serial 2", async () => {
|
||||
const myIndex = nestedCounter++;
|
||||
parentResults.push({ block: "nested-serial", index: myIndex });
|
||||
await Bun.sleep(10);
|
||||
expect(myIndex).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
test("parent test 2", async () => {
|
||||
const myIndex = parentCounter++;
|
||||
parentResults.push({ block: "parent", index: myIndex });
|
||||
await Bun.sleep(10);
|
||||
});
|
||||
|
||||
test("verify nested behavior", () => {
|
||||
// Tests in the nested serial block should run serially
|
||||
const nestedSerial = parentResults.filter(r => r.block === "nested-serial");
|
||||
expect(nestedSerial[0].index).toBe(0);
|
||||
expect(nestedSerial[1].index).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// Test explicit serial overrides concurrent describe
|
||||
describe.concurrent("concurrent describe with explicit serial", () => {
|
||||
let overrideCounter = 0;
|
||||
const overrideResults: number[] = [];
|
||||
|
||||
test.serial("explicit serial in concurrent describe 1", async () => {
|
||||
const myIndex = overrideCounter++;
|
||||
overrideResults.push(myIndex);
|
||||
await Bun.sleep(10);
|
||||
expect(myIndex).toBe(0);
|
||||
});
|
||||
|
||||
test.serial("explicit serial in concurrent describe 2", async () => {
|
||||
const myIndex = overrideCounter++;
|
||||
overrideResults.push(myIndex);
|
||||
await Bun.sleep(10);
|
||||
expect(myIndex).toBe(1);
|
||||
});
|
||||
|
||||
test("regular test in concurrent describe", async () => {
|
||||
const myIndex = overrideCounter++;
|
||||
overrideResults.push(myIndex);
|
||||
await Bun.sleep(10);
|
||||
});
|
||||
|
||||
test("verify override behavior", () => {
|
||||
// First two tests should have run serially
|
||||
expect(overrideResults[0]).toBe(0);
|
||||
expect(overrideResults[1]).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// Test explicit concurrent overrides serial describe
|
||||
describe.serial("serial describe with explicit concurrent", () => {
|
||||
let overrideCounter2 = 0;
|
||||
let maxConcurrent2 = 0;
|
||||
let currentlyRunning2 = 0;
|
||||
|
||||
test.concurrent("explicit concurrent in serial describe 1", async () => {
|
||||
currentlyRunning2++;
|
||||
maxConcurrent2 = Math.max(maxConcurrent2, currentlyRunning2);
|
||||
overrideCounter2++;
|
||||
await Bun.sleep(10);
|
||||
currentlyRunning2--;
|
||||
});
|
||||
|
||||
test.concurrent("explicit concurrent in serial describe 2", async () => {
|
||||
currentlyRunning2++;
|
||||
maxConcurrent2 = Math.max(maxConcurrent2, currentlyRunning2);
|
||||
overrideCounter2++;
|
||||
await Bun.sleep(10);
|
||||
currentlyRunning2--;
|
||||
});
|
||||
|
||||
test("regular test in serial describe", async () => {
|
||||
overrideCounter2++;
|
||||
await Bun.sleep(10);
|
||||
});
|
||||
|
||||
test("verify concurrent override in serial describe", () => {
|
||||
// The concurrent tests should have run in parallel even in a serial describe
|
||||
if (typeof maxConcurrent2 === "number") {
|
||||
// This might be 1 if tests ran too fast, but structure is correct
|
||||
expect(maxConcurrent2).toBeGreaterThanOrEqual(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,7 @@ test("14135", async () => {
|
||||
cmd: [bunExe(), "test", import.meta.dir + "/14135.fixture.ts"],
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: bunEnv,
|
||||
env: { ...bunEnv, CI: "false" }, // tests '.only()'
|
||||
});
|
||||
const exitCode = await result.exited;
|
||||
const stdout = await result.stdout.text();
|
||||
|
||||
@@ -6,7 +6,7 @@ test("19875", async () => {
|
||||
cmd: [bunExe(), "test", import.meta.dir + "/19875.fixture.ts"],
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: bunEnv,
|
||||
env: { ...bunEnv, CI: "false" }, // tests '.only()'
|
||||
});
|
||||
const exitCode = await result.exited;
|
||||
const stdout = await result.stdout.text();
|
||||
|
||||
@@ -6,7 +6,7 @@ test("20092", async () => {
|
||||
cmd: [bunExe(), "test", import.meta.dir + "/20092.fixture.ts"],
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: bunEnv,
|
||||
env: { ...bunEnv, CI: "false" }, // tests '.only()'
|
||||
});
|
||||
const exitCode = await result.exited;
|
||||
const stdout = await result.stdout.text();
|
||||
|
||||
@@ -6,7 +6,7 @@ test("5961", async () => {
|
||||
cmd: [bunExe(), "test", import.meta.dir + "/5961.fixture.ts"],
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: bunEnv,
|
||||
env: { ...bunEnv, CI: "false" }, // tests '.only()'
|
||||
});
|
||||
const exitCode = await result.exited;
|
||||
const stdout = await result.stdout.text();
|
||||
|
||||
Reference in New Issue
Block a user