feat: update version to 1.3.2, add regression tests for RR scheduling with threads
This commit is contained in:
1 parent
44ee62e2c2
commit
2ee3eeb2b6
3 files changed
+247
-1
No files matched your search
BIN
Binary file not shown.
+1
-1
@@ -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": {
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
Reference in new issue
Block a user