diff --git a/AllTests.c b/AllTests.c index b5d901d..4a2e197 100644 --- a/AllTests.c +++ b/AllTests.c @@ -4,6 +4,8 @@ CuSuite* CuGetSuite(void); CuSuite* CuStringGetSuite(void); +CuSuite* CuSuiteFrameGetSuite(void); +CuSuite* CuSuiteChainGetSuite(void); int RunAllTests(void) { @@ -12,6 +14,8 @@ int RunAllTests(void) CuSuiteAddSuite(suite, CuGetSuite()); CuSuiteAddSuite(suite, CuStringGetSuite()); + CuSuiteAddSuite(suite, CuSuiteFrameGetSuite()); + CuSuiteAddSuite(suite, CuSuiteChainGetSuite()); CuSuiteRun(suite); CuSuiteSummary(suite, output); diff --git a/CuTest.c b/CuTest.c index da6d180..f7446b0 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]; @@ -154,9 +158,13 @@ static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* str CuStringInsert(string, buf, 0); tc->failed = 1; - free(tc->message); - tc->message = CuStringNew(); - CuStringAppend(tc->message, string->buffer); + if (NULL != tc->message) { + CuStringAppend(tc->message, "\n"); + } + else { + tc->message = CuStringNew(); + } + CuStringAppend(tc->message, string->buffer); if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0); } @@ -233,22 +241,53 @@ 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; + testSuite->next = NULL; } 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; } @@ -275,68 +314,100 @@ void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase) void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2) { - int i; - for (i = 0 ; i < testSuite2->count ; ++i) - { - CuTest* testCase = testSuite2->list[i]; - CuSuiteAdd(testSuite, testCase); + CuSuite *cursor = testSuite; + while (NULL != cursor->next) { + cursor = cursor->next; } + cursor->next = testSuite2; + testSuite2->next = NULL; } void CuSuiteRun(CuSuite* testSuite) { - int i; - for (i = 0 ; i < testSuite->count ; ++i) - { - CuTest* testCase = testSuite->list[i]; - CuTestRun(testCase); - if (testCase->failed) { testSuite->failCount += 1; } + while (NULL != testSuite) { + const CuTestFrame * const frame = testSuite->frame; + int i; + + for (i = 0 ; i < testSuite->count ; ++i) + { + CuTest* testCase = testSuite->list[i]; + 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; } + } + + testSuite = testSuite->next; } } void CuSuiteSummary(CuSuite* testSuite, CuString* summary) { - int i; - for (i = 0 ; i < testSuite->count ; ++i) - { - CuTest* testCase = testSuite->list[i]; - CuStringAppend(summary, testCase->failed ? "F" : "."); + while (NULL != testSuite) { + int i; + for (i = 0 ; i < testSuite->count ; ++i) + { + CuTest* testCase = testSuite->list[i]; + CuStringAppend(summary, testCase->failed ? "F" : "."); + } + testSuite = testSuite->next; } CuStringAppend(summary, "\n\n"); } void CuSuiteDetails(CuSuite* testSuite, CuString* details) { - int i; int failCount = 0; + int testCount = 0; + CuString epilogue; + const CuSuite *cursor = testSuite; - if (testSuite->failCount == 0) - { - int passCount = testSuite->count - testSuite->failCount; - const char* testWord = passCount == 1 ? "test" : "tests"; - CuStringAppendFormat(details, "OK (%d %s)\n", passCount, testWord); - } - else - { - if (testSuite->failCount == 1) - CuStringAppend(details, "There was 1 failure:\n"); - else - CuStringAppendFormat(details, "There were %d failures:\n", testSuite->failCount); + CuStringInit(&epilogue); - for (i = 0 ; i < testSuite->count ; ++i) + while (NULL != cursor) { + testCount += cursor->count; + + int i; + for (i = 0 ; i < cursor->count ; ++i) { - CuTest* testCase = testSuite->list[i]; + CuTest* testCase = cursor->list[i]; if (testCase->failed) { failCount++; - CuStringAppendFormat(details, "%d) %s: %s\n", - failCount, testCase->name, testCase->message->buffer); + CuStringAppendFormat(&epilogue, "%d) %s: %s\n", + failCount, testCase->name, testCase->message->buffer); } } - CuStringAppend(details, "\n!!!FAILURES!!!\n"); - CuStringAppendFormat(details, "Runs: %d ", testSuite->count); - CuStringAppendFormat(details, "Passes: %d ", testSuite->count - testSuite->failCount); - CuStringAppendFormat(details, "Fails: %d\n", testSuite->failCount); + cursor = cursor->next; + } + + { + int passCount = testCount - failCount; + + if (failCount == 0) + { + const char* testWord = passCount == 1 ? "test" : "tests"; + CuStringAppendFormat(details, "OK (%d %s)\n", passCount, testWord); + } + else + { + if (failCount == 1) + CuStringAppend(details, "There was 1 failure:\n"); + else + CuStringAppendFormat(details, "There were %d failures:\n", failCount); + + CuStringAppend(details, epilogue.buffer); + + CuStringAppend(details, "\n!!!FAILURES!!!\n"); + CuStringAppendFormat(details, "Runs: %d ", testCount); + CuStringAppendFormat(details, "Passes: %d ", passCount); + CuStringAppendFormat(details, "Fails: %d\n", failCount); + } } } diff --git a/CuTest.h b/CuTest.h index 4d08be3..80f9d53 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,27 @@ void CuAssertPtrEquals_LineMsg(CuTest* tc, #define SUITE_ADD_TEST(SUITE,TEST) CuSuiteAdd(SUITE, CuTestNew(#TEST, TEST)) -typedef struct +typedef struct CuTestFrame { + void (*setup)(CuTest *tc); + void (*teardown)(CuTest *tc); +}CuTestFrame; + +typedef struct CuSuite { int count; CuTest* list[MAX_TEST_CASES]; int failCount; -} CuSuite; + const CuTestFrame *frame; + void *frameContext; + struct CuSuite *next; +} 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..b4c00a7 100644 --- a/CuTestTest.c +++ b/CuTestTest.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "CuTest.h" @@ -169,24 +170,30 @@ void TestCuTestInit(CuTest *tc) void TestCuAssert(CuTest* tc) { + CuTest tc1; + CuTestInit(&tc1, "MyTest", TestPasses); + CuAssert(&tc1, "test 1", 5 == 4 + 1); + CuAssertTrue(tc, !tc1.failed); + CuAssertTrue(tc, tc1.message == NULL); + CuTest tc2; CuTestInit(&tc2, "MyTest", TestPasses); - - CuAssert(&tc2, "test 1", 5 == 4 + 1); - CuAssertTrue(tc, !tc2.failed); - CuAssertTrue(tc, tc2.message == NULL); - CuAssert(&tc2, "test 2", 0); CuAssertTrue(tc, tc2.failed); CompareAsserts(tc, "CuAssert didn't fail", "test 2", tc2.message); - CuAssert(&tc2, "test 3", 1); - CuAssertTrue(tc, tc2.failed); - CompareAsserts(tc, "CuAssert didn't fail", "test 2", tc2.message); + CuTest tc3; + CuTestInit(&tc3, "MyTest", TestPasses); + CuAssert(&tc3, "test 2", 0); + CuAssert(&tc3, "test 3", 1); + CuAssertTrue(tc, tc3.failed); + CompareAsserts(tc, "CuAssert didn't fail", "test 2", tc3.message); - CuAssert(&tc2, "test 4", 0); - CuAssertTrue(tc, tc2.failed); - CompareAsserts(tc, "CuAssert didn't fail", "test 4", tc2.message); + CuTest tc4; + CuTestInit(&tc4, "MyTest", TestPasses); + CuAssert(&tc4, "test 4", 0); + CuAssertTrue(tc, tc4.failed); + CompareAsserts(tc, "CuAssert didn't fail", "test 4", tc4.message); } @@ -266,6 +273,7 @@ void TestCuSuiteInit(CuTest* tc) CuSuiteInit(&ts); CuAssertTrue(tc, ts.count == 0); CuAssertTrue(tc, ts.failCount == 0); + CuAssertPtrEquals(tc, NULL, ts.next); } void TestCuSuiteNew(CuTest* tc) @@ -273,6 +281,7 @@ void TestCuSuiteNew(CuTest* tc) CuSuite* ts = CuSuiteNew(); CuAssertTrue(tc, ts->count == 0); CuAssertTrue(tc, ts->failCount == 0); + CuAssertPtrEquals(tc, NULL, ts->next); } void TestCuSuiteAddTest(CuTest* tc) @@ -301,12 +310,14 @@ void TestCuSuiteAddSuite(CuTest* tc) CuSuiteAdd(ts2, CuTestNew("TestFails4", zTestFails)); CuSuiteAddSuite(ts1, ts2); - CuAssertIntEquals(tc, 4, ts1->count); + CuAssertIntEquals(tc, 2, ts1->count); + CuAssertIntEquals(tc, 2, ts2->count); + CuAssertPtrEquals(tc, ts2, ts1->next); CuAssertStrEquals(tc, "TestFails1", ts1->list[0]->name); CuAssertStrEquals(tc, "TestFails2", ts1->list[1]->name); - CuAssertStrEquals(tc, "TestFails3", ts1->list[2]->name); - CuAssertStrEquals(tc, "TestFails4", ts1->list[3]->name); + CuAssertStrEquals(tc, "TestFails3", ts2->list[0]->name); + CuAssertStrEquals(tc, "TestFails4", ts2->list[1]->name); } void TestCuSuiteRun(CuTest* tc) @@ -523,18 +534,21 @@ void TestFail(CuTest* tc) void TestAssertStrEquals(CuTest* tc) { jmp_buf buf; - CuTest *tc2 = CuTestNew("TestAssertStrEquals", zTestFails); const char* expected = "expected but was "; const char *expectedMsg = "some text: expected but was "; - tc2->jumpBuf = &buf; + CuTest *tc1 = CuTestNew("TestAssertStrEquals", zTestFails); + tc1->jumpBuf = &buf; if (setjmp(buf) == 0) { - CuAssertStrEquals(tc2, "hello", "world"); + CuAssertStrEquals(tc1, "hello", "world"); } - CuAssertTrue(tc, tc2->failed); - CompareAsserts(tc, "CuAssertStrEquals failed", expected, tc2->message); + CuAssertTrue(tc, tc1->failed); + CompareAsserts(tc, "CuAssertStrEquals failed", expected, tc1->message); + + CuTest *tc2 = CuTestNew("TestAssertStrEquals", zTestFails); + tc2->jumpBuf = &buf; if (setjmp(buf) == 0) { CuAssertStrEquals_Msg(tc2, "some text", "hello", "world"); @@ -566,18 +580,21 @@ void TestAssertStrEquals_NULL(CuTest* tc) void TestAssertStrEquals_FailNULLStr(CuTest* tc) { jmp_buf buf; - CuTest *tc2 = CuTestNew("TestAssertStrEquals_FailNULLStr", zTestFails); const char* expected = "expected but was "; const char *expectedMsg = "some text: expected but was "; - tc2->jumpBuf = &buf; + CuTest *tc1 = CuTestNew("TestAssertStrEquals_FailNULLStr", zTestFails); + tc1->jumpBuf = &buf; if (setjmp(buf) == 0) { - CuAssertStrEquals(tc2, "hello", NULL); + CuAssertStrEquals(tc1, "hello", NULL); } - CuAssertTrue(tc, tc2->failed); - CompareAsserts(tc, "CuAssertStrEquals_FailNULLStr failed", expected, tc2->message); + CuAssertTrue(tc, tc1->failed); + CompareAsserts(tc, "CuAssertStrEquals_FailNULLStr failed", expected, tc1->message); + + CuTest *tc2 = CuTestNew("TestAssertStrEquals_FailNULLStr", zTestFails); + tc2->jumpBuf = &buf; if (setjmp(buf) == 0) { CuAssertStrEquals_Msg(tc2, "some text", "hello", NULL); @@ -589,18 +606,20 @@ void TestAssertStrEquals_FailNULLStr(CuTest* tc) void TestAssertStrEquals_FailStrNULL(CuTest* tc) { jmp_buf buf; - CuTest *tc2 = CuTestNew("TestAssertStrEquals_FailStrNULL", zTestFails); - const char* expected = "expected but was "; const char *expectedMsg = "some text: expected but was "; - tc2->jumpBuf = &buf; + CuTest *tc1 = CuTestNew("TestAssertStrEquals_FailStrNULL", zTestFails); + tc1->jumpBuf = &buf; if (setjmp(buf) == 0) { - CuAssertStrEquals(tc2, NULL, "hello"); + CuAssertStrEquals(tc1, NULL, "hello"); } - CuAssertTrue(tc, tc2->failed); - CompareAsserts(tc, "CuAssertStrEquals_FailStrNULL failed", expected, tc2->message); + CuAssertTrue(tc, tc1->failed); + CompareAsserts(tc, "CuAssertStrEquals_FailStrNULL failed", expected, tc1->message); + + CuTest *tc2 = CuTestNew("TestAssertStrEquals_FailStrNULL", zTestFails); + tc2->jumpBuf = &buf; if (setjmp(buf) == 0) { CuAssertStrEquals_Msg(tc2, "some text", NULL, "hello"); @@ -612,16 +631,19 @@ void TestAssertStrEquals_FailStrNULL(CuTest* tc) void TestAssertIntEquals(CuTest* tc) { jmp_buf buf; - CuTest *tc2 = CuTestNew("TestAssertIntEquals", zTestFails); + CuTest *tc1 = CuTestNew("TestAssertIntEquals", zTestFails); const char* expected = "expected <42> but was <32>"; const char* expectedMsg = "some text: expected <42> but was <32>"; - tc2->jumpBuf = &buf; + tc1->jumpBuf = &buf; if (setjmp(buf) == 0) { - CuAssertIntEquals(tc2, 42, 32); + CuAssertIntEquals(tc1, 42, 32); } - CuAssertTrue(tc, tc2->failed); - CompareAsserts(tc, "CuAssertIntEquals failed", expected, tc2->message); + CuAssertTrue(tc, tc1->failed); + CompareAsserts(tc, "CuAssertIntEquals failed", expected, tc1->message); + + CuTest *tc2 = CuTestNew("TestAssertIntEquals", zTestFails); + tc2->jumpBuf = &buf; if (setjmp(buf) == 0) { CuAssertIntEquals_Msg(tc2, "some text", 42, 32); @@ -658,13 +680,15 @@ void TestAssertDblEquals(CuTest* tc) } CuAssertTrue(tc, tc2->failed); CompareAsserts(tc, "CuAssertDblEquals failed", expected, tc2->message); - tc2->jumpBuf = &buf; + + CuTest *tc3 = CuTestNew("TestAssertDblEquals", zTestFails); + tc3->jumpBuf = &buf; if (setjmp(buf) == 0) { - CuAssertDblEquals_Msg(tc2, "some text", x, y, 0.001); + CuAssertDblEquals_Msg(tc3, "some text", x, y, 0.001); } - CuAssertTrue(tc, tc2->failed); - CompareAsserts(tc, "CuAssertDblEquals failed", expectedMsg, tc2->message); + CuAssertTrue(tc, tc3->failed); + CompareAsserts(tc, "CuAssertDblEquals failed", expectedMsg, tc3->message); } /*-------------------------------------------------------------------------* @@ -707,3 +731,413 @@ 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, "Setup has 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, "Test has to fail", !fail); + TestMockData.testContinuedAfterAssert = true; +} + +static void FrameMockTearDown(CuTest *tc) { + bool fail = TestMockData.teardownShallFail; + TestMockData.teardownShallFail = false; + + TestPhaseRecord(&TestMockData.tracker, TEARDOWN); + + CuAssert(tc, "Teardown has 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 TestSuiteFailingTestMessageIsPreservedWhenTeardownFails(CuTest *tc) { + int context = 0; + TestMockDataInit(); + CuSuite* uut = CuSuiteNewWithFrame(&FrameMock, &context); + + SUITE_ADD_TEST(uut, FrameMockTest); + + TestMockData.testShallFail = true; + TestMockData.teardownShallFail = true; + + CuSuiteRun(uut); + + char *message = uut->list[0]->message->buffer; + char *testFailMsg = strstr(message, "Test has to fail\n"); + char *teardownFailMsg = strstr(message, "Teardown has to fail"); + + CuAssertPtrNotNullMsg(tc, "Test fail message not found", testFailMsg); + CuAssertPtrNotNullMsg(tc, "Teardown fail message not found", teardownFailMsg); + CuAssert(tc, "Message order correct", testFailMsg < teardownFailMsg); +} + +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); +} + +static void TestSuitesSetupTestTeardownWithTwoSeparateContexts(CuTest *tc) { + int context = 0; + + TheSetupContext = NULL; + TheTestContext = NULL; + TheTeardownContext = NULL; + + CuSuite* uut = CuSuiteNew(); + CuSuite* uut2 = CuSuiteNewWithFrame(&FrameContextMock, &context); + + SUITE_ADD_TEST(uut2, FrameContextTest); + + CuSuiteAddSuite(uut, uut2); + + CuSuiteRun(uut); + + CuAssertPtrEquals(tc, &context, TheSetupContext); +} + +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, TestSuiteFailingTestMessageIsPreservedWhenTeardownFails); + 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); + SUITE_ADD_TEST(suite, TestSuitesSetupTestTeardownWithTwoSeparateContexts); + + return suite; +} + +static void TestSuiteChainSetup(CuTest *tc) { + CuSuite *uut = CuSuiteNew(); + + CuSuite *uut2 = CuSuiteNew(); + SUITE_ADD_TEST(uut2, TestPasses); + SUITE_ADD_TEST(uut2, TestPasses); + + CuSuite *uut3 = CuSuiteNew(); + SUITE_ADD_TEST(uut3, zTestFails); + SUITE_ADD_TEST(uut3, TestPasses); + + CuSuite *uut4 = CuSuiteNew(); + SUITE_ADD_TEST(uut4, TestPasses); + + CuSuiteAddSuite(uut, uut2); + CuSuiteAddSuite(uut, uut3); + CuSuiteAddSuite(uut, uut4); + + CuSuiteRun(uut); + + CuTestContextSet(tc, uut); +} + +static void TestSuiteChainTeardown(CuTest *tc) { + CuSuite *uut = CuTestContextGet(tc); + CuTestContextSet(tc, NULL); + + CuSuite *toDelete = uut; + while (NULL != toDelete) { + CuSuite *next = toDelete->next; + CuSuiteDelete(toDelete); + toDelete = next; + } +} + +static const CuTestFrame TestSuiteChainFrame = { + .setup = TestSuiteChainSetup, + .teardown = TestSuiteChainTeardown, +}; + +static void TestFrameSuiteChainStatistics(CuTest *tc) { + CuSuite *uut = CuTestContextGet(tc); + CuSuite *uut2 = uut->next; + CuSuite *uut3 = uut2->next; + CuSuite *uut4 = uut3->next; + + CuAssertIntEquals(tc, 0, uut->count); + CuAssertIntEquals(tc, 0, uut->failCount); + CuAssertIntEquals(tc, 2, uut2->count); + CuAssertIntEquals(tc, 0, uut2->failCount); + CuAssertIntEquals(tc, 2, uut3->count); + CuAssertIntEquals(tc, 1, uut3->failCount); + CuAssertIntEquals(tc, 1, uut4->count); + CuAssertIntEquals(tc, 0, uut4->failCount); +} + +static void TestFrameSuiteChainSummary(CuTest *tc) { + CuSuite *uut = CuTestContextGet(tc); + + const char *expectedSummary = "..F..\n\n"; + CuString summary; + CuStringInit(&summary); + CuSuiteSummary(uut, &summary); + CuAssertStrEquals(tc, expectedSummary, summary.buffer); +} + +static void TestFrameSuiteChainDetails(CuTest *tc) { + CuSuite *uut = CuTestContextGet(tc); + + const char* expectedDetails = "There was 1 failure:\n" + "1) zTestFails: ../CuTestTest.c:143: test should fail\n\n" + "!!!FAILURES!!!\n" + "Runs: 5 Passes: 4 Fails: 1\n"; + CuString details; + CuStringInit(&details); + CuSuiteDetails(uut, &details); + CuAssertStrEquals(tc, expectedDetails, details.buffer); +} + +CuSuite* CuSuiteChainGetSuite(void) { + CuSuite* suite = CuSuiteNewWithFrame(&TestSuiteChainFrame, NULL); + + SUITE_ADD_TEST(suite, TestFrameSuiteChainStatistics); + SUITE_ADD_TEST(suite, TestFrameSuiteChainSummary); + SUITE_ADD_TEST(suite, TestFrameSuiteChainDetails); + + return suite; +} + diff --git a/README b/README index 12b9796..e15fcc5 100644 --- a/README +++ b/README @@ -154,6 +154,13 @@ to form a CuSuite. CuSuites can hold CuTests or other CuSuites. AllTests.c collects all the CuSuites in the program into a single CuSuite which it then runs as a single CuSuite. +A CuSuite also allows you to execute setup (for preparations) and +teardown (for cleanup) procedures that are shared among all tests +grouped in the respective suite. This is typically used to create +and/or setup your unit under test and simplifies the actual test +to be executed. A typical use is shown below in the section +"USING THE SETUP-TEARDOWN FACILITY". + The project is open source so feel free to take a peek under the hood at the CuTest.c file to see how it works. CuTestTest.c contains tests for CuTest.c. So CuTest tests itself. @@ -199,6 +206,91 @@ directory and generate the code to run all the tests contained in them. Using this script you don't have to worry about writing AllTests.c or dealing with any of the other suite code. +USING SETUP-TEARDOWN FACILITY +Virtually every test should be written in a setup-test-assert- +tearodwn sequence. Although test and assertion vary depending on +the concrete test cases, it's quite common, that a group of tests +have a common starting point or common needs that have to be +setup before the test is executed. Those can be grouped in a suite +where they share their setup and teardown code. + +Below is a code example showing how this can be done based on an +assumed vector arithmetics library that is supposed to be tested. + +For the demonstration it is assumed, that a Point.h & Point.c +file exist implementing some vector arithmetics: + typedef struct PointXY { + int x; + int y; + }PointXY; + + void POINT_add(PointXY *result, const PointXY *a, const PointXY *b) { + ... + } + + void POINT_sub(PointXY *result, const PointXY *a, const PointXY *b) { + ... + } + +In order to test those functions, your PointTest.c would look as follows: + #include "CuTest.h" + + static void PointSetup(CuTest *tc) { + PointXY *uut = calloc(2, sizeof PointXY); + uut[0].x = 1; + uut[0].y = -3; + + uut[1].x = 5; + uut[1].y = -7; + CuTestContextSet(tc, uut); + } + + static void PointTeardown(CuTest *tc) { + PointXY *uut = CuTestContextGet(tc); + CuTestContextSet(tc, NULL); + free(uut); + uut = NULL; + } + + static const CuTestFrame PointTestFrame = { + .setup = PointSetup, + .teardown = PointTeardown, + }; + + void TestVectorAddition(CuTest *tc) { + PointXY result; + PointXY *args = CuTestContextGet(tc); + + POINT_add(&result, &(args[0]), &(args[1])); + + CuAssertIntEquals(tc, 6, result.x); + CuAssertIntEquals(tc, -10, result.y); + } + + void TestVectorSubstract(CuTest *tc) { + PointXY result; + PointXY *args = CuTestContextGet(tc); + + POINT_sub(&result, &(args[0]), &(args[1])); + + CuAssertIntEquals(tc, -4, result.x); + CuAssertIntEquals(tc, 4, result.y); + } + + CuSuite* PointTestSuiteGet() { + CuSuite* suite = CuSuiteNewWithFrame(&PointTestFrame, NULL); + + SUITE_ADD_TEST(suite, TestVectorAddition); + SUITE_ADD_TEST(suite, TestVectorSubstract); + + return suite; + } + +The second parameter for CuSuiteNewWithFrame can be used to preset +the context to the test respectively the setup function that is called. + +A context modified during test execution is always brought back to the +suite. Hence be careful to not introduce test interdependencies. CREDITS