From 9d3bd7802bc7897e06ad115ddf48f4dea368dc9a Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 20 Dec 2016 08:46:58 -0800 Subject: [PATCH] Update the `MessageQueue` and add unit test for it. (#1089) This commit makes the following changes: - It adds a unit test for testing different `MessageQueue` operations. - It modifies how the `MessageQueue` determines the lists (the `kListAll` is maintained by the message pool and the `kListInterface` is accessed from the `MessageQueue` associated with the list). - It modifies the underlying list implementation to use a circular doubly linked-list. --- etc/visual-studio/UnitTests.vcxproj | 1 + etc/visual-studio/UnitTests.vcxproj.filters | 3 + src/core/common/message.cpp | 130 +++++++++--------- src/core/common/message.hpp | 122 ++++++++++------- src/core/net/ip6.cpp | 2 +- tests/unit/Makefile.am | 4 + tests/unit/test_message_queue.cpp | 141 ++++++++++++++++++++ tests/unit/test_windows.cpp | 12 +- 8 files changed, 303 insertions(+), 112 deletions(-) create mode 100644 tests/unit/test_message_queue.cpp diff --git a/etc/visual-studio/UnitTests.vcxproj b/etc/visual-studio/UnitTests.vcxproj index 87225c2df..3c4a3a9ae 100644 --- a/etc/visual-studio/UnitTests.vcxproj +++ b/etc/visual-studio/UnitTests.vcxproj @@ -69,6 +69,7 @@ + diff --git a/etc/visual-studio/UnitTests.vcxproj.filters b/etc/visual-studio/UnitTests.vcxproj.filters index d7d5eb2ea..ded7f8a5c 100644 --- a/etc/visual-studio/UnitTests.vcxproj.filters +++ b/etc/visual-studio/UnitTests.vcxproj.filters @@ -36,6 +36,9 @@ Source Files + + Source Files + Source Files diff --git a/src/core/common/message.cpp b/src/core/common/message.cpp index 544ad6aab..45a3e911d 100644 --- a/src/core/common/message.cpp +++ b/src/core/common/message.cpp @@ -44,7 +44,7 @@ namespace Thread { MessagePool::MessagePool(void) { memset(mBuffers, 0, sizeof(mBuffers)); - memset(&mAll, 0, sizeof(mAll)); + mAllListTail = NULL; mFreeBuffers = mBuffers; @@ -87,11 +87,21 @@ exit: ThreadError MessagePool::Free(Message *aMessage) { - assert(aMessage->GetMessageList(MessageInfo::kListAll).mList == NULL && - aMessage->GetMessageList(MessageInfo::kListInterface).mList == NULL); + assert(aMessage->Next(MessageInfo::kListAll) == NULL && + aMessage->Prev(MessageInfo::kListAll) == NULL); + + assert(aMessage->Next(MessageInfo::kListInterface) == NULL && + aMessage->Prev(MessageInfo::kListInterface) == NULL); + return FreeBuffers(static_cast(aMessage)); } +Message *MessagePool::GetAllMessagesListHead(void) const +{ + return (mAllListTail == NULL) ? NULL : mAllListTail->Next(MessageInfo::kListAll); +} + + Buffer *MessagePool::NewBuffer(void) { Buffer *buffer = NULL; @@ -186,7 +196,9 @@ ThreadError Message::Free(void) Message *Message::GetNext(void) const { - return GetMessageList(MessageInfo::kListInterface).mNext; + assert(GetMessageQueue() != NULL); + + return (this == GetMessageQueue()->mTail) ? NULL : Next(MessageInfo::kListInterface); } uint16_t Message::GetLength(void) const @@ -740,94 +752,90 @@ void Message::SetReserved(uint16_t aReserved) mInfo.mReserved = aReserved; } -MessageQueue::MessageQueue(void) +MessageQueue::MessageQueue(void) : + mTail(NULL) { - mInterface.mHead = NULL; - mInterface.mTail = NULL; } -ThreadError MessageQueue::AddToList(uint8_t aList, Message &aMessage) +void MessageQueue::AddToList(Message *&aListTail, uint8_t aList, Message &aMessage) { - MessageList *list; + Message *head; - assert(aMessage.GetMessageList(aList).mNext == NULL && - aMessage.GetMessageList(aList).mPrev == NULL && - aMessage.GetMessageList(aList).mList != NULL); + assert((aMessage.Next(aList) == NULL) && (aMessage.Prev(aList) == NULL)); - list = aMessage.GetMessageList(aList).mList; - - if (list->mHead == NULL) + if (aListTail == NULL) { - list->mHead = &aMessage; - list->mTail = &aMessage; + aMessage.Next(aList) = &aMessage; + aMessage.Prev(aList) = &aMessage; } else { - list->mTail->GetMessageList(aList).mNext = &aMessage; - aMessage.GetMessageList(aList).mPrev = list->mTail; - list->mTail = &aMessage; + head = aListTail->Next(aList); + + aMessage.Next(aList) = head; + aMessage.Prev(aList) = aListTail; + + head->Prev(aList) = &aMessage; + aListTail->Next(aList) = &aMessage; } - return kThreadError_None; + aListTail = &aMessage; } -ThreadError MessageQueue::RemoveFromList(uint8_t aList, Message &aMessage) +void MessageQueue::RemoveFromList(Message *&aListTail, uint8_t aList, Message &aMessage) { - MessageList *list; + assert((aMessage.Next(aList) != NULL) && (aMessage.Prev(aList) != NULL)); - assert(aMessage.GetMessageList(aList).mList != NULL); - - list = aMessage.GetMessageList(aList).mList; - - assert(list->mHead == &aMessage || - aMessage.GetMessageList(aList).mNext != NULL || - aMessage.GetMessageList(aList).mPrev != NULL); - - if (aMessage.GetMessageList(aList).mPrev) + if (&aMessage == aListTail) { - aMessage.GetMessageList(aList).mPrev->GetMessageList(aList).mNext = aMessage.GetMessageList(aList).mNext; - } - else - { - list->mHead = aMessage.GetMessageList(aList).mNext; + aListTail = aListTail->Prev(aList); + + if (&aMessage == aListTail) + { + aListTail = NULL; + } } - if (aMessage.GetMessageList(aList).mNext) - { - aMessage.GetMessageList(aList).mNext->GetMessageList(aList).mPrev = aMessage.GetMessageList(aList).mPrev; - } - else - { - list->mTail = aMessage.GetMessageList(aList).mPrev; - } + aMessage.Prev(aList)->Next(aList) = aMessage.Next(aList); + aMessage.Next(aList)->Prev(aList) = aMessage.Prev(aList); - aMessage.GetMessageList(aList).mPrev = NULL; - aMessage.GetMessageList(aList).mNext = NULL; - - return kThreadError_None; + aMessage.Prev(aList) = NULL; + aMessage.Next(aList) = NULL; } Message *MessageQueue::GetHead(void) const { - return mInterface.mHead; + return (mTail == NULL) ? NULL : mTail->Next(MessageInfo::kListInterface); } ThreadError MessageQueue::Enqueue(Message &aMessage) { - aMessage.GetMessageList(MessageInfo::kListAll).mList = &aMessage.GetMessagePool()->mAll; - aMessage.GetMessageList(MessageInfo::kListInterface).mList = &mInterface; - AddToList(MessageInfo::kListAll, aMessage); - AddToList(MessageInfo::kListInterface, aMessage); - return kThreadError_None; + ThreadError error = kThreadError_None; + + VerifyOrExit(aMessage.GetMessageQueue() == NULL, error = kThreadError_Already); + + aMessage.SetMessageQueue(this); + + AddToList(aMessage.GetMessagePool()->mAllListTail, MessageInfo::kListAll, aMessage); + AddToList(mTail, MessageInfo::kListInterface, aMessage); + +exit: + return error; } ThreadError MessageQueue::Dequeue(Message &aMessage) { - RemoveFromList(MessageInfo::kListAll, aMessage); - RemoveFromList(MessageInfo::kListInterface, aMessage); - aMessage.GetMessageList(MessageInfo::kListAll).mList = NULL; - aMessage.GetMessageList(MessageInfo::kListInterface).mList = NULL; - return kThreadError_None; + ThreadError error = kThreadError_None; + + VerifyOrExit(aMessage.GetMessageQueue() == this, error = kThreadError_NotFound); + + RemoveFromList(aMessage.GetMessagePool()->mAllListTail, MessageInfo::kListAll, aMessage); + RemoveFromList(mTail, MessageInfo::kListInterface, aMessage); + + aMessage.SetMessageQueue(NULL); + +exit: + return error; } void MessageQueue::GetInfo(uint16_t &aMessageCount, uint16_t &aBufferCount) const @@ -835,7 +843,7 @@ void MessageQueue::GetInfo(uint16_t &aMessageCount, uint16_t &aBufferCount) cons aMessageCount = 0; aBufferCount = 0; - for (const Message *message = mInterface.mHead; message; message = message->GetNext()) + for (const Message *message = GetHead(); message != NULL; message = message->GetNext()) { aMessageCount++; aBufferCount += message->GetBufferCount(); diff --git a/src/core/common/message.hpp b/src/core/common/message.hpp index 05021d70f..503d55a91 100644 --- a/src/core/common/message.hpp +++ b/src/core/common/message.hpp @@ -69,29 +69,7 @@ enum class Message; class MessagePool; - -/** - * This structure contains pointers to the head and tail of a Message list. - * - */ -struct MessageList -{ - Message *mHead; ///< A pointer to the first Message in the list. - Message *mTail; ///< A pointer to the last Message in the list. -}; - -/** - * This structure contains pointers to the MessageList structure, the next Message, and previous Message. - * - */ -struct MessageListEntry -{ - struct MessageList *mList; ///< A pointer to the MessageList structure for the list. - Message *mNext; ///< A pointer to the next Message in the list. - Message *mPrev; ///< A pointer to the previous Message in the list. -}; - - +class MessageQueue; /** * This structure contains metdata about a Message. @@ -99,13 +77,18 @@ struct MessageListEntry */ struct MessageInfo { - MessagePool *mMessagePool; ///< Identifies the message pool for this message. enum { - kListAll = 0, ///< Identifies the all messages list. - kListInterface = 1, ///< Identifies the per-interface message list. + kListAll = 0, ///< Identifies the all messages list (maintained by the MessagePool). + kListInterface = 1, ///< Identifies the list for per-interface message queue. + kNumLists = 2, ///< Number of lists. }; - MessageListEntry mList[2]; ///< Message lists. + + Message *mNext[kNumLists]; ///< A pointer to the next Message in a doubly linked list. + Message *mPrev[kNumLists]; ///< A pointer to the previous Message in a doubly linked list. + MessagePool *mMessagePool; ///< Identifies the message pool for this message. + MessageQueue *mMessageQueue; ///< Identifies the message queue (if any) where this message is queued. + uint16_t mReserved; ///< Number of header bytes reserved for the message. uint16_t mLength; ///< Number of bytes within the message. uint16_t mOffset; ///< A byte offset within the message. @@ -586,29 +569,67 @@ public: uint16_t UpdateChecksum(uint16_t aChecksum, uint16_t aOffset, uint16_t aLength) const; private: + + /** + * This method returns a pointer to the message pool to which this message belongs + * + * @returns A pointer to the message pool. + * + */ MessagePool *GetMessagePool(void) const { return mInfo.mMessagePool; } + /** + * This method sets the message pool this message to which this message belongs. + * + * @param[in] aMessagePool A pointer to the message pool + * + */ void SetMessagePool(MessagePool *aMessagePool) { mInfo.mMessagePool = aMessagePool; } /** - * This method returns a reference to a message list. + * This method returns a pointer to the message queue (if any) where this message is queued. * - * @param[in] aList The message list. - * - * @returns A reference to a message list. * */ - MessageListEntry &GetMessageList(uint8_t aList) { return mInfo.mList[aList]; } + MessageQueue *GetMessageQueue(void) const { return mInfo.mMessageQueue; } /** - * This method returns a reference to a message list. + * This method sets the message queue information for the message. * - * @param[in] aList The message list. - * - * @returns A reference to a message list. + * @param[in] aMessageQueue A pointer to the message queue where this message is queued. * */ - const MessageListEntry &GetMessageList(uint8_t aList) const { return mInfo.mList[aList]; } + void SetMessageQueue(MessageQueue *aMessageQueue) { mInfo.mMessageQueue = aMessageQueue; } + + /** + * This method returns a reference to the `mNext` pointer for a given list. + * + * @param[in] aList The index to the message list. + * + * @returns A reference to the mNext pointer for the specified list. + * + */ + Message *&Next(uint8_t aList) { return mInfo.mNext[aList]; } + + /** + * This method returns a const reference to the `mNext` pointer for a given list. + * + * @param[in] aList The index to the message list. + * + * @returns A const reference to the mNext pointer for the specified list. + * + */ + Message *const &Next(uint8_t aList) const { return mInfo.mNext[aList]; } + + /** + * This method returns a reference to the `mPrev` pointer for a given list. + * + * @param[in] aList The index to the message list. + * + * @returns A reference to the mPrev pointer for the specified list. + * + */ + Message *&Prev(uint8_t aList) { return mInfo.mPrev[aList]; } /** * This method returns the number of reserved header bytes. @@ -652,6 +673,8 @@ private: */ class MessageQueue { + friend class Message; + public: /** * This constructor initializes the message queue. @@ -699,31 +722,28 @@ public: void GetInfo(uint16_t &aMessageCount, uint16_t &aBufferCount) const; private: + /** * This static method adds a message to a list. * + * @param[in] aListTail A reference to the list tail pointer. * @param[in] aListId The list to add @p aMessage to. * @param[in] aMessage The message to add to @p aListId. * - * @retval kThreadError_None Successfully added the message to the list. - * @retval kThreadError_Already The message is already enqueued in a list. - * */ - static ThreadError AddToList(uint8_t aListId, Message &aMessage); + static void AddToList(Message *&aListTail, uint8_t aListId, Message &aMessage); /** * This static method removes a message from a list. * + * @param[in] aListTail A reference to the list tale pointer. * @param[in] aListId The list to add @p aMessage to. * @param[in] aMessage The message to add to @p aListId. * - * @retval kThreadError_None Successfully added the message to the list. - * @retval kThreadError_NotFound The message is not enqueued in the list. - * */ - static ThreadError RemoveFromList(uint8_t aListId, Message &aMessage); + static void RemoveFromList(Message *&aListTail, uint8_t aListId, Message &aMessage); - MessageList mInterface; ///< The instance-specific message list. + Message *mTail; ///< A pointer to the last Message in the list. }; class MessagePool @@ -760,6 +780,14 @@ public: */ ThreadError Free(Message *aMessage); + /** + * This method returns a pointer to the first message in the all-messages list. + * + * @returns A pointer to the first message. + * + */ + Message *GetAllMessagesListHead(void) const; + /** * This method returns the number of free buffers. * @@ -776,7 +804,7 @@ private: int mNumFreeBuffers; Buffer mBuffers[kNumBuffers]; Buffer *mFreeBuffers; - MessageList mAll; + Message *mAllListTail; }; /** diff --git a/src/core/net/ip6.cpp b/src/core/net/ip6.cpp index 9065e8218..f7d44af05 100644 --- a/src/core/net/ip6.cpp +++ b/src/core/net/ip6.cpp @@ -1059,7 +1059,7 @@ exit: return rval; } -otInstance *Ip6::GetInstance() +otInstance *Ip6::GetInstance(void) { return otInstanceFromIp6(this); } diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 0d5cf7d6b..339de8322 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -80,6 +80,7 @@ check_PROGRAMS = \ test-link-quality \ test-mac-frame \ test-message \ + test-message-queue \ test-timer \ test-toolchain \ $(NULL) @@ -138,6 +139,9 @@ test_mac_frame_SOURCES = test_platform.cpp test_mac_frame.cpp test_message_LDADD = $(COMMON_LDADD) test_message_SOURCES = test_platform.cpp test_message.cpp +test_message_queue_LDADD = $(COMMON_LDADD) +test_message_queue_SOURCES = test_platform.cpp test_message_queue.cpp + test_ncp_buffer_LDADD = $(COMMON_LDADD) test_ncp_buffer_SOURCES = test_platform.cpp test_ncp_buffer.cpp diff --git a/tests/unit/test_message_queue.cpp b/tests/unit/test_message_queue.cpp new file mode 100644 index 000000000..3e6b5d079 --- /dev/null +++ b/tests/unit/test_message_queue.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2016, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test_util.h" +#include +#include +#include +#include +#include + +#define kNumMessages 5 + +// This function verifies the content of the message to matches the passed in messages +void VerifyMessageQueueContent(Thread::MessageQueue &aMessageQueue, int aExpectedLength, ...) +{ + va_list args; + Thread::Message *message; + Thread::Message *msgArg; + + va_start(args, aExpectedLength); + + if (aExpectedLength == 0) + { + message = aMessageQueue.GetHead(); + VerifyOrQuit(message == NULL, "MessageQueue is not empty when expected len is zero."); + } + else + { + for (message = aMessageQueue.GetHead(); message != NULL; message = message->GetNext()) + { + VerifyOrQuit(aExpectedLength != 0, "MessageQueue contains more entries than expected"); + + msgArg = va_arg(args, Thread::Message *); + VerifyOrQuit(msgArg == message, "MessageQueue content does not match what is expected."); + + aExpectedLength--; + } + + VerifyOrQuit(aExpectedLength == 0, "MessageQueue contains less entries than expected"); + } + + va_end(args); +} + +void TestMessageQueue(void) +{ + Thread::MessagePool messagePool; + Thread::MessageQueue messageQueue; + Thread::Message *msg[kNumMessages]; + + for (int i = 0; i < kNumMessages; i++) + { + msg[i] = messagePool.New(Thread::Message::kTypeIp6, 0); + VerifyOrQuit(msg[i] != NULL, "Message::New failed\n"); + } + + VerifyMessageQueueContent(messageQueue, 0); + + //printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + //printf("\nEnqueue/Dequeue one message"); + + // Enqueue 1 message and remove it + SuccessOrQuit(messageQueue.Enqueue(*msg[0]), "MessageQueue::Enqueue() failed."); + VerifyMessageQueueContent(messageQueue, 1, msg[0]); + SuccessOrQuit(messageQueue.Dequeue(*msg[0]), "MessageQueue::Dequeue() failed."); + VerifyMessageQueueContent(messageQueue, 0); + + // Enqueue 5 messages + SuccessOrQuit(messageQueue.Enqueue(*msg[0]), "MessageQueue::Enqueue() failed."); + VerifyMessageQueueContent(messageQueue, 1, msg[0]); + SuccessOrQuit(messageQueue.Enqueue(*msg[1]), "MessageQueue::Enqueue() failed."); + VerifyMessageQueueContent(messageQueue, 2, msg[0], msg[1]); + SuccessOrQuit(messageQueue.Enqueue(*msg[2]), "MessageQueue::Enqueue() failed."); + VerifyMessageQueueContent(messageQueue, 3, msg[0], msg[1], msg[2]); + SuccessOrQuit(messageQueue.Enqueue(*msg[3]), "MessageQueue::Enqueue() failed."); + VerifyMessageQueueContent(messageQueue, 4, msg[0], msg[1], msg[2], msg[3]); + SuccessOrQuit(messageQueue.Enqueue(*msg[4]), "MessageQueue::Enqueue() failed."); + VerifyMessageQueueContent(messageQueue, 5, msg[0], msg[1], msg[2], msg[3], msg[4]); + + // Remove from head + SuccessOrQuit(messageQueue.Dequeue(*msg[0]), "MessageQueue::Dequeue() failed."); + VerifyMessageQueueContent(messageQueue, 4, msg[1], msg[2], msg[3], msg[4]); + + // Remove a message in middle + SuccessOrQuit(messageQueue.Dequeue(*msg[3]), "MessageQueue::Dequeue() failed."); + VerifyMessageQueueContent(messageQueue, 3, msg[1], msg[2], msg[4]); + + // Remove from tail + SuccessOrQuit(messageQueue.Dequeue(*msg[4]), "MessageQueue::Dequeue() failed."); + VerifyMessageQueueContent(messageQueue, 2, msg[1], msg[2]); + + // Add after removes + SuccessOrQuit(messageQueue.Enqueue(*msg[0]), "MessageQueue::Enqueue() failed."); + VerifyMessageQueueContent(messageQueue, 3, msg[1], msg[2], msg[0]); + SuccessOrQuit(messageQueue.Enqueue(*msg[3]), "MessageQueue::Enqueue() failed."); + VerifyMessageQueueContent(messageQueue, 4, msg[1], msg[2], msg[0], msg[3]); + + // Remove all messages + SuccessOrQuit(messageQueue.Dequeue(*msg[2]), "MessageQueue::Dequeue() failed."); + VerifyMessageQueueContent(messageQueue, 3, msg[1], msg[0], msg[3]); + SuccessOrQuit(messageQueue.Dequeue(*msg[1]), "MessageQueue::Dequeue() failed."); + VerifyMessageQueueContent(messageQueue, 2, msg[0], msg[3]); + SuccessOrQuit(messageQueue.Dequeue(*msg[3]), "MessageQueue::Dequeue() failed."); + VerifyMessageQueueContent(messageQueue, 1, msg[0]); + SuccessOrQuit(messageQueue.Dequeue(*msg[0]), "MessageQueue::Dequeue() failed."); + VerifyMessageQueueContent(messageQueue, 0); +} + +#ifdef ENABLE_TEST_MAIN +int main(void) +{ + TestMessageQueue(); + printf("All tests passed\n"); + return 0; +} +#endif diff --git a/tests/unit/test_windows.cpp b/tests/unit/test_windows.cpp index 7e42027a1..16b350950 100644 --- a/tests/unit/test_windows.cpp +++ b/tests/unit/test_windows.cpp @@ -65,6 +65,9 @@ namespace Thread // test_message.cpp void TestMessage(); +// test_message_queue.cpp +void TestMessageQueue(); + // test_ncp_buffer.cpp namespace Thread { @@ -92,7 +95,7 @@ utAssertTrue s_AssertTrue; utLogMessage s_LogMessage; namespace Thread -{ +{ TEST_CLASS(UnitTests) { public: @@ -114,7 +117,7 @@ namespace Thread va_start(args, format); vsnprintf(message, sizeof(message), format, args); va_end(args); - + Logger::WriteMessage(message); } @@ -145,7 +148,10 @@ namespace Thread // test_message.cpp TEST_METHOD(TestMessage) { ::TestMessage(); } - // test_message.cpp + // test_message_queue.cpp + TEST_METHOD(TestMessageQueue) { ::TestMessageQueue(); } + + // test_timer.cpp TEST_METHOD(TestOneTimer) { ::TestOneTimer(); } TEST_METHOD(TestTenTimers) { ::TestTenTimers(); }