aboutsummaryrefslogtreecommitdiff
path: root/tests/convey.h
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2016-12-20 20:59:33 -0800
committerGarrett D'Amore <garrett@damore.org>2016-12-20 20:59:33 -0800
commit529c84d6a1bf2400170263c9e68d9433a70cc43d (patch)
treeee97e857548a3cfe8dc4c7e2b0a179c14f9fb69c /tests/convey.h
parent09c631a793e46a1acc5848592f246fbb2b6c6f4e (diff)
downloadnng-529c84d6a1bf2400170263c9e68d9433a70cc43d.tar.gz
nng-529c84d6a1bf2400170263c9e68d9433a70cc43d.tar.bz2
nng-529c84d6a1bf2400170263c9e68d9433a70cc43d.zip
Updates to reflect new external convey framework.
Diffstat (limited to 'tests/convey.h')
-rw-r--r--tests/convey.h349
1 files changed, 252 insertions, 97 deletions
diff --git a/tests/convey.h b/tests/convey.h
index ea9d7a29..bb52ad7d 100644
--- a/tests/convey.h
+++ b/tests/convey.h
@@ -1,39 +1,21 @@
/*
* Copyright 2016 Garrett D'Amore <garrett@damore.org>
*
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom
- * the Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included
- * in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
+ * This software is supplied under the terms of the MIT License, a
+ * copy of which should be located in the distribution where this
+ * file was obtained (LICENSE.txt). A copy of the license may also be
+ * found online at https://opensource.org/licenses/MIT.
*/
-#ifndef TESTS_CONVEY_H
-
-#define TESTS_CONVEY_H
-
-#include "test.h"
-
-/*
- * This header file provides a friendlier API to the test-convey framework.
- * It basically provides some "friendly" names for symbols to use instead of
- * the test_xxx symbols. Basically we pollute your namespace, for your
- * benefit. Don't like the pollution? Use test.h instead.
- */
+#ifndef CONVEY_H
+#define CONVEY_H
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <setjmp.h>
+#include <stdarg.h>
/*
* This test framework allows one to write tests as a form of assertion,
@@ -41,7 +23,7 @@
*
* The test framework provides a main() function.
*
- * To use this call the test_main() macro, and embed test_convey() references.
+ * To use this call the Main() macro, and embed Test() and Convey() blocks.
* These can be nested, and after each convey the entire stack is popped so
* that execution can continue from the beginning, giving each test section
* the same environment.
@@ -51,112 +33,285 @@
*
* Here's a sample file:
*
- * TestMain("Integer Tests", {
- * int x = 1; int y = 2;
- * Convey("Addition works", func() {
- * So(y == 2);
- * So(y + x == 3);
- * So(x + y == 3);
- * Convey("Even big numbers", func() {
- * y = 100;
- * So(x + y == 101);
- * });
- * Convey("Notice y is still 2 in this context", func() {
+ * Main({
+ * Test({"Integer Tests", {
+ * int x = 1; int y = 2;
+ * Convey("Addition works", func() {
* So(y == 2);
- * });
- * });
+ * So(y + x == 3);
+ * So(x + y == 3);
+ * Convey("Even big numbers", {
+ * y = 100;
+ * So(x + y == 101);
+ * });
+ * Convey("Notice y is still 2 in this context", {
+ * So(y == 2);
+ * });
+ * });
+ * });
* })
- *
- * There are other macros, but this is a work in progress. The inspiration
- * for this is from GoConvey -- github.com/smartystreets/goconvey - but this
- * is a version for C programs.
*
- * In addition to the names listed here, your test code should avoid using
- * names beginning with "test_" or "T_" as we use those names internally
- * in macros, which may collide or do other bad things with your names.
+ * This was inspired by GoConvey -- github.com/smartystreets/goconvey - but
+ * there are differences of course -- C is not Go!
+ *
+ * Pleaes note that we abuse the C preprocessor and setjmp fairly heavily,
+ * and as a result of the magic we have to do, a lot of these guts must be
+ * exposed in this header file. HOWEVER, only symbols beginning with a
+ * capital letter are intended for consumers. All others are for internal
+ * use only. Otherwise, welcome to the sausage factory.
+ *
+ * Please see the documentation at github.com/gdamore/c-convey for more
+ * details about how to use this.
+ */
+
+/*
+ * This structure has to be exposed in order to expose the buffer used for
+ * setjmp. It's members should never be accessed directly. These should be
+ * allocated statically in the routine(s) that need custom contexts. The
+ * framework creates a context automatically for each convey scope.
+ */
+typedef struct {
+ jmp_buf cs_jmp;
+ void *cs_data;
+} conveyScope;
+
+/* These functions are not for use by tests -- they are used internally. */
+extern int conveyStart(conveyScope *, const char *);
+extern int conveyLoop(conveyScope *, int);
+extern void conveyFinish(conveyScope *, int *);
+extern int conveyMain(int, char **);
+
+extern void conveyAssertPass(const char *, const char *, int);
+extern void conveyAssertSkip(const char *, const char *, int);
+extern void conveyAssertFail(const char *, const char *, int);
+extern void conveySkip(const char *, int, const char *, ...);
+extern void conveyFail(const char *, int, const char *, ...);
+extern void conveyError(const char *, int, const char *, ...);
+extern void conveyPrintf(const char *, int, const char *, ...);
+
+/*
+ * conveyRun is a helper macro not to be called directly by user
+ * code. It has to be here exposed, in order for setjmp() to work.
+ * and for the code block to be inlined. Becuase this inlines user
+ * code, we have to be *very* careful with symbol names.
+ */
+#define conveyRun(convey_name, convey_code, convey_resultp) \
+ do { \
+ static conveyScope convey_scope; \
+ int convey_unwind; \
+ int convey_break = 0; \
+ if (conveyStart(&convey_scope, convey_name) != 0) { \
+ break; \
+ } \
+ convey_unwind = setjmp(convey_scope.cs_jmp); \
+ if (conveyLoop(&convey_scope, convey_unwind) != 0) { \
+ break; \
+ } \
+ do { \
+ convey_code \
+ } while (0); \
+ if (convey_break) { \
+ break; \
+ } \
+ conveyFinish(&convey_scope, convey_resultp); \
+ } while (0);
+
+/*
+ * ConveyRset establishes a reset for the current scope. This code will
+ * be executed every time the current scope is unwinding. This means that
+ * the code will be executed each time a child convey exits. It is also
+ * going to be executed once more, for the final pass, which doesn't actually
+ * execute any convey blocks. (This final pass is required in order to
+ * learn that all convey's, as well as any code beyond them, are complete.)
+ *
+ * The way this works is by overriding the existing scope's jump buffer.
+ *
+ * Unlike with GoConvey, this must be registered before any children
+ * convey blocks; the logic only affects convey blocks that follow this
+ * one, within the same scope.
+ *
+ * This must be in a conveyRun scope (i.e. part of a Convey() or a
+ * top level Test() or it will not compile.
+ *
+ * It is possible to have a subsequent reset at the same convey scope
+ * override a prior reset. Normally you should avoid this, and just
+ * use lower level convey blocks.
*/
+#define ConveyReset(convey_reset_code) \
+ convey_unwind = setjmp(convey_scope.cs_jmp); \
+ if (convey_unwind) { \
+ do { \
+ convey_reset_code \
+ } while (0); \
+ } \
+ if (conveyLoop(&convey_scope, convey_unwind) != 0) { \
+ convey_break = 1; \
+ break; \
+ }
/*
- * TestMain is used to generate a main() function that runs your code,
- * and is appropriate when your entire program consists of one test.
- * This is equivalent to doing Main() with just a single Test(), but it
- * may spare you a level of indentation.
+ * ConveyMain is the outer most scope that most test programs use, unless they
+ * use the short-cut ConveyTestMain. This creates a main() routine that
+ * sets up the program, parses options, and then executes the tests nested
+ * within it.
*/
-#define TestMain(name, code) test_main(name, code)
+#define ConveyMain(code) \
+ static int convey_main_rv; \
+ int conveyMainImpl(void) { \
+ do { \
+ code \
+ } while (0); \
+ return (convey_main_rv); \
+ } \
+ int main(int argc, char **argv) { \
+ return (conveyMain(argc, argv)); \
+ }
/*
- * Main() wraps zero or more Tests, which will then contain Convey
- * scopes. This emits a main function, and can only be used once.
- * It also cannot be used with TestMain.
+ * ConveyTest creates a top-level test instance, which can contain multiple
+ * Convey blocks.
*/
-#define Main(code) test_main_group(code)
+#define ConveyTest(name, code) \
+ do { \
+ int convey_rv; \
+ conveyRun(name, code, &convey_rv); \
+ if (convey_rv > convey_main_rv) { \
+ convey_main_rv = convey_rv; \
+ }; \
+ } while (0);
/*
- * Test creates a top-level test scope.
+ * ConveyTestMain is used to wrap the top-level of your test suite, and is
+ * used in lieu of a normal main() function. This is the usual case where
+ * the executable only contains a single top level test group. It
+ * is the same as using Main with just a single Test embedded, but saves
+ * some typing and probably a level of indentation.
*/
-#define Test(name, code) test_group(name, code)
+#define ConveyTestMain(name, code) \
+ ConveyMain(ConveyTest(name, code))
/*
- * Convey starts a new test scope. These can be nested. The code is
- * executed, including new scopes, but each time a new scope is encountered,
- * the stack is unwound to give the code a fresh start to work with.
+ * EXPERIMENTAL:
+ * If you don't want to use the test framework's main routine, but
+ * prefer (or need, because of threading for example) to have your
+ * test code driven separately, you can use inject ConveyBlock() in
+ * your function. It works like ConveyMain(). These must not be
+ * nested within other Conveys, Tests, or Blocks (or Main). The
+ * results are undefined if you try that. The final result pointer may
+ * be NULL, or a pointer to an integer to receive the an integer
+ * result from the test. (0 is success, 4 indicates a failure to allocate
+ * memory in the test framework, and anything else indicates a
+ * an error or failure in the code being tested.
+ *
+ * Blocks do not contain Tests, rather they contain Conveys only. The
+ * Block takes the place of both Main() and Test(). It is to be hoped
+ * that you will not need this.
*/
-#define Convey(name, code) test_convey(name, code)
+#define ConveyBlock(name, code, resultp) conveyRun(name, code, resultp)
/*
- * So is to be used like assert(), except that it always is checked,
- * and records results in the current scope. If the assertion fails,
- * then no further processing in the same scope (other than possible
- * reset logic) is performed. Additional tests at higher scopes, or
- * in sibling scopes, may be executed.
+ * ConveyAssert and ConveySo allow you to run assertions.
*/
-#define So(condition) test_so(condition)
+#define ConveyAssert(truth) \
+ do { \
+ if (!(truth)) { \
+ conveyAssertFail(#truth, __FILE__, __LINE__); \
+ } else { \
+ conveyAssertPass(#truth, __FILE__, __LINE__); \
+ } \
+ } while (0)
+
+#define ConveySo(truth) ConveyAssert(truth)
/*
- * Skip ceases further processing the current scope (Convey). The
- * reason is a string that will be emitted to the log.
+ * Convey(name, <code>) starts a convey context, with <code> as
+ * the body. The <code> is its scope, and may be called repeatedly
+ * within the body of a loop.
*/
-#define Skip(reason) test_skip(reason)
+#define Convey(name, code) conveyRun(name, code, NULL)
/*
- * Fail records a test failure, and is much like So, except that
- * no condition is recorded, and instead you may supply your own
- * reason.
+ * ConveySkip() just stops processing of the rest of the current context,
+ * and records that processing was skipped.
*/
-#define Fail(reason) test_fail(reason)
+/*
+ * If your preprocessor doesn't understand C99 variadics, indicate it
+ * with CONVEY_NO_VARIADICS. In that case you lose support for printf-style
+ * format specifiers.
+ */
+#ifdef CONVEY_NO_VARIADICS
+#define ConveySkip(reason) conveySkip(__FILE__, __LINE__, reason)
+#define ConveyFail(reason) conveyFail(__FILE__, __LINE__, reason)
+#define ConveyError(reason) conveyError(__FILE__, __LINE__, reason)
+#define ConveyPrintf(reason) conveyPrintf(__FILE__, __LINE__, reason)
+#else
+#define ConveySkip(...) conveySkip(__FILE__, __LINE__, __VA_ARGS__)
+#define ConveyFail(...) conveyFail(__FILE__, __LINE__, __VA_ARGS__)
+#define ConveyError(...) conveyError(__FILE__, __LINE__, __VA_ARGS__)
+#define ConveyPrintf(...) conveyPrintf(__FILE__, __LINE__, __VA_ARGS__)
+#endif
/*
- * SkipSo is a way to skip a check. The fact that it was skipped
- * will be noted.
+ * ConveySkipSo() is used to skip processing of a single assertion.
+ * Further processing in the same context continues.
*/
-#define SkipSo(condition) test_skip_so(condition)
+#define ConveySkipAssert(truth) \
+ conveyAssertSkip(truth, __FILE__, __LINE__)
+#define ConveySkipSo(truth) ConveySkipAssert(truth)
/*
- * SkipConvey is a way to skip an entire Convey scope. The fact
- * will be noted.
+ * ConveySkipConvey() is used to skip a convey context. This is intended
+ * to permit changing "Convey", to "SkipConvey". This is logged,
+ * and the current convey context continues processing.
*/
-#define SkipConvey(name, code) test_skip_convey(name, code)
+#define ConveySkipConvey(name, code) \
+ Convey(name, ConveySkip("Skipped"))
/*
- * Reset is establishes a block of code to be reset when exiting from
- * Convey blocks, or even when finishing the current scope. It only
- * affects the following code, and it is possible to override a prior
- * Reset block with a new one in the same scope. Unlike with GoConvey,
- * you must put this *before* other Convey blocks you wish to cover.
+ * ConveyInit sets up initial things required for testing. If you don't
+ * use ConveyMain(), then you need to call this somewhere early in your
+ * main routine. If it returns non-zero, then you can't use the framework.
*/
-#define Reset(code) test_reset(code)
+extern int ConveyInit(void);
/*
- * Printf is like printf, but it sends its output to the test debug
- * log, which is emitted only after the test is finished. The system
- * injects events in the debug log as well, which makes this useful for
- * debugging flow of execution.
+ * ConveySetVerbose sets verbose mode. You shouldn't set this normally,
+ * as the main() wrapper looks at argv, and does if -v is supplied.
+ */
+extern void ConveySetVerbose(void);
+
+/*
+ * These are some public macros intended to make the API more friendly.
+ * The user is welcome to #undefine any of these he wishes not to
+ * use, or he can simply avoid the pollution altogether by defining
+ * CONVEY_NAMESPACE_CLEAN before including this header file. Any
+ * of these names are already defined using the Convey prefix, with
+ * the sole exception of Convey() itself, which you cannot undefine.
+ * (We don't define a ConveyConvey()... that's just silly.) Most of the
+ * time you won't need this, because its test code that you control, and
+ * you're writing to Convey(), so you can trivially avoid the conflicts and
+ * benefit from the friendlier names. This is why this is the default.
*
- * NB: We avoid variadic macros since some systems don't support them.
+ * There are some other less often used functions that we haven't aliased,
+ * like ConveyBlock() and ConveySetVerbose(). Aliases for those offer
+ * little benefit for the extra pollution they would create.
*/
-#define Printf test_debugf
+#ifndef CONVEY_NAMESPACE_CLEAN
+
+#define TestMain ConveyTestMain
+#define Test ConveyTest
+#define Main ConveyMain
+#define So ConveySo
+#define Skip ConveySkip
+#define Fail ConveyFail
+#define Error ConveyError
+#define SkipConvey ConveySkipConvey
+#define SkipSo ConveySkipSo
+#define Reset ConveyReset
+#define Printf ConveyPrintf
+#endif /* CONVEY_NAMESPACE_CLEAN */
-#endif /* TEST_CONVEY_H */
+#endif /* CONVEY_H */