test: cover shellTest

This commit is contained in:
dualfroz committed 2026-09-11 20:05:25 +02:00
1 parent 1916c7298b
commit a5176fe8d9
1 file changed
+156
+156
View File
@@ -0,0 +1,156 @@
#include "ktest.h"
#include "shell.h"
#include "string.h"
/* shellTokenize (shell.c) is the pure-string half of the shell's new
`NAME [ARGS] [&]` job-control dispatch (runProgram, shell.c) -- turning
a typed line into a real argv[] the way a normal shell does, instead of
the single "rest of the line" string every other built-in command is
content with. Everything below runs before schedStart() (ktest.h's own
file comment), so it deliberately never touches the VFS, jobctl.h, or a
real spawn -- only the tokenizer itself, which is ordinary deterministic
string manipulation. */
KTEST(shellTokenizeEmptyLine)
{
char text[] = "";
char* tokens[4];
int count = shellTokenize(text, tokens, 4);
KT_EQ(t, count, 0);
}
KTEST(shellTokenizeSingleWord)
{
char text[] = "hello";
char* tokens[4];
int count = shellTokenize(text, tokens, 4);
KT_EQ(t, count, 1);
KT_STREQ(t, tokens[0], "hello");
}
KTEST(shellTokenizeMultipleWordsCollapsesRepeatedSpaces)
{
char text[] = " one two three ";
char* tokens[8];
int count = shellTokenize(text, tokens, 8);
KT_EQ(t, count, 3);
KT_STREQ(t, tokens[0], "one");
KT_STREQ(t, tokens[1], "two");
KT_STREQ(t, tokens[2], "three");
}
KTEST(shellTokenizeAllSpacesYieldsNothing)
{
char text[] = " ";
char* tokens[4];
int count = shellTokenize(text, tokens, 4);
KT_EQ(t, count, 0);
}
KTEST(shellTokenizeRespectsCapacityInsteadOfOverrunningArray)
{
char text[] = "a b c d e";
char* tokens[3];
int count = shellTokenize(text, tokens, 3);
KT_EQ(t, count, 3);
KT_STREQ(t, tokens[0], "a");
KT_STREQ(t, tokens[1], "b");
KT_STREQ(t, tokens[2], "c");
}
KTEST(shellTokenizeTrailingAmpersandIsJustAnotherToken)
{
/* runProgram (shell.c) is the one that gives "&" its background
meaning by inspecting the LAST token itself -- shellTokenize's own
job is only to split words apart, so it must hand "&" back
unchanged like any other token, not special-case it. */
char text[] = "sleepjob arg1 &";
char* tokens[8];
int count = shellTokenize(text, tokens, 8);
KT_EQ(t, count, 3);
KT_STREQ(t, tokens[0], "sleepjob");
KT_STREQ(t, tokens[1], "arg1");
KT_STREQ(t, tokens[2], "&");
}
KTEST(shellTokenizeZeroCapacityYieldsNothing)
{
char text[] = "one two";
char* tokens[1];
int count = shellTokenize(text, tokens, 0);
KT_EQ(t, count, 0);
}
/* shellSplitPipelineStages (shell.c) is the pure-string half of the shell's
`cmd1 | cmd2 | ... | cmdN` pipeline syntax -- deciding whether execute()
should run runPipelineStages (N spawned processes joined by N-1 real
pipes) or fall back to a single command, exactly the same "pure logic, no
VFS/scheduler" split shellTokenize already has from its own doc comment
above. */
KTEST(shellSplitPipelineStagesNoPipeReturnsZero)
{
char text[] = "ls -l";
char* stages[shellMaxPipelineStages];
int count = shellSplitPipelineStages(text, stages, shellMaxPipelineStages);
KT_EQ(t, count, 0);
}
KTEST(shellSplitPipelineStagesBasicTwoStageSplit)
{
char text[] = "producer|consumer";
char* stages[shellMaxPipelineStages];
int count = shellSplitPipelineStages(text, stages, shellMaxPipelineStages);
KT_EQ(t, count, 2);
KT_STREQ(t, stages[0], "producer");
KT_STREQ(t, stages[1], "consumer");
}
KTEST(shellSplitPipelineStagesTrimsSpacesAroundEachPipe)
{
char text[] = "pipesrc arg1 | pipecat | pipesink arg2";
char* stages[shellMaxPipelineStages];
int count = shellSplitPipelineStages(text, stages, shellMaxPipelineStages);
KT_EQ(t, count, 3);
KT_STREQ(t, stages[0], "pipesrc arg1");
KT_STREQ(t, stages[1], "pipecat");
KT_STREQ(t, stages[2], "pipesink arg2");
}
KTEST(shellSplitPipelineStagesThreeStageSplitIsRealNotJustFirstPipe)
{
/* Unlike the old two-way shellSplitPipeline this generalizes, every
top-level '|' is now recognized -- a real N-stage pipeline, not just
the first split with the rest left embedded in one text blob. */
char text[] = "a | b | c";
char* stages[shellMaxPipelineStages];
int count = shellSplitPipelineStages(text, stages, shellMaxPipelineStages);
KT_EQ(t, count, 3);
KT_STREQ(t, stages[0], "a");
KT_STREQ(t, stages[1], "b");
KT_STREQ(t, stages[2], "c");
}
KTEST(shellSplitPipelineStagesEmptyLastStageYieldsEmptyString)
{
char text[] = "producer |";
char* stages[shellMaxPipelineStages];
int count = shellSplitPipelineStages(text, stages, shellMaxPipelineStages);
KT_EQ(t, count, 2);
KT_STREQ(t, stages[0], "producer");
KT_STREQ(t, stages[1], "");
}
KTEST(shellSplitPipelineStagesTooManyStagesReturnsNegativeOneWithoutMutating)
{
char text[] = "a | b | c | d | e";
char* stages[shellMaxPipelineStages];
/* shellMaxPipelineStages is 4, but this line has 5 stages -- must be
rejected outright, leaving `text` completely untouched, exactly the
same "malformed, nothing changed" contract shellExtractRedirects'
own dangling-operator case already uses. */
int count = shellSplitPipelineStages(text, stages, shellMaxPipelineStages);
KT_EQ(t, count, -1);
KT_STREQ(t, text, "a | b | c | d | e");
}