diff --git a/src/schedTest.c b/src/schedTest.c index 09ed3a1..5e6053e 100644 --- a/src/schedTest.c +++ b/src/schedTest.c @@ -1570,3 +1570,86 @@ KTEST(currentThreadEnvEntryExposesRawStorageForListing) KT_NULL(t, currentThreadEnvEntry(2)); KT_NULL(t, currentThreadEnvEntry(-1)); + threadAt(0)->envCount = 0; +} + +/* currentThreadUnsetEnv (sched.h) -- real unsetenv(3): a no-op success on a + missing name, a real compacting removal on a present one (never just a + tombstone, since currentThreadEnvEntry's own index space must stay dense + for shell.c's `env` listing to keep working after a removal in the + middle). */ +KTEST(currentThreadUnsetEnvOnMissingNameIsNoOpSuccess) +{ + threadAt(0)->envCount = 0; + + KT_TRUE(t, currentThreadSetEnv("FOO", "bar")); + KT_FALSE(t, currentThreadUnsetEnv("MISSING")); + KT_EQ(t, currentThreadEnvCount(), 1); + KT_STREQ(t, currentThreadGetEnv("FOO"), "bar"); + + threadAt(0)->envCount = 0; +} + +KTEST(currentThreadUnsetEnvRemovesLastEntry) +{ + threadAt(0)->envCount = 0; + + KT_TRUE(t, currentThreadSetEnv("A", "1")); + KT_TRUE(t, currentThreadSetEnv("B", "2")); + KT_TRUE(t, currentThreadUnsetEnv("B")); + KT_EQ(t, currentThreadEnvCount(), 1); + KT_STREQ(t, currentThreadGetEnv("A"), "1"); + KT_NULL(t, currentThreadGetEnv("B")); + + threadAt(0)->envCount = 0; +} + +KTEST(currentThreadUnsetEnvCompactsMiddleEntryDown) +{ + threadAt(0)->envCount = 0; + + /* Removing the MIDDLE of three entries must shift every LATER entry + down by one slot (memmove, not just marking a hole) -- otherwise + currentThreadEnvEntry(1) after this removal would either still show + the removed "B=2" or skip straight to garbage past envCount. */ + KT_TRUE(t, currentThreadSetEnv("A", "1")); + KT_TRUE(t, currentThreadSetEnv("B", "2")); + KT_TRUE(t, currentThreadSetEnv("C", "3")); + KT_TRUE(t, currentThreadUnsetEnv("B")); + + KT_EQ(t, currentThreadEnvCount(), 2); + KT_STREQ(t, currentThreadEnvEntry(0), "A=1"); + KT_STREQ(t, currentThreadEnvEntry(1), "C=3"); + KT_NULL(t, currentThreadEnvEntry(2)); + KT_NULL(t, currentThreadGetEnv("B")); + KT_STREQ(t, currentThreadGetEnv("A"), "1"); + KT_STREQ(t, currentThreadGetEnv("C"), "3"); + + threadAt(0)->envCount = 0; +} + +KTEST(currentThreadUnsetEnvActuallyFreesSlotForReuse) +{ + threadAt(0)->envCount = 0; + + /* Fill the table completely, remove one, then prove a genuinely NEW + name can now be set -- if unset only tombstoned the slot instead of + compacting envCount back down, this set would still see a "full" + table and fail. */ + for (int i = 0; i < envMaxVars; i++) { + char name[8]; + snprintf(name, sizeof(name), "V%d", i); + KT_TRUE(t, currentThreadSetEnv(name, "x")); + } + KT_EQ(t, currentThreadEnvCount(), envMaxVars); + + KT_TRUE(t, currentThreadUnsetEnv("V0")); + KT_EQ(t, currentThreadEnvCount(), envMaxVars - 1); + + KT_TRUE(t, currentThreadSetEnv("FRESH", "new")); + KT_EQ(t, currentThreadEnvCount(), envMaxVars); + KT_STREQ(t, currentThreadGetEnv("FRESH"), "new"); + KT_NULL(t, currentThreadGetEnv("V0")); + + threadAt(0)->envCount = 0; +}