From a5176fe8d96ec5e9b80e16bc96c920fca8432efa Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 20:05:25 +0200 Subject: [PATCH] test: cover shellTest --- src/shellTest.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 src/shellTest.c diff --git a/src/shellTest.c b/src/shellTest.c new file mode 100644 index 0000000..97db9fe --- /dev/null +++ b/src/shellTest.c @@ -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"); +} +