feat: update version to 1.3.2, add regression tests for RR scheduling with threads

This commit is contained in:
Lucas Rayan committed 2026-03-25 10:04:52 -03:00
1 parent 44ee62e2c2
commit 2ee3eeb2b6
3 files changed
+247 -1

No files matched your search

BIN
View File
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pss",
"version": "1.3.1",
"version": "1.3.2",
"description": "Process Scheduler Simulator — an interactive educational tool that runs entirely in the browser",
"main": "index.js",
"scripts": {
+246
View File
@@ -412,6 +412,252 @@ describe("Engine — RR intra-processo", () => {
});
});
// ── Quantum de processo com threads ────────────────────────────────────────
//
// Testa o comportamento correto quando o burst de uma thread termina
// exatamente no mesmo tick em que o quantum do processo expira.
// Antes do fix, o processo recebia quantum+1 ticks (bug).
describe("Engine — quantum de processo com threads (regressão)", () => {
const rrConfig: SchedulerConfig = {
algorithm: "RR",
quantum: 2,
contextSwitchTime: 0,
isPreemptive: false,
agingEnabled: false,
agingInterval: 5,
};
const priorityRrConfig: SchedulerConfig = {
algorithm: "PRIORITY_RR",
quantum: 2,
contextSwitchTime: 0,
isPreemptive: false,
agingEnabled: false,
agingInterval: 5,
};
it("RR: segundo processo recebe CPU no tick 2 quando burst da thread = quantum", () => {
// P1: thread T1 com CPU=2 (= quantum). Ao fim do tick 1, T1 termina E
// o quantum expira ao mesmo tempo. P1 deve ceder a CPU imediatamente.
// P2 deve começar no tick 2, não no tick 3.
const p1: Process = {
pid: 1,
id: "p1",
name: "P1",
arrivalTime: 0,
priority: 5,
color: "#4FC3F7",
bursts: [],
threadModel: "ONE_TO_ONE",
threadSchedulingPolicy: "FCFS",
threads: [
{ tid: "t1", name: "T1", bursts: [{ type: "cpu", duration: 2 }] },
{ tid: "t2", name: "T2", bursts: [{ type: "cpu", duration: 2 }] },
],
};
const p2: Process = {
pid: 2,
id: "p2",
name: "P2",
arrivalTime: 0,
priority: 5,
color: "#81C784",
bursts: [{ type: "cpu", duration: 3 }],
};
const engine = new SimulationEngine([p1, p2], rrConfig);
const ticks = engine.runAll();
const cpu = ticks.map((t) => t.cpuProcess);
// Tick 0 e 1: P1 roda (T1, burst=2=quantum)
expect(cpu[0]).toBe("p1");
expect(cpu[1]).toBe("p1");
// Tick 2: P2 deve receber CPU (não tick 3)
expect(cpu[2]).toBe("p2");
const rt2 = engine.runtimeStates.find((r) => r.processId === "p2")!;
expect(rt2.startTick).toBe(2);
});
it("RR: processo alterna exatamente a cada quantum quando bursts coincidem com quantum", () => {
// Ambos os processos têm threads com CPU=2 = quantum.
// Devem alternar estritamente: P1(0-1), P2(2-3), P1(4-5), P2(6-7)...
const makeThreaded = (id: string, pid: number, color: string): Process => ({
pid,
id,
name: id,
arrivalTime: 0,
priority: 5,
color,
bursts: [],
threadModel: "ONE_TO_ONE" as const,
threadSchedulingPolicy: "FCFS" as const,
threads: [
{ tid: `${id}_t1`, name: "T1", bursts: [{ type: "cpu" as const, duration: 2 }] },
{ tid: `${id}_t2`, name: "T2", bursts: [{ type: "cpu" as const, duration: 2 }] },
],
});
const engine = new SimulationEngine(
[makeThreaded("p1", 1, "#4FC3F7"), makeThreaded("p2", 2, "#81C784")],
rrConfig,
);
const ticks = engine.runAll();
const cpu = ticks.map((t) => t.cpuProcess);
// Cada bloco de 2 ticks deve pertencer a um processo diferente
expect(cpu[0]).toBe("p1");
expect(cpu[1]).toBe("p1");
expect(cpu[2]).toBe("p2");
expect(cpu[3]).toBe("p2");
expect(cpu[4]).toBe("p1");
expect(cpu[5]).toBe("p1");
expect(cpu[6]).toBe("p2");
expect(cpu[7]).toBe("p2");
});
it("PRIORITY_RR: reproduz o cenário reportado — P2.startTick deve ser 2", () => {
// Replica o config.json do usuário (versão simplificada):
// App: ONE_TO_ONE, PRIORITY, High-1(prio=1, cpu=2), High-2(prio=2, cpu=2)
// P2: ONE_TO_ONE, FCFS, T1(cpu=3)
// Algoritmo: PRIORITY_RR, quantum=2, ambos prio=1
// Bug: App rodava ticks 0,1,2 (quantum+1). Correto: 0,1 → P2 começa no tick 2.
const app: Process = {
pid: 1,
id: "app",
name: "App",
arrivalTime: 0,
priority: 1,
color: "#F06292",
bursts: [],
threadModel: "ONE_TO_ONE",
threadSchedulingPolicy: "PRIORITY",
threads: [
{ tid: "h1", name: "High-1", priority: 1, bursts: [{ type: "cpu", duration: 2 }] },
{ tid: "h2", name: "High-2", priority: 2, bursts: [{ type: "cpu", duration: 2 }] },
],
};
const p2: Process = {
pid: 2,
id: "p2",
name: "P2",
arrivalTime: 0,
priority: 1,
color: "#f472b6",
bursts: [],
threadModel: "ONE_TO_ONE",
threadSchedulingPolicy: "FCFS",
threads: [
{ tid: "p2t1", name: "T1", bursts: [{ type: "cpu", duration: 3 }] },
],
};
const engine = new SimulationEngine([app, p2], priorityRrConfig);
const ticks = engine.runAll();
const cpu = ticks.map((t) => t.cpuProcess);
// App roda ticks 0 e 1 (quantum=2). P2 deve começar no tick 2.
expect(cpu[0]).toBe("app");
expect(cpu[1]).toBe("app");
expect(cpu[2]).toBe("p2");
const p2Rt = engine.runtimeStates.find((r) => r.processId === "p2")!;
expect(p2Rt.startTick).toBe(2);
// responseTime de threads é calculado em _dispatchThreadForProcess
const p2ThreadRts = engine.threadRuntimeStates.filter((t) => t.processId === "p2");
expect(p2ThreadRts[0]!.startTick).toBe(2);
expect(p2ThreadRts[0]!.responseTime).toBe(2);
});
it("thread com burst menor que quantum não causa preempção prematura", () => {
// T1 tem burst=1 (< quantum=2). T1 termina no tick 1 sem quantum expirado.
// T2 deve continuar rodando no tick 2 (dentro do mesmo quantum).
// P2 só recebe CPU no tick 3 (quando quantum do processo expirar com T2).
const p1: Process = {
pid: 1,
id: "p1",
name: "P1",
arrivalTime: 0,
priority: 5,
color: "#4FC3F7",
bursts: [],
threadModel: "ONE_TO_ONE",
threadSchedulingPolicy: "FCFS",
threads: [
{ tid: "t1", name: "T1", bursts: [{ type: "cpu", duration: 1 }] },
{ tid: "t2", name: "T2", bursts: [{ type: "cpu", duration: 3 }] },
],
};
const p2: Process = {
pid: 2,
id: "p2",
name: "P2",
arrivalTime: 0,
priority: 5,
color: "#81C784",
bursts: [{ type: "cpu", duration: 2 }],
};
const engine = new SimulationEngine([p1, p2], rrConfig);
const ticks = engine.runAll();
const cpu = ticks.map((t) => t.cpuProcess);
// Tick 0: P1/T1 roda (burst=1, quantum=2 → quantum não expirado)
expect(cpu[0]).toBe("p1");
// Tick 1: P1/T2 assume (quantum restante=1)
expect(cpu[1]).toBe("p1");
// Tick 2: quantum do P1 expira → P2 recebe CPU
expect(cpu[2]).toBe("p2");
});
it("processo com thread em I/O no momento do quantum cede corretamente", () => {
// T1: CPU(2) → IO(2) → CPU(1). Quantum=2.
// T1 termina burst CPU no tick 1 E quantum=0: vai para IO, processo cede.
// P2 deve receber CPU no tick 2.
const p1: Process = {
pid: 1,
id: "p1",
name: "P1",
arrivalTime: 0,
priority: 5,
color: "#4FC3F7",
bursts: [],
threadModel: "ONE_TO_ONE",
threadSchedulingPolicy: "FCFS",
threads: [
{
tid: "t1",
name: "T1",
bursts: [
{ type: "cpu", duration: 2 },
{ type: "io", duration: 2 },
{ type: "cpu", duration: 1 },
],
},
],
};
const p2: Process = {
pid: 2,
id: "p2",
name: "P2",
arrivalTime: 0,
priority: 5,
color: "#81C784",
bursts: [{ type: "cpu", duration: 3 }],
};
const engine = new SimulationEngine([p1, p2], rrConfig);
const ticks = engine.runAll();
const cpu = ticks.map((t) => t.cpuProcess);
expect(cpu[0]).toBe("p1");
expect(cpu[1]).toBe("p1");
// Tick 2: T1 foi para IO e quantum expirou → P2 recebe CPU
expect(cpu[2]).toBe("p2");
});
});
// ── Política intra-processo PRIORITY ──────────────────────────────────────
describe("Engine — Priority intra-processo", () => {