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: 29453 $
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 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 // 戻ってこないはず
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 // 戻ってこないはず
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 //該当するテスト関数の含んだTestInfoの取得
311 String funcNameStr = String(testFuncName);
312 TestInfo testInfo;
313 if(GetTestInfo(funcNameStr, testInfo) == false)
314 {
315 //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 //サブSuiteの取得
329 Suite* correspondSuite = NULL;
330 String suiteNameStr = String(subSuiteName);
331 if(GetSubSuite(suiteNameStr, &correspondSuite) == false)
332 {
333 //指定されたサブSuiteが存在しない場合
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 //該当するテスト関数の含んだTestInfoの取得
343 TestInfo testInfo;
344 if(GetTestSuiteAndInfo(testFuncId, NULL, testInfo) == false)
345 {
346 //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 * テストの成否は、m_IsTestSuccessに設定する。テスト開始前にm_IsTestSuccessをtrueに設定し、失敗時には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 「assertにかかると、以降のテストを継続しない」モードのテストでは、ExitCurrentTest が呼ばれこのパスに入る。
418 (モードはRun()やRunSpecificTestFunc()の引数で指定)
419 「assertにかかっても、以降のテストを継続する」モードのテストでは、assert時はこのパスに入らない。
420 代わりにSuite::AddAssertInfo()に入り、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 //IDに該当するテスト関数が存在するかを確認
457 if(testId > m_TotalTestsNum)
458 {
459 //存在しない場合
460 return false;
461 }
462
463 //IDに該当するテスト関数が、このSuite自身に存在するのか、サブSuite内に存在するかを確認
464 if(testId <= m_Tests.GetNum())
465 {
466 //このSuite自身に存在する場合
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 //サブSuiteに存在する場合
481 u32 checkedIdMax = m_Tests.GetNum();//確認済みのIDの最大値。IDに該当する関数がどのサブSuiteに存在するかを調査するために用いる。//
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 //pSuitが示すサブSuiteにIDが含まれないため、調査済みIDを更新し、次のサブSuiteの調査に移る。
490 checkedIdMax = subSuiteMaxId;
491 }
492 else
493 {
494 /*
495 * pSuitが示すサブSuiteにIDが含まれる場合は、そのサブSuiteのGetTestSuiteAndInfo()でSuiteとTestInfoを得る。
496 * 引数のIDは、サブSuite基点でのtestIDに変換する。
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 //IDに該当するTestInfoが存在するかを確認
531 if((testInfoId > m_Tests.GetNum()) || (testInfoId == 0))
532 {
533 //存在しない場合
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;//特定テストの実施では、Suiteとしての実施時間を計測して意味がない(テスト単体の実施時間がわかればよい)ため、実施時刻には0を出力する//
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<nn::Result&>(result), filename, lineno, fmt, vlist);
626 }
627
628 void nn::dbg::TVPanic(const char* filename, int lineno, const char* fmt, va_list vlist)
629 {
630 nn::Result result = nnMakeInvalidResult();
631 nn::test::Suite::ResultHolder::ResultPanicHandler(reinterpret_cast<nn::Result&>(result), filename, lineno, fmt, vlist);
632 }
633 */
634