mirror of
https://github.com/YuzuZensai/Termix.git
synced 2026-11-12 17:11:48 +00:00
163 lines
4.7 KiB
TypeScript
163 lines
4.7 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|||
|
|
import {
|
||
|
|
computeAdaptiveResourceBudget,
|
||
|
|
markAdaptiveResourceUsed,
|
||
|
|
resetAdaptiveResourceState,
|
||
|
|
runAdaptiveBackgroundTask,
|
||
|
|
} from "../../lib/adaptive-resource-budget";
|
||
|
|
import {
|
||
|
|
clearLocalAdaptiveEngine,
|
||
|
|
getLocalAdaptiveStats,
|
||
|
|
} from "../../lib/local-adaptive-engine";
|
||
|
|
|
||
|
|
describe("adaptive resource budget", () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
clearLocalAdaptiveEngine();
|
||
|
|
resetAdaptiveResourceState();
|
||
|
|
// jsdom reports the host's real core count, so the tier the runtime picks
|
||
|
|
// would otherwise depend on the machine (CI runners have 4 cores).
|
||
|
|
Object.defineProperty(navigator, "hardwareConcurrency", {
|
||
|
|
value: 8,
|
||
|
|
configurable: true,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("uses hard environmental constraints before optional work", () => {
|
||
|
|
expect(computeAdaptiveResourceBudget({ visible: false }).tier).toBe(
|
||
|
|
"paused",
|
||
|
|
);
|
||
|
|
expect(
|
||
|
|
computeAdaptiveResourceBudget({ visible: true, saveData: true }).tier,
|
||
|
|
).toBe("constrained");
|
||
|
|
expect(
|
||
|
|
computeAdaptiveResourceBudget({ visible: true, effectiveType: "3g" })
|
||
|
|
.allowNetworkPrefetch,
|
||
|
|
).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("scales eager work down on limited devices", () => {
|
||
|
|
const eager = computeAdaptiveResourceBudget({
|
||
|
|
visible: true,
|
||
|
|
deviceMemoryGb: 8,
|
||
|
|
hardwareConcurrency: 8,
|
||
|
|
});
|
||
|
|
expect(eager).toMatchObject({
|
||
|
|
tier: "eager",
|
||
|
|
maxPrefetchBytes: 512 * 1024,
|
||
|
|
maxConcurrentNetworkPrefetches: 2,
|
||
|
|
});
|
||
|
|
|
||
|
|
const balanced = computeAdaptiveResourceBudget({
|
||
|
|
visible: true,
|
||
|
|
deviceMemoryGb: 4,
|
||
|
|
hardwareConcurrency: 8,
|
||
|
|
});
|
||
|
|
expect(balanced).toMatchObject({
|
||
|
|
tier: "balanced",
|
||
|
|
maxPrefetchBytes: 128 * 1024,
|
||
|
|
maxConcurrentNetworkPrefetches: 1,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("requires enough poor local feedback before degrading", () => {
|
||
|
|
const environment = { visible: true, deviceMemoryGb: 8 };
|
||
|
|
expect(
|
||
|
|
computeAdaptiveResourceBudget(environment, {
|
||
|
|
observations: 3,
|
||
|
|
successes: 0,
|
||
|
|
cancellations: 0,
|
||
|
|
fallbacks: 0,
|
||
|
|
}).tier,
|
||
|
|
).toBe("eager");
|
||
|
|
expect(
|
||
|
|
computeAdaptiveResourceBudget(environment, {
|
||
|
|
observations: 4,
|
||
|
|
successes: 2,
|
||
|
|
cancellations: 0,
|
||
|
|
fallbacks: 0,
|
||
|
|
}),
|
||
|
|
).toMatchObject({ tier: "balanced", reason: "feedback" });
|
||
|
|
});
|
||
|
|
|
||
|
|
it("degrades only after enough speculative work goes unused", () => {
|
||
|
|
const environment = { visible: true, deviceMemoryGb: 8 };
|
||
|
|
expect(
|
||
|
|
computeAdaptiveResourceBudget(environment, {
|
||
|
|
observations: 4,
|
||
|
|
successes: 4,
|
||
|
|
cancellations: 0,
|
||
|
|
fallbacks: 0,
|
||
|
|
usefulnessObservations: 3,
|
||
|
|
usefulPreloads: 0,
|
||
|
|
}).tier,
|
||
|
|
).toBe("eager");
|
||
|
|
expect(
|
||
|
|
computeAdaptiveResourceBudget(environment, {
|
||
|
|
observations: 4,
|
||
|
|
successes: 4,
|
||
|
|
cancellations: 0,
|
||
|
|
fallbacks: 0,
|
||
|
|
usefulnessObservations: 4,
|
||
|
|
usefulPreloads: 0,
|
||
|
|
}),
|
||
|
|
).toMatchObject({ tier: "balanced", reason: "feedback" });
|
||
|
|
});
|
||
|
|
|
||
|
|
it("bounds concurrency and records aggregate task outcomes", async () => {
|
||
|
|
let releaseFirst!: () => void;
|
||
|
|
let releaseSecond!: () => void;
|
||
|
|
const first = new Promise<void>((resolve) => (releaseFirst = resolve));
|
||
|
|
const second = new Promise<void>((resolve) => (releaseSecond = resolve));
|
||
|
|
|
||
|
|
expect(
|
||
|
|
runAdaptiveBackgroundTask("module", "tab:terminal", () => first),
|
||
|
|
).toBe(true);
|
||
|
|
expect(runAdaptiveBackgroundTask("module", "tab:files", () => second)).toBe(
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
expect(
|
||
|
|
runAdaptiveBackgroundTask("module", "tab:docker", async () => {}),
|
||
|
|
).toBe(false);
|
||
|
|
expect(
|
||
|
|
runAdaptiveBackgroundTask(
|
||
|
|
"network",
|
||
|
|
"file-content:large",
|
||
|
|
async () => {},
|
||
|
|
{
|
||
|
|
estimatedBytes: 512 * 1024 + 1,
|
||
|
|
},
|
||
|
|
),
|
||
|
|
).toBe(false);
|
||
|
|
|
||
|
|
releaseFirst();
|
||
|
|
releaseSecond();
|
||
|
|
await vi.waitFor(() => {
|
||
|
|
expect(
|
||
|
|
getLocalAdaptiveStats("resource-budget:module")["tab:terminal"]
|
||
|
|
.successes,
|
||
|
|
).toBe(1);
|
||
|
|
expect(
|
||
|
|
getLocalAdaptiveStats("resource-budget:module")["tab:files"].successes,
|
||
|
|
).toBe(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
// A finished task must free its slot no later than it records its outcome.
|
||
|
|
expect(
|
||
|
|
runAdaptiveBackgroundTask("module", "tab:docker", async () => {}),
|
||
|
|
).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("records whether foreground navigation used a preload", async () => {
|
||
|
|
expect(
|
||
|
|
runAdaptiveBackgroundTask("module", "tab:files", async () => {}),
|
||
|
|
).toBe(true);
|
||
|
|
markAdaptiveResourceUsed("module", "tab:files");
|
||
|
|
|
||
|
|
await vi.waitFor(() => {
|
||
|
|
expect(
|
||
|
|
getLocalAdaptiveStats("resource-usefulness:module")["tab:files"],
|
||
|
|
).toMatchObject({ observations: 1, successes: 1 });
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|