Commit 11385c3c by Vicent Marti

Add sample "Status" clay tests

parent d8b903da
......@@ -114,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)
......@@ -135,6 +135,9 @@ IF (BUILD_TESTS)
ENDIF ()
IF (BUILD_CLAY)
SET(CLAY_FIXTURES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources/")
ADD_DEFINITIONS(-DCLAY_FIXTURE_PATH=\"${CLAY_FIXTURES}\")
INCLUDE_DIRECTORIES(tests-clay)
FILE(GLOB_RECURSE SRC_TEST tests-clay/*.c)
......
......@@ -12,6 +12,13 @@ void clay__assert(
int should_abort);
void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
void cl_fs_cleanup(void);
#ifdef CLAY_FIXTURE_PATH
const char *cl_fixture(const char *fixture_name);
void cl_fixture_sandbox(const char *fixture_name);
void cl_fixture_cleanup(const char *fixture_name);
#endif
/**
* Assertion macros with explicit error message
......
......@@ -17,11 +17,36 @@
#include <math.h>
/* required for sandboxing */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef _WIN32
# include <windows.h>
# include <io.h>
# include <Shellapi.h>
# pragma comment(lib, "shell32")
# define stat(path, st) _stat(path, st)
# define mkdir(path, mode) _mkdir(path)
# define access(path, mode) _access(path, mode)
# define mktemp(path) _mktemp(path)
# define W_OK 02
# define S_ISDIR(x) (x & _S_IFDIR) != 0
typedef struct _stat STAT_T;
#else
# include <unistd.h>
typedef struct stat STAT_T;
#endif
#include "clay.h"
static void fs_rm(const char *_source);
static void fs_copy(const char *_source, const char *dest);
static const char *
fixture_path(const char *base, const char *fixture_name);
struct clay_error {
const char *test;
int test_number;
......@@ -243,7 +268,7 @@ clay_test(
{
clay_print("Loaded %d suites: %s\n", (int)suite_count, suites_str);
if (!clay_sandbox()) {
if (clay_sandbox() < 0) {
fprintf(stderr,
"Failed to sandbox the test runner.\n"
"Testing will proceed without sandboxing.\n");
......@@ -310,7 +335,7 @@ clay__assert(
if (should_abort) {
if (!_clay.trampoline_enabled) {
fprintf(stderr,
"Unhandled exception: a cleanup method raised an exception.");
"Fatal error: a cleanup method raised an exception.");
exit(-1);
}
......@@ -324,22 +349,20 @@ void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
_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);
STAT_T st;
if (stat(path, &st) != 0)
return 0;
if (!S_ISDIR(st.st_mode))
return 0;
return (access(path, W_OK) == 0);
}
static int
......@@ -354,7 +377,7 @@ find_tmp_path(char *buffer, size_t length)
#ifdef _WIN32
if (GetTempPath((DWORD)length, buffer))
return 1;
return 0;
#endif
for (i = 0; i < var_count; ++i) {
......@@ -364,37 +387,23 @@ find_tmp_path(char *buffer, size_t length)
if (is_valid_tmp_path(env)) {
strncpy(buffer, env, length);
return 1;
return 0;
}
}
/* If the environment doesn't say anything, try to use /tmp */
if (is_valid_tmp_path("/tmp")) {
strncpy(buffer, "/tmp", length);
return 1;
return 0;
}
/* 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);
return -1;
}
static void clay_unsandbox(void)
......@@ -402,37 +411,245 @@ static void clay_unsandbox(void)
if (_clay_path[0] == '\0')
return;
clean_folder(_clay_path);
#ifdef _WIN32
chdir("..");
#endif
fs_rm(_clay_path);
}
static int clay_sandbox(void)
static int build_sandbox_path(void)
{
const char path_tail[] = "clay_tmp_XXXXXX";
size_t len;
if (!find_tmp_path(_clay_path, sizeof(_clay_path)))
return 0;
if (find_tmp_path(_clay_path, sizeof(_clay_path)) < 0)
return -1;
len = strlen(_clay_path);
if (_clay_path[len - 1] != PLATFORM_SEP) {
_clay_path[len++] = PLATFORM_SEP;
#ifdef _WIN32
{ /* normalize path to POSIX forward slashes */
size_t i;
for (i = 0; i < len; ++i) {
if (_clay_path[i] == '\\')
_clay_path[i] = '/';
}
}
#endif
if (_clay_path[len - 1] != '/') {
_clay_path[len++] = '/';
}
strcpy(_clay_path + len, path_tail);
if (mktemp(_clay_path) == NULL)
return -1;
return 0;
}
static int clay_sandbox(void)
{
if (_clay_path[0] == '\0' && build_sandbox_path() < 0)
return -1;
if (mkdir(_clay_path, 0700) != 0)
return 0;
return -1;
if (chdir(_clay_path) != 0)
return -1;
return 0;
}
static const char *
fixture_path(const char *base, const char *fixture_name)
{
static char _path[4096];
size_t root_len;
root_len = strlen(base);
strncpy(_path, base, sizeof(_path));
if (_path[root_len - 1] != '/')
_path[root_len++] = '/';
if (fixture_name[0] == '/')
fixture_name++;
strncpy(_path + root_len,
fixture_name,
sizeof(_path) - root_len);
return 1;
return _path;
}
#ifdef CLAY_FIXTURE_PATH
const char *cl_fixture(const char *fixture_name)
{
return fixture_path(CLAY_FIXTURE_PATH, fixture_name);
}
void cl_fixture_sandbox(const char *fixture_name)
{
fs_copy(cl_fixture(fixture_name), _clay_path);
}
void cl_fixture_cleanup(const char *fixture_name)
{
fs_rm(fixture_path(_clay_path, fixture_name));
}
#endif
#ifdef _WIN32
#define FOF_FLAGS (FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR)
static char *
fileops_path(const char *_path)
{
char *path = NULL;
size_t length, i;
if (_path == NULL)
return NULL;
length = strlen(_path);
path = malloc(length + 2);
if (path == NULL)
return NULL;
memcpy(path, _path, length);
path[length] = 0;
path[length + 1] = 0;
for (i = 0; i < length; ++i) {
if (path[i] == '/')
path[i] = '\\';
}
return path;
}
static void
fileops(int mode, const char *_source, const char *_dest)
{
SHFILEOPSTRUCT fops;
char *source = fileops_path(_source);
char *dest = fileops_path(_dest);
ZeroMemory(&fops, sizeof(SHFILEOPSTRUCT));
fops.wFunc = mode;
fops.pFrom = source;
fops.pTo = dest;
fops.fFlags = FOF_FLAGS;
cl_assert_(
SHFileOperation(&fops) == 0,
"Windows SHFileOperation failed"
);
free(source);
free(dest);
}
static void
fs_rm(const char *_source)
{
fileops(FO_DELETE, _source, NULL);
}
static void
fs_copy(const char *_source, const char *_dest)
{
fileops(FO_COPY, _source, _dest);
}
void
cl_fs_cleanup(void)
{
fs_rm(fixture_path(_clay_path, "*"));
}
#else
static int
shell_out(char * const argv[])
{
int status;
pid_t pid;
pid = fork();
if (pid < 0) {
fprintf(stderr,
"System error: `fork()` call failed.\n");
exit(-1);
}
if (pid == 0) {
execv(argv[0], argv);
}
waitpid(pid, &status, 0);
return WEXITSTATUS(status);
}
static void
fs_copy(const char *_source, const char *dest)
{
char *argv[5];
char *source;
size_t source_len;
source = strdup(_source);
source_len = strlen(source);
if (source[source_len - 1] == '/')
source[source_len - 1] = 0;
argv[0] = "/bin/cp";
argv[1] = "-R";
argv[2] = source;
argv[3] = (char *)dest;
argv[4] = NULL;
cl_must_pass_(
shell_out(argv),
"Failed to copy test fixtures to sandbox"
);
free(source);
}
static void
fs_rm(const char *source)
{
char *argv[4];
argv[0] = "/bin/rm";
argv[1] = "-Rf";
argv[2] = (char *)source;
argv[3] = NULL;
cl_must_pass_(
shell_out(argv),
"Failed to cleanup the sandbox"
);
}
void
cl_fs_cleanup(void)
{
clay_unsandbox();
clay_sandbox();
}
#endif
extern void test_core_dirent__dont_traverse_dot(void);
......@@ -456,6 +673,11 @@ 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);
extern void test_status_single__hash_single_file();
extern void test_status_worktree__initialize();
extern void test_status_worktree__cleanup();
extern void test_status_worktree__whole_repository();
extern void test_status_worktree__empty_repository();
static const struct clay_func _all_callbacks[] = {
{"dont_traverse_dot", &test_core_dirent__dont_traverse_dot, 0},
......@@ -477,7 +699,10 @@ static const struct clay_func _all_callbacks[] = {
{"1", &test_core_string__1, 4},
{"0", &test_core_vector__0, 5},
{"1", &test_core_vector__1, 5},
{"2", &test_core_vector__2, 5}
{"2", &test_core_vector__2, 5},
{"hash_single_file", &test_status_single__hash_single_file, 6},
{"whole_repository", &test_status_worktree__whole_repository, 7},
{"empty_repository", &test_status_worktree__empty_repository, 7}
};
static const struct clay_suite _all_suites[] = {
......@@ -516,16 +741,28 @@ static const struct clay_suite _all_suites[] = {
{NULL, NULL, 0},
{NULL, NULL, 0},
&_all_callbacks[17], 3
},
{
"status::single",
{NULL, NULL, 0},
{NULL, NULL, 0},
&_all_callbacks[20], 1
},
{
"status::worktree",
{"initialize", &test_status_worktree__initialize, 7},
{"cleanup", &test_status_worktree__cleanup, 7},
&_all_callbacks[21], 2
}
};
static const char _suites_str[] = "core::dirent, core::filebuf, core::path, core::rmdir, core::string, core::vector";
static const char _suites_str[] = "core::dirent, core::filebuf, core::path, core::rmdir, core::string, core::vector, status::single, status::worktree";
int main(int argc, char *argv[])
{
return clay_test(
argc, argv, _suites_str,
_all_callbacks, 20,
_all_suites, 6
_all_callbacks, 23,
_all_suites, 8
);
}
#include "clay_libgit2.h"
#include "posix.h"
static void
cleanup__remove_file(void *_file)
{
cl_must_pass(p_unlink((char *)_file));
}
static void
file_create(const char *filename, const char *content)
{
int fd = p_creat(filename, 0644);
cl_assert(fd >= 0);
cl_must_pass(p_write(fd, content, strlen(content)));
cl_must_pass(p_close(fd));
}
/* test retrieving OID from a file apart from the ODB */
void test_status_single__hash_single_file()
{
static const char file_name[] = "new_file";
static const char file_contents[] = "new_file\n";
static const char file_hash[] = "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a";
git_oid expected_id, actual_id;
/* initialization */
git_oid_fromstr(&expected_id, file_hash);
file_create(file_name, file_contents);
cl_set_cleanup(&cleanup__remove_file, (void *)file_name);
cl_git_pass(git_odb_hashfile(&actual_id, file_name, GIT_OBJ_BLOB));
cl_assert(git_oid_cmp(&expected_id, &actual_id) == 0);
}
struct status_entry_counts {
int wrong_status_flags_count;
int wrong_sorted_path;
int entry_count;
const unsigned int* expected_statuses;
const char** expected_paths;
int expected_entry_count;
};
static const char *entry_paths0[] = {
"file_deleted",
"modified_file",
"new_file",
"staged_changes",
"staged_changes_file_deleted",
"staged_changes_modified_file",
"staged_delete_file_deleted",
"staged_delete_modified_file",
"staged_new_file",
"staged_new_file_deleted_file",
"staged_new_file_modified_file",
"subdir/deleted_file",
"subdir/modified_file",
"subdir/new_file",
};
static const unsigned int entry_statuses0[] = {
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_MODIFIED,
GIT_STATUS_WT_NEW,
GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_DELETED,
GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED,
GIT_STATUS_INDEX_DELETED,
GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_NEW,
GIT_STATUS_INDEX_NEW,
GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_DELETED,
GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_MODIFIED,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_MODIFIED,
GIT_STATUS_WT_NEW,
};
static const size_t entry_count0 = 14;
#include "clay_libgit2.h"
#include "fileops.h"
#include "status_data.h"
/**
* Test fixtures
*/
static git_repository *_repository = NULL;
/**
* Auxiliary methods
*/
static int
cb_status__normal( const char *path, unsigned int status_flags, void *payload)
{
struct status_entry_counts *counts = payload;
if (counts->entry_count > counts->expected_entry_count) {
counts->wrong_status_flags_count++;
goto exit;
}
if (strcmp(path, counts->expected_paths[counts->entry_count])) {
counts->wrong_sorted_path++;
goto exit;
}
if (status_flags != counts->expected_statuses[counts->entry_count])
counts->wrong_status_flags_count++;
exit:
counts->entry_count++;
return GIT_SUCCESS;
}
static int
cb_status__count(const char *GIT_UNUSED(p), unsigned int GIT_UNUSED(s), void *payload)
{
volatile int *count = (int *)payload;
GIT_UNUSED_ARG(path);
GIT_UNUSED_ARG(status_flags);
*count++;
return GIT_SUCCESS;
}
/**
* Initializer
*
* This method is called once before starting each
* test, and will load the required fixtures
*/
void test_status_worktree__initialize()
{
/*
* Sandbox the `status/` repository from our Fixtures.
* This will copy the whole folder to our sandbox,
* so now it can be accessed with `./status`
*/
cl_fixture_sandbox("status");
/*
* Rename `status/.gitted` to `status/.git`
* We do this because we cannot store a folder named `.git`
* inside the fixtures folder in our libgit2 repo.
*/
cl_git_pass(
git_futils_mv_atomic("status/.gitted", "status/.git")
);
/*
* Open the sandboxed "status" repository
*/
cl_git_pass(git_repository_open(&_repository, "status/.git"));
}
/**
* Cleanup
*
* This will be called once after each test finishes, even
* if the test failed
*/
void test_status_worktree__cleanup()
{
git_repository_free(_repository);
_repository = NULL;
cl_fixture_cleanup("status");
}
/**
* Tests - Status determination on a working tree
*/
void test_status_worktree__whole_repository()
{
struct status_entry_counts counts;
memset(&counts, 0x0, sizeof(struct status_entry_counts));
counts.expected_entry_count = entry_count0;
counts.expected_paths = entry_paths0;
counts.expected_statuses = entry_statuses0;
git_status_foreach(_repository, cb_status__normal, &counts);
cl_assert(counts.entry_count == counts.expected_entry_count);
cl_assert(counts.wrong_status_flags_count == 0);
cl_assert(counts.wrong_sorted_path == 0);
}
void test_status_worktree__empty_repository()
{
int count = 0;
git_status_foreach(_repository, cb_status__count, &count);
cl_assert(count == 0);
}
......@@ -48,20 +48,6 @@ static int file_create(const char *filename, const char *content)
return GIT_SUCCESS;
}
BEGIN_TEST(file0, "test retrieving OID from a file apart from the ODB")
git_oid expected_id, actual_id;
char filename[] = "new_file";
must_pass(file_create(filename, "new_file\n\0"));
must_pass(git_odb_hashfile(&actual_id, filename, GIT_OBJ_BLOB));
must_pass(git_oid_fromstr(&expected_id, test_blob_oid));
must_be_true(git_oid_cmp(&expected_id, &actual_id) == 0);
must_pass(p_unlink(filename));
END_TEST
static const char *entry_paths0[] = {
"file_deleted",
"modified_file",
......@@ -100,15 +86,6 @@ static const unsigned int entry_statuses0[] = {
#define ENTRY_COUNT0 14
struct status_entry_counts {
int wrong_status_flags_count;
int wrong_sorted_path;
int entry_count;
const unsigned int* expected_statuses;
const char** expected_paths;
int expected_entry_count;
};
static int status_cb(const char *path, unsigned int status_flags, void *payload)
{
struct status_entry_counts *counts = (struct status_entry_counts *)payload;
......@@ -154,18 +131,6 @@ BEGIN_TEST(statuscb0, "test retrieving status for worktree of repository")
git_futils_rmdir_r(TEMP_REPO_FOLDER, 1);
END_TEST
static int status_cb1(const char *path, unsigned int status_flags, void *payload)
{
int *count = (int *)payload;;
GIT_UNUSED_ARG(path);
GIT_UNUSED_ARG(status_flags);
*count++;
return GIT_SUCCESS;
}
BEGIN_TEST(statuscb1, "test retrieving status for a worktree of an empty repository")
git_repository *repo;
int count = 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment