aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Geissler <peter@haxx.in>2026-08-28 15:10:24 +0200
committerPeter Geissler <peter@haxx.in>2026-08-28 15:10:24 +0200
commit1e84ca2a1e521ec3ffcb77f1a26e025e55aab90c (patch)
tree032f59ec09c1481af9aa74e929b92adb615d1005
parentpath: a directory ends at either separator on Windows (diff)
downloadslosh-1e84ca2a1e521ec3ffcb77f1a26e025e55aab90c.tar.gz
slosh-1e84ca2a1e521ec3ffcb77f1a26e025e55aab90c.tar.xz
slosh-1e84ca2a1e521ec3ffcb77f1a26e025e55aab90c.zip
path: unit tests for the two spellings
The functions the last two commits fixed are only interesting on the platform the suite does not run on, which is how they came to be wrong in the first place. So the Windows cases are asserted under #ifdef and the POSIX ones assert the opposite: that a drive letter is an ordinary name here, that a backslash is an ordinary character, that neither rule leaked across. That second half is the one worth having. Both fixes are guarded, and a guard is exactly the kind of thing a later edit removes without meaning to -- `\` splitting a POSIX path would invent a directory out of a file called `a\b`, and nothing else in the suite would notice. Pure functions, so it joins the C unit tests rather than driving a session. Run on the Windows box as well, cross-compiled: 28 checks there, 20 here, the difference being the cases each platform has.
-rw-r--r--Makefile12
-rw-r--r--tests/path_test.c118
2 files changed, 127 insertions, 3 deletions
diff --git a/Makefile b/Makefile
index 2a06e94..df5d414 100644
--- a/Makefile
+++ b/Makefile
@@ -139,6 +139,12 @@ $(KDL_TEST): tests/kdl_test.c src/kdl.c | build
$(call say,[LD],$@)
$(Q)$(CC) $(CFLAGS) tests/kdl_test.c src/kdl.c -o $@
+PATH_TEST := build/path_test
+
+$(PATH_TEST): tests/path_test.c src/path.c | build
+ $(call say,[LD],$@)
+ $(Q)$(CC) $(CFLAGS) tests/path_test.c src/path.c -o $@
+
EXPR_TEST := build/expr_test
$(EXPR_TEST): tests/expr_test.c src/expr.c | build
@@ -187,10 +193,10 @@ build/.pass-%: tests/%.py tests/harness.py $(BIN) | build
@printf ' ok %-24s %s\n' "$(notdir $<)" "$$(grep -c '^ok' build/.log-$* 2>/dev/null || echo ?) checks"
@touch $@
-test: $(BIN) $(TEST_BIN) $(KDL_TEST) $(SHADER_TEST) $(EXPR_TEST) $(EXPR_EVAL) ## unit + headless checks (fast)
+test: $(BIN) $(TEST_BIN) $(KDL_TEST) $(SHADER_TEST) $(EXPR_TEST) $(EXPR_EVAL) $(PATH_TEST) ## unit + headless checks (fast)
@./$(TEST_BIN) >/dev/null && ./$(KDL_TEST) >/dev/null && ./$(SHADER_TEST) >/dev/null \
- && ./$(EXPR_TEST) >/dev/null \
- && printf ' ok %-24s %s\n' "C unit tests" "4 binaries"
+ && ./$(EXPR_TEST) >/dev/null && ./$(PATH_TEST) >/dev/null \
+ && printf ' ok %-24s %s\n' "C unit tests" "5 binaries"
@$(MAKE) --no-print-directory -j$(JOBS) $(PY_STAMPS)
@printf '\nall green\n'
diff --git a/tests/path_test.c b/tests/path_test.c
new file mode 100644
index 0000000..80c0396
--- /dev/null
+++ b/tests/path_test.c
@@ -0,0 +1,118 @@
+/* Paths, as the two platforms spell them.
+ *
+ * Windows takes `/` and `\` and mixes them without being asked, and calls a
+ * path absolute when it starts with a drive. POSIX does neither: `\` is an
+ * ordinary character in a file name there, and `C:` an ordinary directory.
+ * So half of what follows asserts that the Windows rules are *not* applied on
+ * POSIX -- which is the half that would otherwise rot, because the platform
+ * where these functions are interesting is not the platform the suite runs on.
+ */
+#include "slosh.h"
+
+#include <stdio.h>
+#include <string.h>
+
+static int fails = 0;
+
+static void ok(const char *name, bool cond, const char *detail) {
+ if (!cond) fails++;
+ printf("%s %-52s %s\n", cond ? "ok " : "FAIL", name, cond ? "" : detail);
+}
+
+static void eq(const char *name, const char *got, const char *want) {
+ bool same = got && want && strcmp(got, want) == 0;
+ char detail[512];
+ snprintf(detail, sizeof detail, "got %s, wanted %s", got ? got : "(null)",
+ want);
+ ok(name, same, detail);
+}
+
+int main(void) {
+ char buf[512];
+
+ /* ---- absoluteness ----------------------------------------------------- */
+ ok("a leading slash is absolute", path_is_absolute("/etc/slosh.kdl"), "");
+ ok("a bare name is not", !path_is_absolute("themes/t.kdl"), "");
+ ok("a dotted name is not", !path_is_absolute("./themes/t.kdl"), "");
+ ok("an empty path is not", !path_is_absolute(""), "");
+ ok("NULL is not", !path_is_absolute(NULL), "");
+
+#ifdef _WIN32
+ ok("a drive with a slash is absolute", path_is_absolute("C:/themes"), "");
+ ok("a drive with a backslash is absolute", path_is_absolute("C:\\themes"),
+ "");
+ ok("a lowercase drive counts", path_is_absolute("d:/x"), "");
+ ok("a drive-relative root is absolute", path_is_absolute("\\themes"), "");
+ ok("a UNC share is absolute", path_is_absolute("\\\\host\\share\\t.kdl"), "");
+ /* `C:t.kdl` is relative to the current directory *of drive C*, which is a
+ * real thing and not one we can resolve for anybody. */
+ ok("a drive without a separator is not", !path_is_absolute("C:t.kdl"), "");
+#else
+ /* The whole reason the drive rules are #ifdef'd: these are ordinary
+ * relative paths here, and reading them as absolute would break the
+ * platform that works in order to fix the one that did not. */
+ ok("a drive letter is just a name on POSIX", !path_is_absolute("C:/themes"),
+ "");
+ ok("a backslash is just a character on POSIX", !path_is_absolute("\\themes"),
+ "");
+#endif
+
+ /* ---- splitting -------------------------------------------------------- */
+ eq("the directory of a nested path", path_dir("/a/b/c.kdl", buf, sizeof buf),
+ "/a/b");
+ eq("the name of a nested path", path_base("/a/b/c.kdl"), "c.kdl");
+ eq("a bare name is in the current directory", path_dir("c.kdl", buf,
+ sizeof buf),
+ ".");
+ eq("and is its own name", path_base("c.kdl"), "c.kdl");
+ eq("a path at the root", path_dir("/c.kdl", buf, sizeof buf), "/");
+ eq("its name", path_base("/c.kdl"), "c.kdl");
+
+#ifdef _WIN32
+ eq("a backslash separates", path_dir("C:\\Users\\you\\config.kdl", buf,
+ sizeof buf),
+ "C:\\Users\\you");
+ eq("and names", path_base("C:\\Users\\you\\config.kdl"), "config.kdl");
+ /* The mixture is not hypothetical: slosh joins with '/' and $HOME arrives
+ * with '\', so the default config path is spelled both ways at once. */
+ eq("the last separator wins in a mixed path",
+ path_dir("C:\\Users\\you/.config/slosh/config.kdl", buf, sizeof buf),
+ "C:\\Users\\you/.config/slosh");
+ eq("and the other order", path_dir("C:/Users/you\\config.kdl", buf,
+ sizeof buf),
+ "C:/Users/you");
+ /* `C:` alone is the current directory on drive C, which is somewhere else. */
+ eq("a file at a drive root keeps the separator",
+ path_dir("C:\\config.kdl", buf, sizeof buf), "C:\\");
+ eq("forward-slash spelling too", path_dir("C:/config.kdl", buf, sizeof buf),
+ "C:/");
+#else
+ /* A POSIX file may legitimately be called `a\b`; splitting there would
+ * invent a directory that does not exist. */
+ eq("a backslash does not split on POSIX", path_dir("a\\b", buf, sizeof buf),
+ ".");
+ eq("nor name", path_base("a\\b"), "a\\b");
+ eq("a colon is not a drive on POSIX", path_dir("a:/b", buf, sizeof buf),
+ "a:");
+#endif
+
+ /* ---- resolving against a base ----------------------------------------- */
+ eq("a relative path joins the base",
+ path_resolve("themes/t.kdl", "/cfg", buf, sizeof buf), "/cfg/themes/t.kdl");
+ eq("an absolute path ignores the base",
+ path_resolve("/etc/t.kdl", "/cfg", buf, sizeof buf), "/etc/t.kdl");
+ eq("no base leaves it alone",
+ path_resolve("themes/t.kdl", NULL, buf, sizeof buf), "themes/t.kdl");
+ eq("`.` is the base itself", path_resolve(".", "/cfg", buf, sizeof buf),
+ "/cfg");
+#ifdef _WIN32
+ /* The bug this was written for: joined instead of taken as it stood, and
+ * opened as C:/cfg/C:/themes/t.kdl. */
+ eq("a drive path ignores the base",
+ path_resolve("C:/themes/t.kdl", "C:/cfg", buf, sizeof buf),
+ "C:/themes/t.kdl");
+#endif
+
+ if (fails) printf("\n%d failed\n", fails);
+ return fails ? 1 : 0;
+}