1 /*---------------------------------------------------------------------------*
2 Project: Horizon
3 File: test_Suite.cpp
4
5 Copyright (C)2009 Nintendo Co., Ltd. All rights reserved.
6
7 These coded instructions, statements, and computer programs contain
8 proprietary information of Nintendo of America Inc. and/or Nintendo
9 Company Ltd., and are protected by Federal copyright law. They may
10 not be disclosed to third parties or copied or duplicated in any form,
11 in whole or in part, without the prior written consent of Nintendo.
12
13 $Rev: 25571 $
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(); // サブスイートを含むテスト数
68 int subSuiteNum = m_SubSuites.GetNum();
69
70 // テスト開始前の初期化
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(); // サブスイートを含まないテスト数
88 m_pOutput = pOutput;
89
90 // このスイートが保持するテストを実行
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 // 子スイートの実行
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 // 1回でもアサートに引っかかっているならこのテストは失敗
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 // メインスレッドでなかったらスレッドを強制終了
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("Is not empty ThreadLocalStorage for Arm9");
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("Not found ThreadLocalStorage for Arm9");
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 NN_TASSERT_(s_CurrentSuite);
273 char buf[256];
274 ::std::vsnprintf(buf, sizeof(buf), fmt, vlist);
275 NN_TLOG_("Result Failure\n");
276 nn::dbg::PrintResult(result);
277 s_CurrentSuite->OnPanic(filename, lineno, buf);
278 // 戻ってこないはず
279 NN_TLOG_("Failed OnPanic\n");
280 nn::svc::Break(nn::dbg::BREAK_REASON_PANIC);
281 }
282 }
283
ResultPanicHandler(Result result,const char * filename,int lineno,const char * fmt,::std::va_list vlist)284 void Suite::ResultHolder::ResultPanicHandler(Result result, const char* filename, int lineno, const char* fmt, ::std::va_list vlist)
285 {
286 if (GetCurrent())
287 {
288 GetCurrent()->m_Result = result;
289 longjmp(GetCurrent()->m_Jmpbuf, 1);
290 NN_TLOG_("longjmp failed.");
291 nn::svc::Break(nn::dbg::BREAK_REASON_PANIC);
292 }
293 else
294 {
295 NN_TASSERT_(s_CurrentSuite);
296 char buf[256];
297 ::std::vsnprintf(buf, sizeof(buf), fmt, vlist);
298 NN_TLOG_("Panic Result\n");
299 nn::dbg::PrintResult(result);
300 s_CurrentSuite->OnPanic(filename, lineno, buf);
301 // 戻ってこないはず
302 NN_TLOG_("Failed OnPanic\n");
303 nn::svc::Break(nn::dbg::BREAK_REASON_PANIC);
304 }
305 }
306
RunSpecificTestFunc(const char * testFuncName,Output & output,bool contAfterFail)307 bool Suite::RunSpecificTestFunc(const char* testFuncName, Output& output, bool contAfterFail)
308 {
309 //該当するテスト関数の含んだTestInfoの取得
310 String funcNameStr = String(testFuncName);
311 TestInfo testInfo;
312 if(GetTestInfo(funcNameStr, testInfo) == false)
313 {
314 //Suiteに該当するテスト関数が登録されていない場合
315 return false;
316 }
317
318 PreProcessForSpecificTest(&output, contAfterFail);
319 bool result = DoRunSpecificTest(testInfo, &output);
320 PostProcessForSpecificTest();
321
322 return result;
323 }
324
RunSpecificTestFunc(const char * subSuiteName,const char * testFuncName,Output & output,bool contAfterFail)325 bool Suite::RunSpecificTestFunc(const char* subSuiteName, const char* testFuncName, Output& output, bool contAfterFail)
326 {
327 //サブSuiteの取得
328 Suite* correspondSuite = NULL;
329 String suiteNameStr = String(subSuiteName);
330 if(GetSubSuite(suiteNameStr, &correspondSuite) == false)
331 {
332 //指定されたサブSuiteが存在しない場合
333 return false;
334 }
335
336 return correspondSuite->RunSpecificTestFunc(testFuncName, output, contAfterFail);
337 }
338
RunSpecificTestFunc(s32 testFuncId,Output & output,bool contAfterFail)339 bool Suite::RunSpecificTestFunc(s32 testFuncId, Output& output, bool contAfterFail)
340 {
341 //該当するテスト関数の含んだTestInfoの取得
342 TestInfo testInfo;
343 if(GetTestSuiteAndInfo(testFuncId, NULL, testInfo) == false)
344 {
345 //Suiteに該当するテスト関数が登録されていない場合
346 return false;
347 }
348
349 PreProcessForSpecificTest(&output, contAfterFail);
350 bool result = DoRunSpecificTest(testInfo, &output);
351 PostProcessForSpecificTest();
352
353 return result;
354 }
355
GetTestFuncInfos(s32 testId,String & suiteName,String & testFuncName)356 bool Suite::GetTestFuncInfos(s32 testId, String& suiteName, String& testFuncName)
357 {
358 Suite* correspondSuite = NULL;
359 TestInfo correspondTestInfo;
360 bool resultGetInfo = GetTestSuiteAndInfo(testId, &correspondSuite, correspondTestInfo);
361 if(resultGetInfo == true)
362 {
363 suiteName = correspondSuite->GetTestName();
364 testFuncName = correspondTestInfo.testName;
365 return true;
366 }
367 else
368 {
369 return false;
370 }
371 }
372
GetTestFuncInfos(s32 testId,String & suiteName,String & testFuncName,String & testInfo)373 bool Suite::GetTestFuncInfos(s32 testId, String& suiteName, String& testFuncName, String& testInfo)
374 {
375 Suite* correspondSuite = NULL;
376 TestInfo correspondTestInfo;
377 bool resultGetInfo = GetTestSuiteAndInfo(testId, &correspondSuite, correspondTestInfo);
378 if(resultGetInfo == true)
379 {
380 suiteName = correspondSuite->GetTestName();
381 testFuncName = correspondTestInfo.testName;
382 testInfo = correspondTestInfo.testInfo;
383 return true;
384 }
385 else
386 {
387 return false;
388 }
389 }
390
391
DoRunSpecificTest(Suite::TestInfo & testInfo,Output * pOutput)392 bool Suite::DoRunSpecificTest(Suite::TestInfo& testInfo, Output* pOutput)
393 {
394 /*
395 * テストの成否は、m_IsTestSuccessに設定する。テスト開始前にm_IsTestSuccessをtrueに設定し、失敗時にはfalseを再設定する
396 */
397 m_IsTestSuccess = true;
398 Time timeCase;
399 bool testResult = true;
400
401 SetUp();
402
403 pOutput->OnTestStart(testInfo.testName);
404
405 timeCase.Start();
406 ::std::jmp_buf jumpBuf;
407 m_pExitTestJumpBuffer = &jumpBuf;
408 s_CurrentSuite = this;
409 if(setjmp(jumpBuf) == 0)
410 {
411 (this->*testInfo.testFunc)();
412 }
413 else
414 {
415 /*
416 「assertにかかると、以降のテストを継続しない」モードのテストでは、ExitCurrentTest が呼ばれこのパスに入る。
417 (モードはRun()やRunSpecificTestFunc()の引数で指定)
418 「assertにかかっても、以降のテストを継続する」モードのテストでは、assert時はこのパスに入らない。
419 代わりにSuite::AddAssertInfo()に入り、m_IsTestSuccessを設定する。
420 */
421 m_IsTestSuccess = false;
422 }
423 m_pExitTestJumpBuffer = NULL;
424 s_CurrentSuite = 0;
425 timeCase.End();
426 pOutput->OnTestEnd(testInfo.testName, testResult, timeCase);
427
428 TearDown();
429
430 return m_IsTestSuccess;
431 }
432
GetSubSuite(String & subSuiteName,Suite ** ppSuite)433 bool Suite::GetSubSuite(String& subSuiteName, Suite** ppSuite)
434 {
435 NN_TASSERT_(ppSuite != NULL);
436 bool isSuiteFound = false;
437 const u32 subSuiteNum = m_SubSuites.GetNum();
438
439 for(unsigned int i = 0; i < subSuiteNum; ++i)
440 {
441 Suite* suitPos = m_SubSuites.GetElement(i);
442 if(suitPos->GetTestName() == subSuiteName)
443 {
444 *ppSuite = suitPos;
445 isSuiteFound = true;
446 break;
447 }
448 }
449
450 return isSuiteFound;
451 }
452
GetTestSuiteAndInfo(s32 testId,Suite ** ppSuite,Suite::TestInfo & testInfoBuf)453 bool Suite::GetTestSuiteAndInfo(s32 testId, Suite** ppSuite, Suite::TestInfo& testInfoBuf)
454 {
455 //IDに該当するテスト関数が存在するかを確認
456 if(testId > m_TotalTestsNum)
457 {
458 //存在しない場合
459 return false;
460 }
461
462 //IDに該当するテスト関数が、このSuite自身に存在するのか、サブSuite内に存在するかを確認
463 if(testId <= m_Tests.GetNum())
464 {
465 //このSuite自身に存在する場合
466 if(GetTestInfo(testId, testInfoBuf) == false)
467 {
468 return false;
469 }
470
471 if(ppSuite != NULL)
472 {
473 *ppSuite = this;
474 }
475 return true;
476 }
477 else
478 {
479 //サブSuiteに存在する場合
480 u32 checkedIdMax = m_Tests.GetNum();//確認済みのIDの最大値。IDに該当する関数がどのサブSuiteに存在するかを調査するために用いる。//
481 const u32 subSuiteNum = m_SubSuites.GetNum();
482 for(unsigned int i = 0; i < subSuiteNum; ++i)
483 {
484 Suite* pSuit = m_SubSuites.GetElement(i);
485 const u32 subSuiteMaxId = checkedIdMax + pSuit->GetTotalTestNum();
486 if(testId > subSuiteMaxId)
487 {
488 //pSuitが示すサブSuiteにIDが含まれないため、調査済みIDを更新し、次のサブSuiteの調査に移る。
489 checkedIdMax = subSuiteMaxId;
490 }
491 else
492 {
493 /*
494 * pSuitが示すサブSuiteにIDが含まれる場合は、そのサブSuiteのGetTestSuiteAndInfo()でSuiteとTestInfoを得る。
495 * 引数のIDは、サブSuite基点でのtestIDに変換する。
496 */
497 const u32 testIdInSubSuite = testId - checkedIdMax;
498 pSuit->GetTestSuiteAndInfo(testIdInSubSuite, ppSuite, testInfoBuf);
499 break;
500 }
501 }
502
503 return true;
504 }
505 }
506
507
GetTestInfo(String funcName,Suite::TestInfo & testInfoBuf)508 bool Suite::GetTestInfo(String funcName, Suite::TestInfo& testInfoBuf)
509 {
510 bool isInfoFound = false;
511 const u32 infoNum = m_Tests.GetNum();
512 for(unsigned int i = 0; i < infoNum; ++i)
513 {
514 TestInfo pTestInfo = m_Tests.GetElement(i);
515 if(pTestInfo.testName == funcName)
516 {
517 isInfoFound = true;
518 testInfoBuf = pTestInfo;
519 break;
520 }
521 }
522
523 return isInfoFound;
524 }
525
526
GetTestInfo(s32 testInfoId,Suite::TestInfo & testInfoBuf)527 bool Suite::GetTestInfo(s32 testInfoId, Suite::TestInfo& testInfoBuf)
528 {
529 //IDに該当するTestInfoが存在するかを確認
530 if((testInfoId > m_Tests.GetNum()) || (testInfoId == 0))
531 {
532 //存在しない場合
533 return false;
534 }
535
536 testInfoBuf = m_Tests.GetElement((testInfoId-1));
537 return true;
538 }
539
Initialize()540 void Suite::Initialize()
541 {
542 if(m_IsInitialized == false)
543 {
544 InitializeSuite();
545 m_IsInitialized = true;
546 }
547 }
548
Finalize()549 void Suite::Finalize()
550 {
551 if(m_IsInitialized == true)
552 {
553 FinalizeSuite();
554 m_IsInitialized = false;
555 }
556 }
557
PreProcessForSpecificTest(Output * pOutput,bool contAfterFail)558 void Suite::PreProcessForSpecificTest(Output* pOutput, bool contAfterFail)
559 {
560 Initialize();
561 m_Continue = contAfterFail;
562 m_pOutput = pOutput;
563 m_pOutput->OnInitialize(1, 1);
564 m_pOutput->OnSuiteStart(1, GetTestName());
565 }
566
PostProcessForSpecificTest()567 void Suite::PostProcessForSpecificTest()
568 {
569 Time time;//特定テストの実施では、Suiteとしての実施時間を計測して意味がない(テスト単体の実施時間がわかればよい)ため、実施時刻には0を出力する//
570 m_pOutput->OnSuiteEnd(1, GetTestName(), time);
571 m_pOutput->OnFinished(1, time);
572 Finalize();
573 }
574
GetCurrentSuite()575 Suite* Suite::GetCurrentSuite()
576 {
577 if ( !s_CurrentSuite )
578 {
579 NN_PANIC("No running test suite");
580 }
581 return s_CurrentSuite;
582 }
583 }}
584
nnResultFailureHandler(nnResult result,const char * filename,int lineno,const char * fmt,...)585 extern "C" int nnResultFailureHandler(nnResult result, const char* filename, int lineno, const char* fmt, ...)
586 {
587 va_list vlist;
588 va_start(vlist, fmt);
589 nn::test::Suite::ResultHolder::ResultFailureHandler(reinterpret_cast<nn::Result&>(result), filename, lineno, fmt, vlist);
590 va_end(vlist);
591 return 0;
592 }
593
nnResultTFailureHandler(nnResult result,const char * filename,int lineno,const char * fmt,...)594 extern "C" int nnResultTFailureHandler(nnResult result, const char* filename, int lineno, const char* fmt, ...)
595 {
596 va_list vlist;
597 va_start(vlist, fmt);
598 nn::test::Suite::ResultHolder::ResultFailureHandler(reinterpret_cast<nn::Result&>(result), filename, lineno, fmt, vlist);
599 va_end(vlist);
600 return 0;
601 }
602
nnResultPanicHandler(nnResult result,const char * filename,int lineno,const char * fmt,...)603 extern "C" int nnResultPanicHandler(nnResult result, const char* filename, int lineno, const char* fmt, ...)
604 {
605 va_list vlist;
606 va_start(vlist, fmt);
607 nn::test::Suite::ResultHolder::ResultPanicHandler(reinterpret_cast<nn::Result&>(result), filename, lineno, fmt, vlist);
608 va_end(vlist);
609 return 0;
610 }
611
nnResultTPanicHandler(nnResult result,const char * filename,int lineno,const char * fmt,...)612 extern "C" int nnResultTPanicHandler(nnResult result, const char* filename, int lineno, const char* fmt, ...)
613 {
614 va_list vlist;
615 va_start(vlist, fmt);
616 nn::test::Suite::ResultHolder::ResultPanicHandler(reinterpret_cast<nn::Result&>(result), filename, lineno, fmt, vlist);
617 va_end(vlist);
618 return 0;
619 }
620 /*
621 void nn::dbg::Panic(const char* filename, int lineno, const char* fmt, va_list vlist)
622 {
623 nn::Result result = nnMakeInvalidResult();
624 nn::test::Suite::ResultHolder::ResultPanicHandler(reinterpret_cast<nn::Result&>(result), filename, lineno, fmt, vlist);
625 }
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<nn::Result&>(result), filename, lineno, fmt, vlist);
631 }
632 */
633