test: extend coverage of shellTest

This commit is contained in:
dualfroz committed 2026-09-11 20:47:42 +02:00
1 parent a5176fe8d9
commit 2681016ffb
1 file changed
+153
+153
View File
@@ -154,3 +154,156 @@ KTEST(shellSplitPipelineStagesTooManyStagesReturnsNegativeOneWithoutMutating)
KT_STREQ(t, text, "a | b | c | d | e"); 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);
}