From 6f07fe8429b10ea64f7076aa1df02b5854377093 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 21:27:14 +0200 Subject: [PATCH] test: strengthen tests for shellTest --- src/shellTest.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/src/shellTest.c b/src/shellTest.c index 2dc2f89..30d8db7 100644 --- a/src/shellTest.c +++ b/src/shellTest.c @@ -464,3 +464,161 @@ KTEST(shellExtractRedirectsDanglingErrOperatorFailsWithoutMutating) KT_STREQ(t, argv[2], "2>"); } +/* `<<<` (here-string) is a third, independent fd-0 operator alongside `<` -- + covered on its own (basic recognition, combined with real args and `>`, + dangling), plus the two last-one-wins cases proving `<` and `<<<` share + ownership of the SAME fd 0 (whichever comes later in the line wins, + clearing the other field entirely) rather than being two unrelated + fields a caller could accidentally read both of at once. */ + +KTEST(shellExtractRedirectsHereStringBasic) +{ + char text[] = "cat <<< hello"; + 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], "cat"); + KT_STREQ(t, redirect.hereString, "hello"); + KT_NULL(t, redirect.inPath); +} + +KTEST(shellExtractRedirectsHereStringWithRealArgsAndOutput) +{ + char text[] = "sort -r <<< hello > 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, 2); + KT_STREQ(t, argv[0], "sort"); + KT_STREQ(t, argv[1], "-r"); + KT_STREQ(t, redirect.hereString, "hello"); + KT_STREQ(t, redirect.outPath, "out.txt"); + KT_NULL(t, redirect.inPath); +} + +KTEST(shellExtractRedirectsDanglingHereStringFailsWithoutMutating) +{ + char text[] = "cat <<<"; + char* argv[8]; + int argc = shellTokenize(text, argv, 8); + int originalArgc = argc; + ShellRedirect redirect; + bool ok = shellExtractRedirects(argv, &argc, &redirect); + KT_FALSE(t, ok); + KT_EQ(t, argc, originalArgc); + KT_STREQ(t, argv[0], "cat"); + KT_STREQ(t, argv[1], "<<<"); +} + +KTEST(shellExtractRedirectsHereStringAfterFileRedirectWins) +{ + char text[] = "cat < a.txt <<< b"; + 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, redirect.hereString, "b"); + KT_NULL(t, redirect.inPath); +} + +KTEST(shellExtractRedirectsFileRedirectAfterHereStringWins) +{ + char text[] = "cat <<< b < a.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, redirect.inPath, "a.txt"); + KT_NULL(t, redirect.hereString); +} + +/* shellParseVarRef (shell.c) is the pure-syntax half of the shell's new + `$NAME` unquoted parameter expansion -- recognizing the "$" + name-chars + shape, never touching sched.h's Thread.env (that lookup is + shellExpandArgv's job, not KTEST-able here for the same pre-scheduler + reason shellTokenize's own file comment gives). */ +KTEST(shellParseVarRefAcceptsPlainName) +{ + char name[16]; + bool ok = shellParseVarRef("$HOME", name, sizeof(name)); + KT_TRUE(t, ok); + KT_STREQ(t, name, "HOME"); +} + +KTEST(shellParseVarRefAcceptsDigitsAndUnderscore) +{ + char name[16]; + bool ok = shellParseVarRef("$_var_2", name, sizeof(name)); + KT_TRUE(t, ok); + KT_STREQ(t, name, "_var_2"); +} + +KTEST(shellParseVarRefRejectsBareDollarSign) +{ + char name[16]; + KT_FALSE(t, shellParseVarRef("$", name, sizeof(name))); +} + +KTEST(shellParseVarRefRejectsTokenWithoutLeadingDollar) +{ + char name[16]; + KT_FALSE(t, shellParseVarRef("HOME", name, sizeof(name))); +} + +KTEST(shellParseVarRefRejectsEmbeddedPunctuation) +{ + char name[16]; + KT_FALSE(t, shellParseVarRef("$FOO=bar", name, sizeof(name))); + KT_FALSE(t, shellParseVarRef("$FOO/bar", name, sizeof(name))); +} + +KTEST(shellParseVarRefRejectsNameTooLongForCapacity) +{ + char name[4]; + KT_FALSE(t, shellParseVarRef("$TOOLONG", name, sizeof(name))); +} + +/* shellParseVarRef must never accept `$?` itself -- '?' is not in its own + [A-Za-z0-9_] name-character set -- so the two token classes + (shellIsStatusVarToken below and shellParseVarRef above) can never + both claim the same token. */ +KTEST(shellParseVarRefRejectsQuestionMark) +{ + char name[16]; + KT_FALSE(t, shellParseVarRef("$?", name, sizeof(name))); +} + +/* shellIsStatusVarToken (shell.c) is the pure-syntax half of the shell's + new `$?` special parameter -- recognizing the EXACT two-character token + "$?", never touching this file's own file-scope `lastStatus` (that + substitution is shellExpandToken's job, not KTEST-able here for the + same pre-scheduler reason shellExpandArgv's own env lookups are not). */ +KTEST(shellIsStatusVarTokenAcceptsExactMatch) +{ + KT_TRUE(t, shellIsStatusVarToken("$?")); +} + +KTEST(shellIsStatusVarTokenRejectsBareDollarSign) +{ + KT_FALSE(t, shellIsStatusVarToken("$")); +} + +KTEST(shellIsStatusVarTokenRejectsBareQuestionMark) +{ + KT_FALSE(t, shellIsStatusVarToken("?")); +} + +KTEST(shellIsStatusVarTokenRejectsTrailingGarbage) +{ + KT_FALSE(t, shellIsStatusVarToken("$?x")); +} +