You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
527 lines
17 KiB
527 lines
17 KiB
--[[
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// date : 2013-5-4
|
|
// auth : macroli(idehong@gmail.com)
|
|
// ver : 0.3
|
|
// desc : ltest - lua tester
|
|
//////////////////////////////////////////////////////////////////////////
|
|
--]]
|
|
------------------------------------------------------------------------------
|
|
|
|
--module(..., package.seeall)
|
|
|
|
VERSION = "0.51"
|
|
|
|
-- for test environment
|
|
TestEnvironment = {}
|
|
function TestEnvironment:new(oo)
|
|
local o = oo or {}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end
|
|
|
|
function TestEnvironment.privateName(self)
|
|
return "env_macro"
|
|
end
|
|
|
|
function TestEnvironment.SetUp(self)
|
|
end
|
|
|
|
function TestEnvironment.TearDown(self)
|
|
end
|
|
|
|
|
|
-- for test case
|
|
TestCase = {}
|
|
function TestCase:new(oo)
|
|
local o = oo or {}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end
|
|
|
|
function TestCase.privateName(self)
|
|
return "case_macro"
|
|
end
|
|
|
|
function TestCase.SetUpTestCase(self)
|
|
end
|
|
|
|
function TestCase.TearDownTestCase(self)
|
|
end
|
|
|
|
function TestCase.SetUp(self)
|
|
end
|
|
|
|
function TestCase.TearDown(self)
|
|
end
|
|
|
|
require("ltest.loutput")
|
|
require("ltest.lassert")
|
|
|
|
|
|
-- for assert
|
|
local _atER = true -- for event result
|
|
local _atStopLv = 2 -- for stop level
|
|
local _atStopTip = "macro_error_stop" -- for stop level
|
|
local _atErrLv = 6 -- for error level
|
|
local _atMgr = false
|
|
|
|
function ASSERT_EQ(v1, v2, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atER = _atMgr:AssertEQ(v1, v2):PCR(bNoP, _atErrLv, "ASSERT_EQ failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function ASSERT_LT(v1, v2, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atER = _atMgr:AssertLT(v1, v2):PCR(bNoP, _atErrLv, "ASSERT_LT failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function ASSERT_LE(v1, v2, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atER = _atMgr:AssertLE(v1, v2):PCR(bNoP, _atErrLv, "ASSERT_LE failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function ASSERT_GT(v1, v2, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atER = _atMgr:AssertGT(v1, v2):PCR(bNoP, _atErrLv, "ASSERT_GT failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function ASSERT_GE(v1, v2, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atER = _atMgr:AssertGE(v1, v2):PCR(bNoP, _atErrLv, "ASSERT_GE failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function ASSERT_NE(v1, v2, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atER = _atMgr:AssertNE(v1, v2):PCR(bNoP, _atErrLv, "ASSERT_NE failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function ASSERT_NEAR(v1, v2, nearValue, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atER = _atMgr:AssertNear(v1, v2, nearValue):PCR(bNoP, _atErrLv, "ASSERT_NEAR failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function ASSERT_TRUE(v1, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atER = _atMgr:AssertTrue(v1):PCR1(bNoP, _atErrLv, "ASSERT_TRUE failed", v1); return _atMgr
|
|
end
|
|
|
|
function ASSERT_FALSE(v1, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atER = _atMgr:AssertFalse(v1):PCR1(bNoP, _atErrLv, "ASSERT_FALSE failed", v1); return _atMgr
|
|
end
|
|
|
|
|
|
function EXCEPT_EQ(v1, v2, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atMgr:AssertEQ(v1, v2):PCR(bNoP, _atErrLv, "EXCEPT_EQ failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function EXCEPT_LT(v1, v2, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atMgr:AssertLT(v1, v2):PCR(bNoP, _atErrLv, "EXCEPT_LT failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function EXCEPT_LE(v1, v2, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atMgr:AssertLE(v1, v2):PCR(bNoP, _atErrLv, "EXCEPT_LE failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function EXCEPT_GT(v1, v2, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atMgr:AssertGT(v1, v2):PCR(bNoP, _atErrLv, "EXCEPT_GT failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function EXCEPT_GE(v1, v2, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atMgr:AssertGE(v1, v2):PCR(bNoP, _atErrLv, "EXCEPT_GE failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function EXCEPT_NE(v1, v2, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atMgr:AssertNE(v1, v2):PCR(bNoP, _atErrLv, "EXCEPT_NE failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function EXCEPT_NEAR(v1, v2, nearValue, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atMgr:AssertNear(v1, v2, nearValue):PCR(bNoP, _atErrLv, "EXCEPT_NEAR failed", v1, v2); return _atMgr
|
|
end
|
|
|
|
function EXCEPT_TRUE(v1, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atMgr:AssertTrue(v1):PCR1(bNoP, _atErrLv, "EXCEPT_TRUE failed", v1); return _atMgr
|
|
end
|
|
|
|
function EXCEPT_FALSE(v1, bNoP)
|
|
if not _atMgr then return end
|
|
if not _atER then error(_atStopTip, _atStopLv) end
|
|
_atMgr:AssertFalse(v1):PCR1(bNoP, _atErrLv, "EXCEPT_FALSE failed", v1); return _atMgr
|
|
end
|
|
|
|
|
|
-- for test mgr
|
|
local TestMgr = {}
|
|
function TestMgr:new(oo)
|
|
local o = {
|
|
data = {
|
|
oOut = false, generalGlobalName="global",
|
|
|
|
-- suite seq
|
|
generalSuiteNo=2,
|
|
|
|
-- global suite value
|
|
global={ prop={name="global", generalNo=1,bSys=true, oCls=false, no=1, costTime=0, totalNum=0, okNum=0, failedNum=0,}, list={}},
|
|
|
|
-- key for name, value = {prop={}, list={}}
|
|
all_suite = {},
|
|
|
|
-- filter case name
|
|
notcase = {privateName=true, SetUpTestCase=true, TearDownTestCase=true, SetUp=true, TearDown=true, TestCase=true, new=new},
|
|
},
|
|
|
|
-- user use para
|
|
para = {
|
|
ltest_list_tests=false, -- List the names of all tests instead of running them.
|
|
ltest_repeat=false, -- Run the tests repeatedly; use a negative count to repeat forever.
|
|
ltest_filter=false, -- Run only the tests whose name matches
|
|
|
|
ltest_list_falied=false, -- Generate an TXT report in the given directory or with the given file name
|
|
ltest_output=false, -- Generate an XML report in the given directory or with the given file name
|
|
ltest_silence=false, -- if true then silence output
|
|
},
|
|
|
|
-- stat info
|
|
stat = {},
|
|
}
|
|
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end
|
|
|
|
function TestMgr.Init(self, tPara)
|
|
if tPara and tPara.ltest_list_tests then self.para.ltest_list_tests = tPara.ltest_list_tests end
|
|
if tPara and tPara.ltest_repeat then self.para.ltest_repeat = tPara.ltest_repeat end
|
|
if tPara and tPara.ltest_filter then self.para.ltest_filter = tPara.ltest_filter end
|
|
if tPara and tPara.ltest_list_falied then self.para.ltest_list_falied = tPara.ltest_list_falied end
|
|
if tPara and tPara.ltest_output then self.para.ltest_output = tPara.ltest_output end
|
|
if tPara and tPara.ltest_silence then self.ltest_silence = tPara.ltest_silence end
|
|
|
|
local tOutputOther = {}
|
|
if self.para.ltest_list_tests then
|
|
local oTmp = CAllCaseListOutPut:new({filename=self.para.ltest_list_tests})
|
|
table.insert(tOutputOther, oTmp)
|
|
end
|
|
if self.para.ltest_list_falied then
|
|
local oTmp = CFailedCaseListOutPut:new({filename=self.para.ltest_list_falied})
|
|
table.insert(tOutputOther, oTmp)
|
|
end
|
|
|
|
self.data.oOutput = CmdTestOutPut:new({outer = tOutputOther, silence=self.ltest_silence})
|
|
_atMgr = CAssertMgr:new({oOutput=self.data.oOutput})
|
|
|
|
self.data.all_suite[self.data.generalGlobalName] = self.data.global
|
|
end
|
|
|
|
function TestMgr.Fini(self)
|
|
self.data.all_suite = {}
|
|
self.data.generalSuiteNo = 2
|
|
self.data.global={ prop={name="global", generalNo=1,bSys=true, oCls=false, no=1, costTime=0, totalNum=0, okNum=0, failedNum=0,}, list={}}
|
|
end
|
|
|
|
function TestMgr.AddSuite(self, strSuiteName, oCls, filterCaseName)
|
|
if not oCls or TestCase:privateName() ~= oCls:privateName() or self.data.notcase[strSuiteName] then return false end
|
|
local strFind = "Test"
|
|
if filterCaseName and type(filterCaseName) == "string" then strFind = filterCaseName end
|
|
|
|
local tSuite = self:addRealSuite(strSuiteName, oCls)
|
|
if not tSuite then return false end
|
|
|
|
local tCaseKey = {}
|
|
for k, v in pairs(oCls) do
|
|
if type(k) == "string" and "__" ~= string.sub(k, 1, 2) and type(v) == "function" and string.find(k, strFind) and not self.data.notcase[k] then
|
|
table.insert(tCaseKey, k)
|
|
end
|
|
end
|
|
for k, v in pairs(oCls.__index) do
|
|
if type(k) == "string" and "__" ~= string.sub(k, 1, 2) and type(v) == "function" and string.find(k, strFind) and not self.data.notcase[k] then
|
|
table.insert(tCaseKey, k)
|
|
end
|
|
end
|
|
|
|
table.sort(tCaseKey)
|
|
for k, v in pairs(tCaseKey) do
|
|
self:addRealCase(tSuite, v, oCls[v], nil)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function TestMgr.AddCase(self, strCaseName, oFun, tPara, strSuiteName, oCls)
|
|
if type(oFun) ~= "function" or type(strCaseName) ~= type("") then return false end
|
|
|
|
local tSuite = false
|
|
if strSuiteName then
|
|
tSuite = self:addRealSuite(strSuiteName, oCls)
|
|
else
|
|
tSuite = self:addRealSuite(self.data.generalGlobalName, oCls)
|
|
end
|
|
|
|
if tSuite then return self:addRealCase(tSuite, strCaseName, oFun, tPara) else return false end
|
|
end
|
|
|
|
function TestMgr.Run(self, oEnv)
|
|
local oOutput = self.data.oOutput
|
|
if self.para.ltest_filter and type(self.para.ltest_filter) == type("") then
|
|
oOutput:FilterInfo("Note: ltest filter = %s\r\n", self.para.ltest_filter)
|
|
end
|
|
|
|
local iCount, iGroup, tSuiteCaseKey = self:countAllCase(self.para.ltest_filter)
|
|
oOutput:BeginGroupSuite( iCount, iGroup )
|
|
|
|
local bUseEnv = (oEnv and TestEnvironment:privateName() == oEnv:privateName())
|
|
if bUseEnv then oEnv:SetUp(); oOutput:BeginGroupEnv() end
|
|
|
|
local tFailedList = self:runAllSuite(oOutput, tSuiteCaseKey)
|
|
|
|
if bUseEnv then oEnv:TearDown(); oOutput:EndGroupEnv() end
|
|
|
|
local iTotalTime = 0
|
|
for k, v in pairs(self.data.all_suite) do
|
|
iTotalTime = iTotalTime + v.prop.costTime
|
|
end
|
|
|
|
oOutput:EndGroupSuite(iTotalTime)
|
|
oOutput:StaticInfo()
|
|
|
|
self.data.stat = oOutput:GetStaticInfo()
|
|
self:Fini()
|
|
return 0
|
|
end
|
|
|
|
function TestMgr.GetStatInfo(self)
|
|
return self.data.stat
|
|
end
|
|
|
|
function TestMgr.countAllCase(self, strFilter)
|
|
-- parset filter
|
|
local tFilter = {}
|
|
local iPosStart, iPosEnd = 1, 1
|
|
while strFilter do
|
|
iPosEnd = string.find(strFilter, ":", iPosStart)
|
|
if not iPosEnd then
|
|
table.insert(tFilter, string.sub(strFilter, iPosStart, #strFilter - 1) )
|
|
break
|
|
end
|
|
table.insert(tFilter, string.sub(strFilter, iPosStart, iPosEnd - 3) )
|
|
iPosStart = iPosEnd + 1
|
|
end
|
|
|
|
-- filter case
|
|
local iCount, iGroup = 0, 0
|
|
local tSuiteKey = {}
|
|
local bUsed = true
|
|
for k, v in pairs(self.data.all_suite) do
|
|
local tCaseKey = {}
|
|
for kk, vv in pairs(v.list) do
|
|
if tFilter and #tFilter > 0 then
|
|
bUsed = false
|
|
for wk, kf in pairs(tFilter) do
|
|
if bUsed then break end
|
|
if k == self.data.generalGlobalName then
|
|
for w in string.gmatch(k .. "." .. vv.label, kf) do bUsed = true; break end
|
|
else
|
|
for w in string.gmatch(vv.label, kf) do bUsed = true; break end
|
|
end
|
|
end
|
|
end
|
|
if bUsed then table.insert(tCaseKey, {name=kk, id=vv.no}) end
|
|
end
|
|
if #tCaseKey > 0 then
|
|
table.sort(tCaseKey, function(a, b) return a.id < b.id end)
|
|
table.insert(tSuiteKey, {name=k, id=v.prop.no, list=tCaseKey})
|
|
iCount = iCount + #tCaseKey
|
|
iGroup = iGroup + 1
|
|
end
|
|
end
|
|
table.sort(tSuiteKey, function(a, b) return a.id < b.id end)
|
|
return iCount, iGroup, tSuiteKey
|
|
end
|
|
|
|
function TestMgr.runAllSuite(self, oOutput, tSuiteCaseKey)
|
|
local tFailedList = {}
|
|
for k, v in ipairs(tSuiteCaseKey) do
|
|
self:runGroupSuite(tFailedList, self.data.all_suite[v.name], v.list, oOutput)
|
|
end
|
|
return tFailedList
|
|
end
|
|
|
|
|
|
function TestMgr.runGroupSuite(self, tFailedList, tSuite, tCaseKey, oOutput)
|
|
oOutput:BeginSuite( #tCaseKey, tSuite.prop.name )
|
|
if tSuite.prop.oCls then tSuite.prop.oCls:SetUpTestCase() end
|
|
|
|
local bExecResult = false
|
|
for k, v in ipairs(tCaseKey) do
|
|
local oTmpCase = tSuite.list[v.name]
|
|
oTmpCase.result, oTmpCase.costTime = self:runCase(oTmpCase, tSuite, oOutput)
|
|
tSuite.prop.costTime = oTmpCase.costTime + tSuite.prop.costTime
|
|
|
|
if not oTmpCase.result then
|
|
tSuite.prop.failedNum = 1 + tSuite.prop.failedNum
|
|
table.insert(tFailedList, oTmpCase.label)
|
|
else
|
|
tSuite.prop.okNum = 1 + tSuite.prop.okNum
|
|
end
|
|
end
|
|
|
|
if tSuite.prop.oCls then tSuite.prop.oCls:TearDownTestCase() end
|
|
oOutput:EndSuite( #tCaseKey, tSuite.prop.name, tSuite.prop.costTime, tSuite.prop.failedNum)
|
|
end
|
|
|
|
|
|
|
|
function TestMgr.runCase(self, oTmpCase, tSuite, oOutput)
|
|
-- for assert step over
|
|
_atER = true
|
|
local bExecResult, bResult, strError = false, false, ""
|
|
|
|
local iItemTime = os.clock()
|
|
oOutput:BeginCase(oTmpCase.label)
|
|
|
|
if tSuite.prop.oCls then
|
|
tSuite.prop.oCls:SetUp()
|
|
if oTmpCase.para and #oTmpCase.para > 0 then
|
|
bResult, strError = pcall(function () oTmpCase.fun(tSuite.prop.oCls, unpack( oTmpCase.para) ) end)
|
|
else
|
|
bResult, strError = pcall(function () oTmpCase.fun(tSuite.prop.oCls) end)
|
|
end
|
|
else
|
|
if oTmpCase.para and #oTmpCase.para > 0 then
|
|
bResult, strError = pcall(function () oTmpCase.fun( unpack( oTmpCase.para) ) end)
|
|
else
|
|
bResult, strError = pcall(function () oTmpCase.fun() end)
|
|
end
|
|
end
|
|
|
|
if not bResult or not _atMgr:GetResult() then
|
|
if strError and not (not _atER and string.find(strError, _atStopTip)) then oOutput:FailedTxt(strError) end
|
|
else
|
|
bExecResult = true
|
|
end
|
|
|
|
if tSuite.prop.oCls then tSuite.prop.oCls:TearDown() end
|
|
iItemTime = os.clock() - iItemTime
|
|
oOutput:EndCase(true, bExecResult, iItemTime, oTmpCase.label)
|
|
|
|
return bExecResult, iItemTime
|
|
end
|
|
|
|
|
|
function TestMgr.addRealSuite(self, strSuiteName, cCls)
|
|
if oCls and (type(oCls) ~= type({}) or type(oCls.privateName) ~= type(print) or TestCase:privateName() ~= oCls:privateName()) then return end
|
|
if not strSuiteName or type(strSuiteName) ~= type("") or self.data.notcase[strSuiteName] then return end
|
|
|
|
local tSuiteGroup = self.data.all_suite
|
|
if not tSuiteGroup[strSuiteName] then
|
|
tSuiteGroup[strSuiteName] = {prop={name=strSuiteName, generalNo=1, bSys=false, oCls=cCls, no=self.data.generalSuiteNo, costTime=0, totalNum=0, okNum=0, failedNum=0,}, list={}}
|
|
self.data.generalSuiteNo = 1 + self.data.generalSuiteNo
|
|
end
|
|
return tSuiteGroup[strSuiteName]
|
|
end
|
|
|
|
function TestMgr.addRealCase(self, tSuite, strCaseName, oFun, tPara)
|
|
if strCaseName and self.data.notcase[strCaseName] then return false end
|
|
|
|
local tSuiteProp = tSuite.prop
|
|
local tCurrSuite = tSuite.list
|
|
|
|
if tCurrSuite[strCaseName] then return false end
|
|
local strLabel = strCaseName
|
|
if not tSuiteProp.bSys then strLabel = tSuiteProp.name .. "." .. strCaseName end
|
|
tCurrSuite[strCaseName] = {fun=oFun, para=tPara, label=strLabel, result=true, costTime=0, no=tSuiteProp.generalNo,}
|
|
tSuiteProp.generalNo = 1 + tSuiteProp.generalNo
|
|
tSuiteProp.totalNum = 1 + tSuiteProp.totalNum
|
|
return true
|
|
end
|
|
|
|
|
|
local _oTestMgr = false
|
|
--------------------------------------------------------------------------------
|
|
-- for init help function
|
|
function InitLTest(tPara)
|
|
if not _oTestMgr then _oTestMgr = TestMgr:new() else _oTestMgr:Fini() end
|
|
_oTestMgr:Init(tPara)
|
|
return _oTestMgr
|
|
end
|
|
|
|
-- add all case of suite(oCls) by match ��filterCaseName��
|
|
-- oCls : <table>, for suite class, the mt must is ltest.TestCase
|
|
-- strSuiteName : <string>, for suite name
|
|
-- filterCaseName : <string>, math case name
|
|
-- return true if add success else return false
|
|
function AddLTestSuite(oCls, strSuiteName, filterCaseName)
|
|
if not _oTestMgr then return false end
|
|
return _oTestMgr:AddSuite(strSuiteName, oCls, filterCaseName)
|
|
end
|
|
|
|
-- add a case of suite(oCls)
|
|
-- oFun : <function>, for case
|
|
-- strCaseName : <string>, for case name
|
|
-- tPara : <table>, for oFun para
|
|
-- strSuiteName : <string>, for suite name
|
|
-- oCls : <table>, for suite class, the mt must is ltest.TestCase
|
|
-- return true if add success else return false
|
|
function AddLTestCase(oFun, strCaseName, tPara, strSuiteName, oCls)
|
|
if not _oTestMgr then return false end
|
|
return _oTestMgr:AddCase(strCaseName, oFun, tPara, strSuiteName, oCls)
|
|
end
|
|
|
|
|
|
-- add a group case of suite(oCls)
|
|
-- oFun : <function>, for case
|
|
-- strCaseName : <string>, for case name
|
|
-- tGroupPara : <table>, for oFun para, like {{},{},...}
|
|
-- strSuiteName : <string>, for suite name
|
|
-- oCls : <table>, for suite class, the mt must is ltest.TestCase
|
|
-- return true if add success else return false
|
|
function AddLTestGroupCase(oFun, strCaseName, tGroupPara, strSuiteName, oCls)
|
|
if not _oTestMgr then return false end
|
|
for i, v in ipairs(tGroupPara) do
|
|
_oTestMgr:AddCase(strCaseName .. "/" .. i, oFun, v, strSuiteName, oCls)
|
|
end
|
|
end
|
|
|
|
|
|
-- run all test case
|
|
-- oEnv : <function>, the mt must is ltest.TestEnvironment
|
|
-- return 0 if success
|
|
function RunAllTests(oEnv)
|
|
if not _oTestMgr then return false end
|
|
return _oTestMgr:Run(oEnv)
|
|
end
|
|
|
|
-- return total_suite��total_case�� total_faild
|
|
function GetRunStatInfo()
|
|
if not _oTestMgr then return end
|
|
return _oTestMgr:GetStatInfo()
|
|
end
|
|
|