дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
Stop flipped the session back to Run as soon as the direct child exited, but a shell, Maven or the debug adapter can exit while the application it started still holds its port, so the next Run failed with "port already in use". The console also went quiet on a debug Stop because the DAP session was disconnected before java-debug drained the program's output, and closing a window killed running programs with SIGKILL at once. Run and Debug sessions now stay in a stopping state from the Stop press until the whole process group (or the debuggee's pid tree) is gone. As in IntelliJ IDEA, the first Stop only interrupts and waits, and a second Stop kills; the Stop button turns into Kill process meanwhile. A rerun or restart waits for the old process without a time limit, and the termination time limit setting now only bounds a configuration's stop command. Debug launches record the program pid from the adapter's process/processid event, signal its pid tree and keep the adapter attached until the program exits; closing a project also stops debug sessions under its root. Closing a window with running programs shows a Terminating panel with Kill process and closes once they are gone. Co-authored-by: misswell <[email protected]>
159 рядки
5.9 KiB
TypeScript
159 рядки
5.9 KiB
TypeScript
import { afterEach, beforeEach, expect, it, vi } from "vitest";
|
|
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
import { executeAction } from "@/platform/actions";
|
|
import { setContext } from "@/platform/context";
|
|
import type { RunConfig } from "@/platform/ipc";
|
|
import { registerMenuGroup, useDebugRunSessions, type DebugRunSession } from "@/workbench/contributions";
|
|
import { useContextMenuStore } from "@/ui";
|
|
import { applicationConfig } from "./application";
|
|
import { activate } from "./index";
|
|
import { RunWidget } from "./RunWidget";
|
|
import { resetRuns, useRun, type RunSession } from "./store";
|
|
import { selectedTarget } from "./sessionTargets";
|
|
|
|
const config = (id: string): RunConfig => ({
|
|
id,
|
|
name: id,
|
|
kind: "shell",
|
|
command: "sleep",
|
|
args: ["60"],
|
|
cwd: null,
|
|
env: {},
|
|
detected: false,
|
|
isTest: false,
|
|
debugAdapter: null,
|
|
});
|
|
const app = config("SdCloudOaApplication");
|
|
const npm = config("sdcloud-oa-vue3: npm run dev");
|
|
const run = (c: RunConfig, startedAt = 1): RunSession => ({
|
|
id: c.id,
|
|
config: c,
|
|
runId: 1,
|
|
status: "running",
|
|
exitCode: null,
|
|
startedAt,
|
|
endedAt: null,
|
|
stdinClosed: false,
|
|
});
|
|
const debug = (c: RunConfig, startedAt = 2): DebugRunSession => ({
|
|
id: 1,
|
|
configId: c.id,
|
|
name: c.name,
|
|
startedAt,
|
|
paused: false,
|
|
rerun: vi.fn(),
|
|
stop: vi.fn(),
|
|
resume: vi.fn(),
|
|
});
|
|
let dispose: () => void;
|
|
beforeEach(() => {
|
|
resetRuns();
|
|
useRun.setState({ configs: [app, npm], selectedId: app.id, loaded: true });
|
|
useDebugRunSessions.setState({ sessions: [] });
|
|
setContext("projectOpen", true);
|
|
dispose = activate();
|
|
});
|
|
afterEach(() => {
|
|
cleanup();
|
|
dispose();
|
|
useDebugRunSessions.setState({ sessions: [] });
|
|
});
|
|
|
|
it("reruns and stops the selected configuration among several running sessions", () => {
|
|
useRun.setState({ sessions: [run(app), run(npm, 3)] });
|
|
render(<RunWidget />);
|
|
expect(screen.getByRole("button", { name: `Rerun '${app.name}'` })).toBeTruthy();
|
|
fireEvent.click(screen.getByRole("button", { name: `Stop '${app.name}'` }));
|
|
expect(useRun.getState().sessions.map((s) => !!s.stopped)).toEqual([true, false]);
|
|
});
|
|
|
|
it("offers Run/Debug for an idle selection and Stop for the other process", () => {
|
|
useRun.setState({ sessions: [run(npm)] });
|
|
render(<RunWidget />);
|
|
expect(screen.getByRole("button", { name: `Run '${app.name}'` })).toBeTruthy();
|
|
expect(screen.getByRole("button", { name: `Debug '${app.name}'` })).toBeTruthy();
|
|
expect(screen.queryByRole("button", { name: /Rerun/ })).toBeNull();
|
|
fireEvent.click(screen.getByRole("button", { name: `Stop '${npm.name}'` }));
|
|
expect(useRun.getState().sessions[0]?.stopped).toBe(true);
|
|
});
|
|
|
|
it("offers a stop list when the selection is idle and several other sessions run", () => {
|
|
useRun.setState({ sessions: [run(npm), run(config("other"))] });
|
|
render(<RunWidget />);
|
|
expect(screen.getByRole("button", { name: "Stop (2 running)" })).toBeTruthy();
|
|
expect(screen.queryByRole("button", { name: /Rerun/ })).toBeNull();
|
|
});
|
|
|
|
it("targets the selected debug session while npm runs, through buttons and shortcut actions", async () => {
|
|
const session = debug(app);
|
|
useRun.setState({ sessions: [run(npm, 3)] });
|
|
useDebugRunSessions.setState({ sessions: [session] });
|
|
render(<RunWidget />);
|
|
fireEvent.click(screen.getByRole("button", { name: `Rerun '${app.name}'` }));
|
|
await vi.waitFor(() => expect(session.rerun).toHaveBeenCalledOnce());
|
|
await executeAction("run.run");
|
|
expect(session.rerun).toHaveBeenCalledTimes(2);
|
|
fireEvent.click(screen.getByRole("button", { name: `Stop '${app.name}'` }));
|
|
await vi.waitFor(() => expect(session.stop).toHaveBeenCalledOnce());
|
|
expect(useRun.getState().sessions[0]?.stopped).toBeUndefined();
|
|
});
|
|
|
|
it("turns Stop into Kill process while the process is shutting down", () => {
|
|
useRun.setState({ sessions: [{ ...run(app), status: "stopping" }] });
|
|
render(<RunWidget />);
|
|
expect(screen.getByRole("button", { name: "Kill process" })).toBeTruthy();
|
|
});
|
|
|
|
it("lists every running process with Stop All for the Stop shortcut, like IDEA", async () => {
|
|
const session = debug(app);
|
|
useRun.setState({ sessions: [run(npm, 3)] });
|
|
useDebugRunSessions.setState({ sessions: [session] });
|
|
const unregister = registerMenuGroup({
|
|
id: "test.stopDebug",
|
|
menus: ["stopProcess"],
|
|
items: () => [{ label: app.name, run: () => session.stop() }],
|
|
});
|
|
try {
|
|
render(<RunWidget />);
|
|
await executeAction("run.stop");
|
|
const items = useContextMenuStore.getState().menu!.items;
|
|
expect(items.map((i) => ("label" in i ? i.label : i.kind))).toEqual([
|
|
"Stop Process",
|
|
npm.name,
|
|
app.name,
|
|
"separator",
|
|
"Stop All",
|
|
]);
|
|
const stopAll = items.at(-1) as { run: () => void };
|
|
stopAll.run();
|
|
expect(session.stop).toHaveBeenCalledOnce();
|
|
expect(useRun.getState().sessions[0]?.stopped).toBe(true);
|
|
} finally {
|
|
unregister();
|
|
}
|
|
});
|
|
|
|
it("ignores an unrelated debug session for an idle selection", () => {
|
|
useDebugRunSessions.setState({ sessions: [debug(npm)] });
|
|
render(<RunWidget />);
|
|
expect(screen.getByRole("button", { name: `Run '${app.name}'` })).toBeTruthy();
|
|
expect(screen.queryByRole("button", { name: /Rerun/ })).toBeNull();
|
|
});
|
|
|
|
it.each([1, 3])("prefers the newest Run or Debug session of the same config (run start %s)", (startedAt) => {
|
|
expect(selectedTarget(app.id, [run(app, startedAt)], [debug(app, 2)])?.debug).toBe(startedAt < 2);
|
|
});
|
|
|
|
it("matches a detected Spring configuration after its object and id are regenerated", () => {
|
|
const detect = () =>
|
|
applicationConfig(
|
|
{ className: "com.example.SdCloudOaApplication", spring: true },
|
|
"src/main/java/com/example/SdCloudOaApplication.java",
|
|
);
|
|
const original = detect();
|
|
const regenerated = detect();
|
|
expect(regenerated).not.toBe(original);
|
|
expect(regenerated.id).toBe(original.id);
|
|
expect(selectedTarget(regenerated.id, [run(original), run(npm)], [])?.name).toBe(original.name);
|
|
});
|