1 /*---------------------------------------------------------------------------*
2 Project: Horizon
3 File: test_Suite.cpp
4 Copyright (C)2009 Nintendo Co., Ltd. All rights reserved.
5 These coded instructions, statements, and computer programs contain
6 proprietary information of Nintendo of America Inc. and/or Nintendo
7 Company Ltd., and are protected by Federal copyright law. They may
8 not be disclosed to third parties or copied or duplicated in any form,
9 in whole or in part, without the prior written consent of Nintendo.
10 $Rev: 31762 $
11 *---------------------------------------------------------------------------
12
13
14 */
15
16 #include <nn/test/test_Suite.h>
17 #include <nn/test/test_Output.h>
18 #include <nn/test/test_Time.h>
19 #include <nn/test/test_Api.h>
20 #include <nn/dbg/dbg_PrintResult.h>
21 namespace nn{ namespace test{
22
23 namespace {
24 Suite* s_CurrentSuite = 0;
25
26 #ifdef NN_PROCESSOR_ARM946ES
27 const int NUM_RESULTHOLDER = 32;
28 const bit32 INVALID_THREADID = 0xffffffff;
29
30 struct ThreadLocalStrageArm9 {
31 bit32 threadId;
32 uptr value;
33 } s_ResultHolderArm9[NUM_RESULTHOLDER];
34 #endif
35 }
36
Suite()37 Suite::Suite() :
38 m_pOutput(NULL), m_TotalTestsNum(0), m_pExitTestJumpBuffer(NULL),
39 m_IsSuccess(false), m_Continue(false), m_IsInitialized(false), m_MainThreadId(nn::os::Thread::GetCurrentId())
40 {
41 #ifdef NN_PROCESSOR_ARM946ES
42 for ( int i = 0; i < NUM_RESULTHOLDER; i++ )
43 {
44 s_ResultHolderArm9[i].threadId = INVALID_THREADID;
45 s_ResultHolderArm9[i].value = 0;
46 }
47 #endif
48 }
49
~Suite()50 Suite::~Suite()
51 {
52 }
53
IsOnMainThread() const54 bool Suite::IsOnMainThread() const
55 {
56 return m_MainThreadId == nn::os::Thread::GetCurrentId();
57 }
58
AddSubSuite(Suite * pSuite)59 void Suite::AddSubSuite(Suite* pSuite)
60 {
61 m_SubSuites.PushBack(pSuite);
62 m_TotalTestsNum += pSuite->m_TotalTestsNum;
63 }
64
Run(Output & output,bool isContinue)65 bool Suite::Run(Output& output, bool isContinue)
66 {
67 int total = TotalTests(); // Number of tests including sub-suites
68 int subSuiteNum = m_SubSuites.GetNum();
69
70 // Initialization before starting test
71 m_Continue = isContinue;
72 output.OnInitialize(total, (subSuiteNum+1));
73 Initialize();
74
75 Time time;
76 time.Start();
77 DoRun(&output, isContinue);
78 time.End();
79 output.OnFinished(total, time);
80 Finalize();
81 return m_IsSuccess;
82 }
83
DoRun(Output * pOutput,bool isContinue)84 void Suite::DoRun(Output* pOutput, bool isContinue)
85 {
86 m_IsSuccess = true;
87 int testsNum = m_Tests.GetNum(); // Number of tests not including sub-suites
88 m_pOutput = pOutput;
89
90 // Executing test maintained by this suite
91 pOutput->OnSuiteStart(testsNum, m_TestName);
92 Time timeSuite;
93 timeSuite.Start();
94 while(!m_Tests.IsEmpty())
95 {
96 TestInfo info = m_Tests.PopFront();
97 bool testResult = DoRunSpecificTest(info, pOutput);
98 m_IsSuccess &= testResult;
99 }
100 timeSuite.End();
101 pOutput->OnSuiteEnd(testsNum, m_TestName, timeSuite);
102
103 // Execute child suite
104 while(!m_SubSuites.IsEmpty())
105 {
106 Suite* pSuite = m_SubSuites.PopFront();
107 pSuite->Initialize();
108 pSuite->DoRun(pOutput, isContinue);
109 pSuite->Finalize();
110 m_IsSuccess &= pSuite->m_IsSuccess;
111 }
112 }
113
AddAssertInfo(AssertInfo info)114 void Suite::AddAssertInfo(AssertInfo info)
115 {
116 m_pOutput->OnAssert(info);
117 // Test fails if caught by an assert even once.
118 m_IsTestSuccess = false;
119 }
120
RegisterTest(TestFunc func,const String & name)121 void Suite::RegisterTest(TestFunc func, const String& name)
122 {
123 m_Tests.PushBack(TestInfo(func, name));
124 m_TotalTestsNum++;
125 }
126
UnregisterTest(TestFunc func)127 void Suite::UnregisterTest(TestFunc func)
128 {
129 NN_TASSERT_(m_TotalTestsNum == m_Tests.GetNum());
130 for (int i = 0; i < m_TotalTestsNum; i++)
131 {
132 if (m_Tests.GetElement(i).testFunc == func)
133 {
134 m_Tests.DeleteElement(i);
135 i--;
136 m_TotalTestsNum--;
137 }
138 }
139 }
140
RegisterTestWithInfo(TestFunc func,const String & name,const String & info)141 void Suite::RegisterTestWithInfo(TestFunc func, const String& name, const String& info)
142 {
143 m_Tests.PushBack(TestInfo(func, name, info));
144 m_TotalTestsNum++;
145 }
146
ExitCurrentTest()147 void Suite::ExitCurrentTest()
148 {
149 if (!IsOnMainThread())
150 {
151 // Force thread to terminate if it is not the main thread
152 nn::svc::ExitThread();
153 NN_TASSERT_(0);
154 }
155 if(m_pExitTestJumpBuffer)
156 {
157 ::std::longjmp(*m_pExitTestJumpBuffer, 1);
158 }
159 else
160 {
161 NN_TPANIC_("Failed long jump, Suite::ExitCurrentTest()");
162 }
163 }
164
OnPanic(const char * pFilename,int lineNo,const char * pMessage)165 void Suite::OnPanic(const char* pFilename, int lineNo, const char* pMessage)
166 {
167 AddAssertInfo(AssertInfo(pFilename, lineNo, pMessage));
168 ExitCurrentTest();
169 }
170
171 nn::os::ThreadLocalStorage Suite::ResultHolder::tls_pExpectedResult;
172
GetCurrent()173 inline Suite::ResultHolder* Suite::ResultHolder::GetCurrent()
174 {
175 #ifndef NN_PROCESSOR_ARM946ES
176 return reinterpret_cast<Suite::ResultHolder*>(tls_pExpectedResult.GetValue());
177 #else
178 bit32 threadId = nn::os::Thread::GetCurrentId();
179 for ( int i = 0; i < NUM_RESULTHOLDER; i++ )
180 {
181 if ( threadId == s_ResultHolderArm9[i].threadId )
182 {
183 return reinterpret_cast<Suite::ResultHolder*>(s_ResultHolderArm9[i].value);
184 }
185 }
186 return 0;
187 #endif
188 }
189
SetCurrent(Suite::ResultHolder * result)190 inline void Suite::ResultHolder::SetCurrent(Suite::ResultHolder* result)
191 {
192 #ifndef NN_PROCESSOR_ARM946ES
193 tls_pExpectedResult.SetValue(reinterpret_cast<uptr>(result));
194 #else
195 bit32 threadId = nn::os::Thread::GetCurrentId();
196 int lastBlank = -1;
197 for ( int i = 0; i < NUM_RESULTHOLDER; i++ )
198 {
199 if ( threadId == s_ResultHolderArm9[i].threadId )
200 {
201 s_ResultHolderArm9[i].value = reinterpret_cast<uptr>(result);
202 return;
203 }
204 if ( s_ResultHolderArm9[i].threadId == INVALID_THREADID )
205 {
206 lastBlank = i;
207 }
208 }
209 if ( lastBlank != -1 )
210 {
211 s_ResultHolderArm9[lastBlank].threadId = threadId;
212 s_ResultHolderArm9[lastBlank].value = reinterpret_cast<uptr>(result);
213 return;
214 }
215 NN_LOG("ThreadLocalStorage for Arm9 not empty");
216 nn::svc::Break(nn::dbg::BREAK_REASON_PANIC);
217 #endif
218 }
219
GetCurrentResult()220 Result Suite::ResultHolder::GetCurrentResult()
221 {
222 return GetCurrent()->m_Result;
223 }
224
ResultHolder()225 Suite::ResultHolder::ResultHolder()
226 {
227 NN_TASSERT_(!GetCurrent());
228 SetCurrent(this);
229 }
230
~ResultHolder()231 Suite::ResultHolder::~ResultHolder()
232 {
233 NN_TASSERT_(GetCurrent());
234 #ifndef NN_PROCESSOR_ARM946ES
235 SetCurrent(0);
236 #else
237 bit32 threadId = nn::os::Thread::GetCurrentId();
238 bool foundStorage = false;
239 for ( int i = 0; i < NUM_RESULTHOLDER; i++ )
240 {
241 if ( threadId == s_ResultHolderArm9[i].threadId )
242 {
243 s_ResultHolderArm9[i].threadId = INVALID_THREADID;
244 s_ResultHolderArm9[i].value = 0;
245 foundStorage = true;
246 }
247 }
248 if ( !foundStorage )
249 {
250 NN_LOG("ThreadLocalStorage for Arm9 not found");
251 nn::svc::Break(nn::dbg::BREAK_REASON_PANIC);
252 }
253 #endif
254 }
255
GetJmpbuf()256 ::std::jmp_buf* Suite::ResultHolder::GetJmpbuf()
257 {
258 return &m_Jmpbuf;
259 }
260
ResultFailureHandler(Result result,const char * filename,int lineno,const char * fmt,::std::va_list vlist)261 void Suite::ResultHolder::ResultFailureHandler(Result result, const char* filename, int lineno, const char* fmt, ::std::va_list vlist)
262 {
263 if (GetCurrent())
264 {
265 GetCurrent()->m_Result = result;
266 longjmp(GetCurrent()->m_Jmpbuf, 1);
267 NN_TLOG_("longjmp failed.");
268 nn::svc::Break(nn::dbg::BREAK_REASON_PANIC);
269 }
270 else
271 {
272 char buf[256];
273 ::std::vsnprintf(buf, sizeof(buf), fmt, vlist);
274 NN_TLOG_("Result Failure\n");
275 nn::dbg::PrintResult(result);
276
277 NN_TASSERT_(s_CurrentSuite);
278 s_CurrentSuite->OnPanic(filename, lineno, buf);
279 // Should not return
280 NN_TLOG_("Failed OnPanic\n");
281 nn::svc::Break(nn::dbg::BREAK_REASON_PANIC);
282 }
283 }
284
ResultPanicHandler(Result result,const char * filename,int lineno,const char * fmt,::std::va_list vlist)285 void Suite::ResultHolder::ResultPanicHandler(Result result, const char* filename, int lineno, const char* fmt, ::std::va_list vlist)
286 {
287 if (GetCurrent())
288 {
289 GetCurrent()->m_Result = result;
290 longjmp(GetCurrent()->m_Jmpbuf, 1);
291 NN_TLOG_("longjmp failed.");
292 nn::svc::Break(nn::dbg::BREAK_REASON_PANIC);
293 }
294 else
295 {
296 NN_TASSERT_(s_CurrentSuite);
297 char buf[256];
298 ::std::vsnprintf(buf, sizeof(buf), fmt, vlist);
299 NN_TLOG_("Panic Result\n");
300 nn::dbg::PrintResult(result);
301 s_CurrentSuite->OnPanic(filename, lineno, buf);
302 // Should not return
303 NN_TLOG_("Failed OnPanic\n");
304 nn::svc::Break(nn::dbg::BREAK_REASON_PANIC);
305 }
306 }
307
RunSpecificTestFunc(const char * testFuncName,Output & output,bool contAfterFail)308 bool Suite::RunSpecificTestFunc(const char* testFuncName, Output& output, bool contAfterFail)
309 {
310 //Get TestInfo that includes the appropriate test functions
311 String funcNameStr = String(testFuncName);
312 TestInfo testInfo;
313 if(GetTestInfo(funcNameStr, testInfo) == false)
314 {
315 //When the appropriate test functions are not registered in the Suite
316 return false;
317 }
318
319 PreProcessForSpecificTest(&output, contAfterFail);
320 bool result = DoRunSpecificTest(testInfo, &output);
321 PostProcessForSpecificTest();
322
323 return result;
324 }
325
RunSpecificTestFunc(const char * subSuiteName,const char * testFuncName,Output & output,bool contAfterFail)326 bool Suite::RunSpecificTestFunc(const char* subSuiteName, const char* testFuncName, Output& output, bool contAfterFail)
327 {
328 //Get the sub Suite
329 Suite* correspondSuite = NULL;
330 String suiteNameStr = String(subSuiteName);
331 if(GetSubSuite(suiteNameStr, &correspondSuite) == false)
332 {
333 //When the specified sub Suite does not exist
334 return false;
335 }
336
337 return correspondSuite->RunSpecificTestFunc(testFuncName, output, contAfterFail);
338 }
339
RunSpecificTestFunc(s32 testFuncId,Output & output,bool contAfterFail)340 bool Suite::RunSpecificTestFunc(s32 testFuncId, Output& output, bool contAfterFail)
341 {
342 //Get TestInfo that includes the appropriate test functions
343 TestInfo testInfo;
344 if(GetTestSuiteAndInfo(testFuncId, NULL, testInfo) == false)
345 {
346 //When the appropriate test functions are not registered in the Suite
347 return false;
348 }
349
350 PreProcessForSpecificTest(&output, contAfterFail);
351 bool result = DoRunSpecificTest(testInfo, &output);
352 PostProcessForSpecificTest();
353
354 return result;
355 }
356
GetTestFuncInfos(s32 testId,String & suiteName,String & testFuncName)357 bool Suite::GetTestFuncInfos(s32 testId, String& suiteName, String& testFuncName)
358 {
359 Suite* correspondSuite = NULL;
360 TestInfo correspondTestInfo;
361 bool resultGetInfo = GetTestSuiteAndInfo(testId, &correspondSuite, correspondTestInfo);
362 if(resultGetInfo == true)
363 {
364 suiteName = correspondSuite->GetTestName();
365 testFuncName = correspondTestInfo.testName;
366 return true;
367 }
368 else
369 {
370 return false;
371 }
372 }
373
GetTestFuncInfos(s32 testId,String & suiteName,String & testFuncName,String & testInfo)374 bool Suite::GetTestFuncInfos(s32 testId, String& suiteName, String& testFuncName, String& testInfo)
375 {
376 Suite* correspondSuite = NULL;
377 TestInfo correspondTestInfo;
378 bool resultGetInfo = GetTestSuiteAndInfo(testId, &correspondSuite, correspondTestInfo);
379 if(resultGetInfo == true)
380 {
381 suiteName = correspondSuite->GetTestName();
382 testFuncName = correspondTestInfo.testName;
383 testInfo = correspondTestInfo.testInfo;
384 return true;
385 }
386 else
387 {
388 return false;
389 }
390 }
391
392
DoRunSpecificTest(Suite::TestInfo & testInfo,Output * pOutput)393 bool Suite::DoRunSpecificTest(Suite::TestInfo& testInfo, Output* pOutput)
394 {
395 /*
396 * Success or failure of the test is set in m_IsTestSuccess. Before the test starts, m_IsTestSuccess is set to true. When it fails, it is reset to false.
397 */
398 m_IsTestSuccess = true;
399 Time timeCase;
400 bool testResult = true;
401
402 SetUp();
403
404 pOutput->OnTestStart(testInfo.testName);
405
406 timeCase.Start();
407 ::std::jmp_buf jumpBuf;
408 m_pExitTestJumpBuffer = &jumpBuf;
409 s_CurrentSuite = this;
410 if(setjmp(jumpBuf) == 0)
411 {
412 (this->*testInfo.testFunc)();
413 }
414 else
415 {
416 /*
417 For test in the "do not proceed to subsequent tests when an assert is encountered" mode, ExitCurrentTest is called and this path is taken.
418 (Mode is specified by the Run() or RunSpecificTestFunc() argument)
419 For test in the "do not proceed to subsequent tests when an assert is encountered" mode,this path is not taken at the time of the assert.
420 Instead enters Suite::AddAssertInfo() and sets m_IsTestSuccess.
421 */
422 m_IsTestSuccess = false;
423 }
424 m_pExitTestJumpBuffer = NULL;
425 s_CurrentSuite = 0;
426 timeCase.End();
427 pOutput->OnTestEnd(testInfo.testName, testResult, timeCase);
428
429 TearDown();
430
431 return m_IsTestSuccess;
432 }
433
GetSubSuite(String & subSuiteName,Suite ** ppSuite)434 bool Suite::GetSubSuite(String& subSuiteName, Suite** ppSuite)
435 {
436 NN_TASSERT_(ppSuite != NULL);
437 bool isSuiteFound = false;
438 const u32 subSuiteNum = m_SubSuites.GetNum();
439
440 for(unsigned int i = 0; i < subSuiteNum; ++i)
441 {
442 Suite* suitPos = m_SubSuites.GetElement(i);
443 if(suitPos->GetTestName() == subSuiteName)
444 {
445 *ppSuite = suitPos;
446 isSuiteFound = true;
447 break;
448 }
449 }
450
451 return isSuiteFound;
452 }
453
GetTestSuiteAndInfo(s32 testId,Suite ** ppSuite,Suite::TestInfo & testInfoBuf)454 bool Suite::GetTestSuiteAndInfo(s32 testId, Suite** ppSuite, Suite::TestInfo& testInfoBuf)
455 {
456 //Confirms whether the test function corresponding to the ID exists
457 if(testId > m_TotalTestsNum)
458 {
459 //When it does not exist
460 return false;
461 }
462
463 //Confirms whether the test corresponding to the ID exists in this Suite itself or in a sub-Suite
464 if(testId <= m_Tests.GetNum())
465 {
466 //When it exists in this Suite itself
467 if(GetTestInfo(testId, testInfoBuf) == false)
468 {
469 return false;
470 }
471
472 if(ppSuite != NULL)
473 {
474 *ppSuite = this;
475 }
476 return true;
477 }
478 else
479 {
480 //When it exists in a sub-Suite
481 u32 checkedIdMax = m_Tests.GetNum();//Maximum value of the confirmed ID. Used to check in which sub-Suite the function corresponding to the ID exists.//
482 const u32 subSuiteNum = m_SubSuites.GetNum();
483 for(unsigned int i = 0; i < subSuiteNum; ++i)
484 {
485 Suite* pSuit = m_SubSuites.GetElement(i);
486 const u32 subSuiteMaxId = checkedIdMax + pSuit->GetTotalTestNum();
487 if(testId > subSuiteMaxId)
488 {
489 //Because the ID is not included in the sub-Suite indicated by pSuit, updates the checked ID and moves on to check the next sub-Suite.
490 checkedIdMax = subSuiteMaxId;
491 }
492 else
493 {
494 /*
495 * When the ID is included in the sub-Suite indicated by pSuit, the Suite and TestInfo is obtained with GetTestSuiteAndInfo() of that sub-Suite.
496 * The argument ID is converted to testID in the sub-Suite origin.
497 */
498 const u32 testIdInSubSuite = testId - checkedIdMax;
499 pSuit->GetTestSuiteAndInfo(testIdInSubSuite, ppSuite, testInfoBuf);
500 break;
501 }
502 }
503
504 return true;
505 }
506 }
507
508
GetTestInfo(String funcName,Suite::TestInfo & testInfoBuf)509 bool Suite::GetTestInfo(String funcName, Suite::TestInfo& testInfoBuf)
510 {
511 bool isInfoFound = false;
512 const u32 infoNum = m_Tests.GetNum();
513 for(unsigned int i = 0; i < infoNum; ++i)
514 {
515 TestInfo pTestInfo = m_Tests.GetElement(i);
516 if(pTestInfo.testName == funcName)
517 {
518 isInfoFound = true;
519 testInfoBuf = pTestInfo;
520 break;
521 }
522 }
523
524 return isInfoFound;
525 }
526
527
GetTestInfo(s32 testInfoId,Suite::TestInfo & testInfoBuf)528 bool Suite::GetTestInfo(s32 testInfoId, Suite::TestInfo& testInfoBuf)
529 {
530 //Confirms whether TestInfo corresponding to the ID exists
531 if((testInfoId > m_Tests.GetNum()) || (testInfoId == 0))
532 {
533 //When it does not exist
534 return false;
535 }
536
537 testInfoBuf = m_Tests.GetElement((testInfoId-1));
538 return true;
539 }
540
Initialize()541 void Suite::Initialize()
542 {
543 if(m_IsInitialized == false)
544 {
545 InitializeSuite();
546 m_IsInitialized = true;
547 }
548 }
549
Finalize()550 void Suite::Finalize()
551 {
552 if(m_IsInitialized == true)
553 {
554 FinalizeSuite();
555 m_IsInitialized = false;
556 }
557 }
558
PreProcessForSpecificTest(Output * pOutput,bool contAfterFail)559 void Suite::PreProcessForSpecificTest(Output* pOutput, bool contAfterFail)
560 {
561 Initialize();
562 m_Continue = contAfterFail;
563 m_pOutput = pOutput;
564 m_pOutput->OnInitialize(1, 1);
565 m_pOutput->OnSuiteStart(1, GetTestName());
566 }
567
PostProcessForSpecificTest()568 void Suite::PostProcessForSpecificTest()
569 {
570 Time time;//In the operation of a specific test, the operation time is output as 0 since it is meaningless to measure the operation time as a Suite (only the single unit test operation time needs to be known) //
571 m_pOutput->OnSuiteEnd(1, GetTestName(), time);
572 m_pOutput->OnFinished(1, time);
573 Finalize();
574 }
575
GetCurrentSuite()576 Suite* Suite::GetCurrentSuite()
577 {
578 if ( !s_CurrentSuite )
579 {
580 NN_PANIC("No running test suite");
581 }
582 return s_CurrentSuite;
583 }
584 }}
585
nnResultFailureHandler(nnResult result,const char * filename,int lineno,const char * fmt,...)586 extern "C" int nnResultFailureHandler(nnResult result, const char* filename, int lineno, const char* fmt, ...)
587 {
588 va_list vlist;
589 va_start(vlist, fmt);
590 nn::test::Suite::ResultHolder::ResultFailureHandler(reinterpret_cast<nn::Result&>(result), filename, lineno, fmt, vlist);
591 va_end(vlist);
592 return 0;
593 }
594
nnResultTFailureHandler(nnResult result,const char * filename,int lineno,const char * fmt,...)595 extern "C" int nnResultTFailureHandler(nnResult result, const char* filename, int lineno, const char* fmt, ...)
596 {
597 va_list vlist;
598 va_start(vlist, fmt);
599 nn::test::Suite::ResultHolder::ResultFailureHandler(reinterpret_cast<nn::Result&>(result), filename, lineno, fmt, vlist);
600 va_end(vlist);
601 return 0;
602 }
603
nnResultPanicHandler(nnResult result,const char * filename,int lineno,const char * fmt,...)604 extern "C" int nnResultPanicHandler(nnResult result, const char* filename, int lineno, const char* fmt, ...)
605 {
606 va_list vlist;
607 va_start(vlist, fmt);
608 nn::test::Suite::ResultHolder::ResultPanicHandler(reinterpret_cast<nn::Result&>(result), filename, lineno, fmt, vlist);
609 va_end(vlist);
610 return 0;
611 }
612
nnResultTPanicHandler(nnResult result,const char * filename,int lineno,const char * fmt,...)613 extern "C" int nnResultTPanicHandler(nnResult result, const char* filename, int lineno, const char* fmt, ...)
614 {
615 va_list vlist;
616 va_start(vlist, fmt);
617 nn::test::Suite::ResultHolder::ResultPanicHandler(reinterpret_cast<nn::Result&>(result), filename, lineno, fmt, vlist);
618 va_end(vlist);
619 return 0;
620 }
621 /*
622 void nn::dbg::Panic(const char* filename, int lineno, const char* fmt, va_list vlist)
623 {
624 nn::Result result = nnMakeInvalidResult();
625 nn::test::Suite::ResultHolder::ResultPanicHandler(reinterpret_cast>(result), filename, lineno, fmt, vlist);
626 }
627 void nn::dbg::TVPanic(const char* filename, int lineno, const char* fmt, va_list vlist)
628 {
629 nn::Result result = nnMakeInvalidResult();
630 nn::test::Suite::ResultHolder::ResultPanicHandler(reinterpret_cast>(result), filename, lineno, fmt, vlist);
631 }
632
633 */
634