/* ============================================================================ Name : unittest.c Author : Version : Copyright : Your copyright notice Description : Hello World in C, Ansi-style ============================================================================ */ #include #include #include #include #include #include #include #include #include extern "C" void Crc_StartNextTask(struct crcstatus_t *status); DMA_TypeDef * DMA1 __attribute__((weak)); DMA_TypeDef * DMA2 __attribute__((weak)); static DMA_TypeDef dma1, dma2; static crcstatus_t crcStatus; static crcslot_t slot1, slot2; static crctask_t tasks1[2], tasks2[2]; static CRC_TypeDef fakeCrc; static DMA_TypeDef *expectedDma; static uint32_t expectedStream; static uint32_t expectedSrcAddress; static uint32_t expectedDstAddress; static uint32_t expectedLength; static void *expectedCustomPtr; static uint8_t expectedSuccess; static uint32_t expectedCrc; ////////////////////////////////////////////////////////////////////////////// uint32_t effective_primask = 0; DEFINE_MOCK(__set_PRIMASK, mock, uint32_t primask) { effective_primask = primask; LEAVE_MOCK; } DEFINE_MOCK_RET(uint32_t, __get_PRIMASK, mock) { RETURN_MOCK(__get_PRIMASK, mock, effective_primask); } DEFINE_MOCK_VAR(crcslot_t *, __disable_irq, mock, firstslot_required); DEFINE_MOCK(__disable_irq, mock) { if(MOCK_CALLCOUNT(__disable_irq, mock) < 2) { EXPECT_EQ(crcStatus.firstSlot, MOCK_VAR(__disable_irq, mock, firstslot_required)); } effective_primask = 1; LEAVE_MOCK; } DEFINE_MOCK(LL_DMA_EnableIT_TC, mock, DMA_TypeDef *dma, uint32_t stream) { EXPECT_EQ(expectedDma, dma); EXPECT_EQ(expectedStream, stream); LEAVE_MOCK; } DEFINE_MOCK(LL_DMA_EnableIT_TE, mock, DMA_TypeDef *dma, uint32_t stream) { EXPECT_EQ(expectedDma, dma); EXPECT_EQ(expectedStream, stream); LEAVE_MOCK; } DEFINE_MOCK(LL_DMA_SetM2MDstAddress, mock, DMA_TypeDef *dma, uint32_t stream, uint32_t address) { EXPECT_EQ(expectedDma, dma); EXPECT_EQ(expectedStream, stream); EXPECT_EQ(expectedDstAddress, address); LEAVE_MOCK; } DEFINE_MOCK(LL_DMA_SetM2MSrcAddress, mock, DMA_TypeDef *dma, uint32_t stream, uint32_t address) { EXPECT_EQ(expectedDma, dma); EXPECT_EQ(expectedStream, stream); EXPECT_EQ(expectedSrcAddress, address); LEAVE_MOCK; } DEFINE_MOCK(LL_DMA_SetDataLength, mock, DMA_TypeDef *dma, uint32_t stream, uint32_t length) { EXPECT_EQ(expectedDma, dma); EXPECT_EQ(expectedStream, stream); EXPECT_EQ(expectedLength, length); LEAVE_MOCK; } DEFINE_MOCK(LL_DMA_EnableStream, mock, DMA_TypeDef *dma, uint32_t stream) { EXPECT_EQ(expectedDma, dma); EXPECT_EQ(expectedStream, stream); LEAVE_MOCK; } DEFINE_MOCK(LL_DMA_DisableStream, mock, DMA_TypeDef *dma, uint32_t stream) { EXPECT_EQ(expectedDma, dma); EXPECT_EQ(expectedStream, stream); LEAVE_MOCK; } DEFINE_MOCK(Crc_StartNextTask, mock, struct crcstatus_t *status) { EXPECT_EQ(status, &crcStatus); LEAVE_MOCK } void FakeCallback_1(void*, uint32_t, uint8_t) {} void FakeCallback_2(void*, uint32_t, uint8_t) {} void FakeCallback_3(void*, uint32_t, uint8_t) {} void FakeCallbackCheck(void* ptr, uint32_t crc, uint8_t success) { EXPECT_EQ(ptr, expectedCustomPtr); EXPECT_EQ(crc, expectedCrc); EXPECT_EQ(success, expectedSuccess); } DEFINE_MOCK(Dma_Init, mock, struct dmainfo_t * info, DMA_TypeDef *dma, uint32_t stream) { LEAVE_MOCK; } ////////////////////////////////////////////////////////////////////////////// TEST(CrcScheduler, InitStatus) { expectedDma = DMA2; expectedStream = LL_DMA_STREAM_4; expectedDstAddress = (uint32_t)&fakeCrc; memset(&crcStatus, 0xff, sizeof(crcStatus)); ACTIVATE_MOCK(LL_DMA_EnableIT_TC, mock); ACTIVATE_MOCK(LL_DMA_EnableIT_TE, mock); ACTIVATE_MOCK(LL_DMA_SetM2MDstAddress, mock); ACTIVATE_MOCK(Dma_Init, mock); Crc_InitStatus(&crcStatus, &fakeCrc, DMA2, LL_DMA_STREAM_4); EXPECT_EQ(crcStatus.crcUnit, &fakeCrc); EXPECT_EQ(crcStatus.activeSlot, nullptr); EXPECT_EQ(crcStatus.firstSlot, nullptr); EXPECT_EQ(MOCK_VAR(Dma_Init, mock, callcount), 1); } TEST(CrcScheduler, AttachTask_single) { DMA1 = &dma1; DMA2 = &dma2; effective_primask = 0; Crc_InitStatus(&crcStatus, &fakeCrc, DMA2, LL_DMA_STREAM_4); ACTIVATE_MOCK_RV(__get_PRIMASK, mock, 0); ACTIVATE_MOCK(__set_PRIMASK, mock); ACTIVATE_MOCK(__disable_irq, mock); MOCK_STORE(__disable_irq, mock, firstslot_required, nullptr); Crc_AttachTasks(&crcStatus, &slot1, tasks1, 2); EXPECT_EQ(MOCK_CALLCOUNT(__get_PRIMASK, mock), 1); EXPECT_EQ(MOCK_CALLCOUNT(__set_PRIMASK, mock), 1); EXPECT_EQ(MOCK_CALLCOUNT(__disable_irq, mock), 1); EXPECT_EQ(crcStatus.firstSlot, &slot1); EXPECT_EQ(slot1.next, nullptr); EXPECT_EQ(slot1.count, 2); EXPECT_EQ(crcStatus.activeSlot, nullptr); } // Are tasks attached in the expected order internally TEST(CrcScheduler, AttachTask_multiple) { ACTIVATE_MOCK_RV(__get_PRIMASK, mock, 1); ACTIVATE_MOCK(__set_PRIMASK, mock); ACTIVATE_MOCK(__disable_irq, mock); MOCK_STORE(__disable_irq, mock, firstslot_required, nullptr); DMA1 = &dma1; DMA2 = &dma2; Crc_InitStatus(&crcStatus, NULL, DMA2, LL_DMA_STREAM_4); Crc_AttachTasks(&crcStatus, &slot1, tasks1, 2); MOCK_STORE(__disable_irq, mock, firstslot_required, &slot1); Crc_AttachTasks(&crcStatus, &slot2, tasks2, 2); EXPECT_EQ(MOCK_CALLCOUNT(__get_PRIMASK, mock), 2); EXPECT_EQ(MOCK_CALLCOUNT(__set_PRIMASK, mock), 2); EXPECT_EQ(MOCK_CALLCOUNT(__disable_irq, mock), 2); EXPECT_EQ(crcStatus.firstSlot, &slot2); EXPECT_EQ(slot2.next, &slot1); EXPECT_EQ(slot1.next, nullptr); EXPECT_EQ(slot2.count, 2); EXPECT_EQ(crcStatus.activeSlot, nullptr); } // No blocking should occur if the the task is not busy TEST(CrcScheduler, Enqueue_nowait) { uint32_t fakeCrcResult; uint8_t testData[] = "qwerty"; expectedDma = DMA2; expectedStream = LL_DMA_STREAM_4; expectedSrcAddress = (uint32_t)testData; memset(tasks1, 0, sizeof(tasks1)); memset(&fakeCrc, 0, sizeof(fakeCrc)); Crc_InitStatus(&crcStatus, &fakeCrc, DMA2, LL_DMA_STREAM_4); Crc_AttachTasks(&crcStatus, &slot1, tasks1, 2); expectedLength = 2; ACTIVATE_MOCK(LL_DMA_SetM2MSrcAddress, mock); ACTIVATE_MOCK(LL_DMA_SetDataLength, mock); ACTIVATE_MOCK(LL_DMA_EnableStream, mock); EXPECT_TRUE(Crc_Enqueue(&crcStatus, &slot1, 0, testData, sizeof(testData), FakeCallback_1, &fakeCrcResult)); //first task should be picked up before return; EXPECT_EQ(slot1.tasks[0].address, nullptr); EXPECT_EQ((uintptr_t)slot1.tasks[0].callback, (uintptr_t)FakeCallback_1); EXPECT_EQ(MOCK_VAR(LL_DMA_SetM2MSrcAddress, mock, callcount), 1); EXPECT_EQ(MOCK_VAR(LL_DMA_SetDataLength, mock, callcount), 1); EXPECT_EQ(MOCK_VAR(LL_DMA_EnableStream, mock, callcount), 1); EXPECT_FALSE(Crc_Enqueue(&crcStatus, &slot1, 1, testData, 4, FakeCallback_1, &fakeCrcResult)); // second task should be queued EXPECT_EQ(slot1.tasks[1].address, testData); EXPECT_EQ((uintptr_t)slot1.tasks[1].callback, (uintptr_t)FakeCallback_1); EXPECT_EQ(slot1.tasks[1].wordCount, 1); // should be no new calls to hardware handling functions EXPECT_EQ(MOCK_VAR(LL_DMA_SetM2MSrcAddress, mock, callcount), 1); EXPECT_EQ(MOCK_VAR(LL_DMA_SetDataLength, mock, callcount), 1); EXPECT_EQ(MOCK_VAR(LL_DMA_EnableStream, mock, callcount), 1); } // When trying to enqueue for a busy task it should blok firs // then when the previously blocked task finishes it should // enqueue the new one TEST(CrcScheduler, Enqueue_shouldblockthencontinue) { uint8_t testData[] = "qwerty"; uint32_t fakeCrcResult; memset(tasks1, 0, sizeof(tasks1)); memset(&fakeCrc, 0, sizeof(fakeCrc)); Crc_InitStatus(&crcStatus, &fakeCrc, DMA2, LL_DMA_STREAM_4); Crc_AttachTasks(&crcStatus, &slot1, tasks1, 2); Crc_Enqueue(&crcStatus, &slot1, 0, testData, sizeof(testData), FakeCallback_1, &fakeCrcResult); // black magic to test if the function blocks (at least for 100ms) std::promise promisedFinished; auto futureResult = promisedFinished.get_future(); pthread_t th; std::thread t([&testData](std::promise& finished) { pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL); Crc_Enqueue(&crcStatus, &slot1, 0, testData, sizeof(testData), FakeCallback_1, nullptr); finished.set_value(true); }, std::ref(promisedFinished)); th = t.native_handle(); t.detach(); EXPECT_EQ(futureResult.wait_for(std::chrono::milliseconds(100)), std::future_status::timeout); tasks1[0].callback = nullptr; tasks1[0].callbackParam = nullptr; auto waitResult(futureResult.wait_for(std::chrono::milliseconds(100))); EXPECT_NE(waitResult, std::future_status::timeout); if(waitResult == std::future_status::timeout) pthread_cancel(th); } // StartNextTask should start the scheduled tasks in predefined order TEST(CrcScheduler, Crc_StartNextTask) { uint8_t testData[] = "qwerty"; uint32_t fakeCrcResult1, fakeCrcResult2, fakeCrcResult3; memset(tasks1, 0, sizeof(tasks1)); memset(tasks2, 0, sizeof(tasks2)); memset(&fakeCrc, 0, sizeof(fakeCrc)); Crc_InitStatus(&crcStatus, &fakeCrc, DMA2, LL_DMA_STREAM_4); Crc_AttachTasks(&crcStatus, &slot1, tasks1, 2); Crc_AttachTasks(&crcStatus, &slot2, tasks2, 2); Crc_Enqueue(&crcStatus, &slot1, 0, testData, sizeof(testData), FakeCallback_1, &fakeCrcResult1); Crc_Enqueue(&crcStatus, &slot1, 1, testData, sizeof(testData), FakeCallback_2, &fakeCrcResult2); Crc_Enqueue(&crcStatus, &slot2, 0, testData, sizeof(testData), FakeCallback_3, &fakeCrcResult3); EXPECT_EQ(crcStatus.activeSlot, &slot1); EXPECT_EQ(crcStatus.activeTask, 0); crcStatus.activeSlot->tasks[crcStatus.activeTask].callback = nullptr; crcStatus.activeSlot->tasks[crcStatus.activeTask].callbackParam = nullptr; ACTIVATE_MOCK(LL_DMA_SetM2MSrcAddress, mock); ACTIVATE_MOCK(LL_DMA_SetDataLength, mock); ACTIVATE_MOCK(LL_DMA_EnableStream, mock); expectedDma = DMA2; expectedStream = LL_DMA_STREAM_4; expectedSrcAddress = (uint32_t)testData; expectedLength = (sizeof(testData) + 3) / 4; Crc_StartNextTask(&crcStatus); EXPECT_EQ(crcStatus.activeSlot, &slot2); EXPECT_EQ(crcStatus.activeTask, 0); EXPECT_EQ(LL_DMA_SetM2MSrcAddress_mock_callcount, 1); EXPECT_EQ(LL_DMA_SetDataLength_mock_callcount, 1); EXPECT_EQ(LL_DMA_EnableStream_mock_callcount, 1); crcStatus.activeSlot->tasks[crcStatus.activeTask].callback = nullptr; crcStatus.activeSlot->tasks[crcStatus.activeTask].callbackParam = nullptr; Crc_StartNextTask(&crcStatus); EXPECT_EQ(crcStatus.activeSlot, &slot1); EXPECT_EQ(crcStatus.activeTask, 1); EXPECT_EQ(LL_DMA_SetM2MSrcAddress_mock_callcount, 2); EXPECT_EQ(LL_DMA_SetDataLength_mock_callcount, 2); EXPECT_EQ(LL_DMA_EnableStream_mock_callcount, 2); } // HandleDmaIrq should start the next scheduled task or // disable the CRC DMA engine if there is no other task scheduled. TEST(CrcScheduler, HandleDmaIrq_callback) { uint8_t testData[] = "qwerty"; uint32_t FakeCustomData1, FakeCustomData2; memset(tasks1, 0, sizeof(tasks1)); memset(tasks2, 0, sizeof(tasks2)); memset(&fakeCrc, 0, sizeof(fakeCrc)); fakeCrc.DR = 0xa5a55a5a; Crc_InitStatus(&crcStatus, &fakeCrc, DMA2, LL_DMA_STREAM_4); Crc_AttachTasks(&crcStatus, &slot1, tasks1, 2); Crc_AttachTasks(&crcStatus, &slot2, tasks2, 2); Crc_Enqueue(&crcStatus, &slot1, 1, testData, sizeof(testData), FakeCallbackCheck, &FakeCustomData1); // we need to set this up here to check if HandleDmaIrq calls Crc_StartNextTask or not; Crc_Enqueue(&crcStatus, &slot2, 0, testData, sizeof(testData), FakeCallbackCheck, &FakeCustomData2); ACTIVATE_MOCK(LL_DMA_DisableStream, mock); ACTIVATE_MOCK(LL_DMA_SetM2MSrcAddress, mock); ACTIVATE_MOCK(LL_DMA_SetDataLength, mock); ACTIVATE_MOCK(LL_DMA_EnableStream, mock); ACTIVATE_MOCK(Crc_StartNextTask, mock); expectedDma = DMA2; expectedStream = LL_DMA_STREAM_4; expectedCustomPtr = &FakeCustomData1; expectedCrc = fakeCrc.DR; expectedSuccess = 1; DMA2->HISR |= DMA_HISR_TCIF4; DMA2->HIFCR = 0; Crc_HandleDmaIrq(&crcStatus); EXPECT_EQ(LL_DMA_DisableStream_mock_callcount, 1); EXPECT_EQ(DMA2->HIFCR & DMA_HIFCR_CTCIF4, DMA_HIFCR_CTCIF4); expectedCustomPtr = &FakeCustomData2; DMA2->HISR |= DMA_HISR_TCIF4 | DMA_HISR_TEIF4; DMA2->HIFCR = 0; crcStatus.activeSlot->tasks[crcStatus.activeTask].callback = nullptr; crcStatus.activeSlot->tasks[crcStatus.activeTask].callbackParam = nullptr; Crc_HandleDmaIrq(&crcStatus); EXPECT_EQ(DMA2->HIFCR & (DMA_HIFCR_CTCIF4 | DMA_HIFCR_CTEIF4), DMA_HIFCR_CTCIF4 | DMA_HIFCR_CTEIF4); EXPECT_EQ(LL_DMA_DisableStream_mock_callcount, 2); } // Crc_StartNextTask starts executing the next task and removes it from the queue // Test if IsTaskQueued reflects this behaviour correctly TEST(CrcScheduler, IsTaskQueued) { uint8_t testData[] = "qwerty"; uint32_t FakeCustomData1; Crc_InitStatus(&crcStatus, &fakeCrc, DMA2, LL_DMA_STREAM_4); Crc_AttachTasks(&crcStatus, &slot1, tasks1, 2); Crc_Enqueue(&crcStatus, &slot1, 0, testData, sizeof(testData), FakeCallback_1, &FakeCustomData1); Crc_Enqueue(&crcStatus, &slot1, 1, testData, sizeof(testData), FakeCallback_1, &FakeCustomData1); EXPECT_EQ(Crc_IsTaskQueued(&slot1, 0), 0); EXPECT_NE(Crc_IsTaskQueued(&slot1, 1), 0); Crc_StartNextTask(&crcStatus); EXPECT_EQ(Crc_IsTaskQueued(&slot1, 1), 0); } // Crc_HandleDmaIrq completes the active task and start executing the next (by calling StartNextTask) // Crc_IsTaskBusy should reflect these changes TEST(CrcScheduler, IsTaskBusy) { uint8_t testData[] = "qwerty"; uint32_t FakeCustomData1; Crc_InitStatus(&crcStatus, &fakeCrc, DMA2, LL_DMA_STREAM_4); Crc_AttachTasks(&crcStatus, &slot1, tasks1, 2); Crc_Enqueue(&crcStatus, &slot1, 0, testData, sizeof(testData), FakeCallback_1, &FakeCustomData1); Crc_Enqueue(&crcStatus, &slot1, 1, testData, sizeof(testData), FakeCallback_1, &FakeCustomData1); EXPECT_NE(Crc_IsTaskBusy(&slot1, 0), 0); EXPECT_NE(Crc_IsTaskBusy(&slot1, 1), 0); DMA2->HISR |= DMA_HISR_TCIF4; Crc_HandleDmaIrq(&crcStatus); EXPECT_EQ(Crc_IsTaskBusy(&slot1, 0), 0); EXPECT_NE(Crc_IsTaskBusy(&slot1, 1), 0); DMA2->HISR |= DMA_HISR_TCIF4; Crc_HandleDmaIrq(&crcStatus); EXPECT_EQ(Crc_IsTaskBusy(&slot1, 0), 0); EXPECT_EQ(Crc_IsTaskBusy(&slot1, 1), 0); }