]> Kevux Git Server - fll/commitdiff
Update: Add unit tests for fll_path.
authorKevin Day <kevin@kevux.org>
Thu, 16 Mar 2023 23:17:26 +0000 (18:17 -0500)
committerKevin Day <kevin@kevux.org>
Thu, 16 Mar 2023 23:20:14 +0000 (18:20 -0500)
I noticed that the '~' path is not expanded.
I checked to see if this is intentional or not.
It is intentionally not supported yet.
I need another function.

This check encouraged me to write some unit tests for the current behavior.
It seems that I had forgotten just how few unit tests I have.
Now there are at least more.

level_2/fll_path/c/path.c
level_2/fll_path/c/path.h
level_2/fll_path/data/build/dependencies-tests [new file with mode: 0644]
level_2/fll_path/data/build/settings-tests [new file with mode: 0644]
level_2/fll_path/data/build/testfile [new file with mode: 0644]
level_2/fll_path/tests/unit/c/test-path-canonical.c [new file with mode: 0644]
level_2/fll_path/tests/unit/c/test-path-canonical.h [new file with mode: 0644]
level_2/fll_path/tests/unit/c/test-path.c [new file with mode: 0644]
level_2/fll_path/tests/unit/c/test-path.h [new file with mode: 0644]

index f218c9a2d52b82407a0f528d9c4cbf2d187b6bf4..30cf64ae16290d9d2d0f657521d2c96131273dda 100644 (file)
@@ -36,9 +36,7 @@ extern "C" {
       status = f_path_current(F_true, canonical);
       if (F_status_is_error(status)) return status;
 
-      if (!path.string[0] || path.used == 1) {
-        return F_none;
-      }
+      if (!path.string[0]) return F_none;
 
       at = 0;
     }
index 49da446cde51ebfd94992ea88aa01ee14485f098..4ffa250f7e1cb3c15a8ce830e9d60a60e46c3fa9 100644 (file)
@@ -31,6 +31,7 @@ extern "C" {
  * This processes the relative parts: './', '../', and extra '/'.
  * This does not process symbolic links.
  * This has a max size of F_string_t_size_d.
+ * This removes trailing slashes, but leaves the leading slash ('/' remains as-is, but '/a/' becomes '/a').
  *
  * An empty path (first character is NULL or path.used is 0) appends only the current path to canonical.
  *
diff --git a/level_2/fll_path/data/build/dependencies-tests b/level_2/fll_path/data/build/dependencies-tests
new file mode 100644 (file)
index 0000000..dea3179
--- /dev/null
@@ -0,0 +1,3 @@
+# fss-0001
+
+cmocka 1.*
diff --git a/level_2/fll_path/data/build/settings-tests b/level_2/fll_path/data/build/settings-tests
new file mode 100644 (file)
index 0000000..bf78c7e
--- /dev/null
@@ -0,0 +1,50 @@
+# fss-0001
+
+build_name test-fll_path
+
+version_major 0
+version_minor 6
+version_micro 4
+version_file micro
+version_target minor
+
+modes individual clang test coverage
+modes_default individual test
+
+build_compiler gcc
+build_compiler-clang clang
+build_indexer ar
+build_indexer_arguments rcs
+build_language c
+
+build_libraries -lc -lcmocka
+build_libraries-individual -lf_memory -lf_path -lf_string -lfll_path
+
+build_sources_program test-path-canonical.c
+
+build_sources_program test-path.c
+
+build_script no
+build_shared yes
+build_static no
+
+path_headers tests/unit/c
+path_sources tests/unit/c
+
+has_path_standard no
+preserve_path_headers yes
+
+search_exclusive yes
+search_shared yes
+search_static yes
+
+defines -Ibuild/includes
+defines_static -Lbuild/libraries/static
+defines_shared -Lbuild/libraries/shared
+
+flags -O2 -z now -g -fdiagnostics-color=always -Wno-logical-not-parentheses -Wno-parentheses
+flags-clang -Wno-logical-op-parentheses
+flags-test -fstack-protector -Wall
+flags-coverage -O0 --coverage -fprofile-abs-path -fprofile-dir=build/coverage/
+
+flags_program -fPIE
diff --git a/level_2/fll_path/data/build/testfile b/level_2/fll_path/data/build/testfile
new file mode 100644 (file)
index 0000000..1e73743
--- /dev/null
@@ -0,0 +1,55 @@
+# fss-0005 iki-0002
+
+settings:
+  load_build yes
+  fail exit
+
+  environment PATH LD_LIBRARY_PATH
+  environment CMOCKA_XML_FILE CMOCKA_MESSAGE_OUTPUT CMOCKA_TEST_ABORT
+
+  # Cmcka is not fully thread-safe, set this to "1" to have cmocka call abort() on a test failure.
+  #CMOCKA_TEST_ABORT 1
+
+  # One of: STDOUT, SUBUNIT, TAP, or XML.
+  #define CMOCKA_MESSAGE_OUTPUT STDOUT
+
+  # When in "XML" output mode, output to this file rather than stdout.
+  #define CMOCKA_XML_FILE ./out.xml
+
+main:
+  build settings individual test
+  build settings-tests individual test
+
+  operate ld_library_path
+
+  if exist build/programs/shared/test-fll_path
+    shell build/programs/shared/test-fll_path
+
+  if exist build/programs/static/test-fll_path
+    shell build/programs/static/test-fll_path
+
+  if not exist build/programs/shared/test-fll_path
+  and not exist build/programs/static/test-fll_path
+    operate not_created
+
+not_created:
+  print
+  print 'context:"error"Failed to test due to being unable to find either a shared or static test binary to perform tests. context:"reset"'
+
+  exit failure
+
+ld_library_path:
+  if define LD_LIBRARY_PATH
+  and parameter work
+    define LD_LIBRARY_PATH 'build/libraries/shared:parameter:"work:value"libraries/shared:define:"LD_LIBRARY_PATH"'
+
+  else
+  if define LD_LIBRARY_PATH
+    define LD_LIBRARY_PATH 'build/libraries/shared:define:"LD_LIBRARY_PATH"'
+
+  else
+  if parameter work
+    define LD_LIBRARY_PATH 'build/libraries/shared:parameter:"work:value"libraries/shared'
+
+  else
+    define LD_LIBRARY_PATH build/libraries/shared
diff --git a/level_2/fll_path/tests/unit/c/test-path-canonical.c b/level_2/fll_path/tests/unit/c/test-path-canonical.c
new file mode 100644 (file)
index 0000000..03ab0de
--- /dev/null
@@ -0,0 +1,483 @@
+#include "test-path.h"
+#include "test-path-canonical.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void test__fll_path_canonical__back_paths(void **state) {
+
+  // Paths that don't begin with '/' or '../' will expand to the full PWD.
+  f_char_t pwd[PATH_MAX + 1];
+
+  memset(pwd, 0, PATH_MAX + 1);
+
+  getcwd(pwd, PATH_MAX);
+
+  const unsigned int pwd_length = strnlen(pwd, PATH_MAX);
+
+  const f_string_static_t contents[] = {
+    macro_f_string_static_t_initialize("a/../", 0, 5),
+    macro_f_string_static_t_initialize("a/../b", 0, 6),
+    macro_f_string_static_t_initialize("a/../b/", 0, 7),
+    macro_f_string_static_t_initialize("a/../b/c", 0, 8),
+    macro_f_string_static_t_initialize("a/../b/c/", 0, 9),
+    macro_f_string_static_t_initialize("a/../b/c//", 0, 10),
+    macro_f_string_static_t_initialize("a/.././", 0, 7),
+    macro_f_string_static_t_initialize("a/.././b", 0, 8),
+    macro_f_string_static_t_initialize("a/.././b/", 0, 9),
+    macro_f_string_static_t_initialize("a/.././b/c", 0, 10),
+    macro_f_string_static_t_initialize("a/.././b/c/", 0, 11),
+    macro_f_string_static_t_initialize("a/.././b/c//", 0, 12),
+    macro_f_string_static_t_initialize("a/.././/", 0, 8),
+    macro_f_string_static_t_initialize("a/.././/b", 0, 9),
+    macro_f_string_static_t_initialize("a/.././/b/", 0, 10),
+    macro_f_string_static_t_initialize("a/.././/b/c", 0, 11),
+    macro_f_string_static_t_initialize("a/.././/b/c/", 0, 12),
+    macro_f_string_static_t_initialize("a/.././/b/c//", 0, 13),
+    macro_f_string_static_t_initialize("/z/../", 0, 6),
+    macro_f_string_static_t_initialize("/z/../b", 0, 7),
+    macro_f_string_static_t_initialize("/z/../b/", 0, 8),
+    macro_f_string_static_t_initialize("/z/../b/c", 0, 9),
+    macro_f_string_static_t_initialize("/z/../b/c/", 0, 10),
+    macro_f_string_static_t_initialize("/z/../b/c//", 0, 11),
+  };
+
+  const f_string_static_t expected[] = {
+    macro_f_string_static_t_initialize("", 0, 0),
+    macro_f_string_static_t_initialize("b", 0, 1),
+    macro_f_string_static_t_initialize("b", 0, 1),
+    macro_f_string_static_t_initialize("b/c", 0, 3),
+    macro_f_string_static_t_initialize("b/c", 0, 3),
+    macro_f_string_static_t_initialize("b/c", 0, 3),
+    macro_f_string_static_t_initialize("", 0, 0),
+    macro_f_string_static_t_initialize("b", 0, 1),
+    macro_f_string_static_t_initialize("b", 0, 1),
+    macro_f_string_static_t_initialize("b/c", 0, 3),
+    macro_f_string_static_t_initialize("b/c", 0, 3),
+    macro_f_string_static_t_initialize("b/c", 0, 3),
+    macro_f_string_static_t_initialize("", 0, 0),
+    macro_f_string_static_t_initialize("b", 0, 1),
+    macro_f_string_static_t_initialize("b", 0, 1),
+    macro_f_string_static_t_initialize("b/c", 0, 3),
+    macro_f_string_static_t_initialize("b/c", 0, 3),
+    macro_f_string_static_t_initialize("b/c", 0, 3),
+    macro_f_string_static_t_initialize("/", 0, 1),
+    macro_f_string_static_t_initialize("/b", 0, 2),
+    macro_f_string_static_t_initialize("/b", 0, 2),
+    macro_f_string_static_t_initialize("/b/c", 0, 4),
+    macro_f_string_static_t_initialize("/b/c", 0, 4),
+    macro_f_string_static_t_initialize("/b/c", 0, 4),
+  };
+
+  const uint8_t prepend[] = {
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_false,
+    F_false,
+    F_false,
+    F_false,
+    F_false,
+    F_false,
+  };
+
+  f_string_dynamic_t path = f_string_dynamic_t_initialize;
+
+  for (uint8_t i = 0; i < 24; ++i) {
+
+    const f_status_t status = fll_path_canonical(contents[i], &path);
+
+    assert_int_equal(status, F_none);
+
+    // Assert_string_equal() is NULL terminated, so ensure NULL termination at end of path.used.
+    path.string[path.used] = 0;
+
+    if (prepend[i]) {
+      f_char_t prepended_string[pwd_length + expected[i].used + 2];
+      f_string_static_t prepended = macro_f_string_static_t_initialize(prepended_string, 0, pwd_length + expected[i].used);
+
+      prepended_string[prepended.used] = 0;
+      prepended_string[prepended.used + 1] = 0;
+
+      if (pwd_length) {
+        memcpy(prepended_string, pwd, pwd_length);
+      }
+
+      if (expected[i].used) {
+        if (pwd_length && prepended_string[pwd_length] != f_path_separator_s.string[0]) {
+          prepended_string[pwd_length] = f_path_separator_s.string[0];
+          memcpy(prepended_string + pwd_length + 1, expected[i].string, expected[i].used);
+          ++prepended.used;
+        }
+        else {
+          memcpy(prepended_string + pwd_length, expected[i].string, expected[i].used);
+        }
+      }
+
+      // The last slash is always removed.
+      if (prepended.used && prepended_string[prepended.used - 1] == f_path_separator_s.string[0]) {
+        prepended_string[prepended.used--] = 0;
+      }
+
+      assert_int_equal(prepended.used, path.used);
+      assert_string_equal(prepended.string, path.string);
+    }
+    else {
+      assert_int_equal(expected[i].used, path.used);
+      assert_string_equal(expected[i].string, path.string);
+    }
+  } // for
+
+  f_string_dynamic_resize(0, &path);
+}
+
+void test__fll_path_canonical__empty_becomes_pwd(void **state) {
+
+  f_char_t pwd[PATH_MAX + 1];
+
+  memset(pwd, 0, PATH_MAX + 1);
+
+  getcwd(pwd, PATH_MAX);
+
+  const unsigned int pwd_length = strnlen(pwd, PATH_MAX);
+
+  f_string_dynamic_t path = f_string_dynamic_t_initialize;
+
+  {
+    const f_status_t status = fll_path_canonical(f_string_empty_s, &path);
+
+    assert_int_equal(status, F_none);
+    assert_int_equal(pwd_length, path.used);
+    assert_string_equal(pwd, path.string);
+  } // for
+
+  f_string_dynamic_resize(0, &path);
+}
+
+void test__fll_path_canonical__present_paths(void **state) {
+
+  // Paths that don't begin with '/' or '../' will expand to the full PWD.
+  f_char_t pwd[PATH_MAX + 1];
+
+  memset(pwd, 0, PATH_MAX + 1);
+
+  getcwd(pwd, PATH_MAX);
+
+  const unsigned int pwd_length = strnlen(pwd, PATH_MAX);
+
+  const f_string_static_t contents[] = {
+    macro_f_string_static_t_initialize("", 0, 1),
+    macro_f_string_static_t_initialize("a", 0, 2),
+    macro_f_string_static_t_initialize("a/", 0, 3),
+    macro_f_string_static_t_initialize("a/b", 0, 4),
+    macro_f_string_static_t_initialize("a/b/", 0, 5),
+    macro_f_string_static_t_initialize("a/b//", 0, 6),
+    macro_f_string_static_t_initialize("./", 0, 2),
+    macro_f_string_static_t_initialize("./a", 0, 3),
+    macro_f_string_static_t_initialize("./a/", 0, 4),
+    macro_f_string_static_t_initialize("./a/b", 0, 5),
+    macro_f_string_static_t_initialize("./a/b/", 0, 6),
+    macro_f_string_static_t_initialize("./a/b//", 0, 7),
+    macro_f_string_static_t_initialize(".//", 0, 3),
+    macro_f_string_static_t_initialize(".//a", 0, 4),
+    macro_f_string_static_t_initialize(".//a/", 0, 5),
+    macro_f_string_static_t_initialize(".//a/b", 0, 6),
+    macro_f_string_static_t_initialize(".//a/b/", 0, 7),
+    macro_f_string_static_t_initialize(".//a/b//", 0, 8),
+  };
+
+  const f_string_static_t expected[] = {
+    macro_f_string_static_t_initialize("", 0, 0),
+    macro_f_string_static_t_initialize("a", 0, 1),
+    macro_f_string_static_t_initialize("a", 0, 1),
+    macro_f_string_static_t_initialize("a/b", 0, 3),
+    macro_f_string_static_t_initialize("a/b", 0, 3),
+    macro_f_string_static_t_initialize("a/b", 0, 3),
+    macro_f_string_static_t_initialize("", 0, 0),
+    macro_f_string_static_t_initialize("a", 0, 1),
+    macro_f_string_static_t_initialize("a", 0, 1),
+    macro_f_string_static_t_initialize("a/b", 0, 3),
+    macro_f_string_static_t_initialize("a/b", 0, 3),
+    macro_f_string_static_t_initialize("a/b", 0, 3),
+    macro_f_string_static_t_initialize("", 0, 0),
+    macro_f_string_static_t_initialize("a", 0, 1),
+    macro_f_string_static_t_initialize("a", 0, 1),
+    macro_f_string_static_t_initialize("a/b", 0, 3),
+    macro_f_string_static_t_initialize("a/b", 0, 3),
+    macro_f_string_static_t_initialize("a/b", 0, 3),
+  };
+
+  f_string_dynamic_t path = f_string_dynamic_t_initialize;
+
+  for (uint8_t i = 0; i < 18; ++i) {
+
+    const f_status_t status = fll_path_canonical(contents[i], &path);
+
+    assert_int_equal(status, F_none);
+
+    // Assert_string_equal() is NULL terminated, so ensure NULL termination at end of path.used.
+    path.string[path.used] = 0;
+
+    f_char_t prepended_string[pwd_length + expected[i].used + 2];
+    f_string_static_t prepended = macro_f_string_static_t_initialize(prepended_string, 0, pwd_length + expected[i].used);
+
+    prepended_string[prepended.used] = 0;
+    prepended_string[prepended.used + 1] = 0;
+
+    if (pwd_length) {
+      memcpy(prepended_string, pwd, pwd_length);
+    }
+
+    if (expected[i].used) {
+      if (pwd_length && prepended_string[pwd_length] != f_path_separator_s.string[0]) {
+        prepended_string[pwd_length] = f_path_separator_s.string[0];
+        memcpy(prepended_string + pwd_length + 1, expected[i].string, expected[i].used);
+        ++prepended.used;
+      }
+      else {
+        memcpy(prepended_string + pwd_length, expected[i].string, expected[i].used);
+      }
+    }
+
+    // The last slash is always removed.
+    if (prepended.used && prepended_string[prepended.used - 1] == f_path_separator_s.string[0]) {
+      prepended_string[prepended.used--] = 0;
+    }
+
+    assert_int_equal(prepended.used, path.used);
+    assert_string_equal(prepended.string, path.string);
+  } // for
+
+  f_string_dynamic_resize(0, &path);
+}
+
+void test__fll_path_canonical__root_paths(void **state) {
+
+  const f_string_static_t contents[] = {
+    macro_f_string_static_t_initialize("/", 0, 1),
+    macro_f_string_static_t_initialize("//", 0, 2),
+    macro_f_string_static_t_initialize("///", 0, 3),
+    macro_f_string_static_t_initialize("/a", 0, 2),
+    macro_f_string_static_t_initialize("//a", 0, 3),
+    macro_f_string_static_t_initialize("///a", 0, 4),
+    macro_f_string_static_t_initialize("/a/", 0, 3),
+    macro_f_string_static_t_initialize("//a/", 0, 4),
+    macro_f_string_static_t_initialize("///a/", 0, 5),
+    macro_f_string_static_t_initialize("/a/b", 0, 4),
+    macro_f_string_static_t_initialize("//a/b", 0, 5),
+    macro_f_string_static_t_initialize("///a/b", 0, 6),
+    macro_f_string_static_t_initialize("/a/b/", 0, 5),
+    macro_f_string_static_t_initialize("//a/b/", 0, 6),
+    macro_f_string_static_t_initialize("///a/b/", 0, 7),
+    macro_f_string_static_t_initialize("/a/b//", 0, 6),
+    macro_f_string_static_t_initialize("//a/b//", 0, 7),
+    macro_f_string_static_t_initialize("///a/b//", 0, 8),
+  };
+
+  const f_string_static_t expected[] = {
+    macro_f_string_static_t_initialize("/", 0, 1),
+    macro_f_string_static_t_initialize("/", 0, 1),
+    macro_f_string_static_t_initialize("/", 0, 1),
+    macro_f_string_static_t_initialize("/a", 0, 2),
+    macro_f_string_static_t_initialize("/a", 0, 2),
+    macro_f_string_static_t_initialize("/a", 0, 2),
+    macro_f_string_static_t_initialize("/a", 0, 2),
+    macro_f_string_static_t_initialize("/a", 0, 2),
+    macro_f_string_static_t_initialize("/a", 0, 2),
+    macro_f_string_static_t_initialize("/a/b", 0, 4),
+    macro_f_string_static_t_initialize("/a/b", 0, 4),
+    macro_f_string_static_t_initialize("/a/b", 0, 4),
+    macro_f_string_static_t_initialize("/a/b", 0, 4),
+    macro_f_string_static_t_initialize("/a/b", 0, 4),
+    macro_f_string_static_t_initialize("/a/b", 0, 4),
+    macro_f_string_static_t_initialize("/a/b", 0, 4),
+    macro_f_string_static_t_initialize("/a/b", 0, 4),
+    macro_f_string_static_t_initialize("/a/b", 0, 4),
+  };
+
+  f_string_dynamic_t path = f_string_dynamic_t_initialize;
+
+  for (uint8_t i = 0; i < 18; ++i) {
+
+    const f_status_t status = fll_path_canonical(contents[i], &path);
+
+    // Assert_string_equal() is NULL terminated, so ensure NULL termination at end of path.used.
+    if (status == F_none) {
+      path.string[path.used] = 0;
+    }
+
+    assert_int_equal(status, F_none);
+    assert_int_equal(expected[i].used, path.used);
+    assert_string_equal(expected[i].string, path.string);
+  } // for
+
+  f_string_dynamic_resize(0, &path);
+}
+
+void test__fll_path_canonical__tilde_remains(void **state) {
+
+  // Paths that don't begin with '/' or '../' will expand to the full PWD.
+  f_char_t pwd[PATH_MAX + 1];
+
+  memset(pwd, 0, PATH_MAX + 1);
+
+  getcwd(pwd, PATH_MAX);
+
+  const unsigned int pwd_length = strnlen(pwd, PATH_MAX);
+
+  const f_string_static_t contents[] = {
+    macro_f_string_static_t_initialize("~", 0, 1),
+    macro_f_string_static_t_initialize("~/", 0, 2),
+    macro_f_string_static_t_initialize("~//", 0, 3),
+    macro_f_string_static_t_initialize("~a", 0, 2),
+    macro_f_string_static_t_initialize("a~", 0, 2),
+    macro_f_string_static_t_initialize("a~b", 0, 3),
+    macro_f_string_static_t_initialize("/~", 0, 2),
+    macro_f_string_static_t_initialize("//~", 0, 3),
+    macro_f_string_static_t_initialize("~ ", 0, 2),
+    macro_f_string_static_t_initialize("~ /", 0, 3),
+    macro_f_string_static_t_initialize("./~", 0, 3),
+    macro_f_string_static_t_initialize("./~a", 0, 4),
+    macro_f_string_static_t_initialize("./a~", 0, 4),
+    macro_f_string_static_t_initialize("./a~b", 0, 5),
+    macro_f_string_static_t_initialize("a/~/b", 0, 5),
+    macro_f_string_static_t_initialize("a/~b", 0, 4),
+    macro_f_string_static_t_initialize("a/~b/c", 0, 6),
+    macro_f_string_static_t_initialize("/a/~/b", 0, 6),
+    macro_f_string_static_t_initialize("/a/~b", 0, 5),
+    macro_f_string_static_t_initialize("/a/~b/c", 0, 7),
+    macro_f_string_static_t_initialize("//a/~/b", 0, 7),
+    macro_f_string_static_t_initialize("//a/~b", 0, 6),
+    macro_f_string_static_t_initialize("//a/~b/c", 0, 8),
+  };
+
+  const f_string_static_t expected[] = {
+    macro_f_string_static_t_initialize("~", 0, 1),
+    macro_f_string_static_t_initialize("~", 0, 1),
+    macro_f_string_static_t_initialize("~", 0, 1),
+    macro_f_string_static_t_initialize("~a", 0, 2),
+    macro_f_string_static_t_initialize("a~", 0, 2),
+    macro_f_string_static_t_initialize("a~b", 0, 3),
+    macro_f_string_static_t_initialize("/~", 0, 2),
+    macro_f_string_static_t_initialize("/~", 0, 2),
+    macro_f_string_static_t_initialize("~ ", 0, 2),
+    macro_f_string_static_t_initialize("~ ", 0, 2),
+    macro_f_string_static_t_initialize("~", 0, 1),
+    macro_f_string_static_t_initialize("~a", 0, 2),
+    macro_f_string_static_t_initialize("a~", 0, 2),
+    macro_f_string_static_t_initialize("a~b", 0, 3),
+    macro_f_string_static_t_initialize("a/~/b", 0, 5),
+    macro_f_string_static_t_initialize("a/~b", 0, 4),
+    macro_f_string_static_t_initialize("a/~b/c", 0, 6),
+    macro_f_string_static_t_initialize("/a/~/b", 0, 6),
+    macro_f_string_static_t_initialize("/a/~b", 0, 5),
+    macro_f_string_static_t_initialize("/a/~b/c", 0, 7),
+    macro_f_string_static_t_initialize("/a/~/b", 0, 6),
+    macro_f_string_static_t_initialize("/a/~b", 0, 5),
+    macro_f_string_static_t_initialize("/a/~b/c", 0, 7),
+  };
+
+  const uint8_t prepend[] = {
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_false,
+    F_false,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_true,
+    F_false,
+    F_false,
+    F_false,
+    F_false,
+    F_false,
+    F_false,
+  };
+
+  f_string_dynamic_t path = f_string_dynamic_t_initialize;
+
+  for (uint8_t i = 0; i < 23; ++i) {
+
+    const f_status_t status = fll_path_canonical(contents[i], &path);
+
+    assert_int_equal(status, F_none);
+
+    // Assert_string_equal() is NULL terminated, so ensure NULL termination at end of path.used.
+    path.string[path.used] = 0;
+
+    if (prepend[i]) {
+      f_char_t prepended_string[pwd_length + expected[i].used + 2];
+      f_string_static_t prepended = macro_f_string_static_t_initialize(prepended_string, 0, pwd_length + expected[i].used);
+
+      prepended_string[prepended.used] = 0;
+      prepended_string[prepended.used + 1] = 0;
+
+      if (pwd_length) {
+        memcpy(prepended_string, pwd, pwd_length);
+      }
+
+      if (expected[i].used) {
+        if (pwd_length && prepended_string[pwd_length] != f_path_separator_s.string[0]) {
+          prepended_string[pwd_length] = f_path_separator_s.string[0];
+          memcpy(prepended_string + pwd_length + 1, expected[i].string, expected[i].used);
+          ++prepended.used;
+        }
+        else {
+          memcpy(prepended_string + pwd_length, expected[i].string, expected[i].used);
+        }
+      }
+
+      // The last slash is always removed.
+      if (prepended.used && prepended_string[prepended.used - 1] == f_path_separator_s.string[0]) {
+        prepended_string[prepended.used--] = 0;
+      }
+
+      assert_int_equal(prepended.used, path.used);
+      assert_string_equal(prepended.string, path.string);
+    }
+    else {
+      assert_int_equal(expected[i].used, path.used);
+      assert_string_equal(expected[i].string, path.string);
+    }
+  } // for
+
+  f_string_dynamic_resize(0, &path);
+}
+
+void test__fll_path_canonical__parameter_checking(void **state) {
+
+  {
+    const f_status_t status = fll_path_canonical(f_string_empty_s, 0);
+
+    assert_int_equal(status, F_status_set_error(F_parameter));
+  }
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/level_2/fll_path/tests/unit/c/test-path-canonical.h b/level_2/fll_path/tests/unit/c/test-path-canonical.h
new file mode 100644 (file)
index 0000000..6114e90
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * FLL - Level 2
+ *
+ * Project: IKI
+ * API Version: 0.6
+ * Licenses: lgpl-2.1-or-later
+ *
+ * Test the fll_path project.
+ */
+#ifndef _TEST__FLL_path_canonical_h
+#define _TEST__FLL_path_canonical_h
+
+/**
+ * Test that function works with back paths (such as '../').
+ *
+ * @see fll_path_canonical()
+ */
+extern void test__fll_path_canonical__back_paths(void **state);
+
+/**
+ * Test that function returns the present working directory when path is an empty string.
+ *
+ * @see fll_path_canonical()
+ */
+extern void test__fll_path_canonical__empty_becomes_pwd(void **state);
+
+/**
+ * Test that function works with present directory paths.
+ *
+ * @see fll_path_canonical()
+ */
+extern void test__fll_path_canonical__present_paths(void **state);
+
+/**
+ * Test that function works with root paths.
+ *
+ * @see fll_path_canonical()
+ */
+extern void test__fll_path_canonical__root_paths(void **state);
+
+/**
+ * Test that function returns the string without expanding the tilde.
+ *
+ * @see fll_path_canonical()
+ */
+extern void test__fll_path_canonical__tilde_remains(void **state);
+
+/**
+ * Test that parameter checking works as expected.
+ *
+ * @see fll_path_canonical()
+ */
+extern void test__fll_path_canonical__parameter_checking(void **state);
+
+#endif // _TEST__FLL_path_canonical_h
diff --git a/level_2/fll_path/tests/unit/c/test-path.c b/level_2/fll_path/tests/unit/c/test-path.c
new file mode 100644 (file)
index 0000000..8b362bb
--- /dev/null
@@ -0,0 +1,38 @@
+#include "test-path.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int setup(void **state) {
+
+  return 0;
+}
+
+int setdown(void **state) {
+
+  errno = 0;
+
+  return 0;
+}
+
+int main(void) {
+
+  const struct CMUnitTest tests[] = {
+    cmocka_unit_test(test__fll_path_canonical__back_paths),
+    cmocka_unit_test(test__fll_path_canonical__empty_becomes_pwd),
+    cmocka_unit_test(test__fll_path_canonical__present_paths),
+    cmocka_unit_test(test__fll_path_canonical__root_paths),
+    cmocka_unit_test(test__fll_path_canonical__tilde_remains),
+
+    #ifndef _di_level_0_parameter_checking_
+      cmocka_unit_test(test__fll_path_canonical__parameter_checking),
+    #endif // _di_level_0_parameter_checking_
+  };
+
+  return cmocka_run_group_tests(tests, setup, setdown);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/level_2/fll_path/tests/unit/c/test-path.h b/level_2/fll_path/tests/unit/c/test-path.h
new file mode 100644 (file)
index 0000000..9e13f2e
--- /dev/null
@@ -0,0 +1,74 @@
+/**
+ * FLL - Level 2
+ *
+ * Project: IKI
+ * API Version: 0.6
+ * Licenses: lgpl-2.1-or-later
+ *
+ * Test the fll_path project.
+ */
+#ifndef _TEST__FLL_path_h
+#define _TEST__FLL_path_h
+
+// Libc includes.
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+// cmocka includes.
+#include <cmocka.h>
+
+// FLL-2 includes.
+#include <fll/level_2/path.h>
+
+// Mock includes.
+//#include "mock-path.h"
+
+// Test includes.
+#include "test-path-canonical.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Perform any setup operations.
+ *
+ * @param state
+ *   The test state.
+ *
+ * @return
+ *   The status of this function, where 0 means success.
+ */
+extern int setup(void **state);
+
+/**
+ * Peform any setdown operations.
+ *
+ * @param state
+ *   The test state.
+ *
+ * @return
+ *   The status of this function, where 0 means success.
+ */
+extern int setdown(void **state);
+
+/**
+ * Run all tests.
+ *
+ * @return
+ *   The final result of the tests.
+ *
+ * @see cmocka_run_group_tests()
+ * @see cmocka_unit_test()
+ */
+extern int main(void);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // _TEST__FLL_path_h