mirror of
https://github.com/google/pebble.git
synced 2025-06-21 16:46:17 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
361
tools/clar/_clar.py
Executable file
361
tools/clar/_clar.py
Executable file
|
@ -0,0 +1,361 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from __future__ import with_statement
|
||||
from string import Template
|
||||
import re, fnmatch, os
|
||||
|
||||
VERSION = "0.10.0"
|
||||
|
||||
TEST_FUNC_REGEX = r"^(void\s+(%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')
|
||||
parser.add_option('-f', '--file', dest='file')
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
folder = args[0] or '.'
|
||||
print 'folder: %s' % folder
|
||||
builder = ClarTestBuilder(folder,
|
||||
clar_path = options.clar_path,
|
||||
print_mode = options.print_mode)
|
||||
|
||||
if options.file is not None:
|
||||
builder.load_file(options.file)
|
||||
else:
|
||||
builder.load_dir(folder)
|
||||
|
||||
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_mock.c",
|
||||
"clar_categorize.c",
|
||||
]
|
||||
|
||||
self.modules.append("clar_print_%s.c" % print_mode)
|
||||
|
||||
def load_dir(self, folder):
|
||||
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 open(full_path) as f:
|
||||
self._process_test_file(test_name, f.read())
|
||||
|
||||
def load_file(self, filename):
|
||||
with open(filename) as f:
|
||||
test_name = os.path.basename(filename)[:-2]
|
||||
self._process_test_file(test_name, f.read())
|
||||
|
||||
def render(self):
|
||||
if not self.suite_data:
|
||||
raise RuntimeError('No tests found under "%s"' % self.path)
|
||||
|
||||
if not os.path.isdir(self.path):
|
||||
os.makedirs(self.path)
|
||||
|
||||
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) ]
|
735
tools/clar/clar.c
Normal file
735
tools/clar/clar.c
Normal file
|
@ -0,0 +1,735 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* required for sandboxing */
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
# include <io.h>
|
||||
# include <shellapi.h>
|
||||
# include <direct.h>
|
||||
|
||||
# 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)
|
||||
|
||||
# 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
|
||||
|
||||
# ifdef UNITTEST_DEBUG
|
||||
# include <signal.h>
|
||||
# endif
|
||||
|
||||
# include <sys/wait.h> /* waitpid(2) */
|
||||
# include <unistd.h>
|
||||
# define _MAIN_CC
|
||||
# define snprint_eq snprintf
|
||||
typedef struct stat STAT_T;
|
||||
#endif
|
||||
|
||||
#include "clar.h"
|
||||
|
||||
#define CL_WITHIN(n, min, max) ((n) >= (min) && (n) <= (max))
|
||||
|
||||
bool clar_expecting_passert = false;
|
||||
bool clar_passert_occurred = false;
|
||||
jmp_buf clar_passert_jmp_buf;
|
||||
|
||||
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 test_count;
|
||||
|
||||
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 {
|
||||
int index;
|
||||
const char *name;
|
||||
struct clar_func initialize;
|
||||
struct clar_func cleanup;
|
||||
const char **categories;
|
||||
const struct clar_func *tests;
|
||||
size_t test_count;
|
||||
};
|
||||
|
||||
/* 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);
|
||||
|
||||
/* From clar_mock.c */
|
||||
static void clar_mock_reset(void);
|
||||
static void clar_mock_cleanup(void);
|
||||
|
||||
/* From clar_categorize.c */
|
||||
static int clar_category_is_suite_enabled(const struct clar_suite *);
|
||||
static void clar_category_enable(const char *category);
|
||||
static void clar_category_enable_all(size_t, const struct clar_suite *);
|
||||
static void clar_category_print_enabled(const char *prefix);
|
||||
|
||||
/* Event callback overrides */
|
||||
${clar_event_overrides}
|
||||
|
||||
/* Autogenerated test data by clar */
|
||||
${clar_callbacks}
|
||||
|
||||
${clar_categories}
|
||||
|
||||
static const struct clar_suite _clar_suites[] = {
|
||||
${clar_suites}
|
||||
};
|
||||
|
||||
static size_t _clar_suite_count = ${clar_suite_count};
|
||||
static size_t _clar_callback_count = ${clar_callback_count};
|
||||
|
||||
/* 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_on_test();
|
||||
_clar.trampoline_enabled = 1;
|
||||
|
||||
clar_passert_occurred = false;
|
||||
clar_expecting_passert = false;
|
||||
if (setjmp(_clar.trampoline) == 0) {
|
||||
if (initialize->ptr != NULL)
|
||||
initialize->ptr();
|
||||
|
||||
printf("\n#------------------------------------------------------------------------");
|
||||
printf("\n# Running test: %s...\n", test->name);
|
||||
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.test_count++;
|
||||
|
||||
/* 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.test_count,
|
||||
(_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 (!clar_category_is_suite_enabled(suite))
|
||||
return;
|
||||
|
||||
if (_clar.exit_on_error && _clar.total_errors)
|
||||
return;
|
||||
|
||||
if (!_clar.report_errors_only)
|
||||
clar_print_onsuite(suite->name, suite->index);
|
||||
clar_on_suite();
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0 /* temporarily disabled */
|
||||
static void
|
||||
clar_run_single(const struct clar_func *test,
|
||||
const struct clar_suite *suite)
|
||||
{
|
||||
_clar.suite_errors = 0;
|
||||
_clar.active_suite = suite->name;
|
||||
_clar.active_test = test->name;
|
||||
|
||||
clar_run_test(test, &suite->initialize, &suite->cleanup);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
clar_usage(const char *arg)
|
||||
{
|
||||
printf("Usage: %s [options]\n\n", arg);
|
||||
printf("Options:\n");
|
||||
printf(" -sXX\t\tRun only the suite number or name XX\n");
|
||||
printf(" -i<name>\tInclude category <name> tests\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, category, and test names\n");
|
||||
printf(" -tXX\t\tRun a specifc test by name\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': { /* given suite number, name, or prefix */
|
||||
int num = 0, offset = (argument[2] == '=') ? 3 : 2;
|
||||
int len = 0, is_num = 1, has_colon = 0, j;
|
||||
|
||||
for (argument += offset; *argument; ++argument) {
|
||||
len++;
|
||||
if (*argument >= '0' && *argument <= '9')
|
||||
num = (num * 10) + (*argument - '0');
|
||||
else {
|
||||
is_num = 0;
|
||||
if (*argument == ':')
|
||||
has_colon = 1;
|
||||
}
|
||||
}
|
||||
|
||||
argument = argv[i] + offset;
|
||||
|
||||
if (!len)
|
||||
clar_usage(argv[0]);
|
||||
else if (is_num) {
|
||||
if ((size_t)num >= _clar_suite_count) {
|
||||
clar_print_onabort("Suite number %d does not exist.\n", num);
|
||||
exit(-1);
|
||||
}
|
||||
clar_run_suite(&_clar_suites[num]);
|
||||
}
|
||||
else if (!has_colon || argument[-1] == ':') {
|
||||
for (j = 0; j < (int)_clar_suite_count; ++j)
|
||||
if (strncmp(argument, _clar_suites[j].name, len) == 0)
|
||||
clar_run_suite(&_clar_suites[j]);
|
||||
}
|
||||
else {
|
||||
for (j = 0; j < (int)_clar_suite_count; ++j)
|
||||
if (strcmp(argument, _clar_suites[j].name) == 0) {
|
||||
clar_run_suite(&_clar_suites[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_clar.active_suite == NULL) {
|
||||
clar_print_onabort("No suite matching '%s' found.\n", argument);
|
||||
exit(-1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Run a particular test only. This code was added on top of base clar.
|
||||
case 't': {
|
||||
int offset = (argument[2] == '=') ? 3 : 2;
|
||||
char *test_name = argument + offset;
|
||||
const struct clar_suite *suite = &_clar_suites[0];
|
||||
|
||||
const struct clar_func *test_funcs = suite->tests;
|
||||
const struct clar_func *test = NULL;
|
||||
for (int i = 0; i < suite->test_count; i++) {
|
||||
if (!strcmp(test_funcs[i].name, test_name)) {
|
||||
test = &test_funcs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (test == NULL) {
|
||||
clar_print_onabort("No test named '%s'.\n", argument);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
_clar.active_suite = suite->name;
|
||||
_clar.suite_errors = 0;
|
||||
_clar.active_test = test->name;
|
||||
clar_run_test(test, &suite->initialize, &suite->cleanup);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 'q':
|
||||
_clar.report_errors_only = 1;
|
||||
break;
|
||||
|
||||
case 'Q':
|
||||
_clar.exit_on_error = 1;
|
||||
break;
|
||||
|
||||
case 'i': {
|
||||
int offset = (argument[2] == '=') ? 3 : 2;
|
||||
if (strcasecmp("all", argument + offset) == 0)
|
||||
clar_category_enable_all(_clar_suite_count, _clar_suites);
|
||||
else
|
||||
clar_category_enable(argument + offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'l': {
|
||||
size_t j;
|
||||
printf("Test suites (use -s<name> to run just one):\n");
|
||||
for (j = 0; j < _clar_suite_count; ++j)
|
||||
printf(" %3d: %s\n", (int)j, _clar_suites[j].name);
|
||||
|
||||
printf("\nCategories (use -i<category> to include):\n");
|
||||
clar_category_enable_all(_clar_suite_count, _clar_suites);
|
||||
clar_category_print_enabled(" - ");
|
||||
|
||||
printf("\nTest names (use -t<name> to run just one):\n");
|
||||
for (j = 0; j < _clar_suite_count; ++j) {
|
||||
const struct clar_suite *suite = &_clar_suites[j];
|
||||
for (int i = 0; i < suite->test_count; i++) {
|
||||
printf(" %s\n", suite->tests[i].name);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
default:
|
||||
clar_usage(argv[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static 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);
|
||||
}
|
||||
|
||||
clar_mock_reset();
|
||||
|
||||
clar_on_init();
|
||||
|
||||
if (argc > 1)
|
||||
clar_parse_args(argc, argv);
|
||||
|
||||
if (_clar.active_suite == NULL) {
|
||||
size_t i;
|
||||
for (i = 0; i < _clar_suite_count; ++i)
|
||||
clar_run_suite(&_clar_suites[i]);
|
||||
}
|
||||
|
||||
clar_print_shutdown(
|
||||
_clar.test_count,
|
||||
(int)_clar_suite_count,
|
||||
_clar.total_errors
|
||||
);
|
||||
|
||||
clar_on_shutdown();
|
||||
|
||||
clar_mock_cleanup();
|
||||
|
||||
clar_unsandbox();
|
||||
return _clar.total_errors;
|
||||
}
|
||||
|
||||
char *strdup(const char *s1);
|
||||
|
||||
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;
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef UNITTEST_DEBUG
|
||||
// Break in debugger:
|
||||
raise(SIGINT);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
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.test_count;
|
||||
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 clar__assert_equal_d(
|
||||
double d1,
|
||||
double d2,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *err,
|
||||
int should_abort)
|
||||
{
|
||||
if (d1 != d2) {
|
||||
char buf[128];
|
||||
snprint_eq(buf, 128, "%f != %f", d1, d2);
|
||||
clar__assert(0, file, line, err, buf, should_abort);
|
||||
}
|
||||
}
|
||||
|
||||
void clar__assert_within(
|
||||
int n,
|
||||
int min,
|
||||
int max,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *err,
|
||||
int should_abort)
|
||||
{
|
||||
if (!CL_WITHIN(n, min, max)) {
|
||||
char buf[256];
|
||||
snprint_eq(buf, 256, "%d not within [%d, %d]", n, min, max);
|
||||
clar__assert(0, file, line, err, buf, should_abort);
|
||||
}
|
||||
}
|
||||
|
||||
void clar__assert_near(
|
||||
int i1,
|
||||
int i2,
|
||||
int abs_err,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *err,
|
||||
int should_abort)
|
||||
{
|
||||
if (abs(i1 - i2) > abs_err) {
|
||||
char buf[256];
|
||||
snprint_eq(buf, 256, "Difference between %d and %d exceeds %d", i1, i2, abs_err);
|
||||
clar__assert(0, file, line, err, buf, should_abort);
|
||||
}
|
||||
}
|
||||
|
||||
void clar__assert_cmp_i(
|
||||
int i1,
|
||||
int i2,
|
||||
ClarCmpOp op,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *err,
|
||||
int should_abort)
|
||||
{
|
||||
char *fmt;
|
||||
bool rv = false;
|
||||
switch (op) {
|
||||
case ClarCmpOp_EQ:
|
||||
fmt = "NOT ==";
|
||||
rv = (i1 == i2);
|
||||
break;
|
||||
case ClarCmpOp_LE:
|
||||
rv = (i1 <= i2);
|
||||
fmt = "NOT <=";
|
||||
break;
|
||||
case ClarCmpOp_LT:
|
||||
rv = (i1 < i2);
|
||||
fmt = "NOT <";
|
||||
break;
|
||||
case ClarCmpOp_GE:
|
||||
rv = (i1 >= i2);
|
||||
fmt = "NOT >=";
|
||||
break;
|
||||
case ClarCmpOp_GT:
|
||||
rv = (i1 > i2);
|
||||
fmt = "NOT >";
|
||||
break;
|
||||
case ClarCmpOp_NE:
|
||||
rv = (i1 != i2);
|
||||
fmt = "NOT !=";
|
||||
break;
|
||||
}
|
||||
if (!rv) {
|
||||
char buf[256] = {};
|
||||
snprintf(buf, 256, "%d %s %d", i1, fmt, i2);
|
||||
clar__assert(0, file, line, err, buf, should_abort);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Shamelessly stolen from
|
||||
// http://stackoverflow.com/questions/7775991/how-to-get-hexdump-of-a-structure-data
|
||||
void hex_dump(char *desc, void *addr, int len) {
|
||||
int i;
|
||||
unsigned char buff[17];
|
||||
unsigned char *pc = (unsigned char*)addr;
|
||||
|
||||
// Output description if given.
|
||||
if (desc != NULL) {
|
||||
printf("%s:\n", desc);
|
||||
}
|
||||
|
||||
// Process every byte in the data.
|
||||
for (i = 0; i < len; i++) {
|
||||
// Multiple of 16 means new line (with line offset).
|
||||
|
||||
if ((i % 16) == 0) {
|
||||
// Just don't print ASCII for the zeroth line.
|
||||
if (i != 0) {
|
||||
printf(" %s\n", buff);
|
||||
}
|
||||
|
||||
// Output the offset.
|
||||
printf(" %04x ", i);
|
||||
}
|
||||
|
||||
// Now the hex code for the specific character.
|
||||
printf(" %02x", pc[i]);
|
||||
|
||||
// And store a printable ASCII character for later.
|
||||
if ((pc[i] < 0x20) || (pc[i] > 0x7e)) {
|
||||
buff[i % 16] = '.';
|
||||
} else {
|
||||
buff[i % 16] = pc[i];
|
||||
}
|
||||
buff[(i % 16) + 1] = '\0';
|
||||
}
|
||||
|
||||
// Pad out last line if not exactly 16 characters.
|
||||
while ((i % 16) != 0) {
|
||||
printf(" ");
|
||||
i++;
|
||||
}
|
||||
|
||||
// And print the final ASCII bit.
|
||||
printf(" %s\n", buff);
|
||||
}
|
||||
|
||||
void clar__assert_equal_m(
|
||||
uint8_t *m1,
|
||||
uint8_t *m2,
|
||||
int n,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *err,
|
||||
int should_abort)
|
||||
{
|
||||
if (m1 == m2 || n == 0) {
|
||||
return;
|
||||
}
|
||||
if (m1 == NULL || m2 == NULL) {
|
||||
char buf[4096];
|
||||
snprint_eq(buf, 4096, "'%p' != '%p'", m1, m2);
|
||||
clar__assert(0, file, line, err, buf, should_abort);
|
||||
}
|
||||
|
||||
for (int offset = 0; offset < n; offset++) {
|
||||
if (m1[offset] == m2[offset]) {
|
||||
continue;
|
||||
}
|
||||
int len = 4096;
|
||||
int off = 0;
|
||||
char buf[len];
|
||||
off += snprint_eq(buf + off, len - off, "Mismatch at offset %d. Look above for diff.\n", offset);
|
||||
hex_dump("m1:", m1, n);
|
||||
hex_dump("m2:", m2, n);
|
||||
|
||||
fflush(stdout);
|
||||
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;
|
||||
}
|
||||
|
||||
${clar_modules}
|
||||
|
||||
int _MAIN_CC main(int argc, char *argv[])
|
||||
{
|
||||
return clar_test(argc, argv);
|
||||
}
|
27
tools/clar/clar.h
Normal file
27
tools/clar/clar.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __CLAR_TEST_H__
|
||||
#define __CLAR_TEST_H__
|
||||
|
||||
#include "clar_asserts.h"
|
||||
|
||||
/**
|
||||
* Test method declarations
|
||||
*/
|
||||
${extern_declarations}
|
||||
|
||||
#endif
|
376
tools/clar/clar.py
Executable file
376
tools/clar/clar.py
Executable file
File diff suppressed because one or more lines are too long
98
tools/clar/clar_categorize.c
Normal file
98
tools/clar/clar_categorize.c
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define CLAR_CATEGORY_DEFAULT "default"
|
||||
|
||||
typedef struct {
|
||||
const char **names;
|
||||
int count;
|
||||
int alloc;
|
||||
} clar_category_list;
|
||||
|
||||
static clar_category_list _clar_categorize_enabled;
|
||||
|
||||
static int clar_category_in_list(clar_category_list *list, const char *cat)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < list->count; ++i)
|
||||
if (strcasecmp(cat, list->names[i]) == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void clar_category_add_to_list(clar_category_list *list, const char *cat)
|
||||
{
|
||||
if (clar_category_in_list(list, cat))
|
||||
return;
|
||||
|
||||
if (list->count >= list->alloc) {
|
||||
list->alloc += 10;
|
||||
list->names = realloc(list->names, list->alloc * sizeof(const char *));
|
||||
}
|
||||
|
||||
list->names[list->count++] = cat;
|
||||
}
|
||||
|
||||
static void clar_category_enable(const char *category)
|
||||
{
|
||||
clar_category_add_to_list(&_clar_categorize_enabled, category);
|
||||
}
|
||||
|
||||
static void clar_category_enable_all(size_t suite_count, const struct clar_suite *suites)
|
||||
{
|
||||
size_t i;
|
||||
const char **cat;
|
||||
|
||||
clar_category_enable(CLAR_CATEGORY_DEFAULT);
|
||||
|
||||
for (i = 0; i < suite_count; i++)
|
||||
for (cat = suites[i].categories; cat && *cat; cat++)
|
||||
clar_category_enable(*cat);
|
||||
}
|
||||
|
||||
static int clar_category_cmp(const void *a, const void *b)
|
||||
{
|
||||
return - strcasecmp(a,b);
|
||||
}
|
||||
|
||||
static void clar_category_print_enabled(const char *prefix)
|
||||
{
|
||||
int i;
|
||||
|
||||
qsort(_clar_categorize_enabled.names, _clar_categorize_enabled.count,
|
||||
sizeof(const char *), clar_category_cmp);
|
||||
|
||||
for (i = 0; i < _clar_categorize_enabled.count; ++i)
|
||||
printf("%s%s\n", prefix, _clar_categorize_enabled.names[i]);
|
||||
}
|
||||
|
||||
static int clar_category_is_suite_enabled(const struct clar_suite *suite)
|
||||
{
|
||||
const char **scan;
|
||||
|
||||
if (!_clar_categorize_enabled.count)
|
||||
clar_category_enable(CLAR_CATEGORY_DEFAULT);
|
||||
|
||||
if (!suite->categories)
|
||||
return clar_category_in_list(
|
||||
&_clar_categorize_enabled, CLAR_CATEGORY_DEFAULT);
|
||||
|
||||
for (scan = suite->categories; *scan != NULL; scan++)
|
||||
if (clar_category_in_list(&_clar_categorize_enabled, *scan))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
54
tools/clar/clar_fixtures.c
Normal file
54
tools/clar/clar_fixtures.c
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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
|
194
tools/clar/clar_fs.c
Normal file
194
tools/clar/clar_fs.c
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#define FOF_FLAGS (FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR)
|
||||
|
||||
static char *
|
||||
fileops_path(const char *_path)
|
||||
{
|
||||
char *path = NULL;
|
||||
size_t length, i;
|
||||
|
||||
if (_path == NULL)
|
||||
return NULL;
|
||||
|
||||
length = strlen(_path);
|
||||
path = malloc(length + 2);
|
||||
|
||||
if (path == NULL)
|
||||
return NULL;
|
||||
|
||||
memcpy(path, _path, length);
|
||||
path[length] = 0;
|
||||
path[length + 1] = 0;
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
if (path[i] == '/')
|
||||
path[i] = '\\';
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static void
|
||||
fileops(int mode, const char *_source, const char *_dest)
|
||||
{
|
||||
SHFILEOPSTRUCT fops;
|
||||
|
||||
char *source = fileops_path(_source);
|
||||
char *dest = fileops_path(_dest);
|
||||
|
||||
ZeroMemory(&fops, sizeof(SHFILEOPSTRUCT));
|
||||
|
||||
fops.wFunc = mode;
|
||||
fops.pFrom = source;
|
||||
fops.pTo = dest;
|
||||
fops.fFlags = FOF_FLAGS;
|
||||
|
||||
cl_assert_(
|
||||
SHFileOperation(&fops) == 0,
|
||||
"Windows SHFileOperation failed"
|
||||
);
|
||||
|
||||
free(source);
|
||||
free(dest);
|
||||
}
|
||||
|
||||
static void
|
||||
fs_rm(const char *_source)
|
||||
{
|
||||
fileops(FO_DELETE, _source, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
fs_copy(const char *_source, const char *_dest)
|
||||
{
|
||||
fileops(FO_COPY, _source, _dest);
|
||||
}
|
||||
|
||||
void
|
||||
cl_fs_cleanup(void)
|
||||
{
|
||||
fs_rm(fixture_path(_clar_path, "*"));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
#include <emscripten/emscripten.h>
|
||||
// fork() is not supported by Emscripten, so try to use Node's child_process.execSync
|
||||
static int
|
||||
shell_out(char * const argv[])
|
||||
{
|
||||
return EM_ASM_INT({
|
||||
var child_process = require('child_process');
|
||||
try {
|
||||
var args = [];
|
||||
for (var i = 0;; i++) {
|
||||
var argStrPtr = getValue($0 + i*4, '*');
|
||||
if (argStrPtr === 0) {
|
||||
break;
|
||||
}
|
||||
var arg = Pointer_stringify(argStrPtr);
|
||||
args.push(arg);
|
||||
}
|
||||
// FIXME: need to escape args?
|
||||
var out = child_process.execSync(args.join(' '));
|
||||
return 0;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return -1;
|
||||
}
|
||||
}, argv);
|
||||
}
|
||||
#else // #ifdef EMSCRIPTEN
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
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
|
122
tools/clar/clar_mock.c
Normal file
122
tools/clar/clar_mock.c
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
typedef struct MockListNode {
|
||||
struct MockListNode *next;
|
||||
struct MockListNode *prev;
|
||||
const char *func;
|
||||
uintmax_t value;
|
||||
ssize_t count;
|
||||
} MockListNode;
|
||||
|
||||
static MockListNode s_mock_list_head = {
|
||||
&s_mock_list_head, &s_mock_list_head,
|
||||
NULL, 0, -1,
|
||||
};
|
||||
|
||||
uintmax_t clar__mock(const char *const func, const char *const file, const size_t line)
|
||||
{
|
||||
MockListNode *node;
|
||||
// Walk the list backwards, for FIFO behavior.
|
||||
for (node = s_mock_list_head.prev; node != &s_mock_list_head; node = node->prev) {
|
||||
if (node->func == NULL) {
|
||||
continue;
|
||||
} else if (strcmp(node->func, func) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
char error_msg[128];
|
||||
snprintf(error_msg, sizeof(error_msg), "No more mock values available for '%s'!", func);
|
||||
cl_assert_(node != &s_mock_list_head, error_msg);
|
||||
|
||||
// Save the value.
|
||||
uintmax_t value = node->value;
|
||||
cl_assert_(node->count != 0, "Mock node count is invalid");
|
||||
// If the node isn't permanent, lower its counter.
|
||||
// If the result is zero, we need to remove this mock value.
|
||||
if (node->count > 0 && --node->count == 0) {
|
||||
node->prev->next = node->next;
|
||||
node->next->prev = node->prev;
|
||||
free(node);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void clar__will_return(const char *const func, const char *const file, const size_t line,
|
||||
const uintmax_t value, const ssize_t count)
|
||||
{
|
||||
MockListNode *node = calloc(1, sizeof(MockListNode));
|
||||
cl_assert_(func != NULL, "cl_will_return with invalid function name");
|
||||
node->func = func;
|
||||
node->value = value;
|
||||
node->count = count;
|
||||
|
||||
// Add to beginning of list
|
||||
node->next = s_mock_list_head.next;
|
||||
node->prev = &s_mock_list_head;
|
||||
s_mock_list_head.next = node;
|
||||
// Fixup the next entry.
|
||||
// In the case that the list was empty before this call, this will make
|
||||
// s_mock_list_head.prev point to the new node, which is what we want.
|
||||
cl_assert_(node->next != NULL, "Mock list corrupted!");
|
||||
node->next->prev = node;
|
||||
}
|
||||
|
||||
static void clar_mock_reset(void)
|
||||
{
|
||||
s_mock_list_head.next = &s_mock_list_head;
|
||||
s_mock_list_head.prev = &s_mock_list_head;
|
||||
s_mock_list_head.func = NULL;
|
||||
s_mock_list_head.value = 0;
|
||||
s_mock_list_head.count = -1;
|
||||
}
|
||||
|
||||
static void clar_mock_cleanup(void)
|
||||
{
|
||||
MockListNode *node, *next;
|
||||
for (node = s_mock_list_head.next; node != &s_mock_list_head; node = next) {
|
||||
next = node->next;
|
||||
free(node);
|
||||
}
|
||||
clar_mock_reset();
|
||||
}
|
||||
|
||||
// Who tests the test framework!
|
||||
#if 0
|
||||
int gack(void)
|
||||
{
|
||||
return cl_mock_type(int);
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
{
|
||||
cl_will_return(gack, 573);
|
||||
printf("gack() = %d\n", gack()); // 573
|
||||
cl_will_return(gack, 123);
|
||||
cl_will_return(gack, 456);
|
||||
cl_will_return(gack, 789);
|
||||
printf("gack() = %d\n", gack()); // 123
|
||||
printf("gack() = %d\n", gack()); // 456
|
||||
printf("gack() = %d\n", gack()); // 789
|
||||
cl_will_return_count(gack, 765, 3);
|
||||
printf("gack() = %d\n", gack()); // 765
|
||||
printf("gack() = %d\n", gack()); // 765
|
||||
printf("gack() = %d\n", gack()); // 765
|
||||
|
||||
printf("gack() = %d\n", gack());
|
||||
return 0;
|
||||
}
|
||||
#endif
|
75
tools/clar/clar_print_default.c
Normal file
75
tools/clar/clar_print_default.c
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
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) [%s:%d] [-t%d]\n",
|
||||
error->suite,
|
||||
error->test,
|
||||
"no description",
|
||||
error->file,
|
||||
error->line_number,
|
||||
error->test_number);
|
||||
|
||||
printf(" %s\n", error->error_msg);
|
||||
|
||||
if (error->description != NULL)
|
||||
printf(" %s\n", error->description);
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void clar_print_ontest(const char *test_name, int test_number, int failed)
|
||||
{
|
||||
(void)test_name;
|
||||
(void)test_number;
|
||||
printf("%c", failed ? 'F' : '.');
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
81
tools/clar/clar_print_tap.c
Normal file
81
tools/clar/clar_print_tap.c
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
static void clar_print_init(int test_count, int suite_count, const char *suite_names)
|
||||
{
|
||||
(void)test_count;
|
||||
(void)suite_names;
|
||||
(void)suite_count;
|
||||
printf("TAP version 13\n");
|
||||
}
|
||||
|
||||
static void clar_print_shutdown(int test_count, int suite_count, int error_count)
|
||||
{
|
||||
(void)test_count;
|
||||
(void)suite_count;
|
||||
(void)error_count;
|
||||
|
||||
if (!error_count)
|
||||
printf("# passed all %d test(s)\n", test_count);
|
||||
else
|
||||
printf("# failed %d among %d test(s)\n", error_count,
|
||||
test_count);
|
||||
printf("1..%d\n", test_count);
|
||||
}
|
||||
|
||||
static void clar_print_error(int num, const struct clar_error *error)
|
||||
{
|
||||
(void)num;
|
||||
|
||||
printf(" ---\n");
|
||||
printf(" message : %s\n", error->error_msg);
|
||||
printf(" severity: fail\n");
|
||||
printf(" suite : %s\n", error->suite);
|
||||
printf(" test : %s\n", error->test);
|
||||
printf(" file : %s\n", error->file);
|
||||
printf(" line : %d\n", error->line_number);
|
||||
|
||||
if (error->description != NULL)
|
||||
printf(" description: %s\n", error->description);
|
||||
|
||||
printf(" ...\n");
|
||||
}
|
||||
|
||||
static void clar_print_ontest(const char *test_name, int test_number, int failed)
|
||||
{
|
||||
printf("%s %d - %s\n",
|
||||
failed ? "not ok" : "ok",
|
||||
test_number,
|
||||
test_name
|
||||
);
|
||||
|
||||
clar_report_errors();
|
||||
}
|
||||
|
||||
static void clar_print_onsuite(const char *suite_name, int suite_index)
|
||||
{
|
||||
printf("# *** %s (#%d) ***\n", suite_name, suite_index);
|
||||
}
|
||||
|
||||
static void clar_print_onabort(const char *msg, ...)
|
||||
{
|
||||
va_list argp;
|
||||
va_start(argp, msg);
|
||||
fprintf(stdout, "Bail out! ");
|
||||
vfprintf(stdout, msg, argp);
|
||||
va_end(argp);
|
||||
}
|
147
tools/clar/clar_sandbox.c
Normal file
147
tools/clar/clar_sandbox.c
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
char *mkdtemp(char *template);
|
||||
|
||||
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;
|
||||
}
|
||||
|
64
tools/clar/gen_clar.py
Executable file
64
tools/clar/gen_clar.py
Executable file
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from __future__ import with_statement
|
||||
import base64, zlib, re, sys
|
||||
|
||||
def compress_file(filename):
|
||||
with open(filename) as f:
|
||||
contents = f.read()
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
bin = zlib.compress(bytes(contents, 'utf-8'))
|
||||
return ('"%s" : r"""' % filename) + base64.b64encode(bin).decode('utf-8') + '"""'
|
||||
else:
|
||||
bin = zlib.compress(contents)
|
||||
return ('"%s" : r"""' % filename) + base64.b64encode(bin) + '"""'
|
||||
|
||||
def decompress_file(content):
|
||||
return zlib.decompress(base64.b64decode(content))
|
||||
|
||||
def build_table(filenames):
|
||||
table = "\n\nCLAR_FILES = {\n"
|
||||
table += ",\n".join(compress_file(f) for f in filenames)
|
||||
table += "\n}"
|
||||
return table
|
||||
|
||||
CLAR_FOOTER = """
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
"""
|
||||
|
||||
if __name__ == '__main__':
|
||||
clar_table = build_table([
|
||||
'clar.c',
|
||||
'clar_print_default.c',
|
||||
'clar_print_tap.c',
|
||||
'clar_sandbox.c',
|
||||
'clar_fixtures.c',
|
||||
'clar_mock.c',
|
||||
'clar_fs.c',
|
||||
'clar_categorize.c',
|
||||
'clar.h'
|
||||
])
|
||||
|
||||
with open('_clar.py') as f:
|
||||
clar_source = f.read()
|
||||
|
||||
with open('clar.py', 'w') as f:
|
||||
f.write(clar_source)
|
||||
f.write(clar_table)
|
||||
f.write(CLAR_FOOTER)
|
Loading…
Add table
Add a link
Reference in a new issue