#! @AWK_PATH@ -f # @configure_input@ # checkmk - translate more concise versions of test suite specifications # into C programs suitable for use with the Check unit test # framework. # -- LICENSE -- # # Written by Micah Cowan # Copyright (c) 2006, 2010 Micah Cowan # # Redistribution of this program in any form, with or without # modifications, is permitted, provided that the above copyright is # retained in distributions of this program in source form. # # (This is a free, non-copyleft license compatible with pretty much any # other free or proprietary license, including the GPL. It's essentially # a scaled-down version of the "modified" BSD license.) BEGIN { progname="checkmk"; is_stdin=0; outfname="/dev/stdout"; # Tokens pp_ws = "[ \\t\\f\\v\\r\\n]+"; pp_ws_op = "[ \\t\\f\\v\\r\\n]*"; pp_digit = "[0-9]+" pp_prefix = pp_ws_op "#" pp_ws_op; pp_sep = "[ \\t\\f\\v\\r\\n]+"; pp_name = ".+"; pp_hex_quad = "[A-F0-9a-f][A-F0-9a-f][A-F0-9a-f][A-F0-9a-f]" pp_ucn = "\\\\(u" pp_hex_quad "|U" pp_hex_quad pp_hex_quad ")"; pp_test_name = "([A-Za-z_]|" pp_ucn ")([A-Za-z0-9_]|" pp_ucn ")*"; pp_tag = "([Ss][Uu][Ii][Tt][Ee]|[Tt][Cc][Aa][Ss][Ee])"; pp_test_tag = "[Tt][Ee][Ss][Tt]"; pp_main_pre_tag = "[Mm][Aa][Ii][Nn]-[Pp][Rr][Ee]"; pp_main_post_tag = "[Mm][Aa][Ii][Nn]-[Pp][Oo][Ss][Tt]"; # Tests with arguments pp_test_exit_tag = "[Tt][Ee][Ss][Tt]-[Ee][Xx][Ii][Tt]" pp_ws_op "[(]" \ pp_ws_op "[+-]?" pp_ws_op pp_digit pp_ws_op "[)]"; pp_test_signal_tag = "[Tt][Ee][Ss][Tt]-[Ss][Ii][Gg][Nn][Aa][Ll]" pp_ws_op \ "[(]" pp_ws_op "[+-]?" pp_ws_op pp_digit pp_ws_op "[)]"; pp_test_loop_tag = "[Tt][Ee][Ss][Tt]-[Ll][Oo][Oo][Pp]" pp_ws_op "[(]" \ pp_ws_op "[+-]?" pp_ws_op pp_digit pp_ws_op "[,]" pp_ws_op \ "[+-]?" pp_ws_op pp_digit pp_ws_op "[)]"; pp_test_loop_exit_tag = "[Tt][Ee][Ss][Tt]-[Ll][Oo][Oo][Pp]-[Ee][Xx][Ii][Tt]" \ pp_ws_op "[(]" pp_ws_op "[+-]?" pp_ws_op pp_digit pp_ws_op \ "[,]" pp_ws_op "[+-]?" pp_ws_op pp_digit pp_ws_op "[,]" \ pp_ws_op "[+-]?" pp_ws_op pp_digit pp_ws_op "[)]"; pp_test_loop_signal_tag = "[Tt][Ee][Ss][Tt]-[Ll][Oo][Oo][Pp]-[Ss][Ii][Gg]" \ "[Nn][Aa][Ll]" pp_ws_op "[(]" pp_ws_op "[+-]?" pp_ws_op pp_digit \ pp_ws_op "[,]" pp_ws_op "[+-]?" pp_ws_op pp_digit pp_ws_op \ "[,]" pp_ws_op "[+-]?" pp_ws_op pp_digit pp_ws_op "[)]"; pp_suite_or_tcase_line = "^" pp_prefix pp_tag pp_ws pp_name "$"; pp_test_line_prefix = "^" pp_prefix pp_test_tag pp_ws; pp_test_exit_line_prefix = "^" pp_prefix pp_test_exit_tag pp_ws; pp_test_signal_line_prefix = "^" pp_prefix pp_test_signal_tag pp_ws; pp_test_loop_line_prefix = "^" pp_prefix pp_test_loop_tag pp_ws; pp_test_loop_exit_line_prefix = "^" pp_prefix pp_test_loop_exit_tag pp_ws; pp_test_loop_signal_line_prefix = "^" pp_prefix pp_test_loop_signal_tag pp_ws; pp_test_line = pp_test_line_prefix pp_name pp_ws_op "$"; pp_test_exit_line = pp_test_exit_line_prefix pp_name pp_ws_op "$"; pp_test_signal_line = pp_test_signal_line_prefix pp_name pp_ws_op "$"; pp_test_loop_line = pp_test_loop_line_prefix pp_name pp_ws_op "$"; pp_test_loop_exit_line = pp_test_loop_exit_line_prefix pp_name pp_ws_op "$"; pp_test_loop_signal_line = pp_test_loop_signal_line_prefix pp_name pp_ws_op "$"; pp_main_pre_line = "^" pp_prefix pp_main_pre_tag pp_ws_op "$"; pp_main_post_line = "^" pp_prefix pp_main_post_tag pp_ws_op "$"; # Global vars in_test = needs_line_decl = 0; cur_suite = cur_tcase = "Core"; cur_test = ""; exit_okay = start = 1; num_cur_tcases = num_cur_tests = 0; test_type = num_tests = 0; arg1 = 0; arg2 = 1; arg3 = 2; test_name = 0; test_type_flag = 1; } # Run on the first line of the input file. start { print_boilerplate(); start = 0; } # (Executed every line:) { print_line = 1; } $0 ~ pp_suite_or_tcase_line { if (in_main()) in_main_error(); # Skip to the start of the tag ("suite" or "tcase"). match($0, pp_prefix); rol = substr($0, RLENGTH+1); # Save away the tag. match(rol, "^" pp_tag); tag = substr(rol, 1, RLENGTH); # Advance past the ws following tag. rol = substr(rol, RLENGTH+1); match(rol, pp_ws); rol = substr(rol, RLENGTH+1); # The suite or tcase name is the rest of the line, minus any # trailing ws. if (match(rol, pp_ws "$")) { name = substr(rol, 1, RSTART-1); } else { name = rol; } if (tolower(tag) == "suite") { # Does this suite already exist? if ((name, 0) in suite_tcase_map) { error_with_line("Suite \"" name "\" already exists."); } cur_suite = name; num_cur_tcases = 0; } else if ((name, 0, 0) in tcase_test_map) { error_with_line("Test Case \"" name "\" already exists."); } cur_tcase = name; num_cur_tests = 0; finish_test(); print_line = 0; if (!clean_mode) needs_line_decl = 1; } $0 ~ pp_test_line { # Pre checks test_pre(); # Get the test name match($0, pp_test_line_prefix) cur_test = substr($0, RLENGTH+1); # Remove trailing ws. sub(pp_ws_op "$", "", cur_test); # Check for duplicate tests / cases and valid names duplicate_check(); # Boilerplate printing code test_post(); # Set type before calling register test_type = 0; register_test(); print_line = 0; in_test = 1; } $0 ~ pp_test_loop_line { test_pre(); match($0, pp_test_loop_line_prefix) cur_test = substr($0, RLENGTH+1); sub(pp_ws_op "$", "", cur_test); duplicate_check(); # Split the line into an array to extract the arguments split($0, arr, pp_ws_op "[(),]" pp_ws_op); # Eliminate possible whitespace between sign and numbers gsub(pp_ws_op, "", arr[2]); gsub(pp_ws_op, "", arr[3]); test_post(); test_type = 1; register_test(); print_line = 0; in_test = 1; } $0 ~ pp_test_exit_line { test_pre(); match($0, pp_test_exit_line_prefix) cur_test = substr($0, RLENGTH+1); sub(pp_ws_op "$", "", cur_test); duplicate_check(); split($0, arr, pp_ws_op "[(),]" pp_ws_op); gsub(pp_ws_op, "", arr[2]); test_post(); test_type = 2; register_test(); print_line = 0; in_test = 1; } $0 ~ pp_test_signal_line { test_pre(); match($0, pp_test_signal_line_prefix) cur_test = substr($0, RLENGTH+1); sub(pp_ws_op "$", "", cur_test); duplicate_check(); split($0, arr, pp_ws_op "[(),]" pp_ws_op); gsub(pp_ws_op, "", arr[2]); test_post(); test_type = 3; register_test(); print_line = 0; in_test = 1; } $0 ~ pp_test_loop_exit_line { test_pre(); match($0, pp_test_loop_exit_line_prefix) cur_test = substr($0, RLENGTH+1); sub(pp_ws_op "$", "", cur_test); duplicate_check(); split($0, arr, pp_ws_op "[(),]" pp_ws_op); gsub(pp_ws_op, "", arr[2]); gsub(pp_ws_op, "", arr[3]); gsub(pp_ws_op, "", arr[4]); test_post(); test_type = 4; register_test(); print_line = 0; in_test = 1; } $0 ~ pp_test_loop_signal_line { test_pre(); match($0, pp_test_loop_signal_line_prefix) cur_test = substr($0, RLENGTH+1); sub(pp_ws_op "$", "", cur_test); duplicate_check(); split($0, arr, pp_ws_op "[(),]" pp_ws_op); gsub(pp_ws_op, "", arr[2]); gsub(pp_ws_op, "", arr[3]); gsub(pp_ws_op, "", arr[4]); test_post(); test_type = 5; register_test(); print_line = 0; in_test = 1; } $0 ~ pp_main_pre_line { main_pre(); print ""; print " /* User-specified pre-run code */"; if (!clean_mode) needs_line_decl = 1; print_line = 0; } $0 ~ pp_main_post_line { main_post(); print ""; print " /* User-specified post-run code */"; if (!clean_mode) needs_line_decl = 1; print_line = 0; } print_line { if (/[^ \t\f\v\r\n]/ && needs_line_decl && !clean_mode) { print "#line " FNR; needs_line_decl = 0; } print; } END { if (!exit_okay) { # We're exiting due to an error. Don't do anything else. } else if (num_tests) { if (!main_post_done) { main_post(); print ""; print " return nf == 0 ? 0 : 1;"; } print "}"; } else { error("Expected at least one #test line."); } } ### Functions ### function test_pre() { if (in_main()) in_main_error(); if (in_test) { finish_test(); print ""; } ++num_tests; } function duplicate_check() { # Confirm that the test name is a valid C identifier. if (!match(cur_test, "^" pp_test_name "$")) { error_with_line("Malformed test name \"" cur_test \ "\" (must be a C identifier)."); } # Verify that it has not already been used. if (cur_test in test_registry) { error_with_line("Test \"" cur_test "\" already exists."); } # Verify that any implied test case is not a repeat. if (num_cur_tests == 0 && (cur_tcase, 0, 0) in tcase_test_map) { error_with_line("Test Case \"" name "\" already exists."); } } function test_post() { print "START_TEST(" cur_test ")"; print "{"; if (!clean_mode) print "#line " FNR+1; needs_line_decl = 0; } function main_post() { if (main_post_done) error_with_line("main-post specified multiple times."); if (!main_pre_done) main_pre(); main_post_done = 1; print ""; print_runner_bindings(); print ""; print " srunner_run_all(sr, CK_ENV);"; print " nf = srunner_ntests_failed(sr);"; print " srunner_free(sr);"; } function in_main() { return main_pre_done || main_post_done; } function in_main_error() { error_with_line("Cannot specify tests after main-pre or main-post."); } function main_pre() { if (main_post_done) error_with_line("main-pre specified after main-post."); if (main_pre_done) error_with_line("main-pre specified multiple times."); main_pre_done = 1; finish_test(); print ""; print "int main(void)"; print "{"; print_main_declarations(); } function suite_var_name(num) { return "s" num+1; } function tcase_var_name(snum, tcnum) { return "tc" snum+1 "_" tcnum+1; } function print_main_declarations() { for (i=0; i != num_suites; ++i) { s = suite_names[i]; svar = suite_var_name(i); print " Suite *" svar " = suite_create(" string_encode(s) ");"; for (j=0; j != suite_num_tcases[s]; ++j) { tc = suite_tcase_map[s, j]; tcvar = tcase_var_name(i, j); print " TCase *" tcvar " = tcase_create(" \ string_encode(tc) ");"; } } print " SRunner *sr = srunner_create(s1);"; print " int nf;"; } function string_encode(raw) { # The next line might look funny, but remember that the first # argument will go through both string interpolation /and/ regex # interpolation, so the backslashes must be double-escaped. The # substitution string is supposed to result in an actual # double-backslash. gsub("\\\\", "@AWK_GSUB_DBL_BSLASH@", raw); gsub("\"", "\\\"", raw); return "\"" raw "\""; } function print_runner_bindings() { for (i=0; i != num_suites; ++i) { s = suite_names[i]; svar = suite_var_name(i); for (j=0; j != suite_num_tcases[s]; ++j) { tc = suite_tcase_map[s, j]; tcvar = tcase_var_name(i, j); print " suite_add_tcase(" svar ", " tcvar ");"; for (k=0; k != tcase_num_tests[tc]; ++k) { t = tcase_test_map[tc, k, test_name]; test_type = tcase_test_map[tc, k, test_type_flag]; if (test_type == 0) { print " tcase_add_test(" tcvar ", " t ");"; } else if (test_type == 1) { print " tcase_add_loop_test(" tcvar ", " t ", " \ test_parameters[t, arg1] ", " test_parameters[t, arg2] \ ");"; } else if (test_type == 2) { print " tcase_add_exit_test(" tcvar ", " t ", " \ test_parameters[t, arg1] ");"; } else if (test_type == 3) { print " tcase_add_test_raise_signal(" tcvar ", " t ", " \ test_parameters[t, arg1] ");"; } else if (test_type == 4) { print " tcase_add_loop_exit_test(" tcvar ", " t ", " \ test_parameters[t, arg1] ", " test_parameters[t, arg2] \ ", " test_parameters[t, arg3] ");"; } else if (test_type == 5) { print " tcase_add_loop_test_raise_signal(" tcvar ", " t \ ", " test_parameters[t, arg1] ", " test_parameters[t, arg2] \ ", " test_parameters[t, arg3] ");"; } } } } if (num_suites > 1) { print ""; for (i=1; i != num_suites; ++i) { svar = suite_var_name(i); print " srunner_add_suite(sr, " svar ");"; } } } function register_test() { if (num_cur_tcases == 0) { suite_names[num_suites++] = cur_suite; } if (num_cur_tests == 0) { suite_tcase_map[cur_suite, num_cur_tcases++] = cur_tcase; suite_num_tcases[cur_suite] = num_cur_tcases; } tcase_test_map[cur_tcase, num_cur_tests, test_name] = cur_test; tcase_test_map[cur_tcase, num_cur_tests++, test_type_flag] = test_type; tcase_num_tests[cur_tcase] = num_cur_tests; test_registry[cur_test] = 1; # Store arguments to array test_parameters[cur_test, arg1] = arr[2]; test_parameters[cur_test, arg2] = arr[3]; test_parameters[cur_test, arg3] = arr[4]; } function finish_test() { if (in_test) { in_test = 0; print "}"; print "END_TEST"; } } function print_boilerplate() { print "/*"; print " * DO NOT EDIT THIS FILE. Generated by " progname "."; if (!FILENAME || FILENAME == "-") { clean_mode=1; is_stdin=1; } if (is_stdin) srcfile = "(standard input)"; else srcfile = "\"" FILENAME "\""; print " * Edit the original source file " srcfile " instead."; print " */"; print ""; print "#include "; print ""; if (!clean_mode) print "#line 1 " string_encode(FILENAME) } function error_with_line(err) { error((is_stdin ? "(standard input)" : FILENAME) " line " FNR ": " err); } function error(err) { print progname ": " err > "/dev/stderr"; exit_okay = 0; exit 1; }