diff --git a/src/shellTest.c b/src/shellTest.c index 30d8db7..8303677 100644 --- a/src/shellTest.c +++ b/src/shellTest.c @@ -622,3 +622,191 @@ KTEST(shellIsStatusVarTokenRejectsTrailingGarbage) KT_FALSE(t, shellIsStatusVarToken("$?x")); } +KTEST(shellIsStatusVarTokenRejectsLeadingGarbage) +{ + KT_FALSE(t, shellIsStatusVarToken("x$?")); +} + +/* shellGlobMatch/shellTokenHasGlob/shellSplitGlobToken (shell.c) are the + pure-logic half of the shell's new wildcard glob expansion + (shellExpandGlobs, which actually walks the VFS and is not KTEST-able + here for the same pre-scheduler reason shellExpandArgv's own VFS/thread + lookups are not) -- the same "pure logic here, thread/VFS-touching glue + stays static in shell.c" split every other pure function above already + follows. */ + +KTEST(shellGlobMatchExactLiteral) +{ + KT_TRUE(t, shellGlobMatch("hello", "hello")); + KT_FALSE(t, shellGlobMatch("hello", "hellox")); + KT_FALSE(t, shellGlobMatch("hello", "hell")); +} + +KTEST(shellGlobMatchStarMatchesAnySuffix) +{ + KT_TRUE(t, shellGlobMatch("a*", "a")); + KT_TRUE(t, shellGlobMatch("a*", "abc")); + KT_FALSE(t, shellGlobMatch("a*", "xabc")); +} + +KTEST(shellGlobMatchStarMatchesAnyPrefix) +{ + KT_TRUE(t, shellGlobMatch("*.txt", "a.txt")); + KT_TRUE(t, shellGlobMatch("*.txt", ".txt")); + KT_FALSE(t, shellGlobMatch("*.txt", "a.log")); +} + +KTEST(shellGlobMatchStarInTheMiddle) +{ + KT_TRUE(t, shellGlobMatch("a*b", "ab")); + KT_TRUE(t, shellGlobMatch("a*b", "axxxb")); + KT_FALSE(t, shellGlobMatch("a*b", "axxxc")); +} + +KTEST(shellGlobMatchMultipleStars) +{ + KT_TRUE(t, shellGlobMatch("*a*b*", "aXbY")); + KT_TRUE(t, shellGlobMatch("*a*b*", "ab")); + KT_FALSE(t, shellGlobMatch("*a*b*", "ba")); +} + +KTEST(shellGlobMatchBareStarMatchesEverythingIncludingEmpty) +{ + KT_TRUE(t, shellGlobMatch("*", "")); + KT_TRUE(t, shellGlobMatch("*", "anything")); +} + +KTEST(shellGlobMatchQuestionMarkMatchesExactlyOneChar) +{ + KT_TRUE(t, shellGlobMatch("a?c", "abc")); + KT_FALSE(t, shellGlobMatch("a?c", "ac")); + KT_FALSE(t, shellGlobMatch("a?c", "abbc")); +} + +KTEST(shellGlobMatchEmptyPatternOnlyMatchesEmptyName) +{ + KT_TRUE(t, shellGlobMatch("", "")); + KT_FALSE(t, shellGlobMatch("", "x")); +} + +KTEST(shellTokenHasGlobDetectsStarAndQuestionMark) +{ + KT_TRUE(t, shellTokenHasGlob("*.txt")); + KT_TRUE(t, shellTokenHasGlob("file?.log")); + KT_FALSE(t, shellTokenHasGlob("plain.txt")); + KT_FALSE(t, shellTokenHasGlob("")); +} + +KTEST(shellSplitGlobTokenNoSlashYieldsEmptyDir) +{ + char dir[64]; + char pattern[64]; + shellSplitGlobToken("*.txt", dir, sizeof(dir), pattern, sizeof(pattern)); + KT_STREQ(t, dir, ""); + KT_STREQ(t, pattern, "*.txt"); +} + +KTEST(shellSplitGlobTokenSplitsAtLastSlashIncludingIt) +{ + char dir[64]; + char pattern[64]; + shellSplitGlobToken("/tmp/gd/*.txt", dir, sizeof(dir), pattern, sizeof(pattern)); + KT_STREQ(t, dir, "/tmp/gd/"); + KT_STREQ(t, pattern, "*.txt"); +} + +KTEST(shellSplitGlobTokenNoGlobCharStillSplitsCleanly) +{ + /* shellSplitGlobToken itself does not care whether the pattern half + actually contains a glob character -- that gate is + shellTokenHasGlob's own job, called first by shellExpandGlobs. */ + char dir[64]; + char pattern[64]; + shellSplitGlobToken("/tmp/gd/plain.txt", dir, sizeof(dir), pattern, sizeof(pattern)); + KT_STREQ(t, dir, "/tmp/gd/"); + KT_STREQ(t, pattern, "plain.txt"); +} + +/* shellFindCommandSubst (shell.c) is the pure-syntax half of the shell's + new `$(cmd [args])` command-substitution expansion -- the impure glue + that actually spawns and captures a command's output (shellExpandCommandSubst, + static in shell.c) needs a real jobctl spawn, off limits for KTEST's + pre-scheduler context, so only the locate-and-extract half is covered + here, the same "pure logic here, thread/VFS-touching glue stays static + in shell.c" split every other shell.h syntax function already gets. */ + +KTEST(shellFindCommandSubstNoDollarParenReturnsNotFound) +{ + int start, end; + char cmd[32]; + KT_TRUE(t, shellFindCommandSubst("echo hello", &start, &end, cmd, sizeof(cmd))); + KT_EQ(t, start, -1); +} + +KTEST(shellFindCommandSubstWholeToken) +{ + int start, end; + char cmd[32]; + KT_TRUE(t, shellFindCommandSubst("$(date)", &start, &end, cmd, sizeof(cmd))); + KT_EQ(t, start, 0); + KT_EQ(t, end, 7); + KT_STREQ(t, cmd, "date"); +} + +KTEST(shellFindCommandSubstEmbeddedInLargerLine) +{ + int start, end; + char cmd[32]; + KT_TRUE(t, shellFindCommandSubst("echo $(date) done", &start, &end, cmd, sizeof(cmd))); + KT_EQ(t, start, 5); + KT_EQ(t, end, 12); + KT_STREQ(t, cmd, "date"); +} + +KTEST(shellFindCommandSubstCapturesMultiWordCommand) +{ + int start, end; + char cmd[32]; + KT_TRUE(t, shellFindCommandSubst("argecho $(argecho a b)", &start, &end, cmd, sizeof(cmd))); + KT_STREQ(t, cmd, "argecho a b"); +} + +KTEST(shellFindCommandSubstUnterminatedIsMalformed) +{ + int start, end; + char cmd[32]; + KT_FALSE(t, shellFindCommandSubst("echo $(date", &start, &end, cmd, sizeof(cmd))); +} + +KTEST(shellFindCommandSubstEmptyParensYieldsEmptyCommand) +{ + int start, end; + char cmd[32]; + KT_TRUE(t, shellFindCommandSubst("$()", &start, &end, cmd, sizeof(cmd))); + KT_EQ(t, start, 0); + KT_EQ(t, end, 3); + KT_STREQ(t, cmd, ""); +} + +KTEST(shellFindCommandSubstTruncatesToCapacity) +{ + int start, end; + char cmd[4]; + KT_TRUE(t, shellFindCommandSubst("$(longcommand)", &start, &end, cmd, sizeof(cmd))); + KT_STREQ(t, cmd, "lon"); +} + +KTEST(shellFindCommandSubstOnlyMatchesFirstOccurrence) +{ + /* No nested-paren support (documented in shell.h) -- the very NEXT ')' + after the opening '$(' ends the match, so a second, unrelated + `$(...)` later in the line is left completely untouched for a + caller's own repeat scan (shellExpandCommandSubst's own loop) to + find on a later pass, never folded into this one's own cmdOut. */ + int start, end; + char cmd[32]; + KT_TRUE(t, shellFindCommandSubst("$(a) $(b)", &start, &end, cmd, sizeof(cmd))); + KT_EQ(t, start, 0); + KT_EQ(t, end, 4); + KT_STREQ(t, cmd, "a"); +}