diff --git a/src/check.c b/src/check.c index d0bb7ee3..a52099fc 100644 --- a/src/check.c +++ b/src/check.c @@ -103,6 +103,7 @@ static void suite_free(Suite * s) free(s); } + TCase *tcase_create(const char *name) { char *env; @@ -149,7 +150,19 @@ TCase *tcase_create(const char *name) tc->ch_sflst = check_list_create(); tc->unch_tflst = check_list_create(); tc->ch_tflst = check_list_create(); + tc->tags = NULL; + + return tc; +} + +TCase *tcase_create_tagged(const char *name, const char *tags) +{ + TCase *tc; + tc = tcase_create(name); + if ((tc != NULL) && (tags != NULL)) { + tc->tags = strdup(tags); + } return tc; } @@ -166,7 +179,10 @@ static void tcase_free(TCase * tc) check_list_free(tc->ch_sflst); check_list_free(tc->unch_tflst); check_list_free(tc->ch_tflst); - + if (tc->tags) { + free(tc->tags); + tc->tags = NULL; + } free(tc); } diff --git a/src/check.h.in b/src/check.h.in index d096b2ad..9b0f5429 100644 --- a/src/check.h.in +++ b/src/check.h.in @@ -169,6 +169,23 @@ CK_DLL_EXP void CK_EXPORT suite_add_tcase(Suite * s, TCase * tc); * */ CK_DLL_EXP TCase *CK_EXPORT tcase_create(const char *name); +/** + * Create a test case associated with certain tags. + * + * Once created, tests can be added with the tcase_add_test() + * function, and the test case assigned to a suite with the + * suite_add_tcase() function. + * + * @param name name of the test case + * @param tags string containing arbitrary tags separated by spaces + * + * @return test case containing no tests + * + * @since 0.11.0 + * */ +CK_DLL_EXP TCase *CK_EXPORT tcase_create_tagged(const char *name, + const char *tags); + /** * Add a test function to a test case * @@ -980,6 +997,7 @@ CK_DLL_EXP void CK_EXPORT srunner_run_all(SRunner * sr, */ CK_DLL_EXP void CK_EXPORT srunner_run(SRunner * sr, const char *sname, const char *tcname, + const char *inc_tags, const char *exc_tags, enum print_output print_mode); diff --git a/src/check_impl.h b/src/check_impl.h index 15b14f17..4866455d 100644 --- a/src/check_impl.h +++ b/src/check_impl.h @@ -65,6 +65,7 @@ struct TCase List *unch_tflst; List *ch_sflst; List *ch_tflst; + char *tags; }; typedef struct TestStats diff --git a/src/check_run.c b/src/check_run.c index fe09e08f..b476eb8d 100644 --- a/src/check_run.c +++ b/src/check_run.c @@ -60,6 +60,7 @@ static void srunner_run_init(SRunner * sr, enum print_output print_mode); static void srunner_run_end(SRunner * sr, enum print_output print_mode); static void srunner_iterate_suites(SRunner * sr, const char *sname, const char *tcname, + const char *inc_tags, const char *exc_tags, enum print_output print_mode); static void srunner_iterate_tcase_tfuns(SRunner * sr, TCase * tc); static void srunner_add_failure(SRunner * sr, TestResult * tf); @@ -158,8 +159,50 @@ static void srunner_run_end(SRunner * sr, set_fork_status(CK_FORK); } +/* + * Helper func to compare two lists of tags and return true if there is a + * common tag. + */ +static unsigned int matching_tag(const char *tags1_orig, const char *tags2_orig) +{ + + char *saveptr1; + char *saveptr2; + char *tags1; + char *tag1; + + + if ((tags1_orig == NULL) || (tags2_orig == NULL)) + return 0; + + tags1 = strdup(tags1_orig); + tag1 = strtok_r(tags1, " ", &saveptr1); + while (tag1) { + + char *tags2, *tag2; + + tags2 = strdup(tags2_orig); + tag2 = strtok_r(tags2, " ", &saveptr2); + while (tag2) { + if (strcmp(tag1, tag2) == 0) { + + free(tags2); + free(tags1); + return 1; + } + + tag2 = strtok_r(NULL, " ", &saveptr2); + } + free(tags2); + tag1 = strtok_r(NULL, " ", &saveptr1); + } + free(tags1); + return 0; +} + static void srunner_iterate_suites(SRunner * sr, const char *sname, const char *tcname, + const char *inc_tags, const char *exc_tags, enum print_output CK_ATTRIBUTE_UNUSED print_mode) { @@ -191,6 +234,14 @@ static void srunner_iterate_suites(SRunner * sr, { continue; } + if (inc_tags != NULL) { + if (!matching_tag(inc_tags, tc->tags)) + continue; + } + if (exc_tags != NULL) { + if (matching_tag(inc_tags, tc->tags)) + continue; + } srunner_run_tcase(sr, tc); } @@ -738,10 +789,13 @@ void srunner_run_all(SRunner * sr, enum print_output print_mode) { srunner_run(sr, NULL, /* All test suites. */ NULL, /* All test cases. */ + NULL, /* Include all tags */ + NULL, /* Exclude no tags */ print_mode); } void srunner_run(SRunner * sr, const char *sname, const char *tcname, + const char *inc_tags, const char *exc_tags, enum print_output print_mode) { #if defined(HAVE_SIGACTION) && defined(HAVE_FORK) @@ -756,7 +810,11 @@ void srunner_run(SRunner * sr, const char *sname, const char *tcname, if(!tcname) tcname = getenv("CK_RUN_CASE"); if(!sname) - sname = getenv("CK_RUN_SUITE"); + sname = getenv("CK_RUN_SUITE"); + if(!inc_tags) + inc_tags = getenv("CK_INC_TAGS"); + if(!exc_tags) + exc_tags = getenv("CK_EXC_TAGS"); if(sr == NULL) return; @@ -779,7 +837,7 @@ void srunner_run(SRunner * sr, const char *sname, const char *tcname, sigaction(SIGTERM, &sigterm_new_action, &sigterm_old_action); #endif /* HAVE_SIGACTION && HAVE_FORK */ srunner_run_init(sr, print_mode); - srunner_iterate_suites(sr, sname, tcname, print_mode); + srunner_iterate_suites(sr, sname, tcname, inc_tags, exc_tags, print_mode); srunner_run_end(sr, print_mode); #if defined(HAVE_SIGACTION) && defined(HAVE_FORK) sigaction(SIGALRM, &sigalarm_old_action, NULL); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 02f406ec..6e80679c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -53,6 +53,7 @@ set(CHECK_CHECK_SOURCES check_check_pack.c check_check_selective.c check_check_sub.c + check_check_tags.c check_list.c) set(CHECK_CHECK_HEADERS check_check.h) add_executable(check_check ${CHECK_CHECK_HEADERS} ${CHECK_CHECK_SOURCES}) diff --git a/tests/Makefile.am b/tests/Makefile.am index 4995a74e..dd2c18c7 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -41,6 +41,7 @@ check_check_export_SOURCES = \ check_check_master.c \ check_check_log.c \ check_check_fork.c \ + check_check_tags.c \ check_check_export_main.c check_check_export_LDADD = $(top_builddir)/src/libcheck.la $(top_builddir)/lib/libcompat.la @@ -58,6 +59,7 @@ check_check_SOURCES = \ check_check_pack.c \ check_check_exit.c \ check_check_selective.c \ + check_check_tags.c \ check_check_main.c check_check_LDADD = $(top_builddir)/src/libcheckinternal.la $(top_builddir)/lib/libcompat.la @@ -67,6 +69,7 @@ check_mem_leaks_SOURCES = \ check_check_fork.c \ check_check_exit.c \ check_check_selective.c \ + check_check_tags.c \ check_check_sub.c \ check_check_master.c check_mem_leaks_LDADD = $(top_builddir)/src/libcheck.la $(top_builddir)/lib/libcompat.la diff --git a/tests/check_check.h b/tests/check_check.h index ba174926..32c23d10 100644 --- a/tests/check_check.h +++ b/tests/check_check.h @@ -55,6 +55,7 @@ Suite *make_fixture_suite(void); Suite *make_pack_suite(void); Suite *make_exit_suite(void); Suite *make_selective_suite(void); +Suite *make_tag_suite(void); extern int master_tests_lineno[]; void init_master_tests_lineno(int num_master_tests); diff --git a/tests/check_check_main.c b/tests/check_check_main.c index 758aaf28..33914931 100644 --- a/tests/check_check_main.c +++ b/tests/check_check_main.c @@ -43,6 +43,7 @@ int main (void) srunner_add_suite(sr, make_fork_suite()); srunner_add_suite(sr, make_fixture_suite()); srunner_add_suite(sr, make_pack_suite()); + srunner_add_suite(sr, make_tag_suite()); #if defined(HAVE_FORK) && HAVE_FORK==1 srunner_add_suite(sr, make_exit_suite()); diff --git a/tests/check_check_selective.c b/tests/check_check_selective.c index d07a0937..93b4bd99 100644 --- a/tests/check_check_selective.c +++ b/tests/check_check_selective.c @@ -100,6 +100,8 @@ START_TEST(test_srunner_run_run_all) srunner_run (sr, NULL, /* NULL tsuite name. */ NULL, /* NULL tcase name. */ + NULL, /* include any tags */ + NULL, /* exclude no tags */ CK_VERBOSE); ck_assert_msg (srunner_ntests_run(sr) == 3, @@ -116,6 +118,8 @@ START_TEST(test_srunner_run_suite) srunner_run (sr, "suite1", NULL, /* NULL tcase name. */ + NULL, /* include any tags */ + NULL, /* exclude no tags */ CK_VERBOSE); ck_assert_msg (test_tc11_executed @@ -134,6 +138,8 @@ START_TEST(test_srunner_run_no_suite) srunner_run (sr, "non-existing-suite", NULL, /* NULL tcase name. */ + NULL, /* include any tags */ + NULL, /* exclude no tags */ CK_VERBOSE); ck_assert_msg (!(test_tc11_executed @@ -152,6 +158,8 @@ START_TEST(test_srunner_run_tcase) srunner_run (sr, NULL, /* NULL suite name. */ "tcase12", + NULL, /* include any tags */ + NULL, /* exclude no tags */ CK_VERBOSE); ck_assert_msg (!test_tc11_executed @@ -170,6 +178,8 @@ START_TEST(test_srunner_no_tcase) srunner_run (sr, NULL, /* NULL suite name. */ "non-existant-test-case", + NULL, /* include any tags */ + NULL, /* exclude no tags */ CK_VERBOSE); ck_assert_msg (!(test_tc11_executed @@ -188,6 +198,8 @@ START_TEST(test_srunner_suite_tcase) srunner_run (sr, "suite2", "tcase21", + NULL, /* include any tags */ + NULL, /* exclude no tags */ CK_VERBOSE); ck_assert_msg (!test_tc11_executed @@ -206,6 +218,8 @@ START_TEST(test_srunner_suite_no_tcase) srunner_run (sr, "suite1", "non-existant-test-case", + NULL, /* include any tags */ + NULL, /* exclude no tags */ CK_VERBOSE); ck_assert_msg (!(test_tc11_executed diff --git a/tests/check_check_tags.c b/tests/check_check_tags.c new file mode 100644 index 00000000..92c2a6ac --- /dev/null +++ b/tests/check_check_tags.c @@ -0,0 +1,312 @@ +/* + * Check: a unit test framework for C + * Copyright (C) 2001, 2002 Arien Malec + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#include "../lib/libcompat.h" + +#include +#include +#include +#include +#include +#include "check_check.h" + +/* from check_check_master.c */ +#define MAXSTR 300 +char * escape_percent(const char *original, size_t original_size); + + +START_TEST(red_test1) +{ + ck_assert_msg(0, "Red fail"); +} +END_TEST + +START_TEST(blue_test1) +{ + ck_assert_msg(0, "Blue fail"); +} +END_TEST + +START_TEST(purple_test1) +{ + ck_assert_msg(0, "Purple fail"); +} +END_TEST + +START_TEST(yellow_test1) +{ + ck_assert_msg(0, "Yellow fail"); +} +END_TEST + +START_TEST(black_test1) +{ + ck_assert_msg(0, "Black fail"); +} +END_TEST + + +static Suite *make_tagged_suite(void) +{ + Suite *s; + + TCase *red, *blue, *purple, *yellow, *black; + + s = suite_create("Check Tag Filtering"); + + red = tcase_create_tagged("Red", "Red"); + suite_add_tcase (s, red); + tcase_add_test(red, red_test1); + + blue = tcase_create_tagged("Blue", "Blue"); + suite_add_tcase (s, blue); + tcase_add_test(blue, blue_test1); + + purple = tcase_create_tagged("Purple", "Red Blue"); + suite_add_tcase (s, purple); + tcase_add_test(purple, purple_test1); + + yellow = tcase_create_tagged("Yellow", "Yellow"); + suite_add_tcase (s, yellow); + tcase_add_test(yellow, yellow_test1); + + black = tcase_create("Black"); + suite_add_tcase (s, black); + tcase_add_test(black, black_test1); + + return s; +} + +static SRunner *sr = NULL; + +static void tag_test_setup(void) +{ + Suite *s; + + s = make_tagged_suite();; + sr = srunner_create(s); + srunner_set_fork_status(sr, CK_NOFORK); + +} + +static void tag_test_teardown (void) +{ + srunner_free (sr); +} + +/* + * Show that with no filter we run all the tests + */ +START_TEST(no_filter) +{ + const char *expected_msgs[5] = { "Red fail", + "Blue fail", + "Purple fail", + "Yellow fail", + "Black fail"}; + TestResult **tr_fail_array; + unsigned int ntests_failed; + unsigned int ntests_run; + size_t n_expected_msgs; + const char *got_msg; + unsigned int i; + TestResult *tr; + + + n_expected_msgs = sizeof(expected_msgs)/sizeof(expected_msgs[0]); + + srunner_run(sr, NULL, NULL, NULL, NULL, CK_VERBOSE); + + ntests_run = srunner_ntests_run(sr); + ck_assert_msg(ntests_run == n_expected_msgs, + "Did not run expected num tests expected %u but got %u", + n_expected_msgs, ntests_run); + + ntests_failed = srunner_ntests_failed(sr); + ck_assert_msg(ntests_failed == n_expected_msgs, + "Expected to fail %u tests but actually failed %u", + n_expected_msgs, ntests_failed); + + tr_fail_array = srunner_failures(sr); + + for (i=0; i < n_expected_msgs; i++) { + tr = tr_fail_array[i]; + got_msg = tr_msg(tr); + + if (strcmp(got_msg, expected_msgs[i]) != 0) { + char *emsg; + char *tmp = (char *)malloc(MAXSTR); + snprintf(tmp, MAXSTR,"Expected %s, got %s", + expected_msgs[i], got_msg); + + /* + * NOTE: ck_abort_msg() will take the passed string + * and feed it to printf. We need to escape any + * '%' found, else they will result in odd formatting + * in ck_abort_msg(). + */ + emsg = escape_percent(tmp, MAXSTR); + free(tmp); + + ck_abort_msg(emsg); + free(emsg); + } + } +} END_TEST + +/* + * Show that we can select just "Yellow" tagged test cases + */ +START_TEST(inc_yellow) +{ + const char *expected_msgs[1] = { "Yellow fail" }; + TestResult **tr_fail_array; + unsigned int ntests_failed; + unsigned int ntests_run; + size_t n_expected_msgs; + const char *got_msg; + unsigned int i; + TestResult *tr; + + n_expected_msgs = sizeof(expected_msgs)/sizeof(expected_msgs[0]); + + srunner_run(sr, NULL, NULL, "Yellow", NULL, CK_VERBOSE); + + ntests_run = srunner_ntests_run(sr); + ck_assert_msg(ntests_run == n_expected_msgs, + "Did not run expected num tests expected %u but got %u", + n_expected_msgs, ntests_run); + + ntests_failed = srunner_ntests_failed(sr); + ck_assert_msg(ntests_failed == n_expected_msgs, + "Expected to fail %u tests but actually failed %u", + n_expected_msgs, ntests_failed); + + tr_fail_array = srunner_failures(sr); + + + for (i=0; i < n_expected_msgs; i++) { + tr = tr_fail_array[i]; + got_msg = tr_msg(tr); + + if (strcmp(got_msg, expected_msgs[i]) != 0) { + char *emsg; + char *tmp = (char *)malloc(MAXSTR); + snprintf(tmp, MAXSTR,"Expected %s, got %s", + expected_msgs[i], got_msg); + + /* + * NOTE: ck_abort_msg() will take the passed string + * and feed it to printf. We need to escape any + * '%' found, else they will result in odd formatting + * in ck_abort_msg(). + */ + emsg = escape_percent(tmp, MAXSTR); + free(tmp); + + ck_abort_msg(emsg); + free(emsg); + } + } +} END_TEST + +/* + * Show that selecting "Red" tagged test cases gives us red and purple + */ +START_TEST(inc_red) +{ + const char *expected_msgs[2] = { "Red fail", "Purple fail" }; + TestResult **tr_fail_array; + unsigned int ntests_failed; + unsigned int ntests_run; + size_t n_expected_msgs; + const char *got_msg; + unsigned int i; + TestResult *tr; + + + n_expected_msgs = sizeof(expected_msgs)/sizeof(expected_msgs[0]); + + srunner_run(sr, NULL, NULL, "Red", NULL, CK_VERBOSE); + ntests_run = srunner_ntests_run(sr); + + ck_assert_msg(ntests_run == n_expected_msgs, + "Expected to run %u tests but actually ran %u", + n_expected_msgs, ntests_run); + + ntests_failed = srunner_ntests_failed(sr); + + ck_assert_msg(ntests_failed == n_expected_msgs, + "Expected to fail %u tests but actually failed %u", + n_expected_msgs, ntests_failed); + + tr_fail_array = srunner_failures(sr); + + + for (i=0; i < n_expected_msgs; i++) { + tr = tr_fail_array[i]; + got_msg = tr_msg(tr); + + if (strcmp(got_msg, expected_msgs[i]) != 0) { + char *emsg; + char *tmp = (char *)malloc(MAXSTR); + + snprintf(tmp, MAXSTR,"Expected %s, got %s", + expected_msgs[i], got_msg); + + /* + * NOTE: ck_abort_msg() will take the passed string + * and feed it to printf. We need to escape any + * '%' found, else they will result in odd formatting + * in ck_abort_msg(). + */ + emsg = escape_percent(tmp, MAXSTR); + free(tmp); + + ck_abort_msg(emsg); + free(emsg); + } + } + +} END_TEST + +Suite *make_tag_suite(void) +{ + TCase *no_filters, *inc_filters, *exc_filters, *inc_exc_filters; + Suite *s; + + s = suite_create("Check Tag Filtering"); + + no_filters = tcase_create("no tag filters"); + suite_add_tcase (s, no_filters); + tcase_add_test(no_filters, no_filter); + tcase_add_unchecked_fixture (no_filters, + tag_test_setup, + tag_test_teardown); + + inc_filters = tcase_create("include tags"); + suite_add_tcase (s, inc_filters); + tcase_add_test(inc_filters, inc_yellow); + tcase_add_test(inc_filters, inc_red); + + tcase_add_unchecked_fixture (inc_filters, + tag_test_setup, + tag_test_teardown); + return s; +}