feat: enhance context switch handling in Gantt chart and add related tests
This commit is contained in:
1 parent
9f0a05db62
commit
64bbc4ca27
10 files changed
+135
-14
No files matched your search
@@ -21,9 +21,9 @@ const GanttRow = React.memo(function GanttRow({
|
||||
<div className="flex" style={{ height: CELL_H }}>
|
||||
{ticks.map((tick) => {
|
||||
const state: StateLabel = tick.states[process.id] ?? "New";
|
||||
// A context-switch tick affects the process currently on CPU
|
||||
// A context-switch tick is shown on the incoming (next-to-run) process
|
||||
const isCtxSwitch =
|
||||
tick.contextSwitching && tick.cpuProcess === process.id;
|
||||
tick.contextSwitching && tick.ctxSwitchForProcess === process.id;
|
||||
|
||||
return (
|
||||
<GanttCell
|
||||
|
||||
@@ -20,6 +20,7 @@ describe("GanttCell", () => {
|
||||
readyQueue: [],
|
||||
states: { p1: "Running" },
|
||||
contextSwitching: false,
|
||||
ctxSwitchForProcess: null,
|
||||
};
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render } from "@testing-library/react";
|
||||
import GanttRow from "../GanttRow";
|
||||
import type { Process, SimTick } from "@core/types";
|
||||
|
||||
describe("GanttRow – isCtxSwitch propagation", () => {
|
||||
const p1: Process = {
|
||||
id: "p1", name: "P1", arrivalTime: 0, priority: 5,
|
||||
color: "#4FC3F7", bursts: [{ type: "cpu", duration: 2 }],
|
||||
};
|
||||
const p2: Process = {
|
||||
id: "p2", name: "P2", arrivalTime: 0, priority: 5,
|
||||
color: "#FF7043", bursts: [{ type: "cpu", duration: 2 }],
|
||||
};
|
||||
|
||||
const ctxTick: SimTick = {
|
||||
tick: 2,
|
||||
cpuProcess: null,
|
||||
ioProcesses: [],
|
||||
readyQueue: ["p2"],
|
||||
states: { p1: "Ready", p2: "Ready" },
|
||||
contextSwitching: true,
|
||||
ctxSwitchForProcess: "p2",
|
||||
};
|
||||
|
||||
const noop = vi.fn();
|
||||
|
||||
it("renders the context-switch color on the incoming process (p2)", () => {
|
||||
const { container } = render(
|
||||
<GanttRow
|
||||
process={p2}
|
||||
ticks={[ctxTick]}
|
||||
onCellMouseEnter={noop}
|
||||
onCellMouseLeave={noop}
|
||||
/>
|
||||
);
|
||||
// The outer div is the row; its first child is the GanttCell div
|
||||
const cell = container.firstChild?.firstChild as HTMLElement;
|
||||
expect(cell.className).toContain("bg-[var(--color-state-ctx)]");
|
||||
});
|
||||
|
||||
it("does NOT render context-switch color on the outgoing process (p1)", () => {
|
||||
const { container } = render(
|
||||
<GanttRow
|
||||
process={p1}
|
||||
ticks={[ctxTick]}
|
||||
onCellMouseEnter={noop}
|
||||
onCellMouseLeave={noop}
|
||||
/>
|
||||
);
|
||||
const cell = container.firstChild?.firstChild as HTMLElement;
|
||||
expect(cell.className).not.toContain("bg-[var(--color-state-ctx)]");
|
||||
});
|
||||
|
||||
it("does not mark ctx-switch when contextSwitching is false", () => {
|
||||
const normalTick: SimTick = {
|
||||
tick: 0,
|
||||
cpuProcess: "p1",
|
||||
ioProcesses: [],
|
||||
readyQueue: [],
|
||||
states: { p1: "Running", p2: "Ready" },
|
||||
contextSwitching: false,
|
||||
ctxSwitchForProcess: null,
|
||||
};
|
||||
const { container } = render(
|
||||
<GanttRow
|
||||
process={p1}
|
||||
ticks={[normalTick]}
|
||||
onCellMouseEnter={noop}
|
||||
onCellMouseLeave={noop}
|
||||
/>
|
||||
);
|
||||
const cell = container.firstChild?.firstChild as HTMLElement;
|
||||
expect(cell.className).not.toContain("bg-[var(--color-state-ctx)]");
|
||||
expect(cell.className).toContain("bg-[var(--color-state-running)]");
|
||||
});
|
||||
});
|
||||
@@ -55,7 +55,7 @@ const AppHeader = () => {
|
||||
return (
|
||||
<header className="h-12 flex items-center px-5 gap-4 bg-slate-900 border-b border-slate-700/60 shrink-0 z-30">
|
||||
{/* Title */}
|
||||
<h1 className="text-sm font-semibold bg-gradient-to-r from-blue-400 to-indigo-400 bg-clip-text text-transparent whitespace-nowrap">
|
||||
<h1 className="text-sm font-semibold bg-linear-to-r from-blue-400 to-indigo-400 bg-clip-text text-transparent whitespace-nowrap">
|
||||
Process Scheduler Simulator
|
||||
</h1>
|
||||
|
||||
@@ -81,10 +81,11 @@ const AppHeader = () => {
|
||||
onClick={() => setPresetsOpen(false)}
|
||||
/>
|
||||
{/* Dropdown */}
|
||||
<div className="absolute right-0 top-full mt-1 w-72 bg-slate-800 border border-slate-600 rounded-lg shadow-xl z-50 overflow-hidden">
|
||||
<div className="px-3 py-2 border-b border-slate-700 text-[10px] text-slate-400 uppercase tracking-wider font-medium">
|
||||
<div className="absolute right-0 top-full mt-1 w-72 bg-slate-800 border border-slate-600 rounded-lg shadow-xl z-50 overflow-hidden flex flex-col max-h-[min(32rem,calc(100vh-4rem))]">
|
||||
<div className="px-3 py-2 border-b border-slate-700 text-[10px] text-slate-400 uppercase tracking-wider font-medium shrink-0">
|
||||
Cenários pré-definidos
|
||||
</div>
|
||||
<div className="overflow-y-auto">
|
||||
{presets.map((preset, i) => (
|
||||
<button
|
||||
key={preset.name}
|
||||
@@ -102,6 +103,7 @@ const AppHeader = () => {
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -276,6 +276,25 @@ describe("SimulationEngine general", () => {
|
||||
expect(ticks[1]?.cpuProcess).toBe("p1");
|
||||
expect(ticks[2]?.contextSwitching).toBe(true);
|
||||
expect(ticks[2]?.cpuProcess).toBeNull();
|
||||
expect(ticks[2]?.ctxSwitchForProcess).toBe("p2");
|
||||
expect(ticks[3]?.cpuProcess).toBe("p2");
|
||||
});
|
||||
|
||||
it("ctxSwitchForProcess identifies incoming process across all overhead ticks", () => {
|
||||
const engine = new SimulationEngine(
|
||||
[
|
||||
p("p1", 0, [{ type: "cpu", duration: 2 }]),
|
||||
p("p2", 0, [{ type: "cpu", duration: 2 }]),
|
||||
],
|
||||
cfg({ algorithm: "FCFS", contextSwitchTime: 2 }),
|
||||
);
|
||||
const ticks = engine.runAll();
|
||||
// P1 runs ticks 0-1, ctx-switch ticks 2-3, P2 runs ticks 4-5
|
||||
expect(ticks[2]?.contextSwitching).toBe(true);
|
||||
expect(ticks[2]?.ctxSwitchForProcess).toBe("p2");
|
||||
expect(ticks[3]?.contextSwitching).toBe(true);
|
||||
expect(ticks[3]?.ctxSwitchForProcess).toBe("p2");
|
||||
expect(ticks[4]?.contextSwitching).toBe(false);
|
||||
expect(ticks[4]?.ctxSwitchForProcess).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -239,6 +239,9 @@ export class SimulationEngine {
|
||||
readyQueue: this._readyQueue.map((rt) => rt.processId),
|
||||
states,
|
||||
contextSwitching,
|
||||
ctxSwitchForProcess: contextSwitching
|
||||
? (this._readyQueue[0]?.processId ?? null)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,16 @@ export const presets: Preset[] = [
|
||||
],
|
||||
config: defaultConfig({ algorithm: "FCFS" }),
|
||||
},
|
||||
{
|
||||
name: "context_switch_demo",
|
||||
description: "3 processos FCFS com 2 ticks de overhead — veja as células vermelhas de Context Switch",
|
||||
processes: [
|
||||
{ id: "p1", name: "P1", arrivalTime: 0, priority: 5, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 3 }] },
|
||||
{ id: "p2", name: "P2", arrivalTime: 0, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 3 }] },
|
||||
{ id: "p3", name: "P3", arrivalTime: 0, priority: 5, color: "#FFB74D", bursts: [{ type: "cpu", duration: 3 }] },
|
||||
],
|
||||
config: defaultConfig({ algorithm: "FCFS", contextSwitchTime: 2 }),
|
||||
},
|
||||
{
|
||||
name: "multilevel_demo",
|
||||
description: "Priority RR with 3 priority tiers — demonstrates multilevel scheduling",
|
||||
|
||||
@@ -76,6 +76,7 @@ export interface SimTick {
|
||||
readyQueue: string[]; // processIds in ready queue (in queue order)
|
||||
states: Record<string, StateLabel>; // all processId → state
|
||||
contextSwitching: boolean; // true during context-switch overhead
|
||||
ctxSwitchForProcess: string | null; // incoming processId during ctx-switch overhead (null otherwise)
|
||||
}
|
||||
|
||||
// --------------- Metrics ------------------------------------
|
||||
|
||||
Reference in new issue
Block a user