Skip to content

Commit

Permalink
Come out and Clay
Browse files Browse the repository at this point in the history
  • Loading branch information
vmg committed Sep 14, 2011
1 parent edb644d commit f1558d9
Show file tree
Hide file tree
Showing 11 changed files with 1,420 additions and 4 deletions.
25 changes: 21 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand All @@ -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 ()
226 changes: 226 additions & 0 deletions tests-clay/clay
Original file line number Diff line number Diff line change
@@ -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()
51 changes: 51 additions & 0 deletions tests-clay/clay.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#ifndef __CLAY_TEST_H__
#define __CLAY_TEST_H__

#include <stdlib.h>

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
28 changes: 28 additions & 0 deletions tests-clay/clay_libgit2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#ifndef __CLAY_LIBGIT2__
#define __CLAY_LIBGIT2__

#include "clay.h"
#include <git2.h>
#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
Loading

0 comments on commit f1558d9

Please sign in to comment.