From 2681016ffbb755ba5b54ba5a54137abf76cddbd5 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 20:47:42 +0200 Subject: [PATCH] test: extend coverage of shellTest --- src/shellTest.c | 153 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/src/shellTest.c b/src/shellTest.c index 97db9fe..5aafe27 100644 --- a/src/shellTest.c +++ b/src/shellTest.c @@ -154,3 +154,156 @@ KTEST(shellSplitPipelineStagesTooManyStagesReturnsNegativeOneWithoutMutating) KT_STREQ(t, text, "a | b | c | d | e"); } +/* shellSplitAndOr (shell.c) is the pure-string half of the shell's new + `cmd1 && cmd2 || cmd3 ...` conditional-chaining syntax -- deciding + whether execute() should short-circuit through several commands' real + exit statuses (executeSingle, shell.c) or fall back to a single + command-or-pipeline exactly as before, run BEFORE pipeline splitting + (`|` binds tighter than `&&`/`||`), the same "pure logic, no + VFS/scheduler" split every other parser above already has. */ + +KTEST(shellSplitAndOrNoOperatorReturnsZero) +{ + char text[] = "ls -l"; + char* segments[shellMaxAndOrSegments]; + ShellAndOrOp ops[shellMaxAndOrSegments - 1]; + int count = shellSplitAndOr(text, segments, ops, shellMaxAndOrSegments); + KT_EQ(t, count, 0); +} + +KTEST(shellSplitAndOrBasicAndSplit) +{ + char text[] = "mkdir /tmp/a && echo ok"; + char* segments[shellMaxAndOrSegments]; + ShellAndOrOp ops[shellMaxAndOrSegments - 1]; + int count = shellSplitAndOr(text, segments, ops, shellMaxAndOrSegments); + KT_EQ(t, count, 2); + KT_STREQ(t, segments[0], "mkdir /tmp/a"); + KT_STREQ(t, segments[1], "echo ok"); + KT_EQ(t, ops[0], shellAndOrAnd); +} + +KTEST(shellSplitAndOrBasicOrSplit) +{ + char text[] = "false || echo fallback"; + char* segments[shellMaxAndOrSegments]; + ShellAndOrOp ops[shellMaxAndOrSegments - 1]; + int count = shellSplitAndOr(text, segments, ops, shellMaxAndOrSegments); + KT_EQ(t, count, 2); + KT_STREQ(t, segments[0], "false"); + KT_STREQ(t, segments[1], "echo fallback"); + KT_EQ(t, ops[0], shellAndOrOr); +} + +KTEST(shellSplitAndOrMixedChainRecordsEachOperatorIndependently) +{ + /* The classic shell if-then-else idiom: "run b only if a succeeded, + run c only if the segment that actually ran last (a or b) did NOT + succeed" -- proves && and || are tracked as two independently + recorded operators, not collapsed into one boolean. */ + char text[] = "a && b || c"; + char* segments[shellMaxAndOrSegments]; + ShellAndOrOp ops[shellMaxAndOrSegments - 1]; + int count = shellSplitAndOr(text, segments, ops, shellMaxAndOrSegments); + KT_EQ(t, count, 3); + KT_STREQ(t, segments[0], "a"); + KT_STREQ(t, segments[1], "b"); + KT_STREQ(t, segments[2], "c"); + KT_EQ(t, ops[0], shellAndOrAnd); + KT_EQ(t, ops[1], shellAndOrOr); +} + +KTEST(shellSplitAndOrTrimsSpacesAroundEachOperator) +{ + char text[] = "a&&b"; + char* segments[shellMaxAndOrSegments]; + ShellAndOrOp ops[shellMaxAndOrSegments - 1]; + int count = shellSplitAndOr(text, segments, ops, shellMaxAndOrSegments); + KT_EQ(t, count, 2); + KT_STREQ(t, segments[0], "a"); + KT_STREQ(t, segments[1], "b"); +} + +KTEST(shellSplitAndOrLeavesSingleTopLevelPipeInsideItsOwnSegment) +{ + /* '|' binds tighter than '&&'/'||': a lone pipe character must never be + mistaken for half of '||' (which only matches two CONSECUTIVE '|' + characters), so "a | b" stays a single, whole segment here -- only + execute()'s own later shellSplitPipelineStages call on that segment + ever splits it further. */ + char text[] = "a | b && c"; + char* segments[shellMaxAndOrSegments]; + ShellAndOrOp ops[shellMaxAndOrSegments - 1]; + int count = shellSplitAndOr(text, segments, ops, shellMaxAndOrSegments); + KT_EQ(t, count, 2); + KT_STREQ(t, segments[0], "a | b"); + KT_STREQ(t, segments[1], "c"); + KT_EQ(t, ops[0], shellAndOrAnd); +} + +KTEST(shellSplitAndOrTooManySegmentsReturnsNegativeOneWithoutMutating) +{ + char text[] = "a && b && c && d && e"; + char* segments[shellMaxAndOrSegments]; + ShellAndOrOp ops[shellMaxAndOrSegments - 1]; + /* shellMaxAndOrSegments is 4, but this line has 5 segments -- must be + rejected outright, leaving `text` completely untouched, the same + "malformed, nothing changed" contract shellSplitPipelineStages' own + analogous limit already uses. */ + int count = shellSplitAndOr(text, segments, ops, shellMaxAndOrSegments); + KT_EQ(t, count, -1); + KT_STREQ(t, text, "a && b && c && d && e"); +} + +/* shellExtractRedirects (shell.c) is the pure-token half of shell.c's new + `cmd [ARGS] [< IN] [> OUT | >> OUT]` file redirection -- exactly the same + "pure logic, no VFS/scheduler" split shellTokenize/shellSplitPipelineStages + already have from their own doc comments above, operating on + shellTokenize's own output shape (an already-split argv) instead of a + raw line. */ + +KTEST(shellExtractRedirectsNoOperatorsLeavesArgvUnchanged) +{ + char text[] = "cat file.txt"; + char* argv[8]; + int argc = shellTokenize(text, argv, 8); + ShellRedirect redirect; + bool ok = shellExtractRedirects(argv, &argc, &redirect); + KT_TRUE(t, ok); + KT_EQ(t, argc, 2); + KT_STREQ(t, argv[0], "cat"); + KT_STREQ(t, argv[1], "file.txt"); + KT_NULL(t, redirect.inPath); + KT_NULL(t, redirect.outPath); +} + +KTEST(shellExtractRedirectsOutputTruncate) +{ + char text[] = "sort > out.txt"; + char* argv[8]; + int argc = shellTokenize(text, argv, 8); + ShellRedirect redirect; + bool ok = shellExtractRedirects(argv, &argc, &redirect); + KT_TRUE(t, ok); + KT_EQ(t, argc, 1); + KT_STREQ(t, argv[0], "sort"); + KT_NULL(t, redirect.inPath); + KT_NOTNULL(t, redirect.outPath); + KT_STREQ(t, redirect.outPath, "out.txt"); + KT_FALSE(t, redirect.appendOut); +} + +KTEST(shellExtractRedirectsOutputAppend) +{ + char text[] = "sort >> out.txt"; + char* argv[8]; + int argc = shellTokenize(text, argv, 8); + ShellRedirect redirect; + bool ok = shellExtractRedirects(argv, &argc, &redirect); + KT_TRUE(t, ok); + KT_EQ(t, argc, 1); + KT_STREQ(t, argv[0], "sort"); + KT_STREQ(t, redirect.outPath, "out.txt"); + KT_TRUE(t, redirect.appendOut); +} +