diff --git a/CMakeLists.txt b/CMakeLists.txt index e149cd27f..f4c1da574 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,9 +43,10 @@ SET(INSTALL_INC include CACHE PATH "Where to install headers to.") # Build options OPTION (BUILD_SHARED_LIBS "Build Shared Library (OFF for Static)" ON) -OPTION (BUILD_TESTS "Build Tests" ON) OPTION (THREADSAFE "Build libgit2 as threadsafe" OFF) OPTION (STDCALL "Buildl libgit2 with the __stdcall convention (Windows)" ON) +OPTION (BUILD_TESTS "Build Tests" OFF) +OPTION (BUILD_CLAY "Build Tests using the Clay suite" ON) # Platform specific compilation flags IF (MSVC) @@ -113,11 +114,11 @@ INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc DESTINATION ${INSTALL_LIB}/ INSTALL(DIRECTORY include/git2 DESTINATION ${INSTALL_INC} ) INSTALL(FILES include/git2.h DESTINATION ${INSTALL_INC} ) +SET(TEST_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources" CACHE PATH "Path to test resources.") +ADD_DEFINITIONS(-DTEST_RESOURCES=\"${TEST_RESOURCES}\") + # Tests IF (BUILD_TESTS) - SET(TEST_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources" CACHE PATH "Path to test resources.") - ADD_DEFINITIONS(-DTEST_RESOURCES=\"${TEST_RESOURCES}\") - INCLUDE_DIRECTORIES(tests) FILE(GLOB SRC_TEST tests/t??-*.c) @@ -132,3 +133,19 @@ IF (BUILD_TESTS) ENABLE_TESTING() ADD_TEST(libgit2_test libgit2_test) ENDIF () + +IF (BUILD_CLAY) + INCLUDE_DIRECTORIES(tests-clay) + FILE(GLOB_RECURSE SRC_TEST tests-clay/*.c) + + ADD_EXECUTABLE(libgit2_test ${SRC} ${SRC_TEST} ${SRC_ZLIB}) + TARGET_LINK_LIBRARIES(libgit2_test ${CMAKE_THREAD_LIBS_INIT}) + IF (WIN32) + TARGET_LINK_LIBRARIES(libgit2_test ws2_32) + ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + TARGET_LINK_LIBRARIES(libgit2_test socket nsl) + ENDIF () + + ENABLE_TESTING() + ADD_TEST(libgit2_test libgit2_test) +ENDIF () diff --git a/tests-clay/clay b/tests-clay/clay new file mode 100755 index 000000000..4d803f556 --- /dev/null +++ b/tests-clay/clay @@ -0,0 +1,226 @@ +#!/usr/bin/env python + +from __future__ import with_statement +from string import Template +import re, fnmatch, os + +VERSION = "0.7.0" + +TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\(\s*(void)?\s*\))\s*\{" + +TEMPLATE_MAIN = Template( +r""" +/* + * Clay v${version} + * + * This is an autogenerated file. Do not modify. + * To add new unit tests or suites, regenerate the whole + * file with `./clay` + */ + +#define clay_print(...) ${clay_print} + +${clay_library} + +${extern_declarations} + +static const struct clay_func _all_callbacks[] = { + ${test_callbacks} +}; + +static const struct clay_suite _all_suites[] = { + ${test_suites} +}; + +static const char _suites_str[] = "${suites_str}"; + +int main(int argc, char *argv[]) +{ + return clay_test( + argc, argv, _suites_str, + _all_callbacks, ${cb_count}, + _all_suites, ${suite_count} + ); +} +""") + +TEMPLATE_SUITE = Template( +r""" + { + "${clean_name}", + ${initialize}, + ${cleanup}, + ${cb_ptr}, ${cb_count} + } +""") + +def main(): + from optparse import OptionParser + + parser = OptionParser() + + parser.add_option('-c', '--clay-path', dest='clay_path') + parser.add_option('-o', '--output', dest='output') + parser.add_option('-v', '--report-to', dest='print_mode', default='stdout') + + options, args = parser.parse_args() + + for folder in args: + builder = ClayTestBuilder(folder, + clay_path = options.clay_path, + output_folder = options.output, + print_mode = options.print_mode) + + builder.render() + + +class ClayTestBuilder: + def __init__(self, folder_name, output_folder = None, clay_path = None, print_mode = 'stdout'): + self.declarations = [] + self.callbacks = [] + self.suites = [] + self.suite_list = [] + + self.clay_path = os.path.abspath(clay_path) + self.print_mode = print_mode + + folder_name = os.path.abspath(folder_name) + if not output_folder: + output_folder = folder_name + + self.output = os.path.join(output_folder, "clay_main.c") + self.output_header = os.path.join(output_folder, "clay.h") + + self.modules = ["clay.c", "clay_sandbox.c"] + + print("Loading test suites...") + + for root, dirs, files in os.walk(folder_name): + module_root = root[len(folder_name):] + module_root = [c for c in module_root.split(os.sep) if c] + + tests_in_module = fnmatch.filter(files, "*.c") + + for test_file in tests_in_module: + full_path = os.path.join(root, test_file) + test_name = "_".join(module_root + [test_file[:-2]]) + + with open(full_path) as f: + self._process_test_file(test_name, f.read()) + + if not self.suites: + raise RuntimeError( + 'No tests found under "%s"' % folder_name) + + def render(self): + template = TEMPLATE_MAIN.substitute( + version = VERSION, + clay_print = self._get_print_method(), + clay_library = self._get_library(), + extern_declarations = "\n".join(self.declarations), + + suites_str = ", ".join(self.suite_list), + + test_callbacks = ",\n\t".join(self.callbacks), + cb_count = len(self.callbacks), + + test_suites = ",\n\t".join(self.suites), + suite_count = len(self.suites), + ) + + with open(self.output, "w") as out: + out.write(template) + + with open(self.output_header, "w") as out: + out.write(self._load_file('clay.h')) + + print ('Written test suite to "%s"' % self.output) + print ('Written header to "%s"' % self.output_header) + + ##################################################### + # Internal methods + ##################################################### + def _get_print_method(self): + return { + 'stdout' : 'printf(__VA_ARGS__)', + 'stderr' : 'fprintf(stderr, __VA_ARGS__)', + 'silent' : '' + }[self.print_mode] + + def _load_file(self, filename): + if self.clay_path: + filename = os.path.join(self.clay_path, filename) + with open(filename) as cfile: + return cfile.read() + + else: + import zlib, base64, sys + content = CLAY_FILES[filename] + + if sys.version_info >= (3, 0): + content = bytearray(content, 'utf_8') + content = base64.b64decode(content) + content = zlib.decompress(content) + return str(content) + else: + content = base64.b64decode(content) + return zlib.decompress(content) + + def _get_library(self): + return "\n".join(self._load_file(f) for f in self.modules) + + def _parse_comment(self, comment): + comment = comment[2:-2] + comment = comment.splitlines() + comment = [line.strip() for line in comment] + comment = "\n".join(comment) + + return comment + + def _process_test_file(self, test_name, contents): + regex_string = TEST_FUNC_REGEX % test_name + regex = re.compile(regex_string, re.MULTILINE) + + callbacks = [] + initialize = cleanup = "{NULL, NULL, 0}" + + for (declaration, symbol, short_name, _) in regex.findall(contents): + self.declarations.append("extern %s;" % declaration) + func_ptr = '{"%s", &%s, %d}' % ( + short_name, symbol, len(self.suites) + ) + + if short_name == 'initialize': + initialize = func_ptr + elif short_name == 'cleanup': + cleanup = func_ptr + else: + callbacks.append(func_ptr) + + if not callbacks: + return + + clean_name = test_name.replace("_", "::") + + suite = TEMPLATE_SUITE.substitute( + clean_name = clean_name, + initialize = initialize, + cleanup = cleanup, + cb_ptr = "&_all_callbacks[%d]" % len(self.callbacks), + cb_count = len(callbacks) + ).strip() + + self.callbacks += callbacks + self.suites.append(suite) + self.suite_list.append(clean_name) + + print(" %s (%d tests)" % (clean_name, len(callbacks))) + +CLAY_FILES = { +"clay.c" : r"""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""", +"clay_sandbox.c" : r"""eJx9VW1P2zAQ/pz8iiNoNKEZDRvaNGX9MIkyVRSoCohJtIpC4lCLxOlst1o3+O+7S0KalJeqamLf47vnOd9dd3kSswSCm+H550/mroELLhiMRz+uTi4mZ8HlYAyd6bRj7rJUsbcAPbKLmCemqXSoeQTRPJQQRGm4Dhahnt8eed++zPzazIU2uQpWYcrjQGeLAmRHuVC6PLpPG475zzSUlstIAx3EH980JNNLKcBOacsmnAt7SjvQ74MHe3umYdiXwfDyeDixlT5QOsjymDnw+IgWMozOT5sGpzwTRhFTqvJ3E1yclg4d33xq0Ub5TcoF2btlkjDpguJ/WaAhZeK+Zl+mo1BWmVehDKJ8KTT04cjfwpQOmVhhcqS6nSEG3RjW1dkYBVku0FvxGJTP68vBZDy5OBmOBpYJxhMmGYwqEsfFbuuGDZ6A/ZPpK5YtxiTAPr65mBw7JWUXSiWOgyGrPB/69d0aSS7B5kjJ84HD940SH7pd7hRMt2Qg+J5pfLFrTXyGSTUKJju4SbHolOZiyZBwaXlZHQQtQ1BNiGixtp/zjib3OevkusHdMJ5M/JpGbx+GCeg5IzSXucgY3kCcMyU6eDXhGkKx1nMu7l3Qcg06h6Vi0MP4sN8z3yBlkd2qeG3TKo0tZg1iFamrOVeg1kqzrGaT8geG0VtEiHm0lLJgzSWLdI7Gd5gdvEnr4F1O1dLbKnyIUhaKIMnTmMnXW7WxmasgymIq3+0CNCwZQ09B7zdMrQ9qall+NVzIlMFHmTQNVeUVTqM8y0IRPw8TQ4mFRGaJXRnKBszrteNWPFwoOG6GR5nvGteUusp5DMXgWgqFxrv8j017hUJKdWOqeTOaEZ2p19k0DFVwK1Ub/PYsKcO8CNJII50KdMjTYhBYBZ5u+FfxsSgH9cihwEVPtSfUJnydngajZqd75AEdYSQsGXxpU3+hnqAf4XAGO/3W/0FZdW1gt0sCmqiq2jAS1eYGDV0S426k16GzB7yzRZMUZf/8ejTaFlGisUta6r2vnucQWe81fDRv419HbnoFyf8Hmxc92w==""", +"clay.h" : r"""eJy9VUFvmzAUPpdf8RYuAaEmu3ZdpapKtEqol2WaerJc+1GsOTazzZr9+9lAkoaA2HbICfPw5+97733PxKJQHAsg5CG/fyab1dcN+UJIFPugUHgWj2KhmKw5wq11XIqX6/Iuin5pwYFJ+psQai0aN4+uhHLAtOLCCa2y6MqvrY+U1EBaCIlZu0V6lt5XNEabXoyjZUZU3VkBaEtdS07oizYu+XTQQCw6wiRSVVfzJjZPu9ekfU+TDNqFrujPGgN4kaYRpHDfiPccsKXMaAtvwpWAu0oKJhw0wmCL1tJX9PsXhzJ53m1tHal8+mTuASaDIDk5rUrzJYG7z7DMfGnXj/mKkLDKH5+a1WxdK9YIYFRKKKgvFL+BGcTHMzP4mJwRh53TxLejvKtdhcwhh+JEgNONhgkFLck0/YfxvD2/8XUNxMKC0g6cqXGId9+rhxLZj//oEwu4CzVqmZwzX7hTQxIu0KhlMjFUSv/lOHWSTiesDWbw9C3Ph4ehB2urPgrr0j9g9o4+BYxbbyqbo+mOHO+NOCrs6Jk+cCKjZlMf089nn9BaGxYc5Y+sDS7eqFFCvfaTaFQMmGbMKhv013c7Gdez4ZujY/qXU7+3EOC1CQ8XSHCHrA4Wu5m9N2CM/vdTRH8A5MtAqA==""" +} + +if __name__ == '__main__': + main() diff --git a/tests-clay/clay.h b/tests-clay/clay.h new file mode 100644 index 000000000..382b140e8 --- /dev/null +++ b/tests-clay/clay.h @@ -0,0 +1,51 @@ +#ifndef __CLAY_TEST_H__ +#define __CLAY_TEST_H__ + +#include + +void clay__assert( + int condition, + const char *file, + int line, + const char *error, + const char *description, + int should_abort); + +void cl_set_cleanup(void (*cleanup)(void *), void *opaque); + +/** + * Assertion macros with explicit error message + */ +#define cl_must_pass_(expr, desc) clay__assert((expr) >= 0, __FILE__, __LINE__, "Function call failed: " #expr, desc, 1) +#define cl_must_fail_(expr, desc) clay__assert((expr) < 0, __FILE__, __LINE__, "Expected function call to fail: " #expr, desc, 1) +#define cl_assert_(expr, desc) clay__assert((expr) != 0, __FILE__, __LINE__, "Expression is not true: " #expr, desc, 1) + +/** + * Check macros with explicit error message + */ +#define cl_check_pass_(expr, desc) clay__assert((expr) >= 0, __FILE__, __LINE__, "Function call failed: " #expr, desc, 0) +#define cl_check_fail_(expr, desc) clay__assert((expr) < 0, __FILE__, __LINE__, "Expected function call to fail: " #expr, desc, 0) +#define cl_check_(expr, desc) clay__assert((expr) != 0, __FILE__, __LINE__, "Expression is not true: " #expr, desc, 0) + +/** + * Assertion macros with no error message + */ +#define cl_must_pass(expr) cl_must_pass_((expr), NULL) +#define cl_must_fail(expr) cl_must_fail_((expr), NULL) +#define cl_assert(expr) cl_assert_((expr), NULL) + +/** + * Check macros with no error message + */ +#define cl_check_pass(expr) cl_check_pass_((expr), NULL) +#define cl_check_fail(expr) cl_check_fail_((expr), NULL) +#define cl_check(expr) cl_check_((expr), NULL) + + +/** + * Forced failure/warning + */ +#define cl_fail(desc) clay__assert(0, __FILE__, __LINE__, "Test failed.", desc, 1) +#define cl_warning(desc) clay__assert(0, __FILE__, __LINE__, "Warning during test execution:", desc, 0) + +#endif diff --git a/tests-clay/clay_libgit2.h b/tests-clay/clay_libgit2.h new file mode 100644 index 000000000..ab3cf67ec --- /dev/null +++ b/tests-clay/clay_libgit2.h @@ -0,0 +1,28 @@ +#ifndef __CLAY_LIBGIT2__ +#define __CLAY_LIBGIT2__ + +#include "clay.h" +#include +#include "common.h" + +/** + * Special wrapper for `clay_must_pass` that passes + * the last library error as the test failure message. + * + * Use this wrapper around all `git_` library calls that + * return error codes! + */ +#define cl_git_pass(expr) do { \ + git_clearerror(); \ + if ((expr) != GIT_SUCCESS) \ + clay__assert(0, __FILE__, __LINE__, "Function call failed: " #expr, git_lasterror(), 1); \ + } while(0); + +/** + * Wrapper for `clay_must_fail` -- this one is + * just for consistency. Use with `git_` library + * calls that are supposed to fail! + */ +#define cl_git_fail(expr) cl_must_fail((expr)) + +#endif diff --git a/tests-clay/clay_main.c b/tests-clay/clay_main.c new file mode 100644 index 000000000..39336b326 --- /dev/null +++ b/tests-clay/clay_main.c @@ -0,0 +1,531 @@ + +/* + * Clay v0.7.0 + * + * This is an autogenerated file. Do not modify. + * To add new unit tests or suites, regenerate the whole + * file with `./clay` + */ + +#define clay_print(...) printf(__VA_ARGS__) + +#include +#include +#include +#include +#include +#include + +/* required for sandboxing */ +#include +#include + +#include "clay.h" + +struct clay_error { + const char *test; + int test_number; + const char *suite; + const char *file; + int line_number; + const char *error_msg; + char *description; + + struct clay_error *next; +}; + +static struct { + const char *active_test; + const char *active_suite; + + int suite_errors; + int total_errors; + + int test_count; + + struct clay_error *errors; + struct clay_error *last_error; + + void (*local_cleanup)(void *); + void *local_cleanup_payload; + + jmp_buf trampoline; + int trampoline_enabled; +} _clay; + +struct clay_func { + const char *name; + void (*ptr)(void); + size_t suite_n; +}; + +struct clay_suite { + const char *name; + struct clay_func initialize; + struct clay_func cleanup; + const struct clay_func *tests; + size_t test_count; +}; + +/* From clay_sandbox.c */ +static void clay_unsandbox(void); +static int clay_sandbox(void); + +static void +clay_run_test( + const struct clay_func *test, + const struct clay_func *initialize, + const struct clay_func *cleanup) +{ + int error_st = _clay.suite_errors; + + _clay.trampoline_enabled = 1; + + if (setjmp(_clay.trampoline) == 0) { + if (initialize->ptr != NULL) + initialize->ptr(); + + test->ptr(); + } + + _clay.trampoline_enabled = 0; + + if (_clay.local_cleanup != NULL) + _clay.local_cleanup(_clay.local_cleanup_payload); + + if (cleanup->ptr != NULL) + cleanup->ptr(); + + _clay.test_count++; + + /* remove any local-set cleanup methods */ + _clay.local_cleanup = NULL; + _clay.local_cleanup_payload = NULL; + + clay_print("%c", (_clay.suite_errors > error_st) ? 'F' : '.'); +} + +static void +clay_print_error(int num, const struct clay_error *error) +{ + clay_print(" %d) Failure:\n", num); + + clay_print("%s::%s (%s) [%s:%d] [-t%d]\n", + error->suite, + error->test, + "no description", + error->file, + error->line_number, + error->test_number); + + clay_print(" %s\n", error->error_msg); + + if (error->description != NULL) + clay_print(" %s\n", error->description); + + clay_print("\n"); +} + +static void +clay_report_errors(void) +{ + int i = 1; + struct clay_error *error, *next; + + error = _clay.errors; + while (error != NULL) { + next = error->next; + clay_print_error(i++, error); + free(error->description); + free(error); + error = next; + } +} + +static void +clay_run_suite(const struct clay_suite *suite) +{ + const struct clay_func *test = suite->tests; + size_t i; + + _clay.active_suite = suite->name; + _clay.suite_errors = 0; + + for (i = 0; i < suite->test_count; ++i) { + _clay.active_test = test[i].name; + clay_run_test(&test[i], &suite->initialize, &suite->cleanup); + } +} + +static void +clay_run_single(const struct clay_func *test, + const struct clay_suite *suite) +{ + _clay.suite_errors = 0; + _clay.active_suite = suite->name; + _clay.active_test = test->name; + + clay_run_test(test, &suite->initialize, &suite->cleanup); +} + +static void +clay_usage(const char *arg) +{ + printf("Usage: %s [options]\n\n", arg); + printf("Options:\n"); + printf(" -tXX\t\tRun only the test number XX\n"); + printf(" -sXX\t\tRun only the suite number XX\n"); + exit(-1); +} + +static void +clay_parse_args( + int argc, char **argv, + const struct clay_func *callbacks, + size_t cb_count, + const struct clay_suite *suites, + size_t suite_count) +{ + int i; + + for (i = 1; i < argc; ++i) { + char *argument = argv[i]; + char action; + int num; + + if (argument[0] != '-') + clay_usage(argv[0]); + + action = argument[1]; + num = strtol(argument + 2, &argument, 10); + + if (*argument != '\0' || num < 0) + clay_usage(argv[0]); + + switch (action) { + case 't': + if ((size_t)num >= cb_count) { + fprintf(stderr, "Test number %d does not exist.\n", num); + exit(-1); + } + + clay_print("Started (%s::%s)\n", + suites[callbacks[num].suite_n].name, + callbacks[num].name); + + clay_run_single(&callbacks[num], &suites[callbacks[num].suite_n]); + break; + + case 's': + if ((size_t)num >= suite_count) { + fprintf(stderr, "Suite number %d does not exist.\n", num); + exit(-1); + } + + clay_print("Started (%s::*)\n", suites[num].name); + clay_run_suite(&suites[num]); + break; + + default: + clay_usage(argv[0]); + } + } +} + +static int +clay_test( + int argc, char **argv, + const char *suites_str, + const struct clay_func *callbacks, + size_t cb_count, + const struct clay_suite *suites, + size_t suite_count) +{ + clay_print("Loaded %d suites: %s\n", (int)suite_count, suites_str); + + if (!clay_sandbox()) { + fprintf(stderr, + "Failed to sandbox the test runner.\n" + "Testing will proceed without sandboxing.\n"); + } + + if (argc > 1) { + clay_parse_args(argc, argv, + callbacks, cb_count, suites, suite_count); + + } else { + size_t i; + clay_print("Started\n"); + + for (i = 0; i < suite_count; ++i) { + const struct clay_suite *s = &suites[i]; + clay_run_suite(s); + } + } + + clay_print("\n\n"); + clay_report_errors(); + + clay_unsandbox(); + return _clay.total_errors; +} + +void +clay__assert( + int condition, + const char *file, + int line, + const char *error_msg, + const char *description, + int should_abort) +{ + struct clay_error *error; + + if (condition) + return; + + error = calloc(1, sizeof(struct clay_error)); + + if (_clay.errors == NULL) + _clay.errors = error; + + if (_clay.last_error != NULL) + _clay.last_error->next = error; + + _clay.last_error = error; + + error->test = _clay.active_test; + error->test_number = _clay.test_count; + error->suite = _clay.active_suite; + error->file = file; + error->line_number = line; + error->error_msg = error_msg; + + if (description != NULL) + error->description = strdup(description); + + _clay.suite_errors++; + _clay.total_errors++; + + if (should_abort) { + if (!_clay.trampoline_enabled) { + fprintf(stderr, + "Unhandled exception: a cleanup method raised an exception."); + exit(-1); + } + + longjmp(_clay.trampoline, -1); + } +} + +void cl_set_cleanup(void (*cleanup)(void *), void *opaque) +{ + _clay.local_cleanup = cleanup; + _clay.local_cleanup_payload = opaque; +} + +#ifdef _WIN32 +# define PLATFORM_SEP '\\' +#else +# define PLATFORM_SEP '/' +#endif + +static char _clay_path[4096]; + +static int +is_valid_tmp_path(const char *path) +{ + struct stat st; + return (lstat(path, &st) == 0 && + (S_ISDIR(st.st_mode) || + S_ISLNK(st.st_mode)) && + access(path, W_OK) == 0); +} + +static int +find_tmp_path(char *buffer, size_t length) +{ + static const size_t var_count = 4; + static const char *env_vars[] = { + "TMPDIR", "TMP", "TEMP", "USERPROFILE" + }; + + size_t i; + +#ifdef _WIN32 + if (GetTempPath((DWORD)length, buffer)) + return 1; +#endif + + for (i = 0; i < var_count; ++i) { + const char *env = getenv(env_vars[i]); + if (!env) + continue; + + if (is_valid_tmp_path(env)) { + strncpy(buffer, env, length); + return 1; + } + } + + /* If the environment doesn't say anything, try to use /tmp */ + if (is_valid_tmp_path("/tmp")) { + strncpy(buffer, "/tmp", length); + return 1; + } + + /* This system doesn't like us, try to use the current directory */ + if (is_valid_tmp_path(".")) { + strncpy(buffer, ".", length); + return 1; + } + + return 0; +} + +static int clean_folder(const char *path) +{ + const char os_cmd[] = +#ifdef _WIN32 + "rd /s /q \"%s\""; +#else + "rm -rf \"%s\""; +#endif + + char command[4096]; + snprintf(command, sizeof(command), os_cmd, path); + return system(command); +} + +static void clay_unsandbox(void) +{ + if (_clay_path[0] == '\0') + return; + + clean_folder(_clay_path); +} + +static int clay_sandbox(void) +{ + const char path_tail[] = "clay_tmp_XXXXXX"; + size_t len; + + if (!find_tmp_path(_clay_path, sizeof(_clay_path))) + return 0; + + len = strlen(_clay_path); + + if (_clay_path[len - 1] != PLATFORM_SEP) { + _clay_path[len++] = PLATFORM_SEP; + } + + strcpy(_clay_path + len, path_tail); + + if (mktemp(_clay_path) == NULL) + return 0; + + if (mkdir(_clay_path, 0700) != 0) + return 0; + + if (chdir(_clay_path) != 0) + return 0; + + return 1; +} + + + +extern void test_core_dirent__dont_traverse_dot(void); +extern void test_core_dirent__traverse_subfolder(void); +extern void test_core_dirent__traverse_slash_terminated_folder(void); +extern void test_core_dirent__dont_traverse_empty_folders(void); +extern void test_core_dirent__traverse_weird_filenames(void); +extern void test_core_filebuf__0(void); +extern void test_core_filebuf__1(void); +extern void test_core_filebuf__2(void); +extern void test_core_path__0(void); +extern void test_core_path__1(void); +extern void test_core_path__2(void); +extern void test_core_path__5(void); +extern void test_core_path__6(void); +extern void test_core_rmdir__initialize(); +extern void test_core_rmdir__delete_recursive(void); +extern void test_core_rmdir__fail_to_delete_non_empty_dir(void); +extern void test_core_string__0(void); +extern void test_core_string__1(void); +extern void test_core_vector__0(void); +extern void test_core_vector__1(void); +extern void test_core_vector__2(void); + +static const struct clay_func _all_callbacks[] = { + {"dont_traverse_dot", &test_core_dirent__dont_traverse_dot, 0}, + {"traverse_subfolder", &test_core_dirent__traverse_subfolder, 0}, + {"traverse_slash_terminated_folder", &test_core_dirent__traverse_slash_terminated_folder, 0}, + {"dont_traverse_empty_folders", &test_core_dirent__dont_traverse_empty_folders, 0}, + {"traverse_weird_filenames", &test_core_dirent__traverse_weird_filenames, 0}, + {"0", &test_core_filebuf__0, 1}, + {"1", &test_core_filebuf__1, 1}, + {"2", &test_core_filebuf__2, 1}, + {"0", &test_core_path__0, 2}, + {"1", &test_core_path__1, 2}, + {"2", &test_core_path__2, 2}, + {"5", &test_core_path__5, 2}, + {"6", &test_core_path__6, 2}, + {"delete_recursive", &test_core_rmdir__delete_recursive, 3}, + {"fail_to_delete_non_empty_dir", &test_core_rmdir__fail_to_delete_non_empty_dir, 3}, + {"0", &test_core_string__0, 4}, + {"1", &test_core_string__1, 4}, + {"0", &test_core_vector__0, 5}, + {"1", &test_core_vector__1, 5}, + {"2", &test_core_vector__2, 5} +}; + +static const struct clay_suite _all_suites[] = { + { + "core::dirent", + {NULL, NULL, 0}, + {NULL, NULL, 0}, + &_all_callbacks[0], 5 + }, + { + "core::filebuf", + {NULL, NULL, 0}, + {NULL, NULL, 0}, + &_all_callbacks[5], 3 + }, + { + "core::path", + {NULL, NULL, 0}, + {NULL, NULL, 0}, + &_all_callbacks[8], 5 + }, + { + "core::rmdir", + {"initialize", &test_core_rmdir__initialize, 3}, + {NULL, NULL, 0}, + &_all_callbacks[13], 2 + }, + { + "core::string", + {NULL, NULL, 0}, + {NULL, NULL, 0}, + &_all_callbacks[15], 2 + }, + { + "core::vector", + {NULL, NULL, 0}, + {NULL, NULL, 0}, + &_all_callbacks[17], 3 + } +}; + +static const char _suites_str[] = "core::dirent, core::filebuf, core::path, core::rmdir, core::string, core::vector"; + +int main(int argc, char *argv[]) +{ + return clay_test( + argc, argv, _suites_str, + _all_callbacks, 20, + _all_suites, 6 + ); +} diff --git a/tests-clay/core/dirent.c b/tests-clay/core/dirent.c new file mode 100644 index 000000000..ed51890e6 --- /dev/null +++ b/tests-clay/core/dirent.c @@ -0,0 +1,222 @@ +#include "clay_libgit2.h" +#include "fileops.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 */ +} walk_data; + + +static char path_buffer[GIT_PATH_MAX]; +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, 0755)); + + cl_must_pass(p_chdir(top_dir)); + + if (strcmp(d->sub, ".") != 0) + cl_must_pass(p_mkdir(d->sub, 0755)); + + strcpy(path_buffer, d->sub); + state_loc = d; + + for (n = d->names; n->name; n++) { + git_file fd = p_creat(n->name, 0600); + 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)); +} + +static void check_counts(walk_data *d) +{ + name_data *n; + + for (n = d->names; n->name; n++) { + cl_assert(n->count == 1); + } +} + +static int one_entry(void *state, char *path) +{ + walk_data *d = (walk_data *) state; + name_data *n; + + if (state != state_loc) + return GIT_ERROR; + + if (path != path_buffer) + return GIT_ERROR; + + for (n = d->names; n->name; n++) { + if (!strcmp(n->name, path)) { + n->count++; + return 0; + } + } + + return GIT_ERROR; +} + +static int dont_call_me(void *GIT_UNUSED(state), char *GIT_UNUSED(path)) +{ + GIT_UNUSED_ARG(state) + GIT_UNUSED_ARG(path) + return GIT_ERROR; +} + + + +static name_data dot_names[] = { + { 0, "./a" }, + { 0, "./asdf" }, + { 0, "./pack-foo.pack" }, + { 0, NULL } +}; +static walk_data dot = { + ".", + dot_names +}; + +/* make sure that the '.' folder is not traversed */ +void test_core_dirent__dont_traverse_dot(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &dot); + setup(&dot); + + cl_git_pass(git_futils_direach(path_buffer, + sizeof(path_buffer), + 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 +}; + +/* traverse a subfolder */ +void test_core_dirent__traverse_subfolder(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &sub); + setup(&sub); + + cl_git_pass(git_futils_direach(path_buffer, + sizeof(path_buffer), + one_entry, + &sub)); + + check_counts(&sub); +} + + +static walk_data sub_slash = { + "sub/", + sub_names +}; + +/* traverse a slash-terminated subfolder */ +void test_core_dirent__traverse_slash_terminated_folder(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &sub_slash); + setup(&sub_slash); + + cl_git_pass(git_futils_direach(path_buffer, + sizeof(path_buffer), + one_entry, + &sub_slash)); + + check_counts(&sub_slash); +} + + +static name_data empty_names[] = { + { 0, NULL } +}; +static walk_data empty = { + "empty", + empty_names +}; + +/* make sure that empty folders are not traversed */ +void test_core_dirent__dont_traverse_empty_folders(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &empty); + setup(&empty); + + cl_git_pass(git_futils_direach(path_buffer, + sizeof(path_buffer), + one_entry, + &empty)); + + check_counts(&empty); + + /* make sure callback not called */ + cl_git_pass(git_futils_direach(path_buffer, + sizeof(path_buffer), + dont_call_me, + &empty)); +} + +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 +}; + +/* make sure that strange looking filenames ('..c') are traversed */ +void test_core_dirent__traverse_weird_filenames(void) +{ + cl_set_cleanup(&dirent_cleanup__cb, &odd); + setup(&odd); + + cl_git_pass(git_futils_direach(path_buffer, + sizeof(path_buffer), + one_entry, + &odd)); + + check_counts(&odd); +} diff --git a/tests-clay/core/filebuf.c b/tests-clay/core/filebuf.c new file mode 100644 index 000000000..e00e20497 --- /dev/null +++ b/tests-clay/core/filebuf.c @@ -0,0 +1,58 @@ +#include "clay_libgit2.h" +#include "filebuf.h" + +/* make sure git_filebuf_open doesn't delete an existing lock */ +void test_core_filebuf__0(void) +{ + git_filebuf file; + int fd; + char test[] = "test", testlock[] = "test.lock"; + + fd = p_creat(testlock, 0744); + + cl_must_pass(fd); + cl_must_pass(p_close(fd)); + + cl_git_fail(git_filebuf_open(&file, test, 0)); + cl_git_pass(git_futils_exists(testlock)); + + cl_must_pass(p_unlink(testlock)); +} + + +/* make sure GIT_FILEBUF_APPEND works as expected */ +void test_core_filebuf__1(void) +{ + git_filebuf file; + int fd; + char test[] = "test"; + + fd = p_creat(test, 0644); + cl_must_pass(fd); + cl_must_pass(p_write(fd, "libgit2 rocks\n", 14)); + cl_must_pass(p_close(fd)); + + cl_git_pass(git_filebuf_open(&file, test, GIT_FILEBUF_APPEND)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + cl_git_pass(git_filebuf_commit(&file)); + + cl_must_pass(p_unlink(test)); +} + + +/* make sure git_filebuf_write writes large buffer correctly */ +void test_core_filebuf__2(void) +{ + git_filebuf file; + 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)); + cl_git_pass(git_filebuf_write(&file, buf, sizeof(buf))); + cl_git_pass(git_filebuf_commit(&file)); + + cl_must_pass(p_unlink(test)); +} + diff --git a/tests-clay/core/path.c b/tests-clay/core/path.c new file mode 100644 index 000000000..db8f33d21 --- /dev/null +++ b/tests-clay/core/path.c @@ -0,0 +1,139 @@ +#include "clay_libgit2.h" +#include + +static void +check_dirname(const char *A, const char *B) +{ + char dir[64], *dir2; + + cl_assert(git_path_dirname_r(dir, sizeof(dir), A) >= 0); + cl_assert(strcmp(dir, B) == 0); + cl_assert((dir2 = git_path_dirname(A)) != NULL); + cl_assert(strcmp(dir2, B) == 0); + + free(dir2); +} + +static void +check_basename(const char *A, const char *B) +{ + char base[64], *base2; + + cl_assert(git_path_basename_r(base, sizeof(base), A) >= 0); + cl_assert(strcmp(base, B) == 0); + cl_assert((base2 = git_path_basename(A)) != NULL); + cl_assert(strcmp(base2, B) == 0); + + free(base2); +} + +static void +check_topdir(const char *A, const char *B) +{ + const char *dir; + + cl_assert((dir = git_path_topdir(A)) != NULL); + cl_assert(strcmp(dir, B) == 0); +} + +static void +check_joinpath(const char *path_a, const char *path_b, const char *expected_path) +{ + char joined_path[GIT_PATH_MAX]; + + git_path_join(joined_path, path_a, path_b); + cl_assert(strcmp(joined_path, expected_path) == 0); +} + +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) +{ + char joined_path[GIT_PATH_MAX]; + + git_path_join_n(joined_path, 4, path_a, path_b, path_c, path_d); + cl_assert(strcmp(joined_path, expected_path) == 0); +} + + +/* get the dirname of a path */ +void test_core_path__0(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/", "."); +} + +/* get the base name of a path */ +void test_core_path__1(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"); +} + +/* get the latest component in a path */ +void test_core_path__2(void) +{ + check_topdir(".git/", ".git/"); + check_topdir("/.git/", ".git/"); + check_topdir("usr/local/.git/", ".git/"); + check_topdir("./.git/", ".git/"); + check_topdir("/usr/.git/", ".git/"); + check_topdir("/", "/"); + check_topdir("a/", "a/"); + + cl_assert(git_path_topdir("/usr/.git") == NULL); + cl_assert(git_path_topdir(".") == NULL); + cl_assert(git_path_topdir("") == NULL); + cl_assert(git_path_topdir("a") == NULL); +} + +/* properly join path components */ +void test_core_path__5(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/"); +} + +/* properly join path components for more than one path */ +void test_core_path__6(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"); +} diff --git a/tests-clay/core/rmdir.c b/tests-clay/core/rmdir.c new file mode 100644 index 000000000..9f946d98a --- /dev/null +++ b/tests-clay/core/rmdir.c @@ -0,0 +1,50 @@ +#include "clay_libgit2.h" +#include "fileops.h" + +static const char *empty_tmp_dir = "test_gitfo_rmdir_recurs_test"; + +void test_core_rmdir__initialize() +{ + char path[GIT_PATH_MAX]; + + cl_must_pass(p_mkdir(empty_tmp_dir, 0755)); + + git_path_join(path, empty_tmp_dir, "/one"); + cl_must_pass(p_mkdir(path, 0755)); + + git_path_join(path, empty_tmp_dir, "/one/two_one"); + cl_must_pass(p_mkdir(path, 0755)); + + git_path_join(path, empty_tmp_dir, "/one/two_two"); + cl_must_pass(p_mkdir(path, 0755)); + + git_path_join(path, empty_tmp_dir, "/one/two_two/three"); + cl_must_pass(p_mkdir(path, 0755)); + + git_path_join(path, empty_tmp_dir, "/two"); + cl_must_pass(p_mkdir(path, 0755)); +} + +/* make sure empty dir can be deleted recusively */ +void test_core_rmdir__delete_recursive(void) +{ + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, 0)); +} + +/* make sure non-empty dir cannot be deleted recusively */ +void test_core_rmdir__fail_to_delete_non_empty_dir(void) +{ + char file[GIT_PATH_MAX]; + int fd; + + git_path_join(file, empty_tmp_dir, "/two/file.txt"); + + fd = p_creat(file, 0755); + cl_assert(fd >= 0); + + cl_must_pass(p_close(fd)); + cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, 0)); + + cl_must_pass(p_unlink(file)); + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, 0)); +} diff --git a/tests-clay/core/string.c b/tests-clay/core/string.c new file mode 100644 index 000000000..c154aaf18 --- /dev/null +++ b/tests-clay/core/string.c @@ -0,0 +1,28 @@ +#include "clay_libgit2.h" + +/* compare prefixes */ +void test_core_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_core_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); +} + diff --git a/tests-clay/core/vector.c b/tests-clay/core/vector.c new file mode 100644 index 000000000..ba9d18857 --- /dev/null +++ b/tests-clay/core/vector.c @@ -0,0 +1,66 @@ +#include "clay_libgit2.h" +#include "vector.h" + +/* initial size of 1 would cause writing past array bounds */ +void test_core_vector__0(void) +{ + git_vector x; + int i; + 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_core_vector__1(void) +{ + git_vector x; + // make initial capacity exact for our insertions. + 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_core_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); + cl_assert(x.length == 2); + + git_vector_free(&x); + + free(ptrs[0]); + free(ptrs[1]); +} + +