]> granicus.if.org Git - apache/commitdiff
httpdunit: a Check-based unit test suite
authorJacob Champion <jchampion@apache.org>
Thu, 25 May 2017 21:18:32 +0000 (21:18 +0000)
committerJacob Champion <jchampion@apache.org>
Thu, 25 May 2017 21:18:32 +0000 (21:18 +0000)
Add a unit test suite based on Check:

    https://libcheck.github.io/check/

The suite depends on the build system to automatically generate the code
stubs that call every test case.

httpdunit is automatically enabled in the build if configure is able to
find Check via pkg-config. At the moment pkg-config is the only official
(non-deprecated) way to build and link against Check with an autoconf
system, since platforms may distribute Check as a static library.

Note that Check is an LGPL'd library, so we can't distribute test
objects and binaries. Building and running the suite remains optional
and is not required to run the server.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/httpdunit@1796202 13f79535-47bb-0310-9956-ffa450edef68

Makefile.in
build/httpdunit_gen_cases.pl [new file with mode: 0755]
build/httpdunit_gen_stubs.pl [new file with mode: 0755]
configure.in
test/httpdunit.c [new file with mode: 0644]
test/httpdunit.h [new file with mode: 0644]

index 4eacffe9c0ff18787f72e8daab0d687acff2b856..f6fac281321d3554e228b4afb007eff975f2b9e7 100644 (file)
@@ -28,7 +28,7 @@ DISTCLEAN_TARGETS  = include/ap_config_auto.h include/ap_config_layout.h \
        build/pkg/pkginfo build/config_vars.sh bsd_converted
 EXTRACLEAN_TARGETS = configure include/ap_config_auto.h.in generated_lists \
        httpd.spec
-PHONY_TARGETS := check check-conf check-dirs check-include
+PHONY_TARGETS := check check-conf check-dirs check-include unittest-objdir
 
 include $(top_builddir)/build/rules.mk
 include $(top_srcdir)/build/program.mk
@@ -431,3 +431,39 @@ check: check-include check-dirs check-conf check/build/config_vars.mk check/apxs
            ./t/TEST -clean && \
            ./t/TEST -config && \
            ./t/TEST
+
+#
+# Unit Test Suite
+#
+
+# Make sure the object subdirectories we use exist in the build directory during
+# VPATH builds.
+unittest-objdir:
+       @mkdir -p test/unit
+
+# Normally I don't like wildcard sources, but for tests, autodiscovery is the
+# way to go.
+testcase_SOURCES := $(patsubst $(top_srcdir)/%,%,$(wildcard $(top_srcdir)/test/unit/*.c))
+testcase_OBJECTS := $(testcase_SOURCES:%.c=%.lo)
+testcase_STUBS   := $(testcase_SOURCES:%.c=%.tests)
+
+# Each testcase depends on the source file as well as the autogenerated .tests
+# stub.
+$(testcase_OBJECTS): %.lo: %.c %.tests | unittest-objdir
+
+$(testcase_STUBS): %.tests: %.c
+       $(top_srcdir)/build/httpdunit_gen_stubs.pl < "$<" > "$@"
+
+test/httpdunit.cases: $(testcase_SOURCES) | unittest-objdir
+       for t in $^; do \
+           $(top_srcdir)/build/httpdunit_gen_cases.pl < "$$t"; \
+       done > $@
+
+test/httpdunit.lo: test/httpdunit.c test/httpdunit.cases | unittest-objdir
+
+# httpdunit is only added to $(other_targets) if configure detects a working
+# libcheck on the system.
+httpdunit_OBJECTS := test/httpdunit.lo $(testcase_OBJECTS)
+$(httpdunit_OBJECTS): override LTCFLAGS += $(UNITTEST_CFLAGS)
+test/httpdunit: $(httpdunit_OBJECTS) $(PROGRAM_DEPENDENCIES) $(PROGRAM_OBJECTS)
+       $(LINK) $(httpdunit_OBJECTS) $(PROGRAM_OBJECTS) $(UNITTEST_LIBS) $(PROGRAM_LDADD)
diff --git a/build/httpdunit_gen_cases.pl b/build/httpdunit_gen_cases.pl
new file mode 100755 (executable)
index 0000000..a40d12c
--- /dev/null
@@ -0,0 +1,23 @@
+#! /usr/bin/env perl
+
+#
+# Generates a list of test cases to be pulled into the httpdunit main test
+# suite.
+#
+# Supply all the test cases' source file contents on stdin; the resulting code
+# will be printed to stdout. Normally you will want to call this twice: once
+# with --declaration to print the function declarations of all the test cases,
+# and once without any options to produce the code that actually adds each test
+# case to the main suite.
+#
+
+use strict;
+use warnings;
+
+while (my $line = <>) {
+    if ($line =~ /^HTTPD_BEGIN_TEST_CASE(?:\w+)?\((\w+)/) {
+        my $name = "$1_test_case";
+        print "TCase *$name(void); ";
+        print "suite_add_tcase(suite, $name());\n";
+    }
+}
diff --git a/build/httpdunit_gen_stubs.pl b/build/httpdunit_gen_stubs.pl
new file mode 100755 (executable)
index 0000000..abb640c
--- /dev/null
@@ -0,0 +1,22 @@
+#! /usr/bin/env perl
+
+#
+# Generates a code stub that adds unit tests to a Check test case.
+#
+# Supply the test case's source file contents on stdin; the resulting code will
+# be printed to stdout. This code is designed to be included as part of the
+# boilerplate at the end of each test case.
+#
+
+use strict;
+use warnings;
+
+while (my $line = <>) {
+    # FIXME: this does not correctly handle macro invocations that are split
+    # over multiple lines.
+    if ($line =~ /^HTTPD_START_LOOP_TEST\((\w+),(.*)\)/) {
+        print "tcase_add_loop_test(testcase, $1, 0, ($2));\n";
+    } elsif ($line =~ /^START_TEST\((\w+)\)/) {
+        print "tcase_add_test(testcase, $1);\n"
+    }
+}
index 0309d77c437e29fadcbdc6c2a69e83e10ea5fa2a..ef7bd02482437dac20063621cd82e17c53dd9b86 100644 (file)
@@ -706,6 +706,22 @@ AC_ARG_WITH(valgrind,
     fi ]
 )
 
+dnl Enable the unit test executable if Check is installed.
+dnl TODO: at the moment, only pkg-config discovery is supported.
+AC_MSG_CHECKING([for Check to enable unit tests])
+if test "x$PKGCONFIG" != "x" && `$PKGCONFIG --atleast-version='0.9.12' check`; then
+  UNITTEST_CFLAGS=`$PKGCONFIG --cflags check`
+  UNITTEST_LIBS=`$PKGCONFIG --libs check`
+  other_targets="$other_targets test/httpdunit"
+
+  AC_MSG_RESULT([yes])
+else
+  AC_MSG_RESULT([no])
+fi
+APACHE_SUBST(UNITTEST_CFLAGS)
+APACHE_SUBST(UNITTEST_LIBS)
+
+
 prefix="$orig_prefix"
 APACHE_ENABLE_MODULES
 
diff --git a/test/httpdunit.c b/test/httpdunit.c
new file mode 100644 (file)
index 0000000..ac66121
--- /dev/null
@@ -0,0 +1,53 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "check.h"
+
+#include "apr_general.h"
+
+static Suite *main_test_suite(void)
+{
+    Suite *suite = suite_create("main");
+
+    /* The list of test cases is automatically generated from the test/unit
+     * directory by the build system. */
+#   include "test/httpdunit.cases"
+
+    return suite;
+}
+
+int main(int argc, const char * const argv[])
+{
+    SRunner *runner;
+    int failed;
+
+    /* Initialize APR and create our test runner. */
+    apr_app_initialize(&argc, &argv, NULL);
+    runner = srunner_create(main_test_suite());
+
+    /* Log TAP to stdout. */
+    srunner_set_tap(runner, "-");
+
+    /* Run the tests and collect failures. */
+    srunner_run_all(runner, CK_SILENT /* output only TAP */);
+    failed = srunner_ntests_failed(runner);
+
+    /* Clean up. */
+    srunner_free(runner);
+    apr_terminate();
+
+    return failed ? 1 : 0;
+}
diff --git a/test/httpdunit.h b/test/httpdunit.h
new file mode 100644 (file)
index 0000000..eddb956
--- /dev/null
@@ -0,0 +1,100 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * httpdunit.h: a collection of test helper macros designed to reduce test
+ * boilerplate and help the build system autogenerate test case definitions.
+ *
+ * Here's how the magic works:
+ *
+ * Every test case under test/unit declares test functions using Check's
+ * START_TEST or the HTTPD_START_LOOP_TEST macro. The build system searches each
+ * source file for those macros and generates an appropriate stub, called
+ * test/unit/<filename>.tests, that will add each test function to the test
+ * case. This stub is then pulled in by three lines of boilerplate at the end of
+ * every test case's source file (see the HTTPD_BEGIN_TEST_CASE documentation,
+ * below).
+ *
+ * The build system uses that same three-line boilerplate to determine the names
+ * of all the test cases, and adds them automatically to the main test suite
+ * using a similar generate-stub-and-include process.
+ */
+
+#include "check.h"
+
+/*
+ * Boilerplate Macros
+ */
+
+/**
+ * Declares a test case. The build system uses this macro to generate a stub,
+ * which will automatically pull the new test case into the main suite.
+ *
+ * After beginning a test case, you must #include the autogenerated testcase
+ * stub ("test/unit/<filename>.tests") and then end the test case with
+ * HTTPD_END_TEST_CASE. For example, for a test case called foobar.c:
+ *
+ *    HTTPD_BEGIN_TEST_CASE(foobar)
+ *    #include "test/unit/foobar.tests"
+ *    HTTPD_END_TEST_CASE
+ *
+ * The NAME identifying the test case must be globally unique within the test
+ * suite; if in doubt, just use the filename (minus extension).
+ */
+#define HTTPD_BEGIN_TEST_CASE(NAME) \
+TCase * NAME##_test_case(void); \
+TCase * NAME##_test_case(void) \
+{ \
+    TCase *testcase = tcase_create(#NAME);
+
+/**
+ * Like HTTPD_BEGIN_TEST_CASE, but additionally declares a pair of setup and
+ * teardown functions for the test case. The setup function is run once before
+ * every test function, and the teardown function is run directly after. Keep in
+ * mind that expensive fixtures will heavily impact test runtime.
+ *
+ * Both setup and teardown take no parameters and return void.
+ *
+ * These functions are passed to Check's tcase_add_checked_fixture(). See the
+ * Check documentation for details; the gist is that checked fixures run after
+ * Check forks the test process, and therefore they cannot influence each other
+ * even if something smashes the heap.
+ */
+#define HTTPD_BEGIN_TEST_CASE_WITH_FIXTURE(NAME, SETUP, TEARDOWN) \
+HTTPD_BEGIN_TEST_CASE(NAME) \
+    tcase_add_checked_fixture(testcase, (SETUP), (TEARDOWN));
+
+/**
+ * Bookend for HTTPD_BEGIN_TEST_CASE[_WITH_FIXTURE], which finishes the test
+ * case function.
+ */
+#define HTTPD_END_TEST_CASE \
+    return testcase; \
+}
+
+/*
+ * Test Declaration Macros
+ */
+
+/**
+ * Use this macro instead of Check's START_TEST when you want to run a test
+ * function multiple times. The build system will add the test function to the
+ * test case using Check's tcase_add_loop_test().
+ *
+ * The loop index is available to the test function as the special variable _i.
+ * _i will start at 0 and increment to a maximum of (NUM_ITERATIONS - 1).
+ */
+#define HTTPD_START_LOOP_TEST(NAME, NUM_ITERATIONS) START_TEST(NAME)