summaryrefslogtreecommitdiffstats
path: root/tests/util
diff options
context:
space:
mode:
Diffstat (limited to 'tests/util')
-rw-r--r--tests/util/CMakeLists.txt68
-rw-r--r--tests/util/alloc.c68
-rw-r--r--tests/util/array.c57
-rw-r--r--tests/util/assert.c117
-rw-r--r--tests/util/bitvec.c64
-rw-r--r--tests/util/copy.c162
-rw-r--r--tests/util/crlf.h30
-rw-r--r--tests/util/dirent.c306
-rw-r--r--tests/util/encoding.c42
-rw-r--r--tests/util/errors.c222
-rw-r--r--tests/util/filebuf.c267
-rw-r--r--tests/util/ftruncate.c48
-rw-r--r--tests/util/futils.c115
-rw-r--r--tests/util/gitstr.c1044
-rw-r--r--tests/util/hex.c22
-rw-r--r--tests/util/hostname.c13
-rw-r--r--tests/util/iconv.c78
-rw-r--r--tests/util/init.c54
-rw-r--r--tests/util/integer.c253
-rw-r--r--tests/util/link.c630
-rw-r--r--tests/util/memmem.c46
-rw-r--r--tests/util/mkdir.c291
-rw-r--r--tests/util/path.c768
-rw-r--r--tests/util/path/core.c343
-rw-r--r--tests/util/path/win32.c282
-rw-r--r--tests/util/pool.c62
-rw-r--r--tests/util/posix.c238
-rw-r--r--tests/util/pqueue.c150
-rw-r--r--tests/util/precompiled.c1
-rw-r--r--tests/util/precompiled.h3
-rw-r--r--tests/util/qsort.c90
-rw-r--r--tests/util/regexp.c197
-rw-r--r--tests/util/rmdir.c120
-rw-r--r--tests/util/sha1.c100
-rw-r--r--tests/util/sha256.c113
-rw-r--r--tests/util/sortedcache.c363
-rw-r--r--tests/util/stat.c113
-rw-r--r--tests/util/str/basic.c50
-rw-r--r--tests/util/str/oom.c71
-rw-r--r--tests/util/str/percent.c48
-rw-r--r--tests/util/str/quote.c87
-rw-r--r--tests/util/str/splice.c92
-rw-r--r--tests/util/string.c136
-rw-r--r--tests/util/strmap.c190
-rw-r--r--tests/util/strtol.c128
-rw-r--r--tests/util/url/http.c752
-rw-r--r--tests/util/url/joinpath.c193
-rw-r--r--tests/util/url/parse.c805
-rw-r--r--tests/util/url/pattern.c103
-rw-r--r--tests/util/url/redirect.c146
-rw-r--r--tests/util/url/scp.c317
-rw-r--r--tests/util/url/valid.c17
-rw-r--r--tests/util/utf8.c20
-rw-r--r--tests/util/vector.c430
-rw-r--r--tests/util/wildmatch.c248
-rw-r--r--tests/util/zstream.c167
56 files changed, 10940 insertions, 0 deletions
diff --git a/tests/util/CMakeLists.txt b/tests/util/CMakeLists.txt
new file mode 100644
index 0000000..232590f
--- /dev/null
+++ b/tests/util/CMakeLists.txt
@@ -0,0 +1,68 @@
+# util: the unit tests for libgit2's utility library
+
+set(Python_ADDITIONAL_VERSIONS 3 2.7)
+find_package(PythonInterp)
+
+if(NOT PYTHONINTERP_FOUND)
+ message(FATAL_ERROR "Could not find a python interpeter, which is needed to build the tests. "
+ "Make sure python is available, or pass -DBUILD_TESTS=OFF to skip building the tests")
+ENDIF()
+
+set(CLAR_PATH "${libgit2_SOURCE_DIR}/tests/clar")
+set(CLAR_FIXTURES "${libgit2_SOURCE_DIR}/tests/resources/")
+set(TEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
+add_definitions(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\")
+add_definitions(-DCLAR_TMPDIR=\"libgit2_tests\")
+add_definitions(-DCLAR_WIN32_LONGPATHS)
+add_definitions(-D_FILE_OFFSET_BITS=64)
+
+# Ensure that we do not use deprecated functions internally
+add_definitions(-DGIT_DEPRECATE_HARD)
+
+set(TEST_INCLUDES "${CLAR_PATH}" "${TEST_PATH}" "${CMAKE_CURRENT_BINARY_DIR}")
+file(GLOB_RECURSE SRC_TEST ${TEST_PATH}/*.c ${TEST_PATH}/*.h ${TEST_PATH}/*/*.c ${TEST_PATH}/*/*.h)
+file(GLOB_RECURSE SRC_CLAR ${CLAR_PATH}/*.c ${CLAR_PATH}/*.h)
+
+if(MSVC_IDE)
+ list(APPEND SRC_TEST "precompiled.c")
+endif()
+
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/clar.suite ${CMAKE_CURRENT_BINARY_DIR}/clar_suite.h
+ COMMAND ${PYTHON_EXECUTABLE} ${CLAR_PATH}/generate.py -o "${CMAKE_CURRENT_BINARY_DIR}" -f .
+ DEPENDS ${SRC_TEST}
+ WORKING_DIRECTORY ${TEST_PATH})
+
+set_source_files_properties(
+ ${CLAR_PATH}/clar.c
+ PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/clar.suite)
+
+add_executable(util_tests ${SRC_CLAR} ${SRC_TEST} ${LIBGIT2_OBJECTS})
+
+set_target_properties(util_tests PROPERTIES C_STANDARD 90)
+set_target_properties(util_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR})
+
+target_include_directories(util_tests PRIVATE ${TEST_INCLUDES} ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES})
+target_include_directories(util_tests SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES})
+target_link_libraries(util_tests ${LIBGIT2_SYSTEM_LIBS})
+
+ide_split_sources(util_tests)
+
+#
+# Old versions of gcc require us to declare our test functions; don't do
+# this on newer compilers to avoid unnecessary recompilation.
+#
+if(CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
+ target_compile_options(util_tests PRIVATE -include "clar_suite.h")
+endif()
+
+if(MSVC_IDE)
+ # Precompiled headers
+ set_target_properties(util_tests PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h")
+ set_source_files_properties("precompiled.c" COMPILE_FLAGS "/Ycprecompiled.h")
+endif()
+
+enable_testing()
+
+include(AddClarTest)
+add_clar_test(util_tests util -v)
diff --git a/tests/util/alloc.c b/tests/util/alloc.c
new file mode 100644
index 0000000..492394a
--- /dev/null
+++ b/tests/util/alloc.c
@@ -0,0 +1,68 @@
+#include "clar_libgit2.h"
+#include "clar_libgit2_alloc.h"
+#include "alloc.h"
+
+void test_alloc__cleanup(void)
+{
+ cl_alloc_reset();
+}
+
+void test_alloc__oom(void)
+{
+ void *ptr = NULL;
+
+ cl_alloc_limit(0);
+
+ cl_assert(git__malloc(1) == NULL);
+ cl_assert(git__calloc(1, 1) == NULL);
+ cl_assert(git__realloc(ptr, 1) == NULL);
+ cl_assert(git__strdup("test") == NULL);
+ cl_assert(git__strndup("test", 4) == NULL);
+}
+
+void test_alloc__single_byte_is_exhausted(void)
+{
+ void *ptr;
+
+ cl_alloc_limit(1);
+
+ cl_assert(ptr = git__malloc(1));
+ cl_assert(git__malloc(1) == NULL);
+ git__free(ptr);
+}
+
+void test_alloc__free_replenishes_byte(void)
+{
+ void *ptr;
+
+ cl_alloc_limit(1);
+
+ cl_assert(ptr = git__malloc(1));
+ cl_assert(git__malloc(1) == NULL);
+ git__free(ptr);
+ cl_assert(ptr = git__malloc(1));
+ git__free(ptr);
+}
+
+void test_alloc__realloc(void)
+{
+ char *ptr = NULL;
+
+ cl_alloc_limit(3);
+
+ cl_assert(ptr = git__realloc(ptr, 1));
+ *ptr = 'x';
+
+ cl_assert(ptr = git__realloc(ptr, 1));
+ cl_assert_equal_i(*ptr, 'x');
+
+ cl_assert(ptr = git__realloc(ptr, 2));
+ cl_assert_equal_i(*ptr, 'x');
+
+ cl_assert(git__realloc(ptr, 2) == NULL);
+
+ cl_assert(ptr = git__realloc(ptr, 1));
+ cl_assert_equal_i(*ptr, 'x');
+
+ git__free(ptr);
+}
diff --git a/tests/util/array.c b/tests/util/array.c
new file mode 100644
index 0000000..39fbc81
--- /dev/null
+++ b/tests/util/array.c
@@ -0,0 +1,57 @@
+#include "clar_libgit2.h"
+#include "array.h"
+
+static int int_lookup(const void *k, const void *a)
+{
+ const int *one = (const int *)k;
+ int *two = (int *)a;
+
+ return *one - *two;
+}
+
+#define expect_pos(k, n, ret) \
+ key = (k); \
+ cl_assert_equal_i((ret), \
+ git_array_search(&p, integers, int_lookup, &key)); \
+ cl_assert_equal_i((n), p);
+
+void test_array__bsearch2(void)
+{
+ git_array_t(int) integers = GIT_ARRAY_INIT;
+ int *i, key;
+ size_t p;
+
+ i = git_array_alloc(integers); *i = 2;
+ i = git_array_alloc(integers); *i = 3;
+ i = git_array_alloc(integers); *i = 5;
+ i = git_array_alloc(integers); *i = 7;
+ i = git_array_alloc(integers); *i = 7;
+ i = git_array_alloc(integers); *i = 8;
+ i = git_array_alloc(integers); *i = 13;
+ i = git_array_alloc(integers); *i = 21;
+ i = git_array_alloc(integers); *i = 25;
+ i = git_array_alloc(integers); *i = 42;
+ i = git_array_alloc(integers); *i = 69;
+ i = git_array_alloc(integers); *i = 121;
+ i = git_array_alloc(integers); *i = 256;
+ i = git_array_alloc(integers); *i = 512;
+ i = git_array_alloc(integers); *i = 513;
+ i = git_array_alloc(integers); *i = 514;
+ i = git_array_alloc(integers); *i = 516;
+ i = git_array_alloc(integers); *i = 516;
+ i = git_array_alloc(integers); *i = 517;
+
+ /* value to search for, expected position, return code */
+ expect_pos(3, 1, GIT_OK);
+ expect_pos(2, 0, GIT_OK);
+ expect_pos(1, 0, GIT_ENOTFOUND);
+ expect_pos(25, 8, GIT_OK);
+ expect_pos(26, 9, GIT_ENOTFOUND);
+ expect_pos(42, 9, GIT_OK);
+ expect_pos(50, 10, GIT_ENOTFOUND);
+ expect_pos(68, 10, GIT_ENOTFOUND);
+ expect_pos(256, 12, GIT_OK);
+
+ git_array_clear(integers);
+}
+
diff --git a/tests/util/assert.c b/tests/util/assert.c
new file mode 100644
index 0000000..3babc47
--- /dev/null
+++ b/tests/util/assert.c
@@ -0,0 +1,117 @@
+#ifdef GIT_ASSERT_HARD
+# undef GIT_ASSERT_HARD
+#endif
+
+#define GIT_ASSERT_HARD 0
+
+#include "clar_libgit2.h"
+
+static const char *hello_world = "hello, world";
+static const char *fail = "FAIL";
+
+static int dummy_fn(const char *myarg)
+{
+ GIT_ASSERT_ARG(myarg);
+ GIT_ASSERT_ARG(myarg != hello_world);
+ return 0;
+}
+
+static const char *fn_returns_string(const char *myarg)
+{
+ GIT_ASSERT_ARG_WITH_RETVAL(myarg, fail);
+ GIT_ASSERT_ARG_WITH_RETVAL(myarg != hello_world, fail);
+
+ return myarg;
+}
+
+static int bad_math(void)
+{
+ GIT_ASSERT(1 + 1 == 3);
+ return 42;
+}
+
+static const char *bad_returns_string(void)
+{
+ GIT_ASSERT_WITH_RETVAL(1 + 1 == 3, NULL);
+ return hello_world;
+}
+
+static int has_cleanup(void)
+{
+ int error = 42;
+
+ GIT_ASSERT_WITH_CLEANUP(1 + 1 == 3, {
+ error = 99;
+ goto foobar;
+ });
+
+ return 0;
+
+foobar:
+ return error;
+}
+
+void test_assert__argument(void)
+{
+ cl_git_fail(dummy_fn(NULL));
+ cl_assert(git_error_last());
+ cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass);
+ cl_assert_equal_s("invalid argument: 'myarg'", git_error_last()->message);
+
+ cl_git_fail(dummy_fn(hello_world));
+ cl_assert(git_error_last());
+ cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass);
+ cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message);
+
+ cl_git_pass(dummy_fn("foo"));
+}
+
+void test_assert__argument_with_non_int_return_type(void)
+{
+ const char *foo = "foo";
+
+ cl_assert_equal_p(fail, fn_returns_string(NULL));
+ cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass);
+ cl_assert_equal_s("invalid argument: 'myarg'", git_error_last()->message);
+
+ cl_assert_equal_p(fail, fn_returns_string(hello_world));
+ cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass);
+ cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message);
+
+ cl_assert_equal_p(foo, fn_returns_string(foo));
+}
+
+void test_assert__argument_with_void_return_type(void)
+{
+ const char *foo = "foo";
+
+ git_error_clear();
+ fn_returns_string(hello_world);
+ cl_assert_equal_i(GIT_ERROR_INVALID, git_error_last()->klass);
+ cl_assert_equal_s("invalid argument: 'myarg != hello_world'", git_error_last()->message);
+
+ git_error_clear();
+ cl_assert_equal_p(foo, fn_returns_string(foo));
+ cl_assert_equal_p(NULL, git_error_last());
+}
+
+void test_assert__internal(void)
+{
+ cl_git_fail(bad_math());
+ cl_assert(git_error_last());
+ cl_assert_equal_i(GIT_ERROR_INTERNAL, git_error_last()->klass);
+ cl_assert_equal_s("unrecoverable internal error: '1 + 1 == 3'", git_error_last()->message);
+
+ cl_assert_equal_p(NULL, bad_returns_string());
+ cl_assert(git_error_last());
+ cl_assert_equal_i(GIT_ERROR_INTERNAL, git_error_last()->klass);
+ cl_assert_equal_s("unrecoverable internal error: '1 + 1 == 3'", git_error_last()->message);
+}
+
+void test_assert__with_cleanup(void)
+{
+ cl_git_fail_with(99, has_cleanup());
+ cl_assert(git_error_last());
+ cl_assert_equal_i(GIT_ERROR_INTERNAL, git_error_last()->klass);
+ cl_assert_equal_s("unrecoverable internal error: '1 + 1 == 3'", git_error_last()->message);
+}
diff --git a/tests/util/bitvec.c b/tests/util/bitvec.c
new file mode 100644
index 0000000..f784589
--- /dev/null
+++ b/tests/util/bitvec.c
@@ -0,0 +1,64 @@
+#include "clar_libgit2.h"
+#include "bitvec.h"
+
+#if 0
+static void print_bitvec(git_bitvec *bv)
+{
+ int b;
+
+ if (!bv->length) {
+ for (b = 63; b >= 0; --b)
+ fprintf(stderr, "%d", (bv->u.bits & (1ul << b)) ? 1 : 0);
+ } else {
+ for (b = bv->length * 8; b >= 0; --b)
+ fprintf(stderr, "%d", (bv->u.ptr[b >> 3] & (b & 0x0ff)) ? 1 : 0);
+ }
+ fprintf(stderr, "\n");
+}
+#endif
+
+static void set_some_bits(git_bitvec *bv, size_t length)
+{
+ size_t i;
+
+ for (i = 0; i < length; ++i) {
+ if (i % 3 == 0 || i % 7 == 0)
+ git_bitvec_set(bv, i, true);
+ }
+}
+
+static void check_some_bits(git_bitvec *bv, size_t length)
+{
+ size_t i;
+
+ for (i = 0; i < length; ++i)
+ cl_assert_equal_b(i % 3 == 0 || i % 7 == 0, git_bitvec_get(bv, i));
+}
+
+void test_bitvec__0(void)
+{
+ git_bitvec bv;
+
+ cl_git_pass(git_bitvec_init(&bv, 32));
+ set_some_bits(&bv, 16);
+ check_some_bits(&bv, 16);
+ git_bitvec_clear(&bv);
+ set_some_bits(&bv, 32);
+ check_some_bits(&bv, 32);
+ git_bitvec_clear(&bv);
+ set_some_bits(&bv, 64);
+ check_some_bits(&bv, 64);
+ git_bitvec_free(&bv);
+
+ cl_git_pass(git_bitvec_init(&bv, 128));
+ set_some_bits(&bv, 32);
+ check_some_bits(&bv, 32);
+ set_some_bits(&bv, 128);
+ check_some_bits(&bv, 128);
+ git_bitvec_free(&bv);
+
+ cl_git_pass(git_bitvec_init(&bv, 4000));
+ set_some_bits(&bv, 4000);
+ check_some_bits(&bv, 4000);
+ git_bitvec_free(&bv);
+}
diff --git a/tests/util/copy.c b/tests/util/copy.c
new file mode 100644
index 0000000..2613730
--- /dev/null
+++ b/tests/util/copy.c
@@ -0,0 +1,162 @@
+#include "clar_libgit2.h"
+#include "futils.h"
+#include "posix.h"
+
+void test_copy__file(void)
+{
+ struct stat st = {0};
+ const char *content = "This is some stuff to copy\n";
+
+ cl_git_mkfile("copy_me", content);
+
+ cl_git_pass(git_futils_cp("copy_me", "copy_me_two", 0664));
+
+ cl_git_pass(git_fs_path_lstat("copy_me_two", &st));
+ cl_assert(S_ISREG(st.st_mode));
+
+ if (!cl_is_env_set("GITTEST_FLAKY_STAT"))
+ cl_assert_equal_sz(strlen(content), (size_t)st.st_size);
+
+ cl_git_pass(p_unlink("copy_me_two"));
+ cl_git_pass(p_unlink("copy_me"));
+}
+
+void test_copy__file_in_dir(void)
+{
+ struct stat st = {0};
+ const char *content = "This is some other stuff to copy\n";
+
+ cl_git_pass(git_futils_mkdir("an_dir/in_a_dir", 0775, GIT_MKDIR_PATH));
+ cl_git_mkfile("an_dir/in_a_dir/copy_me", content);
+ cl_assert(git_fs_path_isdir("an_dir"));
+
+ cl_git_pass(git_futils_mkpath2file
+ ("an_dir/second_dir/and_more/copy_me_two", 0775));
+
+ cl_git_pass(git_futils_cp
+ ("an_dir/in_a_dir/copy_me",
+ "an_dir/second_dir/and_more/copy_me_two",
+ 0664));
+
+ cl_git_pass(git_fs_path_lstat("an_dir/second_dir/and_more/copy_me_two", &st));
+ cl_assert(S_ISREG(st.st_mode));
+
+ if (!cl_is_env_set("GITTEST_FLAKY_STAT"))
+ cl_assert_equal_sz(strlen(content), (size_t)st.st_size);
+
+ cl_git_pass(git_futils_rmdir_r("an_dir", NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_assert(!git_fs_path_isdir("an_dir"));
+}
+
+#ifndef GIT_WIN32
+static void assert_hard_link(const char *path)
+{
+ /* we assert this by checking that there's more than one link to the file */
+ struct stat st;
+
+ cl_assert(git_fs_path_isfile(path));
+ cl_git_pass(p_stat(path, &st));
+ cl_assert(st.st_nlink > 1);
+}
+#endif
+
+void test_copy__tree(void)
+{
+ struct stat st;
+ const char *content = "File content\n";
+
+ cl_git_pass(git_futils_mkdir("src/b", 0775, GIT_MKDIR_PATH));
+ cl_git_pass(git_futils_mkdir("src/c/d", 0775, GIT_MKDIR_PATH));
+ cl_git_pass(git_futils_mkdir("src/c/e", 0775, GIT_MKDIR_PATH));
+
+ cl_git_mkfile("src/f1", content);
+ cl_git_mkfile("src/b/f2", content);
+ cl_git_mkfile("src/c/f3", content);
+ cl_git_mkfile("src/c/d/f4", content);
+ cl_git_mkfile("src/c/d/.f5", content);
+
+#ifndef GIT_WIN32
+ cl_assert(p_symlink("../../b/f2", "src/c/d/l1") == 0);
+#endif
+
+ cl_assert(git_fs_path_isdir("src"));
+ cl_assert(git_fs_path_isdir("src/b"));
+ cl_assert(git_fs_path_isdir("src/c/d"));
+ cl_assert(git_fs_path_isfile("src/c/d/f4"));
+
+ /* copy with no empty dirs, yes links, no dotfiles, no overwrite */
+
+ cl_git_pass(
+ git_futils_cp_r("src", "t1", GIT_CPDIR_COPY_SYMLINKS, 0) );
+
+ cl_assert(git_fs_path_isdir("t1"));
+ cl_assert(git_fs_path_isdir("t1/b"));
+ cl_assert(git_fs_path_isdir("t1/c"));
+ cl_assert(git_fs_path_isdir("t1/c/d"));
+ cl_assert(!git_fs_path_isdir("t1/c/e"));
+
+ cl_assert(git_fs_path_isfile("t1/f1"));
+ cl_assert(git_fs_path_isfile("t1/b/f2"));
+ cl_assert(git_fs_path_isfile("t1/c/f3"));
+ cl_assert(git_fs_path_isfile("t1/c/d/f4"));
+ cl_assert(!git_fs_path_isfile("t1/c/d/.f5"));
+
+ memset(&st, 0, sizeof(struct stat));
+ cl_git_pass(git_fs_path_lstat("t1/c/f3", &st));
+ cl_assert(S_ISREG(st.st_mode));
+
+ if (!cl_is_env_set("GITTEST_FLAKY_STAT"))
+ cl_assert_equal_sz(strlen(content), (size_t)st.st_size);
+
+#ifndef GIT_WIN32
+ memset(&st, 0, sizeof(struct stat));
+ cl_git_pass(git_fs_path_lstat("t1/c/d/l1", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+#endif
+
+ cl_git_pass(git_futils_rmdir_r("t1", NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_assert(!git_fs_path_isdir("t1"));
+
+ /* copy with empty dirs, no links, yes dotfiles, no overwrite */
+
+ cl_git_pass(
+ git_futils_cp_r("src", "t2", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_COPY_DOTFILES, 0) );
+
+ cl_assert(git_fs_path_isdir("t2"));
+ cl_assert(git_fs_path_isdir("t2/b"));
+ cl_assert(git_fs_path_isdir("t2/c"));
+ cl_assert(git_fs_path_isdir("t2/c/d"));
+ cl_assert(git_fs_path_isdir("t2/c/e"));
+
+ cl_assert(git_fs_path_isfile("t2/f1"));
+ cl_assert(git_fs_path_isfile("t2/b/f2"));
+ cl_assert(git_fs_path_isfile("t2/c/f3"));
+ cl_assert(git_fs_path_isfile("t2/c/d/f4"));
+ cl_assert(git_fs_path_isfile("t2/c/d/.f5"));
+
+#ifndef GIT_WIN32
+ memset(&st, 0, sizeof(struct stat));
+ cl_git_fail(git_fs_path_lstat("t2/c/d/l1", &st));
+#endif
+
+ cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_assert(!git_fs_path_isdir("t2"));
+
+#ifndef GIT_WIN32
+ cl_git_pass(git_futils_cp_r("src", "t3", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_LINK_FILES, 0));
+ cl_assert(git_fs_path_isdir("t3"));
+
+ cl_assert(git_fs_path_isdir("t3"));
+ cl_assert(git_fs_path_isdir("t3/b"));
+ cl_assert(git_fs_path_isdir("t3/c"));
+ cl_assert(git_fs_path_isdir("t3/c/d"));
+ cl_assert(git_fs_path_isdir("t3/c/e"));
+
+ assert_hard_link("t3/f1");
+ assert_hard_link("t3/b/f2");
+ assert_hard_link("t3/c/f3");
+ assert_hard_link("t3/c/d/f4");
+#endif
+
+ cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_RMDIR_REMOVE_FILES));
+}
diff --git a/tests/util/crlf.h b/tests/util/crlf.h
new file mode 100644
index 0000000..786edfc
--- /dev/null
+++ b/tests/util/crlf.h
@@ -0,0 +1,30 @@
+#ifndef INCLUDE_filter_crlf_h__
+#define INCLUDE_filter_crlf_h__
+
+/*
+ * file content for files in the resources/crlf repository
+ */
+
+#define UTF8_BOM "\xEF\xBB\xBF"
+
+#define ALL_CRLF_TEXT_RAW "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n"
+#define ALL_LF_TEXT_RAW "lf\nlf\nlf\nlf\nlf\n"
+#define MORE_CRLF_TEXT_RAW "crlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf\r\n"
+#define MORE_LF_TEXT_RAW "lf\nlf\ncrlf\r\nlf\nlf\n"
+
+#define ALL_CRLF_TEXT_AS_CRLF ALL_CRLF_TEXT_RAW
+#define ALL_LF_TEXT_AS_CRLF "lf\r\nlf\r\nlf\r\nlf\r\nlf\r\n"
+#define MORE_CRLF_TEXT_AS_CRLF "crlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf\r\n"
+#define MORE_LF_TEXT_AS_CRLF "lf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\n"
+
+#define ALL_CRLF_TEXT_AS_LF "crlf\ncrlf\ncrlf\ncrlf\n"
+#define ALL_LF_TEXT_AS_LF ALL_LF_TEXT_RAW
+#define MORE_CRLF_TEXT_AS_LF "crlf\ncrlf\nlf\ncrlf\ncrlf\n"
+#define MORE_LF_TEXT_AS_LF "lf\nlf\ncrlf\nlf\nlf\n"
+
+#define FEW_UTF8_CRLF_RAW "\xe2\x9a\xbdThe rest is ASCII01.\r\nThe rest is ASCII02.\r\nThe rest is ASCII03.\r\nThe rest is ASCII04.\r\nThe rest is ASCII05.\r\nThe rest is ASCII06.\r\nThe rest is ASCII07.\r\nThe rest is ASCII08.\r\nThe rest is ASCII09.\r\nThe rest is ASCII10.\r\nThe rest is ASCII11.\r\nThe rest is ASCII12.\r\nThe rest is ASCII13.\r\nThe rest is ASCII14.\r\nThe rest is ASCII15.\r\nThe rest is ASCII16.\r\nThe rest is ASCII17.\r\nThe rest is ASCII18.\r\nThe rest is ASCII19.\r\nThe rest is ASCII20.\r\nThe rest is ASCII21.\r\nThe rest is ASCII22.\r\n"
+#define FEW_UTF8_LF_RAW "\xe2\x9a\xbdThe rest is ASCII01.\nThe rest is ASCII02.\nThe rest is ASCII03.\nThe rest is ASCII04.\nThe rest is ASCII05.\nThe rest is ASCII06.\nThe rest is ASCII07.\nThe rest is ASCII08.\nThe rest is ASCII09.\nThe rest is ASCII10.\nThe rest is ASCII11.\nThe rest is ASCII12.\nThe rest is ASCII13.\nThe rest is ASCII14.\nThe rest is ASCII15.\nThe rest is ASCII16.\nThe rest is ASCII17.\nThe rest is ASCII18.\nThe rest is ASCII19.\nThe rest is ASCII20.\nThe rest is ASCII21.\nThe rest is ASCII22.\n"
+#define MANY_UTF8_CRLF_RAW "Lets sing!\r\n\xe2\x99\xab\xe2\x99\xaa\xe2\x99\xac\xe2\x99\xa9\r\nEat food\r\n\xf0\x9f\x8d\x85\xf0\x9f\x8d\x95\r\n"
+#define MANY_UTF8_LF_RAW "Lets sing!\n\xe2\x99\xab\xe2\x99\xaa\xe2\x99\xac\xe2\x99\xa9\nEat food\n\xf0\x9f\x8d\x85\xf0\x9f\x8d\x95\n"
+
+#endif
diff --git a/tests/util/dirent.c b/tests/util/dirent.c
new file mode 100644
index 0000000..5840c67
--- /dev/null
+++ b/tests/util/dirent.c
@@ -0,0 +1,306 @@
+#include "clar_libgit2.h"
+#include "futils.h"
+
+typedef struct name_data {
+ int count; /* return count */
+ char *name; /* filename */
+} name_data;
+
+typedef struct walk_data {
+ char *sub; /* sub-directory name */
+ name_data *names; /* name state data */
+ git_str path;
+} walk_data;
+
+
+static char *top_dir = "dir-walk";
+static walk_data *state_loc;
+
+static void setup(walk_data *d)
+{
+ name_data *n;
+
+ cl_must_pass(p_mkdir(top_dir, 0777));
+
+ cl_must_pass(p_chdir(top_dir));
+
+ if (strcmp(d->sub, ".") != 0)
+ cl_must_pass(p_mkdir(d->sub, 0777));
+
+ cl_git_pass(git_str_sets(&d->path, d->sub));
+
+ state_loc = d;
+
+ for (n = d->names; n->name; n++) {
+ git_file fd = p_creat(n->name, 0666);
+ cl_assert(fd >= 0);
+ p_close(fd);
+ n->count = 0;
+ }
+}
+
+static void dirent_cleanup__cb(void *_d)
+{
+ walk_data *d = _d;
+ name_data *n;
+
+ for (n = d->names; n->name; n++) {
+ cl_must_pass(p_unlink(n->name));
+ }
+
+ if (strcmp(d->sub, ".") != 0)
+ cl_must_pass(p_rmdir(d->sub));
+
+ cl_must_pass(p_chdir(".."));
+
+ cl_must_pass(p_rmdir(top_dir));
+
+ git_str_dispose(&d->path);
+}
+
+static void check_counts(walk_data *d)
+{
+ name_data *n;
+
+ for (n = d->names; n->name; n++) {
+ cl_assert(n->count == 1);
+ }
+}
+
+static int update_count(name_data *data, const char *name)
+{
+ name_data *n;
+
+ for (n = data; n->name; n++) {
+ if (!strcmp(n->name, name)) {
+ n->count++;
+ return 0;
+ }
+ }
+
+ return GIT_ERROR;
+}
+
+static int one_entry(void *state, git_str *path)
+{
+ walk_data *d = (walk_data *) state;
+
+ if (state != state_loc)
+ return GIT_ERROR;
+
+ if (path != &d->path)
+ return GIT_ERROR;
+
+ return update_count(d->names, path->ptr);
+}
+
+
+static name_data dot_names[] = {
+ { 0, "./a" },
+ { 0, "./asdf" },
+ { 0, "./pack-foo.pack" },
+ { 0, NULL }
+};
+static walk_data dot = {
+ ".",
+ dot_names,
+ GIT_STR_INIT
+};
+
+/* make sure that the '.' folder is not traversed */
+void test_dirent__dont_traverse_dot(void)
+{
+ cl_set_cleanup(&dirent_cleanup__cb, &dot);
+ setup(&dot);
+
+ cl_git_pass(git_fs_path_direach(&dot.path, 0, one_entry, &dot));
+
+ check_counts(&dot);
+}
+
+
+static name_data sub_names[] = {
+ { 0, "sub/a" },
+ { 0, "sub/asdf" },
+ { 0, "sub/pack-foo.pack" },
+ { 0, NULL }
+};
+static walk_data sub = {
+ "sub",
+ sub_names,
+ GIT_STR_INIT
+};
+
+/* traverse a subfolder */
+void test_dirent__traverse_subfolder(void)
+{
+ cl_set_cleanup(&dirent_cleanup__cb, &sub);
+ setup(&sub);
+
+ cl_git_pass(git_fs_path_direach(&sub.path, 0, one_entry, &sub));
+
+ check_counts(&sub);
+}
+
+
+static walk_data sub_slash = {
+ "sub/",
+ sub_names,
+ GIT_STR_INIT
+};
+
+/* traverse a slash-terminated subfolder */
+void test_dirent__traverse_slash_terminated_folder(void)
+{
+ cl_set_cleanup(&dirent_cleanup__cb, &sub_slash);
+ setup(&sub_slash);
+
+ cl_git_pass(git_fs_path_direach(&sub_slash.path, 0, one_entry, &sub_slash));
+
+ check_counts(&sub_slash);
+}
+
+
+static name_data empty_names[] = {
+ { 0, NULL }
+};
+static walk_data empty = {
+ "empty",
+ empty_names,
+ GIT_STR_INIT
+};
+
+/* make sure that empty folders are not traversed */
+void test_dirent__dont_traverse_empty_folders(void)
+{
+ cl_set_cleanup(&dirent_cleanup__cb, &empty);
+ setup(&empty);
+
+ cl_git_pass(git_fs_path_direach(&empty.path, 0, one_entry, &empty));
+
+ check_counts(&empty);
+
+ /* make sure callback not called */
+ cl_assert(git_fs_path_is_empty_dir(empty.path.ptr));
+}
+
+static name_data odd_names[] = {
+ { 0, "odd/.a" },
+ { 0, "odd/..c" },
+ /* the following don't work on cygwin/win32 */
+ /* { 0, "odd/.b." }, */
+ /* { 0, "odd/..d.." }, */
+ { 0, NULL }
+};
+static walk_data odd = {
+ "odd",
+ odd_names,
+ GIT_STR_INIT
+};
+
+/* make sure that strange looking filenames ('..c') are traversed */
+void test_dirent__traverse_weird_filenames(void)
+{
+ cl_set_cleanup(&dirent_cleanup__cb, &odd);
+ setup(&odd);
+
+ cl_git_pass(git_fs_path_direach(&odd.path, 0, one_entry, &odd));
+
+ check_counts(&odd);
+}
+
+/* test filename length limits */
+void test_dirent__length_limits(void)
+{
+ char *big_filename = (char *)git__malloc(FILENAME_MAX + 1);
+ memset(big_filename, 'a', FILENAME_MAX + 1);
+ big_filename[FILENAME_MAX] = 0;
+
+ cl_must_fail(p_creat(big_filename, 0666));
+
+ git__free(big_filename);
+}
+
+void test_dirent__empty_dir(void)
+{
+ cl_must_pass(p_mkdir("empty_dir", 0777));
+ cl_assert(git_fs_path_is_empty_dir("empty_dir"));
+
+ cl_git_mkfile("empty_dir/content", "whatever\n");
+ cl_assert(!git_fs_path_is_empty_dir("empty_dir"));
+ cl_assert(!git_fs_path_is_empty_dir("empty_dir/content"));
+
+ cl_must_pass(p_unlink("empty_dir/content"));
+
+ cl_must_pass(p_mkdir("empty_dir/content", 0777));
+ cl_assert(!git_fs_path_is_empty_dir("empty_dir"));
+ cl_assert(git_fs_path_is_empty_dir("empty_dir/content"));
+
+ cl_must_pass(p_rmdir("empty_dir/content"));
+
+ cl_must_pass(p_rmdir("empty_dir"));
+}
+
+static void handle_next(git_fs_path_diriter *diriter, walk_data *walk)
+{
+ const char *fullpath, *filename;
+ size_t fullpath_len, filename_len;
+
+ cl_git_pass(git_fs_path_diriter_fullpath(&fullpath, &fullpath_len, diriter));
+ cl_git_pass(git_fs_path_diriter_filename(&filename, &filename_len, diriter));
+
+ cl_assert_equal_strn(fullpath, "sub/", 4);
+ cl_assert_equal_s(fullpath+4, filename);
+
+ update_count(walk->names, fullpath);
+}
+
+/* test directory iterator */
+void test_dirent__diriter_with_fullname(void)
+{
+ git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT;
+ int error;
+
+ cl_set_cleanup(&dirent_cleanup__cb, &sub);
+ setup(&sub);
+
+ cl_git_pass(git_fs_path_diriter_init(&diriter, sub.path.ptr, 0));
+
+ while ((error = git_fs_path_diriter_next(&diriter)) == 0)
+ handle_next(&diriter, &sub);
+
+ cl_assert_equal_i(error, GIT_ITEROVER);
+
+ git_fs_path_diriter_free(&diriter);
+
+ check_counts(&sub);
+}
+
+void test_dirent__diriter_at_directory_root(void)
+{
+ git_fs_path_diriter diriter = GIT_FS_PATH_DIRITER_INIT;
+ const char *sandbox_path, *path;
+ char *root_path;
+ size_t path_len;
+ int root_offset, error;
+
+ sandbox_path = clar_sandbox_path();
+ cl_assert((root_offset = git_fs_path_root(sandbox_path)) >= 0);
+
+ cl_assert(root_path = git__calloc(1, root_offset + 2));
+ strncpy(root_path, sandbox_path, root_offset + 1);
+
+ cl_git_pass(git_fs_path_diriter_init(&diriter, root_path, 0));
+
+ while ((error = git_fs_path_diriter_next(&diriter)) == 0) {
+ cl_git_pass(git_fs_path_diriter_fullpath(&path, &path_len, &diriter));
+
+ cl_assert(path_len > (size_t)(root_offset + 1));
+ cl_assert(path[root_offset+1] != '/');
+ }
+
+ cl_assert_equal_i(error, GIT_ITEROVER);
+
+ git_fs_path_diriter_free(&diriter);
+ git__free(root_path);
+}
diff --git a/tests/util/encoding.c b/tests/util/encoding.c
new file mode 100644
index 0000000..a25a334
--- /dev/null
+++ b/tests/util/encoding.c
@@ -0,0 +1,42 @@
+#include "clar_libgit2.h"
+#include "varint.h"
+
+void test_encoding__decode(void)
+{
+ const unsigned char *buf = (unsigned char *)"AB";
+ size_t size;
+
+ cl_assert(git_decode_varint(buf, &size) == 65);
+ cl_assert(size == 1);
+
+ buf = (unsigned char *)"\xfe\xdc\xbaXY";
+ cl_assert(git_decode_varint(buf, &size) == 267869656);
+ cl_assert(size == 4);
+
+ buf = (unsigned char *)"\xaa\xaa\xfe\xdc\xbaXY";
+ cl_assert(git_decode_varint(buf, &size) == UINT64_C(1489279344088));
+ cl_assert(size == 6);
+
+ buf = (unsigned char *)"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xfe\xdc\xbaXY";
+ cl_assert(git_decode_varint(buf, &size) == 0);
+ cl_assert(size == 0);
+
+}
+
+void test_encoding__encode(void)
+{
+ unsigned char buf[100];
+ cl_assert(git_encode_varint(buf, 100, 65) == 1);
+ cl_assert(buf[0] == 'A');
+
+ cl_assert(git_encode_varint(buf, 1, 1) == 1);
+ cl_assert(!memcmp(buf, "\x01", 1));
+
+ cl_assert(git_encode_varint(buf, 100, 267869656) == 4);
+ cl_assert(!memcmp(buf, "\xfe\xdc\xbaX", 4));
+
+ cl_assert(git_encode_varint(buf, 100, UINT64_C(1489279344088)) == 6);
+ cl_assert(!memcmp(buf, "\xaa\xaa\xfe\xdc\xbaX", 6));
+
+ cl_assert(git_encode_varint(buf, 1, UINT64_C(1489279344088)) == -1);
+}
diff --git a/tests/util/errors.c b/tests/util/errors.c
new file mode 100644
index 0000000..78654a7
--- /dev/null
+++ b/tests/util/errors.c
@@ -0,0 +1,222 @@
+#include "clar_libgit2.h"
+
+void test_errors__public_api(void)
+{
+ char *str_in_error;
+
+ git_error_clear();
+ cl_assert(git_error_last() == NULL);
+
+ git_error_set_oom();
+
+ cl_assert(git_error_last() != NULL);
+ cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY);
+ str_in_error = strstr(git_error_last()->message, "memory");
+ cl_assert(str_in_error != NULL);
+
+ git_error_clear();
+
+ git_error_set_str(GIT_ERROR_REPOSITORY, "This is a test");
+
+ cl_assert(git_error_last() != NULL);
+ str_in_error = strstr(git_error_last()->message, "This is a test");
+ cl_assert(str_in_error != NULL);
+
+ git_error_clear();
+ cl_assert(git_error_last() == NULL);
+}
+
+#include "common.h"
+#include "util.h"
+#include "posix.h"
+
+void test_errors__new_school(void)
+{
+ char *str_in_error;
+
+ git_error_clear();
+ cl_assert(git_error_last() == NULL);
+
+ git_error_set_oom(); /* internal fn */
+
+ cl_assert(git_error_last() != NULL);
+ cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY);
+ str_in_error = strstr(git_error_last()->message, "memory");
+ cl_assert(str_in_error != NULL);
+
+ git_error_clear();
+
+ git_error_set(GIT_ERROR_REPOSITORY, "This is a test"); /* internal fn */
+
+ cl_assert(git_error_last() != NULL);
+ str_in_error = strstr(git_error_last()->message, "This is a test");
+ cl_assert(str_in_error != NULL);
+
+ git_error_clear();
+ cl_assert(git_error_last() == NULL);
+
+ do {
+ struct stat st;
+ memset(&st, 0, sizeof(st));
+ cl_assert(p_lstat("this_file_does_not_exist", &st) < 0);
+ GIT_UNUSED(st);
+ } while (false);
+ git_error_set(GIT_ERROR_OS, "stat failed"); /* internal fn */
+
+ cl_assert(git_error_last() != NULL);
+ str_in_error = strstr(git_error_last()->message, "stat failed");
+ cl_assert(str_in_error != NULL);
+ cl_assert(git__prefixcmp(str_in_error, "stat failed: ") == 0);
+ cl_assert(strlen(str_in_error) > strlen("stat failed: "));
+
+#ifdef GIT_WIN32
+ git_error_clear();
+
+ /* The MSDN docs use this to generate a sample error */
+ cl_assert(GetProcessId(NULL) == 0);
+ git_error_set(GIT_ERROR_OS, "GetProcessId failed"); /* internal fn */
+
+ cl_assert(git_error_last() != NULL);
+ str_in_error = strstr(git_error_last()->message, "GetProcessId failed");
+ cl_assert(str_in_error != NULL);
+ cl_assert(git__prefixcmp(str_in_error, "GetProcessId failed: ") == 0);
+ cl_assert(strlen(str_in_error) > strlen("GetProcessId failed: "));
+#endif
+
+ git_error_clear();
+}
+
+void test_errors__restore(void)
+{
+ git_error_state err_state = {0};
+
+ git_error_clear();
+ cl_assert(git_error_last() == NULL);
+
+ cl_assert_equal_i(0, git_error_state_capture(&err_state, 0));
+
+ memset(&err_state, 0x0, sizeof(git_error_state));
+
+ git_error_set(42, "Foo: %s", "bar");
+ cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1));
+
+ cl_assert(git_error_last() == NULL);
+
+ git_error_set(99, "Bar: %s", "foo");
+
+ git_error_state_restore(&err_state);
+
+ cl_assert_equal_i(42, git_error_last()->klass);
+ cl_assert_equal_s("Foo: bar", git_error_last()->message);
+}
+
+void test_errors__free_state(void)
+{
+ git_error_state err_state = {0};
+
+ git_error_clear();
+
+ git_error_set(42, "Foo: %s", "bar");
+ cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1));
+
+ git_error_set(99, "Bar: %s", "foo");
+
+ git_error_state_free(&err_state);
+
+ cl_assert_equal_i(99, git_error_last()->klass);
+ cl_assert_equal_s("Bar: foo", git_error_last()->message);
+
+ git_error_state_restore(&err_state);
+
+ cl_assert(git_error_last() == NULL);
+}
+
+void test_errors__restore_oom(void)
+{
+ git_error_state err_state = {0};
+ const git_error *oom_error = NULL;
+
+ git_error_clear();
+
+ git_error_set_oom(); /* internal fn */
+ oom_error = git_error_last();
+ cl_assert(oom_error);
+
+ cl_assert_equal_i(-1, git_error_state_capture(&err_state, -1));
+
+ cl_assert(git_error_last() == NULL);
+ cl_assert_equal_i(GIT_ERROR_NOMEMORY, err_state.error_msg.klass);
+ cl_assert_equal_s("Out of memory", err_state.error_msg.message);
+
+ git_error_state_restore(&err_state);
+
+ cl_assert(git_error_last()->klass == GIT_ERROR_NOMEMORY);
+ cl_assert_(git_error_last() == oom_error, "static oom error not restored");
+
+ git_error_clear();
+}
+
+static int test_arraysize_multiply(size_t nelem, size_t size)
+{
+ size_t out;
+ GIT_ERROR_CHECK_ALLOC_MULTIPLY(&out, nelem, size);
+ return 0;
+}
+
+void test_errors__integer_overflow_alloc_multiply(void)
+{
+ cl_git_pass(test_arraysize_multiply(10, 10));
+ cl_git_pass(test_arraysize_multiply(1000, 1000));
+ cl_git_pass(test_arraysize_multiply(SIZE_MAX/sizeof(void *), sizeof(void *)));
+ cl_git_pass(test_arraysize_multiply(0, 10));
+ cl_git_pass(test_arraysize_multiply(10, 0));
+
+ cl_git_fail(test_arraysize_multiply(SIZE_MAX-1, sizeof(void *)));
+ cl_git_fail(test_arraysize_multiply((SIZE_MAX/sizeof(void *))+1, sizeof(void *)));
+
+ cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass);
+ cl_assert_equal_s("Out of memory", git_error_last()->message);
+}
+
+static int test_arraysize_add(size_t one, size_t two)
+{
+ size_t out;
+ GIT_ERROR_CHECK_ALLOC_ADD(&out, one, two);
+ return 0;
+}
+
+void test_errors__integer_overflow_alloc_add(void)
+{
+ cl_git_pass(test_arraysize_add(10, 10));
+ cl_git_pass(test_arraysize_add(1000, 1000));
+ cl_git_pass(test_arraysize_add(SIZE_MAX-10, 10));
+
+ cl_git_fail(test_arraysize_multiply(SIZE_MAX-1, 2));
+ cl_git_fail(test_arraysize_multiply(SIZE_MAX, SIZE_MAX));
+
+ cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass);
+ cl_assert_equal_s("Out of memory", git_error_last()->message);
+}
+
+void test_errors__integer_overflow_sets_oom(void)
+{
+ size_t out;
+
+ git_error_clear();
+ cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX-1, 1));
+ cl_assert_equal_p(NULL, git_error_last());
+
+ git_error_clear();
+ cl_assert(!GIT_ADD_SIZET_OVERFLOW(&out, 42, 69));
+ cl_assert_equal_p(NULL, git_error_last());
+
+ git_error_clear();
+ cl_assert(GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX, SIZE_MAX));
+ cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass);
+ cl_assert_equal_s("Out of memory", git_error_last()->message);
+
+ git_error_clear();
+ cl_assert(GIT_ADD_SIZET_OVERFLOW(&out, SIZE_MAX, SIZE_MAX));
+ cl_assert_equal_i(GIT_ERROR_NOMEMORY, git_error_last()->klass);
+ cl_assert_equal_s("Out of memory", git_error_last()->message);
+}
diff --git a/tests/util/filebuf.c b/tests/util/filebuf.c
new file mode 100644
index 0000000..29b8211
--- /dev/null
+++ b/tests/util/filebuf.c
@@ -0,0 +1,267 @@
+#include "clar_libgit2.h"
+#include "filebuf.h"
+
+/* make sure git_filebuf_open doesn't delete an existing lock */
+void test_filebuf__0(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ int fd;
+ char test[] = "test", testlock[] = "test.lock";
+
+ fd = p_creat(testlock, 0744); /* -V536 */
+
+ cl_must_pass(fd);
+ cl_must_pass(p_close(fd));
+
+ cl_git_fail(git_filebuf_open(&file, test, 0, 0666));
+ cl_assert(git_fs_path_exists(testlock));
+
+ cl_must_pass(p_unlink(testlock));
+}
+
+
+/* make sure GIT_FILEBUF_APPEND works as expected */
+void test_filebuf__1(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ char test[] = "test";
+
+ cl_git_mkfile(test, "libgit2 rocks\n");
+
+ cl_git_pass(git_filebuf_open(&file, test, GIT_FILEBUF_APPEND, 0666));
+ cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
+ cl_git_pass(git_filebuf_commit(&file));
+
+ cl_assert_equal_file("libgit2 rocks\nlibgit2 rocks\n", 0, test);
+
+ cl_must_pass(p_unlink(test));
+}
+
+
+/* make sure git_filebuf_write writes large buffer correctly */
+void test_filebuf__2(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ char test[] = "test";
+ unsigned char buf[4096 * 4]; /* 2 * WRITE_BUFFER_SIZE */
+
+ memset(buf, 0xfe, sizeof(buf));
+
+ cl_git_pass(git_filebuf_open(&file, test, 0, 0666));
+ cl_git_pass(git_filebuf_write(&file, buf, sizeof(buf)));
+ cl_git_pass(git_filebuf_commit(&file));
+
+ cl_assert_equal_file((char *)buf, sizeof(buf), test);
+
+ cl_must_pass(p_unlink(test));
+}
+
+/* make sure git_filebuf_cleanup clears the buffer */
+void test_filebuf__4(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ char test[] = "test";
+
+ cl_assert(file.buffer == NULL);
+
+ cl_git_pass(git_filebuf_open(&file, test, 0, 0666));
+ cl_assert(file.buffer != NULL);
+
+ git_filebuf_cleanup(&file);
+ cl_assert(file.buffer == NULL);
+}
+
+
+/* make sure git_filebuf_commit clears the buffer */
+void test_filebuf__5(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ char test[] = "test";
+
+ cl_assert(file.buffer == NULL);
+
+ cl_git_pass(git_filebuf_open(&file, test, 0, 0666));
+ cl_assert(file.buffer != NULL);
+ cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
+ cl_assert(file.buffer != NULL);
+
+ cl_git_pass(git_filebuf_commit(&file));
+ cl_assert(file.buffer == NULL);
+
+ cl_must_pass(p_unlink(test));
+}
+
+
+/* make sure git_filebuf_commit takes umask into account */
+void test_filebuf__umask(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ char test[] = "test";
+ struct stat statbuf;
+ mode_t mask, os_mask;
+
+#ifdef GIT_WIN32
+ os_mask = 0600;
+#else
+ os_mask = 0777;
+#endif
+
+ p_umask(mask = p_umask(0));
+
+ cl_assert(file.buffer == NULL);
+
+ cl_git_pass(git_filebuf_open(&file, test, 0, 0666));
+ cl_assert(file.buffer != NULL);
+ cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
+ cl_assert(file.buffer != NULL);
+
+ cl_git_pass(git_filebuf_commit(&file));
+ cl_assert(file.buffer == NULL);
+
+ cl_must_pass(p_stat("test", &statbuf));
+ cl_assert_equal_i(statbuf.st_mode & os_mask, (0666 & ~mask) & os_mask);
+
+ cl_must_pass(p_unlink(test));
+}
+
+void test_filebuf__rename_error(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ char *dir = "subdir", *test = "subdir/test", *test_lock = "subdir/test.lock";
+ int fd;
+
+#ifndef GIT_WIN32
+ cl_skip();
+#endif
+
+ cl_git_pass(p_mkdir(dir, 0666));
+ cl_git_mkfile(test, "dummy content");
+ fd = p_open(test, O_RDONLY);
+ cl_assert(fd > 0);
+ cl_git_pass(git_filebuf_open(&file, test, 0, 0666));
+
+ cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
+
+ cl_assert_equal_i(true, git_fs_path_exists(test_lock));
+
+ cl_git_fail(git_filebuf_commit(&file));
+ p_close(fd);
+
+ git_filebuf_cleanup(&file);
+
+ cl_assert_equal_i(false, git_fs_path_exists(test_lock));
+}
+
+void test_filebuf__symlink_follow(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ const char *dir = "linkdir", *source = "linkdir/link";
+
+ if (!git_fs_path_supports_symlinks(clar_sandbox_path()))
+ cl_skip();
+
+ cl_git_pass(p_mkdir(dir, 0777));
+ cl_git_pass(p_symlink("target", source));
+
+ cl_git_pass(git_filebuf_open(&file, source, 0, 0666));
+ cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
+
+ cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock"));
+
+ cl_git_pass(git_filebuf_commit(&file));
+ cl_assert_equal_i(true, git_fs_path_exists("linkdir/target"));
+
+ git_filebuf_cleanup(&file);
+
+ /* The second time around, the target file does exist */
+ cl_git_pass(git_filebuf_open(&file, source, 0, 0666));
+ cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
+
+ cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock"));
+
+ cl_git_pass(git_filebuf_commit(&file));
+ cl_assert_equal_i(true, git_fs_path_exists("linkdir/target"));
+
+ git_filebuf_cleanup(&file);
+ cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES));
+}
+
+void test_filebuf__symlink_follow_absolute_paths(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_str source = GIT_STR_INIT, target = GIT_STR_INIT;
+
+ if (!git_fs_path_supports_symlinks(clar_sandbox_path()))
+ cl_skip();
+
+ cl_git_pass(git_str_joinpath(&source, clar_sandbox_path(), "linkdir/link"));
+ cl_git_pass(git_str_joinpath(&target, clar_sandbox_path(), "linkdir/target"));
+ cl_git_pass(p_mkdir("linkdir", 0777));
+ cl_git_pass(p_symlink(target.ptr, source.ptr));
+
+ cl_git_pass(git_filebuf_open(&file, source.ptr, 0, 0666));
+ cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
+
+ cl_assert_equal_i(true, git_fs_path_exists("linkdir/target.lock"));
+
+ cl_git_pass(git_filebuf_commit(&file));
+ cl_assert_equal_i(true, git_fs_path_exists("linkdir/target"));
+
+ git_filebuf_cleanup(&file);
+ git_str_dispose(&source);
+ git_str_dispose(&target);
+
+ cl_git_pass(git_futils_rmdir_r("linkdir", NULL, GIT_RMDIR_REMOVE_FILES));
+}
+
+void test_filebuf__symlink_depth(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ const char *dir = "linkdir", *source = "linkdir/link";
+
+ if (!git_fs_path_supports_symlinks(clar_sandbox_path()))
+ cl_skip();
+
+ cl_git_pass(p_mkdir(dir, 0777));
+ /* Endless loop */
+ cl_git_pass(p_symlink("link", source));
+
+ cl_git_fail(git_filebuf_open(&file, source, 0, 0666));
+
+ cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES));
+}
+
+void test_filebuf__hidden_file(void)
+{
+#ifndef GIT_WIN32
+ cl_skip();
+#else
+ git_filebuf file = GIT_FILEBUF_INIT;
+ char *dir = "hidden", *test = "hidden/test";
+ bool hidden;
+
+ cl_git_pass(p_mkdir(dir, 0666));
+ cl_git_mkfile(test, "dummy content");
+
+ cl_git_pass(git_win32__set_hidden(test, true));
+ cl_git_pass(git_win32__hidden(&hidden, test));
+ cl_assert(hidden);
+
+ cl_git_pass(git_filebuf_open(&file, test, 0, 0666));
+
+ cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
+
+ cl_git_pass(git_filebuf_commit(&file));
+
+ git_filebuf_cleanup(&file);
+#endif
+}
+
+void test_filebuf__detects_directory(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+
+ cl_must_pass(p_mkdir("foo", 0777));
+ cl_git_fail_with(GIT_EDIRECTORY, git_filebuf_open(&file, "foo", 0, 0666));
+ cl_must_pass(p_rmdir("foo"));
+}
diff --git a/tests/util/ftruncate.c b/tests/util/ftruncate.c
new file mode 100644
index 0000000..e60e5d9
--- /dev/null
+++ b/tests/util/ftruncate.c
@@ -0,0 +1,48 @@
+/**
+ * Some tests for p_ftruncate() to ensure that
+ * properly handles large (2Gb+) files.
+ */
+
+#include "clar_libgit2.h"
+
+static const char *filename = "core_ftruncate.txt";
+static int fd = -1;
+
+void test_ftruncate__initialize(void)
+{
+ if (!cl_is_env_set("GITTEST_INVASIVE_FS_SIZE"))
+ cl_skip();
+
+ cl_must_pass((fd = p_open(filename, O_CREAT | O_RDWR, 0644)));
+}
+
+void test_ftruncate__cleanup(void)
+{
+ if (fd < 0)
+ return;
+
+ p_close(fd);
+ fd = 0;
+
+ p_unlink(filename);
+}
+
+static void _extend(off64_t i64len)
+{
+ struct stat st;
+ int error;
+
+ cl_assert((error = p_ftruncate(fd, i64len)) == 0);
+ cl_assert((error = p_fstat(fd, &st)) == 0);
+ cl_assert(st.st_size == i64len);
+}
+
+void test_ftruncate__2gb(void)
+{
+ _extend(0x80000001);
+}
+
+void test_ftruncate__4gb(void)
+{
+ _extend(0x100000001);
+}
diff --git a/tests/util/futils.c b/tests/util/futils.c
new file mode 100644
index 0000000..993b14d
--- /dev/null
+++ b/tests/util/futils.c
@@ -0,0 +1,115 @@
+#include "clar_libgit2.h"
+#include "futils.h"
+
+/* Fixture setup and teardown */
+void test_futils__initialize(void)
+{
+ cl_must_pass(p_mkdir("futils", 0777));
+}
+
+void test_futils__cleanup(void)
+{
+ cl_fixture_cleanup("futils");
+}
+
+void test_futils__writebuffer(void)
+{
+ git_str out = GIT_STR_INIT,
+ append = GIT_STR_INIT;
+
+ /* create a new file */
+ git_str_puts(&out, "hello!\n");
+ git_str_printf(&out, "this is a %s\n", "test");
+
+ cl_git_pass(git_futils_writebuffer(&out, "futils/test-file", O_RDWR|O_CREAT, 0666));
+
+ cl_assert_equal_file(out.ptr, out.size, "futils/test-file");
+
+ /* append some more data */
+ git_str_puts(&append, "And some more!\n");
+ git_str_put(&out, append.ptr, append.size);
+
+ cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR|O_APPEND, 0666));
+
+ cl_assert_equal_file(out.ptr, out.size, "futils/test-file");
+
+ git_str_dispose(&out);
+ git_str_dispose(&append);
+}
+
+void test_futils__write_hidden_file(void)
+{
+#ifndef GIT_WIN32
+ cl_skip();
+#else
+ git_str out = GIT_STR_INIT, append = GIT_STR_INIT;
+ bool hidden;
+
+ git_str_puts(&out, "hidden file.\n");
+ git_futils_writebuffer(&out, "futils/test-file", O_RDWR | O_CREAT, 0666);
+
+ cl_git_pass(git_win32__set_hidden("futils/test-file", true));
+
+ /* append some more data */
+ git_str_puts(&append, "And some more!\n");
+ git_str_put(&out, append.ptr, append.size);
+
+ cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR | O_APPEND, 0666));
+
+ cl_assert_equal_file(out.ptr, out.size, "futils/test-file");
+
+ cl_git_pass(git_win32__hidden(&hidden, "futils/test-file"));
+ cl_assert(hidden);
+
+ git_str_dispose(&out);
+ git_str_dispose(&append);
+#endif
+}
+
+void test_futils__recursive_rmdir_keeps_symlink_targets(void)
+{
+ if (!git_fs_path_supports_symlinks(clar_sandbox_path()))
+ cl_skip();
+
+ cl_git_pass(git_futils_mkdir_r("a/b", 0777));
+ cl_git_pass(git_futils_mkdir_r("dir-target", 0777));
+ cl_git_mkfile("dir-target/file", "Contents");
+ cl_git_mkfile("file-target", "Contents");
+ cl_must_pass(p_symlink("dir-target", "a/symlink"));
+ cl_must_pass(p_symlink("file-target", "a/b/symlink"));
+
+ cl_git_pass(git_futils_rmdir_r("a", NULL, GIT_RMDIR_REMOVE_FILES));
+
+ cl_assert(git_fs_path_exists("dir-target"));
+ cl_assert(git_fs_path_exists("file-target"));
+
+ cl_must_pass(p_unlink("dir-target/file"));
+ cl_must_pass(p_rmdir("dir-target"));
+ cl_must_pass(p_unlink("file-target"));
+}
+
+void test_futils__mktmp_umask(void)
+{
+#ifdef GIT_WIN32
+ cl_skip();
+#else
+ git_str path = GIT_STR_INIT;
+ struct stat st;
+ int fd;
+
+ umask(0);
+ cl_assert((fd = git_futils_mktmp(&path, "foo", 0777)) >= 0);
+ cl_must_pass(p_fstat(fd, &st));
+ cl_assert_equal_i(st.st_mode & 0777, 0777);
+ cl_must_pass(p_unlink(path.ptr));
+ close(fd);
+
+ umask(077);
+ cl_assert((fd = git_futils_mktmp(&path, "foo", 0777)) >= 0);
+ cl_must_pass(p_fstat(fd, &st));
+ cl_assert_equal_i(st.st_mode & 0777, 0700);
+ cl_must_pass(p_unlink(path.ptr));
+ close(fd);
+ git_str_dispose(&path);
+#endif
+}
diff --git a/tests/util/gitstr.c b/tests/util/gitstr.c
new file mode 100644
index 0000000..aea3556
--- /dev/null
+++ b/tests/util/gitstr.c
@@ -0,0 +1,1044 @@
+#include "clar_libgit2.h"
+#include "futils.h"
+
+#define TESTSTR "Have you seen that? Have you seeeen that??"
+const char *test_string = TESTSTR;
+const char *test_string_x2 = TESTSTR TESTSTR;
+
+#define TESTSTR_4096 REP1024("1234")
+#define TESTSTR_8192 REP1024("12341234")
+const char *test_4096 = TESTSTR_4096;
+const char *test_8192 = TESTSTR_8192;
+
+/* test basic data concatenation */
+void test_gitstr__0(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ cl_assert(buf.size == 0);
+
+ git_str_puts(&buf, test_string);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(test_string, git_str_cstr(&buf));
+
+ git_str_puts(&buf, test_string);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(test_string_x2, git_str_cstr(&buf));
+
+ git_str_dispose(&buf);
+}
+
+/* test git_str_printf */
+void test_gitstr__1(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ git_str_printf(&buf, "%s %s %d ", "shoop", "da", 23);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s("shoop da 23 ", git_str_cstr(&buf));
+
+ git_str_printf(&buf, "%s %d", "woop", 42);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s("shoop da 23 woop 42", git_str_cstr(&buf));
+
+ git_str_dispose(&buf);
+}
+
+/* more thorough test of concatenation options */
+void test_gitstr__2(void)
+{
+ git_str buf = GIT_STR_INIT;
+ int i;
+ char data[128];
+
+ cl_assert(buf.size == 0);
+
+ /* this must be safe to do */
+ git_str_dispose(&buf);
+ cl_assert(buf.size == 0);
+ cl_assert(buf.asize == 0);
+
+ /* empty buffer should be empty string */
+ cl_assert_equal_s("", git_str_cstr(&buf));
+ cl_assert(buf.size == 0);
+ /* cl_assert(buf.asize == 0); -- should not assume what git_str does */
+
+ /* free should set us back to the beginning */
+ git_str_dispose(&buf);
+ cl_assert(buf.size == 0);
+ cl_assert(buf.asize == 0);
+
+ /* add letter */
+ git_str_putc(&buf, '+');
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s("+", git_str_cstr(&buf));
+
+ /* add letter again */
+ git_str_putc(&buf, '+');
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s("++", git_str_cstr(&buf));
+
+ /* let's try that a few times */
+ for (i = 0; i < 16; ++i) {
+ git_str_putc(&buf, '+');
+ cl_assert(git_str_oom(&buf) == 0);
+ }
+ cl_assert_equal_s("++++++++++++++++++", git_str_cstr(&buf));
+
+ git_str_dispose(&buf);
+
+ /* add data */
+ git_str_put(&buf, "xo", 2);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s("xo", git_str_cstr(&buf));
+
+ /* add letter again */
+ git_str_put(&buf, "xo", 2);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s("xoxo", git_str_cstr(&buf));
+
+ /* let's try that a few times */
+ for (i = 0; i < 16; ++i) {
+ git_str_put(&buf, "xo", 2);
+ cl_assert(git_str_oom(&buf) == 0);
+ }
+ cl_assert_equal_s("xoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxo",
+ git_str_cstr(&buf));
+
+ git_str_dispose(&buf);
+
+ /* set to string */
+ git_str_sets(&buf, test_string);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(test_string, git_str_cstr(&buf));
+
+ /* append string */
+ git_str_puts(&buf, test_string);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(test_string_x2, git_str_cstr(&buf));
+
+ /* set to string again (should overwrite - not append) */
+ git_str_sets(&buf, test_string);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(test_string, git_str_cstr(&buf));
+
+ /* test clear */
+ git_str_clear(&buf);
+ cl_assert_equal_s("", git_str_cstr(&buf));
+
+ git_str_dispose(&buf);
+
+ /* test extracting data into buffer */
+ git_str_puts(&buf, REP4("0123456789"));
+ cl_assert(git_str_oom(&buf) == 0);
+
+ git_str_copy_cstr(data, sizeof(data), &buf);
+ cl_assert_equal_s(REP4("0123456789"), data);
+ git_str_copy_cstr(data, 11, &buf);
+ cl_assert_equal_s("0123456789", data);
+ git_str_copy_cstr(data, 3, &buf);
+ cl_assert_equal_s("01", data);
+ git_str_copy_cstr(data, 1, &buf);
+ cl_assert_equal_s("", data);
+
+ git_str_copy_cstr(data, sizeof(data), &buf);
+ cl_assert_equal_s(REP4("0123456789"), data);
+
+ git_str_sets(&buf, REP256("x"));
+ git_str_copy_cstr(data, sizeof(data), &buf);
+ /* since sizeof(data) == 128, only 127 bytes should be copied */
+ cl_assert_equal_s(REP4(REP16("x")) REP16("x") REP16("x")
+ REP16("x") "xxxxxxxxxxxxxxx", data);
+
+ git_str_dispose(&buf);
+
+ git_str_copy_cstr(data, sizeof(data), &buf);
+ cl_assert_equal_s("", data);
+}
+
+/* let's do some tests with larger buffers to push our limits */
+void test_gitstr__3(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ /* set to string */
+ git_str_set(&buf, test_4096, 4096);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(test_4096, git_str_cstr(&buf));
+
+ /* append string */
+ git_str_puts(&buf, test_4096);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(test_8192, git_str_cstr(&buf));
+
+ /* set to string again (should overwrite - not append) */
+ git_str_set(&buf, test_4096, 4096);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(test_4096, git_str_cstr(&buf));
+
+ git_str_dispose(&buf);
+}
+
+/* let's try some producer/consumer tests */
+void test_gitstr__4(void)
+{
+ git_str buf = GIT_STR_INIT;
+ int i;
+
+ for (i = 0; i < 10; ++i) {
+ git_str_puts(&buf, "1234"); /* add 4 */
+ cl_assert(git_str_oom(&buf) == 0);
+ git_str_consume(&buf, buf.ptr + 2); /* eat the first two */
+ cl_assert(strlen(git_str_cstr(&buf)) == (size_t)((i + 1) * 2));
+ }
+ /* we have appended 1234 10x and removed the first 20 letters */
+ cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf));
+
+ git_str_consume(&buf, NULL);
+ cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf));
+
+ git_str_consume(&buf, "invalid pointer");
+ cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf));
+
+ git_str_consume(&buf, buf.ptr);
+ cl_assert_equal_s("12341234123412341234", git_str_cstr(&buf));
+
+ git_str_consume(&buf, buf.ptr + 1);
+ cl_assert_equal_s("2341234123412341234", git_str_cstr(&buf));
+
+ git_str_consume(&buf, buf.ptr + buf.size);
+ cl_assert_equal_s("", git_str_cstr(&buf));
+
+ git_str_dispose(&buf);
+}
+
+
+static void
+check_buf_append(
+ const char* data_a,
+ const char* data_b,
+ const char* expected_data,
+ size_t expected_size,
+ size_t expected_asize)
+{
+ git_str tgt = GIT_STR_INIT;
+
+ git_str_sets(&tgt, data_a);
+ cl_assert(git_str_oom(&tgt) == 0);
+ git_str_puts(&tgt, data_b);
+ cl_assert(git_str_oom(&tgt) == 0);
+ cl_assert_equal_s(expected_data, git_str_cstr(&tgt));
+ cl_assert_equal_i(tgt.size, expected_size);
+ if (expected_asize > 0)
+ cl_assert_equal_i(tgt.asize, expected_asize);
+
+ git_str_dispose(&tgt);
+}
+
+static void
+check_buf_append_abc(
+ const char* buf_a,
+ const char* buf_b,
+ const char* buf_c,
+ const char* expected_ab,
+ const char* expected_abc,
+ const char* expected_abca,
+ const char* expected_abcab,
+ const char* expected_abcabc)
+{
+ git_str buf = GIT_STR_INIT;
+
+ git_str_sets(&buf, buf_a);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(buf_a, git_str_cstr(&buf));
+
+ git_str_puts(&buf, buf_b);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(expected_ab, git_str_cstr(&buf));
+
+ git_str_puts(&buf, buf_c);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(expected_abc, git_str_cstr(&buf));
+
+ git_str_puts(&buf, buf_a);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(expected_abca, git_str_cstr(&buf));
+
+ git_str_puts(&buf, buf_b);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(expected_abcab, git_str_cstr(&buf));
+
+ git_str_puts(&buf, buf_c);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(expected_abcabc, git_str_cstr(&buf));
+
+ git_str_dispose(&buf);
+}
+
+/* more variations on append tests */
+void test_gitstr__5(void)
+{
+ check_buf_append("", "", "", 0, 0);
+ check_buf_append("a", "", "a", 1, 0);
+ check_buf_append("", "a", "a", 1, 8);
+ check_buf_append("", "a", "a", 1, 8);
+ check_buf_append("a", "b", "ab", 2, 8);
+ check_buf_append("", "abcdefgh", "abcdefgh", 8, 16);
+ check_buf_append("abcdefgh", "", "abcdefgh", 8, 16);
+
+ /* buffer with starting asize will grow to:
+ * 1 -> 2, 2 -> 3, 3 -> 5, 4 -> 6, 5 -> 8, 6 -> 9,
+ * 7 -> 11, 8 -> 12, 9 -> 14, 10 -> 15, 11 -> 17, 12 -> 18,
+ * 13 -> 20, 14 -> 21, 15 -> 23, 16 -> 24, 17 -> 26, 18 -> 27,
+ * 19 -> 29, 20 -> 30, 21 -> 32, 22 -> 33, 23 -> 35, 24 -> 36,
+ * ...
+ * follow sequence until value > target size,
+ * then round up to nearest multiple of 8.
+ */
+
+ check_buf_append("abcdefgh", "/", "abcdefgh/", 9, 16);
+ check_buf_append("abcdefgh", "ijklmno", "abcdefghijklmno", 15, 16);
+ check_buf_append("abcdefgh", "ijklmnop", "abcdefghijklmnop", 16, 24);
+ check_buf_append("0123456789", "0123456789",
+ "01234567890123456789", 20, 24);
+ check_buf_append(REP16("x"), REP16("o"),
+ REP16("x") REP16("o"), 32, 40);
+
+ check_buf_append(test_4096, "", test_4096, 4096, 4104);
+ check_buf_append(test_4096, test_4096, test_8192, 8192, 8200);
+
+ /* check sequences of appends */
+ check_buf_append_abc("a", "b", "c",
+ "ab", "abc", "abca", "abcab", "abcabc");
+ check_buf_append_abc("a1", "b2", "c3",
+ "a1b2", "a1b2c3", "a1b2c3a1",
+ "a1b2c3a1b2", "a1b2c3a1b2c3");
+ check_buf_append_abc("a1/", "b2/", "c3/",
+ "a1/b2/", "a1/b2/c3/", "a1/b2/c3/a1/",
+ "a1/b2/c3/a1/b2/", "a1/b2/c3/a1/b2/c3/");
+}
+
+/* test swap */
+void test_gitstr__6(void)
+{
+ git_str a = GIT_STR_INIT;
+ git_str b = GIT_STR_INIT;
+
+ git_str_sets(&a, "foo");
+ cl_assert(git_str_oom(&a) == 0);
+ git_str_sets(&b, "bar");
+ cl_assert(git_str_oom(&b) == 0);
+
+ cl_assert_equal_s("foo", git_str_cstr(&a));
+ cl_assert_equal_s("bar", git_str_cstr(&b));
+
+ git_str_swap(&a, &b);
+
+ cl_assert_equal_s("bar", git_str_cstr(&a));
+ cl_assert_equal_s("foo", git_str_cstr(&b));
+
+ git_str_dispose(&a);
+ git_str_dispose(&b);
+}
+
+
+/* test detach/attach data */
+void test_gitstr__7(void)
+{
+ const char *fun = "This is fun";
+ git_str a = GIT_STR_INIT;
+ char *b = NULL;
+
+ git_str_sets(&a, "foo");
+ cl_assert(git_str_oom(&a) == 0);
+ cl_assert_equal_s("foo", git_str_cstr(&a));
+
+ b = git_str_detach(&a);
+
+ cl_assert_equal_s("foo", b);
+ cl_assert_equal_s("", a.ptr);
+ git__free(b);
+
+ b = git_str_detach(&a);
+
+ cl_assert_equal_s(NULL, b);
+ cl_assert_equal_s("", a.ptr);
+
+ git_str_dispose(&a);
+
+ b = git__strdup(fun);
+ git_str_attach(&a, b, 0);
+
+ cl_assert_equal_s(fun, a.ptr);
+ cl_assert(a.size == strlen(fun));
+ cl_assert(a.asize == strlen(fun) + 1);
+
+ git_str_dispose(&a);
+
+ b = git__strdup(fun);
+ git_str_attach(&a, b, strlen(fun) + 1);
+
+ cl_assert_equal_s(fun, a.ptr);
+ cl_assert(a.size == strlen(fun));
+ cl_assert(a.asize == strlen(fun) + 1);
+
+ git_str_dispose(&a);
+}
+
+
+static void
+check_joinbuf_2(
+ const char *a,
+ const char *b,
+ const char *expected)
+{
+ char sep = '/';
+ git_str buf = GIT_STR_INIT;
+
+ git_str_join(&buf, sep, a, b);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(expected, git_str_cstr(&buf));
+ git_str_dispose(&buf);
+}
+
+static void
+check_joinbuf_overlapped(
+ const char *oldval,
+ int ofs_a,
+ const char *b,
+ const char *expected)
+{
+ char sep = '/';
+ git_str buf = GIT_STR_INIT;
+
+ git_str_sets(&buf, oldval);
+ git_str_join(&buf, sep, buf.ptr + ofs_a, b);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(expected, git_str_cstr(&buf));
+ git_str_dispose(&buf);
+}
+
+static void
+check_joinbuf_n_2(
+ const char *a,
+ const char *b,
+ const char *expected)
+{
+ char sep = '/';
+ git_str buf = GIT_STR_INIT;
+
+ git_str_sets(&buf, a);
+ cl_assert(git_str_oom(&buf) == 0);
+
+ git_str_join_n(&buf, sep, 1, b);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(expected, git_str_cstr(&buf));
+
+ git_str_dispose(&buf);
+}
+
+static void
+check_joinbuf_n_4(
+ const char *a,
+ const char *b,
+ const char *c,
+ const char *d,
+ const char *expected)
+{
+ char sep = ';';
+ git_str buf = GIT_STR_INIT;
+ git_str_join_n(&buf, sep, 4, a, b, c, d);
+ cl_assert(git_str_oom(&buf) == 0);
+ cl_assert_equal_s(expected, git_str_cstr(&buf));
+ git_str_dispose(&buf);
+}
+
+/* test join */
+void test_gitstr__8(void)
+{
+ git_str a = GIT_STR_INIT;
+
+ git_str_join_n(&a, '/', 1, "foo");
+ cl_assert(git_str_oom(&a) == 0);
+ cl_assert_equal_s("foo", git_str_cstr(&a));
+
+ git_str_join_n(&a, '/', 1, "bar");
+ cl_assert(git_str_oom(&a) == 0);
+ cl_assert_equal_s("foo/bar", git_str_cstr(&a));
+
+ git_str_join_n(&a, '/', 1, "baz");
+ cl_assert(git_str_oom(&a) == 0);
+ cl_assert_equal_s("foo/bar/baz", git_str_cstr(&a));
+
+ git_str_dispose(&a);
+
+ check_joinbuf_2(NULL, "", "");
+ check_joinbuf_2(NULL, "a", "a");
+ check_joinbuf_2(NULL, "/a", "/a");
+ check_joinbuf_2("", "", "");
+ check_joinbuf_2("", "a", "a");
+ check_joinbuf_2("", "/a", "/a");
+ check_joinbuf_2("a", "", "a/");
+ check_joinbuf_2("a", "/", "a/");
+ check_joinbuf_2("a", "b", "a/b");
+ check_joinbuf_2("/", "a", "/a");
+ check_joinbuf_2("/", "", "/");
+ check_joinbuf_2("/a", "/b", "/a/b");
+ check_joinbuf_2("/a", "/b/", "/a/b/");
+ check_joinbuf_2("/a/", "b/", "/a/b/");
+ check_joinbuf_2("/a/", "/b/", "/a/b/");
+ check_joinbuf_2("/a/", "//b/", "/a/b/");
+ check_joinbuf_2("/abcd", "/defg", "/abcd/defg");
+ check_joinbuf_2("/abcd", "/defg/", "/abcd/defg/");
+ check_joinbuf_2("/abcd/", "defg/", "/abcd/defg/");
+ check_joinbuf_2("/abcd/", "/defg/", "/abcd/defg/");
+
+ check_joinbuf_overlapped("abcd", 0, "efg", "abcd/efg");
+ check_joinbuf_overlapped("abcd", 1, "efg", "bcd/efg");
+ check_joinbuf_overlapped("abcd", 2, "efg", "cd/efg");
+ check_joinbuf_overlapped("abcd", 3, "efg", "d/efg");
+ check_joinbuf_overlapped("abcd", 4, "efg", "efg");
+ check_joinbuf_overlapped("abc/", 2, "efg", "c/efg");
+ check_joinbuf_overlapped("abc/", 3, "efg", "/efg");
+ check_joinbuf_overlapped("abc/", 4, "efg", "efg");
+ check_joinbuf_overlapped("abcd", 3, "", "d/");
+ check_joinbuf_overlapped("abcd", 4, "", "");
+ check_joinbuf_overlapped("abc/", 2, "", "c/");
+ check_joinbuf_overlapped("abc/", 3, "", "/");
+ check_joinbuf_overlapped("abc/", 4, "", "");
+
+ check_joinbuf_n_2("", "", "");
+ check_joinbuf_n_2("", "a", "a");
+ check_joinbuf_n_2("", "/a", "/a");
+ check_joinbuf_n_2("a", "", "a/");
+ check_joinbuf_n_2("a", "/", "a/");
+ check_joinbuf_n_2("a", "b", "a/b");
+ check_joinbuf_n_2("/", "a", "/a");
+ check_joinbuf_n_2("/", "", "/");
+ check_joinbuf_n_2("/a", "/b", "/a/b");
+ check_joinbuf_n_2("/a", "/b/", "/a/b/");
+ check_joinbuf_n_2("/a/", "b/", "/a/b/");
+ check_joinbuf_n_2("/a/", "/b/", "/a/b/");
+ check_joinbuf_n_2("/abcd", "/defg", "/abcd/defg");
+ check_joinbuf_n_2("/abcd", "/defg/", "/abcd/defg/");
+ check_joinbuf_n_2("/abcd/", "defg/", "/abcd/defg/");
+ check_joinbuf_n_2("/abcd/", "/defg/", "/abcd/defg/");
+
+ check_joinbuf_n_4("", "", "", "", "");
+ check_joinbuf_n_4("", "a", "", "", "a;");
+ check_joinbuf_n_4("a", "", "", "", "a;");
+ check_joinbuf_n_4("", "", "", "a", "a");
+ check_joinbuf_n_4("a", "b", "", ";c;d;", "a;b;c;d;");
+ check_joinbuf_n_4("a", "b", "", ";c;d", "a;b;c;d");
+ check_joinbuf_n_4("abcd", "efgh", "ijkl", "mnop", "abcd;efgh;ijkl;mnop");
+ check_joinbuf_n_4("abcd;", "efgh;", "ijkl;", "mnop;", "abcd;efgh;ijkl;mnop;");
+ check_joinbuf_n_4(";abcd;", ";efgh;", ";ijkl;", ";mnop;", ";abcd;efgh;ijkl;mnop;");
+}
+
+void test_gitstr__9(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ /* just some exhaustive tests of various separator placement */
+ char *a[] = { "", "-", "a-", "-a", "-a-" };
+ char *b[] = { "", "-", "b-", "-b", "-b-" };
+ char sep[] = { 0, '-', '/' };
+ char *expect_null[] = { "", "-", "a-", "-a", "-a-",
+ "-", "--", "a--", "-a-", "-a--",
+ "b-", "-b-", "a-b-", "-ab-", "-a-b-",
+ "-b", "--b", "a--b", "-a-b", "-a--b",
+ "-b-", "--b-", "a--b-", "-a-b-", "-a--b-" };
+ char *expect_dash[] = { "", "-", "a-", "-a-", "-a-",
+ "-", "-", "a-", "-a-", "-a-",
+ "b-", "-b-", "a-b-", "-a-b-", "-a-b-",
+ "-b", "-b", "a-b", "-a-b", "-a-b",
+ "-b-", "-b-", "a-b-", "-a-b-", "-a-b-" };
+ char *expect_slas[] = { "", "-/", "a-/", "-a/", "-a-/",
+ "-", "-/-", "a-/-", "-a/-", "-a-/-",
+ "b-", "-/b-", "a-/b-", "-a/b-", "-a-/b-",
+ "-b", "-/-b", "a-/-b", "-a/-b", "-a-/-b",
+ "-b-", "-/-b-", "a-/-b-", "-a/-b-", "-a-/-b-" };
+ char **expect_values[] = { expect_null, expect_dash, expect_slas };
+ char separator, **expect;
+ unsigned int s, i, j;
+
+ for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {
+ separator = sep[s];
+ expect = expect_values[s];
+
+ for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {
+ for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {
+ git_str_join(&buf, separator, a[i], b[j]);
+ cl_assert_equal_s(*expect, buf.ptr);
+ expect++;
+ }
+ }
+ }
+
+ git_str_dispose(&buf);
+}
+
+void test_gitstr__10(void)
+{
+ git_str a = GIT_STR_INIT;
+
+ cl_git_pass(git_str_join_n(&a, '/', 1, "test"));
+ cl_assert_equal_s(a.ptr, "test");
+ cl_git_pass(git_str_join_n(&a, '/', 1, "string"));
+ cl_assert_equal_s(a.ptr, "test/string");
+ git_str_clear(&a);
+ cl_git_pass(git_str_join_n(&a, '/', 3, "test", "string", "join"));
+ cl_assert_equal_s(a.ptr, "test/string/join");
+ cl_git_pass(git_str_join_n(&a, '/', 2, a.ptr, "more"));
+ cl_assert_equal_s(a.ptr, "test/string/join/test/string/join/more");
+
+ git_str_dispose(&a);
+}
+
+void test_gitstr__join3(void)
+{
+ git_str a = GIT_STR_INIT;
+
+ cl_git_pass(git_str_join3(&a, '/', "test", "string", "join"));
+ cl_assert_equal_s("test/string/join", a.ptr);
+ cl_git_pass(git_str_join3(&a, '/', "test/", "string", "join"));
+ cl_assert_equal_s("test/string/join", a.ptr);
+ cl_git_pass(git_str_join3(&a, '/', "test/", "/string", "join"));
+ cl_assert_equal_s("test/string/join", a.ptr);
+ cl_git_pass(git_str_join3(&a, '/', "test/", "/string/", "join"));
+ cl_assert_equal_s("test/string/join", a.ptr);
+ cl_git_pass(git_str_join3(&a, '/', "test/", "/string/", "/join"));
+ cl_assert_equal_s("test/string/join", a.ptr);
+
+ cl_git_pass(git_str_join3(&a, '/', "", "string", "join"));
+ cl_assert_equal_s("string/join", a.ptr);
+ cl_git_pass(git_str_join3(&a, '/', "", "string/", "join"));
+ cl_assert_equal_s("string/join", a.ptr);
+ cl_git_pass(git_str_join3(&a, '/', "", "string/", "/join"));
+ cl_assert_equal_s("string/join", a.ptr);
+
+ cl_git_pass(git_str_join3(&a, '/', "string", "", "join"));
+ cl_assert_equal_s("string/join", a.ptr);
+ cl_git_pass(git_str_join3(&a, '/', "string/", "", "join"));
+ cl_assert_equal_s("string/join", a.ptr);
+ cl_git_pass(git_str_join3(&a, '/', "string/", "", "/join"));
+ cl_assert_equal_s("string/join", a.ptr);
+
+ git_str_dispose(&a);
+}
+
+void test_gitstr__11(void)
+{
+ git_str a = GIT_STR_INIT;
+ char *t1[] = { "nothing", "in", "common" };
+ char *t2[] = { "something", "something else", "some other" };
+ char *t3[] = { "something", "some fun", "no fun" };
+ char *t4[] = { "happy", "happier", "happiest" };
+ char *t5[] = { "happiest", "happier", "happy" };
+ char *t6[] = { "no", "nope", "" };
+ char *t7[] = { "", "doesn't matter" };
+
+ cl_git_pass(git_str_common_prefix(&a, t1, 3));
+ cl_assert_equal_s(a.ptr, "");
+
+ cl_git_pass(git_str_common_prefix(&a, t2, 3));
+ cl_assert_equal_s(a.ptr, "some");
+
+ cl_git_pass(git_str_common_prefix(&a, t3, 3));
+ cl_assert_equal_s(a.ptr, "");
+
+ cl_git_pass(git_str_common_prefix(&a, t4, 3));
+ cl_assert_equal_s(a.ptr, "happ");
+
+ cl_git_pass(git_str_common_prefix(&a, t5, 3));
+ cl_assert_equal_s(a.ptr, "happ");
+
+ cl_git_pass(git_str_common_prefix(&a, t6, 3));
+ cl_assert_equal_s(a.ptr, "");
+
+ cl_git_pass(git_str_common_prefix(&a, t7, 3));
+ cl_assert_equal_s(a.ptr, "");
+
+ git_str_dispose(&a);
+}
+
+void test_gitstr__rfind_variants(void)
+{
+ git_str a = GIT_STR_INIT;
+ ssize_t len;
+
+ cl_git_pass(git_str_sets(&a, "/this/is/it/"));
+
+ len = (ssize_t)git_str_len(&a);
+
+ cl_assert(git_str_rfind(&a, '/') == len - 1);
+ cl_assert(git_str_rfind_next(&a, '/') == len - 4);
+
+ cl_assert(git_str_rfind(&a, 'i') == len - 3);
+ cl_assert(git_str_rfind_next(&a, 'i') == len - 3);
+
+ cl_assert(git_str_rfind(&a, 'h') == 2);
+ cl_assert(git_str_rfind_next(&a, 'h') == 2);
+
+ cl_assert(git_str_rfind(&a, 'q') == -1);
+ cl_assert(git_str_rfind_next(&a, 'q') == -1);
+
+ git_str_dispose(&a);
+}
+
+void test_gitstr__puts_escaped(void)
+{
+ git_str a = GIT_STR_INIT;
+
+ git_str_clear(&a);
+ cl_git_pass(git_str_puts_escaped(&a, "this is a test", "", ""));
+ cl_assert_equal_s("this is a test", a.ptr);
+
+ git_str_clear(&a);
+ cl_git_pass(git_str_puts_escaped(&a, "this is a test", "t", "\\"));
+ cl_assert_equal_s("\\this is a \\tes\\t", a.ptr);
+
+ git_str_clear(&a);
+ cl_git_pass(git_str_puts_escaped(&a, "this is a test", "i ", "__"));
+ cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr);
+
+ git_str_clear(&a);
+ cl_git_pass(git_str_puts_escape_regex(&a, "^match\\s*[A-Z]+.*"));
+ cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr);
+
+ git_str_dispose(&a);
+}
+
+static void assert_unescape(char *expected, char *to_unescape) {
+ git_str buf = GIT_STR_INIT;
+
+ cl_git_pass(git_str_sets(&buf, to_unescape));
+ git_str_unescape(&buf);
+ cl_assert_equal_s(expected, buf.ptr);
+ cl_assert_equal_sz(strlen(expected), buf.size);
+
+ git_str_dispose(&buf);
+}
+
+void test_gitstr__unescape(void)
+{
+ assert_unescape("Escaped\\", "Es\\ca\\ped\\");
+ assert_unescape("Es\\caped\\", "Es\\\\ca\\ped\\\\");
+ assert_unescape("\\", "\\");
+ assert_unescape("\\", "\\\\");
+ assert_unescape("", "");
+}
+
+void test_gitstr__encode_base64(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ /* t h i s
+ * 0x 74 68 69 73
+ * 0b 01110100 01101000 01101001 01110011
+ * 0b 011101 000110 100001 101001 011100 110000
+ * 0x 1d 06 21 29 1c 30
+ * d G h p c w
+ */
+ cl_git_pass(git_str_encode_base64(&buf, "this", 4));
+ cl_assert_equal_s("dGhpcw==", buf.ptr);
+
+ git_str_clear(&buf);
+ cl_git_pass(git_str_encode_base64(&buf, "this!", 5));
+ cl_assert_equal_s("dGhpcyE=", buf.ptr);
+
+ git_str_clear(&buf);
+ cl_git_pass(git_str_encode_base64(&buf, "this!\n", 6));
+ cl_assert_equal_s("dGhpcyEK", buf.ptr);
+
+ git_str_dispose(&buf);
+}
+
+void test_gitstr__decode_base64(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ cl_git_pass(git_str_decode_base64(&buf, "dGhpcw==", 8));
+ cl_assert_equal_s("this", buf.ptr);
+
+ git_str_clear(&buf);
+ cl_git_pass(git_str_decode_base64(&buf, "dGhpcyE=", 8));
+ cl_assert_equal_s("this!", buf.ptr);
+
+ git_str_clear(&buf);
+ cl_git_pass(git_str_decode_base64(&buf, "dGhpcyEK", 8));
+ cl_assert_equal_s("this!\n", buf.ptr);
+
+ cl_git_fail(git_str_decode_base64(&buf, "This is not a valid base64 string!!!", 36));
+ cl_assert_equal_s("this!\n", buf.ptr);
+
+ git_str_dispose(&buf);
+}
+
+void test_gitstr__encode_base85(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ cl_git_pass(git_str_encode_base85(&buf, "this", 4));
+ cl_assert_equal_s("bZBXF", buf.ptr);
+ git_str_clear(&buf);
+
+ cl_git_pass(git_str_encode_base85(&buf, "two rnds", 8));
+ cl_assert_equal_s("ba!tca&BaE", buf.ptr);
+ git_str_clear(&buf);
+
+ cl_git_pass(git_str_encode_base85(&buf, "this is base 85 encoded",
+ strlen("this is base 85 encoded")));
+ cl_assert_equal_s("bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", buf.ptr);
+ git_str_clear(&buf);
+
+ git_str_dispose(&buf);
+}
+
+void test_gitstr__decode_base85(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ cl_git_pass(git_str_decode_base85(&buf, "bZBXF", 5, 4));
+ cl_assert_equal_sz(4, buf.size);
+ cl_assert_equal_s("this", buf.ptr);
+ git_str_clear(&buf);
+
+ cl_git_pass(git_str_decode_base85(&buf, "ba!tca&BaE", 10, 8));
+ cl_assert_equal_sz(8, buf.size);
+ cl_assert_equal_s("two rnds", buf.ptr);
+ git_str_clear(&buf);
+
+ cl_git_pass(git_str_decode_base85(&buf, "bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", 30, 23));
+ cl_assert_equal_sz(23, buf.size);
+ cl_assert_equal_s("this is base 85 encoded", buf.ptr);
+ git_str_clear(&buf);
+
+ git_str_dispose(&buf);
+}
+
+void test_gitstr__decode_base85_fails_gracefully(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ git_str_puts(&buf, "foobar");
+
+ cl_git_fail(git_str_decode_base85(&buf, "invalid charsZZ", 15, 42));
+ cl_git_fail(git_str_decode_base85(&buf, "invalidchars__ ", 15, 42));
+ cl_git_fail(git_str_decode_base85(&buf, "overflowZZ~~~~~", 15, 42));
+ cl_git_fail(git_str_decode_base85(&buf, "truncated", 9, 42));
+ cl_assert_equal_sz(6, buf.size);
+ cl_assert_equal_s("foobar", buf.ptr);
+
+ git_str_dispose(&buf);
+}
+
+void test_gitstr__classify_with_utf8(void)
+{
+ char *data0 = "Simple text\n";
+ size_t data0len = 12;
+ char *data1 = "Is that UTF-8 data I see…\nYep!\n";
+ size_t data1len = 31;
+ char *data2 = "Internal NUL!!!\000\n\nI see you!\n";
+ size_t data2len = 29;
+ char *data3 = "\xef\xbb\xbfThis is UTF-8 with a BOM.\n";
+ size_t data3len = 20;
+ git_str b;
+
+ b.ptr = data0; b.size = b.asize = data0len;
+ cl_assert(!git_str_is_binary(&b));
+ cl_assert(!git_str_contains_nul(&b));
+
+ b.ptr = data1; b.size = b.asize = data1len;
+ cl_assert(!git_str_is_binary(&b));
+ cl_assert(!git_str_contains_nul(&b));
+
+ b.ptr = data2; b.size = b.asize = data2len;
+ cl_assert(git_str_is_binary(&b));
+ cl_assert(git_str_contains_nul(&b));
+
+ b.ptr = data3; b.size = b.asize = data3len;
+ cl_assert(!git_str_is_binary(&b));
+ cl_assert(!git_str_contains_nul(&b));
+}
+
+#include "crlf.h"
+
+#define check_buf(expected,buf) do { \
+ cl_assert_equal_s(expected, buf.ptr); \
+ cl_assert_equal_sz(strlen(expected), buf.size); } while (0)
+
+void test_gitstr__lf_and_crlf_conversions(void)
+{
+ git_str src = GIT_STR_INIT, tgt = GIT_STR_INIT;
+
+ /* LF source */
+
+ git_str_sets(&src, "lf\nlf\nlf\nlf\n");
+
+ cl_git_pass(git_str_lf_to_crlf(&tgt, &src));
+ check_buf("lf\r\nlf\r\nlf\r\nlf\r\n", tgt);
+
+ cl_git_pass(git_str_crlf_to_lf(&tgt, &src));
+ check_buf(src.ptr, tgt);
+
+ git_str_sets(&src, "\nlf\nlf\nlf\nlf\nlf");
+
+ cl_git_pass(git_str_lf_to_crlf(&tgt, &src));
+ check_buf("\r\nlf\r\nlf\r\nlf\r\nlf\r\nlf", tgt);
+
+ cl_git_pass(git_str_crlf_to_lf(&tgt, &src));
+ check_buf(src.ptr, tgt);
+
+ /* CRLF source */
+
+ git_str_sets(&src, "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n");
+
+ cl_git_pass(git_str_lf_to_crlf(&tgt, &src));
+ check_buf("crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n", tgt);
+
+ git_str_sets(&src, "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n");
+
+ cl_git_pass(git_str_crlf_to_lf(&tgt, &src));
+ check_buf("crlf\ncrlf\ncrlf\ncrlf\n", tgt);
+
+ git_str_sets(&src, "\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf");
+
+ cl_git_pass(git_str_lf_to_crlf(&tgt, &src));
+ check_buf("\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf", tgt);
+
+ git_str_sets(&src, "\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf");
+
+ cl_git_pass(git_str_crlf_to_lf(&tgt, &src));
+ check_buf("\ncrlf\ncrlf\ncrlf\ncrlf\ncrlf", tgt);
+
+ /* CRLF in LF text */
+
+ git_str_sets(&src, "\nlf\nlf\ncrlf\r\nlf\nlf\ncrlf\r\n");
+
+ cl_git_pass(git_str_lf_to_crlf(&tgt, &src));
+ check_buf("\r\nlf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\ncrlf\r\n", tgt);
+
+ git_str_sets(&src, "\nlf\nlf\ncrlf\r\nlf\nlf\ncrlf\r\n");
+
+ cl_git_pass(git_str_crlf_to_lf(&tgt, &src));
+ check_buf("\nlf\nlf\ncrlf\nlf\nlf\ncrlf\n", tgt);
+
+ /* LF in CRLF text */
+
+ git_str_sets(&src, "\ncrlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf");
+
+ cl_git_pass(git_str_lf_to_crlf(&tgt, &src));
+ check_buf("\r\ncrlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf", tgt);
+
+ cl_git_pass(git_str_crlf_to_lf(&tgt, &src));
+ check_buf("\ncrlf\ncrlf\nlf\ncrlf\ncrlf", tgt);
+
+ /* bare CR test */
+
+ git_str_sets(&src, "\rcrlf\r\nlf\nlf\ncr\rcrlf\r\nlf\ncr\r");
+
+ cl_git_pass(git_str_lf_to_crlf(&tgt, &src));
+ check_buf("\rcrlf\r\nlf\r\nlf\r\ncr\rcrlf\r\nlf\r\ncr\r", tgt);
+
+ git_str_sets(&src, "\rcrlf\r\nlf\nlf\ncr\rcrlf\r\nlf\ncr\r");
+
+ cl_git_pass(git_str_crlf_to_lf(&tgt, &src));
+ check_buf("\rcrlf\nlf\nlf\ncr\rcrlf\nlf\ncr\r", tgt);
+
+ git_str_sets(&src, "\rcr\r");
+ cl_git_pass(git_str_lf_to_crlf(&tgt, &src));
+ check_buf(src.ptr, tgt);
+ cl_git_pass(git_str_crlf_to_lf(&tgt, &src));
+ check_buf("\rcr\r", tgt);
+
+ git_str_dispose(&src);
+ git_str_dispose(&tgt);
+
+ /* blob correspondence tests */
+
+ git_str_sets(&src, ALL_CRLF_TEXT_RAW);
+ cl_git_pass(git_str_lf_to_crlf(&tgt, &src));
+ check_buf(ALL_CRLF_TEXT_AS_CRLF, tgt);
+ git_str_sets(&src, ALL_CRLF_TEXT_RAW);
+ cl_git_pass(git_str_crlf_to_lf(&tgt, &src));
+ check_buf(ALL_CRLF_TEXT_AS_LF, tgt);
+ git_str_dispose(&src);
+ git_str_dispose(&tgt);
+
+ git_str_sets(&src, ALL_LF_TEXT_RAW);
+ cl_git_pass(git_str_lf_to_crlf(&tgt, &src));
+ check_buf(ALL_LF_TEXT_AS_CRLF, tgt);
+ git_str_sets(&src, ALL_LF_TEXT_RAW);
+ cl_git_pass(git_str_crlf_to_lf(&tgt, &src));
+ check_buf(ALL_LF_TEXT_AS_LF, tgt);
+ git_str_dispose(&src);
+ git_str_dispose(&tgt);
+
+ git_str_sets(&src, MORE_CRLF_TEXT_RAW);
+ cl_git_pass(git_str_lf_to_crlf(&tgt, &src));
+ check_buf(MORE_CRLF_TEXT_AS_CRLF, tgt);
+ git_str_sets(&src, MORE_CRLF_TEXT_RAW);
+ cl_git_pass(git_str_crlf_to_lf(&tgt, &src));
+ check_buf(MORE_CRLF_TEXT_AS_LF, tgt);
+ git_str_dispose(&src);
+ git_str_dispose(&tgt);
+
+ git_str_sets(&src, MORE_LF_TEXT_RAW);
+ cl_git_pass(git_str_lf_to_crlf(&tgt, &src));
+ check_buf(MORE_LF_TEXT_AS_CRLF, tgt);
+ git_str_sets(&src, MORE_LF_TEXT_RAW);
+ cl_git_pass(git_str_crlf_to_lf(&tgt, &src));
+ check_buf(MORE_LF_TEXT_AS_LF, tgt);
+ git_str_dispose(&src);
+ git_str_dispose(&tgt);
+}
+
+void test_gitstr__dont_grow_borrowed(void)
+{
+ const char *somestring = "blah blah";
+ git_str buf = GIT_STR_INIT;
+
+ git_str_attach_notowned(&buf, somestring, strlen(somestring) + 1);
+ cl_assert_equal_p(somestring, buf.ptr);
+ cl_assert_equal_i(0, buf.asize);
+ cl_assert_equal_i(strlen(somestring) + 1, buf.size);
+
+ cl_git_fail_with(GIT_EINVALID, git_str_grow(&buf, 1024));
+}
+
+void test_gitstr__dont_hit_infinite_loop_when_resizing(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ cl_git_pass(git_str_puts(&buf, "foobar"));
+ /*
+ * We do not care whether this succeeds or fails, which
+ * would depend on platform-specific allocation
+ * semantics. We only want to know that the function
+ * actually returns.
+ */
+ (void)git_str_try_grow(&buf, SIZE_MAX, true);
+
+ git_str_dispose(&buf);
+}
+
+void test_gitstr__avoid_printing_into_oom_buffer(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ /* Emulate OOM situation with a previous allocation */
+ buf.asize = 8;
+ buf.ptr = git_str__oom;
+
+ /*
+ * Print the same string again. As the buffer still has
+ * an `asize` of 8 due to the previous print,
+ * `ENSURE_SIZE` would not try to reallocate the array at
+ * all. As it didn't explicitly check for `git_str__oom`
+ * in earlier versions, this would've resulted in it
+ * returning successfully and thus `git_str_puts` would
+ * just print into the `git_str__oom` array.
+ */
+ cl_git_fail(git_str_puts(&buf, "foobar"));
+}
diff --git a/tests/util/hex.c b/tests/util/hex.c
new file mode 100644
index 0000000..c5fea0a
--- /dev/null
+++ b/tests/util/hex.c
@@ -0,0 +1,22 @@
+#include "clar_libgit2.h"
+#include "util.h"
+
+void test_hex__fromhex(void)
+{
+ /* Passing cases */
+ cl_assert(git__fromhex('0') == 0x0);
+ cl_assert(git__fromhex('1') == 0x1);
+ cl_assert(git__fromhex('3') == 0x3);
+ cl_assert(git__fromhex('9') == 0x9);
+ cl_assert(git__fromhex('A') == 0xa);
+ cl_assert(git__fromhex('C') == 0xc);
+ cl_assert(git__fromhex('F') == 0xf);
+ cl_assert(git__fromhex('a') == 0xa);
+ cl_assert(git__fromhex('c') == 0xc);
+ cl_assert(git__fromhex('f') == 0xf);
+
+ /* Failing cases */
+ cl_assert(git__fromhex('g') == -1);
+ cl_assert(git__fromhex('z') == -1);
+ cl_assert(git__fromhex('X') == -1);
+}
diff --git a/tests/util/hostname.c b/tests/util/hostname.c
new file mode 100644
index 0000000..5d8bfe2
--- /dev/null
+++ b/tests/util/hostname.c
@@ -0,0 +1,13 @@
+#include "clar_libgit2.h"
+#include "net.h"
+
+void test_hostname__matches_cert(void)
+{
+ cl_assert_equal_b(true, git_net_hostname_matches_cert("www.example.org", "*.example.org"));
+ cl_assert_equal_b(true, git_net_hostname_matches_cert("www.foo.example.org", "*.foo.example.org"));
+ cl_assert_equal_b(false, git_net_hostname_matches_cert("foo.example.org", "*.foo.example.org"));
+ cl_assert_equal_b(false, git_net_hostname_matches_cert("www.example.org", "*.foo.example.org"));
+ cl_assert_equal_b(false, git_net_hostname_matches_cert("example.org", "*.example.org"));
+ cl_assert_equal_b(false, git_net_hostname_matches_cert("www.foo.example.org", "*.example.org"));
+ cl_assert_equal_b(false, git_net_hostname_matches_cert("blah.www.www.example.org", "*.example.org"));
+}
diff --git a/tests/util/iconv.c b/tests/util/iconv.c
new file mode 100644
index 0000000..e14aebb
--- /dev/null
+++ b/tests/util/iconv.c
@@ -0,0 +1,78 @@
+#include "clar_libgit2.h"
+#include "fs_path.h"
+
+#ifdef GIT_USE_ICONV
+static git_fs_path_iconv_t ic;
+static char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D";
+static char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D";
+#endif
+
+void test_iconv__initialize(void)
+{
+#ifdef GIT_USE_ICONV
+ cl_git_pass(git_fs_path_iconv_init_precompose(&ic));
+#endif
+}
+
+void test_iconv__cleanup(void)
+{
+#ifdef GIT_USE_ICONV
+ git_fs_path_iconv_clear(&ic);
+#endif
+}
+
+void test_iconv__unchanged(void)
+{
+#ifdef GIT_USE_ICONV
+ const char *data = "Ascii data", *original = data;
+ size_t datalen = strlen(data);
+
+ cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen));
+ GIT_UNUSED(datalen);
+
+ /* There are no high bits set, so this should leave data untouched */
+ cl_assert(data == original);
+#endif
+}
+
+void test_iconv__decomposed_to_precomposed(void)
+{
+#ifdef GIT_USE_ICONV
+ const char *data = nfd;
+ size_t datalen, nfdlen = strlen(nfd);
+
+ datalen = nfdlen;
+ cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen));
+ GIT_UNUSED(datalen);
+
+ /* The decomposed nfd string should be transformed to the nfc form
+ * (on platforms where iconv is enabled, of course).
+ */
+ cl_assert_equal_s(nfc, data);
+
+ /* should be able to do it multiple times with the same git_fs_path_iconv_t */
+ data = nfd; datalen = nfdlen;
+ cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen));
+ cl_assert_equal_s(nfc, data);
+
+ data = nfd; datalen = nfdlen;
+ cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen));
+ cl_assert_equal_s(nfc, data);
+#endif
+}
+
+void test_iconv__precomposed_is_unmodified(void)
+{
+#ifdef GIT_USE_ICONV
+ const char *data = nfc;
+ size_t datalen = strlen(nfc);
+
+ cl_git_pass(git_fs_path_iconv(&ic, &data, &datalen));
+ GIT_UNUSED(datalen);
+
+ /* data is already in precomposed form, so even though some bytes have
+ * the high-bit set, the iconv transform should result in no change.
+ */
+ cl_assert_equal_s(nfc, data);
+#endif
+}
diff --git a/tests/util/init.c b/tests/util/init.c
new file mode 100644
index 0000000..78b3dd0
--- /dev/null
+++ b/tests/util/init.c
@@ -0,0 +1,54 @@
+#include "clar_libgit2.h"
+
+void test_init__returns_count(void)
+{
+ /* libgit2_tests initializes us first, so we have an existing
+ * initialization.
+ */
+ cl_assert_equal_i(2, git_libgit2_init());
+ cl_assert_equal_i(3, git_libgit2_init());
+
+ cl_assert_equal_i(2, git_libgit2_shutdown());
+ cl_assert_equal_i(1, git_libgit2_shutdown());
+}
+
+void test_init__reinit_succeeds(void)
+{
+ cl_assert_equal_i(0, git_libgit2_shutdown());
+ cl_assert_equal_i(1, git_libgit2_init());
+ cl_sandbox_set_search_path_defaults();
+}
+
+#ifdef GIT_THREADS
+static void *reinit(void *unused)
+{
+ unsigned i;
+
+ for (i = 0; i < 20; i++) {
+ cl_assert(git_libgit2_init() > 0);
+ cl_assert(git_libgit2_shutdown() >= 0);
+ }
+
+ return unused;
+}
+#endif
+
+void test_init__concurrent_init_succeeds(void)
+{
+#ifdef GIT_THREADS
+ git_thread threads[10];
+ unsigned i;
+
+ cl_assert_equal_i(2, git_libgit2_init());
+
+ for (i = 0; i < ARRAY_SIZE(threads); i++)
+ git_thread_create(&threads[i], reinit, NULL);
+ for (i = 0; i < ARRAY_SIZE(threads); i++)
+ git_thread_join(&threads[i], NULL);
+
+ cl_assert_equal_i(1, git_libgit2_shutdown());
+ cl_sandbox_set_search_path_defaults();
+#else
+ cl_skip();
+#endif
+}
diff --git a/tests/util/integer.c b/tests/util/integer.c
new file mode 100644
index 0000000..2226ab4
--- /dev/null
+++ b/tests/util/integer.c
@@ -0,0 +1,253 @@
+#include "clar_libgit2.h"
+
+void test_integer__multiply_int64_no_overflow(void)
+{
+#if !defined(git__multiply_int64_overflow)
+ int64_t result = 0;
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x0)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x1)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x1)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x2)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x2)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x7ffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x7ffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x800000000000000)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x800000000000000)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(0x7fffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x7fffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x0), INT64_C(-0x8000000000000000)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x0)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x1)));
+ cl_assert_equal_i(result, INT64_C(0x1));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x1)));
+ cl_assert_equal_i(result, INT64_C(-0x1));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x2)));
+ cl_assert_equal_i(result, INT64_C(0x2));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x2)));
+ cl_assert_equal_i(result, INT64_C(-0x2));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x7ffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x7ffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x800000000000000)));
+ cl_assert_equal_i(result, INT64_C(0x800000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x800000000000000)));
+ cl_assert_equal_i(result, INT64_C(-0x800000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(0x7fffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x7fffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x0)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x1)));
+ cl_assert_equal_i(result, INT64_C(-0x1));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x1)));
+ cl_assert_equal_i(result, INT64_C(0x1));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x2)));
+ cl_assert_equal_i(result, INT64_C(-0x2));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x2)));
+ cl_assert_equal_i(result, INT64_C(0x2));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x7ffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x7ffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x800000000000000)));
+ cl_assert_equal_i(result, INT64_C(-0x800000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x800000000000000)));
+ cl_assert_equal_i(result, INT64_C(0x800000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(0x7fffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x7fffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x0)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x1)));
+ cl_assert_equal_i(result, INT64_C(0x2));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x1)));
+ cl_assert_equal_i(result, INT64_C(-0x2));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x2)));
+ cl_assert_equal_i(result, INT64_C(0x4));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x2)));
+ cl_assert_equal_i(result, INT64_C(-0x4));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x7ffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(0xffffffffffffffe));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x7ffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x800000000000000)));
+ cl_assert_equal_i(result, INT64_C(0x1000000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x800000000000000)));
+ cl_assert_equal_i(result, INT64_C(-0x1000000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x0)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x1)));
+ cl_assert_equal_i(result, INT64_C(-0x2));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x1)));
+ cl_assert_equal_i(result, INT64_C(0x2));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x2)));
+ cl_assert_equal_i(result, INT64_C(-0x4));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x2)));
+ cl_assert_equal_i(result, INT64_C(0x4));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x7ffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x7ffffffffffffff)));
+ cl_assert_equal_i(result, INT64_C(0xffffffffffffffe));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x800000000000000)));
+ cl_assert_equal_i(result, INT64_C(-0x1000000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x800000000000000)));
+ cl_assert_equal_i(result, INT64_C(0x1000000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x4000000000000000)));
+ cl_assert_equal_i(result, INT64_C(-0x8000000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x0)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x1)));
+ cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x1)));
+ cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x2)));
+ cl_assert_equal_i(result, INT64_C(0xffffffffffffffe));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x2)));
+ cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x0)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x1)));
+ cl_assert_equal_i(result, INT64_C(-0x7ffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x1)));
+ cl_assert_equal_i(result, INT64_C(0x7ffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x2)));
+ cl_assert_equal_i(result, INT64_C(-0xffffffffffffffe));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x2)));
+ cl_assert_equal_i(result, INT64_C(0xffffffffffffffe));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x0)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x1)));
+ cl_assert_equal_i(result, INT64_C(0x800000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x1)));
+ cl_assert_equal_i(result, INT64_C(-0x800000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x2)));
+ cl_assert_equal_i(result, INT64_C(0x1000000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x2)));
+ cl_assert_equal_i(result, INT64_C(-0x1000000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x0)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x1)));
+ cl_assert_equal_i(result, INT64_C(-0x800000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x1)));
+ cl_assert_equal_i(result, INT64_C(0x800000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x2)));
+ cl_assert_equal_i(result, INT64_C(-0x1000000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x2)));
+ cl_assert_equal_i(result, INT64_C(0x1000000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x0)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x1)));
+ cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x1)));
+ cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x4000000000000000), INT64_C(0x2)));
+ cl_assert_equal_i(result, INT64_C(-0x8000000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x0)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x1)));
+ cl_assert_equal_i(result, INT64_C(-0x7fffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x1)));
+ cl_assert_equal_i(result, INT64_C(0x7fffffffffffffff));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x0)));
+ cl_assert_equal_i(result, INT64_C(0x0));
+#endif
+}
+
+void test_integer__multiply_int64_overflow(void)
+{
+#if !defined(git__multiply_int64_overflow)
+ int64_t result = 0;
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x4000000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x2), INT64_C(-0x8000000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x2), INT64_C(-0x8000000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7ffffffffffffff), INT64_C(-0x8000000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7ffffffffffffff), INT64_C(-0x8000000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x800000000000000), INT64_C(-0x8000000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x800000000000000), INT64_C(-0x8000000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x4000000000000000), INT64_C(0x2)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x2)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x2)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x7fffffffffffffff), INT64_C(-0x8000000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x2)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x2)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x7fffffffffffffff), INT64_C(-0x8000000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x2)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x2)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x7ffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x800000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x7fffffffffffffff)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x8000000000000000)));
+#endif
+}
+
+void test_integer__multiply_int64_edge_cases(void)
+{
+#if !defined(git__multiply_int64_overflow)
+ int64_t result = 0;
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(-0x1)));
+ cl_assert_equal_i(result, INT64_C(-0x8000000000000000));
+ cl_assert(!git__multiply_int64_overflow(&result, INT64_C(-0x1), INT64_C(-0x8000000000000000)));
+ cl_assert_equal_i(result, INT64_C(-0x8000000000000000));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(0x1), INT64_C(-0x8000000000000000)));
+ cl_assert(git__multiply_int64_overflow(&result, INT64_C(-0x8000000000000000), INT64_C(0x1)));
+#endif
+}
diff --git a/tests/util/link.c b/tests/util/link.c
new file mode 100644
index 0000000..5909e26
--- /dev/null
+++ b/tests/util/link.c
@@ -0,0 +1,630 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+
+#ifdef GIT_WIN32
+# include "win32/reparse.h"
+#endif
+
+void test_link__cleanup(void)
+{
+#ifdef GIT_WIN32
+ RemoveDirectory("lstat_junction");
+ RemoveDirectory("lstat_dangling");
+ RemoveDirectory("lstat_dangling_dir");
+ RemoveDirectory("lstat_dangling_junction");
+
+ RemoveDirectory("stat_junction");
+ RemoveDirectory("stat_dangling");
+ RemoveDirectory("stat_dangling_dir");
+ RemoveDirectory("stat_dangling_junction");
+#endif
+}
+
+#ifdef GIT_WIN32
+static bool should_run(void)
+{
+ static SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY };
+ PSID admin_sid;
+ BOOL is_admin;
+
+ cl_win32_pass(AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &admin_sid));
+ cl_win32_pass(CheckTokenMembership(NULL, admin_sid, &is_admin));
+ FreeSid(admin_sid);
+
+ return is_admin ? true : false;
+}
+#else
+static bool should_run(void)
+{
+ return true;
+}
+#endif
+
+static void do_symlink(const char *old, const char *new, int is_dir)
+{
+#ifndef GIT_WIN32
+ GIT_UNUSED(is_dir);
+
+ cl_must_pass(symlink(old, new));
+#else
+ typedef DWORD (WINAPI *create_symlink_func)(LPCTSTR, LPCTSTR, DWORD);
+ HMODULE module;
+ create_symlink_func pCreateSymbolicLink;
+
+ cl_assert(module = GetModuleHandle("kernel32"));
+ cl_assert(pCreateSymbolicLink = (create_symlink_func)(void *)GetProcAddress(module, "CreateSymbolicLinkA"));
+
+ cl_win32_pass(pCreateSymbolicLink(new, old, is_dir));
+#endif
+}
+
+static void do_hardlink(const char *old, const char *new)
+{
+#ifndef GIT_WIN32
+ cl_must_pass(link(old, new));
+#else
+ typedef DWORD (WINAPI *create_hardlink_func)(LPCTSTR, LPCTSTR, LPSECURITY_ATTRIBUTES);
+ HMODULE module;
+ create_hardlink_func pCreateHardLink;
+
+ cl_assert(module = GetModuleHandle("kernel32"));
+ cl_assert(pCreateHardLink = (create_hardlink_func)(void *)GetProcAddress(module, "CreateHardLinkA"));
+
+ cl_win32_pass(pCreateHardLink(new, old, 0));
+#endif
+}
+
+#ifdef GIT_WIN32
+
+static void do_junction(const char *old, const char *new)
+{
+ GIT_REPARSE_DATA_BUFFER *reparse_buf;
+ HANDLE handle;
+ git_str unparsed_buf = GIT_STR_INIT;
+ wchar_t *subst_utf16, *print_utf16;
+ DWORD ioctl_ret;
+ int subst_utf16_len, subst_byte_len, print_utf16_len, print_byte_len, ret;
+ USHORT reparse_buflen;
+ size_t i;
+
+ /* Junction targets must be the unparsed name, starting with \??\, using
+ * backslashes instead of forward, and end in a trailing backslash.
+ * eg: \??\C:\Foo\
+ */
+ git_str_puts(&unparsed_buf, "\\??\\");
+
+ for (i = 0; i < strlen(old); i++)
+ git_str_putc(&unparsed_buf, old[i] == '/' ? '\\' : old[i]);
+
+ git_str_putc(&unparsed_buf, '\\');
+
+ subst_utf16_len = git_utf8_to_16(NULL, 0, git_str_cstr(&unparsed_buf));
+ subst_byte_len = subst_utf16_len * sizeof(WCHAR);
+
+ print_utf16_len = subst_utf16_len - 4;
+ print_byte_len = subst_byte_len - (4 * sizeof(WCHAR));
+
+ /* The junction must be an empty directory before the junction attribute
+ * can be added.
+ */
+ cl_win32_pass(CreateDirectoryA(new, NULL));
+
+ handle = CreateFileA(new, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ cl_win32_pass(handle != INVALID_HANDLE_VALUE);
+
+ reparse_buflen = (USHORT)(REPARSE_DATA_HEADER_SIZE +
+ REPARSE_DATA_MOUNTPOINT_HEADER_SIZE +
+ subst_byte_len + sizeof(WCHAR) +
+ print_byte_len + sizeof(WCHAR));
+
+ reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen);
+ cl_assert(reparse_buf);
+
+ subst_utf16 = reparse_buf->ReparseBuffer.MountPoint.PathBuffer;
+ print_utf16 = subst_utf16 + subst_utf16_len + 1;
+
+ ret = git_utf8_to_16(subst_utf16, subst_utf16_len + 1,
+ git_str_cstr(&unparsed_buf));
+ cl_assert_equal_i(subst_utf16_len, ret);
+
+ ret = git_utf8_to_16(print_utf16,
+ print_utf16_len + 1, git_str_cstr(&unparsed_buf) + 4);
+ cl_assert_equal_i(print_utf16_len, ret);
+
+ reparse_buf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
+ reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset = 0;
+ reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength = subst_byte_len;
+ reparse_buf->ReparseBuffer.MountPoint.PrintNameOffset = (USHORT)(subst_byte_len + sizeof(WCHAR));
+ reparse_buf->ReparseBuffer.MountPoint.PrintNameLength = print_byte_len;
+ reparse_buf->ReparseDataLength = reparse_buflen - REPARSE_DATA_HEADER_SIZE;
+
+ cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
+ reparse_buf, reparse_buflen, NULL, 0, &ioctl_ret, NULL));
+
+ CloseHandle(handle);
+ LocalFree(reparse_buf);
+
+ git_str_dispose(&unparsed_buf);
+}
+
+static void do_custom_reparse(const char *path)
+{
+ REPARSE_GUID_DATA_BUFFER *reparse_buf;
+ HANDLE handle;
+ DWORD ioctl_ret;
+
+ const char *reparse_data = "Reparse points are silly.";
+ size_t reparse_buflen = REPARSE_GUID_DATA_BUFFER_HEADER_SIZE +
+ strlen(reparse_data) + 1;
+
+ reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen);
+ cl_assert(reparse_buf);
+
+ reparse_buf->ReparseTag = 42;
+ reparse_buf->ReparseDataLength = (WORD)(strlen(reparse_data) + 1);
+
+ reparse_buf->ReparseGuid.Data1 = 0xdeadbeef;
+ reparse_buf->ReparseGuid.Data2 = 0xdead;
+ reparse_buf->ReparseGuid.Data3 = 0xbeef;
+ reparse_buf->ReparseGuid.Data4[0] = 42;
+ reparse_buf->ReparseGuid.Data4[1] = 42;
+ reparse_buf->ReparseGuid.Data4[2] = 42;
+ reparse_buf->ReparseGuid.Data4[3] = 42;
+ reparse_buf->ReparseGuid.Data4[4] = 42;
+ reparse_buf->ReparseGuid.Data4[5] = 42;
+ reparse_buf->ReparseGuid.Data4[6] = 42;
+ reparse_buf->ReparseGuid.Data4[7] = 42;
+ reparse_buf->ReparseGuid.Data4[8] = 42;
+
+ memcpy(reparse_buf->GenericReparseBuffer.DataBuffer,
+ reparse_data, strlen(reparse_data) + 1);
+
+ handle = CreateFileA(path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ cl_win32_pass(handle != INVALID_HANDLE_VALUE);
+
+ cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
+ reparse_buf,
+ reparse_buf->ReparseDataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE,
+ NULL, 0, &ioctl_ret, NULL));
+
+ CloseHandle(handle);
+ LocalFree(reparse_buf);
+}
+
+#endif
+
+void test_link__stat_regular_file(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("stat_regfile", "This is a regular file!\n");
+
+ cl_must_pass(p_stat("stat_regfile", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(24, st.st_size);
+}
+
+void test_link__lstat_regular_file(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("lstat_regfile", "This is a regular file!\n");
+
+ cl_must_pass(p_stat("lstat_regfile", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(24, st.st_size);
+}
+
+void test_link__stat_symlink(void)
+{
+ struct stat st;
+
+ if (!should_run())
+ clar__skip();
+
+ cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n");
+ do_symlink("stat_target", "stat_symlink", 0);
+
+ cl_must_pass(p_stat("stat_target", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(39, st.st_size);
+
+ cl_must_pass(p_stat("stat_symlink", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(39, st.st_size);
+}
+
+void test_link__stat_symlink_directory(void)
+{
+ struct stat st;
+
+ if (!should_run())
+ clar__skip();
+
+ p_mkdir("stat_dirtarget", 0777);
+ do_symlink("stat_dirtarget", "stat_dirlink", 1);
+
+ cl_must_pass(p_stat("stat_dirtarget", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ cl_must_pass(p_stat("stat_dirlink", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+}
+
+void test_link__stat_symlink_chain(void)
+{
+ struct stat st;
+
+ if (!should_run())
+ clar__skip();
+
+ cl_git_rewritefile("stat_final_target", "Final target of some symbolic links...\n");
+ do_symlink("stat_final_target", "stat_chain_3", 0);
+ do_symlink("stat_chain_3", "stat_chain_2", 0);
+ do_symlink("stat_chain_2", "stat_chain_1", 0);
+
+ cl_must_pass(p_stat("stat_chain_1", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(39, st.st_size);
+}
+
+void test_link__stat_dangling_symlink(void)
+{
+ struct stat st;
+
+ if (!should_run())
+ clar__skip();
+
+ do_symlink("stat_nonexistent", "stat_dangling", 0);
+
+ cl_must_fail(p_stat("stat_nonexistent", &st));
+ cl_must_fail(p_stat("stat_dangling", &st));
+}
+
+void test_link__stat_dangling_symlink_directory(void)
+{
+ struct stat st;
+
+ if (!should_run())
+ clar__skip();
+
+ do_symlink("stat_nonexistent", "stat_dangling_dir", 1);
+
+ cl_must_fail(p_stat("stat_nonexistent_dir", &st));
+ cl_must_fail(p_stat("stat_dangling", &st));
+}
+
+void test_link__lstat_symlink(void)
+{
+ git_str target_path = GIT_STR_INIT;
+ struct stat st;
+
+ if (!should_run())
+ clar__skip();
+
+ /* Windows always writes the canonical path as the link target, so
+ * write the full path on all platforms.
+ */
+ git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_target");
+
+ cl_git_rewritefile("lstat_target", "This is the target of a symbolic link.\n");
+ do_symlink(git_str_cstr(&target_path), "lstat_symlink", 0);
+
+ cl_must_pass(p_lstat("lstat_target", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(39, st.st_size);
+
+ cl_must_pass(p_lstat("lstat_symlink", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(git_str_len(&target_path), st.st_size);
+
+ git_str_dispose(&target_path);
+}
+
+void test_link__lstat_symlink_directory(void)
+{
+ git_str target_path = GIT_STR_INIT;
+ struct stat st;
+
+ if (!should_run())
+ clar__skip();
+
+ git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_dirtarget");
+
+ p_mkdir("lstat_dirtarget", 0777);
+ do_symlink(git_str_cstr(&target_path), "lstat_dirlink", 1);
+
+ cl_must_pass(p_lstat("lstat_dirtarget", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ cl_must_pass(p_lstat("lstat_dirlink", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(git_str_len(&target_path), st.st_size);
+
+ git_str_dispose(&target_path);
+}
+
+void test_link__lstat_dangling_symlink(void)
+{
+ struct stat st;
+
+ if (!should_run())
+ clar__skip();
+
+ do_symlink("lstat_nonexistent", "lstat_dangling", 0);
+
+ cl_must_fail(p_lstat("lstat_nonexistent", &st));
+
+ cl_must_pass(p_lstat("lstat_dangling", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size);
+}
+
+void test_link__lstat_dangling_symlink_directory(void)
+{
+ struct stat st;
+
+ if (!should_run())
+ clar__skip();
+
+ do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1);
+
+ cl_must_fail(p_lstat("lstat_nonexistent", &st));
+
+ cl_must_pass(p_lstat("lstat_dangling_dir", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size);
+}
+
+void test_link__stat_junction(void)
+{
+#ifdef GIT_WIN32
+ git_str target_path = GIT_STR_INIT;
+ struct stat st;
+
+ git_str_join(&target_path, '/', clar_sandbox_path(), "stat_junctarget");
+
+ p_mkdir("stat_junctarget", 0777);
+ do_junction(git_str_cstr(&target_path), "stat_junction");
+
+ cl_must_pass(p_stat("stat_junctarget", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ cl_must_pass(p_stat("stat_junction", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ git_str_dispose(&target_path);
+#endif
+}
+
+void test_link__stat_dangling_junction(void)
+{
+#ifdef GIT_WIN32
+ git_str target_path = GIT_STR_INIT;
+ struct stat st;
+
+ git_str_join(&target_path, '/', clar_sandbox_path(), "stat_nonexistent_junctarget");
+
+ p_mkdir("stat_nonexistent_junctarget", 0777);
+ do_junction(git_str_cstr(&target_path), "stat_dangling_junction");
+
+ RemoveDirectory("stat_nonexistent_junctarget");
+
+ cl_must_fail(p_stat("stat_nonexistent_junctarget", &st));
+ cl_must_fail(p_stat("stat_dangling_junction", &st));
+
+ git_str_dispose(&target_path);
+#endif
+}
+
+void test_link__lstat_junction(void)
+{
+#ifdef GIT_WIN32
+ git_str target_path = GIT_STR_INIT;
+ struct stat st;
+
+ git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_junctarget");
+
+ p_mkdir("lstat_junctarget", 0777);
+ do_junction(git_str_cstr(&target_path), "lstat_junction");
+
+ cl_must_pass(p_lstat("lstat_junctarget", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ cl_must_pass(p_lstat("lstat_junction", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+
+ git_str_dispose(&target_path);
+#endif
+}
+
+void test_link__lstat_dangling_junction(void)
+{
+#ifdef GIT_WIN32
+ git_str target_path = GIT_STR_INIT;
+ struct stat st;
+
+ git_str_join(&target_path, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget");
+
+ p_mkdir("lstat_nonexistent_junctarget", 0777);
+ do_junction(git_str_cstr(&target_path), "lstat_dangling_junction");
+
+ RemoveDirectory("lstat_nonexistent_junctarget");
+
+ cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st));
+
+ cl_must_pass(p_lstat("lstat_dangling_junction", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(git_str_len(&target_path), st.st_size);
+
+ git_str_dispose(&target_path);
+#endif
+}
+
+void test_link__stat_hardlink(void)
+{
+ struct stat st;
+
+ if (!should_run())
+ clar__skip();
+
+ cl_git_rewritefile("stat_hardlink1", "This file has many names!\n");
+ do_hardlink("stat_hardlink1", "stat_hardlink2");
+
+ cl_must_pass(p_stat("stat_hardlink1", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(26, st.st_size);
+
+ cl_must_pass(p_stat("stat_hardlink2", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(26, st.st_size);
+}
+
+void test_link__lstat_hardlink(void)
+{
+ struct stat st;
+
+ if (!should_run())
+ clar__skip();
+
+ cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n");
+ do_hardlink("lstat_hardlink1", "lstat_hardlink2");
+
+ cl_must_pass(p_lstat("lstat_hardlink1", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(26, st.st_size);
+
+ cl_must_pass(p_lstat("lstat_hardlink2", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(26, st.st_size);
+}
+
+void test_link__stat_reparse_point(void)
+{
+#ifdef GIT_WIN32
+ struct stat st;
+
+ /* Generic reparse points should be treated as regular files, only
+ * symlinks and junctions should be treated as links.
+ */
+
+ cl_git_rewritefile("stat_reparse", "This is a reparse point!\n");
+ do_custom_reparse("stat_reparse");
+
+ cl_must_pass(p_lstat("stat_reparse", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(25, st.st_size);
+#endif
+}
+
+void test_link__lstat_reparse_point(void)
+{
+#ifdef GIT_WIN32
+ struct stat st;
+
+ cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n");
+ do_custom_reparse("lstat_reparse");
+
+ cl_must_pass(p_lstat("lstat_reparse", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(25, st.st_size);
+#endif
+}
+
+void test_link__readlink_nonexistent_file(void)
+{
+ char buf[2048];
+
+ cl_must_fail(p_readlink("readlink_nonexistent", buf, 2048));
+ cl_assert_equal_i(ENOENT, errno);
+}
+
+void test_link__readlink_normal_file(void)
+{
+ char buf[2048];
+
+ cl_git_rewritefile("readlink_regfile", "This is a regular file!\n");
+ cl_must_fail(p_readlink("readlink_regfile", buf, 2048));
+ cl_assert_equal_i(EINVAL, errno);
+}
+
+void test_link__readlink_symlink(void)
+{
+ git_str target_path = GIT_STR_INIT;
+ int len;
+ char buf[2048];
+
+ if (!should_run())
+ clar__skip();
+
+ git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_target");
+
+ cl_git_rewritefile("readlink_target", "This is the target of a symlink\n");
+ do_symlink(git_str_cstr(&target_path), "readlink_link", 0);
+
+ len = p_readlink("readlink_link", buf, 2048);
+ cl_must_pass(len);
+
+ buf[len] = 0;
+
+ cl_assert_equal_s(git_str_cstr(&target_path), buf);
+
+ git_str_dispose(&target_path);
+}
+
+void test_link__readlink_dangling(void)
+{
+ git_str target_path = GIT_STR_INIT;
+ int len;
+ char buf[2048];
+
+ if (!should_run())
+ clar__skip();
+
+ git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_nonexistent");
+
+ do_symlink(git_str_cstr(&target_path), "readlink_dangling", 0);
+
+ len = p_readlink("readlink_dangling", buf, 2048);
+ cl_must_pass(len);
+
+ buf[len] = 0;
+
+ cl_assert_equal_s(git_str_cstr(&target_path), buf);
+
+ git_str_dispose(&target_path);
+}
+
+void test_link__readlink_multiple(void)
+{
+ git_str target_path = GIT_STR_INIT,
+ path3 = GIT_STR_INIT, path2 = GIT_STR_INIT, path1 = GIT_STR_INIT;
+ int len;
+ char buf[2048];
+
+ if (!should_run())
+ clar__skip();
+
+ git_str_join(&target_path, '/', clar_sandbox_path(), "readlink_final");
+ git_str_join(&path3, '/', clar_sandbox_path(), "readlink_3");
+ git_str_join(&path2, '/', clar_sandbox_path(), "readlink_2");
+ git_str_join(&path1, '/', clar_sandbox_path(), "readlink_1");
+
+ do_symlink(git_str_cstr(&target_path), git_str_cstr(&path3), 0);
+ do_symlink(git_str_cstr(&path3), git_str_cstr(&path2), 0);
+ do_symlink(git_str_cstr(&path2), git_str_cstr(&path1), 0);
+
+ len = p_readlink("readlink_1", buf, 2048);
+ cl_must_pass(len);
+
+ buf[len] = 0;
+
+ cl_assert_equal_s(git_str_cstr(&path2), buf);
+
+ git_str_dispose(&path1);
+ git_str_dispose(&path2);
+ git_str_dispose(&path3);
+ git_str_dispose(&target_path);
+}
diff --git a/tests/util/memmem.c b/tests/util/memmem.c
new file mode 100644
index 0000000..1c713e8
--- /dev/null
+++ b/tests/util/memmem.c
@@ -0,0 +1,46 @@
+#include "clar_libgit2.h"
+
+static void assert_found(const char *haystack, const char *needle, size_t expected_pos)
+{
+ cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0,
+ needle, needle ? strlen(needle) : 0),
+ haystack + expected_pos);
+}
+
+static void assert_absent(const char *haystack, const char *needle)
+{
+ cl_assert_equal_p(git__memmem(haystack, haystack ? strlen(haystack) : 0,
+ needle, needle ? strlen(needle) : 0),
+ NULL);
+}
+
+void test_memmem__found(void)
+{
+ assert_found("a", "a", 0);
+ assert_found("ab", "a", 0);
+ assert_found("ba", "a", 1);
+ assert_found("aa", "a", 0);
+ assert_found("aab", "aa", 0);
+ assert_found("baa", "aa", 1);
+ assert_found("dabc", "abc", 1);
+ assert_found("abababc", "abc", 4);
+}
+
+void test_memmem__absent(void)
+{
+ assert_absent("a", "b");
+ assert_absent("a", "aa");
+ assert_absent("ba", "ab");
+ assert_absent("ba", "ab");
+ assert_absent("abc", "abcd");
+ assert_absent("abcabcabc", "bcac");
+}
+
+void test_memmem__edgecases(void)
+{
+ assert_absent(NULL, NULL);
+ assert_absent("a", NULL);
+ assert_absent(NULL, "a");
+ assert_absent("", "a");
+ assert_absent("a", "");
+}
diff --git a/tests/util/mkdir.c b/tests/util/mkdir.c
new file mode 100644
index 0000000..8658eec
--- /dev/null
+++ b/tests/util/mkdir.c
@@ -0,0 +1,291 @@
+#include "clar_libgit2.h"
+#include "futils.h"
+#include "posix.h"
+
+static void cleanup_basic_dirs(void *ref)
+{
+ GIT_UNUSED(ref);
+ git_futils_rmdir_r("d0", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+ git_futils_rmdir_r("d1", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+ git_futils_rmdir_r("d2", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+ git_futils_rmdir_r("d3", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+ git_futils_rmdir_r("d4", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+}
+
+void test_mkdir__absolute(void)
+{
+ git_str path = GIT_STR_INIT;
+
+ cl_set_cleanup(cleanup_basic_dirs, NULL);
+
+ git_str_joinpath(&path, clar_sandbox_path(), "d0");
+
+ /* make a directory */
+ cl_assert(!git_fs_path_isdir(path.ptr));
+ cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0));
+ cl_assert(git_fs_path_isdir(path.ptr));
+
+ git_str_joinpath(&path, path.ptr, "subdir");
+ cl_assert(!git_fs_path_isdir(path.ptr));
+ cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0));
+ cl_assert(git_fs_path_isdir(path.ptr));
+
+ /* ensure mkdir_r works for a single subdir */
+ git_str_joinpath(&path, path.ptr, "another");
+ cl_assert(!git_fs_path_isdir(path.ptr));
+ cl_git_pass(git_futils_mkdir_r(path.ptr, 0755));
+ cl_assert(git_fs_path_isdir(path.ptr));
+
+ /* ensure mkdir_r works */
+ git_str_joinpath(&path, clar_sandbox_path(), "d1/foo/bar/asdf");
+ cl_assert(!git_fs_path_isdir(path.ptr));
+ cl_git_pass(git_futils_mkdir_r(path.ptr, 0755));
+ cl_assert(git_fs_path_isdir(path.ptr));
+
+ /* ensure we don't imply recursive */
+ git_str_joinpath(&path, clar_sandbox_path(), "d2/foo/bar/asdf");
+ cl_assert(!git_fs_path_isdir(path.ptr));
+ cl_git_fail(git_futils_mkdir(path.ptr, 0755, 0));
+ cl_assert(!git_fs_path_isdir(path.ptr));
+
+ git_str_dispose(&path);
+}
+
+void test_mkdir__basic(void)
+{
+ cl_set_cleanup(cleanup_basic_dirs, NULL);
+
+ /* make a directory */
+ cl_assert(!git_fs_path_isdir("d0"));
+ cl_git_pass(git_futils_mkdir("d0", 0755, 0));
+ cl_assert(git_fs_path_isdir("d0"));
+
+ /* make a path */
+ cl_assert(!git_fs_path_isdir("d1"));
+ cl_git_pass(git_futils_mkdir("d1/d1.1/d1.2", 0755, GIT_MKDIR_PATH));
+ cl_assert(git_fs_path_isdir("d1"));
+ cl_assert(git_fs_path_isdir("d1/d1.1"));
+ cl_assert(git_fs_path_isdir("d1/d1.1/d1.2"));
+
+ /* make a dir exclusively */
+ cl_assert(!git_fs_path_isdir("d2"));
+ cl_git_pass(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL));
+ cl_assert(git_fs_path_isdir("d2"));
+
+ /* make exclusive failure */
+ cl_git_fail(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL));
+
+ /* make a path exclusively */
+ cl_assert(!git_fs_path_isdir("d3"));
+ cl_git_pass(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
+ cl_assert(git_fs_path_isdir("d3"));
+ cl_assert(git_fs_path_isdir("d3/d3.1/d3.2"));
+
+ /* make exclusive path failure */
+ cl_git_fail(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
+ /* ??? Should EXCL only apply to the last item in the path? */
+
+ /* path with trailing slash? */
+ cl_assert(!git_fs_path_isdir("d4"));
+ cl_git_pass(git_futils_mkdir("d4/d4.1/", 0755, GIT_MKDIR_PATH));
+ cl_assert(git_fs_path_isdir("d4/d4.1"));
+}
+
+static void cleanup_basedir(void *ref)
+{
+ GIT_UNUSED(ref);
+ git_futils_rmdir_r("base", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+}
+
+void test_mkdir__with_base(void)
+{
+#define BASEDIR "base/dir/here"
+
+ cl_set_cleanup(cleanup_basedir, NULL);
+
+ cl_git_pass(git_futils_mkdir(BASEDIR, 0755, GIT_MKDIR_PATH));
+
+ cl_git_pass(git_futils_mkdir_relative("a", BASEDIR, 0755, 0, NULL));
+ cl_assert(git_fs_path_isdir(BASEDIR "/a"));
+
+ cl_git_pass(git_futils_mkdir_relative("b/b1/b2", BASEDIR, 0755, GIT_MKDIR_PATH, NULL));
+ cl_assert(git_fs_path_isdir(BASEDIR "/b/b1/b2"));
+
+ /* exclusive with existing base */
+ cl_git_pass(git_futils_mkdir_relative("c/c1/c2", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL));
+
+ /* fail: exclusive with duplicated suffix */
+ cl_git_fail(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL));
+
+ /* fail: exclusive with any duplicated component */
+ cl_git_fail(git_futils_mkdir_relative("c/cz/cz", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL));
+
+ /* success: exclusive without path */
+ cl_git_pass(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_EXCL, NULL));
+
+ /* path with shorter base and existing dirs */
+ cl_git_pass(git_futils_mkdir_relative("dir/here/d/", "base", 0755, GIT_MKDIR_PATH, NULL));
+ cl_assert(git_fs_path_isdir("base/dir/here/d"));
+
+ /* fail: path with shorter base and existing dirs */
+ cl_git_fail(git_futils_mkdir_relative("dir/here/e/", "base", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL));
+
+ /* fail: base with missing components */
+ cl_git_fail(git_futils_mkdir_relative("f/", "base/missing", 0755, GIT_MKDIR_PATH, NULL));
+
+ /* success: shift missing component to path */
+ cl_git_pass(git_futils_mkdir_relative("missing/f/", "base/", 0755, GIT_MKDIR_PATH, NULL));
+}
+
+static void cleanup_chmod_root(void *ref)
+{
+ mode_t *mode = ref;
+
+ if (mode != NULL) {
+ (void)p_umask(*mode);
+ git__free(mode);
+ }
+
+ git_futils_rmdir_r("r", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+}
+
+#define check_mode(X,A) check_mode_at_line((X), (A), __FILE__, __func__, __LINE__)
+
+static void check_mode_at_line(
+ mode_t expected, mode_t actual,
+ const char *file, const char *func, int line)
+{
+ /* FAT filesystems don't support exec bit, nor group/world bits */
+ if (!cl_is_chmod_supported()) {
+ expected &= 0600;
+ actual &= 0600;
+ }
+
+ clar__assert_equal(
+ file, func, line, "expected_mode != actual_mode", 1,
+ "%07o", (int)expected, (int)(actual & 0777));
+}
+
+void test_mkdir__chmods(void)
+{
+ struct stat st;
+ mode_t *old = git__malloc(sizeof(mode_t));
+ *old = p_umask(022);
+
+ cl_set_cleanup(cleanup_chmod_root, old);
+
+ cl_git_pass(git_futils_mkdir("r", 0777, 0));
+
+ cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL));
+
+ cl_git_pass(git_fs_path_lstat("r/mode", &st));
+ check_mode(0755, st.st_mode);
+ cl_git_pass(git_fs_path_lstat("r/mode/is", &st));
+ check_mode(0755, st.st_mode);
+ cl_git_pass(git_fs_path_lstat("r/mode/is/important", &st));
+ check_mode(0755, st.st_mode);
+
+ cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL));
+
+ cl_git_pass(git_fs_path_lstat("r/mode2", &st));
+ check_mode(0755, st.st_mode);
+ cl_git_pass(git_fs_path_lstat("r/mode2/is2", &st));
+ check_mode(0755, st.st_mode);
+ cl_git_pass(git_fs_path_lstat("r/mode2/is2/important2", &st));
+ check_mode(0777, st.st_mode);
+
+ cl_git_pass(git_futils_mkdir_relative("mode3/is3/important3", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL));
+
+ cl_git_pass(git_fs_path_lstat("r/mode3", &st));
+ check_mode(0777, st.st_mode);
+ cl_git_pass(git_fs_path_lstat("r/mode3/is3", &st));
+ check_mode(0777, st.st_mode);
+ cl_git_pass(git_fs_path_lstat("r/mode3/is3/important3", &st));
+ check_mode(0777, st.st_mode);
+
+ /* test that we chmod existing dir */
+
+ cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL));
+
+ cl_git_pass(git_fs_path_lstat("r/mode", &st));
+ check_mode(0755, st.st_mode);
+ cl_git_pass(git_fs_path_lstat("r/mode/is", &st));
+ check_mode(0755, st.st_mode);
+ cl_git_pass(git_fs_path_lstat("r/mode/is/important", &st));
+ check_mode(0777, st.st_mode);
+
+ /* test that we chmod even existing dirs if CHMOD_PATH is set */
+
+ cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2.1", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL));
+
+ cl_git_pass(git_fs_path_lstat("r/mode2", &st));
+ check_mode(0777, st.st_mode);
+ cl_git_pass(git_fs_path_lstat("r/mode2/is2", &st));
+ check_mode(0777, st.st_mode);
+ cl_git_pass(git_fs_path_lstat("r/mode2/is2/important2.1", &st));
+ check_mode(0777, st.st_mode);
+}
+
+void test_mkdir__keeps_parent_symlinks(void)
+{
+#ifndef GIT_WIN32
+ git_str path = GIT_STR_INIT;
+
+ cl_set_cleanup(cleanup_basic_dirs, NULL);
+
+ /* make a directory */
+ cl_assert(!git_fs_path_isdir("d0"));
+ cl_git_pass(git_futils_mkdir("d0", 0755, 0));
+ cl_assert(git_fs_path_isdir("d0"));
+
+ cl_must_pass(symlink("d0", "d1"));
+ cl_assert(git_fs_path_islink("d1"));
+
+ cl_git_pass(git_futils_mkdir("d1/foo/bar", 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS));
+ cl_assert(git_fs_path_islink("d1"));
+ cl_assert(git_fs_path_isdir("d1/foo/bar"));
+ cl_assert(git_fs_path_isdir("d0/foo/bar"));
+
+ cl_must_pass(symlink("d0", "d2"));
+ cl_assert(git_fs_path_islink("d2"));
+
+ git_str_joinpath(&path, clar_sandbox_path(), "d2/other/dir");
+
+ cl_git_pass(git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS));
+ cl_assert(git_fs_path_islink("d2"));
+ cl_assert(git_fs_path_isdir("d2/other/dir"));
+ cl_assert(git_fs_path_isdir("d0/other/dir"));
+
+ git_str_dispose(&path);
+#endif
+}
+
+void test_mkdir__mkdir_path_inside_unwriteable_parent(void)
+{
+ struct stat st;
+ mode_t *old;
+
+ /* FAT filesystems don't support exec bit, nor group/world bits */
+ if (!cl_is_chmod_supported())
+ return;
+
+ cl_assert((old = git__malloc(sizeof(mode_t))) != NULL);
+ *old = p_umask(022);
+ cl_set_cleanup(cleanup_chmod_root, old);
+
+ cl_git_pass(git_futils_mkdir("r", 0777, 0));
+ cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL));
+ cl_git_pass(git_fs_path_lstat("r/mode", &st));
+ check_mode(0755, st.st_mode);
+
+ cl_must_pass(p_chmod("r/mode", 0111));
+ cl_git_pass(git_fs_path_lstat("r/mode", &st));
+ check_mode(0111, st.st_mode);
+
+ cl_git_pass(
+ git_futils_mkdir_relative("mode/is/okay/inside", "r", 0777, GIT_MKDIR_PATH, NULL));
+ cl_git_pass(git_fs_path_lstat("r/mode/is/okay/inside", &st));
+ check_mode(0755, st.st_mode);
+
+ cl_must_pass(p_chmod("r/mode", 0777));
+}
diff --git a/tests/util/path.c b/tests/util/path.c
new file mode 100644
index 0000000..02ec42f
--- /dev/null
+++ b/tests/util/path.c
@@ -0,0 +1,768 @@
+#include "clar_libgit2.h"
+#include "futils.h"
+#include "fs_path.h"
+
+#ifndef GIT_WIN32
+# include <unistd.h>
+#endif
+
+static char *path_save;
+
+void test_path__initialize(void)
+{
+ path_save = cl_getenv("PATH");
+}
+
+void test_path__cleanup(void)
+{
+ cl_setenv("PATH", path_save);
+ git__free(path_save);
+ path_save = NULL;
+}
+
+static void
+check_dirname(const char *A, const char *B)
+{
+ git_str dir = GIT_STR_INIT;
+ char *dir2;
+
+ cl_assert(git_fs_path_dirname_r(&dir, A) >= 0);
+ cl_assert_equal_s(B, dir.ptr);
+ git_str_dispose(&dir);
+
+ cl_assert((dir2 = git_fs_path_dirname(A)) != NULL);
+ cl_assert_equal_s(B, dir2);
+ git__free(dir2);
+}
+
+static void
+check_basename(const char *A, const char *B)
+{
+ git_str base = GIT_STR_INIT;
+ char *base2;
+
+ cl_assert(git_fs_path_basename_r(&base, A) >= 0);
+ cl_assert_equal_s(B, base.ptr);
+ git_str_dispose(&base);
+
+ cl_assert((base2 = git_fs_path_basename(A)) != NULL);
+ cl_assert_equal_s(B, base2);
+ git__free(base2);
+}
+
+static void
+check_joinpath(const char *path_a, const char *path_b, const char *expected_path)
+{
+ git_str joined_path = GIT_STR_INIT;
+
+ cl_git_pass(git_str_joinpath(&joined_path, path_a, path_b));
+ cl_assert_equal_s(expected_path, joined_path.ptr);
+
+ git_str_dispose(&joined_path);
+}
+
+static void
+check_joinpath_n(
+ const char *path_a,
+ const char *path_b,
+ const char *path_c,
+ const char *path_d,
+ const char *expected_path)
+{
+ git_str joined_path = GIT_STR_INIT;
+
+ cl_git_pass(git_str_join_n(&joined_path, '/', 4,
+ path_a, path_b, path_c, path_d));
+ cl_assert_equal_s(expected_path, joined_path.ptr);
+
+ git_str_dispose(&joined_path);
+}
+
+static void check_setenv(const char* name, const char* value)
+{
+ char* check;
+
+ cl_git_pass(cl_setenv(name, value));
+ check = cl_getenv(name);
+
+ if (value)
+ cl_assert_equal_s(value, check);
+ else
+ cl_assert(check == NULL);
+
+ git__free(check);
+}
+
+/* get the dirname of a path */
+void test_path__00_dirname(void)
+{
+ check_dirname(NULL, ".");
+ check_dirname("", ".");
+ check_dirname("a", ".");
+ check_dirname("/", "/");
+ check_dirname("/usr", "/");
+ check_dirname("/usr/", "/");
+ check_dirname("/usr/lib", "/usr");
+ check_dirname("/usr/lib/", "/usr");
+ check_dirname("/usr/lib//", "/usr");
+ check_dirname("usr/lib", "usr");
+ check_dirname("usr/lib/", "usr");
+ check_dirname("usr/lib//", "usr");
+ check_dirname(".git/", ".");
+
+ check_dirname(REP16("/abc"), REP15("/abc"));
+
+#ifdef GIT_WIN32
+ check_dirname("C:/", "C:/");
+ check_dirname("C:", "C:/");
+ check_dirname("C:/path/", "C:/");
+ check_dirname("C:/path", "C:/");
+ check_dirname("//computername/", "//computername/");
+ check_dirname("//computername", "//computername/");
+ check_dirname("//computername/path/", "//computername/");
+ check_dirname("//computername/path", "//computername/");
+ check_dirname("//computername/sub/path/", "//computername/sub");
+ check_dirname("//computername/sub/path", "//computername/sub");
+#endif
+}
+
+/* get the base name of a path */
+void test_path__01_basename(void)
+{
+ check_basename(NULL, ".");
+ check_basename("", ".");
+ check_basename("a", "a");
+ check_basename("/", "/");
+ check_basename("/usr", "usr");
+ check_basename("/usr/", "usr");
+ check_basename("/usr/lib", "lib");
+ check_basename("/usr/lib//", "lib");
+ check_basename("usr/lib", "lib");
+
+ check_basename(REP16("/abc"), "abc");
+ check_basename(REP1024("/abc"), "abc");
+}
+
+/* properly join path components */
+void test_path__05_joins(void)
+{
+ check_joinpath("", "", "");
+ check_joinpath("", "a", "a");
+ check_joinpath("", "/a", "/a");
+ check_joinpath("a", "", "a/");
+ check_joinpath("a", "/", "a/");
+ check_joinpath("a", "b", "a/b");
+ check_joinpath("/", "a", "/a");
+ check_joinpath("/", "", "/");
+ check_joinpath("/a", "/b", "/a/b");
+ check_joinpath("/a", "/b/", "/a/b/");
+ check_joinpath("/a/", "b/", "/a/b/");
+ check_joinpath("/a/", "/b/", "/a/b/");
+
+ check_joinpath("/abcd", "/defg", "/abcd/defg");
+ check_joinpath("/abcd", "/defg/", "/abcd/defg/");
+ check_joinpath("/abcd/", "defg/", "/abcd/defg/");
+ check_joinpath("/abcd/", "/defg/", "/abcd/defg/");
+
+ check_joinpath("/abcdefgh", "/12345678", "/abcdefgh/12345678");
+ check_joinpath("/abcdefgh", "/12345678/", "/abcdefgh/12345678/");
+ check_joinpath("/abcdefgh/", "12345678/", "/abcdefgh/12345678/");
+
+ check_joinpath(REP1024("aaaa"), "", REP1024("aaaa") "/");
+ check_joinpath(REP1024("aaaa/"), "", REP1024("aaaa/"));
+ check_joinpath(REP1024("/aaaa"), "", REP1024("/aaaa") "/");
+
+ check_joinpath(REP1024("aaaa"), REP1024("bbbb"),
+ REP1024("aaaa") "/" REP1024("bbbb"));
+ check_joinpath(REP1024("/aaaa"), REP1024("/bbbb"),
+ REP1024("/aaaa") REP1024("/bbbb"));
+}
+
+/* properly join path components for more than one path */
+void test_path__06_long_joins(void)
+{
+ check_joinpath_n("", "", "", "", "");
+ check_joinpath_n("", "a", "", "", "a/");
+ check_joinpath_n("a", "", "", "", "a/");
+ check_joinpath_n("", "", "", "a", "a");
+ check_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/");
+ check_joinpath_n("a", "b", "", "/c/d", "a/b/c/d");
+ check_joinpath_n("abcd", "efgh", "ijkl", "mnop", "abcd/efgh/ijkl/mnop");
+ check_joinpath_n("abcd/", "efgh/", "ijkl/", "mnop/", "abcd/efgh/ijkl/mnop/");
+ check_joinpath_n("/abcd/", "/efgh/", "/ijkl/", "/mnop/", "/abcd/efgh/ijkl/mnop/");
+
+ check_joinpath_n(REP1024("a"), REP1024("b"), REP1024("c"), REP1024("d"),
+ REP1024("a") "/" REP1024("b") "/"
+ REP1024("c") "/" REP1024("d"));
+ check_joinpath_n(REP1024("/a"), REP1024("/b"), REP1024("/c"), REP1024("/d"),
+ REP1024("/a") REP1024("/b")
+ REP1024("/c") REP1024("/d"));
+}
+
+
+static void
+check_path_to_dir(
+ const char* path,
+ const char* expected)
+{
+ git_str tgt = GIT_STR_INIT;
+
+ git_str_sets(&tgt, path);
+ cl_git_pass(git_fs_path_to_dir(&tgt));
+ cl_assert_equal_s(expected, tgt.ptr);
+
+ git_str_dispose(&tgt);
+}
+
+static void
+check_string_to_dir(
+ const char* path,
+ size_t maxlen,
+ const char* expected)
+{
+ size_t len = strlen(path);
+ char *buf = git__malloc(len + 2);
+ cl_assert(buf);
+
+ strncpy(buf, path, len + 2);
+
+ git_fs_path_string_to_dir(buf, maxlen);
+
+ cl_assert_equal_s(expected, buf);
+
+ git__free(buf);
+}
+
+/* convert paths to dirs */
+void test_path__07_path_to_dir(void)
+{
+ check_path_to_dir("", "");
+ check_path_to_dir(".", "./");
+ check_path_to_dir("./", "./");
+ check_path_to_dir("a/", "a/");
+ check_path_to_dir("ab", "ab/");
+ /* make sure we try just under and just over an expansion that will
+ * require a realloc
+ */
+ check_path_to_dir("abcdef", "abcdef/");
+ check_path_to_dir("abcdefg", "abcdefg/");
+ check_path_to_dir("abcdefgh", "abcdefgh/");
+ check_path_to_dir("abcdefghi", "abcdefghi/");
+ check_path_to_dir(REP1024("abcd") "/", REP1024("abcd") "/");
+ check_path_to_dir(REP1024("abcd"), REP1024("abcd") "/");
+
+ check_string_to_dir("", 1, "");
+ check_string_to_dir(".", 1, ".");
+ check_string_to_dir(".", 2, "./");
+ check_string_to_dir(".", 3, "./");
+ check_string_to_dir("abcd", 3, "abcd");
+ check_string_to_dir("abcd", 4, "abcd");
+ check_string_to_dir("abcd", 5, "abcd/");
+ check_string_to_dir("abcd", 6, "abcd/");
+}
+
+/* join path to itself */
+void test_path__08_self_join(void)
+{
+ git_str path = GIT_STR_INIT;
+ size_t asize = 0;
+
+ asize = path.asize;
+ cl_git_pass(git_str_sets(&path, "/foo"));
+ cl_assert_equal_s(path.ptr, "/foo");
+ cl_assert(asize < path.asize);
+
+ asize = path.asize;
+ cl_git_pass(git_str_joinpath(&path, path.ptr, "this is a new string"));
+ cl_assert_equal_s(path.ptr, "/foo/this is a new string");
+ cl_assert(asize < path.asize);
+
+ asize = path.asize;
+ cl_git_pass(git_str_joinpath(&path, path.ptr, "/grow the buffer, grow the buffer, grow the buffer"));
+ cl_assert_equal_s(path.ptr, "/foo/this is a new string/grow the buffer, grow the buffer, grow the buffer");
+ cl_assert(asize < path.asize);
+
+ git_str_dispose(&path);
+ cl_git_pass(git_str_sets(&path, "/foo/bar"));
+
+ cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "baz"));
+ cl_assert_equal_s(path.ptr, "/bar/baz");
+
+ asize = path.asize;
+ cl_git_pass(git_str_joinpath(&path, path.ptr + 4, "somethinglongenoughtorealloc"));
+ cl_assert_equal_s(path.ptr, "/baz/somethinglongenoughtorealloc");
+ cl_assert(asize < path.asize);
+
+ git_str_dispose(&path);
+}
+
+static void check_percent_decoding(const char *expected_result, const char *input)
+{
+ git_str buf = GIT_STR_INIT;
+
+ cl_git_pass(git__percent_decode(&buf, input));
+ cl_assert_equal_s(expected_result, git_str_cstr(&buf));
+
+ git_str_dispose(&buf);
+}
+
+void test_path__09_percent_decode(void)
+{
+ check_percent_decoding("abcd", "abcd");
+ check_percent_decoding("a2%", "a2%");
+ check_percent_decoding("a2%3", "a2%3");
+ check_percent_decoding("a2%%3", "a2%%3");
+ check_percent_decoding("a2%3z", "a2%3z");
+ check_percent_decoding("a,", "a%2c");
+ check_percent_decoding("a21", "a2%31");
+ check_percent_decoding("a2%1", "a2%%31");
+ check_percent_decoding("a bc ", "a%20bc%20");
+ check_percent_decoding("Vicent Mart" "\355", "Vicent%20Mart%ED");
+}
+
+static void check_fromurl(const char *expected_result, const char *input, int should_fail)
+{
+ git_str buf = GIT_STR_INIT;
+
+ assert(should_fail || expected_result);
+
+ if (!should_fail) {
+ cl_git_pass(git_fs_path_fromurl(&buf, input));
+ cl_assert_equal_s(expected_result, git_str_cstr(&buf));
+ } else
+ cl_git_fail(git_fs_path_fromurl(&buf, input));
+
+ git_str_dispose(&buf);
+}
+
+#ifdef GIT_WIN32
+#define ABS_PATH_MARKER ""
+#else
+#define ABS_PATH_MARKER "/"
+#endif
+
+void test_path__10_fromurl(void)
+{
+ /* Failing cases */
+ check_fromurl(NULL, "a", 1);
+ check_fromurl(NULL, "http:///c:/Temp%20folder/note.txt", 1);
+ check_fromurl(NULL, "file://c:/Temp%20folder/note.txt", 1);
+ check_fromurl(NULL, "file:////c:/Temp%20folder/note.txt", 1);
+ check_fromurl(NULL, "file:///", 1);
+ check_fromurl(NULL, "file:////", 1);
+ check_fromurl(NULL, "file://servername/c:/Temp%20folder/note.txt", 1);
+
+ /* Passing cases */
+ check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file:///c:/Temp%20folder/note.txt", 0);
+ check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file://localhost/c:/Temp%20folder/note.txt", 0);
+ check_fromurl(ABS_PATH_MARKER "c:/Temp+folder/note.txt", "file:///c:/Temp+folder/note.txt", 0);
+ check_fromurl(ABS_PATH_MARKER "a", "file:///a", 0);
+}
+
+typedef struct {
+ int expect_idx;
+ int cancel_after;
+ char **expect;
+} check_walkup_info;
+
+#define CANCEL_VALUE 1234
+
+static int check_one_walkup_step(void *ref, const char *path)
+{
+ check_walkup_info *info = (check_walkup_info *)ref;
+
+ if (!info->cancel_after) {
+ cl_assert_equal_s(info->expect[info->expect_idx], "[CANCEL]");
+ return CANCEL_VALUE;
+ }
+ info->cancel_after--;
+
+ cl_assert(info->expect[info->expect_idx] != NULL);
+ cl_assert_equal_s(info->expect[info->expect_idx], path);
+ info->expect_idx++;
+
+ return 0;
+}
+
+void test_path__11_walkup(void)
+{
+ git_str p = GIT_STR_INIT;
+
+ char *expect[] = {
+ /* 1 */ "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL,
+ /* 2 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL,
+ /* 3 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL,
+ /* 4 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL,
+ /* 5 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL,
+ /* 6 */ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL,
+ /* 7 */ "this_is_a_path", "", NULL,
+ /* 8 */ "this_is_a_path/", "", NULL,
+ /* 9 */ "///a///b///c///d///e///", "///a///b///c///d///", "///a///b///c///", "///a///b///", "///a///", "///", NULL,
+ /* 10 */ "a/b/c/", "a/b/", "a/", "", NULL,
+ /* 11 */ "a/b/c", "a/b/", "a/", "", NULL,
+ /* 12 */ "a/b/c/", "a/b/", "a/", NULL,
+ /* 13 */ "", NULL,
+ /* 14 */ "/", NULL,
+ /* 15 */ NULL
+ };
+
+ char *root[] = {
+ /* 1 */ NULL,
+ /* 2 */ NULL,
+ /* 3 */ "/",
+ /* 4 */ "",
+ /* 5 */ "/a/b",
+ /* 6 */ "/a/b/",
+ /* 7 */ NULL,
+ /* 8 */ NULL,
+ /* 9 */ NULL,
+ /* 10 */ NULL,
+ /* 11 */ NULL,
+ /* 12 */ "a/",
+ /* 13 */ NULL,
+ /* 14 */ NULL,
+ };
+
+ int i, j;
+ check_walkup_info info;
+
+ info.expect = expect;
+ info.cancel_after = -1;
+
+ for (i = 0, j = 0; expect[i] != NULL; i++, j++) {
+
+ git_str_sets(&p, expect[i]);
+
+ info.expect_idx = i;
+ cl_git_pass(
+ git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info)
+ );
+
+ cl_assert_equal_s(p.ptr, expect[i]);
+ cl_assert(expect[info.expect_idx] == NULL);
+ i = info.expect_idx;
+ }
+
+ git_str_dispose(&p);
+}
+
+void test_path__11a_walkup_cancel(void)
+{
+ git_str p = GIT_STR_INIT;
+ int cancel[] = { 3, 2, 1, 0 };
+ char *expect[] = {
+ "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "[CANCEL]", NULL,
+ "/a/b/c/d/e", "/a/b/c/d/", "[CANCEL]", NULL,
+ "/a/b/c/d/e", "[CANCEL]", NULL,
+ "[CANCEL]", NULL,
+ NULL
+ };
+ char *root[] = { NULL, NULL, "/", "", NULL };
+ int i, j;
+ check_walkup_info info;
+
+ info.expect = expect;
+
+ for (i = 0, j = 0; expect[i] != NULL; i++, j++) {
+
+ git_str_sets(&p, expect[i]);
+
+ info.cancel_after = cancel[j];
+ info.expect_idx = i;
+
+ cl_assert_equal_i(
+ CANCEL_VALUE,
+ git_fs_path_walk_up(&p, root[j], check_one_walkup_step, &info)
+ );
+
+ /* skip to next run of expectations */
+ while (expect[i] != NULL) i++;
+ }
+
+ git_str_dispose(&p);
+}
+
+void test_path__12_offset_to_path_root(void)
+{
+ cl_assert(git_fs_path_root("non/rooted/path") == -1);
+ cl_assert(git_fs_path_root("/rooted/path") == 0);
+
+#ifdef GIT_WIN32
+ /* Windows specific tests */
+ cl_assert(git_fs_path_root("C:non/rooted/path") == -1);
+ cl_assert(git_fs_path_root("C:/rooted/path") == 2);
+ cl_assert(git_fs_path_root("//computername/sharefolder/resource") == 14);
+ cl_assert(git_fs_path_root("//computername/sharefolder") == 14);
+ cl_assert(git_fs_path_root("//computername") == -1);
+#endif
+}
+
+#define NON_EXISTING_FILEPATH "i_hope_i_do_not_exist"
+
+void test_path__13_cannot_prettify_a_non_existing_file(void)
+{
+ git_str p = GIT_STR_INIT;
+
+ cl_assert_equal_b(git_fs_path_exists(NON_EXISTING_FILEPATH), false);
+ cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH, NULL));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_fs_path_prettify(&p, NON_EXISTING_FILEPATH "/so-do-i", NULL));
+
+ git_str_dispose(&p);
+}
+
+void test_path__14_apply_relative(void)
+{
+ git_str p = GIT_STR_INIT;
+
+ cl_git_pass(git_str_sets(&p, "/this/is/a/base"));
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "../test"));
+ cl_assert_equal_s("/this/is/a/test", p.ptr);
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "../../the/./end"));
+ cl_assert_equal_s("/this/is/the/end", p.ptr);
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "./of/this/../the/string"));
+ cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr);
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "../../../../../.."));
+ cl_assert_equal_s("/this/", p.ptr);
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "../"));
+ cl_assert_equal_s("/", p.ptr);
+
+ cl_git_fail(git_fs_path_apply_relative(&p, "../../.."));
+
+
+ cl_git_pass(git_str_sets(&p, "d:/another/test"));
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "../.."));
+ cl_assert_equal_s("d:/", p.ptr);
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "from/here/to/../and/./back/."));
+ cl_assert_equal_s("d:/from/here/and/back/", p.ptr);
+
+
+ cl_git_pass(git_str_sets(&p, "https://my.url.com/test.git"));
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "../another.git"));
+ cl_assert_equal_s("https://my.url.com/another.git", p.ptr);
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "../full/path/url.patch"));
+ cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr);
+
+ cl_git_pass(git_fs_path_apply_relative(&p, ".."));
+ cl_assert_equal_s("https://my.url.com/full/path/", p.ptr);
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "../../../"));
+ cl_assert_equal_s("https://", p.ptr);
+
+
+ cl_git_pass(git_str_sets(&p, "../../this/is/relative"));
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "../../preserves/the/prefix"));
+ cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr);
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "../../../../that"));
+ cl_assert_equal_s("../../that", p.ptr);
+
+ cl_git_pass(git_fs_path_apply_relative(&p, "../there"));
+ cl_assert_equal_s("../../there", p.ptr);
+ git_str_dispose(&p);
+}
+
+static void assert_resolve_relative(
+ git_str *buf, const char *expected, const char *path)
+{
+ cl_git_pass(git_str_sets(buf, path));
+ cl_git_pass(git_fs_path_resolve_relative(buf, 0));
+ cl_assert_equal_s(expected, buf->ptr);
+}
+
+void test_path__15_resolve_relative(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ assert_resolve_relative(&buf, "", "");
+ assert_resolve_relative(&buf, "", ".");
+ assert_resolve_relative(&buf, "", "./");
+ assert_resolve_relative(&buf, "..", "..");
+ assert_resolve_relative(&buf, "../", "../");
+ assert_resolve_relative(&buf, "..", "./..");
+ assert_resolve_relative(&buf, "../", "./../");
+ assert_resolve_relative(&buf, "../", "../.");
+ assert_resolve_relative(&buf, "../", ".././");
+ assert_resolve_relative(&buf, "../..", "../..");
+ assert_resolve_relative(&buf, "../../", "../../");
+
+ assert_resolve_relative(&buf, "/", "/");
+ assert_resolve_relative(&buf, "/", "/.");
+
+ assert_resolve_relative(&buf, "", "a/..");
+ assert_resolve_relative(&buf, "", "a/../");
+ assert_resolve_relative(&buf, "", "a/../.");
+
+ assert_resolve_relative(&buf, "/a", "/a");
+ assert_resolve_relative(&buf, "/a/", "/a/.");
+ assert_resolve_relative(&buf, "/", "/a/../");
+ assert_resolve_relative(&buf, "/", "/a/../.");
+ assert_resolve_relative(&buf, "/", "/a/.././");
+
+ assert_resolve_relative(&buf, "a", "a");
+ assert_resolve_relative(&buf, "a/", "a/");
+ assert_resolve_relative(&buf, "a/", "a/.");
+ assert_resolve_relative(&buf, "a/", "a/./");
+
+ assert_resolve_relative(&buf, "a/b", "a//b");
+ assert_resolve_relative(&buf, "a/b/c", "a/b/c");
+ assert_resolve_relative(&buf, "b/c", "./b/c");
+ assert_resolve_relative(&buf, "a/c", "a/./c");
+ assert_resolve_relative(&buf, "a/b/", "a/b/.");
+
+ assert_resolve_relative(&buf, "/a/b/c", "///a/b/c");
+ assert_resolve_relative(&buf, "/", "////");
+ assert_resolve_relative(&buf, "/a", "///a");
+ assert_resolve_relative(&buf, "/", "///.");
+ assert_resolve_relative(&buf, "/", "///a/..");
+
+ assert_resolve_relative(&buf, "../../path", "../../test//../././path");
+ assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d");
+
+ cl_git_pass(git_str_sets(&buf, "/.."));
+ cl_git_fail(git_fs_path_resolve_relative(&buf, 0));
+
+ cl_git_pass(git_str_sets(&buf, "/./.."));
+ cl_git_fail(git_fs_path_resolve_relative(&buf, 0));
+
+ cl_git_pass(git_str_sets(&buf, "/.//.."));
+ cl_git_fail(git_fs_path_resolve_relative(&buf, 0));
+
+ cl_git_pass(git_str_sets(&buf, "/../."));
+ cl_git_fail(git_fs_path_resolve_relative(&buf, 0));
+
+ cl_git_pass(git_str_sets(&buf, "/../.././../a"));
+ cl_git_fail(git_fs_path_resolve_relative(&buf, 0));
+
+ cl_git_pass(git_str_sets(&buf, "////.."));
+ cl_git_fail(git_fs_path_resolve_relative(&buf, 0));
+
+ /* things that start with Windows network paths */
+#ifdef GIT_WIN32
+ assert_resolve_relative(&buf, "//a/b/c", "//a/b/c");
+ assert_resolve_relative(&buf, "//a/", "//a/b/..");
+ assert_resolve_relative(&buf, "//a/b/c", "//a/Q/../b/x/y/../../c");
+
+ cl_git_pass(git_str_sets(&buf, "//a/b/../.."));
+ cl_git_fail(git_fs_path_resolve_relative(&buf, 0));
+#else
+ assert_resolve_relative(&buf, "/a/b/c", "//a/b/c");
+ assert_resolve_relative(&buf, "/a/", "//a/b/..");
+ assert_resolve_relative(&buf, "/a/b/c", "//a/Q/../b/x/y/../../c");
+ assert_resolve_relative(&buf, "/", "//a/b/../..");
+#endif
+
+ git_str_dispose(&buf);
+}
+
+#define assert_common_dirlen(i, p, q) \
+ cl_assert_equal_i((i), git_fs_path_common_dirlen((p), (q)));
+
+void test_path__16_resolve_relative(void)
+{
+ assert_common_dirlen(0, "", "");
+ assert_common_dirlen(0, "", "bar.txt");
+ assert_common_dirlen(0, "foo.txt", "bar.txt");
+ assert_common_dirlen(0, "foo.txt", "");
+ assert_common_dirlen(0, "foo/bar.txt", "bar/foo.txt");
+ assert_common_dirlen(0, "foo/bar.txt", "../foo.txt");
+
+ assert_common_dirlen(1, "/one.txt", "/two.txt");
+ assert_common_dirlen(4, "foo/one.txt", "foo/two.txt");
+ assert_common_dirlen(5, "/foo/one.txt", "/foo/two.txt");
+
+ assert_common_dirlen(6, "a/b/c/foo.txt", "a/b/c/d/e/bar.txt");
+ assert_common_dirlen(7, "/a/b/c/foo.txt", "/a/b/c/d/e/bar.txt");
+}
+
+static void fix_path(git_str *s)
+{
+#ifndef GIT_WIN32
+ GIT_UNUSED(s);
+#else
+ char* c;
+
+ for (c = s->ptr; *c; c++) {
+ if (*c == '/')
+ *c = '\\';
+ }
+#endif
+}
+
+void test_path__find_exe_in_path(void)
+{
+ char *orig_path;
+ git_str sandbox_path = GIT_STR_INIT;
+ git_str new_path = GIT_STR_INIT, full_path = GIT_STR_INIT,
+ dummy_path = GIT_STR_INIT;
+
+#ifdef GIT_WIN32
+ static const char *bogus_path_1 = "c:\\does\\not\\exist\\";
+ static const char *bogus_path_2 = "e:\\non\\existent";
+#else
+ static const char *bogus_path_1 = "/this/path/does/not/exist/";
+ static const char *bogus_path_2 = "/non/existent";
+#endif
+
+ orig_path = cl_getenv("PATH");
+
+ git_str_puts(&sandbox_path, clar_sandbox_path());
+ git_str_joinpath(&dummy_path, sandbox_path.ptr, "dummmmmmmy_libgit2_file");
+ cl_git_rewritefile(dummy_path.ptr, "this is a dummy file");
+
+ fix_path(&sandbox_path);
+ fix_path(&dummy_path);
+
+ cl_git_pass(git_str_printf(&new_path, "%s%c%s%c%s%c%s",
+ bogus_path_1, GIT_PATH_LIST_SEPARATOR,
+ orig_path, GIT_PATH_LIST_SEPARATOR,
+ sandbox_path.ptr, GIT_PATH_LIST_SEPARATOR,
+ bogus_path_2));
+
+ check_setenv("PATH", new_path.ptr);
+
+ cl_git_fail_with(GIT_ENOTFOUND, git_fs_path_find_executable(&full_path, "this_file_does_not_exist"));
+ cl_git_pass(git_fs_path_find_executable(&full_path, "dummmmmmmy_libgit2_file"));
+
+ cl_assert_equal_s(full_path.ptr, dummy_path.ptr);
+
+ git_str_dispose(&full_path);
+ git_str_dispose(&new_path);
+ git_str_dispose(&dummy_path);
+ git_str_dispose(&sandbox_path);
+ git__free(orig_path);
+}
+
+void test_path__validate_current_user_ownership(void)
+{
+ bool is_cur;
+
+ cl_must_pass(p_mkdir("testdir", 0777));
+ cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testdir"));
+ cl_assert_equal_i(is_cur, 1);
+
+ cl_git_rewritefile("testfile", "This is a test file.");
+ cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "testfile"));
+ cl_assert_equal_i(is_cur, 1);
+
+#ifdef GIT_WIN32
+ cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "C:\\"));
+ cl_assert_equal_i(is_cur, 0);
+
+ cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "c:\\path\\does\\not\\exist"));
+#else
+ cl_git_pass(git_fs_path_owner_is_current_user(&is_cur, "/"));
+ cl_assert_equal_i(is_cur, (geteuid() == 0));
+
+ cl_git_fail(git_fs_path_owner_is_current_user(&is_cur, "/path/does/not/exist"));
+#endif
+}
diff --git a/tests/util/path/core.c b/tests/util/path/core.c
new file mode 100644
index 0000000..f30f6c0
--- /dev/null
+++ b/tests/util/path/core.c
@@ -0,0 +1,343 @@
+#include "clar_libgit2.h"
+#include "fs_path.h"
+
+static void test_make_relative(
+ const char *expected_path,
+ const char *path,
+ const char *parent,
+ int expected_status)
+{
+ git_str buf = GIT_STR_INIT;
+ git_str_puts(&buf, path);
+ cl_assert_equal_i(expected_status, git_fs_path_make_relative(&buf, parent));
+ cl_assert_equal_s(expected_path, buf.ptr);
+ git_str_dispose(&buf);
+}
+
+void test_path_core__make_relative(void)
+{
+ test_make_relative("foo.c", "/path/to/foo.c", "/path/to", 0);
+ test_make_relative("bar/foo.c", "/path/to/bar/foo.c", "/path/to", 0);
+ test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0);
+
+ test_make_relative("", "/path/to", "/path/to", 0);
+ test_make_relative("", "/path/to", "/path/to/", 0);
+
+ test_make_relative("../", "/path/to", "/path/to/foo", 0);
+
+ test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar", 0);
+ test_make_relative("../bar/foo.c", "/path/to/bar/foo.c", "/path/to/baz", 0);
+
+ test_make_relative("../../foo.c", "/path/to/foo.c", "/path/to/foo/bar", 0);
+ test_make_relative("../../foo/bar.c", "/path/to/foo/bar.c", "/path/to/bar/foo", 0);
+
+ test_make_relative("../../foo.c", "/foo.c", "/bar/foo", 0);
+
+ test_make_relative("foo.c", "/path/to/foo.c", "/path/to/", 0);
+ test_make_relative("../foo.c", "/path/to/foo.c", "/path/to/bar/", 0);
+
+ test_make_relative("foo.c", "d:/path/to/foo.c", "d:/path/to", 0);
+
+ test_make_relative("../foo", "/foo", "/bar", 0);
+ test_make_relative("path/to/foo.c", "/path/to/foo.c", "/", 0);
+ test_make_relative("../foo", "path/to/foo", "path/to/bar", 0);
+
+ test_make_relative("/path/to/foo.c", "/path/to/foo.c", "d:/path/to", GIT_ENOTFOUND);
+ test_make_relative("d:/path/to/foo.c", "d:/path/to/foo.c", "/path/to", GIT_ENOTFOUND);
+
+ test_make_relative("/path/to/foo.c", "/path/to/foo.c", "not-a-rooted-path", GIT_ENOTFOUND);
+ test_make_relative("not-a-rooted-path", "not-a-rooted-path", "/path/to", GIT_ENOTFOUND);
+
+ test_make_relative("/path", "/path", "pathtofoo", GIT_ENOTFOUND);
+ test_make_relative("path", "path", "pathtofoo", GIT_ENOTFOUND);
+}
+
+void test_path_core__isvalid_standard(void)
+{
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/file.txt", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/.file", 0));
+}
+
+/* Ensure that `is_valid_str` only reads str->size bytes */
+void test_path_core__isvalid_standard_str(void)
+{
+ git_str str = GIT_STR_INIT_CONST("foo/bar//zap", 0);
+ unsigned int flags = GIT_FS_PATH_REJECT_EMPTY_COMPONENT;
+
+ str.size = 0;
+ cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags));
+
+ str.size = 3;
+ cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags));
+
+ str.size = 4;
+ cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags));
+
+ str.size = 5;
+ cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags));
+
+ str.size = 7;
+ cl_assert_equal_b(true, git_fs_path_str_is_valid(&str, flags));
+
+ str.size = 8;
+ cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags));
+
+ str.size = strlen(str.ptr);
+ cl_assert_equal_b(false, git_fs_path_str_is_valid(&str, flags));
+}
+
+void test_path_core__isvalid_empty_dir_component(void)
+{
+ unsigned int flags = GIT_FS_PATH_REJECT_EMPTY_COMPONENT;
+
+ /* empty component */
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo//bar", 0));
+
+ /* leading slash */
+ cl_assert_equal_b(true, git_fs_path_is_valid("/", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("/foo", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("/foo/bar", 0));
+
+ /* trailing slash */
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo/", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/", 0));
+
+
+ /* empty component */
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo//bar", flags));
+
+ /* leading slash */
+ cl_assert_equal_b(false, git_fs_path_is_valid("/", flags));
+ cl_assert_equal_b(false, git_fs_path_is_valid("/foo", flags));
+ cl_assert_equal_b(false, git_fs_path_is_valid("/foo/bar", flags));
+
+ /* trailing slash */
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo/", flags));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar/", flags));
+}
+
+void test_path_core__isvalid_dot_and_dotdot(void)
+{
+ cl_assert_equal_b(true, git_fs_path_is_valid(".", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("./foo", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo/.", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("./foo", 0));
+
+ cl_assert_equal_b(true, git_fs_path_is_valid("..", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("../foo", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo/..", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("../foo", 0));
+
+ cl_assert_equal_b(false, git_fs_path_is_valid(".", GIT_FS_PATH_REJECT_TRAVERSAL));
+ cl_assert_equal_b(false, git_fs_path_is_valid("./foo", GIT_FS_PATH_REJECT_TRAVERSAL));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo/.", GIT_FS_PATH_REJECT_TRAVERSAL));
+ cl_assert_equal_b(false, git_fs_path_is_valid("./foo", GIT_FS_PATH_REJECT_TRAVERSAL));
+
+ cl_assert_equal_b(false, git_fs_path_is_valid("..", GIT_FS_PATH_REJECT_TRAVERSAL));
+ cl_assert_equal_b(false, git_fs_path_is_valid("../foo", GIT_FS_PATH_REJECT_TRAVERSAL));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo/..", GIT_FS_PATH_REJECT_TRAVERSAL));
+ cl_assert_equal_b(false, git_fs_path_is_valid("../foo", GIT_FS_PATH_REJECT_TRAVERSAL));
+}
+
+void test_path_core__isvalid_backslash(void)
+{
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo\\file.txt", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar\\file.txt", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar\\", 0));
+
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo\\file.txt", GIT_FS_PATH_REJECT_BACKSLASH));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar\\file.txt", GIT_FS_PATH_REJECT_BACKSLASH));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar\\", GIT_FS_PATH_REJECT_BACKSLASH));
+}
+
+void test_path_core__isvalid_trailing_dot(void)
+{
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo.", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo...", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar.", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo./bar", 0));
+
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo.", GIT_FS_PATH_REJECT_TRAILING_DOT));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo...", GIT_FS_PATH_REJECT_TRAILING_DOT));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar.", GIT_FS_PATH_REJECT_TRAILING_DOT));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo./bar", GIT_FS_PATH_REJECT_TRAILING_DOT));
+}
+
+void test_path_core__isvalid_trailing_space(void)
+{
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo ", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo ", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar ", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid(" ", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo /bar", 0));
+
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo ", GIT_FS_PATH_REJECT_TRAILING_SPACE));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo ", GIT_FS_PATH_REJECT_TRAILING_SPACE));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar ", GIT_FS_PATH_REJECT_TRAILING_SPACE));
+ cl_assert_equal_b(false, git_fs_path_is_valid(" ", GIT_FS_PATH_REJECT_TRAILING_SPACE));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo /bar", GIT_FS_PATH_REJECT_TRAILING_SPACE));
+}
+
+void test_path_core__isvalid_trailing_colon(void)
+{
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo:", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar:", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid(":", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("foo:/bar", 0));
+
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo:", GIT_FS_PATH_REJECT_TRAILING_COLON));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar:", GIT_FS_PATH_REJECT_TRAILING_COLON));
+ cl_assert_equal_b(false, git_fs_path_is_valid(":", GIT_FS_PATH_REJECT_TRAILING_COLON));
+ cl_assert_equal_b(false, git_fs_path_is_valid("foo:/bar", GIT_FS_PATH_REJECT_TRAILING_COLON));
+}
+
+void test_path_core__isvalid_dos_paths(void)
+{
+ cl_assert_equal_b(true, git_fs_path_is_valid("aux", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("aux.", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("aux:", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("aux.asdf", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("aux.asdf\\zippy", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("aux:asdf\\foobar", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("con", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("prn", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("nul", 0));
+
+ cl_assert_equal_b(false, git_fs_path_is_valid("aux", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("aux.", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("aux:", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("aux.asdf", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("aux.asdf\\zippy", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("aux:asdf\\foobar", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("con", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("prn", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("nul", GIT_FS_PATH_REJECT_DOS_PATHS));
+
+ cl_assert_equal_b(true, git_fs_path_is_valid("aux1", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("aux1", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(true, git_fs_path_is_valid("auxn", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(true, git_fs_path_is_valid("aux\\foo", GIT_FS_PATH_REJECT_DOS_PATHS));
+}
+
+void test_path_core__isvalid_dos_paths_withnum(void)
+{
+ cl_assert_equal_b(true, git_fs_path_is_valid("com1", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("com1.", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("com1:", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("com1.asdf", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("com1.asdf\\zippy", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("com1:asdf\\foobar", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("com1\\foo", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("lpt1", 0));
+
+ cl_assert_equal_b(false, git_fs_path_is_valid("com1", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("com1.", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("com1:", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("com1.asdf", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("com1.asdf\\zippy", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("com1:asdf\\foobar", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("com1/foo", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("lpt1", GIT_FS_PATH_REJECT_DOS_PATHS));
+
+ cl_assert_equal_b(true, git_fs_path_is_valid("com0", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("com0", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(true, git_fs_path_is_valid("com10", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("com10", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(true, git_fs_path_is_valid("comn", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(true, git_fs_path_is_valid("com1\\foo", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(true, git_fs_path_is_valid("lpt0", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(true, git_fs_path_is_valid("lpt10", GIT_FS_PATH_REJECT_DOS_PATHS));
+ cl_assert_equal_b(true, git_fs_path_is_valid("lptn", GIT_FS_PATH_REJECT_DOS_PATHS));
+}
+
+void test_path_core__isvalid_nt_chars(void)
+{
+ cl_assert_equal_b(true, git_fs_path_is_valid("asdf\001foo", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("asdf\037bar", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("asdf<bar", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("asdf>foo", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("asdf:foo", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("asdf\"bar", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("asdf|foo", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("asdf?bar", 0));
+ cl_assert_equal_b(true, git_fs_path_is_valid("asdf*bar", 0));
+
+ cl_assert_equal_b(false, git_fs_path_is_valid("asdf\001foo", GIT_FS_PATH_REJECT_NT_CHARS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("asdf\037bar", GIT_FS_PATH_REJECT_NT_CHARS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("asdf<bar", GIT_FS_PATH_REJECT_NT_CHARS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("asdf>foo", GIT_FS_PATH_REJECT_NT_CHARS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("asdf:foo", GIT_FS_PATH_REJECT_NT_CHARS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("asdf\"bar", GIT_FS_PATH_REJECT_NT_CHARS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("asdf|foo", GIT_FS_PATH_REJECT_NT_CHARS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("asdf?bar", GIT_FS_PATH_REJECT_NT_CHARS));
+ cl_assert_equal_b(false, git_fs_path_is_valid("asdf*bar", GIT_FS_PATH_REJECT_NT_CHARS));
+}
+
+static void test_join_unrooted(
+ const char *expected_result,
+ ssize_t expected_rootlen,
+ const char *path,
+ const char *base)
+{
+ git_str result = GIT_STR_INIT;
+ ssize_t root_at;
+
+ cl_git_pass(git_fs_path_join_unrooted(&result, path, base, &root_at));
+ cl_assert_equal_s(expected_result, result.ptr);
+ cl_assert_equal_i(expected_rootlen, root_at);
+
+ git_str_dispose(&result);
+}
+
+void test_path_core__join_unrooted(void)
+{
+ git_str out = GIT_STR_INIT;
+
+ test_join_unrooted("foo", 0, "foo", NULL);
+ test_join_unrooted("foo/bar", 0, "foo/bar", NULL);
+
+ /* Relative paths have base prepended */
+ test_join_unrooted("/foo/bar", 4, "bar", "/foo");
+ test_join_unrooted("/foo/bar/foobar", 4, "bar/foobar", "/foo");
+ test_join_unrooted("c:/foo/bar/foobar", 6, "bar/foobar", "c:/foo");
+ test_join_unrooted("c:/foo/bar/foobar", 10, "foobar", "c:/foo/bar");
+
+ /* Absolute paths are not prepended with base */
+ test_join_unrooted("/foo", 0, "/foo", "/asdf");
+ test_join_unrooted("/foo/bar", 0, "/foo/bar", "/asdf");
+
+ /* Drive letter is given as root length on Windows */
+ test_join_unrooted("c:/foo", 2, "c:/foo", "c:/asdf");
+ test_join_unrooted("c:/foo/bar", 2, "c:/foo/bar", "c:/asdf");
+
+#ifdef GIT_WIN32
+ /* Paths starting with '\\' are absolute */
+ test_join_unrooted("\\bar", 0, "\\bar", "c:/foo/");
+ test_join_unrooted("\\\\network\\bar", 9, "\\\\network\\bar", "c:/foo/");
+#else
+ /* Paths starting with '\\' are not absolute on non-Windows systems */
+ test_join_unrooted("/foo/\\bar", 4, "\\bar", "/foo");
+ test_join_unrooted("c:/foo/\\bar", 7, "\\bar", "c:/foo/");
+#endif
+
+ /* Base is returned when it's provided and is the prefix */
+ test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo");
+ test_join_unrooted("c:/foo/bar/foobar", 10, "c:/foo/bar/foobar", "c:/foo/bar");
+
+ /* Trailing slash in the base is ignored */
+ test_join_unrooted("c:/foo/bar/foobar", 6, "c:/foo/bar/foobar", "c:/foo/");
+
+ git_str_dispose(&out);
+}
+
+void test_path_core__join_unrooted_respects_funny_windows_roots(void)
+{
+ test_join_unrooted("💩:/foo/bar/foobar", 9, "bar/foobar", "💩:/foo");
+ test_join_unrooted("💩:/foo/bar/foobar", 13, "foobar", "💩:/foo/bar");
+ test_join_unrooted("💩:/foo", 5, "💩:/foo", "💩:/asdf");
+ test_join_unrooted("💩:/foo/bar", 5, "💩:/foo/bar", "💩:/asdf");
+ test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo");
+ test_join_unrooted("💩:/foo/bar/foobar", 13, "💩:/foo/bar/foobar", "💩:/foo/bar");
+ test_join_unrooted("💩:/foo/bar/foobar", 9, "💩:/foo/bar/foobar", "💩:/foo/");
+}
diff --git a/tests/util/path/win32.c b/tests/util/path/win32.c
new file mode 100644
index 0000000..1aaf686
--- /dev/null
+++ b/tests/util/path/win32.c
@@ -0,0 +1,282 @@
+
+#include "clar_libgit2.h"
+
+#ifdef GIT_WIN32
+#include "win32/path_w32.h"
+#endif
+
+#ifdef GIT_WIN32
+static void test_utf8_to_utf16(const char *utf8_in, const wchar_t *utf16_expected)
+{
+ git_win32_path path_utf16;
+ int path_utf16len;
+
+ cl_assert((path_utf16len = git_win32_path_from_utf8(path_utf16, utf8_in)) >= 0);
+ cl_assert_equal_wcs(utf16_expected, path_utf16);
+ cl_assert_equal_i(wcslen(utf16_expected), path_utf16len);
+}
+
+static void test_utf8_to_utf16_relative(const char* utf8_in, const wchar_t* utf16_expected)
+{
+ git_win32_path path_utf16;
+ int path_utf16len;
+
+ cl_assert((path_utf16len = git_win32_path_relative_from_utf8(path_utf16, utf8_in)) >= 0);
+ cl_assert_equal_wcs(utf16_expected, path_utf16);
+ cl_assert_equal_i(wcslen(utf16_expected), path_utf16len);
+}
+#endif
+
+void test_path_win32__utf8_to_utf16(void)
+{
+#ifdef GIT_WIN32
+ test_utf8_to_utf16("C:\\", L"\\\\?\\C:\\");
+ test_utf8_to_utf16("c:\\", L"\\\\?\\c:\\");
+ test_utf8_to_utf16("C:/", L"\\\\?\\C:\\");
+ test_utf8_to_utf16("c:/", L"\\\\?\\c:\\");
+#endif
+}
+
+void test_path_win32__removes_trailing_slash(void)
+{
+#ifdef GIT_WIN32
+ test_utf8_to_utf16("C:\\Foo\\", L"\\\\?\\C:\\Foo");
+ test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo");
+ test_utf8_to_utf16("C:\\Foo\\\\", L"\\\\?\\C:\\Foo");
+ test_utf8_to_utf16("C:/Foo/", L"\\\\?\\C:\\Foo");
+ test_utf8_to_utf16("C:/Foo///", L"\\\\?\\C:\\Foo");
+#endif
+}
+
+void test_path_win32__squashes_multiple_slashes(void)
+{
+#ifdef GIT_WIN32
+ test_utf8_to_utf16("C:\\\\Foo\\Bar\\\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar");
+ test_utf8_to_utf16("C://Foo/Bar///Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar");
+#endif
+}
+
+void test_path_win32__unc(void)
+{
+#ifdef GIT_WIN32
+ test_utf8_to_utf16("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path");
+ test_utf8_to_utf16("//server/git/style/unc/path", L"\\\\?\\UNC\\server\\git\\style\\unc\\path");
+#endif
+}
+
+void test_path_win32__honors_max_path(void)
+{
+#ifdef GIT_WIN32
+ git_win32_path path_utf16;
+
+ test_utf8_to_utf16("C:\\This path is 261 characters which is fine for our path handling functions which cope with paths longer than MAX_PATH\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghijk",
+ L"\\\\?\\C:\\This path is 261 characters which is fine for our path handling functions which cope with paths longer than MAX_PATH\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghijk");
+
+ cl_check_fail(git_win32_path_from_utf8(path_utf16, "C:\\This path is 4097 chars and exceeds our maximum path length on Windows which is limited to 4096 characters\\alas\\0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij01"));
+
+#endif
+}
+
+void test_path_win32__dot_and_dotdot(void)
+{
+#ifdef GIT_WIN32
+ test_utf8_to_utf16("C:\\Foo\\..\\Foobar", L"\\\\?\\C:\\Foobar");
+ test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar", L"\\\\?\\C:\\Foo\\Foobar");
+ test_utf8_to_utf16("C:\\Foo\\Bar\\..\\Foobar\\..", L"\\\\?\\C:\\Foo");
+ test_utf8_to_utf16("C:\\Foobar\\..", L"\\\\?\\C:\\");
+ test_utf8_to_utf16("C:/Foo/Bar/../Foobar", L"\\\\?\\C:\\Foo\\Foobar");
+ test_utf8_to_utf16("C:/Foo/Bar/../Foobar/../Asdf/", L"\\\\?\\C:\\Foo\\Asdf");
+ test_utf8_to_utf16("C:/Foo/Bar/../Foobar/..", L"\\\\?\\C:\\Foo");
+ test_utf8_to_utf16("C:/Foo/..", L"\\\\?\\C:\\");
+
+ test_utf8_to_utf16("C:\\Foo\\Bar\\.\\Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar");
+ test_utf8_to_utf16("C:\\.\\Foo\\.\\Bar\\.\\Foobar\\.\\", L"\\\\?\\C:\\Foo\\Bar\\Foobar");
+ test_utf8_to_utf16("C:/Foo/Bar/./Foobar", L"\\\\?\\C:\\Foo\\Bar\\Foobar");
+ test_utf8_to_utf16("C:/Foo/../Bar/./Foobar/../", L"\\\\?\\C:\\Bar");
+
+ test_utf8_to_utf16("C:\\Foo\\..\\..\\Bar", L"\\\\?\\C:\\Bar");
+#endif
+}
+
+void test_path_win32__absolute_from_no_drive_letter(void)
+{
+#ifdef GIT_WIN32
+ test_utf8_to_utf16("\\Foo", L"\\\\?\\C:\\Foo");
+ test_utf8_to_utf16("\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar");
+ test_utf8_to_utf16("/Foo/Bar", L"\\\\?\\C:\\Foo\\Bar");
+#endif
+}
+
+void test_path_win32__absolute_from_relative(void)
+{
+#ifdef GIT_WIN32
+ char cwd_backup[MAX_PATH];
+
+ cl_must_pass(p_getcwd(cwd_backup, MAX_PATH));
+ cl_must_pass(p_chdir("C:/"));
+
+ test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Foo");
+ test_utf8_to_utf16("..\\..\\Foo", L"\\\\?\\C:\\Foo");
+ test_utf8_to_utf16("Foo\\..", L"\\\\?\\C:\\");
+ test_utf8_to_utf16("Foo\\..\\..", L"\\\\?\\C:\\");
+ test_utf8_to_utf16("", L"\\\\?\\C:\\");
+
+ cl_must_pass(p_chdir("C:/Windows"));
+
+ test_utf8_to_utf16("Foo", L"\\\\?\\C:\\Windows\\Foo");
+ test_utf8_to_utf16("Foo\\Bar", L"\\\\?\\C:\\Windows\\Foo\\Bar");
+ test_utf8_to_utf16("..\\Foo", L"\\\\?\\C:\\Foo");
+ test_utf8_to_utf16("Foo\\..\\Bar", L"\\\\?\\C:\\Windows\\Bar");
+ test_utf8_to_utf16("", L"\\\\?\\C:\\Windows");
+
+ cl_must_pass(p_chdir(cwd_backup));
+#endif
+}
+
+void test_path_win32__keeps_relative(void)
+{
+#ifdef GIT_WIN32
+ /* Relative paths stay relative */
+ test_utf8_to_utf16_relative("Foo", L"Foo");
+ test_utf8_to_utf16_relative("..\\..\\Foo", L"..\\..\\Foo");
+ test_utf8_to_utf16_relative("Foo\\..", L"Foo\\..");
+ test_utf8_to_utf16_relative("Foo\\..\\..", L"Foo\\..\\..");
+ test_utf8_to_utf16_relative("Foo\\Bar", L"Foo\\Bar");
+ test_utf8_to_utf16_relative("Foo\\..\\Bar", L"Foo\\..\\Bar");
+ test_utf8_to_utf16_relative("../../Foo", L"..\\..\\Foo");
+ test_utf8_to_utf16_relative("Foo/..", L"Foo\\..");
+ test_utf8_to_utf16_relative("Foo/../..", L"Foo\\..\\..");
+ test_utf8_to_utf16_relative("Foo/Bar", L"Foo\\Bar");
+ test_utf8_to_utf16_relative("Foo/../Bar", L"Foo\\..\\Bar");
+ test_utf8_to_utf16_relative("Foo/../Bar/", L"Foo\\..\\Bar\\");
+ test_utf8_to_utf16_relative("", L"");
+
+ /* Absolute paths are canonicalized */
+ test_utf8_to_utf16_relative("\\Foo", L"\\\\?\\C:\\Foo");
+ test_utf8_to_utf16_relative("/Foo/Bar/", L"\\\\?\\C:\\Foo\\Bar");
+ test_utf8_to_utf16_relative("\\\\server\\c$\\unc\\path", L"\\\\?\\UNC\\server\\c$\\unc\\path");
+#endif
+}
+
+#ifdef GIT_WIN32
+static void test_canonicalize(const wchar_t *in, const wchar_t *expected)
+{
+ git_win32_path canonical;
+
+ cl_assert(wcslen(in) < MAX_PATH);
+ wcscpy(canonical, in);
+
+ cl_must_pass(git_win32_path_canonicalize(canonical));
+ cl_assert_equal_wcs(expected, canonical);
+}
+#endif
+
+static void test_remove_namespace(const wchar_t *in, const wchar_t *expected)
+{
+#ifdef GIT_WIN32
+ git_win32_path canonical;
+
+ cl_assert(wcslen(in) < MAX_PATH);
+ wcscpy(canonical, in);
+
+ git_win32_path_remove_namespace(canonical, wcslen(in));
+ cl_assert_equal_wcs(expected, canonical);
+#else
+ GIT_UNUSED(in);
+ GIT_UNUSED(expected);
+#endif
+}
+
+void test_path_win32__remove_namespace(void)
+{
+ test_remove_namespace(L"\\\\?\\C:\\Temp\\Foo", L"C:\\Temp\\Foo");
+ test_remove_namespace(L"\\\\?\\C:\\", L"C:\\");
+ test_remove_namespace(L"\\\\?\\", L"");
+
+ test_remove_namespace(L"\\??\\C:\\Temp\\Foo", L"C:\\Temp\\Foo");
+ test_remove_namespace(L"\\??\\C:\\", L"C:\\");
+ test_remove_namespace(L"\\??\\", L"");
+
+ test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder");
+ test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder");
+ test_remove_namespace(L"\\\\?\\UNC\\server\\C$", L"\\\\server\\C$");
+ test_remove_namespace(L"\\\\?\\UNC\\server\\", L"\\\\server");
+ test_remove_namespace(L"\\\\?\\UNC\\server", L"\\\\server");
+
+ test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder");
+ test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder");
+ test_remove_namespace(L"\\??\\UNC\\server\\C$", L"\\\\server\\C$");
+ test_remove_namespace(L"\\??\\UNC\\server\\", L"\\\\server");
+ test_remove_namespace(L"\\??\\UNC\\server", L"\\\\server");
+
+ test_remove_namespace(L"\\\\server\\C$\\folder", L"\\\\server\\C$\\folder");
+ test_remove_namespace(L"\\\\server\\C$", L"\\\\server\\C$");
+ test_remove_namespace(L"\\\\server\\", L"\\\\server");
+ test_remove_namespace(L"\\\\server", L"\\\\server");
+
+ test_remove_namespace(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar");
+ test_remove_namespace(L"C:\\", L"C:\\");
+ test_remove_namespace(L"", L"");
+
+}
+
+void test_path_win32__canonicalize(void)
+{
+#ifdef GIT_WIN32
+ test_canonicalize(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar");
+ test_canonicalize(L"C:\\Foo\\", L"C:\\Foo");
+ test_canonicalize(L"C:\\Foo\\\\", L"C:\\Foo");
+ test_canonicalize(L"C:\\Foo\\..\\Bar", L"C:\\Bar");
+ test_canonicalize(L"C:\\Foo\\..\\..\\Bar", L"C:\\Bar");
+ test_canonicalize(L"C:\\Foo\\..\\..\\..\\..\\", L"C:\\");
+ test_canonicalize(L"C:/Foo/Bar", L"C:\\Foo\\Bar");
+ test_canonicalize(L"C:/", L"C:\\");
+
+ test_canonicalize(L"\\\\?\\C:\\Foo\\Bar", L"\\\\?\\C:\\Foo\\Bar");
+ test_canonicalize(L"\\\\?\\C:\\Foo\\Bar\\", L"\\\\?\\C:\\Foo\\Bar");
+ test_canonicalize(L"\\\\?\\C:\\\\Foo\\.\\Bar\\\\..\\", L"\\\\?\\C:\\Foo");
+ test_canonicalize(L"\\\\?\\C:\\\\", L"\\\\?\\C:\\");
+ test_canonicalize(L"//?/C:/", L"\\\\?\\C:\\");
+ test_canonicalize(L"//?/C:/../../Foo/", L"\\\\?\\C:\\Foo");
+ test_canonicalize(L"//?/C:/Foo/../../", L"\\\\?\\C:\\");
+
+ test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\?\\UNC\\server\\C$\\folder");
+ test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder");
+ test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\", L"\\\\?\\UNC\\server\\C$\\folder");
+ test_canonicalize(L"\\\\?\\UNC\\server\\C$\\folder\\..\\..\\..\\..\\share\\", L"\\\\?\\UNC\\server\\share");
+
+ test_canonicalize(L"\\\\server\\share", L"\\\\server\\share");
+ test_canonicalize(L"\\\\server\\share\\", L"\\\\server\\share");
+ test_canonicalize(L"\\\\server\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar");
+ test_canonicalize(L"\\\\server\\\\share\\\\foo\\\\bar", L"\\\\server\\share\\foo\\bar");
+ test_canonicalize(L"\\\\server\\share\\..\\foo", L"\\\\server\\foo");
+ test_canonicalize(L"\\\\server\\..\\..\\share\\.\\foo", L"\\\\server\\share\\foo");
+#endif
+}
+
+void test_path_win32__8dot3_name(void)
+{
+#ifdef GIT_WIN32
+ char *shortname;
+
+ if (!cl_sandbox_supports_8dot3())
+ clar__skip();
+
+ /* Some guaranteed short names */
+ cl_assert_equal_s("PROGRA~1", (shortname = git_win32_path_8dot3_name("C:\\Program Files")));
+ git__free(shortname);
+
+ cl_assert_equal_s("WINDOWS", (shortname = git_win32_path_8dot3_name("C:\\WINDOWS")));
+ git__free(shortname);
+
+ /* Create some predictable short names */
+ cl_must_pass(p_mkdir(".foo", 0777));
+ cl_assert_equal_s("FOO~1", (shortname = git_win32_path_8dot3_name(".foo")));
+ git__free(shortname);
+
+ cl_git_write2file("bar~1", "foobar\n", 7, O_RDWR|O_CREAT, 0666);
+ cl_must_pass(p_mkdir(".bar", 0777));
+ cl_assert_equal_s("BAR~2", (shortname = git_win32_path_8dot3_name(".bar")));
+ git__free(shortname);
+#endif
+}
diff --git a/tests/util/pool.c b/tests/util/pool.c
new file mode 100644
index 0000000..464aad7
--- /dev/null
+++ b/tests/util/pool.c
@@ -0,0 +1,62 @@
+#include "clar_libgit2.h"
+#include "pool.h"
+#include "git2/oid.h"
+
+void test_pool__0(void)
+{
+ int i;
+ git_pool p;
+ void *ptr;
+
+ git_pool_init(&p, 1);
+
+ for (i = 1; i < 10000; i *= 2) {
+ ptr = git_pool_malloc(&p, i);
+ cl_assert(ptr != NULL);
+ cl_assert(git_pool__ptr_in_pool(&p, ptr));
+ cl_assert(!git_pool__ptr_in_pool(&p, &i));
+ }
+
+ git_pool_clear(&p);
+}
+
+void test_pool__1(void)
+{
+ int i;
+ git_pool p;
+
+ git_pool_init(&p, 1);
+ p.page_size = 4000;
+
+ for (i = 2010; i > 0; i--)
+ cl_assert(git_pool_malloc(&p, i) != NULL);
+
+#ifndef GIT_DEBUG_POOL
+ /* with fixed page size, allocation must end up with these values */
+ cl_assert_equal_i(591, git_pool__open_pages(&p));
+#endif
+ git_pool_clear(&p);
+
+ git_pool_init(&p, 1);
+ p.page_size = 4120;
+
+ for (i = 2010; i > 0; i--)
+ cl_assert(git_pool_malloc(&p, i) != NULL);
+
+#ifndef GIT_DEBUG_POOL
+ /* with fixed page size, allocation must end up with these values */
+ cl_assert_equal_i(sizeof(void *) == 8 ? 575 : 573, git_pool__open_pages(&p));
+#endif
+ git_pool_clear(&p);
+}
+
+void test_pool__strndup_limit(void)
+{
+ git_pool p;
+
+ git_pool_init(&p, 1);
+ /* ensure 64 bit doesn't overflow */
+ cl_assert(git_pool_strndup(&p, "foo", (size_t)-1) == NULL);
+ git_pool_clear(&p);
+}
+
diff --git a/tests/util/posix.c b/tests/util/posix.c
new file mode 100644
index 0000000..155f03a
--- /dev/null
+++ b/tests/util/posix.c
@@ -0,0 +1,238 @@
+#ifndef _WIN32
+# include <arpa/inet.h>
+# include <sys/socket.h>
+# include <netinet/in.h>
+#else
+# include <ws2tcpip.h>
+# ifdef _MSC_VER
+# pragma comment(lib, "ws2_32")
+# endif
+#endif
+
+#include "clar_libgit2.h"
+#include "futils.h"
+#include "posix.h"
+
+void test_posix__initialize(void)
+{
+#ifdef GIT_WIN32
+ /* on win32, the WSA context needs to be initialized
+ * before any socket calls can be performed */
+ WSADATA wsd;
+
+ cl_git_pass(WSAStartup(MAKEWORD(2,2), &wsd));
+ cl_assert(LOBYTE(wsd.wVersion) == 2 && HIBYTE(wsd.wVersion) == 2);
+#endif
+}
+
+static bool supports_ipv6(void)
+{
+#ifdef GIT_WIN32
+ /* IPv6 is supported on Vista and newer */
+ return git_has_win32_version(6, 0, 0);
+#else
+ return 1;
+#endif
+}
+
+void test_posix__inet_pton(void)
+{
+ struct in_addr addr;
+ struct in6_addr addr6;
+ size_t i;
+
+ struct in_addr_data {
+ const char *p;
+ const uint8_t n[4];
+ };
+
+ struct in6_addr_data {
+ const char *p;
+ const uint8_t n[16];
+ };
+
+ static struct in_addr_data in_addr_data[] = {
+ { "0.0.0.0", { 0, 0, 0, 0 } },
+ { "10.42.101.8", { 10, 42, 101, 8 } },
+ { "127.0.0.1", { 127, 0, 0, 1 } },
+ { "140.177.10.12", { 140, 177, 10, 12 } },
+ { "204.232.175.90", { 204, 232, 175, 90 } },
+ { "255.255.255.255", { 255, 255, 255, 255 } },
+ };
+
+ static struct in6_addr_data in6_addr_data[] = {
+ { "::", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
+ { "::1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } },
+ { "0:0:0:0:0:0:0:1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } },
+ { "2001:db8:8714:3a90::12", { 0x20, 0x01, 0x0d, 0xb8, 0x87, 0x14, 0x3a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12 } },
+ { "fe80::f8ba:c2d6:86be:3645", { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xba, 0xc2, 0xd6, 0x86, 0xbe, 0x36, 0x45 } },
+ { "::ffff:204.152.189.116", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x98, 0xbd, 0x74 } },
+ };
+
+ /* Test some ipv4 addresses */
+ for (i = 0; i < 6; i++) {
+ cl_assert(p_inet_pton(AF_INET, in_addr_data[i].p, &addr) == 1);
+ cl_assert(memcmp(&addr, in_addr_data[i].n, sizeof(struct in_addr)) == 0);
+ }
+
+ /* Test some ipv6 addresses */
+ if (supports_ipv6())
+ {
+ for (i = 0; i < 6; i++) {
+ cl_assert(p_inet_pton(AF_INET6, in6_addr_data[i].p, &addr6) == 1);
+ cl_assert(memcmp(&addr6, in6_addr_data[i].n, sizeof(struct in6_addr)) == 0);
+ }
+ }
+
+ /* Test some invalid strings */
+ cl_assert(p_inet_pton(AF_INET, "", &addr) == 0);
+ cl_assert(p_inet_pton(AF_INET, "foo", &addr) == 0);
+ cl_assert(p_inet_pton(AF_INET, " 127.0.0.1", &addr) == 0);
+ cl_assert(p_inet_pton(AF_INET, "bar", &addr) == 0);
+ cl_assert(p_inet_pton(AF_INET, "10.foo.bar.1", &addr) == 0);
+
+ /* Test unsupported address families */
+ cl_git_fail(p_inet_pton(INT_MAX-1, "52.472", &addr));
+ cl_assert_equal_i(EAFNOSUPPORT, errno);
+}
+
+void test_posix__utimes(void)
+{
+ struct p_timeval times[2];
+ struct stat st;
+ time_t curtime;
+ int fd;
+
+ /* test p_utimes */
+ times[0].tv_sec = 1234567890;
+ times[0].tv_usec = 0;
+ times[1].tv_sec = 1234567890;
+ times[1].tv_usec = 0;
+
+ cl_git_mkfile("foo", "Dummy file.");
+ cl_must_pass(p_utimes("foo", times));
+
+ cl_must_pass(p_stat("foo", &st));
+ cl_assert_equal_i(1234567890, st.st_atime);
+ cl_assert_equal_i(1234567890, st.st_mtime);
+
+
+ /* test p_futimes */
+ times[0].tv_sec = 1414141414;
+ times[0].tv_usec = 0;
+ times[1].tv_sec = 1414141414;
+ times[1].tv_usec = 0;
+
+ cl_must_pass(fd = p_open("foo", O_RDWR));
+ cl_must_pass(p_futimes(fd, times));
+ cl_must_pass(p_close(fd));
+
+ cl_must_pass(p_stat("foo", &st));
+ cl_assert_equal_i(1414141414, st.st_atime);
+ cl_assert_equal_i(1414141414, st.st_mtime);
+
+
+ /* test p_utimes with current time, assume that
+ * it takes < 5 seconds to get the time...!
+ */
+ cl_must_pass(p_utimes("foo", NULL));
+
+ curtime = time(NULL);
+ cl_must_pass(p_stat("foo", &st));
+ cl_assert((st.st_atime - curtime) < 5);
+ cl_assert((st.st_mtime - curtime) < 5);
+
+ cl_must_pass(p_unlink("foo"));
+}
+
+void test_posix__unlink_removes_symlink(void)
+{
+ if (!git_fs_path_supports_symlinks(clar_sandbox_path()))
+ clar__skip();
+
+ cl_git_mkfile("file", "Dummy file.");
+ cl_git_pass(git_futils_mkdir("dir", 0777, 0));
+
+ cl_must_pass(p_symlink("file", "file-symlink"));
+ cl_must_pass(p_symlink("dir", "dir-symlink"));
+
+ cl_must_pass(p_unlink("file-symlink"));
+ cl_must_pass(p_unlink("dir-symlink"));
+
+ cl_assert(git_fs_path_exists("file"));
+ cl_assert(git_fs_path_exists("dir"));
+
+ cl_must_pass(p_unlink("file"));
+ cl_must_pass(p_rmdir("dir"));
+}
+
+void test_posix__symlink_resolves_to_correct_type(void)
+{
+ git_str contents = GIT_STR_INIT;
+
+ if (!git_fs_path_supports_symlinks(clar_sandbox_path()))
+ clar__skip();
+
+ cl_must_pass(git_futils_mkdir("dir", 0777, 0));
+ cl_must_pass(git_futils_mkdir("file", 0777, 0));
+ cl_git_mkfile("dir/file", "symlink target");
+
+ cl_git_pass(p_symlink("file", "dir/link"));
+
+ cl_git_pass(git_futils_readbuffer(&contents, "dir/file"));
+ cl_assert_equal_s(contents.ptr, "symlink target");
+
+ cl_must_pass(p_unlink("dir/link"));
+ cl_must_pass(p_unlink("dir/file"));
+ cl_must_pass(p_rmdir("dir"));
+ cl_must_pass(p_rmdir("file"));
+
+ git_str_dispose(&contents);
+}
+
+void test_posix__relative_symlink(void)
+{
+ git_str contents = GIT_STR_INIT;
+
+ if (!git_fs_path_supports_symlinks(clar_sandbox_path()))
+ clar__skip();
+
+ cl_must_pass(git_futils_mkdir("dir", 0777, 0));
+ cl_git_mkfile("file", "contents");
+ cl_git_pass(p_symlink("../file", "dir/link"));
+ cl_git_pass(git_futils_readbuffer(&contents, "dir/link"));
+ cl_assert_equal_s(contents.ptr, "contents");
+
+ cl_must_pass(p_unlink("file"));
+ cl_must_pass(p_unlink("dir/link"));
+ cl_must_pass(p_rmdir("dir"));
+
+ git_str_dispose(&contents);
+}
+
+void test_posix__symlink_to_file_across_dirs(void)
+{
+ git_str contents = GIT_STR_INIT;
+
+ if (!git_fs_path_supports_symlinks(clar_sandbox_path()))
+ clar__skip();
+
+ /*
+ * Create a relative symlink that points into another
+ * directory. This used to not work on Win32, where we
+ * forgot to convert directory separators to
+ * Windows-style ones.
+ */
+ cl_must_pass(git_futils_mkdir("dir", 0777, 0));
+ cl_git_mkfile("dir/target", "symlink target");
+ cl_git_pass(p_symlink("dir/target", "link"));
+
+ cl_git_pass(git_futils_readbuffer(&contents, "dir/target"));
+ cl_assert_equal_s(contents.ptr, "symlink target");
+
+ cl_must_pass(p_unlink("dir/target"));
+ cl_must_pass(p_unlink("link"));
+ cl_must_pass(p_rmdir("dir"));
+
+ git_str_dispose(&contents);
+}
diff --git a/tests/util/pqueue.c b/tests/util/pqueue.c
new file mode 100644
index 0000000..38931ec
--- /dev/null
+++ b/tests/util/pqueue.c
@@ -0,0 +1,150 @@
+#include "clar_libgit2.h"
+#include "pqueue.h"
+
+static int cmp_ints(const void *v1, const void *v2)
+{
+ int i1 = *(int *)v1, i2 = *(int *)v2;
+ return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
+}
+
+void test_pqueue__items_are_put_in_order(void)
+{
+ git_pqueue pq;
+ int i, vals[20];
+
+ cl_git_pass(git_pqueue_init(&pq, 0, 20, cmp_ints));
+
+ for (i = 0; i < 20; ++i) {
+ if (i < 10)
+ vals[i] = 10 - i; /* 10 down to 1 */
+ else
+ vals[i] = i + 1; /* 11 up to 20 */
+
+ cl_git_pass(git_pqueue_insert(&pq, &vals[i]));
+ }
+
+ cl_assert_equal_i(20, git_pqueue_size(&pq));
+
+ for (i = 1; i <= 20; ++i) {
+ void *p = git_pqueue_pop(&pq);
+ cl_assert(p);
+ cl_assert_equal_i(i, *(int *)p);
+ }
+
+ cl_assert_equal_i(0, git_pqueue_size(&pq));
+
+ git_pqueue_free(&pq);
+}
+
+void test_pqueue__interleave_inserts_and_pops(void)
+{
+ git_pqueue pq;
+ int chunk, v, i, vals[200];
+
+ cl_git_pass(git_pqueue_init(&pq, 0, 20, cmp_ints));
+
+ for (v = 0, chunk = 20; chunk <= 200; chunk += 20) {
+ /* push the next 20 */
+ for (; v < chunk; ++v) {
+ vals[v] = (v & 1) ? 200 - v : v;
+ cl_git_pass(git_pqueue_insert(&pq, &vals[v]));
+ }
+
+ /* pop the lowest 10 */
+ for (i = 0; i < 10; ++i)
+ (void)git_pqueue_pop(&pq);
+ }
+
+ cl_assert_equal_i(100, git_pqueue_size(&pq));
+
+ /* at this point, we've popped 0-99 */
+
+ for (v = 100; v < 200; ++v) {
+ void *p = git_pqueue_pop(&pq);
+ cl_assert(p);
+ cl_assert_equal_i(v, *(int *)p);
+ }
+
+ cl_assert_equal_i(0, git_pqueue_size(&pq));
+
+ git_pqueue_free(&pq);
+}
+
+void test_pqueue__max_heap_size(void)
+{
+ git_pqueue pq;
+ int i, vals[100];
+
+ cl_git_pass(git_pqueue_init(&pq, GIT_PQUEUE_FIXED_SIZE, 50, cmp_ints));
+
+ for (i = 0; i < 100; ++i) {
+ vals[i] = (i & 1) ? 100 - i : i;
+ cl_git_pass(git_pqueue_insert(&pq, &vals[i]));
+ }
+
+ cl_assert_equal_i(50, git_pqueue_size(&pq));
+
+ for (i = 50; i < 100; ++i) {
+ void *p = git_pqueue_pop(&pq);
+ cl_assert(p);
+ cl_assert_equal_i(i, *(int *)p);
+ }
+
+ cl_assert_equal_i(0, git_pqueue_size(&pq));
+
+ git_pqueue_free(&pq);
+}
+
+void test_pqueue__max_heap_size_without_comparison(void)
+{
+ git_pqueue pq;
+ int i, vals[100] = { 0 };
+
+ cl_git_pass(git_pqueue_init(&pq, GIT_PQUEUE_FIXED_SIZE, 50, NULL));
+
+ for (i = 0; i < 100; ++i)
+ cl_git_pass(git_pqueue_insert(&pq, &vals[i]));
+
+ cl_assert_equal_i(50, git_pqueue_size(&pq));
+
+ /* As we have no comparison function, we cannot make any
+ * actual assumptions about which entries are part of the
+ * pqueue */
+ for (i = 0; i < 50; ++i)
+ cl_assert(git_pqueue_pop(&pq));
+
+ cl_assert_equal_i(0, git_pqueue_size(&pq));
+
+ git_pqueue_free(&pq);
+}
+
+static int cmp_ints_like_commit_time(const void *a, const void *b)
+{
+ return *((const int *)a) < *((const int *)b);
+}
+
+void test_pqueue__interleaved_pushes_and_pops(void)
+{
+ git_pqueue pq;
+ int i, j, *val;
+ static int commands[] =
+ { 6, 9, 8, 0, 5, 0, 7, 0, 4, 3, 0, 0, 0, 4, 0, 2, 0, 1, 0, 0, -1 };
+ static int expected[] =
+ { 9, 8, 7, 6, 5, 4, 4, 3, 2, 1, -1 };
+
+ cl_git_pass(git_pqueue_init(&pq, 0, 10, cmp_ints_like_commit_time));
+
+ for (i = 0, j = 0; commands[i] >= 0; ++i) {
+ if (!commands[i]) {
+ cl_assert((val = git_pqueue_pop(&pq)) != NULL);
+ cl_assert_equal_i(expected[j], *val);
+ ++j;
+ } else {
+ cl_git_pass(git_pqueue_insert(&pq, &commands[i]));
+ }
+ }
+
+ cl_assert_equal_i(0, git_pqueue_size(&pq));
+ git_pqueue_free(&pq);
+}
+
diff --git a/tests/util/precompiled.c b/tests/util/precompiled.c
new file mode 100644
index 0000000..5f656a4
--- /dev/null
+++ b/tests/util/precompiled.c
@@ -0,0 +1 @@
+#include "precompiled.h"
diff --git a/tests/util/precompiled.h b/tests/util/precompiled.h
new file mode 100644
index 0000000..6fa2142
--- /dev/null
+++ b/tests/util/precompiled.h
@@ -0,0 +1,3 @@
+#include "common.h"
+#include "clar.h"
+#include "clar_libgit2.h"
diff --git a/tests/util/qsort.c b/tests/util/qsort.c
new file mode 100644
index 0000000..d8fa20a
--- /dev/null
+++ b/tests/util/qsort.c
@@ -0,0 +1,90 @@
+#include "clar_libgit2.h"
+
+#define assert_sorted(a, cmp) \
+ _assert_sorted(a, ARRAY_SIZE(a), sizeof(*a), cmp)
+
+struct big_entries {
+ char c[311];
+};
+
+static void _assert_sorted(void *els, size_t n, size_t elsize, git__sort_r_cmp cmp)
+{
+ int8_t *p = els;
+
+ git__qsort_r(p, n, elsize, cmp, NULL);
+ while (n-- > 1) {
+ cl_assert(cmp(p, p + elsize, NULL) <= 0);
+ p += elsize;
+ }
+}
+
+static int cmp_big(const void *_a, const void *_b, void *payload)
+{
+ const struct big_entries *a = (const struct big_entries *)_a, *b = (const struct big_entries *)_b;
+ GIT_UNUSED(payload);
+ return (a->c[0] < b->c[0]) ? -1 : (a->c[0] > b->c[0]) ? +1 : 0;
+}
+
+static int cmp_int(const void *_a, const void *_b, void *payload)
+{
+ int a = *(const int *)_a, b = *(const int *)_b;
+ GIT_UNUSED(payload);
+ return (a < b) ? -1 : (a > b) ? +1 : 0;
+}
+
+static int cmp_str(const void *_a, const void *_b, void *payload)
+{
+ GIT_UNUSED(payload);
+ return strcmp((const char *) _a, (const char *) _b);
+}
+
+void test_qsort__array_with_single_entry(void)
+{
+ int a[] = { 10 };
+ assert_sorted(a, cmp_int);
+}
+
+void test_qsort__array_with_equal_entries(void)
+{
+ int a[] = { 4, 4, 4, 4 };
+ assert_sorted(a, cmp_int);
+}
+
+void test_qsort__sorted_array(void)
+{
+ int a[] = { 1, 10 };
+ assert_sorted(a, cmp_int);
+}
+
+void test_qsort__unsorted_array(void)
+{
+ int a[] = { 123, 9, 412938, 10, 234, 89 };
+ assert_sorted(a, cmp_int);
+}
+
+void test_qsort__sorting_strings(void)
+{
+ char *a[] = { "foo", "bar", "baz" };
+ assert_sorted(a, cmp_str);
+}
+
+void test_qsort__sorting_big_entries(void)
+{
+ struct big_entries a[5];
+
+ memset(&a, 0, sizeof(a));
+
+ memset(a[0].c, 'w', sizeof(a[0].c) - 1);
+ memset(a[1].c, 'c', sizeof(a[1].c) - 1);
+ memset(a[2].c, 'w', sizeof(a[2].c) - 1);
+ memset(a[3].c, 'h', sizeof(a[3].c) - 1);
+ memset(a[4].c, 'a', sizeof(a[4].c) - 1);
+
+ assert_sorted(a, cmp_big);
+
+ cl_assert_equal_i(strspn(a[0].c, "a"), sizeof(a[0].c) - 1);
+ cl_assert_equal_i(strspn(a[1].c, "c"), sizeof(a[1].c) - 1);
+ cl_assert_equal_i(strspn(a[2].c, "h"), sizeof(a[2].c) - 1);
+ cl_assert_equal_i(strspn(a[3].c, "w"), sizeof(a[3].c) - 1);
+ cl_assert_equal_i(strspn(a[4].c, "w"), sizeof(a[4].c) - 1);
+}
diff --git a/tests/util/regexp.c b/tests/util/regexp.c
new file mode 100644
index 0000000..a76955d
--- /dev/null
+++ b/tests/util/regexp.c
@@ -0,0 +1,197 @@
+#include "clar_libgit2.h"
+
+#include <locale.h>
+
+#include "regexp.h"
+
+#if LC_ALL > 0
+static const char *old_locales[LC_ALL];
+#endif
+
+static git_regexp regex;
+
+void test_regexp__initialize(void)
+{
+#if LC_ALL > 0
+ memset(&old_locales, 0, sizeof(old_locales));
+#endif
+}
+
+void test_regexp__cleanup(void)
+{
+ git_regexp_dispose(&regex);
+}
+
+static void try_set_locale(int category)
+{
+#if LC_ALL > 0
+ old_locales[category] = setlocale(category, NULL);
+#endif
+
+ if (!setlocale(category, "UTF-8") &&
+ !setlocale(category, "c.utf8") &&
+ !setlocale(category, "en_US.UTF-8"))
+ cl_skip();
+
+ if (MB_CUR_MAX == 1)
+ cl_fail("Expected locale to be switched to multibyte");
+}
+
+
+void test_regexp__compile_ignores_global_locale_ctype(void)
+{
+ try_set_locale(LC_CTYPE);
+ cl_git_pass(git_regexp_compile(&regex, "[\xc0-\xff][\x80-\xbf]", 0));
+}
+
+void test_regexp__compile_ignores_global_locale_collate(void)
+{
+#ifdef GIT_WIN32
+ cl_skip();
+#endif
+
+ try_set_locale(LC_COLLATE);
+ cl_git_pass(git_regexp_compile(&regex, "[\xc0-\xff][\x80-\xbf]", 0));
+}
+
+void test_regexp__regex_matches_digits_with_locale(void)
+{
+ char c, str[2];
+
+#ifdef GIT_WIN32
+ cl_skip();
+#endif
+
+ try_set_locale(LC_COLLATE);
+ try_set_locale(LC_CTYPE);
+
+ cl_git_pass(git_regexp_compile(&regex, "[[:digit:]]", 0));
+
+ str[1] = '\0';
+ for (c = '0'; c <= '9'; c++) {
+ str[0] = c;
+ cl_git_pass(git_regexp_match(&regex, str));
+ }
+}
+
+void test_regexp__regex_matches_alphabet_with_locale(void)
+{
+ char c, str[2];
+
+#ifdef GIT_WIN32
+ cl_skip();
+#endif
+
+ try_set_locale(LC_COLLATE);
+ try_set_locale(LC_CTYPE);
+
+ cl_git_pass(git_regexp_compile(&regex, "[[:alpha:]]", 0));
+
+ str[1] = '\0';
+ for (c = 'a'; c <= 'z'; c++) {
+ str[0] = c;
+ cl_git_pass(git_regexp_match(&regex, str));
+ }
+ for (c = 'A'; c <= 'Z'; c++) {
+ str[0] = c;
+ cl_git_pass(git_regexp_match(&regex, str));
+ }
+}
+
+void test_regexp__simple_search_matches(void)
+{
+ cl_git_pass(git_regexp_compile(&regex, "a", 0));
+ cl_git_pass(git_regexp_search(&regex, "a", 0, NULL));
+}
+
+void test_regexp__case_insensitive_search_matches(void)
+{
+ cl_git_pass(git_regexp_compile(&regex, "a", GIT_REGEXP_ICASE));
+ cl_git_pass(git_regexp_search(&regex, "A", 0, NULL));
+}
+
+void test_regexp__nonmatching_search_returns_error(void)
+{
+ cl_git_pass(git_regexp_compile(&regex, "a", 0));
+ cl_git_fail(git_regexp_search(&regex, "b", 0, NULL));
+}
+
+void test_regexp__search_finds_complete_match(void)
+{
+ git_regmatch matches[1];
+
+ cl_git_pass(git_regexp_compile(&regex, "abc", 0));
+ cl_git_pass(git_regexp_search(&regex, "abc", 1, matches));
+ cl_assert_equal_i(matches[0].start, 0);
+ cl_assert_equal_i(matches[0].end, 3);
+}
+
+void test_regexp__search_finds_correct_offsets(void)
+{
+ git_regmatch matches[3];
+
+ cl_git_pass(git_regexp_compile(&regex, "(a*)(b*)", 0));
+ cl_git_pass(git_regexp_search(&regex, "ab", 3, matches));
+ cl_assert_equal_i(matches[0].start, 0);
+ cl_assert_equal_i(matches[0].end, 2);
+ cl_assert_equal_i(matches[1].start, 0);
+ cl_assert_equal_i(matches[1].end, 1);
+ cl_assert_equal_i(matches[2].start, 1);
+ cl_assert_equal_i(matches[2].end, 2);
+}
+
+void test_regexp__search_finds_empty_group(void)
+{
+ git_regmatch matches[3];
+
+ cl_git_pass(git_regexp_compile(&regex, "(a*)(b*)c", 0));
+ cl_git_pass(git_regexp_search(&regex, "ac", 3, matches));
+ cl_assert_equal_i(matches[0].start, 0);
+ cl_assert_equal_i(matches[0].end, 2);
+ cl_assert_equal_i(matches[1].start, 0);
+ cl_assert_equal_i(matches[1].end, 1);
+ cl_assert_equal_i(matches[2].start, 1);
+ cl_assert_equal_i(matches[2].end, 1);
+}
+
+void test_regexp__search_fills_matches_with_first_matching_groups(void)
+{
+ git_regmatch matches[2];
+
+ cl_git_pass(git_regexp_compile(&regex, "(a)(b)(c)", 0));
+ cl_git_pass(git_regexp_search(&regex, "abc", 2, matches));
+ cl_assert_equal_i(matches[0].start, 0);
+ cl_assert_equal_i(matches[0].end, 3);
+ cl_assert_equal_i(matches[1].start, 0);
+ cl_assert_equal_i(matches[1].end, 1);
+}
+
+void test_regexp__search_skips_nonmatching_group(void)
+{
+ git_regmatch matches[4];
+
+ cl_git_pass(git_regexp_compile(&regex, "(a)(b)?(c)", 0));
+ cl_git_pass(git_regexp_search(&regex, "ac", 4, matches));
+ cl_assert_equal_i(matches[0].start, 0);
+ cl_assert_equal_i(matches[0].end, 2);
+ cl_assert_equal_i(matches[1].start, 0);
+ cl_assert_equal_i(matches[1].end, 1);
+ cl_assert_equal_i(matches[2].start, -1);
+ cl_assert_equal_i(matches[2].end, -1);
+ cl_assert_equal_i(matches[3].start, 1);
+ cl_assert_equal_i(matches[3].end, 2);
+}
+
+void test_regexp__search_initializes_trailing_nonmatching_groups(void)
+{
+ git_regmatch matches[3];
+
+ cl_git_pass(git_regexp_compile(&regex, "(a)bc", 0));
+ cl_git_pass(git_regexp_search(&regex, "abc", 3, matches));
+ cl_assert_equal_i(matches[0].start, 0);
+ cl_assert_equal_i(matches[0].end, 3);
+ cl_assert_equal_i(matches[1].start, 0);
+ cl_assert_equal_i(matches[1].end, 1);
+ cl_assert_equal_i(matches[2].start, -1);
+ cl_assert_equal_i(matches[2].end, -1);
+}
diff --git a/tests/util/rmdir.c b/tests/util/rmdir.c
new file mode 100644
index 0000000..71ec05f
--- /dev/null
+++ b/tests/util/rmdir.c
@@ -0,0 +1,120 @@
+#include "clar_libgit2.h"
+#include "futils.h"
+
+static const char *empty_tmp_dir = "test_gitfo_rmdir_recurs_test";
+
+void test_rmdir__initialize(void)
+{
+ git_str path = GIT_STR_INIT;
+
+ cl_must_pass(p_mkdir(empty_tmp_dir, 0777));
+
+ cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one"));
+ cl_must_pass(p_mkdir(path.ptr, 0777));
+
+ cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_one"));
+ cl_must_pass(p_mkdir(path.ptr, 0777));
+
+ cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_two"));
+ cl_must_pass(p_mkdir(path.ptr, 0777));
+
+ cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one/two_two/three"));
+ cl_must_pass(p_mkdir(path.ptr, 0777));
+
+ cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/two"));
+ cl_must_pass(p_mkdir(path.ptr, 0777));
+
+ git_str_dispose(&path);
+}
+
+void test_rmdir__cleanup(void)
+{
+ if (git_fs_path_exists(empty_tmp_dir))
+ cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_REMOVE_FILES));
+}
+
+/* make sure empty dir can be deleted recursively */
+void test_rmdir__delete_recursive(void)
+{
+ git_str path = GIT_STR_INIT;
+ cl_git_pass(git_str_joinpath(&path, empty_tmp_dir, "/one"));
+ cl_assert(git_fs_path_exists(git_str_cstr(&path)));
+
+ cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY));
+
+ cl_assert(!git_fs_path_exists(git_str_cstr(&path)));
+
+ git_str_dispose(&path);
+}
+
+/* make sure non-empty dir cannot be deleted recursively */
+void test_rmdir__fail_to_delete_non_empty_dir(void)
+{
+ git_str file = GIT_STR_INIT;
+
+ cl_git_pass(git_str_joinpath(&file, empty_tmp_dir, "/two/file.txt"));
+
+ cl_git_mkfile(git_str_cstr(&file), "dummy");
+
+ cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY));
+
+ cl_must_pass(p_unlink(file.ptr));
+ cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY));
+
+ cl_assert(!git_fs_path_exists(empty_tmp_dir));
+
+ git_str_dispose(&file);
+}
+
+void test_rmdir__keep_base(void)
+{
+ cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_SKIP_ROOT));
+ cl_assert(git_fs_path_exists(empty_tmp_dir));
+}
+
+void test_rmdir__can_skip_non_empty_dir(void)
+{
+ git_str file = GIT_STR_INIT;
+
+ cl_git_pass(git_str_joinpath(&file, empty_tmp_dir, "/two/file.txt"));
+
+ cl_git_mkfile(git_str_cstr(&file), "dummy");
+
+ cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_SKIP_NONEMPTY));
+ cl_assert(git_fs_path_exists(git_str_cstr(&file)) == true);
+
+ cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_assert(git_fs_path_exists(empty_tmp_dir) == false);
+
+ git_str_dispose(&file);
+}
+
+void test_rmdir__can_remove_empty_parents(void)
+{
+ git_str file = GIT_STR_INIT;
+
+ cl_git_pass(
+ git_str_joinpath(&file, empty_tmp_dir, "/one/two_two/three/file.txt"));
+ cl_git_mkfile(git_str_cstr(&file), "dummy");
+ cl_assert(git_fs_path_isfile(git_str_cstr(&file)));
+
+ cl_git_pass(git_futils_rmdir_r("one/two_two/three/file.txt", empty_tmp_dir,
+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS));
+
+ cl_assert(!git_fs_path_exists(git_str_cstr(&file)));
+
+ git_str_rtruncate_at_char(&file, '/'); /* three (only contained file.txt) */
+ cl_assert(!git_fs_path_exists(git_str_cstr(&file)));
+
+ git_str_rtruncate_at_char(&file, '/'); /* two_two (only contained three) */
+ cl_assert(!git_fs_path_exists(git_str_cstr(&file)));
+
+ git_str_rtruncate_at_char(&file, '/'); /* one (contained two_one also) */
+ cl_assert(git_fs_path_exists(git_str_cstr(&file)));
+
+ cl_assert(git_fs_path_exists(empty_tmp_dir) == true);
+
+ git_str_dispose(&file);
+
+ cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY));
+}
diff --git a/tests/util/sha1.c b/tests/util/sha1.c
new file mode 100644
index 0000000..fe4032c
--- /dev/null
+++ b/tests/util/sha1.c
@@ -0,0 +1,100 @@
+#include "clar_libgit2.h"
+#include "hash.h"
+
+#define FIXTURE_DIR "sha1"
+
+#ifdef GIT_SHA1_WIN32
+static git_hash_win32_provider_t orig_provider;
+#endif
+
+void test_sha1__initialize(void)
+{
+#ifdef GIT_SHA1_WIN32
+ orig_provider = git_hash_win32_provider();
+#endif
+
+ cl_fixture_sandbox(FIXTURE_DIR);
+}
+
+void test_sha1__cleanup(void)
+{
+#ifdef GIT_SHA1_WIN32
+ git_hash_win32_set_provider(orig_provider);
+#endif
+
+ cl_fixture_cleanup(FIXTURE_DIR);
+}
+
+static int sha1_file(unsigned char *out, const char *filename)
+{
+ git_hash_ctx ctx;
+ char buf[2048];
+ int fd, ret;
+ ssize_t read_len;
+
+ fd = p_open(filename, O_RDONLY);
+ cl_assert(fd >= 0);
+
+ cl_git_pass(git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA1));
+
+ while ((read_len = p_read(fd, buf, 2048)) > 0)
+ cl_git_pass(git_hash_update(&ctx, buf, (size_t)read_len));
+
+ cl_assert_equal_i(0, read_len);
+ p_close(fd);
+
+ ret = git_hash_final(out, &ctx);
+ git_hash_ctx_cleanup(&ctx);
+
+ return ret;
+}
+
+void test_sha1__sum(void)
+{
+ unsigned char expected[GIT_HASH_SHA1_SIZE] = {
+ 0x4e, 0x72, 0x67, 0x9e, 0x3e, 0xa4, 0xd0, 0x4e, 0x0c, 0x64,
+ 0x2f, 0x02, 0x9e, 0x61, 0xeb, 0x80, 0x56, 0xc7, 0xed, 0x94
+ };
+ unsigned char actual[GIT_HASH_SHA1_SIZE];
+
+ cl_git_pass(sha1_file(actual, FIXTURE_DIR "/hello_c"));
+ cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA1_SIZE));
+}
+
+/* test that sha1 collision detection works when enabled */
+void test_sha1__detect_collision_attack(void)
+{
+ unsigned char actual[GIT_HASH_SHA1_SIZE];
+ unsigned char expected[GIT_HASH_SHA1_SIZE] = {
+ 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17,
+ 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a
+ };
+
+#ifdef GIT_SHA1_COLLISIONDETECT
+ GIT_UNUSED(&expected);
+ cl_git_fail(sha1_file(actual, FIXTURE_DIR "/shattered-1.pdf"));
+ cl_assert_equal_s("SHA1 collision attack detected", git_error_last()->message);
+#else
+ cl_git_pass(sha1_file(actual, FIXTURE_DIR "/shattered-1.pdf"));
+ cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA1_SIZE));
+#endif
+}
+
+void test_sha1__win32_providers(void)
+{
+#ifdef GIT_SHA1_WIN32
+ unsigned char expected[GIT_HASH_SHA1_SIZE] = {
+ 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17,
+ 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a
+ };
+ unsigned char actual[GIT_HASH_SHA1_SIZE];
+
+ git_hash_win32_set_provider(GIT_HASH_WIN32_CRYPTOAPI);
+ cl_git_pass(sha1_file(actual, FIXTURE_DIR "/shattered-1.pdf"));
+ cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA1_SIZE));
+
+ git_hash_win32_set_provider(GIT_HASH_WIN32_CNG);
+ cl_git_pass(sha1_file(actual, FIXTURE_DIR "/shattered-1.pdf"));
+ cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA1_SIZE));
+#endif
+}
diff --git a/tests/util/sha256.c b/tests/util/sha256.c
new file mode 100644
index 0000000..75b3d73
--- /dev/null
+++ b/tests/util/sha256.c
@@ -0,0 +1,113 @@
+#include "clar_libgit2.h"
+#include "hash.h"
+
+#define FIXTURE_DIR "sha1"
+
+#ifdef GIT_SHA256_WIN32
+static git_hash_win32_provider_t orig_provider;
+#endif
+
+void test_sha256__initialize(void)
+{
+#ifdef GIT_SHA256_WIN32
+ orig_provider = git_hash_win32_provider();
+#endif
+
+ cl_fixture_sandbox(FIXTURE_DIR);
+}
+
+void test_sha256__cleanup(void)
+{
+#ifdef GIT_SHA256_WIN32
+ git_hash_win32_set_provider(orig_provider);
+#endif
+
+ cl_fixture_cleanup(FIXTURE_DIR);
+}
+
+static int sha256_file(unsigned char *out, const char *filename)
+{
+ git_hash_ctx ctx;
+ char buf[2048];
+ int fd, ret;
+ ssize_t read_len;
+
+ fd = p_open(filename, O_RDONLY);
+ cl_assert(fd >= 0);
+
+ cl_git_pass(git_hash_ctx_init(&ctx, GIT_HASH_ALGORITHM_SHA256));
+
+ while ((read_len = p_read(fd, buf, 2048)) > 0)
+ cl_git_pass(git_hash_update(&ctx, buf, (size_t)read_len));
+
+ cl_assert_equal_i(0, read_len);
+ p_close(fd);
+
+ ret = git_hash_final(out, &ctx);
+ git_hash_ctx_cleanup(&ctx);
+
+ return ret;
+}
+
+void test_sha256__empty(void)
+{
+ unsigned char expected[GIT_HASH_SHA256_SIZE] = {
+ 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
+ 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
+ 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
+ 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55
+ };
+ unsigned char actual[GIT_HASH_SHA256_SIZE];
+
+ cl_git_pass(sha256_file(actual, FIXTURE_DIR "/empty"));
+ cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA256_SIZE));
+}
+
+void test_sha256__hello(void)
+{
+ unsigned char expected[GIT_HASH_SHA256_SIZE] = {
+ 0xaa, 0x32, 0x7f, 0xae, 0x5c, 0x91, 0x58, 0x3a,
+ 0x4f, 0xb6, 0x54, 0xcc, 0xb6, 0xc2, 0xb1, 0x0c,
+ 0x77, 0xd7, 0x49, 0xc9, 0x91, 0x2a, 0x8d, 0x6b,
+ 0x47, 0x26, 0x13, 0xc0, 0xa0, 0x4b, 0x4d, 0xad
+ };
+ unsigned char actual[GIT_HASH_SHA256_SIZE];
+
+ cl_git_pass(sha256_file(actual, FIXTURE_DIR "/hello_c"));
+ cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA256_SIZE));
+}
+
+void test_sha256__pdf(void)
+{
+ unsigned char expected[GIT_HASH_SHA256_SIZE] = {
+ 0x2b, 0xb7, 0x87, 0xa7, 0x3e, 0x37, 0x35, 0x2f,
+ 0x92, 0x38, 0x3a, 0xbe, 0x7e, 0x29, 0x02, 0x93,
+ 0x6d, 0x10, 0x59, 0xad, 0x9f, 0x1b, 0xa6, 0xda,
+ 0xaa, 0x9c, 0x1e, 0x58, 0xee, 0x69, 0x70, 0xd0
+ };
+ unsigned char actual[GIT_HASH_SHA256_SIZE];
+
+ cl_git_pass(sha256_file(actual, FIXTURE_DIR "/shattered-1.pdf"));
+ cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA256_SIZE));
+}
+
+void test_sha256__win32_providers(void)
+{
+#ifdef GIT_SHA256_WIN32
+ unsigned char expected[GIT_HASH_SHA256_SIZE] = {
+ 0x2b, 0xb7, 0x87, 0xa7, 0x3e, 0x37, 0x35, 0x2f,
+ 0x92, 0x38, 0x3a, 0xbe, 0x7e, 0x29, 0x02, 0x93,
+ 0x6d, 0x10, 0x59, 0xad, 0x9f, 0x1b, 0xa6, 0xda,
+ 0xaa, 0x9c, 0x1e, 0x58, 0xee, 0x69, 0x70, 0xd0
+ };
+ unsigned char actual[GIT_HASH_SHA256_SIZE];
+
+ git_hash_win32_set_provider(GIT_HASH_WIN32_CRYPTOAPI);
+ cl_git_pass(sha256_file(actual, FIXTURE_DIR "/shattered-1.pdf"));
+ cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA256_SIZE));
+
+ git_hash_win32_set_provider(GIT_HASH_WIN32_CNG);
+ cl_git_pass(sha256_file(actual, FIXTURE_DIR "/shattered-1.pdf"));
+ cl_assert_equal_i(0, memcmp(expected, actual, GIT_HASH_SHA256_SIZE));
+#endif
+}
diff --git a/tests/util/sortedcache.c b/tests/util/sortedcache.c
new file mode 100644
index 0000000..72da7ae
--- /dev/null
+++ b/tests/util/sortedcache.c
@@ -0,0 +1,363 @@
+#include "clar_libgit2.h"
+#include "sortedcache.h"
+
+static int name_only_cmp(const void *a, const void *b)
+{
+ return strcmp(a, b);
+}
+
+void test_sortedcache__name_only(void)
+{
+ git_sortedcache *sc;
+ void *item;
+ size_t pos;
+
+ cl_git_pass(git_sortedcache_new(
+ &sc, 0, NULL, NULL, name_only_cmp, NULL));
+
+ cl_git_pass(git_sortedcache_wlock(sc));
+ cl_git_pass(git_sortedcache_upsert(&item, sc, "aaa"));
+ cl_git_pass(git_sortedcache_upsert(&item, sc, "bbb"));
+ cl_git_pass(git_sortedcache_upsert(&item, sc, "zzz"));
+ cl_git_pass(git_sortedcache_upsert(&item, sc, "mmm"));
+ cl_git_pass(git_sortedcache_upsert(&item, sc, "iii"));
+ git_sortedcache_wunlock(sc);
+
+ cl_assert_equal_sz(5, git_sortedcache_entrycount(sc));
+
+ cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL);
+ cl_assert_equal_s("aaa", item);
+ cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL);
+ cl_assert_equal_s("mmm", item);
+ cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL);
+ cl_assert_equal_s("zzz", item);
+ cl_assert(git_sortedcache_lookup(sc, "qqq") == NULL);
+
+ cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL);
+ cl_assert_equal_s("aaa", item);
+ cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL);
+ cl_assert_equal_s("bbb", item);
+ cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL);
+ cl_assert_equal_s("iii", item);
+ cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL);
+ cl_assert_equal_s("mmm", item);
+ cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL);
+ cl_assert_equal_s("zzz", item);
+ cl_assert(git_sortedcache_entry(sc, 5) == NULL);
+
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa"));
+ cl_assert_equal_sz(0, pos);
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "iii"));
+ cl_assert_equal_sz(2, pos);
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz"));
+ cl_assert_equal_sz(4, pos);
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "abc"));
+
+ cl_git_pass(git_sortedcache_clear(sc, true));
+
+ cl_assert_equal_sz(0, git_sortedcache_entrycount(sc));
+ cl_assert(git_sortedcache_entry(sc, 0) == NULL);
+ cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL);
+ cl_assert(git_sortedcache_entry(sc, 0) == NULL);
+
+ git_sortedcache_free(sc);
+}
+
+typedef struct {
+ int value;
+ char smaller_value;
+ char path[GIT_FLEX_ARRAY];
+} sortedcache_test_struct;
+
+static int sortedcache_test_struct_cmp(const void *a_, const void *b_)
+{
+ const sortedcache_test_struct *a = a_, *b = b_;
+ return strcmp(a->path, b->path);
+}
+
+static void sortedcache_test_struct_free(void *payload, void *item_)
+{
+ sortedcache_test_struct *item = item_;
+ int *count = payload;
+ (*count)++;
+ item->smaller_value = 0;
+}
+
+void test_sortedcache__in_memory(void)
+{
+ git_sortedcache *sc;
+ sortedcache_test_struct *item;
+ int free_count = 0;
+
+ cl_git_pass(git_sortedcache_new(
+ &sc, offsetof(sortedcache_test_struct, path),
+ sortedcache_test_struct_free, &free_count,
+ sortedcache_test_struct_cmp, NULL));
+
+ cl_git_pass(git_sortedcache_wlock(sc));
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "aaa"));
+ item->value = 10;
+ item->smaller_value = 1;
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "bbb"));
+ item->value = 20;
+ item->smaller_value = 2;
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "zzz"));
+ item->value = 30;
+ item->smaller_value = 26;
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "mmm"));
+ item->value = 40;
+ item->smaller_value = 14;
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "iii"));
+ item->value = 50;
+ item->smaller_value = 9;
+ git_sortedcache_wunlock(sc);
+
+ cl_assert_equal_sz(5, git_sortedcache_entrycount(sc));
+
+ cl_git_pass(git_sortedcache_rlock(sc));
+
+ cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL);
+ cl_assert_equal_s("aaa", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL);
+ cl_assert_equal_s("mmm", item->path);
+ cl_assert_equal_i(40, item->value);
+ cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL);
+ cl_assert_equal_s("zzz", item->path);
+ cl_assert_equal_i(30, item->value);
+ cl_assert(git_sortedcache_lookup(sc, "abc") == NULL);
+
+ /* not on Windows:
+ * cl_git_pass(git_sortedcache_rlock(sc)); -- grab more than one
+ */
+
+ cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL);
+ cl_assert_equal_s("aaa", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL);
+ cl_assert_equal_s("bbb", item->path);
+ cl_assert_equal_i(20, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL);
+ cl_assert_equal_s("iii", item->path);
+ cl_assert_equal_i(50, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL);
+ cl_assert_equal_s("mmm", item->path);
+ cl_assert_equal_i(40, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL);
+ cl_assert_equal_s("zzz", item->path);
+ cl_assert_equal_i(30, item->value);
+ cl_assert(git_sortedcache_entry(sc, 5) == NULL);
+
+ git_sortedcache_runlock(sc);
+ /* git_sortedcache_runlock(sc); */
+
+ cl_assert_equal_i(0, free_count);
+
+ cl_git_pass(git_sortedcache_clear(sc, true));
+
+ cl_assert_equal_i(5, free_count);
+
+ cl_assert_equal_sz(0, git_sortedcache_entrycount(sc));
+ cl_assert(git_sortedcache_entry(sc, 0) == NULL);
+ cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL);
+ cl_assert(git_sortedcache_entry(sc, 0) == NULL);
+
+ free_count = 0;
+
+ cl_git_pass(git_sortedcache_wlock(sc));
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "testing"));
+ item->value = 10;
+ item->smaller_value = 3;
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "again"));
+ item->value = 20;
+ item->smaller_value = 1;
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "final"));
+ item->value = 30;
+ item->smaller_value = 2;
+ git_sortedcache_wunlock(sc);
+
+ cl_assert_equal_sz(3, git_sortedcache_entrycount(sc));
+
+ cl_assert((item = git_sortedcache_lookup(sc, "testing")) != NULL);
+ cl_assert_equal_s("testing", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert((item = git_sortedcache_lookup(sc, "again")) != NULL);
+ cl_assert_equal_s("again", item->path);
+ cl_assert_equal_i(20, item->value);
+ cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL);
+ cl_assert_equal_s("final", item->path);
+ cl_assert_equal_i(30, item->value);
+ cl_assert(git_sortedcache_lookup(sc, "zzz") == NULL);
+
+ cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL);
+ cl_assert_equal_s("again", item->path);
+ cl_assert_equal_i(20, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL);
+ cl_assert_equal_s("final", item->path);
+ cl_assert_equal_i(30, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL);
+ cl_assert_equal_s("testing", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert(git_sortedcache_entry(sc, 3) == NULL);
+
+ {
+ size_t pos;
+
+ cl_git_pass(git_sortedcache_wlock(sc));
+
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "again"));
+ cl_assert_equal_sz(0, pos);
+ cl_git_pass(git_sortedcache_remove(sc, pos));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "again"));
+
+ cl_assert_equal_sz(2, git_sortedcache_entrycount(sc));
+
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "testing"));
+ cl_assert_equal_sz(1, pos);
+ cl_git_pass(git_sortedcache_remove(sc, pos));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "testing"));
+
+ cl_assert_equal_sz(1, git_sortedcache_entrycount(sc));
+
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final"));
+ cl_assert_equal_sz(0, pos);
+ cl_git_pass(git_sortedcache_remove(sc, pos));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "final"));
+
+ cl_assert_equal_sz(0, git_sortedcache_entrycount(sc));
+
+ git_sortedcache_wunlock(sc);
+ }
+
+ git_sortedcache_free(sc);
+
+ cl_assert_equal_i(3, free_count);
+}
+
+static void sortedcache_test_reload(git_sortedcache *sc)
+{
+ int count = 0;
+ git_str buf = GIT_STR_INIT;
+ char *scan, *after;
+ sortedcache_test_struct *item;
+
+ cl_assert(git_sortedcache_lockandload(sc, &buf) > 0);
+
+ cl_git_pass(git_sortedcache_clear(sc, false)); /* clear once we already have lock */
+
+ for (scan = buf.ptr; *scan; scan = after + 1) {
+ int val = strtol(scan, &after, 0);
+ cl_assert(after > scan);
+ scan = after;
+
+ for (scan = after; git__isspace(*scan); ++scan) /* find start */;
+ for (after = scan; *after && *after != '\n'; ++after) /* find eol */;
+ *after = '\0';
+
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, scan));
+
+ item->value = val;
+ item->smaller_value = (char)(count++);
+ }
+
+ git_sortedcache_wunlock(sc);
+
+ git_str_dispose(&buf);
+}
+
+void test_sortedcache__on_disk(void)
+{
+ git_sortedcache *sc;
+ sortedcache_test_struct *item;
+ int free_count = 0;
+ size_t pos;
+
+ cl_git_mkfile("cacheitems.txt", "10 abc\n20 bcd\n30 cde\n");
+
+ cl_git_pass(git_sortedcache_new(
+ &sc, offsetof(sortedcache_test_struct, path),
+ sortedcache_test_struct_free, &free_count,
+ sortedcache_test_struct_cmp, "cacheitems.txt"));
+
+ /* should need to reload the first time */
+
+ sortedcache_test_reload(sc);
+
+ /* test what we loaded */
+
+ cl_assert_equal_sz(3, git_sortedcache_entrycount(sc));
+
+ cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL);
+ cl_assert_equal_s("abc", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert((item = git_sortedcache_lookup(sc, "cde")) != NULL);
+ cl_assert_equal_s("cde", item->path);
+ cl_assert_equal_i(30, item->value);
+ cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL);
+
+ cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL);
+ cl_assert_equal_s("abc", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL);
+ cl_assert_equal_s("bcd", item->path);
+ cl_assert_equal_i(20, item->value);
+ cl_assert(git_sortedcache_entry(sc, 3) == NULL);
+
+ /* should not need to reload this time */
+
+ cl_assert_equal_i(0, git_sortedcache_lockandload(sc, NULL));
+
+ /* rewrite ondisk file and reload */
+
+ cl_assert_equal_i(0, free_count);
+
+ cl_git_rewritefile(
+ "cacheitems.txt", "100 abc\n200 zzz\n500 aaa\n10 final\n");
+ sortedcache_test_reload(sc);
+
+ cl_assert_equal_i(3, free_count);
+
+ /* test what we loaded */
+
+ cl_assert_equal_sz(4, git_sortedcache_entrycount(sc));
+
+ cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL);
+ cl_assert_equal_s("abc", item->path);
+ cl_assert_equal_i(100, item->value);
+ cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL);
+ cl_assert_equal_s("final", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert(git_sortedcache_lookup(sc, "cde") == NULL);
+
+ cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL);
+ cl_assert_equal_s("aaa", item->path);
+ cl_assert_equal_i(500, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL);
+ cl_assert_equal_s("final", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL);
+ cl_assert_equal_s("zzz", item->path);
+ cl_assert_equal_i(200, item->value);
+
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa"));
+ cl_assert_equal_sz(0, pos);
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "abc"));
+ cl_assert_equal_sz(1, pos);
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final"));
+ cl_assert_equal_sz(2, pos);
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz"));
+ cl_assert_equal_sz(3, pos);
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "missing"));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "cde"));
+
+ git_sortedcache_free(sc);
+
+ cl_assert_equal_i(7, free_count);
+}
+
diff --git a/tests/util/stat.c b/tests/util/stat.c
new file mode 100644
index 0000000..84c23fb
--- /dev/null
+++ b/tests/util/stat.c
@@ -0,0 +1,113 @@
+#include "clar_libgit2.h"
+#include "futils.h"
+#include "posix.h"
+
+void test_stat__initialize(void)
+{
+ cl_git_pass(git_futils_mkdir("root/d1/d2", 0755, GIT_MKDIR_PATH));
+ cl_git_mkfile("root/file", "whatever\n");
+ cl_git_mkfile("root/d1/file", "whatever\n");
+}
+
+void test_stat__cleanup(void)
+{
+ git_futils_rmdir_r("root", NULL, GIT_RMDIR_REMOVE_FILES);
+}
+
+#define cl_assert_error(val) \
+ do { err = errno; cl_assert_equal_i((val), err); } while (0)
+
+void test_stat__0(void)
+{
+ struct stat st;
+ int err;
+
+ cl_assert_equal_i(0, p_lstat("root", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+ cl_assert_error(0);
+
+ cl_assert_equal_i(0, p_lstat("root/", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+ cl_assert_error(0);
+
+ cl_assert_equal_i(0, p_lstat("root/file", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_error(0);
+
+ cl_assert_equal_i(0, p_lstat("root/d1", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+ cl_assert_error(0);
+
+ cl_assert_equal_i(0, p_lstat("root/d1/", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+ cl_assert_error(0);
+
+ cl_assert_equal_i(0, p_lstat("root/d1/file", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_error(0);
+
+ cl_assert(p_lstat("root/missing", &st) < 0);
+ cl_assert_error(ENOENT);
+
+ cl_assert(p_lstat("root/missing/but/could/be/created", &st) < 0);
+ cl_assert_error(ENOENT);
+
+ cl_assert(p_lstat_posixly("root/missing/but/could/be/created", &st) < 0);
+ cl_assert_error(ENOENT);
+
+ cl_assert(p_lstat("root/d1/missing", &st) < 0);
+ cl_assert_error(ENOENT);
+
+ cl_assert(p_lstat("root/d1/missing/deeper/path", &st) < 0);
+ cl_assert_error(ENOENT);
+
+ cl_assert(p_lstat_posixly("root/d1/missing/deeper/path", &st) < 0);
+ cl_assert_error(ENOENT);
+
+ cl_assert(p_lstat_posixly("root/d1/file/deeper/path", &st) < 0);
+ cl_assert_error(ENOTDIR);
+
+ cl_assert(p_lstat("root/file/invalid", &st) < 0);
+#ifdef GIT_WIN32
+ cl_assert_error(ENOENT);
+#else
+ cl_assert_error(ENOTDIR);
+#endif
+
+ cl_assert(p_lstat_posixly("root/file/invalid", &st) < 0);
+ cl_assert_error(ENOTDIR);
+
+ cl_assert(p_lstat("root/file/invalid/deeper_path", &st) < 0);
+#ifdef GIT_WIN32
+ cl_assert_error(ENOENT);
+#else
+ cl_assert_error(ENOTDIR);
+#endif
+
+ cl_assert(p_lstat_posixly("root/file/invalid/deeper_path", &st) < 0);
+ cl_assert_error(ENOTDIR);
+
+ cl_assert(p_lstat_posixly("root/d1/file/extra", &st) < 0);
+ cl_assert_error(ENOTDIR);
+
+ cl_assert(p_lstat_posixly("root/d1/file/further/invalid/items", &st) < 0);
+ cl_assert_error(ENOTDIR);
+}
+
+void test_stat__root(void)
+{
+ const char *sandbox = clar_sandbox_path();
+ git_str root = GIT_STR_INIT;
+ int root_len;
+ struct stat st;
+
+ root_len = git_fs_path_root(sandbox);
+ cl_assert(root_len >= 0);
+
+ git_str_set(&root, sandbox, root_len+1);
+
+ cl_must_pass(p_stat(root.ptr, &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ git_str_dispose(&root);
+}
diff --git a/tests/util/str/basic.c b/tests/util/str/basic.c
new file mode 100644
index 0000000..5d25568
--- /dev/null
+++ b/tests/util/str/basic.c
@@ -0,0 +1,50 @@
+#include "clar_libgit2.h"
+
+static const char *test_string = "Have you seen that? Have you seeeen that??";
+
+void test_str_basic__resize(void)
+{
+ git_str buf1 = GIT_STR_INIT;
+ git_str_puts(&buf1, test_string);
+ cl_assert(git_str_oom(&buf1) == 0);
+ cl_assert_equal_s(git_str_cstr(&buf1), test_string);
+
+ git_str_puts(&buf1, test_string);
+ cl_assert(strlen(git_str_cstr(&buf1)) == strlen(test_string) * 2);
+ git_str_dispose(&buf1);
+}
+
+void test_str_basic__resize_incremental(void)
+{
+ git_str buf1 = GIT_STR_INIT;
+
+ /* Presently, asking for 6 bytes will round up to 8. */
+ cl_git_pass(git_str_puts(&buf1, "Hello"));
+ cl_assert_equal_i(5, buf1.size);
+ cl_assert_equal_i(8, buf1.asize);
+
+ /* Ensure an additional byte does not realloc. */
+ cl_git_pass(git_str_grow_by(&buf1, 1));
+ cl_assert_equal_i(5, buf1.size);
+ cl_assert_equal_i(8, buf1.asize);
+
+ /* But requesting many does. */
+ cl_git_pass(git_str_grow_by(&buf1, 16));
+ cl_assert_equal_i(5, buf1.size);
+ cl_assert(buf1.asize > 8);
+
+ git_str_dispose(&buf1);
+}
+
+void test_str_basic__printf(void)
+{
+ git_str buf2 = GIT_STR_INIT;
+ git_str_printf(&buf2, "%s %s %d ", "shoop", "da", 23);
+ cl_assert(git_str_oom(&buf2) == 0);
+ cl_assert_equal_s(git_str_cstr(&buf2), "shoop da 23 ");
+
+ git_str_printf(&buf2, "%s %d", "woop", 42);
+ cl_assert(git_str_oom(&buf2) == 0);
+ cl_assert_equal_s(git_str_cstr(&buf2), "shoop da 23 woop 42");
+ git_str_dispose(&buf2);
+}
diff --git a/tests/util/str/oom.c b/tests/util/str/oom.c
new file mode 100644
index 0000000..810c1f2
--- /dev/null
+++ b/tests/util/str/oom.c
@@ -0,0 +1,71 @@
+#include "clar_libgit2.h"
+#include "clar_libgit2_alloc.h"
+
+/* Override default allocators with ones that will fail predictably. */
+
+static git_allocator std_alloc;
+static git_allocator oom_alloc;
+
+static void *oom_malloc(size_t n, const char *file, int line)
+{
+ /* Reject any allocation of more than 100 bytes */
+ return (n > 100) ? NULL : std_alloc.gmalloc(n, file, line);
+}
+
+static void *oom_realloc(void *p, size_t n, const char *file, int line)
+{
+ /* Reject any allocation of more than 100 bytes */
+ return (n > 100) ? NULL : std_alloc.grealloc(p, n, file, line);
+}
+
+void test_str_oom__initialize(void)
+{
+ git_stdalloc_init_allocator(&std_alloc);
+ git_stdalloc_init_allocator(&oom_alloc);
+
+ oom_alloc.gmalloc = oom_malloc;
+ oom_alloc.grealloc = oom_realloc;
+
+ cl_git_pass(git_allocator_setup(&oom_alloc));
+}
+
+void test_str_oom__cleanup(void)
+{
+ cl_git_pass(git_allocator_setup(NULL));
+}
+
+void test_str_oom__grow(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ cl_git_pass(git_str_grow(&buf, 42));
+ cl_assert(!git_str_oom(&buf));
+
+ cl_assert(git_str_grow(&buf, 101) == -1);
+ cl_assert(git_str_oom(&buf));
+
+ git_str_dispose(&buf);
+}
+
+void test_str_oom__grow_by(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ cl_git_pass(git_str_grow_by(&buf, 42));
+ cl_assert(!git_str_oom(&buf));
+
+ cl_assert(git_str_grow_by(&buf, 101) == -1);
+ cl_assert(git_str_oom(&buf));
+}
+
+void test_str_oom__allocation_failure(void)
+{
+ git_str buf = GIT_STR_INIT;
+
+ cl_alloc_limit(10);
+
+ cl_git_pass(git_str_puts(&buf, "foobar"));
+ cl_git_fail(git_str_puts(&buf, "foobar"));
+
+ cl_alloc_reset();
+}
diff --git a/tests/util/str/percent.c b/tests/util/str/percent.c
new file mode 100644
index 0000000..3393890
--- /dev/null
+++ b/tests/util/str/percent.c
@@ -0,0 +1,48 @@
+#include "clar_libgit2.h"
+
+static void expect_decode_pass(const char *expected, const char *encoded)
+{
+ git_str in = GIT_STR_INIT, out = GIT_STR_INIT;
+
+ /*
+ * ensure that we only read the given length of the input buffer
+ * by putting garbage at the end. this will ensure that we do
+ * not, eg, rely on nul-termination or walk off the end of the buf.
+ */
+ cl_git_pass(git_str_puts(&in, encoded));
+ cl_git_pass(git_str_PUTS(&in, "TRAILER"));
+
+ cl_git_pass(git_str_decode_percent(&out, in.ptr, strlen(encoded)));
+
+ cl_assert_equal_s(expected, git_str_cstr(&out));
+ cl_assert_equal_i(strlen(expected), git_str_len(&out));
+
+ git_str_dispose(&in);
+ git_str_dispose(&out);
+}
+
+void test_str_percent__decode_succeeds(void)
+{
+ expect_decode_pass("", "");
+ expect_decode_pass(" ", "%20");
+ expect_decode_pass("a", "a");
+ expect_decode_pass(" a", "%20a");
+ expect_decode_pass("a ", "a%20");
+ expect_decode_pass("github.com", "github.com");
+ expect_decode_pass("github.com", "githu%62.com");
+ expect_decode_pass("github.com", "github%2ecom");
+ expect_decode_pass("foo bar baz", "foo%20bar%20baz");
+ expect_decode_pass("foo bar baz", "foo%20bar%20baz");
+ expect_decode_pass("foo bar ", "foo%20bar%20");
+}
+
+void test_str_percent__ignores_invalid(void)
+{
+ expect_decode_pass("githu%%.com", "githu%%.com");
+ expect_decode_pass("github.co%2", "github.co%2");
+ expect_decode_pass("github%2.com", "github%2.com");
+ expect_decode_pass("githu%2z.com", "githu%2z.com");
+ expect_decode_pass("github.co%9z", "github.co%9z");
+ expect_decode_pass("github.co%2", "github.co%2");
+ expect_decode_pass("github.co%", "github.co%");
+}
diff --git a/tests/util/str/quote.c b/tests/util/str/quote.c
new file mode 100644
index 0000000..2c65462
--- /dev/null
+++ b/tests/util/str/quote.c
@@ -0,0 +1,87 @@
+#include "clar_libgit2.h"
+
+static void expect_quote_pass(const char *expected, const char *str)
+{
+ git_str buf = GIT_STR_INIT;
+
+ cl_git_pass(git_str_puts(&buf, str));
+ cl_git_pass(git_str_quote(&buf));
+
+ cl_assert_equal_s(expected, git_str_cstr(&buf));
+ cl_assert_equal_i(strlen(expected), git_str_len(&buf));
+
+ git_str_dispose(&buf);
+}
+
+void test_str_quote__quote_succeeds(void)
+{
+ expect_quote_pass("", "");
+ expect_quote_pass("foo", "foo");
+ expect_quote_pass("foo/bar/baz.c", "foo/bar/baz.c");
+ expect_quote_pass("foo bar", "foo bar");
+ expect_quote_pass("\"\\\"leading quote\"", "\"leading quote");
+ expect_quote_pass("\"slash\\\\y\"", "slash\\y");
+ expect_quote_pass("\"foo\\r\\nbar\"", "foo\r\nbar");
+ expect_quote_pass("\"foo\\177bar\"", "foo\177bar");
+ expect_quote_pass("\"foo\\001bar\"", "foo\001bar");
+ expect_quote_pass("\"foo\\377bar\"", "foo\377bar");
+}
+
+static void expect_unquote_pass(const char *expected, const char *quoted)
+{
+ git_str buf = GIT_STR_INIT;
+
+ cl_git_pass(git_str_puts(&buf, quoted));
+ cl_git_pass(git_str_unquote(&buf));
+
+ cl_assert_equal_s(expected, git_str_cstr(&buf));
+ cl_assert_equal_i(strlen(expected), git_str_len(&buf));
+
+ git_str_dispose(&buf);
+}
+
+static void expect_unquote_fail(const char *quoted)
+{
+ git_str buf = GIT_STR_INIT;
+
+ cl_git_pass(git_str_puts(&buf, quoted));
+ cl_git_fail(git_str_unquote(&buf));
+
+ git_str_dispose(&buf);
+}
+
+void test_str_quote__unquote_succeeds(void)
+{
+ expect_unquote_pass("", "\"\"");
+ expect_unquote_pass(" ", "\" \"");
+ expect_unquote_pass("foo", "\"foo\"");
+ expect_unquote_pass("foo bar", "\"foo bar\"");
+ expect_unquote_pass("foo\"bar", "\"foo\\\"bar\"");
+ expect_unquote_pass("foo\\bar", "\"foo\\\\bar\"");
+ expect_unquote_pass("foo\tbar", "\"foo\\tbar\"");
+ expect_unquote_pass("\vfoo\tbar\n", "\"\\vfoo\\tbar\\n\"");
+ expect_unquote_pass("foo\nbar", "\"foo\\012bar\"");
+ expect_unquote_pass("foo\r\nbar", "\"foo\\015\\012bar\"");
+ expect_unquote_pass("foo\r\nbar", "\"\\146\\157\\157\\015\\012\\142\\141\\162\"");
+ expect_unquote_pass("newline: \n", "\"newline: \\012\"");
+ expect_unquote_pass("0xff: \377", "\"0xff: \\377\"");
+}
+
+void test_str_quote__unquote_fails(void)
+{
+ expect_unquote_fail("no quotes at all");
+ expect_unquote_fail("\"no trailing quote");
+ expect_unquote_fail("no leading quote\"");
+ expect_unquote_fail("\"invalid \\z escape char\"");
+ expect_unquote_fail("\"\\q invalid escape char\"");
+ expect_unquote_fail("\"invalid escape char \\p\"");
+ expect_unquote_fail("\"invalid \\1 escape char \"");
+ expect_unquote_fail("\"invalid \\14 escape char \"");
+ expect_unquote_fail("\"invalid \\280 escape char\"");
+ expect_unquote_fail("\"invalid \\378 escape char\"");
+ expect_unquote_fail("\"invalid \\380 escape char\"");
+ expect_unquote_fail("\"invalid \\411 escape char\"");
+ expect_unquote_fail("\"truncated escape char \\\"");
+ expect_unquote_fail("\"truncated escape char \\0\"");
+ expect_unquote_fail("\"truncated escape char \\01\"");
+}
diff --git a/tests/util/str/splice.c b/tests/util/str/splice.c
new file mode 100644
index 0000000..14e844e
--- /dev/null
+++ b/tests/util/str/splice.c
@@ -0,0 +1,92 @@
+#include "clar_libgit2.h"
+
+static git_str _buf;
+
+void test_str_splice__initialize(void) {
+ git_str_init(&_buf, 16);
+}
+
+void test_str_splice__cleanup(void) {
+ git_str_dispose(&_buf);
+}
+
+void test_str_splice__preprend(void)
+{
+ git_str_sets(&_buf, "world!");
+
+ cl_git_pass(git_str_splice(&_buf, 0, 0, "Hello Dolly", strlen("Hello ")));
+
+ cl_assert_equal_s("Hello world!", git_str_cstr(&_buf));
+}
+
+void test_str_splice__append(void)
+{
+ git_str_sets(&_buf, "Hello");
+
+ cl_git_pass(git_str_splice(&_buf, git_str_len(&_buf), 0, " world!", strlen(" world!")));
+
+ cl_assert_equal_s("Hello world!", git_str_cstr(&_buf));
+}
+
+void test_str_splice__insert_at(void)
+{
+ git_str_sets(&_buf, "Hell world!");
+
+ cl_git_pass(git_str_splice(&_buf, strlen("Hell"), 0, "o", strlen("o")));
+
+ cl_assert_equal_s("Hello world!", git_str_cstr(&_buf));
+}
+
+void test_str_splice__remove_at(void)
+{
+ git_str_sets(&_buf, "Hello world of warcraft!");
+
+ cl_git_pass(git_str_splice(&_buf, strlen("Hello world"), strlen(" of warcraft"), "", 0));
+
+ cl_assert_equal_s("Hello world!", git_str_cstr(&_buf));
+}
+
+void test_str_splice__replace(void)
+{
+ git_str_sets(&_buf, "Hell0 w0rld!");
+
+ cl_git_pass(git_str_splice(&_buf, strlen("Hell"), strlen("0 w0"), "o wo", strlen("o wo")));
+
+ cl_assert_equal_s("Hello world!", git_str_cstr(&_buf));
+}
+
+void test_str_splice__replace_with_longer(void)
+{
+ git_str_sets(&_buf, "Hello you!");
+
+ cl_git_pass(git_str_splice(&_buf, strlen("Hello "), strlen("you"), "world", strlen("world")));
+
+ cl_assert_equal_s("Hello world!", git_str_cstr(&_buf));
+}
+
+void test_str_splice__replace_with_shorter(void)
+{
+ git_str_sets(&_buf, "Brave new world!");
+
+ cl_git_pass(git_str_splice(&_buf, 0, strlen("Brave new"), "Hello", strlen("Hello")));
+
+ cl_assert_equal_s("Hello world!", git_str_cstr(&_buf));
+}
+
+void test_str_splice__truncate(void)
+{
+ git_str_sets(&_buf, "Hello world!!");
+
+ cl_git_pass(git_str_splice(&_buf, strlen("Hello world!"), strlen("!"), "", 0));
+
+ cl_assert_equal_s("Hello world!", git_str_cstr(&_buf));
+}
+
+void test_str_splice__dont_do_anything(void)
+{
+ git_str_sets(&_buf, "Hello world!");
+
+ cl_git_pass(git_str_splice(&_buf, 3, 0, "Hello", 0));
+
+ cl_assert_equal_s("Hello world!", git_str_cstr(&_buf));
+}
diff --git a/tests/util/string.c b/tests/util/string.c
new file mode 100644
index 0000000..de04dea
--- /dev/null
+++ b/tests/util/string.c
@@ -0,0 +1,136 @@
+#include "clar_libgit2.h"
+
+/* compare prefixes */
+void test_string__0(void)
+{
+ cl_assert(git__prefixcmp("", "") == 0);
+ cl_assert(git__prefixcmp("a", "") == 0);
+ cl_assert(git__prefixcmp("", "a") < 0);
+ cl_assert(git__prefixcmp("a", "b") < 0);
+ cl_assert(git__prefixcmp("b", "a") > 0);
+ cl_assert(git__prefixcmp("ab", "a") == 0);
+ cl_assert(git__prefixcmp("ab", "ac") < 0);
+ cl_assert(git__prefixcmp("ab", "aa") > 0);
+}
+
+/* compare suffixes */
+void test_string__1(void)
+{
+ cl_assert(git__suffixcmp("", "") == 0);
+ cl_assert(git__suffixcmp("a", "") == 0);
+ cl_assert(git__suffixcmp("", "a") < 0);
+ cl_assert(git__suffixcmp("a", "b") < 0);
+ cl_assert(git__suffixcmp("b", "a") > 0);
+ cl_assert(git__suffixcmp("ba", "a") == 0);
+ cl_assert(git__suffixcmp("zaa", "ac") < 0);
+ cl_assert(git__suffixcmp("zaz", "ac") > 0);
+}
+
+/* compare icase sorting with case equality */
+void test_string__2(void)
+{
+ cl_assert(git__strcasesort_cmp("", "") == 0);
+ cl_assert(git__strcasesort_cmp("foo", "foo") == 0);
+ cl_assert(git__strcasesort_cmp("foo", "bar") > 0);
+ cl_assert(git__strcasesort_cmp("bar", "foo") < 0);
+ cl_assert(git__strcasesort_cmp("foo", "FOO") > 0);
+ cl_assert(git__strcasesort_cmp("FOO", "foo") < 0);
+ cl_assert(git__strcasesort_cmp("foo", "BAR") > 0);
+ cl_assert(git__strcasesort_cmp("BAR", "foo") < 0);
+ cl_assert(git__strcasesort_cmp("fooBar", "foobar") < 0);
+}
+
+/* compare prefixes with len */
+void test_string__prefixncmp(void)
+{
+ cl_assert(git__prefixncmp("", 0, "") == 0);
+ cl_assert(git__prefixncmp("a", 1, "") == 0);
+ cl_assert(git__prefixncmp("", 0, "a") < 0);
+ cl_assert(git__prefixncmp("a", 1, "b") < 0);
+ cl_assert(git__prefixncmp("b", 1, "a") > 0);
+ cl_assert(git__prefixncmp("ab", 2, "a") == 0);
+ cl_assert(git__prefixncmp("ab", 1, "a") == 0);
+ cl_assert(git__prefixncmp("ab", 2, "ac") < 0);
+ cl_assert(git__prefixncmp("a", 1, "ac") < 0);
+ cl_assert(git__prefixncmp("ab", 1, "ac") < 0);
+ cl_assert(git__prefixncmp("ab", 2, "aa") > 0);
+ cl_assert(git__prefixncmp("ab", 1, "aa") < 0);
+}
+
+/* compare prefixes with len */
+void test_string__prefixncmp_icase(void)
+{
+ cl_assert(git__prefixncmp_icase("", 0, "") == 0);
+ cl_assert(git__prefixncmp_icase("a", 1, "") == 0);
+ cl_assert(git__prefixncmp_icase("", 0, "a") < 0);
+ cl_assert(git__prefixncmp_icase("a", 1, "b") < 0);
+ cl_assert(git__prefixncmp_icase("A", 1, "b") < 0);
+ cl_assert(git__prefixncmp_icase("a", 1, "B") < 0);
+ cl_assert(git__prefixncmp_icase("b", 1, "a") > 0);
+ cl_assert(git__prefixncmp_icase("B", 1, "a") > 0);
+ cl_assert(git__prefixncmp_icase("b", 1, "A") > 0);
+ cl_assert(git__prefixncmp_icase("ab", 2, "a") == 0);
+ cl_assert(git__prefixncmp_icase("Ab", 2, "a") == 0);
+ cl_assert(git__prefixncmp_icase("ab", 2, "A") == 0);
+ cl_assert(git__prefixncmp_icase("ab", 1, "a") == 0);
+ cl_assert(git__prefixncmp_icase("ab", 2, "ac") < 0);
+ cl_assert(git__prefixncmp_icase("Ab", 2, "ac") < 0);
+ cl_assert(git__prefixncmp_icase("ab", 2, "Ac") < 0);
+ cl_assert(git__prefixncmp_icase("a", 1, "ac") < 0);
+ cl_assert(git__prefixncmp_icase("ab", 1, "ac") < 0);
+ cl_assert(git__prefixncmp_icase("ab", 2, "aa") > 0);
+ cl_assert(git__prefixncmp_icase("ab", 1, "aa") < 0);
+}
+
+void test_string__strcmp(void)
+{
+ cl_assert(git__strcmp("", "") == 0);
+ cl_assert(git__strcmp("foo", "foo") == 0);
+ cl_assert(git__strcmp("Foo", "foo") < 0);
+ cl_assert(git__strcmp("foo", "FOO") > 0);
+ cl_assert(git__strcmp("foo", "fOO") > 0);
+
+ cl_assert(strcmp("rt\303\202of", "rt dev\302\266h") > 0);
+ cl_assert(strcmp("e\342\202\254ghi=", "et") > 0);
+ cl_assert(strcmp("rt dev\302\266h", "rt\303\202of") < 0);
+ cl_assert(strcmp("et", "e\342\202\254ghi=") < 0);
+ cl_assert(strcmp("\303\215", "\303\255") < 0);
+
+ cl_assert(git__strcmp("rt\303\202of", "rt dev\302\266h") > 0);
+ cl_assert(git__strcmp("e\342\202\254ghi=", "et") > 0);
+ cl_assert(git__strcmp("rt dev\302\266h", "rt\303\202of") < 0);
+ cl_assert(git__strcmp("et", "e\342\202\254ghi=") < 0);
+ cl_assert(git__strcmp("\303\215", "\303\255") < 0);
+}
+
+void test_string__strcasecmp(void)
+{
+ cl_assert(git__strcasecmp("", "") == 0);
+ cl_assert(git__strcasecmp("foo", "foo") == 0);
+ cl_assert(git__strcasecmp("foo", "Foo") == 0);
+ cl_assert(git__strcasecmp("foo", "FOO") == 0);
+ cl_assert(git__strcasecmp("foo", "fOO") == 0);
+
+ cl_assert(strcasecmp("rt\303\202of", "rt dev\302\266h") > 0);
+ cl_assert(strcasecmp("e\342\202\254ghi=", "et") > 0);
+ cl_assert(strcasecmp("rt dev\302\266h", "rt\303\202of") < 0);
+ cl_assert(strcasecmp("et", "e\342\202\254ghi=") < 0);
+ cl_assert(strcasecmp("\303\215", "\303\255") < 0);
+
+ cl_assert(git__strcasecmp("rt\303\202of", "rt dev\302\266h") > 0);
+ cl_assert(git__strcasecmp("e\342\202\254ghi=", "et") > 0);
+ cl_assert(git__strcasecmp("rt dev\302\266h", "rt\303\202of") < 0);
+ cl_assert(git__strcasecmp("et", "e\342\202\254ghi=") < 0);
+ cl_assert(git__strcasecmp("\303\215", "\303\255") < 0);
+}
+
+void test_string__strlcmp(void)
+{
+ const char foo[3] = { 'f', 'o', 'o' };
+
+ cl_assert(git__strlcmp("foo", "foo", 3) == 0);
+ cl_assert(git__strlcmp("foo", foo, 3) == 0);
+ cl_assert(git__strlcmp("foo", "foobar", 3) == 0);
+ cl_assert(git__strlcmp("foobar", "foo", 3) > 0);
+ cl_assert(git__strlcmp("foo", "foobar", 6) < 0);
+}
diff --git a/tests/util/strmap.c b/tests/util/strmap.c
new file mode 100644
index 0000000..c4f5c86
--- /dev/null
+++ b/tests/util/strmap.c
@@ -0,0 +1,190 @@
+#include "clar_libgit2.h"
+#include "strmap.h"
+
+static git_strmap *g_table;
+
+void test_strmap__initialize(void)
+{
+ cl_git_pass(git_strmap_new(&g_table));
+ cl_assert(g_table != NULL);
+}
+
+void test_strmap__cleanup(void)
+{
+ git_strmap_free(g_table);
+}
+
+void test_strmap__0(void)
+{
+ cl_assert(git_strmap_size(g_table) == 0);
+}
+
+static void insert_strings(git_strmap *table, size_t count)
+{
+ size_t i, j, over;
+ char *str;
+
+ for (i = 0; i < count; ++i) {
+ str = malloc(10);
+ for (j = 0; j < 10; ++j)
+ str[j] = 'a' + (i % 26);
+ str[9] = '\0';
+
+ /* if > 26, then encode larger value in first letters */
+ for (j = 0, over = i / 26; over > 0; j++, over = over / 26)
+ str[j] = 'A' + (over % 26);
+
+ cl_git_pass(git_strmap_set(table, str, str));
+ }
+
+ cl_assert_equal_i(git_strmap_size(table), count);
+}
+
+void test_strmap__inserted_strings_can_be_retrieved(void)
+{
+ char *str;
+ int i;
+
+ insert_strings(g_table, 20);
+
+ cl_assert(git_strmap_exists(g_table, "aaaaaaaaa"));
+ cl_assert(git_strmap_exists(g_table, "ggggggggg"));
+ cl_assert(!git_strmap_exists(g_table, "aaaaaaaab"));
+ cl_assert(!git_strmap_exists(g_table, "abcdefghi"));
+
+ i = 0;
+ git_strmap_foreach_value(g_table, str, { i++; free(str); });
+ cl_assert(i == 20);
+}
+
+void test_strmap__deleted_entry_cannot_be_retrieved(void)
+{
+ char *str;
+ int i;
+
+ insert_strings(g_table, 20);
+
+ cl_assert(git_strmap_exists(g_table, "bbbbbbbbb"));
+ str = git_strmap_get(g_table, "bbbbbbbbb");
+ cl_assert_equal_s(str, "bbbbbbbbb");
+ cl_git_pass(git_strmap_delete(g_table, "bbbbbbbbb"));
+ free(str);
+
+ cl_assert(!git_strmap_exists(g_table, "bbbbbbbbb"));
+
+ i = 0;
+ git_strmap_foreach_value(g_table, str, { i++; free(str); });
+ cl_assert_equal_i(i, 19);
+}
+
+void test_strmap__inserting_many_keys_succeeds(void)
+{
+ char *str;
+ int i;
+
+ insert_strings(g_table, 10000);
+
+ i = 0;
+ git_strmap_foreach_value(g_table, str, { i++; free(str); });
+ cl_assert_equal_i(i, 10000);
+}
+
+void test_strmap__get_succeeds_with_existing_entries(void)
+{
+ const char *keys[] = { "foo", "bar", "gobble" };
+ char *values[] = { "oof", "rab", "elbbog" };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(keys); i++)
+ cl_git_pass(git_strmap_set(g_table, keys[i], values[i]));
+
+ cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof");
+ cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab");
+ cl_assert_equal_s(git_strmap_get(g_table, "gobble"), "elbbog");
+}
+
+void test_strmap__get_returns_null_on_nonexisting_key(void)
+{
+ const char *keys[] = { "foo", "bar", "gobble" };
+ char *values[] = { "oof", "rab", "elbbog" };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(keys); i++)
+ cl_git_pass(git_strmap_set(g_table, keys[i], values[i]));
+
+ cl_assert_equal_p(git_strmap_get(g_table, "other"), NULL);
+}
+
+void test_strmap__set_persists_key(void)
+{
+ cl_git_pass(git_strmap_set(g_table, "foo", "oof"));
+ cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof");
+}
+
+void test_strmap__set_persists_multpile_keys(void)
+{
+ cl_git_pass(git_strmap_set(g_table, "foo", "oof"));
+ cl_git_pass(git_strmap_set(g_table, "bar", "rab"));
+ cl_assert_equal_s(git_strmap_get(g_table, "foo"), "oof");
+ cl_assert_equal_s(git_strmap_get(g_table, "bar"), "rab");
+}
+
+void test_strmap__set_updates_existing_key(void)
+{
+ cl_git_pass(git_strmap_set(g_table, "foo", "oof"));
+ cl_git_pass(git_strmap_set(g_table, "bar", "rab"));
+ cl_git_pass(git_strmap_set(g_table, "gobble", "elbbog"));
+ cl_assert_equal_i(git_strmap_size(g_table), 3);
+
+ cl_git_pass(git_strmap_set(g_table, "foo", "other"));
+ cl_assert_equal_i(git_strmap_size(g_table), 3);
+
+ cl_assert_equal_s(git_strmap_get(g_table, "foo"), "other");
+}
+
+void test_strmap__iteration(void)
+{
+ struct {
+ char *key;
+ char *value;
+ int seen;
+ } entries[] = {
+ { "foo", "oof" },
+ { "bar", "rab" },
+ { "gobble", "elbbog" },
+ };
+ const char *key, *value;
+ size_t i, n;
+
+ for (i = 0; i < ARRAY_SIZE(entries); i++)
+ cl_git_pass(git_strmap_set(g_table, entries[i].key, entries[i].value));
+
+ i = 0, n = 0;
+ while (git_strmap_iterate((void **) &value, g_table, &i, &key) == 0) {
+ size_t j;
+
+ for (j = 0; j < ARRAY_SIZE(entries); j++) {
+ if (strcmp(entries[j].key, key))
+ continue;
+
+ cl_assert_equal_i(entries[j].seen, 0);
+ cl_assert_equal_s(entries[j].value, value);
+ entries[j].seen++;
+ break;
+ }
+
+ n++;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(entries); i++)
+ cl_assert_equal_i(entries[i].seen, 1);
+
+ cl_assert_equal_i(n, ARRAY_SIZE(entries));
+}
+
+void test_strmap__iterating_empty_map_stops_immediately(void)
+{
+ size_t i = 0;
+
+ cl_git_fail_with(git_strmap_iterate(NULL, g_table, &i, NULL), GIT_ITEROVER);
+}
diff --git a/tests/util/strtol.c b/tests/util/strtol.c
new file mode 100644
index 0000000..54c63ca
--- /dev/null
+++ b/tests/util/strtol.c
@@ -0,0 +1,128 @@
+#include "clar_libgit2.h"
+
+static void assert_l32_parses(const char *string, int32_t expected, int base)
+{
+ int32_t i;
+ cl_git_pass(git__strntol32(&i, string, strlen(string), NULL, base));
+ cl_assert_equal_i(i, expected);
+}
+
+static void assert_l32_fails(const char *string, int base)
+{
+ int32_t i;
+ cl_git_fail(git__strntol32(&i, string, strlen(string), NULL, base));
+}
+
+static void assert_l64_parses(const char *string, int64_t expected, int base)
+{
+ int64_t i;
+ cl_git_pass(git__strntol64(&i, string, strlen(string), NULL, base));
+ cl_assert_equal_i(i, expected);
+}
+
+static void assert_l64_fails(const char *string, int base)
+{
+ int64_t i;
+ cl_git_fail(git__strntol64(&i, string, strlen(string), NULL, base));
+}
+
+void test_strtol__int32(void)
+{
+ assert_l32_parses("123", 123, 10);
+ assert_l32_parses(" +123 ", 123, 10);
+ assert_l32_parses(" -123 ", -123, 10);
+ assert_l32_parses(" +2147483647 ", 2147483647, 10);
+ assert_l32_parses(" -2147483648 ", INT64_C(-2147483648), 10);
+ assert_l32_parses("A", 10, 16);
+ assert_l32_parses("1x1", 1, 10);
+
+ assert_l32_fails("", 10);
+ assert_l32_fails("a", 10);
+ assert_l32_fails("x10x", 10);
+ assert_l32_fails(" 2147483657 ", 10);
+ assert_l32_fails(" -2147483657 ", 10);
+}
+
+void test_strtol__int64(void)
+{
+ assert_l64_parses("123", 123, 10);
+ assert_l64_parses(" +123 ", 123, 10);
+ assert_l64_parses(" -123 ", -123, 10);
+ assert_l64_parses(" +2147483647 ", 2147483647, 10);
+ assert_l64_parses(" -2147483648 ", INT64_C(-2147483648), 10);
+ assert_l64_parses(" 2147483657 ", INT64_C(2147483657), 10);
+ assert_l64_parses(" -2147483657 ", INT64_C(-2147483657), 10);
+ assert_l64_parses(" 9223372036854775807 ", INT64_MAX, 10);
+ assert_l64_parses(" -9223372036854775808 ", INT64_MIN, 10);
+ assert_l64_parses(" 0x7fffffffffffffff ", INT64_MAX, 16);
+ assert_l64_parses(" -0x8000000000000000 ", INT64_MIN, 16);
+ assert_l64_parses("1a", 26, 16);
+ assert_l64_parses("1A", 26, 16);
+
+ assert_l64_fails("", 10);
+ assert_l64_fails("a", 10);
+ assert_l64_fails("x10x", 10);
+ assert_l64_fails("0x8000000000000000", 16);
+ assert_l64_fails("-0x8000000000000001", 16);
+}
+
+void test_strtol__base_autodetection(void)
+{
+ assert_l64_parses("0", 0, 0);
+ assert_l64_parses("00", 0, 0);
+ assert_l64_parses("0x", 0, 0);
+ assert_l64_parses("0foobar", 0, 0);
+ assert_l64_parses("07", 7, 0);
+ assert_l64_parses("017", 15, 0);
+ assert_l64_parses("0x8", 8, 0);
+ assert_l64_parses("0x18", 24, 0);
+}
+
+void test_strtol__buffer_length_with_autodetection_truncates(void)
+{
+ int64_t i64;
+
+ cl_git_pass(git__strntol64(&i64, "011", 2, NULL, 0));
+ cl_assert_equal_i(i64, 1);
+ cl_git_pass(git__strntol64(&i64, "0x11", 3, NULL, 0));
+ cl_assert_equal_i(i64, 1);
+}
+
+void test_strtol__buffer_length_truncates(void)
+{
+ int32_t i32;
+ int64_t i64;
+
+ cl_git_pass(git__strntol32(&i32, "11", 1, NULL, 10));
+ cl_assert_equal_i(i32, 1);
+
+ cl_git_pass(git__strntol64(&i64, "11", 1, NULL, 10));
+ cl_assert_equal_i(i64, 1);
+}
+
+void test_strtol__buffer_length_with_leading_ws_truncates(void)
+{
+ int64_t i64;
+
+ cl_git_fail(git__strntol64(&i64, " 1", 1, NULL, 10));
+
+ cl_git_pass(git__strntol64(&i64, " 11", 2, NULL, 10));
+ cl_assert_equal_i(i64, 1);
+}
+
+void test_strtol__buffer_length_with_leading_sign_truncates(void)
+{
+ int64_t i64;
+
+ cl_git_fail(git__strntol64(&i64, "-1", 1, NULL, 10));
+
+ cl_git_pass(git__strntol64(&i64, "-11", 2, NULL, 10));
+ cl_assert_equal_i(i64, -1);
+}
+
+void test_strtol__error_message_cuts_off(void)
+{
+ assert_l32_fails("2147483657foobar", 10);
+ cl_assert(strstr(git_error_last()->message, "2147483657") != NULL);
+ cl_assert(strstr(git_error_last()->message, "foobar") == NULL);
+}
diff --git a/tests/util/url/http.c b/tests/util/url/http.c
new file mode 100644
index 0000000..8823889
--- /dev/null
+++ b/tests/util/url/http.c
@@ -0,0 +1,752 @@
+#include "clar_libgit2.h"
+#include "net.h"
+
+static git_net_url conndata;
+
+void test_url_http__initialize(void)
+{
+ memset(&conndata, 0, sizeof(conndata));
+}
+
+void test_url_http__cleanup(void)
+{
+ git_net_url_dispose(&conndata);
+}
+
+/* Hostname */
+
+void test_url_http__has_scheme(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "http://example.com/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__no_scheme(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "example.com/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__hostname_root(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "example.com/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__hostname_implied_root(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "example.com"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__hostname_numeric(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "8888888/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "8888888");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__hostname_implied_root_custom_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "example.com:42"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "42");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__hostname_implied_root_empty_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "example.com:"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__hostname_encoded_password(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass%2fis%40bad@hostname.com:1234/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "hostname.com");
+ cl_assert_equal_s(conndata.port, "1234");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass/is@bad");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__hostname_user(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user@example.com/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__hostname_user_pass(void)
+{
+ /* user:pass@hostname.tld/resource */
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass@example.com/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__hostname_port(void)
+{
+ /* hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "example.com:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__hostname_empty_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "example.com:/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__hostname_user_port(void)
+{
+ /* user@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user@example.com:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__hostname_user_pass_port(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass@example.com:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__hostname_user_pass_port_query(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass@example.com:9191/resource?query=q&foo=bar&z=asdf"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_s(conndata.query, "query=q&foo=bar&z=asdf");
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__hostname_user_pass_port_fragment(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass@example.com:9191/resource#fragment"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_s(conndata.fragment, "fragment");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__hostname_user_pass_port_query_fragment(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass@example.com:9191/resource?query=q&foo=bar&z=asdf#fragment"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_s(conndata.query, "query=q&foo=bar&z=asdf");
+ cl_assert_equal_s(conndata.fragment, "fragment");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__fragment_with_question_mark(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass@example.com:9191/resource#fragment_with?question_mark"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_s(conndata.fragment, "fragment_with?question_mark");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+/* IPv4 addresses */
+
+void test_url_http__ipv4_trivial(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "192.168.1.1/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv4_root(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "192.168.1.1/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv4_implied_root(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "192.168.1.1"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv4_implied_root_custom_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "192.168.1.1:42"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "42");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__ipv4_implied_root_empty_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "192.168.1.1:"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv4_encoded_password(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass%2fis%40bad@192.168.1.1:1234/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "1234");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass/is@bad");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__ipv4_user(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user@192.168.1.1/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv4_user_pass(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass@192.168.1.1/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv4_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "192.168.1.1:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__ipv4_empty_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "192.168.1.1:/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv4_user_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user@192.168.1.1:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__ipv4_user_pass_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass@192.168.1.1:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+/* IPv6 addresses */
+
+void test_url_http__ipv6_trivial(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "[fe80::dcad:beff:fe00:0001]/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv6_root(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "[fe80::dcad:beff:fe00:0001]/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv6_implied_root(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "[fe80::dcad:beff:fe00:0001]"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv6_implied_root_custom_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "[fe80::dcad:beff:fe00:0001]:42"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "42");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__ipv6_implied_root_empty_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "[fe80::dcad:beff:fe00:0001]:"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv6_encoded_password(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass%2fis%40bad@[fe80::dcad:beff:fe00:0001]:1234/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "1234");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass/is@bad");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__ipv6_user(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user@[fe80::dcad:beff:fe00:0001]/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv6_user_pass(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass@[fe80::dcad:beff:fe00:0001]/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv6_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "[fe80::dcad:beff:fe00:0001]:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__ipv6_empty_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "[fe80::dcad:beff:fe00:0001]:/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__ipv6_user_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user@[fe80::dcad:beff:fe00:0001]:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__ipv6_user_pass_port(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata,
+ "user:pass@[fe80::dcad:beff:fe00:0001]:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_http__ipv6_invalid_addresses(void)
+{
+ /* Opening bracket missing */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001]/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001]/"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001]"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001]:42"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001]:"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user:pass%2fis%40bad@fe80::dcad:beff:fe00:0001]:1234/"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user@fe80::dcad:beff:fe00:0001]/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user:pass@fe80::dcad:beff:fe00:0001]/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001]:9191/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001]:/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user@fe80::dcad:beff:fe00:0001]:9191/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user:pass@fe80::dcad:beff:fe00:0001]:9191/resource"));
+
+ /* Closing bracket missing */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "[fe80::dcad:beff:fe00:0001/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "[fe80::dcad:beff:fe00:0001/"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "[fe80::dcad:beff:fe00:0001"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "[fe80::dcad:beff:fe00:0001:42"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "[fe80::dcad:beff:fe00:0001:"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user:pass%2fis%40bad@[fe80::dcad:beff:fe00:0001:1234/"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user@[fe80::dcad:beff:fe00:0001/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user:pass@[fe80::dcad:beff:fe00:0001/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "[fe80::dcad:beff:fe00:0001:9191/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "[fe80::dcad:beff:fe00:0001:/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user@[fe80::dcad:beff:fe00:0001:9191/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user:pass@[fe80::dcad:beff:fe00:0001:9191/resource"));
+
+ /* Both brackets missing */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001/"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001:42"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001:"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user:pass%2fis%40bad@fe80::dcad:beff:fe00:0001:1234/"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user@fe80::dcad:beff:fe00:0001/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user:pass@fe80::dcad:beff:fe00:0001/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001:9191/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::dcad:beff:fe00:0001:/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user@fe80::dcad:beff:fe00:0001:9191/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "user:pass@fe80::dcad:beff:fe00:0001:9191/resource"));
+
+ /* Invalid character inside address */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, "[fe8o::dcad:beff:fe00:0001]/resource"));
+
+ /* Characters before/after braces */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "fe80::[dcad:beff:fe00:0001]/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "cafe[fe80::dcad:beff:fe00:0001]/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata,
+ "[fe80::dcad:beff:fe00:0001]cafe/resource"));
+}
+
+/* Oddities */
+
+void test_url_http__invalid_scheme_is_relative(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "foo!bar://host:42/path/to/project?query_string=yes"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "foo!bar");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "//host:42/path/to/project");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_s(conndata.query, "query_string=yes");
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__scheme_case_is_normalized(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "GIT+SSH://host:42/path/to/project"));
+ cl_assert_equal_s(conndata.scheme, "git+ssh");
+}
+
+void test_url_http__no_scheme_relative_path(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "path"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "path");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__no_scheme_absolute_path(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "/path"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/path");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__empty_path_with_empty_authority(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, ""));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_http__spaces_in_the_name(void)
+{
+ cl_git_pass(git_net_url_parse_http(&conndata, "libgit2@dev.azure.com/libgit2/test/_git/spaces%20in%20the%20name"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "dev.azure.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/libgit2/test/_git/spaces%20in%20the%20name");
+ cl_assert_equal_s(conndata.username, "libgit2");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
diff --git a/tests/util/url/joinpath.c b/tests/util/url/joinpath.c
new file mode 100644
index 0000000..6027093
--- /dev/null
+++ b/tests/util/url/joinpath.c
@@ -0,0 +1,193 @@
+#include "clar_libgit2.h"
+#include "net.h"
+
+static git_net_url source, target;
+
+void test_url_joinpath__initialize(void)
+{
+ memset(&source, 0, sizeof(source));
+ memset(&target, 0, sizeof(target));
+}
+
+void test_url_joinpath__cleanup(void)
+{
+ git_net_url_dispose(&source);
+ git_net_url_dispose(&target);
+}
+
+void test_url_joinpath__target_paths_and_queries(void)
+{
+ cl_git_pass(git_net_url_parse(&source, "http://example.com/a/b"));
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d"));
+ cl_assert_equal_s(target.path, "/a/b/c/d");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d?foo"));
+ cl_assert_equal_s(target.path, "/a/b/c/d");
+ cl_assert_equal_s(target.query, "foo");
+ git_net_url_dispose(&target);
+}
+
+void test_url_joinpath__source_query_removed(void)
+{
+ cl_git_pass(git_net_url_parse(&source, "http://example.com/a/b?query&one&two"));
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d"));
+ cl_assert_equal_s(target.path, "/a/b/c/d");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d?foo"));
+ cl_assert_equal_s(target.path, "/a/b/c/d");
+ cl_assert_equal_s(target.query, "foo");
+ git_net_url_dispose(&target);
+}
+
+void test_url_joinpath__source_lacks_path(void)
+{
+ cl_git_pass(git_net_url_parse(&source, "http://example.com"));
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/"));
+ cl_assert_equal_s(target.path, "/");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, ""));
+ cl_assert_equal_s(target.path, "/");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "asdf"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar"));
+ cl_assert_equal_s(target.path, "/foo/bar");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello"));
+ cl_assert_equal_s(target.path, "/foo/bar");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+}
+
+void test_url_joinpath__source_is_slash(void)
+{
+ cl_git_pass(git_net_url_parse(&source, "http://example.com/"));
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/"));
+ cl_assert_equal_s(target.path, "/");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, ""));
+ cl_assert_equal_s(target.path, "/");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "asdf"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar"));
+ cl_assert_equal_s(target.path, "/foo/bar");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello"));
+ cl_assert_equal_s(target.path, "/foo/bar");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+}
+
+
+void test_url_joinpath__source_has_query(void)
+{
+ cl_git_pass(git_net_url_parse(&source, "http://example.com?query"));
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/"));
+ cl_assert_equal_s(target.path, "/");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, ""));
+ cl_assert_equal_s(target.path, "/");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "asdf"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar"));
+ cl_assert_equal_s(target.path, "/foo/bar");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello"));
+ cl_assert_equal_s(target.path, "/foo/bar");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+}
+
+
+void test_url_joinpath__empty_query_ignored(void)
+{
+ cl_git_pass(git_net_url_parse(&source, "http://example.com/foo"));
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/bar/baz?"));
+ cl_assert_equal_s(target.path, "/foo/bar/baz");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+}
diff --git a/tests/util/url/parse.c b/tests/util/url/parse.c
new file mode 100644
index 0000000..35486f7
--- /dev/null
+++ b/tests/util/url/parse.c
@@ -0,0 +1,805 @@
+#include "clar_libgit2.h"
+#include "net.h"
+
+static git_net_url conndata;
+
+void test_url_parse__initialize(void)
+{
+ memset(&conndata, 0, sizeof(conndata));
+}
+
+void test_url_parse__cleanup(void)
+{
+ git_net_url_dispose(&conndata);
+}
+
+/* Hostname */
+
+void test_url_parse__hostname_trivial(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://example.com/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__hostname_root(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://example.com/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__hostname_implied_root(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://example.com"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__hostname_numeric(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://8888888/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "8888888");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__hostname_implied_root_custom_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://example.com:42"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "42");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__hostname_implied_root_empty_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://example.com:"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__hostname_encoded_password(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass%2fis%40bad@hostname.com:1234/"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "hostname.com");
+ cl_assert_equal_s(conndata.port, "1234");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass/is@bad");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__hostname_user(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user@example.com/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__hostname_user_pass(void)
+{
+ /* user:pass@hostname.tld/resource */
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@example.com/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__hostname_port(void)
+{
+ /* hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://example.com:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__hostname_empty_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://example.com:/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__hostname_user_port(void)
+{
+ /* user@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user@example.com:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__hostname_user_pass_port(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@example.com:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__hostname_user_pass_port_query(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@example.com:9191/resource?query=q&foo=bar&z=asdf"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_s(conndata.query, "query=q&foo=bar&z=asdf");
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__hostname_user_pass_port_fragment(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@example.com:9191/resource#fragment"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_s(conndata.fragment, "fragment");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__hostname_user_pass_port_query_fragment(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@example.com:9191/resource?query=q&foo=bar&z=asdf#fragment"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_s(conndata.query, "query=q&foo=bar&z=asdf");
+ cl_assert_equal_s(conndata.fragment, "fragment");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__fragment_with_question_mark(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@example.com:9191/resource#fragment_with?question_mark"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_s(conndata.fragment, "fragment_with?question_mark");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+/* IPv4 addresses */
+
+void test_url_parse__ipv4_trivial(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv4_root(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv4_implied_root(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv4_implied_root_custom_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1:42"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "42");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__ipv4_implied_root_empty_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1:"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv4_encoded_password(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass%2fis%40bad@192.168.1.1:1234/"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "1234");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass/is@bad");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__ipv4_user(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user@192.168.1.1/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv4_user_pass(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@192.168.1.1/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv4_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://192.168.1.1:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__ipv4_empty_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://192.168.1.1:/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv4_user_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user@192.168.1.1:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__ipv4_user_pass_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@192.168.1.1:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "192.168.1.1");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+/* IPv6 addresses */
+
+void test_url_parse__ipv6_trivial(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv6_root(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv6_implied_root(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv6_implied_root_custom_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]:42"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "42");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__ipv6_implied_root_empty_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]:"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv6_encoded_password(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass%2fis%40bad@[fe80::dcad:beff:fe00:0001]:1234/"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "1234");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass/is@bad");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__ipv6_user(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user@[fe80::dcad:beff:fe00:0001]/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv6_user_pass(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@[fe80::dcad:beff:fe00:0001]/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv6_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://[fe80::dcad:beff:fe00:0001]:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__ipv6_empty_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://[fe80::dcad:beff:fe00:0001]:/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ipv6_user_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user@[fe80::dcad:beff:fe00:0001]:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__ipv6_user_pass_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@[fe80::dcad:beff:fe00:0001]:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__ipv6_invalid_addresses(void)
+{
+ /* Opening bracket missing */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::dcad:beff:fe00:0001]/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::dcad:beff:fe00:0001]/"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::dcad:beff:fe00:0001]"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::dcad:beff:fe00:0001]:42"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::dcad:beff:fe00:0001]:"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user:pass%2fis%40bad@fe80::dcad:beff:fe00:0001]:1234/"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user@fe80::dcad:beff:fe00:0001]/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user:pass@fe80::dcad:beff:fe00:0001]/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://fe80::dcad:beff:fe00:0001]:9191/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::dcad:beff:fe00:0001]:/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user@fe80::dcad:beff:fe00:0001]:9191/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user:pass@fe80::dcad:beff:fe00:0001]:9191/resource"));
+
+ /* Closing bracket missing */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://[fe80::dcad:beff:fe00:0001/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://[fe80::dcad:beff:fe00:0001/"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://[fe80::dcad:beff:fe00:0001"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://[fe80::dcad:beff:fe00:0001:42"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://[fe80::dcad:beff:fe00:0001:"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user:pass%2fis%40bad@[fe80::dcad:beff:fe00:0001:1234/"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user@[fe80::dcad:beff:fe00:0001/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user:pass@[fe80::dcad:beff:fe00:0001/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://[fe80::dcad:beff:fe00:0001:9191/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://[fe80::dcad:beff:fe00:0001:/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user@[fe80::dcad:beff:fe00:0001:9191/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user:pass@[fe80::dcad:beff:fe00:0001:9191/resource"));
+
+ /* Both brackets missing */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::dcad:beff:fe00:0001/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::dcad:beff:fe00:0001/"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::dcad:beff:fe00:0001"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::dcad:beff:fe00:0001:42"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::dcad:beff:fe00:0001:"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user:pass%2fis%40bad@fe80::dcad:beff:fe00:0001:1234/"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user@fe80::dcad:beff:fe00:0001/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user:pass@fe80::dcad:beff:fe00:0001/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://fe80::dcad:beff:fe00:0001:9191/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::dcad:beff:fe00:0001:/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user@fe80::dcad:beff:fe00:0001:9191/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "https://user:pass@fe80::dcad:beff:fe00:0001:9191/resource"));
+
+ /* Invalid character inside address */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, "http://[fe8o::dcad:beff:fe00:0001]/resource"));
+
+ /* Characters before/after braces */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::[dcad:beff:fe00:0001]/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://cafe[fe80::dcad:beff:fe00:0001]/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://[fe80::dcad:beff:fe00:0001]cafe/resource"));
+}
+
+/* Oddities */
+
+void test_url_parse__empty_scheme(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "://example.com/resource"));
+ cl_assert_equal_s(conndata.scheme, NULL);
+ cl_assert_equal_s(conndata.host, NULL);
+ cl_assert_equal_s(conndata.port, NULL);
+ cl_assert_equal_s(conndata.path, "//example.com/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__invalid_scheme_is_relative(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "foo!bar://host:42/path/to/project?query_string=yes"));
+ cl_assert_equal_p(conndata.scheme, NULL);
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_p(conndata.port, NULL);
+ cl_assert_equal_s(conndata.path, "foo!bar://host:42/path/to/project");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_s(conndata.query, "query_string=yes");
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__scheme_case_is_normalized(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "GIT+SSH://host:42/path/to/project"));
+ cl_assert_equal_s(conndata.scheme, "git+ssh");
+}
+
+void test_url_parse__nonhierarchical_scheme(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "mailto:foobar@example.com"));
+ cl_assert_equal_s(conndata.scheme, "mailto");
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_p(conndata.port, NULL);
+ cl_assert_equal_s(conndata.path, "foobar@example.com");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__no_scheme_relative_path(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "path"));
+ cl_assert_equal_p(conndata.scheme, NULL);
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_p(conndata.port, NULL);
+ cl_assert_equal_s(conndata.path, "path");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__no_scheme_absolute_path(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "/path"));
+ cl_assert_equal_p(conndata.scheme, NULL);
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_p(conndata.port, NULL);
+ cl_assert_equal_s(conndata.path, "/path");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__empty_path(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "mailto:"));
+ cl_assert_equal_s(conndata.scheme, "mailto");
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_p(conndata.port, NULL);
+ cl_assert_equal_s(conndata.path, NULL);
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__empty_path_with_empty_authority(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__http_follows_the_rfc(void)
+{
+ cl_git_fail(git_net_url_parse(&conndata, "https://my.email.address@gmail.com@source.developers.google.com:4433/p/my-project/r/my-repository"));
+}
+
+void test_url_parse__ssh_from_terrible_google_rfc_violating_products(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "ssh://my.email.address@gmail.com@source.developers.google.com:2022/p/my-project/r/my-repository"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "source.developers.google.com");
+ cl_assert_equal_s(conndata.port, "2022");
+ cl_assert_equal_s(conndata.path, "/p/my-project/r/my-repository");
+ cl_assert_equal_s(conndata.username, "my.email.address@gmail.com");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__ssh_with_password_from_terrible_google_rfc_violating_products(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "ssh://my.email.address@gmail.com:seekret@source.developers.google.com:2022/p/my-project/r/my-repository"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "source.developers.google.com");
+ cl_assert_equal_s(conndata.port, "2022");
+ cl_assert_equal_s(conndata.path, "/p/my-project/r/my-repository");
+ cl_assert_equal_s(conndata.username, "my.email.address@gmail.com");
+ cl_assert_equal_s(conndata.password, "seekret");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__spaces_in_the_name(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "https://libgit2@dev.azure.com/libgit2/test/_git/spaces%20in%20the%20name"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "dev.azure.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/libgit2/test/_git/spaces%20in%20the%20name");
+ cl_assert_equal_s(conndata.username, "libgit2");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
diff --git a/tests/util/url/pattern.c b/tests/util/url/pattern.c
new file mode 100644
index 0000000..f183d7f
--- /dev/null
+++ b/tests/util/url/pattern.c
@@ -0,0 +1,103 @@
+#include "clar_libgit2.h"
+#include "net.h"
+
+struct url_pattern {
+ const char *url;
+ const char *pattern;
+ bool matches;
+};
+
+void test_url_pattern__single(void)
+{
+ git_net_url url;
+ size_t i;
+
+ struct url_pattern url_patterns[] = {
+ /* Wildcard matches */
+ { "https://example.com/", "", false },
+ { "https://example.com/", "*", true },
+
+ /* Literal and wildcard matches */
+ { "https://example.com/", "example.com", true },
+ { "https://example.com/", ".example.com", true },
+ { "https://example.com/", "*.example.com", true },
+ { "https://www.example.com/", "www.example.com", true },
+ { "https://www.example.com/", ".example.com", true },
+ { "https://www.example.com/", "*.example.com", true },
+
+ /* Literal and wildcard failures */
+ { "https://example.com/", "example.org", false },
+ { "https://example.com/", ".example.org", false },
+ { "https://example.com/", "*.example.org", false },
+ { "https://foo.example.com/", "www.example.com", false },
+
+ /*
+ * A port in the pattern is optional; if no port is
+ * present, it matches *all* ports.
+ */
+ { "https://example.com/", "example.com:443", true },
+ { "https://example.com/", "example.com:80", false },
+ { "https://example.com:1443/", "example.com", true },
+
+ /* Failures with similar prefix/suffix */
+ { "https://texample.com/", "example.com", false },
+ { "https://example.com/", "mexample.com", false },
+ { "https://example.com:44/", "example.com:443", false },
+ { "https://example.com:443/", "example.com:44", false },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(url_patterns); i++) {
+ cl_git_pass(git_net_url_parse(&url, url_patterns[i].url));
+ cl_assert_(git_net_url_matches_pattern(&url, url_patterns[i].pattern) == url_patterns[i].matches, url_patterns[i].pattern);
+ git_net_url_dispose(&url);
+ }
+}
+
+void test_url_pattern__list(void)
+{
+ git_net_url url;
+ size_t i;
+
+ struct url_pattern url_patterns[] = {
+ /* Wildcard matches */
+ { "https://example.com/", "", false },
+ { "https://example.com/", "*", true },
+ { "https://example.com/", ",example.com,", true },
+ { "https://example.com/", "foo,,example.com,,bar", true },
+ { "https://example.com/", "foo,,zzz,,*,,bar", true },
+
+ /* Literals */
+ { "https://example.com/", "example.com", true },
+ { "https://example.com/", "foo.bar,example.com", true },
+ { "https://example.com/", "foo.bar", false },
+ { "https://example.com/", "foo.bar,example.org", false },
+ { "https://www.example.com/", "foo.example.com,www.example.com,bar.example.com", true },
+ { "https://www.example.com/", "foo.example.com,baz.example.com,bar.example.com", false },
+ { "https://foo.example.com/", "www.example.com", false },
+ { "https://foo.example.com/", "bar.example.com,www.example.com,", false },
+
+ /* Wildcards */
+ { "https://example.com/", ".example.com", true },
+ { "https://example.com/", "*.example.com", true },
+ { "https://example.com/", "foo.com,bar.com,.example.com", true },
+ { "https://example.com/", ".foo.com,.bar.com,.example.com", true },
+ { "https://example.com/", ".foo.com,.bar.com,asdf.com", false },
+ { "https://example.com/", "*.foo,*.bar,*.example.com,*.asdf", true },
+ { "https://example.com/", "*.foo,*.bar,*.asdf", false },
+
+
+ /* Ports! */
+ { "https://example.com/", "example.com:443", true },
+ { "https://example.com/", "example.com:42,example.com:443,example.com:99", true },
+ { "https://example.com/", "example.com:42,example.com:80,example.org:443", false },
+ { "https://example.com:1443/", "example.com", true },
+ { "https://example.com:44/", "example.com:443", false },
+ { "https://example.com:443/", "example.com:44", false },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(url_patterns); i++) {
+ cl_git_pass(git_net_url_parse(&url, url_patterns[i].url));
+ cl_assert_(git_net_url_matches_pattern_list(&url, url_patterns[i].pattern) == url_patterns[i].matches, url_patterns[i].pattern);
+ git_net_url_dispose(&url);
+ }
+}
diff --git a/tests/util/url/redirect.c b/tests/util/url/redirect.c
new file mode 100644
index 0000000..a6f99dc
--- /dev/null
+++ b/tests/util/url/redirect.c
@@ -0,0 +1,146 @@
+#include "clar_libgit2.h"
+#include "net.h"
+
+static git_net_url conndata;
+
+void test_url_redirect__initialize(void)
+{
+ memset(&conndata, 0, sizeof(conndata));
+}
+
+void test_url_redirect__cleanup(void)
+{
+ git_net_url_dispose(&conndata);
+}
+
+void test_url_redirect__redirect_http(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "http://example.com/foo/bar/baz"));
+ cl_git_pass(git_net_url_apply_redirect(&conndata,
+ "http://example.com/foo/bar/baz", false, "bar/baz"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/foo/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+}
+
+void test_url_redirect__redirect_ssl(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://example.com/foo/bar/baz"));
+ cl_git_pass(git_net_url_apply_redirect(&conndata,
+ "https://example.com/foo/bar/baz", false, "bar/baz"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/foo/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+}
+
+void test_url_redirect__redirect_leaves_root_path(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://example.com/foo/bar/baz"));
+ cl_git_pass(git_net_url_apply_redirect(&conndata,
+ "https://example.com/foo/bar/baz", false, "/foo/bar/baz"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+}
+
+void test_url_redirect__redirect_encoded_username_password(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz"));
+ cl_git_pass(git_net_url_apply_redirect(&conndata,
+ "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", false, "bar/baz"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/foo/");
+ cl_assert_equal_s(conndata.username, "user/name");
+ cl_assert_equal_s(conndata.password, "pass@word%zyx%v");
+}
+
+void test_url_redirect__redirect_cross_host_allowed(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://bar.com/bar/baz"));
+ cl_git_pass(git_net_url_apply_redirect(&conndata,
+ "https://foo.com/bar/baz", true, NULL));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "foo.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/bar/baz");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+}
+
+void test_url_redirect__redirect_cross_host_denied(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://bar.com/bar/baz"));
+ cl_git_fail_with(git_net_url_apply_redirect(&conndata,
+ "https://foo.com/bar/baz", false, NULL), -1);
+}
+
+void test_url_redirect__redirect_http_downgrade_denied(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://foo.com/bar/baz"));
+ cl_git_fail_with(git_net_url_apply_redirect(&conndata,
+ "http://foo.com/bar/baz", true, NULL), -1);
+}
+
+void test_url_redirect__redirect_relative(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "http://foo.com/bar/baz/biff"));
+ cl_git_pass(git_net_url_apply_redirect(&conndata,
+ "/zap/baz/biff?bam", true, NULL));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "foo.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+}
+
+void test_url_redirect__redirect_relative_ssl(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://foo.com/bar/baz/biff"));
+ cl_git_pass(git_net_url_apply_redirect(&conndata,
+ "/zap/baz/biff?bam", true, NULL));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "foo.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+}
+
+void test_url_redirect__service_query_no_query_params_in_location(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://foo.com/bar/info/refs?service=git-upload-pack"));
+ cl_git_pass(git_net_url_apply_redirect(&conndata,
+ "/baz/info/refs", true, "/info/refs?service=git-upload-pack"));
+ cl_assert_equal_s(conndata.path, "/baz");
+}
+
+void test_url_redirect__service_query_with_query_params_in_location(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://foo.com/bar/info/refs?service=git-upload-pack"));
+ cl_git_pass(git_net_url_apply_redirect(&conndata,
+ "/baz/info/refs?service=git-upload-pack", true, "/info/refs?service=git-upload-pack"));
+ cl_assert_equal_s(conndata.path, "/baz");
+}
diff --git a/tests/util/url/scp.c b/tests/util/url/scp.c
new file mode 100644
index 0000000..0e0dce1
--- /dev/null
+++ b/tests/util/url/scp.c
@@ -0,0 +1,317 @@
+#include "clar_libgit2.h"
+#include "net.h"
+
+static git_net_url conndata;
+
+void test_url_scp__initialize(void)
+{
+ memset(&conndata, 0, sizeof(conndata));
+}
+
+void test_url_scp__cleanup(void)
+{
+ git_net_url_dispose(&conndata);
+}
+
+/* Hostname */
+
+void test_url_scp__hostname_trivial(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "example.com:/resource"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__hostname_bracketed(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "[example.com]:/resource"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__hostname_root(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "example.com:/"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__hostname_user(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "git@example.com:/resource"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "git");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__hostname_user_bracketed(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "[git@example.com]:/resource"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "git");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__hostname_port(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "[example.com:42]:/resource"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "42");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_scp__hostname_user_port(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "[git@example.com:42]:/resource"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "42");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "git");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_scp__ipv4_trivial(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "192.168.99.88:/resource/a/b/c"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "192.168.99.88");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "/resource/a/b/c");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__ipv4_bracketed(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "[192.168.99.88]:/resource/a/b/c"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "192.168.99.88");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "/resource/a/b/c");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__ipv4_user(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "git@192.168.99.88:/resource/a/b/c"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "192.168.99.88");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "/resource/a/b/c");
+ cl_assert_equal_s(conndata.username, "git");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__ipv4_port(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "[192.168.99.88:1111]:/resource/a/b/c"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "192.168.99.88");
+ cl_assert_equal_s(conndata.port, "1111");
+ cl_assert_equal_s(conndata.path, "/resource/a/b/c");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_scp__ipv4_user_port(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "[git@192.168.99.88:1111]:/resource/a/b/c"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "192.168.99.88");
+ cl_assert_equal_s(conndata.port, "1111");
+ cl_assert_equal_s(conndata.path, "/resource/a/b/c");
+ cl_assert_equal_s(conndata.username, "git");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_scp__ipv6_trivial(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "[fe80::dcad:beff:fe00:0001]:/resource/foo"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "/resource/foo");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__ipv6_user(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "git@[fe80::dcad:beff:fe00:0001]:/resource/foo"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "/resource/foo");
+ cl_assert_equal_s(conndata.username, "git");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__ipv6_port(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "[[fe80::dcad:beff:fe00:0001]:99]:/resource/foo"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]");
+ cl_assert_equal_s(conndata.port, "99");
+ cl_assert_equal_s(conndata.path, "/resource/foo");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_scp__ipv6_user_port(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "[git@[fe80::dcad:beff:fe00:0001]:99]:/resource/foo"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]");
+ cl_assert_equal_s(conndata.port, "99");
+ cl_assert_equal_s(conndata.path, "/resource/foo");
+ cl_assert_equal_s(conndata.username, "git");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_scp__hexhost_and_port(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "[fe:22]:/resource/foo"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "fe");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "/resource/foo");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__malformed_ipv6_one(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "fe80::dcad:beff:fe00:0001]:/resource"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "fe80");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, ":dcad:beff:fe00:0001]:/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__malformed_ipv6_two(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "[fe80::dcad:beff:fe00:0001]:42]:/resource"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "42]:/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__malformed_ipv6_with_user(void)
+{
+ cl_git_pass(git_net_url_parse_scp(&conndata, "git@[fe80::dcad:beff:fe00:0001]:42]:/resource"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "[fe80::dcad:beff:fe00:0001]");
+ cl_assert_equal_s(conndata.port, "22");
+ cl_assert_equal_s(conndata.path, "42]:/resource");
+ cl_assert_equal_s(conndata.username, "git");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_scp__invalid_addresses(void)
+{
+ /* Path is required */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "example.com"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "example.com:"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[example.com:42]:"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[git@example.com:42]:"));
+
+ /* Host is required */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ ":"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ ":foo"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "git@:foo"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[]:"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "git@[]:"));
+
+ /* User is required if specified */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "@example.com:foo"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "@:foo"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[@localhost:22]:foo"));
+
+ /* Port is required in brackets */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[example.com:]:foo"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[git@example.com:]:foo"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[fe:]:foo"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[@localhost]:foo"));
+
+ /* Extra brackets are disallowed */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[git@[[fe80::dcad:beff:fe00:0001]]:42]:foo"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[[git@[fe80::dcad:beff:fe00:0001]]:42]:foo"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[[git@[fe80::dcad:beff:fe00:0001]:42]]:foo"));
+
+ /* Closing bracket missing */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[fe80::dcad:beff:fe00:0001:/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[[fe80::dcad:beff:fe00:0001]:42:/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[git@[fe80::dcad:beff:fe00:0001]:42:/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_scp(&conndata,
+ "[git@[fe80::dcad:beff:fe00:0001:42]:/resource"));
+}
diff --git a/tests/util/url/valid.c b/tests/util/url/valid.c
new file mode 100644
index 0000000..797b697
--- /dev/null
+++ b/tests/util/url/valid.c
@@ -0,0 +1,17 @@
+#include "clar_libgit2.h"
+#include "net.h"
+
+void test_url_valid__test(void)
+{
+ cl_assert(git_net_str_is_url("http://example.com/"));
+ cl_assert(git_net_str_is_url("file://localhost/tmp/foo/"));
+ cl_assert(git_net_str_is_url("ssh://user@host:42/tmp"));
+ cl_assert(git_net_str_is_url("git+ssh://user@host:42/tmp"));
+ cl_assert(git_net_str_is_url("ssh+git://user@host:42/tmp"));
+ cl_assert(git_net_str_is_url("https://user:pass@example.com/foo/bar"));
+
+ cl_assert(!git_net_str_is_url("host:foo.git"));
+ cl_assert(!git_net_str_is_url("host:/foo.git"));
+ cl_assert(!git_net_str_is_url("[host:42]:/foo.git"));
+ cl_assert(!git_net_str_is_url("[user@host:42]:/foo.git"));
+}
diff --git a/tests/util/utf8.c b/tests/util/utf8.c
new file mode 100644
index 0000000..3987603
--- /dev/null
+++ b/tests/util/utf8.c
@@ -0,0 +1,20 @@
+#include "clar_libgit2.h"
+#include "utf8.h"
+
+void test_utf8__char_length(void)
+{
+ cl_assert_equal_i(0, git_utf8_char_length("", 0));
+ cl_assert_equal_i(1, git_utf8_char_length("$", 1));
+ cl_assert_equal_i(5, git_utf8_char_length("abcde", 5));
+ cl_assert_equal_i(1, git_utf8_char_length("\xc2\xa2", 2));
+ cl_assert_equal_i(2, git_utf8_char_length("\x24\xc2\xa2", 3));
+ cl_assert_equal_i(1, git_utf8_char_length("\xf0\x90\x8d\x88", 4));
+
+ /* uncontinued character counted as single characters */
+ cl_assert_equal_i(2, git_utf8_char_length("\x24\xc2", 2));
+ cl_assert_equal_i(3, git_utf8_char_length("\x24\xc2\xc2\xa2", 4));
+
+ /* invalid characters are counted as single characters */
+ cl_assert_equal_i(4, git_utf8_char_length("\x24\xc0\xc0\x34", 4));
+ cl_assert_equal_i(4, git_utf8_char_length("\x24\xf5\xfd\xc2", 4));
+}
diff --git a/tests/util/vector.c b/tests/util/vector.c
new file mode 100644
index 0000000..04afaa4
--- /dev/null
+++ b/tests/util/vector.c
@@ -0,0 +1,430 @@
+#include <stdint.h>
+
+#include "clar_libgit2.h"
+#include "vector.h"
+
+/* initial size of 1 would cause writing past array bounds */
+void test_vector__0(void)
+{
+ git_vector x;
+ int i;
+ cl_git_pass(git_vector_init(&x, 1, NULL));
+ for (i = 0; i < 10; ++i) {
+ git_vector_insert(&x, (void*) 0xabc);
+ }
+ git_vector_free(&x);
+}
+
+
+/* don't read past array bounds on remove() */
+void test_vector__1(void)
+{
+ git_vector x;
+ /* make initial capacity exact for our insertions. */
+ cl_git_pass(git_vector_init(&x, 3, NULL));
+ git_vector_insert(&x, (void*) 0xabc);
+ git_vector_insert(&x, (void*) 0xdef);
+ git_vector_insert(&x, (void*) 0x123);
+
+ git_vector_remove(&x, 0); /* used to read past array bounds. */
+ git_vector_free(&x);
+}
+
+
+static int test_cmp(const void *a, const void *b)
+{
+ return *(const int *)a - *(const int *)b;
+}
+
+/* remove duplicates */
+void test_vector__2(void)
+{
+ git_vector x;
+ int *ptrs[2];
+
+ ptrs[0] = git__malloc(sizeof(int));
+ ptrs[1] = git__malloc(sizeof(int));
+
+ *ptrs[0] = 2;
+ *ptrs[1] = 1;
+
+ cl_git_pass(git_vector_init(&x, 5, test_cmp));
+ cl_git_pass(git_vector_insert(&x, ptrs[0]));
+ cl_git_pass(git_vector_insert(&x, ptrs[1]));
+ cl_git_pass(git_vector_insert(&x, ptrs[1]));
+ cl_git_pass(git_vector_insert(&x, ptrs[0]));
+ cl_git_pass(git_vector_insert(&x, ptrs[1]));
+ cl_assert(x.length == 5);
+
+ git_vector_uniq(&x, NULL);
+ cl_assert(x.length == 2);
+
+ git_vector_free(&x);
+
+ git__free(ptrs[0]);
+ git__free(ptrs[1]);
+}
+
+
+static int compare_them(const void *a, const void *b)
+{
+ return (int)((intptr_t)a - (intptr_t)b);
+}
+
+/* insert_sorted */
+void test_vector__3(void)
+{
+ git_vector x;
+ intptr_t i;
+ cl_git_pass(git_vector_init(&x, 1, &compare_them));
+
+ for (i = 0; i < 10; i += 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ for (i = 9; i > 0; i -= 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ cl_assert(x.length == 10);
+ for (i = 0; i < 10; ++i) {
+ cl_assert(git_vector_get(&x, i) == (void*)(i + 1));
+ }
+
+ git_vector_free(&x);
+}
+
+/* insert_sorted with duplicates */
+void test_vector__4(void)
+{
+ git_vector x;
+ intptr_t i;
+ cl_git_pass(git_vector_init(&x, 1, &compare_them));
+
+ for (i = 0; i < 10; i += 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ for (i = 9; i > 0; i -= 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ for (i = 0; i < 10; i += 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ for (i = 9; i > 0; i -= 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ cl_assert(x.length == 20);
+ for (i = 0; i < 20; ++i) {
+ cl_assert(git_vector_get(&x, i) == (void*)(i / 2 + 1));
+ }
+
+ git_vector_free(&x);
+}
+
+typedef struct {
+ int content;
+ int count;
+} my_struct;
+
+static int _struct_count = 0;
+
+static int compare_structs(const void *a, const void *b)
+{
+ return ((const my_struct *)a)->content -
+ ((const my_struct *)b)->content;
+}
+
+static int merge_structs(void **old_raw, void *new)
+{
+ my_struct *old = *(my_struct **)old_raw;
+ cl_assert(((my_struct *)old)->content == ((my_struct *)new)->content);
+ ((my_struct *)old)->count += 1;
+ git__free(new);
+ _struct_count--;
+ return GIT_EEXISTS;
+}
+
+static my_struct *alloc_struct(int value)
+{
+ my_struct *st = git__malloc(sizeof(my_struct));
+ st->content = value;
+ st->count = 0;
+ _struct_count++;
+ return st;
+}
+
+/* insert_sorted with duplicates and special handling */
+void test_vector__5(void)
+{
+ git_vector x;
+ int i;
+
+ cl_git_pass(git_vector_init(&x, 1, &compare_structs));
+
+ for (i = 0; i < 10; i += 2)
+ git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
+
+ for (i = 9; i > 0; i -= 2)
+ git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
+
+ cl_assert(x.length == 10);
+ cl_assert(_struct_count == 10);
+
+ for (i = 0; i < 10; i += 2)
+ git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
+
+ for (i = 9; i > 0; i -= 2)
+ git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
+
+ cl_assert(x.length == 10);
+ cl_assert(_struct_count == 10);
+
+ for (i = 0; i < 10; ++i) {
+ cl_assert(((my_struct *)git_vector_get(&x, i))->content == i);
+ git__free(git_vector_get(&x, i));
+ _struct_count--;
+ }
+
+ git_vector_free(&x);
+}
+
+static int remove_ones(const git_vector *v, size_t idx, void *p)
+{
+ GIT_UNUSED(p);
+ return (git_vector_get(v, idx) == (void *)0x001);
+}
+
+/* Test removal based on callback */
+void test_vector__remove_matching(void)
+{
+ git_vector x;
+ size_t i;
+ void *compare;
+
+ cl_git_pass(git_vector_init(&x, 1, NULL));
+ git_vector_insert(&x, (void*) 0x001);
+
+ cl_assert(x.length == 1);
+ git_vector_remove_matching(&x, remove_ones, NULL);
+ cl_assert(x.length == 0);
+
+ git_vector_insert(&x, (void*) 0x001);
+ git_vector_insert(&x, (void*) 0x001);
+ git_vector_insert(&x, (void*) 0x001);
+
+ cl_assert(x.length == 3);
+ git_vector_remove_matching(&x, remove_ones, NULL);
+ cl_assert(x.length == 0);
+
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x001);
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x001);
+
+ cl_assert(x.length == 4);
+ git_vector_remove_matching(&x, remove_ones, NULL);
+ cl_assert(x.length == 2);
+
+ git_vector_foreach(&x, i, compare) {
+ cl_assert(compare != (void *)0x001);
+ }
+
+ git_vector_clear(&x);
+
+ git_vector_insert(&x, (void*) 0x001);
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x001);
+
+ cl_assert(x.length == 4);
+ git_vector_remove_matching(&x, remove_ones, NULL);
+ cl_assert(x.length == 2);
+
+ git_vector_foreach(&x, i, compare) {
+ cl_assert(compare != (void *)0x001);
+ }
+
+ git_vector_clear(&x);
+
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x001);
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x001);
+
+ cl_assert(x.length == 4);
+ git_vector_remove_matching(&x, remove_ones, NULL);
+ cl_assert(x.length == 2);
+
+ git_vector_foreach(&x, i, compare) {
+ cl_assert(compare != (void *)0x001);
+ }
+
+ git_vector_clear(&x);
+
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x003);
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x003);
+
+ cl_assert(x.length == 4);
+ git_vector_remove_matching(&x, remove_ones, NULL);
+ cl_assert(x.length == 4);
+
+ git_vector_free(&x);
+}
+
+static void assert_vector(git_vector *x, void *expected[], size_t len)
+{
+ size_t i;
+
+ cl_assert_equal_i(len, x->length);
+
+ for (i = 0; i < len; i++)
+ cl_assert(expected[i] == x->contents[i]);
+}
+
+void test_vector__grow_and_shrink(void)
+{
+ git_vector x = GIT_VECTOR_INIT;
+ void *expected1[] = {
+ (void *)0x02, (void *)0x03, (void *)0x04, (void *)0x05,
+ (void *)0x06, (void *)0x07, (void *)0x08, (void *)0x09,
+ (void *)0x0a
+ };
+ void *expected2[] = {
+ (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06,
+ (void *)0x07, (void *)0x08, (void *)0x09, (void *)0x0a
+ };
+ void *expected3[] = {
+ (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06,
+ (void *)0x0a
+ };
+ void *expected4[] = {
+ (void *)0x02, (void *)0x04, (void *)0x05
+ };
+ void *expected5[] = {
+ (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04,
+ (void *)0x05
+ };
+ void *expected6[] = {
+ (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04,
+ (void *)0x05, (void *)0x00
+ };
+ void *expected7[] = {
+ (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04,
+ (void *)0x00, (void *)0x00, (void *)0x00, (void *)0x05,
+ (void *)0x00
+ };
+ void *expected8[] = {
+ (void *)0x04, (void *)0x00, (void *)0x00, (void *)0x00,
+ (void *)0x05, (void *)0x00
+ };
+ void *expected9[] = {
+ (void *)0x04, (void *)0x00, (void *)0x05, (void *)0x00
+ };
+ void *expectedA[] = { (void *)0x04, (void *)0x00 };
+ void *expectedB[] = { (void *)0x04 };
+
+ git_vector_insert(&x, (void *)0x01);
+ git_vector_insert(&x, (void *)0x02);
+ git_vector_insert(&x, (void *)0x03);
+ git_vector_insert(&x, (void *)0x04);
+ git_vector_insert(&x, (void *)0x05);
+ git_vector_insert(&x, (void *)0x06);
+ git_vector_insert(&x, (void *)0x07);
+ git_vector_insert(&x, (void *)0x08);
+ git_vector_insert(&x, (void *)0x09);
+ git_vector_insert(&x, (void *)0x0a);
+
+ git_vector_remove_range(&x, 0, 1);
+ assert_vector(&x, expected1, ARRAY_SIZE(expected1));
+
+ git_vector_remove_range(&x, 1, 1);
+ assert_vector(&x, expected2, ARRAY_SIZE(expected2));
+
+ git_vector_remove_range(&x, 4, 3);
+ assert_vector(&x, expected3, ARRAY_SIZE(expected3));
+
+ git_vector_remove_range(&x, 3, 2);
+ assert_vector(&x, expected4, ARRAY_SIZE(expected4));
+
+ git_vector_insert_null(&x, 0, 2);
+ assert_vector(&x, expected5, ARRAY_SIZE(expected5));
+
+ git_vector_insert_null(&x, 5, 1);
+ assert_vector(&x, expected6, ARRAY_SIZE(expected6));
+
+ git_vector_insert_null(&x, 4, 3);
+ assert_vector(&x, expected7, ARRAY_SIZE(expected7));
+
+ git_vector_remove_range(&x, 0, 3);
+ assert_vector(&x, expected8, ARRAY_SIZE(expected8));
+
+ git_vector_remove_range(&x, 1, 2);
+ assert_vector(&x, expected9, ARRAY_SIZE(expected9));
+
+ git_vector_remove_range(&x, 2, 2);
+ assert_vector(&x, expectedA, ARRAY_SIZE(expectedA));
+
+ git_vector_remove_range(&x, 1, 1);
+ assert_vector(&x, expectedB, ARRAY_SIZE(expectedB));
+
+ git_vector_remove_range(&x, 0, 1);
+ assert_vector(&x, NULL, 0);
+
+ git_vector_free(&x);
+}
+
+void test_vector__reverse(void)
+{
+ git_vector v = GIT_VECTOR_INIT;
+ size_t i;
+
+ void *in1[] = {(void *) 0x0, (void *) 0x1, (void *) 0x2, (void *) 0x3};
+ void *out1[] = {(void *) 0x3, (void *) 0x2, (void *) 0x1, (void *) 0x0};
+
+ void *in2[] = {(void *) 0x0, (void *) 0x1, (void *) 0x2, (void *) 0x3, (void *) 0x4};
+ void *out2[] = {(void *) 0x4, (void *) 0x3, (void *) 0x2, (void *) 0x1, (void *) 0x0};
+
+ for (i = 0; i < 4; i++)
+ cl_git_pass(git_vector_insert(&v, in1[i]));
+
+ git_vector_reverse(&v);
+
+ for (i = 0; i < 4; i++)
+ cl_assert_equal_p(out1[i], git_vector_get(&v, i));
+
+ git_vector_clear(&v);
+ for (i = 0; i < 5; i++)
+ cl_git_pass(git_vector_insert(&v, in2[i]));
+
+ git_vector_reverse(&v);
+
+ for (i = 0; i < 5; i++)
+ cl_assert_equal_p(out2[i], git_vector_get(&v, i));
+
+ git_vector_free(&v);
+}
+
+void test_vector__dup_empty_vector(void)
+{
+ git_vector v = GIT_VECTOR_INIT;
+ git_vector dup = GIT_VECTOR_INIT;
+ int dummy;
+
+ cl_assert_equal_i(0, v.length);
+
+ cl_git_pass(git_vector_dup(&dup, &v, v._cmp));
+ cl_assert_equal_i(0, dup._alloc_size);
+ cl_assert_equal_i(0, dup.length);
+
+ cl_git_pass(git_vector_insert(&dup, &dummy));
+ cl_assert_equal_i(8, dup._alloc_size);
+ cl_assert_equal_i(1, dup.length);
+
+ git_vector_free(&dup);
+}
diff --git a/tests/util/wildmatch.c b/tests/util/wildmatch.c
new file mode 100644
index 0000000..a5af61a
--- /dev/null
+++ b/tests/util/wildmatch.c
@@ -0,0 +1,248 @@
+#include "clar_libgit2.h"
+
+#include "wildmatch.h"
+
+#define assert_matches(string, pattern, wildmatch, iwildmatch, pathmatch, ipathmatch) \
+ assert_matches_(string, pattern, wildmatch, iwildmatch, pathmatch, ipathmatch, __FILE__, __func__, __LINE__)
+
+static void assert_matches_(const char *string, const char *pattern,
+ char expected_wildmatch, char expected_iwildmatch,
+ char expected_pathmatch, char expected_ipathmatch,
+ const char *file, const char *func, size_t line)
+{
+ if (wildmatch(pattern, string, WM_PATHNAME) == expected_wildmatch)
+ clar__fail(file, func, line, "Test failed (wildmatch).", string, 1);
+ if (wildmatch(pattern, string, WM_PATHNAME|WM_CASEFOLD) == expected_iwildmatch)
+ clar__fail(file, func, line, "Test failed (iwildmatch).", string, 1);
+ if (wildmatch(pattern, string, 0) == expected_pathmatch)
+ clar__fail(file, func, line, "Test failed (pathmatch).", string, 1);
+ if (wildmatch(pattern, string, WM_CASEFOLD) == expected_ipathmatch)
+ clar__fail(file, func, line, "Test failed (ipathmatch).", string, 1);
+}
+
+/*
+ * Below testcases are imported from git.git, t3070-wildmatch,sh at tag v2.22.0.
+ * Note that we've only imported the direct wildcard tests, but not the matching
+ * tests for git-ls-files.
+ */
+
+void test_wildmatch__basic_wildmatch(void)
+{
+ assert_matches("foo", "foo", 1, 1, 1, 1);
+ assert_matches("foo", "bar", 0, 0, 0, 0);
+ assert_matches("", "", 1, 1, 1, 1);
+ assert_matches("foo", "???", 1, 1, 1, 1);
+ assert_matches("foo", "??", 0, 0, 0, 0);
+ assert_matches("foo", "*", 1, 1, 1, 1);
+ assert_matches("foo", "f*", 1, 1, 1, 1);
+ assert_matches("foo", "*f", 0, 0, 0, 0);
+ assert_matches("foo", "*foo*", 1, 1, 1, 1);
+ assert_matches("foobar", "*ob*a*r*", 1, 1, 1, 1);
+ assert_matches("aaaaaaabababab", "*ab", 1, 1, 1, 1);
+ assert_matches("foo*", "foo\\*", 1, 1, 1, 1);
+ assert_matches("foobar", "foo\\*bar", 0, 0, 0, 0);
+ assert_matches("f\\oo", "f\\\\oo", 1, 1, 1, 1);
+ assert_matches("ball", "*[al]?", 1, 1, 1, 1);
+ assert_matches("ten", "[ten]", 0, 0, 0, 0);
+ assert_matches("ten", "**[!te]", 1, 1, 1, 1);
+ assert_matches("ten", "**[!ten]", 0, 0, 0, 0);
+ assert_matches("ten", "t[a-g]n", 1, 1, 1, 1);
+ assert_matches("ten", "t[!a-g]n", 0, 0, 0, 0);
+ assert_matches("ton", "t[!a-g]n", 1, 1, 1, 1);
+ assert_matches("ton", "t[^a-g]n", 1, 1, 1, 1);
+ assert_matches("a]b", "a[]]b", 1, 1, 1, 1);
+ assert_matches("a-b", "a[]-]b", 1, 1, 1, 1);
+ assert_matches("a]b", "a[]-]b", 1, 1, 1, 1);
+ assert_matches("aab", "a[]-]b", 0, 0, 0, 0);
+ assert_matches("aab", "a[]a-]b", 1, 1, 1, 1);
+ assert_matches("]", "]", 1, 1, 1, 1);
+}
+
+void test_wildmatch__slash_matching_features(void)
+{
+ assert_matches("foo/baz/bar", "foo*bar", 0, 0, 1, 1);
+ assert_matches("foo/baz/bar", "foo**bar", 0, 0, 1, 1);
+ assert_matches("foobazbar", "foo**bar", 1, 1, 1, 1);
+ assert_matches("foo/baz/bar", "foo/**/bar", 1, 1, 1, 1);
+ assert_matches("foo/baz/bar", "foo/**/**/bar", 1, 1, 0, 0);
+ assert_matches("foo/b/a/z/bar", "foo/**/bar", 1, 1, 1, 1);
+ assert_matches("foo/b/a/z/bar", "foo/**/**/bar", 1, 1, 1, 1);
+ assert_matches("foo/bar", "foo/**/bar", 1, 1, 0, 0);
+ assert_matches("foo/bar", "foo/**/**/bar", 1, 1, 0, 0);
+ assert_matches("foo/bar", "foo?bar", 0, 0, 1, 1);
+ assert_matches("foo/bar", "foo[/]bar", 0, 0, 1, 1);
+ assert_matches("foo/bar", "foo[^a-z]bar", 0, 0, 1, 1);
+ assert_matches("foo/bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", 0, 0, 1, 1);
+ assert_matches("foo-bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r", 1, 1, 1, 1);
+ assert_matches("foo", "**/foo", 1, 1, 0, 0);
+ assert_matches("XXX/foo", "**/foo", 1, 1, 1, 1);
+ assert_matches("bar/baz/foo", "**/foo", 1, 1, 1, 1);
+ assert_matches("bar/baz/foo", "*/foo", 0, 0, 1, 1);
+ assert_matches("foo/bar/baz", "**/bar*", 0, 0, 1, 1);
+ assert_matches("deep/foo/bar/baz", "**/bar/*", 1, 1, 1, 1);
+ assert_matches("deep/foo/bar/baz/", "**/bar/*", 0, 0, 1, 1);
+ assert_matches("deep/foo/bar/baz/", "**/bar/**", 1, 1, 1, 1);
+ assert_matches("deep/foo/bar", "**/bar/*", 0, 0, 0, 0);
+ assert_matches("deep/foo/bar/", "**/bar/**", 1, 1, 1, 1);
+ assert_matches("foo/bar/baz", "**/bar**", 0, 0, 1, 1);
+ assert_matches("foo/bar/baz/x", "*/bar/**", 1, 1, 1, 1);
+ assert_matches("deep/foo/bar/baz/x", "*/bar/**", 0, 0, 1, 1);
+ assert_matches("deep/foo/bar/baz/x", "**/bar/*/*", 1, 1, 1, 1);
+}
+
+void test_wildmatch__various_additional(void)
+{
+ assert_matches("acrt", "a[c-c]st", 0, 0, 0, 0);
+ assert_matches("acrt", "a[c-c]rt", 1, 1, 1, 1);
+ assert_matches("]", "[!]-]", 0, 0, 0, 0);
+ assert_matches("a", "[!]-]", 1, 1, 1, 1);
+ assert_matches("", "\\", 0, 0, 0, 0);
+ assert_matches("\\", "\\", 0, 0, 0, 0);
+ assert_matches("XXX/\\", "*/\\", 0, 0, 0, 0);
+ assert_matches("XXX/\\", "*/\\\\", 1, 1, 1, 1);
+ assert_matches("foo", "foo", 1, 1, 1, 1);
+ assert_matches("@foo", "@foo", 1, 1, 1, 1);
+ assert_matches("foo", "@foo", 0, 0, 0, 0);
+ assert_matches("[ab]", "\\[ab]", 1, 1, 1, 1);
+ assert_matches("[ab]", "[[]ab]", 1, 1, 1, 1);
+ assert_matches("[ab]", "[[:]ab]", 1, 1, 1, 1);
+ assert_matches("[ab]", "[[::]ab]", 0, 0, 0, 0);
+ assert_matches("[ab]", "[[:digit]ab]", 1, 1, 1, 1);
+ assert_matches("[ab]", "[\\[:]ab]", 1, 1, 1, 1);
+ assert_matches("?a?b", "\\??\\?b", 1, 1, 1, 1);
+ assert_matches("abc", "\\a\\b\\c", 1, 1, 1, 1);
+ assert_matches("foo", "", 0, 0, 0, 0);
+ assert_matches("foo/bar/baz/to", "**/t[o]", 1, 1, 1, 1);
+}
+
+void test_wildmatch__character_classes(void)
+{
+ assert_matches("a1B", "[[:alpha:]][[:digit:]][[:upper:]]", 1, 1, 1, 1);
+ assert_matches("a", "[[:digit:][:upper:][:space:]]", 0, 1, 0, 1);
+ assert_matches("A", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1);
+ assert_matches("1", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1);
+ assert_matches("1", "[[:digit:][:upper:][:spaci:]]", 0, 0, 0, 0);
+ assert_matches(" ", "[[:digit:][:upper:][:space:]]", 1, 1, 1, 1);
+ assert_matches(".", "[[:digit:][:upper:][:space:]]", 0, 0, 0, 0);
+ assert_matches(".", "[[:digit:][:punct:][:space:]]", 1, 1, 1, 1);
+ assert_matches("5", "[[:xdigit:]]", 1, 1, 1, 1);
+ assert_matches("f", "[[:xdigit:]]", 1, 1, 1, 1);
+ assert_matches("D", "[[:xdigit:]]", 1, 1, 1, 1);
+ assert_matches("_", "[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]", 1, 1, 1, 1);
+ assert_matches(".", "[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]", 1, 1, 1, 1);
+ assert_matches("5", "[a-c[:digit:]x-z]", 1, 1, 1, 1);
+ assert_matches("b", "[a-c[:digit:]x-z]", 1, 1, 1, 1);
+ assert_matches("y", "[a-c[:digit:]x-z]", 1, 1, 1, 1);
+ assert_matches("q", "[a-c[:digit:]x-z]", 0, 0, 0, 0);
+}
+
+void test_wildmatch__additional_with_malformed(void)
+{
+ assert_matches("]", "[\\\\-^]", 1, 1, 1, 1);
+ assert_matches("[", "[\\\\-^]", 0, 0, 0, 0);
+ assert_matches("-", "[\\-_]", 1, 1, 1, 1);
+ assert_matches("]", "[\\]]", 1, 1, 1, 1);
+ assert_matches("\\]", "[\\]]", 0, 0, 0, 0);
+ assert_matches("\\", "[\\]]", 0, 0, 0, 0);
+ assert_matches("ab", "a[]b", 0, 0, 0, 0);
+ assert_matches("a[]b", "a[]b", 0, 0, 0, 0);
+ assert_matches("ab[", "ab[", 0, 0, 0, 0);
+ assert_matches("ab", "[!", 0, 0, 0, 0);
+ assert_matches("ab", "[-", 0, 0, 0, 0);
+ assert_matches("-", "[-]", 1, 1, 1, 1);
+ assert_matches("-", "[a-", 0, 0, 0, 0);
+ assert_matches("-", "[!a-", 0, 0, 0, 0);
+ assert_matches("-", "[--A]", 1, 1, 1, 1);
+ assert_matches("5", "[--A]", 1, 1, 1, 1);
+ assert_matches(" ", "[ --]", 1, 1, 1, 1);
+ assert_matches("$", "[ --]", 1, 1, 1, 1);
+ assert_matches("-", "[ --]", 1, 1, 1, 1);
+ assert_matches("0", "[ --]", 0, 0, 0, 0);
+ assert_matches("-", "[---]", 1, 1, 1, 1);
+ assert_matches("-", "[------]", 1, 1, 1, 1);
+ assert_matches("j", "[a-e-n]", 0, 0, 0, 0);
+ assert_matches("-", "[a-e-n]", 1, 1, 1, 1);
+ assert_matches("a", "[!------]", 1, 1, 1, 1);
+ assert_matches("[", "[]-a]", 0, 0, 0, 0);
+ assert_matches("^", "[]-a]", 1, 1, 1, 1);
+ assert_matches("^", "[!]-a]", 0, 0, 0, 0);
+ assert_matches("[", "[!]-a]", 1, 1, 1, 1);
+ assert_matches("^", "[a^bc]", 1, 1, 1, 1);
+ assert_matches("-b]", "[a-]b]", 1, 1, 1, 1);
+ assert_matches("\\", "[\\]", 0, 0, 0, 0);
+ assert_matches("\\", "[\\\\]", 1, 1, 1, 1);
+ assert_matches("\\", "[!\\\\]", 0, 0, 0, 0);
+ assert_matches("G", "[A-\\\\]", 1, 1, 1, 1);
+ assert_matches("aaabbb", "b*a", 0, 0, 0, 0);
+ assert_matches("aabcaa", "*ba*", 0, 0, 0, 0);
+ assert_matches(",", "[,]", 1, 1, 1, 1);
+ assert_matches(",", "[\\\\,]", 1, 1, 1, 1);
+ assert_matches("\\", "[\\\\,]", 1, 1, 1, 1);
+ assert_matches("-", "[,-.]", 1, 1, 1, 1);
+ assert_matches("+", "[,-.]", 0, 0, 0, 0);
+ assert_matches("-.]", "[,-.]", 0, 0, 0, 0);
+ assert_matches("2", "[\\1-\\3]", 1, 1, 1, 1);
+ assert_matches("3", "[\\1-\\3]", 1, 1, 1, 1);
+ assert_matches("4", "[\\1-\\3]", 0, 0, 0, 0);
+ assert_matches("\\", "[[-\\]]", 1, 1, 1, 1);
+ assert_matches("[", "[[-\\]]", 1, 1, 1, 1);
+ assert_matches("]", "[[-\\]]", 1, 1, 1, 1);
+ assert_matches("-", "[[-\\]]", 0, 0, 0, 0);
+}
+
+void test_wildmatch__recursion(void)
+{
+ assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 1, 1, 1, 1);
+ assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 0, 0, 0, 0);
+ assert_matches("-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*", 0, 0, 0, 0);
+ assert_matches("XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*", 1, 1, 1, 1);
+ assert_matches("XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*", 0, 0, 0, 0);
+ assert_matches("abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt", "**/*a*b*g*n*t", 1, 1, 1, 1);
+ assert_matches("abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz", "**/*a*b*g*n*t", 0, 0, 0, 0);
+ assert_matches("foo", "*/*/*", 0, 0, 0, 0);
+ assert_matches("foo/bar", "*/*/*", 0, 0, 0, 0);
+ assert_matches("foo/bba/arr", "*/*/*", 1, 1, 1, 1);
+ assert_matches("foo/bb/aa/rr", "*/*/*", 0, 0, 1, 1);
+ assert_matches("foo/bb/aa/rr", "**/**/**", 1, 1, 1, 1);
+ assert_matches("abcXdefXghi", "*X*i", 1, 1, 1, 1);
+ assert_matches("ab/cXd/efXg/hi", "*X*i", 0, 0, 1, 1);
+ assert_matches("ab/cXd/efXg/hi", "*/*X*/*/*i", 1, 1, 1, 1);
+ assert_matches("ab/cXd/efXg/hi", "**/*X*/**/*i", 1, 1, 1, 1);
+}
+
+void test_wildmatch__pathmatch(void)
+{
+ assert_matches("foo", "fo", 0, 0, 0, 0);
+ assert_matches("foo/bar", "foo/bar", 1, 1, 1, 1);
+ assert_matches("foo/bar", "foo/*", 1, 1, 1, 1);
+ assert_matches("foo/bba/arr", "foo/*", 0, 0, 1, 1);
+ assert_matches("foo/bba/arr", "foo/**", 1, 1, 1, 1);
+ assert_matches("foo/bba/arr", "foo*", 0, 0, 1, 1);
+ assert_matches("foo/bba/arr", "foo**", 0, 0, 1, 1);
+ assert_matches("foo/bba/arr", "foo/*arr", 0, 0, 1, 1);
+ assert_matches("foo/bba/arr", "foo/**arr", 0, 0, 1, 1);
+ assert_matches("foo/bba/arr", "foo/*z", 0, 0, 0, 0);
+ assert_matches("foo/bba/arr", "foo/**z", 0, 0, 0, 0);
+ assert_matches("foo/bar", "foo?bar", 0, 0, 1, 1);
+ assert_matches("foo/bar", "foo[/]bar", 0, 0, 1, 1);
+ assert_matches("foo/bar", "foo[^a-z]bar", 0, 0, 1, 1);
+ assert_matches("ab/cXd/efXg/hi", "*Xg*i", 0, 0, 1, 1);
+}
+
+void test_wildmatch__case_sensitivity(void)
+{
+ assert_matches("a", "[A-Z]", 0, 1, 0, 1);
+ assert_matches("A", "[A-Z]", 1, 1, 1, 1);
+ assert_matches("A", "[a-z]", 0, 1, 0, 1);
+ assert_matches("a", "[a-z]", 1, 1, 1, 1);
+ assert_matches("a", "[[:upper:]]", 0, 1, 0, 1);
+ assert_matches("A", "[[:upper:]]", 1, 1, 1, 1);
+ assert_matches("A", "[[:lower:]]", 0, 1, 0, 1);
+ assert_matches("a", "[[:lower:]]", 1, 1, 1, 1);
+ assert_matches("A", "[B-Za]", 0, 1, 0, 1);
+ assert_matches("a", "[B-Za]", 1, 1, 1, 1);
+ assert_matches("A", "[B-a]", 0, 1, 0, 1);
+ assert_matches("a", "[B-a]", 1, 1, 1, 1);
+ assert_matches("z", "[Z-y]", 0, 1, 0, 1);
+ assert_matches("Z", "[Z-y]", 1, 1, 1, 1);
+}
diff --git a/tests/util/zstream.c b/tests/util/zstream.c
new file mode 100644
index 0000000..5c89895
--- /dev/null
+++ b/tests/util/zstream.c
@@ -0,0 +1,167 @@
+#include "clar_libgit2.h"
+#include "zstream.h"
+
+static const char *data = "This is a test test test of This is a test";
+
+#define INFLATE_EXTRA 2
+
+static void assert_zlib_equal_(
+ const void *expected, size_t e_len,
+ const void *compressed, size_t c_len,
+ const char *msg, const char *file, const char *func, int line)
+{
+ z_stream stream;
+ char *expanded = git__calloc(1, e_len + INFLATE_EXTRA);
+ cl_assert(expanded);
+
+ memset(&stream, 0, sizeof(stream));
+ stream.next_out = (Bytef *)expanded;
+ stream.avail_out = (uInt)(e_len + INFLATE_EXTRA);
+ stream.next_in = (Bytef *)compressed;
+ stream.avail_in = (uInt)c_len;
+
+ cl_assert(inflateInit(&stream) == Z_OK);
+ cl_assert(inflate(&stream, Z_FINISH));
+ inflateEnd(&stream);
+
+ clar__assert_equal(
+ file, func, line, msg, 1,
+ "%d", (int)stream.total_out, (int)e_len);
+ clar__assert_equal(
+ file, func, line, "Buffer len was not exact match", 1,
+ "%d", (int)stream.avail_out, (int)INFLATE_EXTRA);
+
+ clar__assert(
+ memcmp(expanded, expected, e_len) == 0,
+ file, func, line, "uncompressed data did not match", NULL, 1);
+
+ git__free(expanded);
+}
+
+#define assert_zlib_equal(E,EL,C,CL) \
+ assert_zlib_equal_(E, EL, C, CL, #EL " != " #CL, __FILE__, __func__, (int)__LINE__)
+
+void test_zstream__basic(void)
+{
+ git_zstream z = GIT_ZSTREAM_INIT;
+ char out[128];
+ size_t outlen = sizeof(out);
+
+ cl_git_pass(git_zstream_init(&z, GIT_ZSTREAM_DEFLATE));
+ cl_git_pass(git_zstream_set_input(&z, data, strlen(data) + 1));
+ cl_git_pass(git_zstream_get_output(out, &outlen, &z));
+ cl_assert(git_zstream_done(&z));
+ cl_assert(outlen > 0);
+ git_zstream_free(&z);
+
+ assert_zlib_equal(data, strlen(data) + 1, out, outlen);
+}
+
+void test_zstream__fails_on_trailing_garbage(void)
+{
+ git_str deflated = GIT_STR_INIT, inflated = GIT_STR_INIT;
+ char i = 0;
+
+ /* compress a simple string */
+ git_zstream_deflatebuf(&deflated, "foobar!!", 8);
+
+ /* append some garbage */
+ for (i = 0; i < 10; i++) {
+ git_str_putc(&deflated, i);
+ }
+
+ cl_git_fail(git_zstream_inflatebuf(&inflated, deflated.ptr, deflated.size));
+
+ git_str_dispose(&deflated);
+ git_str_dispose(&inflated);
+}
+
+void test_zstream__buffer(void)
+{
+ git_str out = GIT_STR_INIT;
+ cl_git_pass(git_zstream_deflatebuf(&out, data, strlen(data) + 1));
+ assert_zlib_equal(data, strlen(data) + 1, out.ptr, out.size);
+ git_str_dispose(&out);
+}
+
+#define BIG_STRING_PART "Big Data IS Big - Long Data IS Long - We need a buffer larger than 1024 x 1024 to make sure we trigger chunked compression - Big Big Data IS Bigger than Big - Long Long Data IS Longer than Long"
+
+static void compress_and_decompress_input_various_ways(git_str *input)
+{
+ git_str out1 = GIT_STR_INIT, out2 = GIT_STR_INIT;
+ git_str inflated = GIT_STR_INIT;
+ size_t i, fixed_size = max(input->size / 2, 256);
+ char *fixed = git__malloc(fixed_size);
+ cl_assert(fixed);
+
+ /* compress with deflatebuf */
+
+ cl_git_pass(git_zstream_deflatebuf(&out1, input->ptr, input->size));
+ assert_zlib_equal(input->ptr, input->size, out1.ptr, out1.size);
+
+ /* compress with various fixed size buffer (accumulating the output) */
+
+ for (i = 0; i < 3; ++i) {
+ git_zstream zs = GIT_ZSTREAM_INIT;
+ size_t use_fixed_size;
+
+ switch (i) {
+ case 0: use_fixed_size = 256; break;
+ case 1: use_fixed_size = fixed_size / 2; break;
+ case 2: use_fixed_size = fixed_size; break;
+ }
+ cl_assert(use_fixed_size <= fixed_size);
+
+ cl_git_pass(git_zstream_init(&zs, GIT_ZSTREAM_DEFLATE));
+ cl_git_pass(git_zstream_set_input(&zs, input->ptr, input->size));
+
+ while (!git_zstream_done(&zs)) {
+ size_t written = use_fixed_size;
+ cl_git_pass(git_zstream_get_output(fixed, &written, &zs));
+ cl_git_pass(git_str_put(&out2, fixed, written));
+ }
+
+ git_zstream_free(&zs);
+ assert_zlib_equal(input->ptr, input->size, out2.ptr, out2.size);
+
+ /* did both approaches give the same data? */
+ cl_assert_equal_sz(out1.size, out2.size);
+ cl_assert(!memcmp(out1.ptr, out2.ptr, out1.size));
+
+ git_str_dispose(&out2);
+ }
+
+ cl_git_pass(git_zstream_inflatebuf(&inflated, out1.ptr, out1.size));
+ cl_assert_equal_i(input->size, inflated.size);
+ cl_assert(memcmp(input->ptr, inflated.ptr, inflated.size) == 0);
+
+ git_str_dispose(&out1);
+ git_str_dispose(&inflated);
+ git__free(fixed);
+}
+
+void test_zstream__big_data(void)
+{
+ git_str in = GIT_STR_INIT;
+ size_t scan, target;
+
+ for (target = 1024; target <= 1024 * 1024 * 4; target *= 8) {
+
+ /* make a big string that's easy to compress */
+ git_str_clear(&in);
+ while (in.size < target)
+ cl_git_pass(
+ git_str_put(&in, BIG_STRING_PART, strlen(BIG_STRING_PART)));
+
+ compress_and_decompress_input_various_ways(&in);
+
+ /* make a big string that's hard to compress */
+ srand(0xabad1dea);
+ for (scan = 0; scan < in.size; ++scan)
+ in.ptr[scan] = (char)rand();
+
+ compress_and_decompress_input_various_ways(&in);
+ }
+
+ git_str_dispose(&in);
+}