From 02898b90c344e59782533a7b1b71120663690c5d Mon Sep 17 00:00:00 2001 From: Ueli Niederer Date: Sat, 7 Mar 2020 14:34:10 +0100 Subject: [PATCH] Add setup and teardown hooks per test suite --- AllTests.c | 2 + CuTest.c | 55 ++++++++-- CuTest.h | 13 ++- CuTestTest.c | 279 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 342 insertions(+), 7 deletions(-) diff --git a/AllTests.c b/AllTests.c index b5d901d..929bb4a 100644 --- a/AllTests.c +++ b/AllTests.c @@ -4,6 +4,7 @@ CuSuite* CuGetSuite(void); CuSuite* CuStringGetSuite(void); +CuSuite* CuSuiteFrameGetSuite(void); int RunAllTests(void) { @@ -12,6 +13,7 @@ int RunAllTests(void) CuSuiteAddSuite(suite, CuGetSuite()); CuSuiteAddSuite(suite, CuStringGetSuite()); + CuSuiteAddSuite(suite, CuSuiteFrameGetSuite()); CuSuiteRun(suite); CuSuiteSummary(suite, output); diff --git a/CuTest.c b/CuTest.c index da6d180..dfd3587 100644 --- a/CuTest.c +++ b/CuTest.c @@ -134,18 +134,22 @@ void CuTestDelete(CuTest *t) free(t); } -void CuTestRun(CuTest* tc) -{ +static void TestFunctionRun(CuTest *tc, TestFunction function) { jmp_buf buf; tc->jumpBuf = &buf; if (setjmp(buf) == 0) { tc->ran = 1; - (tc->function)(tc); + function(tc); } tc->jumpBuf = 0; } +void CuTestRun(CuTest* tc) +{ + TestFunctionRun(tc, tc->function); +} + static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* string) { char buf[HUGE_STRING_LEN]; @@ -233,22 +237,52 @@ void CuAssertPtrEquals_LineMsg(CuTest* tc, const char* file, int line, const cha CuFail_Line(tc, file, line, message, buf); } +void *CuTestContextGet(CuTest *tc) { + return tc->context; +} + +void CuTestContextSet(CuTest *tc, void *context) { + tc->context = context; +} /*-------------------------------------------------------------------------* * CuSuite *-------------------------------------------------------------------------*/ +static void EmptySetup(CuTest *tc) { + (void)tc; +} + +static void EmptyTeardown(CuTest *tc) { + (void)tc; +} + +static const CuTestFrame EmptyFrame = { + .setup = EmptySetup, + .teardown = EmptyTeardown, +}; void CuSuiteInit(CuSuite* testSuite) +{ + CuSuiteInitWithFrame(testSuite, &EmptyFrame, NULL); +} + +void CuSuiteInitWithFrame(CuSuite* testSuite, const CuTestFrame *frame, void *frameContext) { testSuite->count = 0; testSuite->failCount = 0; - memset(testSuite->list, 0, sizeof(testSuite->list)); + memset(testSuite->list, 0, sizeof(testSuite->list)); + testSuite->frame = frame; + testSuite->frameContext = frameContext; } CuSuite* CuSuiteNew(void) { + return CuSuiteNewWithFrame(&EmptyFrame, NULL); +} + +CuSuite* CuSuiteNewWithFrame(const CuTestFrame *frame, void *frameContext) { CuSuite* testSuite = CU_ALLOC(CuSuite); - CuSuiteInit(testSuite); + CuSuiteInitWithFrame(testSuite, frame, frameContext); return testSuite; } @@ -285,11 +319,20 @@ void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2) void CuSuiteRun(CuSuite* testSuite) { + const CuTestFrame * const frame = testSuite->frame; int i; + for (i = 0 ; i < testSuite->count ; ++i) { CuTest* testCase = testSuite->list[i]; - CuTestRun(testCase); + testCase->context = testSuite->frameContext; + + TestFunctionRun(testCase, frame->setup); + if (!testCase->failed) { + CuTestRun(testCase); + TestFunctionRun(testCase, frame->teardown); + } + testSuite->frameContext = testCase->context; if (testCase->failed) { testSuite->failCount += 1; } } } diff --git a/CuTest.h b/CuTest.h index 4d08be3..f01a0ac 100644 --- a/CuTest.h +++ b/CuTest.h @@ -48,12 +48,15 @@ struct CuTest int ran; CuString *message; jmp_buf *jumpBuf; + void *context; }; void CuTestInit(CuTest* t, const char* name, TestFunction function); CuTest* CuTestNew(const char* name, TestFunction function); void CuTestRun(CuTest* tc); void CuTestDelete(CuTest *t); +void *CuTestContextGet(CuTest *tc); +void CuTestContextSet(CuTest *tc, void *context); /* Internal versions of assert functions -- use the public versions */ void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message); @@ -95,17 +98,25 @@ void CuAssertPtrEquals_LineMsg(CuTest* tc, #define SUITE_ADD_TEST(SUITE,TEST) CuSuiteAdd(SUITE, CuTestNew(#TEST, TEST)) +typedef struct CuTestFrame { + void (*setup)(CuTest *tc); + void (*teardown)(CuTest *tc); +}CuTestFrame; + typedef struct { int count; CuTest* list[MAX_TEST_CASES]; int failCount; + const CuTestFrame *frame; + void *frameContext; } CuSuite; - void CuSuiteInit(CuSuite* testSuite); +void CuSuiteInitWithFrame(CuSuite* testSuite, const CuTestFrame *frame, void *frameContext); CuSuite* CuSuiteNew(void); +CuSuite* CuSuiteNewWithFrame(const CuTestFrame *frame, void *frameContext); void CuSuiteDelete(CuSuite *testSuite); void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase); void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2); diff --git a/CuTestTest.c b/CuTestTest.c index 1911131..6c8be5f 100644 --- a/CuTestTest.c +++ b/CuTestTest.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "CuTest.h" @@ -707,3 +708,281 @@ CuSuite* CuGetSuite(void) return suite; } + +typedef enum TestPhase { + UNKNOWN = 0, + SETUP, + TEST, + TEARDOWN +}TestPhase; + +#define TEST_PHASE_RECORDER_LEN 50 + +typedef struct TestPhaseRecorder { + TestPhase TestSequence[TEST_PHASE_RECORDER_LEN]; + size_t pos; +}TestPhaseTracker; + +static void TestPhaseRecord(TestPhaseTracker *tracker, TestPhase phase) { + if (tracker->pos < TEST_PHASE_RECORDER_LEN) { + tracker->TestSequence[tracker->pos++] = phase; + } +} + +static void TestPhaseNoMoreEventsFrom(CuTest *tc, TestPhaseTracker *tracker, size_t pos) { + for (size_t i = pos;i < TEST_PHASE_RECORDER_LEN;i++) { + CuAssertIntEquals(tc, UNKNOWN, tracker->TestSequence[i]); + } +} + +static struct TestMockData { + TestPhaseTracker tracker; + + void *ContextPassed; + + bool setupShallFail; + bool teardownShallFail; + bool testShallFail; + + bool setupContinuedAfterAssert; + bool testContinuedAfterAssert; + bool teardownContinuedAfterAssert; +}TestMockData; + +static void TestMockDataInit(void) { + memset(&TestMockData, 0, sizeof(TestMockData)); +} + +static void FrameMockSetup(CuTest *tc) { + bool fail = TestMockData.setupShallFail; + TestMockData.setupShallFail = false; + + TestPhaseRecord(&TestMockData.tracker, SETUP); + + CuAssert(tc, "Have to fail", !fail); + TestMockData.setupContinuedAfterAssert = true; +} + +static void FrameMockTest(CuTest *tc) { + bool fail = TestMockData.testShallFail; + TestMockData.testShallFail = false; + + TestPhaseRecord(&TestMockData.tracker, TEST); + TestMockData.ContextPassed = CuTestContextGet(tc); + + CuAssert(tc, "Have to fail", !fail); + TestMockData.testContinuedAfterAssert = true; +} + +static void FrameMockTearDown(CuTest *tc) { + bool fail = TestMockData.teardownShallFail; + TestMockData.teardownShallFail = false; + + TestPhaseRecord(&TestMockData.tracker, TEARDOWN); + + CuAssert(tc, "Have to fail", !fail); + TestMockData.teardownContinuedAfterAssert = true; +} + +static const CuTestFrame FrameMock = { + .setup = FrameMockSetup, + .teardown = FrameMockTearDown, +}; + +static void TestSuiteWithFrameInit(CuTest *tc) { + CuSuite ts; + int context = 0; + CuSuiteInitWithFrame(&ts, &FrameMock, &context); + CuAssertPtrEquals(tc, (void *)&FrameMock, (void *)ts.frame); + CuAssertPtrEquals(tc, &context, ts.frameContext); +} + +static void TestSuiteWithFrameNew(CuTest *tc) { + int context = 0; + CuSuite* ts = CuSuiteNewWithFrame(&FrameMock, &context); + CuAssertPtrEquals(tc, (void *)&FrameMock, (void *)ts->frame); + CuAssertPtrEquals(tc, &context, ts->frameContext); +} + +static void TestSuiteRunsSetupTestTeardown(CuTest *tc) { + int context = 0; + TestMockDataInit(); + CuSuite* uut = CuSuiteNewWithFrame(&FrameMock, &context); + + SUITE_ADD_TEST(uut, FrameMockTest); + + CuSuiteRun(uut); + + TestPhaseTracker *tracker = &TestMockData.tracker; + CuAssertIntEquals(tc, SETUP, tracker->TestSequence[0]); + CuAssertIntEquals(tc, TEST, tracker->TestSequence[1]); + CuAssertIntEquals(tc, TEARDOWN, tracker->TestSequence[2]); + TestPhaseNoMoreEventsFrom(tc, tracker, 3); +} + +static void TestSuiteTestFailsIfSetupFails(CuTest *tc) { + int context = 0; + TestMockDataInit(); + CuSuite* uut = CuSuiteNewWithFrame(&FrameMock, &context); + + SUITE_ADD_TEST(uut, FrameMockTest); + SUITE_ADD_TEST(uut, TestPasses); + + TestMockData.setupShallFail = true; + + CuSuiteRun(uut); + + CuAssertIntEquals(tc, 2, uut->count); + CuAssertIntEquals(tc, 1, uut->failCount); + + TestPhaseTracker *tracker = &TestMockData.tracker; + CuAssertIntEquals(tc, SETUP, tracker->TestSequence[0]); + CuAssertIntEquals(tc, SETUP, tracker->TestSequence[1]); + CuAssertIntEquals(tc, TEARDOWN, tracker->TestSequence[2]); + TestPhaseNoMoreEventsFrom(tc, tracker, 3); +} + +static void TestSuiteTestFailsIfTeardownFails(CuTest *tc) { + int context = 0; + TestMockDataInit(); + CuSuite* uut = CuSuiteNewWithFrame(&FrameMock, &context); + + SUITE_ADD_TEST(uut, FrameMockTest); + + TestMockData.teardownShallFail = true; + + CuSuiteRun(uut); + + CuAssertIntEquals(tc, 1, uut->count); + CuAssertIntEquals(tc, 1, uut->failCount); +} + +static void TestSuiteTeardownIsExecutedIfTestFails(CuTest *tc) { + int context = 0; + TestMockDataInit(); + CuSuite* uut = CuSuiteNewWithFrame(&FrameMock, &context); + + SUITE_ADD_TEST(uut, FrameMockTest); + + TestMockData.testShallFail = true; + + CuSuiteRun(uut); + + CuAssertIntEquals(tc, 1, uut->count); + CuAssertIntEquals(tc, 1, uut->failCount); + + TestPhaseTracker *tracker = &TestMockData.tracker; + CuAssertIntEquals(tc, SETUP, tracker->TestSequence[0]); + CuAssertIntEquals(tc, TEST, tracker->TestSequence[1]); + CuAssertIntEquals(tc, TEARDOWN, tracker->TestSequence[2]); + TestPhaseNoMoreEventsFrom(tc, tracker, 3); +} + +static void TestSuiteContextPassedToCuTest(CuTest *tc) { + int context = 0; + TestMockDataInit(); + CuSuite* uut = CuSuiteNewWithFrame(&FrameMock, &context); + + SUITE_ADD_TEST(uut, FrameMockTest); + + CuSuiteRun(uut); + + CuAssertPtrEquals(tc, &context, TestMockData.ContextPassed); +} + +static void TestSuiteSetupInterruptsUponFailedAssert(CuTest *tc) { + int context = 0; + TestMockDataInit(); + CuSuite* uut = CuSuiteNewWithFrame(&FrameMock, &context); + TestMockData.setupShallFail = true; + SUITE_ADD_TEST(uut, FrameMockTest); + + CuSuiteRun(uut); + + CuAssert(tc, "Setup did continue", !TestMockData.setupContinuedAfterAssert); +} + +static void TestSuiteTestInterruptsUponFailedAssert(CuTest *tc) { + int context = 0; + TestMockDataInit(); + CuSuite* uut = CuSuiteNewWithFrame(&FrameMock, &context); + TestMockData.testShallFail = true; + SUITE_ADD_TEST(uut, FrameMockTest); + + CuSuiteRun(uut); + + CuAssert(tc, "Test did continue", !TestMockData.testContinuedAfterAssert); +} + +static void TestSuiteTeardownInterruptsUponFailedAssert(CuTest *tc) { + int context = 0; + TestMockDataInit(); + CuSuite* uut = CuSuiteNewWithFrame(&FrameMock, &context); + TestMockData.teardownShallFail = true; + SUITE_ADD_TEST(uut, FrameMockTest); + + CuSuiteRun(uut); + + CuAssert(tc, "Teardown did continue", !TestMockData.teardownContinuedAfterAssert); +} + +static void *TheSetupContext; +static void *TheTestContext; +static void *TheTeardownContext; + +static void FrameContextSetup(CuTest *tc) { + TheSetupContext = CuTestContextGet(tc); + CuTestContextSet(tc, &TheSetupContext); +} + +static void FrameContextTest(CuTest *tc) { + TheTestContext = CuTestContextGet(tc); + CuTestContextSet(tc, &TheTestContext); +} + +static void FrameContextTearDown(CuTest *tc) { + TheTeardownContext = CuTestContextGet(tc); + CuTestContextSet(tc, &TheTeardownContext); +} + +static const CuTestFrame FrameContextMock = { + .setup = FrameContextSetup, + .teardown = FrameContextTearDown, +}; + +static void TestSuiteSetupTestTeardownWithContext(CuTest *tc) { + int context = 0; + + TheSetupContext = NULL; + TheTestContext = NULL; + TheTeardownContext = NULL; + + CuSuite* uut = CuSuiteNewWithFrame(&FrameContextMock, &context); + + SUITE_ADD_TEST(uut, FrameContextTest); + + CuSuiteRun(uut); + + CuAssertPtrEquals(tc, &context, TheSetupContext); + CuAssertPtrEquals(tc, &TheSetupContext, TheTestContext); + CuAssertPtrEquals(tc, &TheTestContext, TheTeardownContext); + CuAssertPtrEquals(tc, &TheTeardownContext, uut->frameContext); +} + +CuSuite* CuSuiteFrameGetSuite(void) { + CuSuite* suite = CuSuiteNew(); + + SUITE_ADD_TEST(suite, TestSuiteWithFrameInit); + SUITE_ADD_TEST(suite, TestSuiteWithFrameNew); + SUITE_ADD_TEST(suite, TestSuiteRunsSetupTestTeardown); + SUITE_ADD_TEST(suite, TestSuiteTestFailsIfSetupFails); + SUITE_ADD_TEST(suite, TestSuiteTestFailsIfTeardownFails); + SUITE_ADD_TEST(suite, TestSuiteTeardownIsExecutedIfTestFails); + SUITE_ADD_TEST(suite, TestSuiteContextPassedToCuTest); + SUITE_ADD_TEST(suite, TestSuiteSetupInterruptsUponFailedAssert); + SUITE_ADD_TEST(suite, TestSuiteTestInterruptsUponFailedAssert); + SUITE_ADD_TEST(suite, TestSuiteTeardownInterruptsUponFailedAssert); + SUITE_ADD_TEST(suite, TestSuiteSetupTestTeardownWithContext); + + return suite; +}