mirror of
https://github.com/SikongJueluo/pi-extensions.git
synced 2026-10-05 11:52:55 +08:00
- add time handler that unwraps the bare reserved-word form time <command> and re-evaluates the full de-wrapped compound like timeout (ADR 0009) - defer fail-closed on dash-leading modifiers, bare time, and nested wrappers in both directions - generalize isRecognizedWrapper to timeout and time, and classifyWrapper to recognized/unsupported/other with a wrapper name - rename defer events to inner_cmd.nested_wrapper and inner_cmd.unsupported_wrapper_syntax with a wrapper field - extract stripWrapperUnit into handlers/strip.ts for shared use
188 lines
7.3 KiB
TypeScript
188 lines
7.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
classifyWrapper,
|
|
isRecognizedWrapper,
|
|
parseTimeWrapper,
|
|
parseTimeoutWrapper,
|
|
} from "../src/recognizer";
|
|
|
|
describe("parseTimeoutWrapper", () => {
|
|
it("matches the simple-timeout form", () => {
|
|
expect(parseTimeoutWrapper("timeout 30s pnpm test")).toEqual({
|
|
duration: "30s",
|
|
innerCommand: "pnpm test",
|
|
});
|
|
expect(parseTimeoutWrapper("timeout 1m echo hi")).toEqual({
|
|
duration: "1m",
|
|
innerCommand: "echo hi",
|
|
});
|
|
expect(parseTimeoutWrapper("timeout 5h deploy")).toEqual({
|
|
duration: "5h",
|
|
innerCommand: "deploy",
|
|
});
|
|
expect(parseTimeoutWrapper("timeout 2d longjob")).toEqual({
|
|
duration: "2d",
|
|
innerCommand: "longjob",
|
|
});
|
|
});
|
|
|
|
it("accepts GNU durations: bare integer (seconds) and decimals", () => {
|
|
expect(parseTimeoutWrapper("timeout 240 pnpm test")).toEqual({
|
|
duration: "240",
|
|
innerCommand: "pnpm test",
|
|
});
|
|
expect(parseTimeoutWrapper("timeout 1.5h deploy")).toEqual({
|
|
duration: "1.5h",
|
|
innerCommand: "deploy",
|
|
});
|
|
expect(parseTimeoutWrapper("timeout 2.5s build")).toEqual({
|
|
duration: "2.5s",
|
|
innerCommand: "build",
|
|
});
|
|
});
|
|
|
|
it("preserves compound inner programs as the inner command", () => {
|
|
expect(parseTimeoutWrapper("timeout 60s pnpm test && git push")).toEqual({
|
|
duration: "60s",
|
|
innerCommand: "pnpm test && git push",
|
|
});
|
|
expect(parseTimeoutWrapper("timeout 30s bash -c something")).toEqual({
|
|
duration: "30s",
|
|
innerCommand: "bash -c something",
|
|
});
|
|
});
|
|
|
|
it("accepts tab-separated and multi-space arguments", () => {
|
|
expect(parseTimeoutWrapper("timeout\t30s\tpnpm test")).toEqual({
|
|
duration: "30s",
|
|
innerCommand: "pnpm test",
|
|
});
|
|
expect(parseTimeoutWrapper("timeout 10s build")).toEqual({
|
|
duration: "10s",
|
|
innerCommand: "build",
|
|
});
|
|
});
|
|
|
|
it("rejects zero, leading-zero, ms, and multi-letter durations", () => {
|
|
expect(parseTimeoutWrapper("timeout 0 pnpm test")).toBeUndefined();
|
|
expect(parseTimeoutWrapper("timeout 0s pnpm test")).toBeUndefined();
|
|
expect(parseTimeoutWrapper("timeout 0.5s pnpm test")).toBeUndefined();
|
|
expect(parseTimeoutWrapper("timeout 030s pnpm test")).toBeUndefined();
|
|
expect(parseTimeoutWrapper("timeout 30ms pnpm test")).toBeUndefined();
|
|
expect(parseTimeoutWrapper("timeout 30sec pnpm test")).toBeUndefined();
|
|
});
|
|
|
|
it("rejects unsupported timeout syntax", () => {
|
|
expect(parseTimeoutWrapper("timeout -k 5s 30s pnpm test")).toBeUndefined();
|
|
expect(parseTimeoutWrapper("timeout --preserve-status 30s pnpm test")).toBeUndefined();
|
|
expect(parseTimeoutWrapper("timeout -- 30s pnpm test")).toBeUndefined();
|
|
expect(parseTimeoutWrapper("timeout 30s")).toBeUndefined();
|
|
expect(parseTimeoutWrapper("timeout")).toBeUndefined();
|
|
});
|
|
|
|
it("does not match commands that merely contain timeout", () => {
|
|
expect(parseTimeoutWrapper("pnpm test")).toBeUndefined();
|
|
expect(parseTimeoutWrapper("timeout30s pnpm test")).toBeUndefined();
|
|
expect(parseTimeoutWrapper("my-timeout 30s pnpm test")).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("parseTimeWrapper", () => {
|
|
it("matches the bare reserved-word form", () => {
|
|
expect(parseTimeWrapper("time pnpm test")).toEqual({
|
|
innerCommand: "pnpm test",
|
|
});
|
|
expect(parseTimeWrapper("time\techo hi")).toEqual({
|
|
innerCommand: "echo hi",
|
|
});
|
|
expect(parseTimeWrapper("time build")).toEqual({
|
|
innerCommand: "build",
|
|
});
|
|
});
|
|
|
|
it("preserves compound inner programs as the inner command", () => {
|
|
expect(parseTimeWrapper("time pnpm test && git push")).toEqual({
|
|
innerCommand: "pnpm test && git push",
|
|
});
|
|
expect(parseTimeWrapper("time bash -c something")).toEqual({
|
|
innerCommand: "bash -c something",
|
|
});
|
|
});
|
|
|
|
it("rejects modifier args regardless of separator width", () => {
|
|
// A dash right after the separator means flags: not transparent.
|
|
expect(parseTimeWrapper("time -p ls")).toBeUndefined();
|
|
// Backtracking must not smuggle a leading space into the inner.
|
|
expect(parseTimeWrapper("time -p ls")).toBeUndefined();
|
|
expect(parseTimeWrapper("time\t-- ls")).toBeUndefined();
|
|
expect(parseTimeWrapper("time -o out.txt ls")).toBeUndefined();
|
|
});
|
|
|
|
it("rejects a bare time and non-time commands", () => {
|
|
expect(parseTimeWrapper("time")).toBeUndefined();
|
|
expect(parseTimeWrapper("timeout 10s ls")).toBeUndefined();
|
|
expect(parseTimeWrapper("my-time ls")).toBeUndefined();
|
|
expect(parseTimeWrapper("/usr/bin/time ls")).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("isRecognizedWrapper", () => {
|
|
it("is true for the strict forms and false otherwise", () => {
|
|
expect(isRecognizedWrapper("timeout 10s pnpm test")).toBe(true);
|
|
expect(isRecognizedWrapper("timeout 10s timeout 5s pnpm test")).toBe(
|
|
true,
|
|
);
|
|
expect(isRecognizedWrapper("time pnpm test")).toBe(true);
|
|
expect(isRecognizedWrapper("time timeout 5s pnpm test")).toBe(true);
|
|
expect(isRecognizedWrapper("timeout 10s time pnpm test")).toBe(true);
|
|
expect(isRecognizedWrapper("pnpm test")).toBe(false);
|
|
expect(isRecognizedWrapper("timeout -k 5s 30s pnpm test")).toBe(false);
|
|
expect(isRecognizedWrapper("time -p pnpm test")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("classifyWrapper", () => {
|
|
it("classifies recognized wrappers with their name", () => {
|
|
expect(classifyWrapper("timeout 30s pnpm test")).toEqual({
|
|
kind: "recognized",
|
|
wrapper: "timeout",
|
|
match: { duration: "30s", innerCommand: "pnpm test" },
|
|
});
|
|
expect(classifyWrapper("time pnpm test")).toEqual({
|
|
kind: "recognized",
|
|
wrapper: "time",
|
|
match: { innerCommand: "pnpm test" },
|
|
});
|
|
});
|
|
|
|
it("classifies unsupported wrapper syntax with its name", () => {
|
|
expect(classifyWrapper("timeout -k 5s 30s pnpm test")).toEqual({
|
|
kind: "unsupported",
|
|
wrapper: "timeout",
|
|
});
|
|
expect(classifyWrapper("timeout 30s")).toEqual({
|
|
kind: "unsupported",
|
|
wrapper: "timeout",
|
|
});
|
|
expect(classifyWrapper("timeout --help")).toEqual({
|
|
kind: "unsupported",
|
|
wrapper: "timeout",
|
|
});
|
|
expect(classifyWrapper("time -p ls")).toEqual({
|
|
kind: "unsupported",
|
|
wrapper: "time",
|
|
});
|
|
expect(classifyWrapper("time")).toEqual({
|
|
kind: "unsupported",
|
|
wrapper: "time",
|
|
});
|
|
});
|
|
|
|
it("classifies ordinary commands as other", () => {
|
|
expect(classifyWrapper("pnpm test").kind).toBe("other");
|
|
expect(classifyWrapper("rm -rf /").kind).toBe("other");
|
|
expect(classifyWrapper("git push").kind).toBe("other");
|
|
expect(classifyWrapper("/usr/bin/time ls").kind).toBe("other");
|
|
});
|
|
});
|