Initial
This commit is contained in:
@@ -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"
|
||||||
|
)
|
||||||
+19
@@ -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
|
||||||
+169
@@ -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
|
||||||
+159
@@ -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
|
||||||
Reference in New Issue
Block a user