diff --git a/.gitignore b/.gitignore index 5441aa597..b81a1b13c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ -/tests-clar/clar.h -/tests-clar/clar_main.c -/tests-clar/clar_main.c.rule +/tests-clar/clar.suite +/tests-clar/.clarcache /apidocs /trash-*.exe /libgit2.pc @@ -28,4 +27,4 @@ CMake* .DS_Store *~ tags -mkmf.log \ No newline at end of file +mkmf.log diff --git a/CMakeLists.txt b/CMakeLists.txt index 696d6a088..742365281 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,7 +148,7 @@ IF (MSVC) # Precompiled headers ELSE () - SET(CMAKE_C_FLAGS "-D_GNU_SOURCE -Wall -Wextra -Wno-missing-field-initializers -Wstrict-aliasing=2 -Wstrict-prototypes -Wmissing-prototypes ${CMAKE_C_FLAGS}") + SET(CMAKE_C_FLAGS "-D_GNU_SOURCE -Wall -Wextra -Wno-missing-field-initializers -Wstrict-aliasing=2 -Wstrict-prototypes ${CMAKE_C_FLAGS}") IF (MINGW) # MinGW always does PIC and complains if we tell it to STRING(REGEX REPLACE "-fPIC" "" CMAKE_SHARED_LIBRARY_C_FLAGS "${CMAKE_SHARED_LIBRARY_C_FLAGS}") ELSE () @@ -233,7 +233,6 @@ INSTALL(FILES include/git2.h DESTINATION ${INCLUDE_INSTALL_DIR} ) # Tests IF (BUILD_CLAR) - FIND_PACKAGE(PythonInterp REQUIRED) SET(CLAR_FIXTURES "${CMAKE_CURRENT_SOURCE_DIR}/tests-clar/resources/") @@ -243,26 +242,33 @@ IF (BUILD_CLAR) ADD_DEFINITIONS(-DCLAR_RESOURCES=\"${TEST_RESOURCES}\") INCLUDE_DIRECTORIES(${CLAR_PATH}) - FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/clar_helpers.c ${CLAR_PATH}/testlib.c) + FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c) + SET(SRC_CLAR "${CLAR_PATH}/main.c" "${CLAR_PATH}/clar_libgit2.c" "${CLAR_PATH}/clar.c") ADD_CUSTOM_COMMAND( - OUTPUT ${CLAR_PATH}/clar_main.c ${CLAR_PATH}/clar.h - COMMAND ${PYTHON_EXECUTABLE} clar . - DEPENDS ${CLAR_PATH}/clar ${SRC_TEST} + OUTPUT ${CLAR_PATH}/clar.suite + COMMAND ${PYTHON_EXECUTABLE} generate.py -xonline . + DEPENDS ${SRC_TEST} WORKING_DIRECTORY ${CLAR_PATH} ) - ADD_EXECUTABLE(libgit2_clar ${SRC_GIT2} ${SRC_OS} ${CLAR_PATH}/clar_main.c ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1}) + + SET_SOURCE_FILES_PROPERTIES( + ${CLAR_PATH}/clar.c + PROPERTIES OBJECT_DEPENDS ${CLAR_PATH}/clar.suite) + + ADD_EXECUTABLE(libgit2_clar ${SRC_GIT2} ${SRC_OS} ${SRC_CLAR} ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1}) + TARGET_LINK_LIBRARIES(libgit2_clar ${SSL_LIBRARIES}) TARGET_OS_LIBRARIES(libgit2_clar) MSVC_SPLIT_SOURCES(libgit2_clar) - IF (MSVC_IDE) - # Precompiled headers - SET_TARGET_PROPERTIES(libgit2_clar PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") - ENDIF () + IF (MSVC_IDE) + # Precompiled headers + SET_TARGET_PROPERTIES(libgit2_clar PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") + ENDIF () ENABLE_TESTING() - ADD_TEST(libgit2_clar libgit2_clar -iall) + ADD_TEST(libgit2_clar libgit2_clar) ENDIF () IF (TAGS) diff --git a/tests-clar/clar b/tests-clar/clar deleted file mode 100755 index 7634b2c76..000000000 --- a/tests-clar/clar +++ /dev/null @@ -1,346 +0,0 @@ -#!/usr/bin/env python - -from __future__ import with_statement -from string import Template -import re, fnmatch, os, codecs - -VERSION = "0.10.0" - -TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\(\s*void\s*\))\s*\{" - -EVENT_CB_REGEX = re.compile( - r"^(void\s+clar_on_(\w+)\(\s*void\s*\))\s*\{", - re.MULTILINE) - -SKIP_COMMENTS_REGEX = re.compile( - r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', - re.DOTALL | re.MULTILINE) - -CATEGORY_REGEX = re.compile(r"CL_IN_CATEGORY\(\s*\"([^\"]+)\"\s*\)") - -CLAR_HEADER = """ -/* - * Clar v%s - * - * This is an autogenerated file. Do not modify. - * To add new unit tests or suites, regenerate the whole - * file with `./clar` - */ -""" % VERSION - -CLAR_EVENTS = [ - 'init', - 'shutdown', - 'test', - 'suite' -] - -def main(): - from optparse import OptionParser - - parser = OptionParser() - - parser.add_option('-c', '--clar-path', dest='clar_path') - parser.add_option('-v', '--report-to', dest='print_mode', default='default') - - options, args = parser.parse_args() - - for folder in args or ['.']: - builder = ClarTestBuilder(folder, - clar_path = options.clar_path, - print_mode = options.print_mode) - - builder.render() - - -class ClarTestBuilder: - def __init__(self, path, clar_path = None, print_mode = 'default'): - self.declarations = [] - self.suite_names = [] - self.callback_data = {} - self.suite_data = {} - self.category_data = {} - self.event_callbacks = [] - - self.clar_path = os.path.abspath(clar_path) if clar_path else None - - self.path = os.path.abspath(path) - self.modules = [ - "clar_sandbox.c", - "clar_fixtures.c", - "clar_fs.c", - "clar_categorize.c", - ] - - self.modules.append("clar_print_%s.c" % print_mode) - - print("Loading test suites...") - - for root, dirs, files in os.walk(self.path): - module_root = root[len(self.path):] - 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 codecs.open(full_path, 'r', 'utf-8') as f: - self._process_test_file(test_name, f.read()) - - if not self.suite_data: - raise RuntimeError( - 'No tests found under "%s"' % path) - - def render(self): - main_file = os.path.join(self.path, 'clar_main.c') - with open(main_file, "w") as out: - out.write(self._render_main()) - - header_file = os.path.join(self.path, 'clar.h') - with open(header_file, "w") as out: - out.write(self._render_header()) - - print ('Written Clar suite to "%s"' % self.path) - - ##################################################### - # Internal methods - ##################################################### - - def _render_cb(self, cb): - return '{"%s", &%s}' % (cb['short_name'], cb['symbol']) - - def _render_suite(self, suite, index): - template = Template( -r""" - { - ${suite_index}, - "${clean_name}", - ${initialize}, - ${cleanup}, - ${categories}, - ${cb_ptr}, ${cb_count} - } -""") - - callbacks = {} - for cb in ['initialize', 'cleanup']: - callbacks[cb] = (self._render_cb(suite[cb]) - if suite[cb] else "{NULL, NULL}") - - if len(self.category_data[suite['name']]) > 0: - cats = "_clar_cat_%s" % suite['name'] - else: - cats = "NULL" - - return template.substitute( - suite_index = index, - clean_name = suite['name'].replace("_", "::"), - initialize = callbacks['initialize'], - cleanup = callbacks['cleanup'], - categories = cats, - cb_ptr = "_clar_cb_%s" % suite['name'], - cb_count = suite['cb_count'] - ).strip() - - def _render_callbacks(self, suite_name, callbacks): - template = Template( -r""" -static const struct clar_func _clar_cb_${suite_name}[] = { - ${callbacks} -}; -""") - callbacks = [ - self._render_cb(cb) - for cb in callbacks - if cb['short_name'] not in ('initialize', 'cleanup') - ] - - return template.substitute( - suite_name = suite_name, - callbacks = ",\n\t".join(callbacks) - ).strip() - - def _render_categories(self, suite_name, categories): - template = Template( -r""" -static const char *_clar_cat_${suite_name}[] = { "${categories}", NULL }; -""") - if len(categories) > 0: - return template.substitute( - suite_name = suite_name, - categories = '","'.join(categories) - ).strip() - else: - return "" - - def _render_event_overrides(self): - overrides = [] - for event in CLAR_EVENTS: - if event in self.event_callbacks: - continue - - overrides.append( - "#define clar_on_%s() /* nop */" % event - ) - - return '\n'.join(overrides) - - def _render_header(self): - template = Template(self._load_file('clar.h')) - - declarations = "\n".join( - "extern %s;" % decl - for decl in sorted(self.declarations) - ) - - return template.substitute( - extern_declarations = declarations, - ) - - def _render_main(self): - template = Template(self._load_file('clar.c')) - suite_names = sorted(self.suite_names) - - suite_data = [ - self._render_suite(self.suite_data[s], i) - for i, s in enumerate(suite_names) - ] - - callbacks = [ - self._render_callbacks(s, self.callback_data[s]) - for s in suite_names - ] - - callback_count = sum( - len(cbs) for cbs in self.callback_data.values() - ) - - categories = [ - self._render_categories(s, self.category_data[s]) - for s in suite_names - ] - - return template.substitute( - clar_modules = self._get_modules(), - clar_callbacks = "\n".join(callbacks), - clar_categories = "".join(categories), - clar_suites = ",\n\t".join(suite_data), - clar_suite_count = len(suite_data), - clar_callback_count = callback_count, - clar_event_overrides = self._render_event_overrides(), - ) - - def _load_file(self, filename): - if self.clar_path: - filename = os.path.join(self.clar_path, filename) - with open(filename) as cfile: - return cfile.read() - - else: - import zlib, base64, sys - content = CLAR_FILES[filename] - - if sys.version_info >= (3, 0): - content = bytearray(content, 'utf_8') - content = base64.b64decode(content) - content = zlib.decompress(content) - return str(content, 'utf-8') - else: - content = base64.b64decode(content) - return zlib.decompress(content) - - def _get_modules(self): - return "\n".join(self._load_file(f) for f in self.modules) - - def _skip_comments(self, text): - def _replacer(match): - s = match.group(0) - return "" if s.startswith('/') else s - - return re.sub(SKIP_COMMENTS_REGEX, _replacer, text) - - def _process_test_file(self, suite_name, contents): - contents = self._skip_comments(contents) - - self._process_events(contents) - self._process_declarations(suite_name, contents) - self._process_categories(suite_name, contents) - - def _process_events(self, contents): - for (decl, event) in EVENT_CB_REGEX.findall(contents): - if event not in CLAR_EVENTS: - continue - - self.declarations.append(decl) - self.event_callbacks.append(event) - - def _process_declarations(self, suite_name, contents): - callbacks = [] - initialize = cleanup = None - - regex_string = TEST_FUNC_REGEX % suite_name - regex = re.compile(regex_string, re.MULTILINE) - - for (declaration, symbol, short_name) in regex.findall(contents): - data = { - "short_name" : short_name, - "declaration" : declaration, - "symbol" : symbol - } - - if short_name == 'initialize': - initialize = data - elif short_name == 'cleanup': - cleanup = data - else: - callbacks.append(data) - - if not callbacks: - return - - tests_in_suite = len(callbacks) - - suite = { - "name" : suite_name, - "initialize" : initialize, - "cleanup" : cleanup, - "cb_count" : tests_in_suite - } - - if initialize: - self.declarations.append(initialize['declaration']) - - if cleanup: - self.declarations.append(cleanup['declaration']) - - self.declarations += [ - callback['declaration'] - for callback in callbacks - ] - - callbacks.sort(key=lambda x: x['short_name']) - self.callback_data[suite_name] = callbacks - self.suite_data[suite_name] = suite - self.suite_names.append(suite_name) - - print(" %s (%d tests)" % (suite_name, tests_in_suite)) - - def _process_categories(self, suite_name, contents): - self.category_data[suite_name] = [ - cat for cat in CATEGORY_REGEX.findall(contents) ] - - -CLAR_FILES = { -"clar.c" : r"""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""", -"clar_print_default.c" : r"""eJyFU8Fu2zAMPdtfwQUwIgVuenew9tZTsMuwU1sYqiW3AhzJkOhswNB/n0Q5rRws6Ukmxff4RD6XHgXqDo5WS+gG4drRaYOtNhpZ+ABUHtvOTgZriLGfNKpTorPGI3RvwsEmXRhxUJ6Xf8uCRUr+Cd+VBVH3bLW3QioJlUxsvoHKP5lVDbEjX3TIWTOGnygcKhlAIftelhde4d8mlPa3+folMaGcsy4lLr0gpTLkRy4D78pPoU8maSxIlVOjddhSrWdXpVMN6TbT4TRpj27qMJVRAWzoILmnlhAGy+FB6GFyqqG5Bgqeq6p801QeWOU5PIagks/weIPhiOVlURDrzR09NIvjLGK4Mhak8p3TI2q7gPR6yBGDNmF90+FFuTOeObvQBScjzHVpqAf/SlW6BzZfZM3h23f48Wu/54H+Ek9Wzpfbue4fa6JSlts8SQ9+TJ7JXpISfZi7kuf+iYDdMkOYzNJVF/QmNNzD+mENDay36y/00YbY///D3ObaSPWHVN1uwFg7wuZ2aWeqOLN4kn2tv3gJhl70D9uqYbvdUrOjaAcdroR7HXcU+vjnshjXkBZbHPt5Bh5lWBjla4LwhFFGsjl8L/8BsUiTTQ==""", -"clar_print_tap.c" : r"""eJyNVE1vnDAQPcOvmGWFBAiQot6yaqr2HFU9tLdKyAGzscLayDbbVlX+e8cDJPbuJtsTzPObmTcfdmwss6KFoxIdtAPTzaiFtI2Qwmb4A5Yb27RqkrYEZ5tJWL4CrZLGQvvINBTzgWQHbvL4bxxlLmT+6r5bIY94gq08ktBnyffP3+DItRFKws2HnzLJd/FzHL8h2TxOtlO/5HXZDuBaKz0D/yM3xDznXRxHoodsEwSMXmrYwsiM4R2wYYC0I2GZybGY0hOJhUV8MDxw7JkY0BGd2EHJ/am3l7BEvyiMtoa5qeu0O8/2dhspLPVQTod1xMbqqbUzjQhQ0MdrHbJdL9a8AFVVzSPzMJy5YXsOt5Ca1yKqu7mWg9mHdMNx/ML+uaVenEWj0QCcRSM8pLri4QLV4SGzx6ZfYjo8ZA5CrszOZzq8wXY8cJ2v67Ecddy0WozWbfTmI3z9cX/vLwuARzgV4B3lYafrur52OZSk1fEvLO2Du4bzhZhNUj0D8/rRhNdUqXFLWC3CUPiyop8gkcqCekqwGQl+3Jkf8MXEdHFE8kmc5qPSy86Z7EoFNNbs8pvj33IhO/470L2FoihQNWTbtMudQY313X3X92WwB5QcyMC9Ld0QKOeRNYPAI6b3445MjIQOzi5hWfF+UWbwxZrwRUq+YCMBfzdAO348JVAKFyKfY3LZZYv5HP8D5Mbj9w==""", -"clar_sandbox.c" : r"""eJydVWtP4kAU/dz+iism0gpKfWQ3G9YPm+gasioEMJgomdR2KhPplMwM7KLxv++dTqEP0DVrTKjcO+eec+6cKpWvWADBxBdAgqkvyMxXk/tT79uXcdu2pSkzrmwmycKfspCoeJY2OUHCpTJH9/UXrv1qW4PhjyEZglR42mIROBrC0eUm7Enlws4ZeK5tWYKqueDgrfp2BqQzOO/08cChVCROQupW+7Jnxw8CKmWGOiLdXy6cadi2/VbiHDFe5JsyfZxHERVNkOyFEgVTyp8M9V0W8ZBGQEadm5Nj28pwjMqse4EGBcmcKziD03alx+BTvkCjhLwfYw8aYtWG1z3UVWuCfko/Lszn7eCi3+t3f3auLmo2WG8oEaxsEtN6o0SAwxDHawOD7/n4NjQazE3hK7Ox+YkqfHDWRNgYjbGMyfilNlWfUozPqZ6SVjbXq1vNCJQpeDBbOivvsNRcOaehC0uyrDcbf22rtQ+dCNSE6m4mEh5TtC1MqOR19NNfgs+XasL4UxOUWIJKYC4ptHA+7Lfsd0jVdL2W8arSMsUSswIxJLVLp5Ia6EuqhjSe9TSocz7q9s9dc6wJBq5y+XYpD1lkdA0nTIJcSkXjtaApe6YooKRFiw/mQqTCmaCBSrD4gbjDd5UdfiRr9efBUTEAi4SFkEZ6zqXPw8fkj6O/S2OqCRTy7o11gOoPXj1XjVcDI1FMRDBBFcgSaRYMiSQRcQGsmkL0k01DklEwStc8CrdXF4jy2TRNTi3F09bcpT81nbZ1ZFcvjXLAcw4m3klUpOVigIpvHu2WbSEYTkO/8aEsoqr+FXD1PBExLu2FpnT1onvdQecOMKm/fRGCnPpyQmW65EKUrY0oaxF5iKv7YNk+HtJ9WFalBPVWfR219SIqGFrZARyN9RsX+82gcr3RyMH0PVpdu7wLGpppM1/ONmdxDDZllgF6xjgNHUKuOzeXo5NjQtyMXPyMkZmVjqLMm9urq4296P74Wd+34la9r5638S9EH8BkF0enKytPJfKf92ML7v8QWb1i8NQn5a5XmOe6HKEU4fMhhr29banbngCNYpJdJLrVixK9v7GvgW8=""", -"clar_fixtures.c" : r"""eJyFUV1LwzAUfW5+xZU9rLUVJ4ggZQ9DFAUfZEwQSglZmrBAl5Qkk6n43236tWbKfMvNOfecc+81llhBgSppLNAN0XCOuNjbnWa4InYTjpE1MSzxuD1Vki2L0BcKTKfn0EYgu57d3uRpjYhPhi1opSwumUwRCvo3zMFYXT9C5xA5stWSVh9hI5FAa+wUFG//osgJCA5tmQ1SF3CVw9kcppfTCAWBj8ZxDg3UN4/zZ7MaHBrHSBw7vpcJ4mGS5Ijtai9qnannNqk1q7myXU+KvhGaCF4wDnfPiyV+eHpbvS7v8cti9YjGq6Yl7lzCkxfo1L0j/lJOwOtrUrwrUcDBBRsii7Xan3bjBlNVL2WUzuMkgGlJdLuIP21oyYjcVf/a6G3ozXTQPRqmsZkwWQiOfgAVGffP""", -"clar_fs.c" : r"""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""", -"clar_categorize.c" : r"""eJydVV1v0zAUfU5+xV3RKudj0/ZcOqkqAyGVIU3bA9omy01dsJQlJXYRY+K/c32ddE6WtMBLGzv365x77s2blVyrQsJ8Mbvm89nN5YfP11/4u8v3s9vFDYzwrdjmZhSG5mkj8QTaVNvMwHMYZGWhDWTfRAVxXIhHqSdhoAq8KreFqZ9FnpfZJPwNWS4qngkjv5bVE8+VRotQG2FU1vMOuH+nfkkuC7HM5erFiTK1HFVBvqwnXGx/U/BLRoMofHZVKqx2XVbAFEzhbAIK3oL1OLlwWCBJVBQGgVoDQwIyoWX2uGEYIq3tCP+deohgihGsbVBJs60KOMfY9eMZErGr/0epVh0AYrXipvwvEFhYPxm1D9rZolwhSCJ5eBDhYlojoY5FtsGBdwHJFM6x/uaS8CJZrKWCqJJkzSx+RgjjyHNI/RwQg8bOlutWjCjCJMiSn+fOKzRJHjAt4jnApdML65BF74ixYebHQ9ojGl2Ev0rOESazELkBvVVGckLQdLCeJPKk1xDTn6b6aj+rzBbFhLxbfg22d4gja94Vt1cOXiSJ1QbZYEi0cnWgnE8bFnC4LXoYj4lHOji3/lJImT5Ldsz4p9nHKz6fd9iiUSKMTjGiYcgdl8RHPUIn4M2fSJeHOrGpMHHTwJYaNhVuvp/+CgiD77qsDGuEO6SDU6dlxD5o4RqNFn0KT1/j723S/ui7pUQQ12x0rI/1fTFKwSFLh/13y6rboM4K0U6XHfqGdOvGyteqzsRu1xztR2OB/KOmKSplxtWwk+nLlhv4OljJ7hnxAzNkITUD4qedAKGFoylc3S4WE7AnNyDDu3lPGRQt6nxH2h+SPx6FmFM=""", -"clar.h" : r"""eJy9VV1vmzAUfS6/wg17gAi1TR+7tlIUJWukqJvaVNueLGNMsQaG2mbNNO2/7xpICF/Juoe+JOZe33uOOecam4ciYCHCeLaaPuD1/HGN7zC2bAhywTpxy+aCxnnA0LXSQcz9s+jWsn6mPEA0JhJjohST2rFOuNCIpiLgmqfCs05grSASEYnGIY+ZV26JAaWVZVKmshULmKKSZ1UvU6iiNI8DTPxUavdjDwfMXnISY+Xs9/GGH6BpJwCNh/pyxxT0FfV12bbBimlMY0ZEnjlFzBlXj275PHY9VC7SjLzkrKaAQ9UoNW1tHhr5CpEWy2/rp4c5/jJd31n7HEwp3+hcMqepQhHDgiQNlCqsiAj8dPOWki27AyU2A0uE1s5gsxVe3uPZdD3/9PnhuwML17LOx2MLjdG0eN8gOUoIlalCr1xHiG2ymFOuUeETlDClyDOD/ee7pkApyZXGGSiGHSiQHjIOcpsmLTIuur1BFx44fbFczTE2q9XyvliNFrmgBQFK4hiFBHwbXKERsuueHpq4HWCz8zjw9SDufJMxqlmAwgYBnRYcjjCobHoU/nT43IAv4b0aYK6QSDXSMmd9uFutZhGjP/5DJ2rq3kmoC7eL/M5K9VF4B6Eujg2VSP9xnCpKfRN2/7Ra9Y9Cq2j/nXeKqqPvKppuLrcPm+7YOWq71QhdC3ZI1V5plx08S7GlXdF7kkUqqTERdIPL8vyVSMHFc5t9QaDHJ0PuWDO4hsthOBv1XxYV0lu6fi1LUJBL86cNCNswmhtXXY16PLf+lcHhSMt57dO1Pttq4qnLJqVdDpKu50Da2zHcERw96oJXwlVCNI2KYVAT+IU5MsvLgQtz912feLwfmDuQBGDeC2zzGoQfBvEdf+L5QyCnp5B2PfPXD+TXQP5hoMzJJl52uTdJDkRcdHODHAjvSWRUTJiO0gD0M7SIkaoU6cNvttFMCryf+WNtP+Z/AaQwXp0=""" -} -if __name__ == '__main__': - main() diff --git a/tests-clar/clar.c b/tests-clar/clar.c new file mode 100644 index 000000000..a7d8d30d0 --- /dev/null +++ b/tests-clar/clar.c @@ -0,0 +1,408 @@ +#include +#include +#include +#include +#include +#include +#include + +/* required for sandboxing */ +#include +#include + +#ifdef _WIN32 +# include +# include +# include +# include + +# define _MAIN_CC __cdecl + +# define stat(path, st) _stat(path, st) +# define mkdir(path, mode) _mkdir(path) +# define chdir(path) _chdir(path) +# define access(path, mode) _access(path, mode) +# define strdup(str) _strdup(str) +# define strcasecmp(a,b) _stricmp(a,b) + +# ifndef __MINGW32__ +# pragma comment(lib, "shell32") +# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) +# define W_OK 02 +# define S_ISDIR(x) ((x & _S_IFDIR) != 0) +# define snprint_eq(buf,sz,fmt,a,b) _snprintf_s(buf,sz,_TRUNCATE,fmt,a,b) +# else +# define snprint_eq snprintf +# endif + typedef struct _stat STAT_T; +#else +# include /* waitpid(2) */ +# include +# define _MAIN_CC +# define snprint_eq snprintf + typedef struct stat STAT_T; +#endif + +#include "clar.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 clar_error { + const char *test; + int test_number; + const char *suite; + const char *file; + int line_number; + const char *error_msg; + char *description; + + struct clar_error *next; +}; + +static struct { + const char *active_test; + const char *active_suite; + + int suite_errors; + int total_errors; + + int tests_ran; + int suites_ran; + + int report_errors_only; + int exit_on_error; + + struct clar_error *errors; + struct clar_error *last_error; + + void (*local_cleanup)(void *); + void *local_cleanup_payload; + + jmp_buf trampoline; + int trampoline_enabled; +} _clar; + +struct clar_func { + const char *name; + void (*ptr)(void); +}; + +struct clar_suite { + const char *name; + struct clar_func initialize; + struct clar_func cleanup; + const struct clar_func *tests; + size_t test_count; + int enabled; +}; + +/* From clar_print_*.c */ +static void clar_print_init(int test_count, int suite_count, const char *suite_names); +static void clar_print_shutdown(int test_count, int suite_count, int error_count); +static void clar_print_error(int num, const struct clar_error *error); +static void clar_print_ontest(const char *test_name, int test_number, int failed); +static void clar_print_onsuite(const char *suite_name, int suite_index); +static void clar_print_onabort(const char *msg, ...); + +/* From clar_sandbox.c */ +static void clar_unsandbox(void); +static int clar_sandbox(void); + +/* Load the declarations for the test suite */ +#include "clar.suite" + +/* Core test functions */ +static void +clar_report_errors(void) +{ + int i = 1; + struct clar_error *error, *next; + + error = _clar.errors; + while (error != NULL) { + next = error->next; + clar_print_error(i++, error); + free(error->description); + free(error); + error = next; + } + + _clar.errors = _clar.last_error = NULL; +} + +static void +clar_run_test( + const struct clar_func *test, + const struct clar_func *initialize, + const struct clar_func *cleanup) +{ + int error_st = _clar.suite_errors; + + _clar.trampoline_enabled = 1; + + if (setjmp(_clar.trampoline) == 0) { + if (initialize->ptr != NULL) + initialize->ptr(); + + test->ptr(); + } + + _clar.trampoline_enabled = 0; + + if (_clar.local_cleanup != NULL) + _clar.local_cleanup(_clar.local_cleanup_payload); + + if (cleanup->ptr != NULL) + cleanup->ptr(); + + _clar.tests_ran++; + + /* remove any local-set cleanup methods */ + _clar.local_cleanup = NULL; + _clar.local_cleanup_payload = NULL; + + if (_clar.report_errors_only) + clar_report_errors(); + else + clar_print_ontest( + test->name, + _clar.tests_ran, + (_clar.suite_errors > error_st) + ); +} + +static void +clar_run_suite(const struct clar_suite *suite) +{ + const struct clar_func *test = suite->tests; + size_t i; + + if (!suite->enabled) + return; + + if (_clar.exit_on_error && _clar.total_errors) + return; + + if (!_clar.report_errors_only) + clar_print_onsuite(suite->name, ++_clar.suites_ran); + + _clar.active_suite = suite->name; + _clar.suite_errors = 0; + + for (i = 0; i < suite->test_count; ++i) { + _clar.active_test = test[i].name; + clar_run_test(&test[i], &suite->initialize, &suite->cleanup); + + if (_clar.exit_on_error && _clar.total_errors) + return; + } +} + +static void +clar_usage(const char *arg) +{ + printf("Usage: %s [options]\n\n", arg); + printf("Options:\n"); + printf(" -sname\t\tRun only the suite with `name`\n"); + printf(" -iname\t\tInclude the suite with `name`\n"); + printf(" -xname\t\tExclude the suite with `name`\n"); + printf(" -q \t\tOnly report tests that had an error\n"); + printf(" -Q \t\tQuit as soon as a test fails\n"); + printf(" -l \t\tPrint suite names\n"); + exit(-1); +} + +static void +clar_parse_args(int argc, char **argv) +{ + int i; + + for (i = 1; i < argc; ++i) { + char *argument = argv[i]; + + if (argument[0] != '-') + clar_usage(argv[0]); + + switch (argument[1]) { + case 's': + case 'i': + case 'x': { /* given suite name */ + int offset = (argument[2] == '=') ? 3 : 2; + char action = argument[1]; + size_t j, len; + + argument += offset; + len = strlen(argument); + + if (len == 0) + clar_usage(argv[0]); + + for (j = 0; j < _clar_suite_count; ++j) { + if (strncmp(argument, _clar_suites[j].name, len) == 0) { + switch (action) { + case 's': clar_run_suite(&_clar_suites[j]); break; + case 'i': _clar_suites[j].enabled = 1; break; + case 'x': _clar_suites[j].enabled = 0; break; + } + break; + } + } + + if (j == _clar_suite_count) { + clar_print_onabort("No suite matching '%s' found.\n", argument); + exit(-1); + } + break; + } + + case 'q': + _clar.report_errors_only = 1; + break; + + case 'Q': + _clar.exit_on_error = 1; + break; + + case 'l': { + size_t j; + printf("Test suites (use -s to run just one):\n"); + for (j = 0; j < _clar_suite_count; ++j) + printf(" %3d: %s\n", (int)j, _clar_suites[j].name); + + exit(0); + } + + default: + clar_usage(argv[0]); + } + } +} + +int +clar_test(int argc, char **argv) +{ + clar_print_init( + (int)_clar_callback_count, + (int)_clar_suite_count, + "" + ); + + if (clar_sandbox() < 0) { + clar_print_onabort("Failed to sandbox the test runner.\n"); + exit(-1); + } + + if (argc > 1) + clar_parse_args(argc, argv); + + if (!_clar.suites_ran) { + size_t i; + for (i = 0; i < _clar_suite_count; ++i) + clar_run_suite(&_clar_suites[i]); + } + + clar_print_shutdown( + _clar.tests_ran, + (int)_clar_suite_count, + _clar.total_errors + ); + + clar_unsandbox(); + return _clar.total_errors; +} + +void +clar__assert( + int condition, + const char *file, + int line, + const char *error_msg, + const char *description, + int should_abort) +{ + struct clar_error *error; + + if (condition) + return; + + error = calloc(1, sizeof(struct clar_error)); + + if (_clar.errors == NULL) + _clar.errors = error; + + if (_clar.last_error != NULL) + _clar.last_error->next = error; + + _clar.last_error = error; + + error->test = _clar.active_test; + error->test_number = _clar.tests_ran; + error->suite = _clar.active_suite; + error->file = file; + error->line_number = line; + error->error_msg = error_msg; + + if (description != NULL) + error->description = strdup(description); + + _clar.suite_errors++; + _clar.total_errors++; + + if (should_abort) { + if (!_clar.trampoline_enabled) { + clar_print_onabort( + "Fatal error: a cleanup method raised an exception."); + clar_report_errors(); + exit(-1); + } + + longjmp(_clar.trampoline, -1); + } +} + +void clar__assert_equal_s( + const char *s1, + const char *s2, + const char *file, + int line, + const char *err, + int should_abort) +{ + int match = (s1 == NULL || s2 == NULL) ? (s1 == s2) : (strcmp(s1, s2) == 0); + + if (!match) { + char buf[4096]; + snprint_eq(buf, 4096, "'%s' != '%s'", s1, s2); + clar__assert(0, file, line, err, buf, should_abort); + } +} + +void clar__assert_equal_i( + int i1, + int i2, + const char *file, + int line, + const char *err, + int should_abort) +{ + if (i1 != i2) { + char buf[128]; + snprint_eq(buf, 128, "%d != %d", i1, i2); + clar__assert(0, file, line, err, buf, should_abort); + } +} + +void cl_set_cleanup(void (*cleanup)(void *), void *opaque) +{ + _clar.local_cleanup = cleanup; + _clar.local_cleanup_payload = opaque; +} + +#include "clar/sandbox.h" +#include "clar/fixtures.h" +#include "clar/fs.h" +#include "clar/print.h" diff --git a/tests-clar/clar.h b/tests-clar/clar.h new file mode 100644 index 000000000..825874116 --- /dev/null +++ b/tests-clar/clar.h @@ -0,0 +1,70 @@ +#ifndef __CLAR_TEST_H__ +#define __CLAR_TEST_H__ + +#include + +int clar_test(int argc, char *argv[]); + +void cl_set_cleanup(void (*cleanup)(void *), void *opaque); +void cl_fs_cleanup(void); + +#ifdef CLAR_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 + */ +#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __LINE__, "Function call failed: " #expr, desc, 1) +#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __LINE__, "Expected function call to fail: " #expr, desc, 1) +#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __LINE__, "Expression is not true: " #expr, desc, 1) + +/** + * Check macros with explicit error message + */ +#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __LINE__, "Function call failed: " #expr, desc, 0) +#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __LINE__, "Expected function call to fail: " #expr, desc, 0) +#define cl_check_(expr, desc) clar__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) clar__assert(0, __FILE__, __LINE__, "Test failed.", desc, 1) +#define cl_warning(desc) clar__assert(0, __FILE__, __LINE__, "Warning during test execution:", desc, 0) + +/** + * Typed assertion macros + */ +#define cl_assert_equal_s(s1,s2) clar__assert_equal_s((s1),(s2),__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1) +#define cl_assert_equal_i(i1,i2) clar__assert_equal_i((i1),(i2),__FILE__,__LINE__,#i1 " != " #i2, 1) +#define cl_assert_equal_b(b1,b2) clar__assert_equal_i(!!(b1),!!(b2),__FILE__,__LINE__,#b1 " != " #b2, 1) +#define cl_assert_equal_p(p1,p2) cl_assert((p1) == (p2)) + +void clar__assert( + int condition, + const char *file, + int line, + const char *error, + const char *description, + int should_abort); + +void clar__assert_equal_s(const char *,const char *,const char *,int,const char *,int); +void clar__assert_equal_i(int,int,const char *,int,const char *,int); + +#endif diff --git a/tests-clar/clar/fixtures.h b/tests-clar/clar/fixtures.h new file mode 100644 index 000000000..264cd7f4f --- /dev/null +++ b/tests-clar/clar/fixtures.h @@ -0,0 +1,38 @@ +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 _path; +} + +#ifdef CLAR_FIXTURE_PATH +const char *cl_fixture(const char *fixture_name) +{ + return fixture_path(CLAR_FIXTURE_PATH, fixture_name); +} + +void cl_fixture_sandbox(const char *fixture_name) +{ + fs_copy(cl_fixture(fixture_name), _clar_path); +} + +void cl_fixture_cleanup(const char *fixture_name) +{ + fs_rm(fixture_path(_clar_path, fixture_name)); +} +#endif diff --git a/tests-clar/clar/fs.h b/tests-clar/clar/fs.h new file mode 100644 index 000000000..1cdc1ce2f --- /dev/null +++ b/tests-clar/clar/fs.h @@ -0,0 +1,325 @@ +#ifdef _WIN32 + +#define RM_RETRY_COUNT 5 +#define RM_RETRY_DELAY 10 + +#ifdef __MINGW32__ + +/* These security-enhanced functions are not available + * in MinGW, so just use the vanilla ones */ +#define wcscpy_s(a, b, c) wcscpy((a), (c)) +#define wcscat_s(a, b, c) wcscat((a), (c)) + +#endif /* __MINGW32__ */ + +static int +fs__dotordotdot(WCHAR *_tocheck) +{ + return _tocheck[0] == '.' && + (_tocheck[1] == '\0' || + (_tocheck[1] == '.' && _tocheck[2] == '\0')); +} + +static int +fs_rmdir_rmdir(WCHAR *_wpath) +{ + unsigned retries = 1; + + while (!RemoveDirectoryW(_wpath)) { + /* Only retry when we have retries remaining, and the + * error was ERROR_DIR_NOT_EMPTY. */ + if (retries++ > RM_RETRY_COUNT || + ERROR_DIR_NOT_EMPTY != GetLastError()) + return -1; + + /* Give whatever has a handle to a child item some time + * to release it before trying again */ + Sleep(RM_RETRY_DELAY * retries * retries); + } + + return 0; +} + +static void +fs_rmdir_helper(WCHAR *_wsource) +{ + WCHAR buffer[MAX_PATH]; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + int buffer_prefix_len; + + /* Set up the buffer and capture the length */ + wcscpy_s(buffer, MAX_PATH, _wsource); + wcscat_s(buffer, MAX_PATH, L"\\"); + buffer_prefix_len = wcslen(buffer); + + /* FindFirstFile needs a wildcard to match multiple items */ + wcscat_s(buffer, MAX_PATH, L"*"); + find_handle = FindFirstFileW(buffer, &find_data); + cl_assert(INVALID_HANDLE_VALUE != find_handle); + + do { + /* FindFirstFile/FindNextFile gives back . and .. + * entries at the beginning */ + if (fs__dotordotdot(find_data.cFileName)) + continue; + + wcscpy_s(buffer + buffer_prefix_len, MAX_PATH - buffer_prefix_len, find_data.cFileName); + + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) + fs_rmdir_helper(buffer); + else { + /* If set, the +R bit must be cleared before deleting */ + if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes) + cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY)); + + cl_assert(DeleteFileW(buffer)); + } + } + while (FindNextFileW(find_handle, &find_data)); + + /* Ensure that we successfully completed the enumeration */ + cl_assert(ERROR_NO_MORE_FILES == GetLastError()); + + /* Close the find handle */ + FindClose(find_handle); + + /* Now that the directory is empty, remove it */ + cl_assert(0 == fs_rmdir_rmdir(_wsource)); +} + +static int +fs_rm_wait(WCHAR *_wpath) +{ + unsigned retries = 1; + DWORD last_error; + + do { + if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath)) + last_error = GetLastError(); + else + last_error = ERROR_SUCCESS; + + /* Is the item gone? */ + if (ERROR_FILE_NOT_FOUND == last_error || + ERROR_PATH_NOT_FOUND == last_error) + return 0; + + Sleep(RM_RETRY_DELAY * retries * retries); + } + while (retries++ <= RM_RETRY_COUNT); + + return -1; +} + +static void +fs_rm(const char *_source) +{ + WCHAR wsource[MAX_PATH]; + DWORD attrs; + + /* The input path is UTF-8. Convert it to wide characters + * for use with the Windows API */ + cl_assert(MultiByteToWideChar(CP_UTF8, + MB_ERR_INVALID_CHARS, + _source, + -1, /* Indicates NULL termination */ + wsource, + MAX_PATH)); + + /* Does the item exist? If not, we have no work to do */ + attrs = GetFileAttributesW(wsource); + + if (INVALID_FILE_ATTRIBUTES == attrs) + return; + + if (FILE_ATTRIBUTE_DIRECTORY & attrs) + fs_rmdir_helper(wsource); + else { + /* The item is a file. Strip the +R bit */ + if (FILE_ATTRIBUTE_READONLY & attrs) + cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY)); + + cl_assert(DeleteFileW(wsource)); + } + + /* Wait for the DeleteFile or RemoveDirectory call to complete */ + cl_assert(0 == fs_rm_wait(wsource)); +} + +static void +fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest) +{ + WCHAR buf_source[MAX_PATH], buf_dest[MAX_PATH]; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + int buf_source_prefix_len, buf_dest_prefix_len; + + wcscpy_s(buf_source, MAX_PATH, _wsource); + wcscat_s(buf_source, MAX_PATH, L"\\"); + buf_source_prefix_len = wcslen(buf_source); + + wcscpy_s(buf_dest, MAX_PATH, _wdest); + wcscat_s(buf_dest, MAX_PATH, L"\\"); + buf_dest_prefix_len = wcslen(buf_dest); + + /* Get an enumerator for the items in the source. */ + wcscat_s(buf_source, MAX_PATH, L"*"); + find_handle = FindFirstFileW(buf_source, &find_data); + cl_assert(INVALID_HANDLE_VALUE != find_handle); + + /* Create the target directory. */ + cl_assert(CreateDirectoryW(_wdest, NULL)); + + do { + /* FindFirstFile/FindNextFile gives back . and .. + * entries at the beginning */ + if (fs__dotordotdot(find_data.cFileName)) + continue; + + wcscpy_s(buf_source + buf_source_prefix_len, MAX_PATH - buf_source_prefix_len, find_data.cFileName); + wcscpy_s(buf_dest + buf_dest_prefix_len, MAX_PATH - buf_dest_prefix_len, find_data.cFileName); + + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) + fs_copydir_helper(buf_source, buf_dest); + else + cl_assert(CopyFileW(buf_source, buf_dest, TRUE)); + } + while (FindNextFileW(find_handle, &find_data)); + + /* Ensure that we successfully completed the enumeration */ + cl_assert(ERROR_NO_MORE_FILES == GetLastError()); + + /* Close the find handle */ + FindClose(find_handle); +} + +static void +fs_copy(const char *_source, const char *_dest) +{ + WCHAR wsource[MAX_PATH], wdest[MAX_PATH]; + DWORD source_attrs, dest_attrs; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + + /* The input paths are UTF-8. Convert them to wide characters + * for use with the Windows API. */ + cl_assert(MultiByteToWideChar(CP_UTF8, + MB_ERR_INVALID_CHARS, + _source, + -1, + wsource, + MAX_PATH)); + + cl_assert(MultiByteToWideChar(CP_UTF8, + MB_ERR_INVALID_CHARS, + _dest, + -1, + wdest, + MAX_PATH)); + + /* Check the source for existence */ + source_attrs = GetFileAttributesW(wsource); + cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs); + + /* Check the target for existence */ + dest_attrs = GetFileAttributesW(wdest); + + if (INVALID_FILE_ATTRIBUTES != dest_attrs) { + /* Target exists; append last path part of source to target. + * Use FindFirstFile to parse the path */ + find_handle = FindFirstFileW(wsource, &find_data); + cl_assert(INVALID_HANDLE_VALUE != find_handle); + wcscat_s(wdest, MAX_PATH, L"\\"); + wcscat_s(wdest, MAX_PATH, find_data.cFileName); + FindClose(find_handle); + + /* Check the new target for existence */ + cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest)); + } + + if (FILE_ATTRIBUTE_DIRECTORY & source_attrs) + fs_copydir_helper(wsource, wdest); + else + cl_assert(CopyFileW(wsource, wdest, TRUE)); +} + +void +cl_fs_cleanup(void) +{ + fs_rm(fixture_path(_clar_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) +{ + clar_unsandbox(); + clar_sandbox(); +} +#endif diff --git a/tests-clar/clar/print.h b/tests-clar/clar/print.h new file mode 100644 index 000000000..db9da9198 --- /dev/null +++ b/tests-clar/clar/print.h @@ -0,0 +1,59 @@ + +static void clar_print_init(int test_count, int suite_count, const char *suite_names) +{ + (void)test_count; + printf("Loaded %d suites: %s\n", (int)suite_count, suite_names); + printf("Started\n"); +} + +static void clar_print_shutdown(int test_count, int suite_count, int error_count) +{ + (void)test_count; + (void)suite_count; + (void)error_count; + + printf("\n\n"); + clar_report_errors(); +} + +static void clar_print_error(int num, const struct clar_error *error) +{ + printf(" %d) Failure:\n", num); + + printf("%s::%s [%s:%d]\n", + error->suite, + error->test, + error->file, + error->line_number); + + printf(" %s\n", error->error_msg); + + if (error->description != NULL) + printf(" %s\n", error->description); + + printf("\n"); + fflush(stdout); +} + +static void clar_print_ontest(const char *test_name, int test_number, int failed) +{ + (void)test_name; + (void)test_number; + printf("%c", failed ? 'F' : '.'); + fflush(stdout); +} + +static void clar_print_onsuite(const char *suite_name, int suite_index) +{ + /* noop */ + (void)suite_index; + (void)suite_name; +} + +static void clar_print_onabort(const char *msg, ...) +{ + va_list argp; + va_start(argp, msg); + vfprintf(stderr, msg, argp); + va_end(argp); +} diff --git a/tests-clar/clar/sandbox.h b/tests-clar/clar/sandbox.h new file mode 100644 index 000000000..bed3011fe --- /dev/null +++ b/tests-clar/clar/sandbox.h @@ -0,0 +1,127 @@ +static char _clar_path[4096]; + +static int +is_valid_tmp_path(const char *path) +{ + 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 +find_tmp_path(char *buffer, size_t length) +{ +#ifndef _WIN32 + static const size_t var_count = 4; + static const char *env_vars[] = { + "TMPDIR", "TMP", "TEMP", "USERPROFILE" + }; + + size_t i; + + 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 0; + } + } + + /* If the environment doesn't say anything, try to use /tmp */ + if (is_valid_tmp_path("/tmp")) { + strncpy(buffer, "/tmp", length); + return 0; + } + +#else + if (GetTempPath((DWORD)length, buffer)) + return 0; +#endif + + /* This system doesn't like us, try to use the current directory */ + if (is_valid_tmp_path(".")) { + strncpy(buffer, ".", length); + return 0; + } + + return -1; +} + +static void clar_unsandbox(void) +{ + if (_clar_path[0] == '\0') + return; + +#ifdef _WIN32 + chdir(".."); +#endif + + fs_rm(_clar_path); +} + +static int build_sandbox_path(void) +{ + const char path_tail[] = "clar_tmp_XXXXXX"; + size_t len; + + if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0) + return -1; + + len = strlen(_clar_path); + +#ifdef _WIN32 + { /* normalize path to POSIX forward slashes */ + size_t i; + for (i = 0; i < len; ++i) { + if (_clar_path[i] == '\\') + _clar_path[i] = '/'; + } + } +#endif + + if (_clar_path[len - 1] != '/') { + _clar_path[len++] = '/'; + } + + strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len); + +#if defined(__MINGW32__) + if (_mktemp(_clar_path) == NULL) + return -1; + + if (mkdir(_clar_path, 0700) != 0) + return -1; +#elif defined(_WIN32) + if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0) + return -1; + + if (mkdir(_clar_path, 0700) != 0) + return -1; +#else + if (mkdtemp(_clar_path) == NULL) + return -1; +#endif + + return 0; +} + +static int clar_sandbox(void) +{ + if (_clar_path[0] == '\0' && build_sandbox_path() < 0) + return -1; + + if (chdir(_clar_path) != 0) + return -1; + + return 0; +} + diff --git a/tests-clar/clar_helpers.c b/tests-clar/clar_libgit2.c similarity index 98% rename from tests-clar/clar_helpers.c rename to tests-clar/clar_libgit2.c index b718d4305..ce3ec4af4 100644 --- a/tests-clar/clar_helpers.c +++ b/tests-clar/clar_libgit2.c @@ -2,17 +2,6 @@ #include "posix.h" #include "path.h" -void clar_on_init(void) -{ - git_threads_init(); -} - -void clar_on_shutdown(void) -{ - giterr_clear(); - git_threads_shutdown(); -} - void cl_git_mkfile(const char *filename, const char *content) { int fd; diff --git a/tests-clar/clone/nonetwork.c b/tests-clar/clone/nonetwork.c index ffcf03d7c..b99c29dcf 100644 --- a/tests-clar/clone/nonetwork.c +++ b/tests-clar/clone/nonetwork.c @@ -20,14 +20,14 @@ void test_clone_nonetwork__initialize(void) g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; } -static void cleanup_repository(void *path) +void test_clone_nonetwork__cleanup(void) { if (g_repo) { git_repository_free(g_repo); g_repo = NULL; } - cl_fixture_cleanup((const char *)path); + cl_fixture_cleanup("./foo"); } void test_clone_nonetwork__bad_url(void) @@ -42,39 +42,30 @@ void test_clone_nonetwork__bad_url(void) void test_clone_nonetwork__local(void) { - cl_set_cleanup(&cleanup_repository, "./foo"); cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); } void test_clone_nonetwork__local_absolute_path(void) { const char *local_src; - cl_set_cleanup(&cleanup_repository, "./foo"); - local_src = cl_fixture("testrepo.git"); cl_git_pass(git_clone(&g_repo, local_src, "./foo", &g_options)); } void test_clone_nonetwork__local_bare(void) { - cl_set_cleanup(&cleanup_repository, "./foo"); - g_options.bare = true; cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); } void test_clone_nonetwork__fail_when_the_target_is_a_file(void) { - cl_set_cleanup(&cleanup_repository, "./foo"); - cl_git_mkfile("./foo", "Bar!"); cl_git_fail(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); } void test_clone_nonetwork__fail_with_already_existing_but_non_empty_directory(void) { - cl_set_cleanup(&cleanup_repository, "./foo"); - p_mkdir("./foo", GIT_DIR_MODE); cl_git_mkfile("./foo/bar", "Baz!"); cl_git_fail(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); @@ -84,7 +75,6 @@ void test_clone_nonetwork__custom_origin_name(void) { git_remote *remote; - cl_set_cleanup(&cleanup_repository, "./foo"); g_options.remote_name = "my_origin"; cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); @@ -98,7 +88,6 @@ void test_clone_nonetwork__custom_push_url(void) git_remote *remote; const char *url = "http://example.com"; - cl_set_cleanup(&cleanup_repository, "./foo"); g_options.pushurl = url; cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); @@ -115,7 +104,6 @@ void test_clone_nonetwork__custom_fetch_spec(void) const git_refspec *actual_fs; const char *spec = "+refs/heads/master:refs/heads/foo"; - cl_set_cleanup(&cleanup_repository, "./foo"); g_options.fetch_spec = spec; cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); @@ -136,7 +124,6 @@ void test_clone_nonetwork__custom_push_spec(void) const git_refspec *actual_fs; const char *spec = "+refs/heads/master:refs/heads/foo"; - cl_set_cleanup(&cleanup_repository, "./foo"); g_options.push_spec = spec; cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); @@ -152,7 +139,6 @@ void test_clone_nonetwork__custom_autotag(void) { git_strarray tags = {0}; - cl_set_cleanup(&cleanup_repository, "./foo"); g_options.remote_autotag = GIT_REMOTE_DOWNLOAD_TAGS_NONE; cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); @@ -162,8 +148,6 @@ void test_clone_nonetwork__custom_autotag(void) void test_clone_nonetwork__cope_with_already_existing_directory(void) { - cl_set_cleanup(&cleanup_repository, "./foo"); - p_mkdir("./foo", GIT_DIR_MODE); cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); } @@ -171,7 +155,6 @@ void test_clone_nonetwork__cope_with_already_existing_directory(void) void test_clone_nonetwork__can_prevent_the_checkout_of_a_standard_repo(void) { git_buf path = GIT_BUF_INIT; - cl_set_cleanup(&cleanup_repository, "./foo"); g_options.checkout_opts.checkout_strategy = 0; cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options)); diff --git a/tests-clar/config/read.c b/tests-clar/config/read.c index e0171a593..9c02307ad 100644 --- a/tests-clar/config/read.c +++ b/tests-clar/config/read.c @@ -431,11 +431,18 @@ void test_config_read__simple_read_from_specific_level(void) git_config_free(cfg); } +static void clean_empty_config(void *unused) +{ + GIT_UNUSED(unused); + cl_fixture_cleanup("./empty"); +} + void test_config_read__can_load_and_parse_an_empty_config_file(void) { git_config *cfg; int i; + cl_set_cleanup(&clean_empty_config, NULL); cl_git_mkfile("./empty", ""); cl_git_pass(git_config_open_ondisk(&cfg, "./empty")); cl_assert_equal_i(GIT_ENOTFOUND, git_config_get_int32(&i, cfg, "nope.neither")); diff --git a/tests-clar/core/env.c b/tests-clar/core/env.c index 9eb2fe5bb..72ef66819 100644 --- a/tests-clar/core/env.c +++ b/tests-clar/core/env.c @@ -14,6 +14,18 @@ static const char *env_vars[NUM_VARS] = { "HOME" }; static char *env_save[NUM_VARS]; +static char *home_values[] = { + "fake_home", + "fáke_hõme", /* all in latin-1 supplement */ + "fĀke_Ĥome", /* latin extended */ + "fακε_hοmέ", /* having fun with greek */ + "faงe_นome", /* now I have no idea, but thai characters */ + "f\xe1\x9cx80ke_\xe1\x9c\x91ome", /* tagalog characters */ + "\xe1\xb8\x9fẢke_hoṁe", /* latin extended additional */ + "\xf0\x9f\x98\x98\xf0\x9f\x98\x82", /* emoticons */ + NULL +}; + void test_core_env__initialize(void) { int i; @@ -24,6 +36,8 @@ void test_core_env__initialize(void) void test_core_env__cleanup(void) { int i; + char **val; + for (i = 0; i < NUM_VARS; ++i) { cl_setenv(env_vars[i], env_save[i]); #ifdef GIT_WIN32 @@ -31,11 +45,20 @@ void test_core_env__cleanup(void) #endif env_save[i] = NULL; } + + /* these will probably have already been cleaned up, but if a test + * fails, then it's probably good to try and clear out these dirs + */ + for (val = home_values; *val != NULL; val++) { + if (**val != '\0') + (void)p_rmdir(*val); + } } static void setenv_and_check(const char *name, const char *value) { char *check; + cl_git_pass(cl_setenv(name, value)); check = cl_getenv(name); cl_assert_equal_s(value, check); @@ -46,17 +69,6 @@ static void setenv_and_check(const char *name, const char *value) void test_core_env__0(void) { - static char *home_values[] = { - "fake_home", - "fáke_hõme", /* all in latin-1 supplement */ - "fĀke_Ĥome", /* latin extended */ - "fακε_hοmέ", /* having fun with greek */ - "faงe_นome", /* now I have no idea, but thai characters */ - "f\xe1\x9cx80ke_\xe1\x9c\x91ome", /* tagalog characters */ - "\xe1\xb8\x9fẢke_hoṁe", /* latin extended additional */ - "\xf0\x9f\x98\x98\xf0\x9f\x98\x82", /* emoticons */ - NULL - }; git_buf path = GIT_BUF_INIT, found = GIT_BUF_INIT; char testfile[16], tidx = '0'; char **val; @@ -71,8 +83,10 @@ void test_core_env__0(void) * we are on a filesystem that doesn't support the * characters in question and skip this test... */ - if (p_mkdir(*val, 0777) != 0) + if (p_mkdir(*val, 0777) != 0) { + *val = ""; /* mark as not created */ continue; + } cl_git_pass(git_path_prettify(&path, *val, NULL)); @@ -122,6 +136,8 @@ void test_core_env__0(void) } } #endif + + (void)p_rmdir(*val); } git_buf_free(&path); diff --git a/tests-clar/generate.py b/tests-clar/generate.py new file mode 100644 index 000000000..74b69bda3 --- /dev/null +++ b/tests-clar/generate.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python + +from __future__ import with_statement +from string import Template +import re, fnmatch, os, codecs, pickle + +class Module(object): + class Template(object): + def __init__(self, module): + self.module = module + + def _render_callback(self, cb): + if not cb: + return '{ NULL, NULL }' + return '{ "%s", &%s }' % (cb['short_name'], cb['symbol']) + + class DeclarationTemplate(Template): + def render(self): + out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + + if self.module.initialize: + out += "extern %s;\n" % self.module.initialize['declaration'] + + if self.module.cleanup: + out += "extern %s;\n" % self.module.cleanup['declaration'] + + return out + + class CallbacksTemplate(Template): + def render(self): + out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name + out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks) + out += "\n};\n" + return out + + class InfoTemplate(Template): + def render(self): + return Template( + r"""{ + "${clean_name}", + ${initialize}, + ${cleanup}, + ${cb_ptr}, ${cb_count}, ${enabled} + }""" + ).substitute( + clean_name = self.module.clean_name(), + initialize = self._render_callback(self.module.initialize), + cleanup = self._render_callback(self.module.cleanup), + cb_ptr = "_clar_cb_%s" % self.module.name, + cb_count = len(self.module.callbacks), + enabled = int(self.module.enabled) + ) + + def __init__(self, name): + self.name = name + + self.mtime = 0 + self.enabled = True + self.modified = False + + def clean_name(self): + return self.name.replace("_", "::") + + def _skip_comments(self, text): + SKIP_COMMENTS_REGEX = re.compile( + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', + re.DOTALL | re.MULTILINE) + + def _replacer(match): + s = match.group(0) + return "" if s.startswith('/') else s + + return re.sub(SKIP_COMMENTS_REGEX, _replacer, text) + + def parse(self, contents): + TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\(\s*void\s*\))\s*\{" + + contents = self._skip_comments(contents) + regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE) + + self.callbacks = [] + self.initialize = None + self.cleanup = None + + for (declaration, symbol, short_name) in regex.findall(contents): + data = { + "short_name" : short_name, + "declaration" : declaration, + "symbol" : symbol + } + + if short_name == 'initialize': + self.initialize = data + elif short_name == 'cleanup': + self.cleanup = data + else: + self.callbacks.append(data) + + return self.callbacks != [] + + def refresh(self, path): + self.modified = False + + try: + st = os.stat(path) + + # Not modified + if st.st_mtime == self.mtime: + return True + + self.modified = True + self.mtime = st.st_mtime + + with open(path) as fp: + raw_content = fp.read() + + except IOError: + return False + + return self.parse(raw_content) + +class TestSuite(object): + + def __init__(self, path): + self.path = path + + def should_generate(self, path): + if not os.path.isfile(path): + return True + + if any(module.modified for module in self.modules.values()): + return True + + return False + + def find_modules(self): + modules = [] + for root, _, files in os.walk(self.path): + module_root = root[len(self.path):] + 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) + module_name = "_".join(module_root + [test_file[:-2]]) + + modules.append((full_path, module_name)) + + return modules + + def load_cache(self): + path = os.path.join(self.path, '.clarcache') + cache = {} + + try: + fp = open(path) + cache = pickle.load(fp) + fp.close() + except IOError: + pass + + return cache + + def save_cache(self): + path = os.path.join(self.path, '.clarcache') + with open(path, 'w') as cache: + pickle.dump(self.modules, cache) + + def load(self, force = False): + module_data = self.find_modules() + self.modules = {} if force else self.load_cache() + + for path, name in module_data: + if name not in self.modules: + self.modules[name] = Module(name) + + if not self.modules[name].refresh(path): + del self.modules[name] + + def disable(self, excluded): + for exclude in excluded: + for module in self.modules.values(): + name = module.clean_name() + if name.startswith(exclude): + module.enabled = False + module.modified = True + + def suite_count(self): + return len(self.modules) + + def callback_count(self): + return sum(len(module.callbacks) for module in self.modules.values()) + + def write(self): + output = os.path.join(self.path, 'clar.suite') + + if not self.should_generate(output): + return False + + with open(output, 'w') as data: + for module in self.modules.values(): + t = Module.DeclarationTemplate(module) + data.write(t.render()) + + for module in self.modules.values(): + t = Module.CallbacksTemplate(module) + data.write(t.render()) + + suites = "static struct clar_suite _clar_suites[] = {" + ','.join( + Module.InfoTemplate(module).render() for module in self.modules.values() + ) + "};" + + data.write(suites) + + data.write("static const size_t _clar_suite_count = %d;" % self.suite_count()) + data.write("static const size_t _clar_callback_count = %d;" % self.callback_count()) + + suite.save_cache() + return True + +if __name__ == '__main__': + from optparse import OptionParser + + parser = OptionParser() + parser.add_option('-f', '--force', dest='force', default=False) + parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[]) + + options, args = parser.parse_args() + + for path in args or ['.']: + suite = TestSuite(path) + suite.load(options.force) + suite.disable(options.excluded) + if suite.write(): + print "Written `clar.suite` (%d suites)" % len(suite.modules) + diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c index 30a5a31fa..989734c1b 100644 --- a/tests-clar/index/tests.c +++ b/tests-clar/index/tests.c @@ -24,7 +24,6 @@ static struct test_entry test_entries[] = { {48, "src/revobject.h", 1448, 0x4C3F7FE2} }; - // Helpers static void copy_file(const char *src, const char *dst) { @@ -72,11 +71,6 @@ void test_index_tests__initialize(void) { } -void test_index_tests__cleanup(void) -{ -} - - void test_index_tests__empty_index(void) { git_index *index; @@ -203,49 +197,57 @@ void test_index_tests__sort1(void) git_index_free(index); } +static void cleanup_myrepo(void *opaque) +{ + GIT_UNUSED(opaque); + cl_fixture_cleanup("myrepo"); +} + void test_index_tests__add(void) { - git_index *index; - git_filebuf file = GIT_FILEBUF_INIT; - git_repository *repo; - const git_index_entry *entry; - git_oid id1; + git_index *index; + git_filebuf file = GIT_FILEBUF_INIT; + git_repository *repo; + const git_index_entry *entry; + git_oid id1; - /* Intialize a new repository */ - cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + cl_set_cleanup(&cleanup_myrepo, NULL); - /* Ensure we're the only guy in the room */ - cl_git_pass(git_repository_index(&index, repo)); - cl_assert(git_index_entrycount(index) == 0); + /* Intialize a new repository */ + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); - /* Create a new file in the working directory */ - cl_git_pass(git_futils_mkpath2file("myrepo/test.txt", 0777)); - cl_git_pass(git_filebuf_open(&file, "myrepo/test.txt", 0)); - cl_git_pass(git_filebuf_write(&file, "hey there\n", 10)); - cl_git_pass(git_filebuf_commit(&file, 0666)); + /* Ensure we're the only guy in the room */ + cl_git_pass(git_repository_index(&index, repo)); + cl_assert(git_index_entrycount(index) == 0); - /* Store the expected hash of the file/blob - * This has been generated by executing the following - * $ echo "hey there" | git hash-object --stdin - */ - cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); + /* Create a new file in the working directory */ + cl_git_pass(git_futils_mkpath2file("myrepo/test.txt", 0777)); + cl_git_pass(git_filebuf_open(&file, "myrepo/test.txt", 0)); + cl_git_pass(git_filebuf_write(&file, "hey there\n", 10)); + cl_git_pass(git_filebuf_commit(&file, 0666)); - /* Add the new file to the index */ - cl_git_pass(git_index_add_from_workdir(index, "test.txt")); + /* Store the expected hash of the file/blob + * This has been generated by executing the following + * $ echo "hey there" | git hash-object --stdin + */ + cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); - /* Wow... it worked! */ - cl_assert(git_index_entrycount(index) == 1); - entry = git_index_get_byindex(index, 0); + /* Add the new file to the index */ + cl_git_pass(git_index_add_from_workdir(index, "test.txt")); - /* And the built-in hashing mechanism worked as expected */ - cl_assert(git_oid_cmp(&id1, &entry->oid) == 0); + /* Wow... it worked! */ + cl_assert(git_index_entrycount(index) == 1); + entry = git_index_get_byindex(index, 0); - /* Test access by path instead of index */ - cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); - cl_assert(git_oid_cmp(&id1, &entry->oid) == 0); + /* And the built-in hashing mechanism worked as expected */ + cl_assert(git_oid_cmp(&id1, &entry->oid) == 0); - git_index_free(index); - git_repository_free(repo); + /* Test access by path instead of index */ + cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); + cl_assert(git_oid_cmp(&id1, &entry->oid) == 0); + + git_index_free(index); + git_repository_free(repo); } void test_index_tests__add_from_workdir_to_a_bare_repository_returns_EBAREPO(void) diff --git a/tests-clar/main.c b/tests-clar/main.c new file mode 100644 index 000000000..e9b3b641c --- /dev/null +++ b/tests-clar/main.c @@ -0,0 +1,16 @@ +#include "clar_libgit2.h" + +int main(int argc, char *argv[]) +{ + int res; + + git_threads_init(); + + /* Run the test suite */ + res = clar_test(argc, argv); + + giterr_clear(); + git_threads_shutdown(); + + return res; +} diff --git a/tests-clar/merge/setup.c b/tests-clar/merge/setup.c index 138c1ab8d..946c67e7b 100644 --- a/tests-clar/merge/setup.c +++ b/tests-clar/merge/setup.c @@ -44,22 +44,6 @@ void test_merge_setup__cleanup(void) cl_git_sandbox_cleanup(); } -static bool test_file_contents(const char *filename, const char *expected) -{ - git_buf file_path_buf = GIT_BUF_INIT, file_buf = GIT_BUF_INIT; - bool equals; - - git_buf_printf(&file_path_buf, "%s/%s", git_repository_path(repo), filename); - - cl_git_pass(git_futils_readbuffer(&file_buf, file_path_buf.ptr)); - equals = (strcmp(file_buf.ptr, expected) == 0); - - git_buf_free(&file_path_buf); - git_buf_free(&file_buf); - - return equals; -} - static void write_file_contents(const char *filename, const char *output) { git_buf file_path_buf = GIT_BUF_INIT; @@ -77,18 +61,14 @@ struct merge_head_cb_data { unsigned int i; }; -int merge_head_foreach_cb(git_oid *oid, void *payload) +static int merge_head_foreach_cb(const git_oid *oid, void *payload) { git_oid expected_oid; - struct merge_head_cb_data *cb_data = payload; git_oid_fromstr(&expected_oid, cb_data->oid_str[cb_data->i]); - cl_assert(git_oid_cmp(&expected_oid, oid) == 0); - cb_data->i++; - return 0; } diff --git a/tests-clar/network/fetchlocal.c b/tests-clar/network/fetchlocal.c index 24949243e..ee3bd9db3 100644 --- a/tests-clar/network/fetchlocal.c +++ b/tests-clar/network/fetchlocal.c @@ -11,6 +11,11 @@ static void transfer_cb(const git_transfer_progress *stats, void *payload) (*callcount)++; } +static void cleanup_local_repo(void *path) +{ + cl_fixture_cleanup((char *)path); +} + void test_network_fetchlocal__complete(void) { git_repository *repo; @@ -19,6 +24,8 @@ void test_network_fetchlocal__complete(void) git_strarray refnames = {0}; const char *url = cl_git_fixture_url("testrepo.git"); + + cl_set_cleanup(&cleanup_local_repo, "foo"); cl_git_pass(git_repository_init(&repo, "foo", true)); cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); @@ -35,6 +42,12 @@ void test_network_fetchlocal__complete(void) git_repository_free(repo); } +static void cleanup_sandbox(void *unused) +{ + GIT_UNUSED(unused); + cl_git_sandbox_cleanup(); +} + void test_network_fetchlocal__partial(void) { git_repository *repo = cl_git_sandbox_init("partial-testrepo"); @@ -43,6 +56,7 @@ void test_network_fetchlocal__partial(void) git_strarray refnames = {0}; const char *url; + cl_set_cleanup(&cleanup_sandbox, NULL); cl_git_pass(git_reference_list(&refnames, repo, GIT_REF_LISTALL)); cl_assert_equal_i(1, (int)refnames.count); @@ -60,6 +74,4 @@ void test_network_fetchlocal__partial(void) git_strarray_free(&refnames); git_remote_free(origin); - - cl_git_sandbox_cleanup(); } diff --git a/tests-clar/odb/alternates.c b/tests-clar/odb/alternates.c index 785d3bc84..be7bfa9cd 100644 --- a/tests-clar/odb/alternates.c +++ b/tests-clar/odb/alternates.c @@ -6,13 +6,18 @@ static git_buf destpath, filepath; static const char *paths[] = { "A.git", "B.git", "C.git", "D.git", "E.git", "F.git", "G.git" }; -static git_filebuf file; +static git_filebuf file; static git_repository *repo; void test_odb_alternates__cleanup(void) { + size_t i; + git_buf_free(&destpath); git_buf_free(&filepath); + + for (i = 0; i < ARRAY_SIZE(paths); i++) + cl_fixture_cleanup(paths[i]); } static void init_linked_repo(const char *path, const char *alternate) diff --git a/tests-clar/clone/network.c b/tests-clar/online/clone.c similarity index 87% rename from tests-clar/clone/network.c rename to tests-clar/online/clone.c index f3b967204..c216a1ea7 100644 --- a/tests-clar/clone/network.c +++ b/tests-clar/online/clone.c @@ -3,15 +3,13 @@ #include "git2/clone.h" #include "repository.h" -CL_IN_CATEGORY("network") - #define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository" #define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository" static git_repository *g_repo; static git_clone_options g_options; -void test_clone_network__initialize(void) +void test_online_clone__initialize(void) { git_checkout_opts dummy_opts = GIT_CHECKOUT_OPTS_INIT; @@ -23,22 +21,19 @@ void test_clone_network__initialize(void) g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; } -static void cleanup_repository(void *path) +void test_online_clone__cleanup(void) { if (g_repo) { git_repository_free(g_repo); g_repo = NULL; } - cl_fixture_cleanup((const char *)path); + cl_fixture_cleanup("./foo"); } - -void test_clone_network__network_full(void) +void test_online_clone__network_full(void) { git_remote *origin; - cl_set_cleanup(&cleanup_repository, "./foo"); - cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); cl_assert(!git_repository_is_bare(g_repo)); cl_git_pass(git_remote_load(&origin, g_repo, "origin")); @@ -46,12 +41,10 @@ void test_clone_network__network_full(void) git_remote_free(origin); } - -void test_clone_network__network_bare(void) +void test_online_clone__network_bare(void) { git_remote *origin; - cl_set_cleanup(&cleanup_repository, "./foo"); g_options.bare = true; cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); @@ -61,12 +54,10 @@ void test_clone_network__network_bare(void) git_remote_free(origin); } -void test_clone_network__empty_repository(void) +void test_online_clone__empty_repository(void) { git_reference *head; - cl_set_cleanup(&cleanup_repository, "./foo"); - cl_git_pass(git_clone(&g_repo, LIVE_EMPTYREPO_URL, "./foo", &g_options)); cl_assert_equal_i(true, git_repository_is_empty(g_repo)); @@ -93,7 +84,7 @@ static void fetch_progress(const git_transfer_progress *stats, void *payload) (*was_called) = true; } -void test_clone_network__can_checkout_a_cloned_repo(void) +void test_online_clone__can_checkout_a_cloned_repo(void) { git_buf path = GIT_BUF_INIT; git_reference *head; @@ -106,8 +97,6 @@ void test_clone_network__can_checkout_a_cloned_repo(void) g_options.fetch_progress_cb = &fetch_progress; g_options.fetch_progress_payload = &fetch_progress_cb_was_called; - cl_set_cleanup(&cleanup_repository, "./foo"); - cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "master.txt")); @@ -137,8 +126,6 @@ void test_clone_network__custom_remote_callbacks(void) git_remote_callbacks remote_callbacks = GIT_REMOTE_CALLBACKS_INIT; int callcount = 0; - cl_set_cleanup(&cleanup_repository, "./foo"); - g_options.remote_callbacks = &remote_callbacks; remote_callbacks.update_tips = update_tips; remote_callbacks.payload = &callcount; @@ -179,8 +166,6 @@ void test_clone_network__credentials(void) if (!remote_url) return; - cl_set_cleanup(&cleanup_repository, "./foo"); - g_options.cred_acquire_cb = cred_acquire; g_options.cred_acquire_payload = &user_pass; diff --git a/tests-clar/network/fetch.c b/tests-clar/online/fetch.c similarity index 87% rename from tests-clar/network/fetch.c rename to tests-clar/online/fetch.c index cde4f0365..41cdb30e1 100644 --- a/tests-clar/network/fetch.c +++ b/tests-clar/online/fetch.c @@ -1,16 +1,14 @@ #include "clar_libgit2.h" -CL_IN_CATEGORY("network") - static git_repository *_repo; static int counter; -void test_network_fetch__initialize(void) +void test_online_fetch__initialize(void) { cl_git_pass(git_repository_init(&_repo, "./fetch", 0)); } -void test_network_fetch__cleanup(void) +void test_online_fetch__cleanup(void) { git_repository_free(_repo); _repo = NULL; @@ -55,22 +53,22 @@ static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n) git_remote_free(remote); } -void test_network_fetch__default_git(void) +void test_online_fetch__default_git(void) { do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6); } -void test_network_fetch__default_http(void) +void test_online_fetch__default_http(void) { do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6); } -void test_network_fetch__no_tags_git(void) +void test_online_fetch__no_tags_git(void) { do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3); } -void test_network_fetch__no_tags_http(void) +void test_online_fetch__no_tags_http(void) { do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3); } @@ -83,7 +81,7 @@ static void transferProgressCallback(const git_transfer_progress *stats, void *p *invoked = true; } -void test_network_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date(void) +void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date(void) { git_repository *_repository; bool invoked = false; diff --git a/tests-clar/fetchhead/network.c b/tests-clar/online/fetchhead.c similarity index 83% rename from tests-clar/fetchhead/network.c rename to tests-clar/online/fetchhead.c index 412f356c3..f89270741 100644 --- a/tests-clar/fetchhead/network.c +++ b/tests-clar/online/fetchhead.c @@ -2,17 +2,15 @@ #include "repository.h" #include "fetchhead.h" -#include "fetchhead_data.h" +#include "../fetchhead/fetchhead_data.h" #include "git2/clone.h" -CL_IN_CATEGORY("network") - #define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository" static git_repository *g_repo; static git_clone_options g_options; -void test_fetchhead_network__initialize(void) +void test_online_fetchhead__initialize(void) { g_repo = NULL; @@ -20,21 +18,18 @@ void test_fetchhead_network__initialize(void) g_options.version = GIT_CLONE_OPTIONS_VERSION; } -static void cleanup_repository(void *path) +void test_online_fetchhead__cleanup(void) { if (g_repo) { git_repository_free(g_repo); g_repo = NULL; } - cl_fixture_cleanup((const char *)path); + cl_fixture_cleanup("./foo"); } - static void fetchhead_test_clone(void) { - cl_set_cleanup(&cleanup_repository, "./foo"); - cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); } @@ -65,19 +60,19 @@ static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fet cl_assert(equals); } -void test_fetchhead_network__wildcard_spec(void) +void test_online_fetchhead__wildcard_spec(void) { fetchhead_test_clone(); fetchhead_test_fetch(NULL, FETCH_HEAD_WILDCARD_DATA); } -void test_fetchhead_network__explicit_spec(void) +void test_online_fetchhead__explicit_spec(void) { fetchhead_test_clone(); fetchhead_test_fetch("refs/heads/first-merge:refs/remotes/origin/first-merge", FETCH_HEAD_EXPLICIT_DATA); } -void test_fetchhead_network__no_merges(void) +void test_online_fetchhead__no_merges(void) { git_config *config; diff --git a/tests-clar/network/push.c b/tests-clar/online/push.c similarity index 96% rename from tests-clar/network/push.c rename to tests-clar/online/push.c index ac9a8f5d4..9d949b77b 100644 --- a/tests-clar/network/push.c +++ b/tests-clar/online/push.c @@ -5,8 +5,6 @@ #include "../submodule/submodule_helpers.h" #include "push_util.h" -CL_IN_CATEGORY("network") - static git_repository *_repo; static char *_remote_url; @@ -112,7 +110,7 @@ static void do_verify_push_status(git_push *push, const push_status expected[], /** * Verifies that after git_push_finish(), refs on a remote have the expected * names, oids, and order. - * + * * @param remote remote to verify * @param expected_refs expected remote refs after push * @param expected_refs_len length of expected_refs @@ -127,7 +125,7 @@ static void verify_refs(git_remote *remote, expected_ref expected_refs[], size_t git_vector_free(&actual_refs); } -void test_network_push__initialize(void) +void test_online_push__initialize(void) { git_vector delete_specs = GIT_VECTOR_INIT; size_t i; @@ -207,7 +205,7 @@ void test_network_push__initialize(void) printf("GITTEST_REMOTE_URL unset; skipping push test\n"); } -void test_network_push__cleanup(void) +void test_online_push__cleanup(void) { if (_remote) git_remote_free(_remote); @@ -224,7 +222,7 @@ void test_network_push__cleanup(void) /** * Calls push and relists refs on remote to verify success. - * + * * @param refspecs refspecs to push * @param refspecs_len length of refspecs * @param expected_refs expected remote refs after push @@ -271,12 +269,12 @@ static void do_push(const char *refspecs[], size_t refspecs_len, } /* Call push_finish() without ever calling git_push_add_refspec() */ -void test_network_push__noop(void) +void test_online_push__noop(void) { do_push(NULL, 0, NULL, 0, NULL, 0, 0); } -void test_network_push__b1(void) +void test_online_push__b1(void) { const char *specs[] = { "refs/heads/b1:refs/heads/b1" }; push_status exp_stats[] = { { "refs/heads/b1", NULL } }; @@ -286,7 +284,7 @@ void test_network_push__b1(void) exp_refs, ARRAY_SIZE(exp_refs), 0); } -void test_network_push__b2(void) +void test_online_push__b2(void) { const char *specs[] = { "refs/heads/b2:refs/heads/b2" }; push_status exp_stats[] = { { "refs/heads/b2", NULL } }; @@ -296,7 +294,7 @@ void test_network_push__b2(void) exp_refs, ARRAY_SIZE(exp_refs), 0); } -void test_network_push__b3(void) +void test_online_push__b3(void) { const char *specs[] = { "refs/heads/b3:refs/heads/b3" }; push_status exp_stats[] = { { "refs/heads/b3", NULL } }; @@ -306,7 +304,7 @@ void test_network_push__b3(void) exp_refs, ARRAY_SIZE(exp_refs), 0); } -void test_network_push__b4(void) +void test_online_push__b4(void) { const char *specs[] = { "refs/heads/b4:refs/heads/b4" }; push_status exp_stats[] = { { "refs/heads/b4", NULL } }; @@ -316,7 +314,7 @@ void test_network_push__b4(void) exp_refs, ARRAY_SIZE(exp_refs), 0); } -void test_network_push__b5(void) +void test_online_push__b5(void) { const char *specs[] = { "refs/heads/b5:refs/heads/b5" }; push_status exp_stats[] = { { "refs/heads/b5", NULL } }; @@ -326,7 +324,7 @@ void test_network_push__b5(void) exp_refs, ARRAY_SIZE(exp_refs), 0); } -void test_network_push__multi(void) +void test_online_push__multi(void) { const char *specs[] = { "refs/heads/b1:refs/heads/b1", @@ -354,7 +352,7 @@ void test_network_push__multi(void) exp_refs, ARRAY_SIZE(exp_refs), 0); } -void test_network_push__implicit_tgt(void) +void test_online_push__implicit_tgt(void) { const char *specs1[] = { "refs/heads/b1:" }; push_status exp_stats1[] = { { "refs/heads/b1", NULL } }; @@ -375,7 +373,7 @@ void test_network_push__implicit_tgt(void) exp_refs2, ARRAY_SIZE(exp_refs2), 0); } -void test_network_push__fast_fwd(void) +void test_online_push__fast_fwd(void) { /* Fast forward b1 in tgt from _oid_b1 to _oid_b6. */ @@ -409,7 +407,7 @@ void test_network_push__fast_fwd(void) exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0); } -void test_network_push__force(void) +void test_online_push__force(void) { const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"}; push_status exp_stats1[] = { { "refs/heads/tgt", NULL } }; @@ -435,7 +433,7 @@ void test_network_push__force(void) exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0); } -void test_network_push__delete(void) +void test_online_push__delete(void) { const char *specs1[] = { "refs/heads/b1:refs/heads/tgt1", @@ -492,7 +490,7 @@ void test_network_push__delete(void) exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0); } -void test_network_push__bad_refspecs(void) +void test_online_push__bad_refspecs(void) { /* All classes of refspecs that should be rejected by * git_push_add_refspec() should go in this test. @@ -510,7 +508,7 @@ void test_network_push__bad_refspecs(void) } } -void test_network_push__expressions(void) +void test_online_push__expressions(void) { /* TODO: Expressions in refspecs doesn't actually work yet */ const char *specs_left_expr[] = { "refs/heads/b2~1:refs/heads/b2" }; diff --git a/tests-clar/network/push_util.c b/tests-clar/online/push_util.c similarity index 100% rename from tests-clar/network/push_util.c rename to tests-clar/online/push_util.c diff --git a/tests-clar/network/push_util.h b/tests-clar/online/push_util.h similarity index 100% rename from tests-clar/network/push_util.h rename to tests-clar/online/push_util.h diff --git a/tests-clar/pack/packbuilder.c b/tests-clar/pack/packbuilder.c index b450be6b6..513778781 100644 --- a/tests-clar/pack/packbuilder.c +++ b/tests-clar/pack/packbuilder.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "iterator.h" #include "vector.h" +#include "posix.h" static git_repository *_repo; static git_revwalk *_revwalker; @@ -11,7 +12,7 @@ static int _commits_is_initialized; void test_pack_packbuilder__initialize(void) { - cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + _repo = cl_git_sandbox_init("testrepo.git"); cl_git_pass(git_revwalk_new(&_revwalker, _repo)); cl_git_pass(git_packbuilder_new(&_packbuilder, _repo)); cl_git_pass(git_vector_init(&_commits, 0, NULL)); @@ -40,7 +41,7 @@ void test_pack_packbuilder__cleanup(void) git_indexer_free(_indexer); _indexer = NULL; - git_repository_free(_repo); + cl_git_sandbox_cleanup(); _repo = NULL; } @@ -88,9 +89,7 @@ static git_transfer_progress stats; static int foreach_cb(void *buf, size_t len, void *payload) { git_indexer_stream *idx = (git_indexer_stream *) payload; - cl_git_pass(git_indexer_stream_add(idx, buf, len, &stats)); - return 0; } diff --git a/tests-clar/stash/save.c b/tests-clar/stash/save.c index f8b427814..e6033e1ef 100644 --- a/tests-clar/stash/save.c +++ b/tests-clar/stash/save.c @@ -32,6 +32,7 @@ void test_stash_save__cleanup(void) repo = NULL; cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_fixture_cleanup("sorry-it-is-a-non-bare-only-party"); } static void assert_object_oid(const char* revision, const char* expected_oid, git_otype type) diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index a1f7f8684..6786b91ff 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -6,16 +6,6 @@ #include "util.h" #include "path.h" -/** - * Initializer - * - * Not all of the tests in this file use the same fixtures, so we allow each - * test to load their fixture at the top of the test function. - */ -void test_status_worktree__initialize(void) -{ -} - /** * Cleanup * @@ -409,256 +399,43 @@ void test_status_worktree__issue_592_ignored_dirs_with_tracked_content(void) */ } -void test_status_worktree__cannot_retrieve_the_status_of_a_bare_repository(void) +void test_status_worktree__conflict_with_diff3(void) { - git_repository *repo; - unsigned int status = 0; - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - - cl_assert_equal_i(GIT_EBAREREPO, git_status_file(&status, repo, "dummy")); - - git_repository_free(repo); -} - -void test_status_worktree__first_commit_in_progress(void) -{ - git_repository *repo; + git_repository *repo = cl_git_sandbox_init("status"); git_index *index; - status_entry_single result; + unsigned int status; + git_index_entry ancestor_entry, our_entry, their_entry; - cl_git_pass(git_repository_init(&repo, "getting_started", 0)); - cl_git_mkfile("getting_started/testfile.txt", "content\n"); + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(1, result.count); - cl_assert(result.status == GIT_STATUS_WT_NEW); + ancestor_entry.path = "modified_file"; + git_oid_fromstr(&ancestor_entry.oid, + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + + our_entry.path = "modified_file"; + git_oid_fromstr(&our_entry.oid, + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + + their_entry.path = "modified_file"; + git_oid_fromstr(&their_entry.oid, + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + + cl_git_pass(git_status_file(&status, repo, "modified_file")); + cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_from_workdir(index, "testfile.txt")); - cl_git_pass(git_index_write(index)); - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(1, result.count); - cl_assert(result.status == GIT_STATUS_INDEX_NEW); + cl_git_pass(git_index_remove(index, "modified_file", 0)); + cl_git_pass(git_index_conflict_add(index, &ancestor_entry, + &our_entry, &their_entry)); + + cl_git_pass(git_status_file(&status, repo, "modified_file")); + + cl_assert_equal_i(GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_NEW, status); git_index_free(index); - git_repository_free(repo); -} - - - -void test_status_worktree__status_file_without_index_or_workdir(void) -{ - git_repository *repo; - unsigned int status = 0; - git_index *index; - - cl_git_pass(p_mkdir("wd", 0777)); - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_set_workdir(repo, "wd", false)); - - cl_git_pass(git_index_open(&index, "empty-index")); - cl_assert_equal_i(0, (int)git_index_entrycount(index)); - git_repository_set_index(repo, index); - - cl_git_pass(git_status_file(&status, repo, "branch_file.txt")); - - cl_assert_equal_i(GIT_STATUS_INDEX_DELETED, status); - - git_repository_free(repo); - git_index_free(index); - cl_git_pass(p_rmdir("wd")); -} - -static void fill_index_wth_head_entries(git_repository *repo, git_index *index) -{ - git_oid oid; - git_commit *commit; - git_tree *tree; - - cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); - cl_git_pass(git_commit_lookup(&commit, repo, &oid)); - cl_git_pass(git_commit_tree(&tree, commit)); - - cl_git_pass(git_index_read_tree(index, tree)); - cl_git_pass(git_index_write(index)); - - git_tree_free(tree); - git_commit_free(commit); -} - -void test_status_worktree__status_file_with_clean_index_and_empty_workdir(void) -{ - git_repository *repo; - unsigned int status = 0; - git_index *index; - - cl_git_pass(p_mkdir("wd", 0777)); - - cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_set_workdir(repo, "wd", false)); - - cl_git_pass(git_index_open(&index, "my-index")); - fill_index_wth_head_entries(repo, index); - - git_repository_set_index(repo, index); - - cl_git_pass(git_status_file(&status, repo, "branch_file.txt")); - - cl_assert_equal_i(GIT_STATUS_WT_DELETED, status); - - git_repository_free(repo); - git_index_free(index); - cl_git_pass(p_rmdir("wd")); - cl_git_pass(p_unlink("my-index")); -} - -void test_status_worktree__bracket_in_filename(void) -{ - git_repository *repo; - git_index *index; - status_entry_single result; - unsigned int status_flags; - int error; - - #define FILE_WITH_BRACKET "LICENSE[1].md" - #define FILE_WITHOUT_BRACKET "LICENSE1.md" - - cl_git_pass(git_repository_init(&repo, "with_bracket", 0)); - cl_git_mkfile("with_bracket/" FILE_WITH_BRACKET, "I have a bracket in my name\n"); - - /* file is new to working directory */ - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(1, result.count); - cl_assert(result.status == GIT_STATUS_WT_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); - cl_assert(status_flags == GIT_STATUS_WT_NEW); - - /* ignore the file */ - - cl_git_rewritefile("with_bracket/.gitignore", "*.md\n.gitignore\n"); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(2, result.count); - cl_assert(result.status == GIT_STATUS_IGNORED); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); - cl_assert(status_flags == GIT_STATUS_IGNORED); - - /* don't ignore the file */ - - cl_git_rewritefile("with_bracket/.gitignore", ".gitignore\n"); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(2, result.count); - cl_assert(result.status == GIT_STATUS_WT_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); - cl_assert(status_flags == GIT_STATUS_WT_NEW); - - /* add the file to the index */ - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_from_workdir(index, FILE_WITH_BRACKET)); - cl_git_pass(git_index_write(index)); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(2, result.count); - cl_assert(result.status == GIT_STATUS_INDEX_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); - cl_assert(status_flags == GIT_STATUS_INDEX_NEW); - - /* Create file without bracket */ - - cl_git_mkfile("with_bracket/" FILE_WITHOUT_BRACKET, "I have no bracket in my name!\n"); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITHOUT_BRACKET)); - cl_assert(status_flags == GIT_STATUS_WT_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md")); - cl_assert(status_flags == GIT_STATUS_INDEX_NEW); - - error = git_status_file(&status_flags, repo, FILE_WITH_BRACKET); - cl_git_fail(error); - cl_assert_equal_i(GIT_EAMBIGUOUS, error); - - git_index_free(index); - git_repository_free(repo); -} - -void test_status_worktree__space_in_filename(void) -{ - git_repository *repo; - git_index *index; - status_entry_single result; - unsigned int status_flags; - -#define FILE_WITH_SPACE "LICENSE - copy.md" - - cl_git_pass(git_repository_init(&repo, "with_space", 0)); - cl_git_mkfile("with_space/" FILE_WITH_SPACE, "I have a space in my name\n"); - - /* file is new to working directory */ - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(1, result.count); - cl_assert(result.status == GIT_STATUS_WT_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); - cl_assert(status_flags == GIT_STATUS_WT_NEW); - - /* ignore the file */ - - cl_git_rewritefile("with_space/.gitignore", "*.md\n.gitignore\n"); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(2, result.count); - cl_assert(result.status == GIT_STATUS_IGNORED); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); - cl_assert(status_flags == GIT_STATUS_IGNORED); - - /* don't ignore the file */ - - cl_git_rewritefile("with_space/.gitignore", ".gitignore\n"); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(2, result.count); - cl_assert(result.status == GIT_STATUS_WT_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); - cl_assert(status_flags == GIT_STATUS_WT_NEW); - - /* add the file to the index */ - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_from_workdir(index, FILE_WITH_SPACE)); - cl_git_pass(git_index_write(index)); - - memset(&result, 0, sizeof(result)); - cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); - cl_assert_equal_i(2, result.count); - cl_assert(result.status == GIT_STATUS_INDEX_NEW); - - cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); - cl_assert(status_flags == GIT_STATUS_INDEX_NEW); - - git_index_free(index); - git_repository_free(repo); } static const char *filemode_paths[] = { @@ -726,52 +503,6 @@ void test_status_worktree__filemode_changes(void) git_config_free(cfg); } -static int cb_status__expected_path(const char *p, unsigned int s, void *payload) -{ - const char *expected_path = (const char *)payload; - - GIT_UNUSED(s); - - if (payload == NULL) - cl_fail("Unexpected path"); - - cl_assert_equal_s(expected_path, p); - - return 0; -} - -void test_status_worktree__disable_pathspec_match(void) -{ - git_repository *repo; - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - char *file_with_bracket = "LICENSE[1].md", - *imaginary_file_with_bracket = "LICENSE[1-2].md"; - - cl_git_pass(git_repository_init(&repo, "pathspec", 0)); - cl_git_mkfile("pathspec/LICENSE[1].md", "screaming bracket\n"); - cl_git_mkfile("pathspec/LICENSE1.md", "no bracket\n"); - - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; - opts.pathspec.count = 1; - opts.pathspec.strings = &file_with_bracket; - - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__expected_path, - file_with_bracket) - ); - - /* Test passing a pathspec matching files in the workdir. */ - /* Must not match because pathspecs are disabled. */ - opts.pathspec.strings = &imaginary_file_with_bracket; - cl_git_pass( - git_status_foreach_ext(repo, &opts, cb_status__expected_path, NULL) - ); - - git_repository_free(repo); -} - - static int cb_status__interrupt(const char *p, unsigned int s, void *payload) { volatile int *count = (int *)payload; @@ -796,33 +527,6 @@ void test_status_worktree__interruptable_foreach(void) cl_assert_equal_i(8, count); } -void test_status_worktree__new_staged_file_must_handle_crlf(void) -{ - git_repository *repo; - git_index *index; - git_config *config; - unsigned int status; - - cl_git_pass(git_repository_init(&repo, "getting_started", 0)); - - // Ensure that repo has core.autocrlf=true - cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_set_bool(config, "core.autocrlf", true)); - - cl_git_mkfile("getting_started/testfile.txt", "content\r\n"); // Content with CRLF - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_from_workdir(index, "testfile.txt")); - cl_git_pass(git_index_write(index)); - - cl_git_pass(git_status_file(&status, repo, "testfile.txt")); - cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status); - - git_config_free(config); - git_index_free(index); - git_repository_free(repo); -} - void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf(void) { git_repository *repo = cl_git_sandbox_init("status"); @@ -876,42 +580,3 @@ void test_status_worktree__conflicted_item(void) git_index_free(index); } -void test_status_worktree__conflict_with_diff3(void) -{ - git_repository *repo = cl_git_sandbox_init("status"); - git_index *index; - unsigned int status; - git_index_entry ancestor_entry, our_entry, their_entry; - - memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); - memset(&our_entry, 0x0, sizeof(git_index_entry)); - memset(&their_entry, 0x0, sizeof(git_index_entry)); - - ancestor_entry.path = "modified_file"; - git_oid_fromstr(&ancestor_entry.oid, - "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); - - our_entry.path = "modified_file"; - git_oid_fromstr(&our_entry.oid, - "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); - - their_entry.path = "modified_file"; - git_oid_fromstr(&their_entry.oid, - "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); - - cl_git_pass(git_status_file(&status, repo, "modified_file")); - cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); - - cl_git_pass(git_repository_index(&index, repo)); - - cl_git_pass(git_index_remove(index, "modified_file", 0)); - cl_git_pass(git_index_conflict_add(index, &ancestor_entry, - &our_entry, &their_entry)); - - cl_git_pass(git_status_file(&status, repo, "modified_file")); - - cl_assert_equal_i(GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_NEW, status); - - git_index_free(index); -} - diff --git a/tests-clar/status/worktree_init.c b/tests-clar/status/worktree_init.c new file mode 100644 index 000000000..6d790b1c9 --- /dev/null +++ b/tests-clar/status/worktree_init.c @@ -0,0 +1,342 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "ignore.h" +#include "status_helpers.h" +#include "posix.h" +#include "util.h" +#include "path.h" + +static void cleanup_new_repo(void *path) +{ + cl_fixture_cleanup((char *)path); +} + +void test_status_worktree_init__cannot_retrieve_the_status_of_a_bare_repository(void) +{ + git_repository *repo; + unsigned int status = 0; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_assert_equal_i(GIT_EBAREREPO, git_status_file(&status, repo, "dummy")); + git_repository_free(repo); +} + +void test_status_worktree_init__first_commit_in_progress(void) +{ + git_repository *repo; + git_index *index; + status_entry_single result; + + cl_set_cleanup(&cleanup_new_repo, "getting_started"); + + cl_git_pass(git_repository_init(&repo, "getting_started", 0)); + cl_git_mkfile("getting_started/testfile.txt", "content\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_from_workdir(index, "testfile.txt")); + cl_git_pass(git_index_write(index)); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_INDEX_NEW); + + git_index_free(index); + git_repository_free(repo); +} + + + +void test_status_worktree_init__status_file_without_index_or_workdir(void) +{ + git_repository *repo; + unsigned int status = 0; + git_index *index; + + cl_git_pass(p_mkdir("wd", 0777)); + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_set_workdir(repo, "wd", false)); + + cl_git_pass(git_index_open(&index, "empty-index")); + cl_assert_equal_i(0, (int)git_index_entrycount(index)); + git_repository_set_index(repo, index); + + cl_git_pass(git_status_file(&status, repo, "branch_file.txt")); + + cl_assert_equal_i(GIT_STATUS_INDEX_DELETED, status); + + git_repository_free(repo); + git_index_free(index); + cl_git_pass(p_rmdir("wd")); +} + +static void fill_index_wth_head_entries(git_repository *repo, git_index *index) +{ + git_oid oid; + git_commit *commit; + git_tree *tree; + + cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD")); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_commit_tree(&tree, commit)); + + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_write(index)); + + git_tree_free(tree); + git_commit_free(commit); +} + +void test_status_worktree_init__status_file_with_clean_index_and_empty_workdir(void) +{ + git_repository *repo; + unsigned int status = 0; + git_index *index; + + cl_git_pass(p_mkdir("wd", 0777)); + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_set_workdir(repo, "wd", false)); + + cl_git_pass(git_index_open(&index, "my-index")); + fill_index_wth_head_entries(repo, index); + + git_repository_set_index(repo, index); + + cl_git_pass(git_status_file(&status, repo, "branch_file.txt")); + + cl_assert_equal_i(GIT_STATUS_WT_DELETED, status); + + git_repository_free(repo); + git_index_free(index); + cl_git_pass(p_rmdir("wd")); + cl_git_pass(p_unlink("my-index")); +} + +void test_status_worktree_init__bracket_in_filename(void) +{ + git_repository *repo; + git_index *index; + status_entry_single result; + unsigned int status_flags; + int error; + + #define FILE_WITH_BRACKET "LICENSE[1].md" + #define FILE_WITHOUT_BRACKET "LICENSE1.md" + + cl_set_cleanup(&cleanup_new_repo, "with_bracket"); + + cl_git_pass(git_repository_init(&repo, "with_bracket", 0)); + cl_git_mkfile("with_bracket/" FILE_WITH_BRACKET, "I have a bracket in my name\n"); + + /* file is new to working directory */ + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* ignore the file */ + + cl_git_rewritefile("with_bracket/.gitignore", "*.md\n.gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_IGNORED); + + /* don't ignore the file */ + + cl_git_rewritefile("with_bracket/.gitignore", ".gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* add the file to the index */ + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_from_workdir(index, FILE_WITH_BRACKET)); + cl_git_pass(git_index_write(index)); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_INDEX_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + + /* Create file without bracket */ + + cl_git_mkfile("with_bracket/" FILE_WITHOUT_BRACKET, "I have no bracket in my name!\n"); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITHOUT_BRACKET)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md")); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + + error = git_status_file(&status_flags, repo, FILE_WITH_BRACKET); + cl_git_fail(error); + cl_assert_equal_i(GIT_EAMBIGUOUS, error); + + git_index_free(index); + git_repository_free(repo); +} + +void test_status_worktree_init__space_in_filename(void) +{ + git_repository *repo; + git_index *index; + status_entry_single result; + unsigned int status_flags; + +#define FILE_WITH_SPACE "LICENSE - copy.md" + + cl_set_cleanup(&cleanup_new_repo, "with_space"); + cl_git_pass(git_repository_init(&repo, "with_space", 0)); + cl_git_mkfile("with_space/" FILE_WITH_SPACE, "I have a space in my name\n"); + + /* file is new to working directory */ + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* ignore the file */ + + cl_git_rewritefile("with_space/.gitignore", "*.md\n.gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); + cl_assert(status_flags == GIT_STATUS_IGNORED); + + /* don't ignore the file */ + + cl_git_rewritefile("with_space/.gitignore", ".gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* add the file to the index */ + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_from_workdir(index, FILE_WITH_SPACE)); + cl_git_pass(git_index_write(index)); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_INDEX_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE)); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + + git_index_free(index); + git_repository_free(repo); +} + +static int cb_status__expected_path(const char *p, unsigned int s, void *payload) +{ + const char *expected_path = (const char *)payload; + + GIT_UNUSED(s); + + if (payload == NULL) + cl_fail("Unexpected path"); + + cl_assert_equal_s(expected_path, p); + + return 0; +} + +void test_status_worktree_init__disable_pathspec_match(void) +{ + git_repository *repo; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + char *file_with_bracket = "LICENSE[1].md", + *imaginary_file_with_bracket = "LICENSE[1-2].md"; + + cl_set_cleanup(&cleanup_new_repo, "pathspec"); + cl_git_pass(git_repository_init(&repo, "pathspec", 0)); + cl_git_mkfile("pathspec/LICENSE[1].md", "screaming bracket\n"); + cl_git_mkfile("pathspec/LICENSE1.md", "no bracket\n"); + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; + opts.pathspec.count = 1; + opts.pathspec.strings = &file_with_bracket; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__expected_path, + file_with_bracket) + ); + + /* Test passing a pathspec matching files in the workdir. */ + /* Must not match because pathspecs are disabled. */ + opts.pathspec.strings = &imaginary_file_with_bracket; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__expected_path, NULL) + ); + + git_repository_free(repo); +} + +void test_status_worktree_init__new_staged_file_must_handle_crlf(void) +{ + git_repository *repo; + git_index *index; + git_config *config; + unsigned int status; + + cl_set_cleanup(&cleanup_new_repo, "getting_started"); + cl_git_pass(git_repository_init(&repo, "getting_started", 0)); + + // Ensure that repo has core.autocrlf=true + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "core.autocrlf", true)); + + cl_git_mkfile("getting_started/testfile.txt", "content\r\n"); // Content with CRLF + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_from_workdir(index, "testfile.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_file(&status, repo, "testfile.txt")); + cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status); + + git_config_free(config); + git_index_free(index); + git_repository_free(repo); +} +