diff options
Diffstat (limited to 'lib/subunit/c')
-rw-r--r-- | lib/subunit/c/README | 68 | ||||
-rw-r--r-- | lib/subunit/c/include/subunit/child.h | 79 | ||||
-rw-r--r-- | lib/subunit/c/lib/child.c | 82 | ||||
-rw-r--r-- | lib/subunit/c/tests/test_child.c | 192 |
4 files changed, 421 insertions, 0 deletions
diff --git a/lib/subunit/c/README b/lib/subunit/c/README new file mode 100644 index 0000000000..b62fd45395 --- /dev/null +++ b/lib/subunit/c/README @@ -0,0 +1,68 @@ +# +# subunit C bindings. +# Copyright (C) 2006 Robert Collins <robertc@robertcollins.net> +# +# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause +# license at the users choice. A copy of both licenses are available in the +# project source as Apache-2.0 and BSD. You may not use this file except in +# compliance with one of these two licences. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# license you chose for the specific language governing permissions and +# limitations under that license. + +This subtree contains an implementation of the subunit child protocol. +Currently I have no plans to write a test runner in C, so I have not written +an implementation of the parent protocol. [but will happily accept patches]. +This implementation is built using SCons and tested via 'check'. +See the tests/ directory for the test programs. +You can use `make check` or `scons check` to run the tests. + +The C protocol consists of four functions which you can use to output test +metadata trivially. See lib/subunit_child.[ch] for details. + +However, this is not a test runner - subunit provides no support for [for +instance] managing assertions, cleaning up on errors etc. You can look at +'check' (http://check.sourceforge.net/) or +'gunit' (https://garage.maemo.org/projects/gunit) for C unit test +frameworks. +There is a patch for 'check' (check-subunit-*.patch) in this source tree. +Its also available as request ID #1470750 in the sourceforge request tracker +http://sourceforge.net/tracker/index.php. The 'check' developers have indicated +they will merge this during the current release cycle. + +If you are a test environment maintainer - either homegrown, or 'check' or +'gunit' or some other, you will to know how the subunit calls should be used. +Here is what a manually written test using the bindings might look like: + + +void +a_test(void) { + int result; + subunit_test_start("test name"); + # determine if test passes or fails + result = SOME_VALUE; + if (!result) { + subunit_test_pass("test name"); + } else { + subunit_test_fail("test name", + "Something went wrong running something:\n" + "exited with result: '%s'", result); + } +} + +Which when run with a subunit test runner will generate something like: +test name ... ok + +on success, and: + +test name ... FAIL + +====================================================================== +FAIL: test name +---------------------------------------------------------------------- +RemoteError: +Something went wrong running something: +exited with result: '1' diff --git a/lib/subunit/c/include/subunit/child.h b/lib/subunit/c/include/subunit/child.h new file mode 100644 index 0000000000..0a4e60127b --- /dev/null +++ b/lib/subunit/c/include/subunit/child.h @@ -0,0 +1,79 @@ +/** + * + * subunit C bindings. + * Copyright (C) 2006 Robert Collins <robertc@robertcollins.net> + * + * Licensed under either the Apache License, Version 2.0 or the BSD 3-clause + * license at the users choice. A copy of both licenses are available in the + * project source as Apache-2.0 and BSD. You may not use this file except in + * compliance with one of these two licences. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under these licenses is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license you chose for the specific language governing permissions + * and limitations under that license. + **/ + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * subunit_test_start: + * + * Report that a test is starting. + * @name: test case name + */ +extern void subunit_test_start(char const * const name); + + +/** + * subunit_test_pass: + * + * Report that a test has passed. + * + * @name: test case name + */ +extern void subunit_test_pass(char const * const name); + + +/** + * subunit_test_fail: + * + * Report that a test has failed. + * @name: test case name + * @error: a string describing the error. + */ +extern void subunit_test_fail(char const * const name, char const * const error); + + +/** + * subunit_test_error: + * + * Report that a test has errored. An error is an unintentional failure - i.e. + * a segfault rather than a failed assertion. + * @name: test case name + * @error: a string describing the error. + */ +extern void subunit_test_error(char const * const name, + char const * const error); + + +/** + * subunit_test_skip: + * + * Report that a test has been skipped. An skip is a test that has not run to + * conclusion but hasn't given an error either - its result is unknown. + * @name: test case name + * @reason: a string describing the reason for the skip. + */ +extern void subunit_test_skip(char const * const name, + char const * const reason); + + +#ifdef __cplusplus +} +#endif diff --git a/lib/subunit/c/lib/child.c b/lib/subunit/c/lib/child.c new file mode 100644 index 0000000000..2b59747c0e --- /dev/null +++ b/lib/subunit/c/lib/child.c @@ -0,0 +1,82 @@ +/** + * + * subunit C child-side bindings: report on tests being run. + * Copyright (C) 2006 Robert Collins <robertc@robertcollins.net> + * + * Licensed under either the Apache License, Version 2.0 or the BSD 3-clause + * license at the users choice. A copy of both licenses are available in the + * project source as Apache-2.0 and BSD. You may not use this file except in + * compliance with one of these two licences. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under these licenses is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license you chose for the specific language governing permissions + * and limitations under that license. + **/ + +#include <stdio.h> +#include <string.h> +#include "subunit/child.h" + +/* Write details about a test event. It is the callers responsibility to ensure + * that details are only provided for events the protocol expects details on. + * @event: The event - e.g. 'skip' + * @name: The test name/id. + * @details: The details of the event, may be NULL if no details are present. + */ +static void +subunit_send_event(char const * const event, char const * const name, + char const * const details) +{ + if (NULL == details) { + fprintf(stdout, "%s: %s\n", event, name); + } else { + fprintf(stdout, "%s: %s [\n", event, name); + fprintf(stdout, "%s", details); + if (details[strlen(details) - 1] != '\n') + fprintf(stdout, "\n"); + fprintf(stdout, "]\n"); + } + fflush(stdout); +} + +/* these functions all flush to ensure that the test runner knows the action + * that has been taken even if the subsequent test etc takes a long time or + * never completes (i.e. a segfault). + */ + +void +subunit_test_start(char const * const name) +{ + subunit_send_event("test", name, NULL); +} + + +void +subunit_test_pass(char const * const name) +{ + /* TODO: add success details as an option */ + subunit_send_event("success", name, NULL); +} + + +void +subunit_test_fail(char const * const name, char const * const error) +{ + subunit_send_event("failure", name, error); +} + + +void +subunit_test_error(char const * const name, char const * const error) +{ + subunit_send_event("error", name, error); +} + + +void +subunit_test_skip(char const * const name, char const * const reason) +{ + subunit_send_event("skip", name, reason); +} diff --git a/lib/subunit/c/tests/test_child.c b/lib/subunit/c/tests/test_child.c new file mode 100644 index 0000000000..6399eeb645 --- /dev/null +++ b/lib/subunit/c/tests/test_child.c @@ -0,0 +1,192 @@ +/** + * + * subunit C bindings. + * Copyright (C) 2006 Robert Collins <robertc@robertcollins.net> + * + * Licensed under either the Apache License, Version 2.0 or the BSD 3-clause + * license at the users choice. A copy of both licenses are available in the + * project source as Apache-2.0 and BSD. You may not use this file except in + * compliance with one of these two licences. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under these licenses is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license you chose for the specific language governing permissions + * and limitations under that license. + **/ + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <check.h> + +#include "subunit/child.h" + +/** + * Helper function to capture stdout, run some call, and check what + * was written. + * @expected the expected stdout content + * @function the function to call. + **/ +static void +test_stdout_function(char const * expected, + void (*function)(void)) +{ + /* test that the start function emits a correct test: line. */ + int bytecount; + int old_stdout; + int new_stdout[2]; + char buffer[100]; + /* we need a socketpair to capture stdout in */ + fail_if(pipe(new_stdout), "Failed to create a socketpair."); + /* backup stdout so we can replace it */ + old_stdout = dup(1); + if (old_stdout == -1) { + close(new_stdout[0]); + close(new_stdout[1]); + fail("Failed to backup stdout before replacing."); + } + /* redirect stdout so we can analyse it */ + if (dup2(new_stdout[1], 1) != 1) { + close(old_stdout); + close(new_stdout[0]); + close(new_stdout[1]); + fail("Failed to redirect stdout"); + } + /* yes this can block. Its a test case with < 100 bytes of output. + * DEAL. + */ + function(); + /* restore stdout now */ + if (dup2(old_stdout, 1) != 1) { + close(old_stdout); + close(new_stdout[0]); + close(new_stdout[1]); + fail("Failed to restore stdout"); + } + /* and we dont need the write side any more */ + if (close(new_stdout[1])) { + close(new_stdout[0]); + fail("Failed to close write side of socketpair."); + } + /* get the output */ + bytecount = read(new_stdout[0], buffer, 100); + if (0 > bytecount) { + close(new_stdout[0]); + fail("Failed to read captured output."); + } + buffer[bytecount]='\0'; + /* and we dont need the read side any more */ + fail_if(close(new_stdout[0]), "Failed to close write side of socketpair."); + /* compare with expected outcome */ + fail_if(strcmp(expected, buffer), "Did not get expected output [%s], got [%s]", expected, buffer); +} + + +static void +call_test_start(void) +{ + subunit_test_start("test case"); +} + + +START_TEST (test_start) +{ + test_stdout_function("test: test case\n", call_test_start); +} +END_TEST + + +static void +call_test_pass(void) +{ + subunit_test_pass("test case"); +} + + +START_TEST (test_pass) +{ + test_stdout_function("success: test case\n", call_test_pass); +} +END_TEST + + +static void +call_test_fail(void) +{ + subunit_test_fail("test case", "Multiple lines\n of error\n"); +} + + +START_TEST (test_fail) +{ + test_stdout_function("failure: test case [\n" + "Multiple lines\n" + " of error\n" + "]\n", + call_test_fail); +} +END_TEST + + +static void +call_test_error(void) +{ + subunit_test_error("test case", "Multiple lines\n of output\n"); +} + + +START_TEST (test_error) +{ + test_stdout_function("error: test case [\n" + "Multiple lines\n" + " of output\n" + "]\n", + call_test_error); +} +END_TEST + + +static void +call_test_skip(void) +{ + subunit_test_skip("test case", "Multiple lines\n of output\n"); +} + + +START_TEST (test_skip) +{ + test_stdout_function("skip: test case [\n" + "Multiple lines\n" + " of output\n" + "]\n", + call_test_skip); +} +END_TEST + +static Suite * +child_suite(void) +{ + Suite *s = suite_create("subunit_child"); + TCase *tc_core = tcase_create("Core"); + suite_add_tcase (s, tc_core); + tcase_add_test (tc_core, test_start); + tcase_add_test (tc_core, test_pass); + tcase_add_test (tc_core, test_fail); + tcase_add_test (tc_core, test_error); + tcase_add_test (tc_core, test_skip); + return s; +} + + +int +main(void) +{ + int nf; + Suite *s = child_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_NORMAL); + nf = srunner_ntests_failed(sr); + srunner_free(sr); + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} |