commit
50a26664e3
@ -0,0 +1,7 @@ |
|||||||
|
cmake-build-debug/ |
||||||
|
CMakeCache.txt |
||||||
|
*.o |
||||||
|
*.out |
||||||
|
*~ |
||||||
|
.idea/ |
||||||
|
|
@ -0,0 +1,16 @@ |
|||||||
|
# Unit tests project |
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.10) |
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11) |
||||||
|
SET(CMAKE_BUILD_TYPE Debug) |
||||||
|
|
||||||
|
project(test) |
||||||
|
|
||||||
|
set(SOURCES prefix_match.c test/main.c test/test_framework.c) |
||||||
|
|
||||||
|
add_executable(test ${SOURCES}) |
||||||
|
|
||||||
|
target_include_directories(test |
||||||
|
PRIVATE "." "test" |
||||||
|
) |
@ -0,0 +1,19 @@ |
|||||||
|
Copyright (c) 2020 Ondřej Hruška |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
@ -0,0 +1,9 @@ |
|||||||
|
.PHONY: build run |
||||||
|
|
||||||
|
build: a.out |
||||||
|
|
||||||
|
a.out: |
||||||
|
cc prefix_match.c test/main.c test/test_framework.c -I. -Itest -Wall -Wextra -pedantic -Og
|
||||||
|
|
||||||
|
run: a.out |
||||||
|
./a.out
|
@ -0,0 +1,169 @@ |
|||||||
|
#include <stddef.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include <string.h> |
||||||
|
#include "prefix_match.h" |
||||||
|
|
||||||
|
int prefix_match(const char *value, const char **options, int flags) { |
||||||
|
flags &= ~PREFIXMATCH_MULTI_PARTIAL; // this doesn't make sense here
|
||||||
|
bool case_sensitive = PREFIXMATCH_CASE_SENSITIVE == (flags & PREFIXMATCH_CASE_SENSITIVE); |
||||||
|
bool can_abbrev = 0 == (flags & PREFIXMATCH_NOABBREV); |
||||||
|
|
||||||
|
int (*cmpfn) (const char *, const char *) = case_sensitive ? strcmp : strcasecmp; |
||||||
|
int (*ncmpfn) (const char *, const char *, size_t) = case_sensitive ? strncmp : strncasecmp; |
||||||
|
|
||||||
|
if (!value || !options) return -1; |
||||||
|
size_t input_len = strlen(value); |
||||||
|
const char *option = NULL; |
||||||
|
int counter = 0; |
||||||
|
int result = -1; |
||||||
|
while (NULL != (option = options[counter])) { |
||||||
|
if (cmpfn(option, value) == 0) { |
||||||
|
return counter; // full exact match
|
||||||
|
} else { |
||||||
|
// Test for partial match
|
||||||
|
if (can_abbrev && ncmpfn(value, option, input_len) == 0) { |
||||||
|
if (result == -1) { |
||||||
|
result = counter; // first partial match
|
||||||
|
} else { |
||||||
|
// ambiguous match
|
||||||
|
return -1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
counter++; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
size_t pm_word_len(const char *word, const char *delims) { |
||||||
|
char d = 0; |
||||||
|
const char *dp = delims; |
||||||
|
size_t word_len = 0; |
||||||
|
while ('\0' != (d = *dp++)) { |
||||||
|
char *end = strchr(word, d); |
||||||
|
if (NULL == end) continue; |
||||||
|
size_t len = end - word; |
||||||
|
if (!word_len || len < word_len) { |
||||||
|
word_len = len; |
||||||
|
} |
||||||
|
} |
||||||
|
if (!word_len) { |
||||||
|
word_len = strlen(word); |
||||||
|
} |
||||||
|
return word_len; |
||||||
|
} |
||||||
|
|
||||||
|
size_t pm_count_words(const char *sentence, const char *delims) { |
||||||
|
char c; |
||||||
|
size_t n = 0; |
||||||
|
bool in_word = false; |
||||||
|
if (!sentence || !delims) return 0; |
||||||
|
while (0 != (c = *sentence++)) { |
||||||
|
bool is_delim = NULL != strchr(delims, c); |
||||||
|
if (is_delim && in_word) { |
||||||
|
in_word = false; |
||||||
|
} else if (!in_word && !is_delim) { |
||||||
|
n++; |
||||||
|
in_word = true; |
||||||
|
} |
||||||
|
} |
||||||
|
return n; |
||||||
|
} |
||||||
|
|
||||||
|
int pm_multipart_test(const char *a, const char* b, const char *delims, int flags) { |
||||||
|
bool case_sensitive = PREFIXMATCH_CASE_SENSITIVE == (flags & PREFIXMATCH_CASE_SENSITIVE); |
||||||
|
bool can_abbrev = 0 == (flags & PREFIXMATCH_NOABBREV); |
||||||
|
|
||||||
|
int (*ncmpfn) (const char *, const char *, size_t) = case_sensitive ? strncmp : strncasecmp; |
||||||
|
// lazy shortcut first...
|
||||||
|
if ((case_sensitive && 0 == strcmp(a, b)) || (!case_sensitive && 0 == strcasecmp(a, b))) { |
||||||
|
return 1; // full match
|
||||||
|
} |
||||||
|
|
||||||
|
const char *word_a = a; |
||||||
|
const char *word_b = b; |
||||||
|
size_t word_a_len = 0; |
||||||
|
size_t word_b_len = 0; |
||||||
|
while (1) { |
||||||
|
word_a += word_a_len; |
||||||
|
word_b += word_b_len; |
||||||
|
|
||||||
|
// advance past leading delims, if any
|
||||||
|
while (*word_a != '\0' && NULL != strchr(delims, *word_a)) word_a++; |
||||||
|
while (*word_b != '\0' && NULL != strchr(delims, *word_b)) word_b++; |
||||||
|
|
||||||
|
// test for terminator
|
||||||
|
if (*word_a == '\0' && *word_b == '\0') { |
||||||
|
// both ended at the same number of words
|
||||||
|
return 1; // full match
|
||||||
|
} |
||||||
|
|
||||||
|
if (*word_a == '\0' || *word_b == '\0') { |
||||||
|
// sentences ended at different length
|
||||||
|
if (0 != (flags & PREFIXMATCH_MULTI_PARTIAL) && *word_b != '\0') { // word prefix match (a is a prefix of b)
|
||||||
|
return 2; // partial word match
|
||||||
|
} else { |
||||||
|
return 0; // no match
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// find end of the words
|
||||||
|
word_a_len = pm_word_len(word_a, delims); |
||||||
|
word_b_len = pm_word_len(word_b, delims); |
||||||
|
|
||||||
|
if (word_a_len > word_b_len || (!can_abbrev && word_a_len != word_b_len)) { |
||||||
|
return 0; // no match
|
||||||
|
} |
||||||
|
|
||||||
|
int cmp = ncmpfn(word_a, word_b, word_a_len); |
||||||
|
if (0 != cmp) { // words differ
|
||||||
|
return 0; // no match
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int prefix_multipart_match(const char *value, const char **options, const char* delims, int flags) { |
||||||
|
bool multi_partial = 0 != (flags & PREFIXMATCH_MULTI_PARTIAL); |
||||||
|
flags &= ~PREFIXMATCH_MULTI_PARTIAL; // turn it off for passing the to test fn
|
||||||
|
bool can_abbrev = 0 == (flags & PREFIXMATCH_NOABBREV); |
||||||
|
|
||||||
|
if (!value || !options) return -1; |
||||||
|
const char *option = NULL; |
||||||
|
int counter = 0; |
||||||
|
int result = -1; |
||||||
|
int result_partial = -1; |
||||||
|
int result_partial_nwords = 0; |
||||||
|
while (NULL != (option = options[counter])) { |
||||||
|
if (pm_multipart_test(value, option, delims, flags | PREFIXMATCH_NOABBREV)) { |
||||||
|
return counter; // full exact match
|
||||||
|
} else if (can_abbrev) { |
||||||
|
// Test for partial match
|
||||||
|
if (pm_multipart_test(value, option, delims, flags)) { |
||||||
|
if (result == -1) { |
||||||
|
result = counter; // first partial match in all words
|
||||||
|
} else { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
} else if (multi_partial && 2 == pm_multipart_test(value, option, delims, flags | PREFIXMATCH_MULTI_PARTIAL)) { |
||||||
|
int nwords = pm_count_words(option, delims); |
||||||
|
if (result_partial == -1 || result_partial_nwords < nwords) { |
||||||
|
result_partial = counter; // first partial match
|
||||||
|
result_partial_nwords = nwords; |
||||||
|
} else { |
||||||
|
result_partial = -2; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
counter++; |
||||||
|
} |
||||||
|
|
||||||
|
if (result != -1) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
if (result_partial >= 0) { |
||||||
|
return result_partial; |
||||||
|
} |
||||||
|
|
||||||
|
return -1; |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
/**
|
||||||
|
* Prefix Match |
||||||
|
* |
||||||
|
* Match input value to a list of options, allowing non-ambiguous abbreviation and partial matching. |
||||||
|
* This library was designed for command recognition in interactive consoles and command interfaces. |
||||||
|
*
|
||||||
|
* Created on 2020/06/09 by Ondřej Hruška |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef _PREFIX_MATCH_H |
||||||
|
#define _PREFIX_MATCH_H |
||||||
|
|
||||||
|
#include <stdbool.h> |
||||||
|
|
||||||
|
/** Use case-sensitive matching */ |
||||||
|
#define PREFIXMATCH_CASE_SENSITIVE 1 |
||||||
|
/** Forbid abbreviations */ |
||||||
|
#define PREFIXMATCH_NOABBREV 2 |
||||||
|
/** Allow matching fewer words, if unambiguous */ |
||||||
|
#define PREFIXMATCH_MULTI_PARTIAL 4 |
||||||
|
|
||||||
|
/**
|
||||||
|
* Recognize (optionally abbreviated) input |
||||||
|
* |
||||||
|
* @param[in] value - tested value |
||||||
|
* @param[in] options - options to match against |
||||||
|
* @param[in] flags - matching options (bitmask) - accepts PREFIXMATCH_CASE_SENSITIVE and PREFIXMATCH_NOABBREV |
||||||
|
* @return index of the matched option, -1 on mismatch or ambiguous match |
||||||
|
*/ |
||||||
|
int prefix_match(const char *value, const char **options, int flags); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Recognize input consisting of one or more (optionally abbreviated) words |
||||||
|
* |
||||||
|
* @param[in] value - tested value |
||||||
|
* @param[in] options - options to match against, multi-word options separated by the listed delimiters |
||||||
|
* @param[in] delims - string with a list of possible delimiters (like for strtok) |
||||||
|
* @param[in] flags - matching options (bitmask) - accepts all options |
||||||
|
* @return index of the matched option, -1 on mismatch or ambiguous match |
||||||
|
*/ |
||||||
|
int prefix_multipart_match(const char *value, const char **options, const char* delims, int flags); |
||||||
|
|
||||||
|
// useful internal functions exported for possible re-use
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if two word sentences match, with individual words optionally allowed to be abbreviated. |
||||||
|
* |
||||||
|
* @internal |
||||||
|
* @param[in] a - tested (optionally abbreviated) sentence |
||||||
|
* @param[in] b - full sentence |
||||||
|
* @param[in] delims - list of possible delimiters, same may be used for both sentences |
||||||
|
* @param[in] flags - matching options (bitmask) - accepts all options |
||||||
|
* @return 1-match; 0-no match; 2-partial (some words) match, if the PREFIXMATCH_MULTI_PARTIAL flag is set |
||||||
|
*/ |
||||||
|
int pm_multipart_test(const char *a, const char* b, const char *delims, int flags); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Count words in a "sentence", delimited by any of the given set of delimiters. |
||||||
|
* |
||||||
|
* @internal |
||||||
|
* @param[in] sentence - one or multi-word string |
||||||
|
* @param[in] delims - delimiters accepted |
||||||
|
* @return number of words |
||||||
|
*/ |
||||||
|
size_t pm_count_words(const char *sentence, const char *delims); |
||||||
|
|
||||||
|
/**
|
||||||
|
* Measure word length |
||||||
|
* |
||||||
|
* @internal |
||||||
|
* @param[in] word - start of a word that ends with either one of the delimiters, or a null byte. |
||||||
|
* @param[in] delims - delimiters accepted |
||||||
|
* @return word length |
||||||
|
*/ |
||||||
|
size_t pm_word_len(const char *word, const char *delims); |
||||||
|
|
||||||
|
#endif //_PREFIX_MATCH_H
|
@ -0,0 +1,159 @@ |
|||||||
|
#include <stdio.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#include "prefix_match.h" |
||||||
|
#include "test_framework.h" |
||||||
|
|
||||||
|
// --- test cases ---
|
||||||
|
|
||||||
|
bool test_prefix_match() { |
||||||
|
const char *options[] = { |
||||||
|
"ahoj",//0
|
||||||
|
"ahojahoj",//1
|
||||||
|
"ble",//2
|
||||||
|
"citron",//3
|
||||||
|
"foo",//4
|
||||||
|
"foo bar",//5
|
||||||
|
"foo baz",//6
|
||||||
|
"foo bcz",//7
|
||||||
|
"eps",//8
|
||||||
|
"eps set",//9
|
||||||
|
"eps set config",//10
|
||||||
|
"eps get config",//11
|
||||||
|
"eps hk2",//12
|
||||||
|
"eps hk2 vi",//13
|
||||||
|
"eps hk2 out",//14
|
||||||
|
"delete from table users",//15
|
||||||
|
"delete,from;,,,,table sessions",//16
|
||||||
|
NULL |
||||||
|
}; |
||||||
|
|
||||||
|
check_eq(prefix_match("", options, 0), -1); |
||||||
|
check_eq(prefix_match("b", options, 0), 2); |
||||||
|
check_eq(prefix_match("c", options, 0), 3); |
||||||
|
check_eq(prefix_match("citron", options, 0), 3); |
||||||
|
check_eq(prefix_match("foo", options, 0), 4); |
||||||
|
check_eq(prefix_match("foo ", options, 0), -1); |
||||||
|
check_eq(prefix_match("foo bc", options, 0), 7); |
||||||
|
check_eq(prefix_match("foo bcz", options, 0), 7); |
||||||
|
|
||||||
|
check_eq(prefix_match("fOO bcz", options, PREFIXMATCH_CASE_SENSITIVE), -1); // CS
|
||||||
|
check_eq(prefix_match("fOO bcz", options, 0), 7); // CI
|
||||||
|
|
||||||
|
check_eq(prefix_match("ahoj", options, 0), 0); |
||||||
|
check_eq(prefix_match("ahoja", options, 0), 1); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool test_multipart_test() { |
||||||
|
check_eq(pm_multipart_test("ahoj", "ahoj", " ", 0), 1); |
||||||
|
check_eq(pm_multipart_test("ah", "ahoj", " ", 0), 1); |
||||||
|
check_eq(pm_multipart_test("xxxx", "ahoj", " ", 0), 0); |
||||||
|
|
||||||
|
check_eq(pm_multipart_test("", "ahoj", " ", 0), 0); |
||||||
|
check_eq(pm_multipart_test("", "", " ", 0), 1); |
||||||
|
|
||||||
|
check_eq(pm_multipart_test("multi part", "multi part", " ", 0), 1); |
||||||
|
check_eq(pm_multipart_test("multi", "multi part", " ", 0), 0); |
||||||
|
check_eq(pm_multipart_test("multi part", "multi", " ", 0), 0); |
||||||
|
check_eq(pm_multipart_test("multi part", "multi dog", " ", 0), 0); |
||||||
|
check_eq(pm_multipart_test("multi part", "multi part", " ", 0), 1); |
||||||
|
check_eq(pm_multipart_test(" multi part ", "multi part", " ", 0), 1); |
||||||
|
check_eq(pm_multipart_test("multi ,; part", "multi,part", ",; ", 0), 1); |
||||||
|
|
||||||
|
check_eq(pm_multipart_test("m p", "multi part", " ", 0), 1); |
||||||
|
check_eq(pm_multipart_test("mu pa", "multi part", " ", 0), 1); |
||||||
|
check_eq(pm_multipart_test("mu pp", "multi part", " ", 0), 0); |
||||||
|
check_eq(pm_multipart_test("m ", "multi part", " ", PREFIXMATCH_MULTI_PARTIAL), 2); |
||||||
|
|
||||||
|
check_eq(pm_multipart_test("v l s h", "very long sentence here", " ", 0), 1); |
||||||
|
check_eq(pm_multipart_test("v l s h", "very long sentence here too", " ", 0), 0); |
||||||
|
check_eq(pm_multipart_test("v l", "very long sentence here too", " ", 0), 0); |
||||||
|
check_eq(pm_multipart_test("v l", "very long sentence here too", " ", PREFIXMATCH_MULTI_PARTIAL), 2); |
||||||
|
|
||||||
|
check_eq(pm_multipart_test("v l s h", "very long sentence here", " ", PREFIXMATCH_MULTI_PARTIAL | PREFIXMATCH_CASE_SENSITIVE), 1); |
||||||
|
check_eq(pm_multipart_test("v l s h", "very long sentence here too", " ", PREFIXMATCH_MULTI_PARTIAL), 2); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool test_multipart_match() { |
||||||
|
const char *options[] = { |
||||||
|
"ahoj",//0
|
||||||
|
"foo",//1
|
||||||
|
"foo bar",//2
|
||||||
|
"foo baz",//3
|
||||||
|
"foo bcz",//4
|
||||||
|
"eps", // 5
|
||||||
|
"eps set",//6
|
||||||
|
"eps set config",//7
|
||||||
|
"eps get config",//8
|
||||||
|
"eps hk2",//9
|
||||||
|
"eps hk2 vi", //10
|
||||||
|
"eps hk2 out",//11
|
||||||
|
"delete from table users",//12
|
||||||
|
"delete,from;,,,,table sessions",//13
|
||||||
|
NULL |
||||||
|
}; |
||||||
|
|
||||||
|
check_eq(prefix_multipart_match("", options, " ", 0), -1); |
||||||
|
check_eq(prefix_multipart_match("x", options, " ", 0), -1); |
||||||
|
|
||||||
|
check_eq(prefix_multipart_match("ahoj", options, " ", 0), 0); |
||||||
|
check_eq(prefix_multipart_match("a", options, " ", 0), 0); |
||||||
|
check_eq(prefix_multipart_match("ah", options, " ", 0), 0); |
||||||
|
|
||||||
|
check_eq(prefix_multipart_match("foo", options, " ", 0), 1); |
||||||
|
check_eq(prefix_multipart_match("f", options, " ", 0), 1); |
||||||
|
|
||||||
|
check_eq(prefix_multipart_match("f b", options, " ", 0), -1); |
||||||
|
check_eq(prefix_multipart_match("f b", options, " ", PREFIXMATCH_MULTI_PARTIAL), -1); |
||||||
|
check_eq(prefix_multipart_match("f bc", options, " ", 0), 4); |
||||||
|
|
||||||
|
check_eq(prefix_multipart_match("e", options, " ", 0), 5); |
||||||
|
check_eq(prefix_multipart_match("eps", options, " ", 0), 5); |
||||||
|
check_eq(prefix_multipart_match("eps banana", options, " ", 0), -1); |
||||||
|
check_eq(prefix_multipart_match("eps banana", options, " ", PREFIXMATCH_MULTI_PARTIAL), -1); |
||||||
|
|
||||||
|
check_eq(prefix_multipart_match("eps set", options, " ", 0), 6); |
||||||
|
check_eq(prefix_multipart_match("e s", options, " ", 0), 6); |
||||||
|
check_eq(prefix_multipart_match("e set", options, " ", 0), 6); |
||||||
|
check_eq(prefix_multipart_match("eps s", options, " ", 0), 6); |
||||||
|
check_eq(prefix_multipart_match("eps s", options, " ", 0), 6); |
||||||
|
|
||||||
|
check_eq(prefix_multipart_match("e s c", options, " ", 0), 7); |
||||||
|
check_eq(prefix_multipart_match("e g c", options, " ", 0), 8); |
||||||
|
|
||||||
|
// there is only one get command
|
||||||
|
|
||||||
|
check_eq(prefix_multipart_match("eps get", options, " ", 0), -1); |
||||||
|
check_eq(prefix_multipart_match("eps get", options, " ", PREFIXMATCH_MULTI_PARTIAL), 8); |
||||||
|
|
||||||
|
check_eq(prefix_multipart_match("e g", options, " ", 0), -1); |
||||||
|
check_eq(prefix_multipart_match("e g", options, " ", PREFIXMATCH_MULTI_PARTIAL), 8); |
||||||
|
|
||||||
|
check_eq(prefix_multipart_match("epx get", options, " ", 0), -1); |
||||||
|
check_eq(prefix_multipart_match("epx get", options, " ", PREFIXMATCH_MULTI_PARTIAL), -1); |
||||||
|
|
||||||
|
check_eq(prefix_multipart_match("d f t u", options, " ", 0), 12); |
||||||
|
check_eq(prefix_multipart_match("d f t s", options, ",; ", 0), 13); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// --- test launcher ---
|
||||||
|
|
||||||
|
static struct Test tests[] = { |
||||||
|
{"prefix_match", test_prefix_match}, |
||||||
|
{"prefix_multipart_test", test_multipart_test}, |
||||||
|
{"prefix_multipart_match", test_multipart_match}, |
||||||
|
{NULL, NULL} |
||||||
|
}; |
||||||
|
|
||||||
|
int main() { |
||||||
|
run_tests(tests, "main"); |
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
#include <stdio.h> |
||||||
|
#include "test_framework.h" |
||||||
|
|
||||||
|
void run_tests(struct Test *tests, const char *module_name) { |
||||||
|
printf("Running tests module \"%s\"...\n\n", module_name); |
||||||
|
|
||||||
|
struct Test *t = &tests[0]; |
||||||
|
int passed = 0, failed = 0; |
||||||
|
do { |
||||||
|
printf("Running test \"%s\"...\n", t->name); |
||||||
|
|
||||||
|
if (t->func()) { |
||||||
|
printf("Test %s \x1b[32mPASSED\x1b[m\n", t->name); |
||||||
|
passed++; |
||||||
|
} else { |
||||||
|
printf("Test %s \x1b[31mFAILED\x1b[m\n", t->name); |
||||||
|
failed++; |
||||||
|
} |
||||||
|
} while((++t)->name != NULL); |
||||||
|
|
||||||
|
printf("\nTests module \"%s\" done. %d passed, %d failed.\n\n", module_name, passed, failed); |
||||||
|
} |
@ -0,0 +1,100 @@ |
|||||||
|
/**
|
||||||
|
* shared defines and utils for tests |
||||||
|
* |
||||||
|
* Created on 2020/05/12. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef TESTS_TEST_FRAMEWORK_H |
||||||
|
#define TESTS_TEST_FRAMEWORK_H |
||||||
|
|
||||||
|
#include <stdbool.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <stdint.h> |
||||||
|
#include <math.h> |
||||||
|
|
||||||
|
#define XSTR(s) STR(s) |
||||||
|
#define STR(s) #s |
||||||
|
|
||||||
|
typedef bool(*testfn_t)(void); |
||||||
|
|
||||||
|
struct Test { |
||||||
|
const char *name; |
||||||
|
testfn_t func; |
||||||
|
}; |
||||||
|
|
||||||
|
void run_tests(struct Test *tests, const char *module_name); |
||||||
|
|
||||||
|
// _good must be an array, not a pointer
|
||||||
|
#define check_array(_var, _good) \ |
||||||
|
do { \
|
||||||
|
for (int i = 0; i < (int)sizeof(_good); i++) { \
|
||||||
|
if (_var[i] != _good[i]) { \
|
||||||
|
printf("%s:%d \x1b[31mAssert failed\x1b[m: arrays differ\n\x1b[37;1m n# exp act\x1b[m\n", __func__, __LINE__); \
|
||||||
|
for (i = 0; i < (int)sizeof(_good); i++) { \
|
||||||
|
if (_var[i] == _good[i]) { \
|
||||||
|
printf(" %2d - 0x%02x -\n", i, _var[i]); \
|
||||||
|
} else { \
|
||||||
|
printf(" %2d \x1b[32m0x%02x \x1b[31m0x%02x\x1b[m\n", i, _good[i], _var[i]); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} while(0) |
||||||
|
|
||||||
|
#define check(_cond) \ |
||||||
|
do { \
|
||||||
|
if (!(_cond)) { \
|
||||||
|
printf("%s:%d \x1b[31mAssert failed\x1b[m: %s\n", __func__, __LINE__, STR(_cond)); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while(0) |
||||||
|
|
||||||
|
#define check_eq(_var, _good) \ |
||||||
|
do { \
|
||||||
|
if ((_var) != (_good)) { \
|
||||||
|
printf("%s:%d \x1b[31mAssert failed\x1b[m: %s == %s\n", __func__, __LINE__, STR(_var), STR(_good)); \
|
||||||
|
printf(" \x1b[31m%d (0x%02x)\x1b[m != \x1b[32m%d (0x%02x)\x1b[m\n", _var, _var, _good, _good); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while(0) |
||||||
|
|
||||||
|
#define check_eq_ul(_var, _good) \ |
||||||
|
do { \
|
||||||
|
if ((_var) != (_good)) { \
|
||||||
|
printf("%s:%d \x1b[31mAssert failed\x1b[m: %s == %s\n", __func__, __LINE__, STR(_var), STR(_good)); \
|
||||||
|
printf(" \x1b[31m%ld (0x%02lx)\x1b[m != \x1b[32m%ld (0x%02lx)\x1b[m\n", _var, _var, _good, _good); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while(0) |
||||||
|
|
||||||
|
#define check_eq_l(_var, _good) \ |
||||||
|
do { \
|
||||||
|
if ((_var) != (_good)) { \
|
||||||
|
printf("%s:%d \x1b[31mAssert failed\x1b[m: %s == %s\n", __func__, __LINE__, STR(_var), STR(_good)); \
|
||||||
|
printf(" \x1b[31m%ld\x1b[m != \x1b[32m%ld\x1b[m\n", _var, _good); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while(0) |
||||||
|
|
||||||
|
#define check_eq_f(_var, _good) \ |
||||||
|
do { \
|
||||||
|
if ((_var) != (_good)) { \
|
||||||
|
printf("%s:%d \x1b[31mAssert failed\x1b[m: %s == %s\n", __func__, __LINE__, STR(_var), STR(_good)); \
|
||||||
|
printf(" \x1b[31m%f\x1b[m != \x1b[32m%f\x1b[m\n", _var, _good); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while(0) |
||||||
|
|
||||||
|
#define check_eq_f_safe(_var, _good, thr) \ |
||||||
|
do { \
|
||||||
|
if (fabs((double)((_var) - (_good))) > thr) { \
|
||||||
|
printf("%s:%d \x1b[31mAssert failed\x1b[m: %s == %s\n", __func__, __LINE__, STR(_var), STR(_good)); \
|
||||||
|
printf(" \x1b[31m%f\x1b[m != \x1b[32m%f\x1b[m\n", _var, _good); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while(0) |
||||||
|
|
||||||
|
|
||||||
|
#endif //TESTS_TEST_FRAMEWORK_H
|
Loading…
Reference in new issue