yachtrock/src/selector.c

258 lines
7.8 KiB
C

#include <yachtrock/selector.h>
#include <fnmatch.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include "yrutil.h"
#include "testcase_internal.h"
struct parsed_glob_specifier {
char *suite_glob;
char *case_glob;
char storage[];
};
static bool match_glob(const char *glob, const char *input)
{
if ( glob == NULL ) {
return true;
} else if ( input == NULL ) {
return false;
}
return fnmatch(glob, input, 0) == 0;
}
static bool testname_glob_match(yr_test_case_t testcase, void *context)
{
struct parsed_glob_specifier *parsed = context;
return match_glob(parsed->suite_glob, testcase->suite->name) &&
match_glob(parsed->case_glob, testcase->name);
}
static struct parsed_glob_specifier *create_parsed_glob_specifier(char *suite, char *testcase)
{
size_t string_size = (suite ? strlen(suite) + 1 : 0) + (testcase ? strlen(testcase) + 1 : 0);
struct parsed_glob_specifier *result =
yr_malloc(sizeof(struct parsed_glob_specifier) + string_size);
char *insertion_point = result->storage;
if ( suite ) {
result->suite_glob = insertion_point;
strcpy(insertion_point, suite);
insertion_point += strlen(insertion_point) + 1;
} else {
result->suite_glob = NULL;
}
if ( testcase ) {
result->case_glob = insertion_point;
strcpy(insertion_point, testcase);
insertion_point += strlen(insertion_point) + 1;
} else {
result ->case_glob = NULL;
}
assert(insertion_point <= result->storage + string_size);
return result;
}
static void *testname_glob_context_copy(void *context)
{
struct parsed_glob_specifier *parsed = context;
struct parsed_glob_specifier *copy = create_parsed_glob_specifier(parsed->suite_glob,
parsed->case_glob);
return copy;
}
static void testname_glob_context_destroy(void *context)
{
free(context);
}
static const struct yr_selector_vtable testname_glob_selector_vtable = {
.match = testname_glob_match,
.copy_context = testname_glob_context_copy,
.destroy_context = testname_glob_context_destroy,
};
yr_selector_t yr_selector_create_from_glob(const char *sel_specifier)
{
/* Right now name globbing is the only thing that exists.
* split at the first unescaped ':'; before is suite glob, after is case glob.
* If no separator exists, treat as a case glob with no suite glob.
* If either glob is the empty string, treat it as not existing (so ":" always matches)
*/
char specifier_copy[strlen(sel_specifier) + 1];
strcpy(specifier_copy, sel_specifier);
char *suite=NULL, *testcase=NULL;
bool escaping = false;
for ( size_t i = 0; specifier_copy[i]; i++ ) {
bool skip = escaping;
escaping = false;
if ( skip ) {
// skip the escaped character
continue;
}
if ( specifier_copy[i] == ':' ) {
specifier_copy[i] = '\0';
suite = specifier_copy;
testcase = specifier_copy + i + 1;
break;
} else if ( specifier_copy[i] == '\\' ) {
escaping = true;
}
}
/* At this point if both glob pointers are still NULL, we did not find an unescaped ':'; set
* testcase glob
*/
if ( suite == NULL && testcase == NULL ) {
testcase = specifier_copy;
}
if ( suite && strlen(suite) == 0 ) {
suite = NULL;
}
if ( testcase && strlen(testcase) == 0 ) {
testcase = NULL;
}
struct parsed_glob_specifier *parsed = create_parsed_glob_specifier(suite, testcase);
yr_selector_t result = yr_selector_create(testname_glob_selector_vtable, parsed);
return result;
}
yr_selector_t yr_selector_create(struct yr_selector_vtable vtable, void *context)
{
yr_selector_t result = yr_malloc(sizeof(struct yr_selector));
result->vtable = vtable;
result->context = context;
return result;
}
void yr_selector_destroy(yr_selector_t selector)
{
selector->vtable.destroy_context(selector->context);
free(selector);
}
yr_selector_t yr_selector_copy(yr_selector_t in)
{
yr_selector_t selector = yr_malloc(sizeof(struct yr_selector));
selector->vtable = in->vtable;
selector->context = in->vtable.copy_context(in->context);
return selector;
}
bool yr_selector_match_testcase(yr_selector_t selector, yr_test_case_t testcase)
{
return selector->vtable.match(testcase, selector->context);
}
yr_selector_set_t yr_selector_set_create(size_t selector_count,
yr_selector_t *selectors)
{
yr_selector_set_t result = yr_malloc(sizeof(struct yr_selector_set) +
selector_count * sizeof(struct yr_selector));
result->selector_count = selector_count;
for ( size_t i = 0; i < selector_count; i++ ) {
result->selectors[i].vtable = selectors[i]->vtable;
result->selectors[i].context = selectors[i]->vtable.copy_context(selectors[i]->context);
}
return result;
}
void yr_selector_set_destroy(yr_selector_set_t set)
{
for ( size_t i = 0; i < set->selector_count; i++ ) {
set->selectors[i].vtable.destroy_context(set->selectors[i].context);
}
free(set);
}
bool yr_selector_set_match_testcase(yr_selector_set_t set,
yr_test_case_t testcase)
{
for ( size_t i = 0; i < set->selector_count; i++ ) {
if ( yr_selector_match_testcase(&(set->selectors[i]), testcase) ) {
return true;
}
}
return false;
}
static char *dup_string_bumping_string_area(const char *input, char **area_ptr)
{
strcpy(*area_ptr, input);
char *result = *area_ptr;
*area_ptr += strlen(*area_ptr) + 1;
return result;
}
yr_test_suite_t yr_test_suite_create_filtered(yr_test_suite_t suite, yr_selector_set_t filter)
{
yr_test_suite_t result = NULL;
yr_test_case_t *cases = yr_malloc(sizeof(yr_test_case_t) * suite->num_cases);
size_t num_filtered_cases = 0;
for ( size_t i = 0; i < suite->num_cases; i++ ) {
if ( yr_selector_set_match_testcase(filter, &suite->cases[i]) ) {
cases[num_filtered_cases++] = &suite->cases[i];
}
}
if ( num_filtered_cases != 0 ) {
char *names[num_filtered_cases];
for ( size_t i = 0; i < num_filtered_cases; i++ ) {
/* need to cast away const here; we promise not to munge them */
names[i] = (char *)cases[i]->name;
}
size_t allocation_size = test_suite_total_size_deconstructed(suite->name, num_filtered_cases,
names);
result = yr_malloc(allocation_size);
/* Copy everything, strings always last
* (Need to set num cases before calculating string storage location)
*/
*result = *suite;
result->num_cases = num_filtered_cases;
char *string_insertion_point = test_suite_string_storage_location(result);
result->name = dup_string_bumping_string_area(suite->name, &string_insertion_point);
for ( size_t i = 0; i < num_filtered_cases; i++ ) {
result->cases[i] = *cases[i];
result->cases[i].suite = result;
result->cases[i].name = dup_string_bumping_string_area(cases[i]->name, &string_insertion_point);
}
assert(string_insertion_point >= (char*)result);
assert((size_t)(string_insertion_point - ((char *)result)) <= allocation_size);
}
free(cases);
return result;
}
yr_test_suite_collection_t
yr_test_suite_collection_create_filtered(yr_test_suite_collection_t collection,
yr_selector_set_t filter)
{
yr_test_suite_collection_t result = NULL;
yr_test_suite_t *suites = yr_malloc(sizeof(yr_test_suite_t) * collection->num_suites);
size_t num_suites = 0;
for ( size_t i = 0; i < collection->num_suites; i++ ) {
yr_test_suite_t filtered = yr_test_suite_create_filtered(collection->suites[i], filter);
if ( filtered ) {
suites[num_suites++] = filtered;
}
}
if ( num_suites > 0 ) {
result = yr_test_suite_collection_create_from_suites(num_suites, suites);
}
for ( size_t i = 0; i < num_suites; i++ ) {
free(suites[i]);
}
free(suites);
return result;
}