model.php 94 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522
  1. <?php
  2. /**
  3. * The model file of test task module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
  7. * @author Chunsheng Wang <chunsheng@cnezsoft.com>
  8. * @package testtask
  9. * @version $Id: model.php 5114 2013-07-12 06:02:59Z chencongzhi520@gmail.com $
  10. * @link https://www.zentao.net
  11. */
  12. ?>
  13. <?php
  14. class testtaskModel extends model
  15. {
  16. /**
  17. * 创建一个测试单。
  18. * Create a test task.
  19. *
  20. * @param object $testtask
  21. * @access public
  22. * @return int|false
  23. */
  24. public function create($testtask)
  25. {
  26. $this->dao->insert(TABLE_TESTTASK)->data($testtask)
  27. ->autoCheck('begin,end')
  28. ->batchcheck($this->config->testtask->create->requiredFields, 'notempty')
  29. ->checkIF(!empty($testtask->begin) && $testtask->begin != '', 'begin', 'date')
  30. ->checkIF(!empty($testtask->end) && $testtask->end != '', 'end', 'date')
  31. ->checkIF(!empty($testtask->begin) && $testtask->begin != '', 'end', 'ge', zget($testtask, 'begin', ''))
  32. ->checkFlow()
  33. ->exec();
  34. if(dao::isError()) return false;
  35. $taskID = $this->dao->lastInsertID();
  36. $this->loadModel('action')->create('testtask', $taskID, 'opened');
  37. return $taskID;
  38. }
  39. /**
  40. * 获取一个产品的测试单列表。
  41. * Get testtasks of a product.
  42. *
  43. * @param int $productID
  44. * @param string $branch
  45. * @param string $type
  46. * @param string $begin
  47. * @param string $end
  48. * @param string $orderBy
  49. * @param object $pager
  50. * @access public
  51. * @return array
  52. */
  53. public function getProductTasks($productID, $branch = 'all', $type = '', $begin = '', $end = '', $orderBy = 'id_desc', $pager = null)
  54. {
  55. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getTesttasks();
  56. $scopeAndStatus = explode(',', $type);
  57. $scope = !empty($scopeAndStatus[0]) ? $scopeAndStatus[0] : '';
  58. $status = !empty($scopeAndStatus[1]) ? $scopeAndStatus[1] : '';
  59. $branch = $scope == 'all' ? 'all' : $branch;
  60. $tasks = $this->fetchTesttaskList($productID, $branch, 0, '', $scope, $status, $begin, $end, $orderBy, $pager);
  61. return $this->processExecutionName($tasks);
  62. }
  63. /**
  64. * 根据项目名称和执行名称来更新执行名称。
  65. * Update the execution name based on the project name and execution name.
  66. *
  67. * @param array $tasks
  68. * @access private
  69. * @return array
  70. */
  71. private function processExecutionName($tasks)
  72. {
  73. foreach($tasks as $task)
  74. {
  75. if(!$task->multiple) continue;
  76. if($task->projectName && $task->executionName)
  77. {
  78. $task->executionName = $task->projectName . '/' . $task->executionName;
  79. continue;
  80. }
  81. if(!$task->executionName) $task->executionName = $task->projectName;
  82. }
  83. return $tasks;
  84. }
  85. /**
  86. * 获取产品对应的单元测试类型的测试单。
  87. * Get product unit tasks.
  88. *
  89. * @param int $productID
  90. * @param string $browseType
  91. * @param string $orderBy
  92. * @param object $pager
  93. * @access public
  94. * @return array
  95. */
  96. public function getProductUnitTasks($productID, $browseType = 'newest', $orderBy = 'id_desc', $pager = null)
  97. {
  98. $begin = '';
  99. $end = '';
  100. $beginAndEnd = $this->loadModel('action')->computeBeginAndEnd($browseType, '', 'next');
  101. if($browseType != 'all' and $browseType != 'newest' and !empty($beginAndEnd))
  102. {
  103. $begin = $beginAndEnd['begin'];
  104. $end = $beginAndEnd['end'];
  105. }
  106. if($browseType == 'newest') $orderBy = 'end_desc,' . $orderBy;
  107. $projectID = $this->lang->navGroup->testtask != 'qa' ? $this->session->project : 0;
  108. $tasks = $this->fetchTesttaskList($productID, '', $projectID, 'unit', 'local', '', $begin, $end, $orderBy, $pager);
  109. $resultGroups = $this->dao->select('t1.task, t2.*')->from(TABLE_TESTRUN)->alias('t1')
  110. ->leftJoin(TABLE_TESTRESULT)->alias('t2')->on('t1.id=t2.run')
  111. ->where('t1.task')->in(array_keys($tasks))
  112. ->fetchGroup('task', 'run');
  113. /* 计算本测试单执行的用例数、成功数、失败数。*/
  114. /* Calculate the number of test cases executed by this test, the number of successes, and the number of failures. */
  115. foreach($tasks as $taskID => $task)
  116. {
  117. $results = zget($resultGroups, $taskID, array());
  118. $task->caseCount = count($results);
  119. $task->passCount = 0;
  120. $task->failCount = 0;
  121. foreach($results as $result)
  122. {
  123. if($result->caseResult == 'pass') $task->passCount ++;
  124. if($result->caseResult == 'fail') $task->failCount ++;
  125. }
  126. }
  127. return $this->processExecutionName($tasks);
  128. }
  129. /**
  130. * 获取一个项目的测试单列表。
  131. * Get testtasks of a project.
  132. *
  133. * @param int $projectID
  134. * @param int $productID
  135. * @param string $orderBy
  136. * @param object $pager
  137. * @access public
  138. * @return array
  139. */
  140. public function getProjectTasks($projectID, $productID = 0, $orderBy = 'id_desc', $pager = null)
  141. {
  142. $tasks = $this->dao->select('t1.*, t1.id AS idName, t5.multiple, IF(t4.shadow = 1, t5.name, t4.name) AS productName, t3.name AS executionName, t2.name AS buildName, t2.branch AS branch, t5.name AS projectName, t4.`order` AS productOrder')
  143. ->from(TABLE_TESTTASK)->alias('t1')
  144. ->leftJoin(TABLE_BUILD)->alias('t2')->on('t1.build = t2.id')
  145. ->leftJoin(TABLE_EXECUTION)->alias('t3')->on('t1.execution = t3.id')
  146. ->leftJoin(TABLE_PRODUCT)->alias('t4')->on('t1.product = t4.id')
  147. ->leftJoin(TABLE_PROJECT)->alias('t5')->on('t3.project = t5.id')
  148. ->where('t1.project')->eq((int)$projectID)
  149. ->andWhere('t1.auto')->ne('unit')
  150. ->andWhere('t1.deleted')->eq('0')
  151. ->beginIF($productID)->andWhere('t1.product')->eq($productID)->fi()
  152. ->orderBy('productOrder_asc, ' . $orderBy)
  153. ->page($pager)
  154. ->fetchAll('id', false);
  155. return $this->processExecutionName($tasks);
  156. }
  157. /**
  158. * 获取一个执行的测试单列表。
  159. * Get testtasks of a execution.
  160. *
  161. * @param int $executionID
  162. * @param int $productID
  163. * @param string $objectType
  164. * @param string $orderBy
  165. * @param object $pager
  166. * @access public
  167. * @return array
  168. */
  169. public function getExecutionTasks($executionID, $productID = 0, $objectType = 'execution', $orderBy = 'id_desc', $pager = null)
  170. {
  171. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getTesttasks();
  172. return $this->dao->select('t1.*, t2.name AS buildName, t3.name AS productName')
  173. ->from(TABLE_TESTTASK)->alias('t1')
  174. ->leftJoin(TABLE_BUILD)->alias('t2')->on('t1.build = t2.id')
  175. ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product = t3.id')
  176. ->where('t1.deleted')->eq('0')
  177. ->beginIF($objectType == 'execution')->andWhere('t1.execution')->eq((int)$executionID)->fi()
  178. ->beginIF($objectType == 'project')->andWhere('t1.project')->eq((int)$executionID)->fi()
  179. ->beginIF($productID)->andWhere('t1.product')->eq($productID)->fi()
  180. ->andWhere('t1.auto')->ne('unit')
  181. ->orderBy($orderBy)
  182. ->page($pager)
  183. ->fetchAll('id', false);
  184. }
  185. /**
  186. * 获取测试单键值对。
  187. * Get key-value pairs of testtask.
  188. *
  189. * @param int $productID
  190. * @param int $executionID
  191. * @param int $appendTaskID
  192. * @access public
  193. * @return array
  194. * @param bool $unit
  195. */
  196. public function getPairs($productID, $executionID = 0, $appendTaskID = 0, $unit = true)
  197. {
  198. $pairs = $this->dao->select('id, name')->from(TABLE_TESTTASK)
  199. ->where('deleted')->eq('0')
  200. ->andWhere('product')->eq($productID)
  201. ->beginIF($executionID)->andWhere('execution')->eq($executionID)->fi()
  202. ->beginIF($unit)->andWhere('auto')->ne('unit')->fi()
  203. ->orderBy('id_desc')
  204. ->fetchPairs();
  205. if($appendTaskID) $pairs += $this->dao->select('id, name')->from(TABLE_TESTTASK)->where('id')->eq($appendTaskID)->andWhere('auto')->ne('unit')->fetchPairs();
  206. return $pairs;
  207. }
  208. /**
  209. * 根据多个测试单 ID 获取测试单列表。
  210. * Get testtasks by id list.
  211. *
  212. * @param array $idList
  213. * @access public
  214. * @return array
  215. */
  216. public function getByList($idList)
  217. {
  218. if(!$idList) return array();
  219. return $this->dao->select('*')->from(TABLE_TESTTASK)->where('id')->in($idList)->fetchAll('id', false);
  220. }
  221. /**
  222. * 通过 ID 列表获取测试单键对。
  223. * Get key-value pairs of testtasks by id list.
  224. *
  225. * @param array $taskIdList
  226. * @access public
  227. * @return array
  228. */
  229. public function getPairsByList($taskIdList)
  230. {
  231. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getTesttaskPairs();
  232. return $this->dao->select('id,name')->from(TABLE_TESTTASK)->where('id')->in($taskIdList)->fetchPairs();
  233. }
  234. /**
  235. * 根据 ID 获取单条测试单的数据。
  236. * Get a testtask by id.
  237. *
  238. * @param int $taskID
  239. * @param bool $setImgSize
  240. * @access public
  241. * @return object|false
  242. * @param int $testtaskID
  243. */
  244. public function getByID($testtaskID, $setImgSize = false)
  245. {
  246. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getTesttask();
  247. $testtask = $this->dao->select('*')->from(TABLE_TESTTASK)->where('id')->eq($testtaskID)->fetch();
  248. if(!$testtask) return false;
  249. $product = $this->dao->select('name,type')->from(TABLE_PRODUCT)->where('id')->eq($testtask->product)->fetch();
  250. $testtask->productName = !empty($product->name) ? $product->name : '';
  251. $testtask->productType = !empty($product->type) ? $product->type : '';
  252. $testtask->branch = 0;
  253. $testtask->executionName = '';
  254. $testtask->buildName = '';
  255. if($testtask->execution)
  256. {
  257. $testtask->executionName = $this->dao->select('name')->from(TABLE_EXECUTION)->where('id')->eq($testtask->execution)->fetch('name');
  258. $testtask->branch = $this->dao->select('branch')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($testtask->execution)->andWhere('product')->eq($testtask->product)->fetch('branch');
  259. }
  260. if($testtask->build)
  261. {
  262. $build = $this->dao->select('branch,name')->from(TABLE_BUILD)->where('id')->eq($testtask->build)->fetch();
  263. $testtask->buildName = zget($build, 'name', '');
  264. $testtask->branch = zget($build, 'branch', '');
  265. }
  266. $testtask = $this->loadModel('file')->replaceImgURL($testtask, 'desc');
  267. if($setImgSize) $testtask->desc = $this->loadModel('file')->setImgSize($testtask->desc);
  268. $testtask->files = $this->loadModel('file')->getByObject('testtask', $testtask->id);
  269. if(!$testtask->branch) $testtask->branch = '0';
  270. return $testtask;
  271. }
  272. /**
  273. * 获取某个用户负责的测试单列表。
  274. * Get testtasks that a user is responsible for.
  275. *
  276. * @param string $account
  277. * @param object $pager
  278. * @param string $orderBy
  279. * @param string $type
  280. * @access public
  281. * @return array
  282. */
  283. public function getByUser($account, $pager = null, $orderBy = 'id_desc', $type = '')
  284. {
  285. return $this->dao->select("t1.*, t2.name AS executionName, t2.multiple AS executionMultiple, t5.name AS projectName, t3.name AS buildName, t4.name AS productName, CONCAT(t2.name, '/', t3.name) as executionBuild")
  286. ->from(TABLE_TESTTASK)->alias('t1')
  287. ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
  288. ->leftJoin(TABLE_BUILD)->alias('t3')->on('t1.build = t3.id')
  289. ->leftJoin(TABLE_PRODUCT)->alias('t4')->on('t1.product = t4.id')
  290. ->leftJoin(TABLE_PROJECT)->alias('t5')->on('t2.project = t5.id')
  291. ->where('t1.deleted')->eq('0')
  292. ->andWhere('t4.deleted')->eq('0')
  293. ->andWhere('t1.auto')->ne('unit')
  294. ->andWhere('(t1.owner')->eq($account)
  295. ->orWhere("FIND_IN_SET('$account', t1.members)")
  296. ->markRight(1)
  297. ->beginIF(!$this->app->user->admin)->andWhere('t2.id')->in($this->app->user->view->sprints)->fi()
  298. ->beginIF($type == 'wait')->andWhere('t1.status')->ne('done')->fi()
  299. ->beginIF($type == 'done')->andWhere('t1.status')->eq('done')->fi()
  300. ->orderBy($orderBy)
  301. ->page($pager)
  302. ->fetchAll('id', false);
  303. }
  304. /**
  305. * 根据测试单 ID 和用例 ID 获取一个测试执行。
  306. * Get a testrun by testtask ID and case ID.
  307. *
  308. * @param int $taskID
  309. * @param int $caseID
  310. * @access public
  311. * @return object
  312. */
  313. public function getRunByCase($taskID, $caseID)
  314. {
  315. return $this->dao->select('*')->from(TABLE_TESTRUN)->where('task')->eq($taskID)->andWhere('`case`')->eq($caseID)->fetch();
  316. }
  317. /**
  318. * 根据不同类型获取可关联的测试用例。
  319. * Get linkable cases according to different typs.
  320. *
  321. * @param int $productID
  322. * @param object $task
  323. * @param string $type
  324. * @param int $param
  325. * @param object $pager
  326. * @access public
  327. * @return array
  328. */
  329. public function getLinkableCases($productID, $task, $type = 'all', $param = 0, $pager = null)
  330. {
  331. if($this->session->testcaseQuery == false) $this->session->set('testcaseQuery', ' 1 = 1');
  332. $query = $this->session->testcaseQuery;
  333. $allProduct = "`product` = 'all'";
  334. if(strpos($query, '`product` =') === false && $type != 'bysuite') $query .= " AND `product` = $productID";
  335. if(strpos($query, $allProduct) !== false) $query = str_replace($allProduct, '1', $query);
  336. $linkedCases = $this->dao->select('`case`')->from(TABLE_TESTRUN)->where('task')->eq($task->id)->fetchPairs('case');
  337. if($type == 'all') return $this->getAllLinkableCases($task, $query, $linkedCases, $pager);
  338. if($type == 'bystory') return $this->getLinkableCasesByStory($productID, $task, $query, $linkedCases, $pager);
  339. if($type == 'bybug') return $this->getLinkableCasesByBug($productID, $task, $query, $linkedCases, $pager);
  340. if($type == 'bysuite') return $this->getLinkableCasesBySuite($productID, $task, $param, $query, $linkedCases, $pager);
  341. if($type == 'bybuild') return $this->getLinkableCasesByTestTask($param, $query, $linkedCases, $pager);
  342. return array();
  343. }
  344. /**
  345. * 获取所有可关联的测试用例。
  346. * Get all linkable cases.
  347. *
  348. * @param object $task
  349. * @param string $query
  350. * @param array $linkedCases
  351. * @param object $pager
  352. * @access public
  353. * @return array
  354. */
  355. public function getAllLinkableCases($task, $query = '', $linkedCases = array(), $pager = null)
  356. {
  357. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getCases();
  358. return $this->dao->select('*')->from(TABLE_CASE)
  359. ->where('deleted')->eq('0')
  360. ->andWhere('status')->ne('wait')
  361. ->andWhere('type')->ne('unit')
  362. ->beginIF($query)->andWhere($query)->fi()
  363. ->beginIF(!empty($linkedCases))->andWhere('id')->notIN($linkedCases)->fi()
  364. ->beginIF($task->branch !== '')->andWhere('branch')->in("0,{$task->branch}")->fi()
  365. ->orderBy('id desc')
  366. ->page($pager)
  367. ->fetchAll('id', false);
  368. }
  369. /**
  370. * 根据测试单所属的版本关联的需求获取可关联的用例。
  371. * Get linkable cases based on the stories associated with the build to which the testtask belongs.
  372. *
  373. * @param int $productID
  374. * @param object $task
  375. * @param string $query
  376. * @param array $linkedCases
  377. * @param object $pager
  378. * @access public
  379. * @return array
  380. */
  381. public function getLinkableCasesByStory($productID, $task, $query = '', $linkedCases = array(), $pager = null)
  382. {
  383. $stories = $this->dao->select('stories')->from(TABLE_BUILD)->where('id')->eq($task->build)->fetch('stories');
  384. if(!$stories) return array();
  385. $query = preg_replace('/`(\w+)`/', 't1.`$1`', $query);
  386. return $this->dao->select('t1.*, t2.title AS storyTitle')->from(TABLE_CASE)->alias('t1')
  387. ->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
  388. ->where('t1.deleted')->eq('0')
  389. ->andWhere('t1.product')->eq($productID)
  390. ->andWhere('t1.status')->ne('wait')
  391. ->andWhere('t1.story')->in(trim($stories, ','))
  392. ->beginIF($query)->andWhere($query)->fi()
  393. ->beginIF(!empty($linkedCases))->andWhere('t1.id')->notin($linkedCases)->fi()
  394. ->beginIF($task->branch !== '')->andWhere('t1.branch')->in("0,{$task->branch}")->fi()
  395. ->beginIF($this->lang->navGroup->testtask != 'qa')->andWhere('t1.project')->eq($this->session->project)->fi()
  396. ->orderBy('t1.id desc')
  397. ->page($pager)
  398. ->fetchAll('id', false);
  399. }
  400. /**
  401. * 根据测试单所属的版本关联的 Bug 获取可关联的用例。
  402. * Get linkable cases based on the bugs associated with the build to which the testtask belongs.
  403. *
  404. * @param int $productID
  405. * @param object $task
  406. * @param string $query
  407. * @param array $linkedCases
  408. * @param object $pager
  409. * @access public
  410. * @return array
  411. */
  412. public function getLinkableCasesByBug($productID, $task, $query = '', $linkedCases = array(), $pager = null)
  413. {
  414. $bugs = $this->dao->select('bugs')->from(TABLE_BUILD)->where('id')->eq($task->build)->fetch('bugs');
  415. if(!$bugs) return array();
  416. return $this->dao->select('*')->from(TABLE_CASE)
  417. ->where('deleted')->eq('0')
  418. ->andWhere('product')->eq($productID)
  419. ->andWhere('status')->ne('wait')
  420. ->andWhere('fromBug')->in(trim($bugs, ','))
  421. ->beginIF($query)->andWhere($query)->fi()
  422. ->beginIF($linkedCases)->andWhere('id')->notIN($linkedCases)->fi()
  423. ->beginIF($task->branch !== '')->andWhere('branch')->in("0,{$task->branch}")->fi()
  424. ->beginIF($this->lang->navGroup->testtask != 'qa')->andWhere('project')->eq($this->session->project)->fi()
  425. ->orderBy('id desc')
  426. ->page($pager)
  427. ->fetchAll('id', false);
  428. }
  429. /**
  430. * 根据测试套件获取可关联的用例。
  431. * Get linkable cases by suite.
  432. *
  433. * @param int $productID
  434. * @param object $task
  435. * @param int $suite
  436. * @param string $query
  437. * @param array $linkedCases
  438. * @param object $pager
  439. * @access public
  440. * @return array
  441. */
  442. public function getLinkableCasesBySuite($productID, $task, $suite, $query = '', $linkedCases = array(), $pager = null)
  443. {
  444. if(strpos($query, '`product`') !== false) $query = str_replace('`product`', 't1.`product`', $query);
  445. return $this->dao->select('t1.*, t2.version AS version, COALESCE(t3.title, t1.title) AS title')->from(TABLE_CASE)->alias('t1')
  446. ->leftJoin(TABLE_SUITECASE)->alias('t2')->on('t1.id=t2.case')
  447. ->leftJoin(TABLE_CASESPEC)->alias('t3')->on('t1.id=t3.case AND t2.version=t3.version')
  448. ->where('t1.deleted')->eq('0')
  449. ->andWhere('t1.product')->eq($productID)
  450. ->andWhere('t1.status')->ne('wait')
  451. ->andWhere('t2.suite')->eq($suite)
  452. ->beginIF($query)->andWhere($query)->fi()
  453. ->beginIF($linkedCases)->andWhere('t1.id')->notIN($linkedCases)->fi()
  454. ->beginIF($task->branch !== '')->andWhere('t1.branch')->in("0,{$task->branch}")->fi()
  455. ->beginIF($this->lang->navGroup->testtask != 'qa')->andWhere('t1.project')->eq($this->session->project)->fi()
  456. ->orderBy('id desc')
  457. ->page($pager)
  458. ->fetchAll('id', false);
  459. }
  460. /**
  461. * 根据测试单获取可关联的用例。
  462. * Get linkeable cases by testtask.
  463. *
  464. * @param int $testTask
  465. * @param string $query
  466. * @param array $linkedCases
  467. * @param object $pager
  468. * @access public
  469. * @return array
  470. */
  471. public function getLinkableCasesByTestTask($testTask, $query = '', $linkedCases = array(), $pager = null)
  472. {
  473. /* Format the query condition. */
  474. $query = preg_replace('/`(\w+)`/', 't1.`$1`', $query);
  475. $query = str_replace('t1.`lastRunner`', 't2.`lastRunner`', $query);
  476. $query = str_replace('t1.`lastRunDate`', 't2.`lastRunDate`', $query);
  477. $query = str_replace('t1.`lastRunResult`', 't2.`lastRunResult`', $query);
  478. return $this->dao->select('t1.*, t2.lastRunner, t2.lastRunDate, t2.lastRunResult')->from(TABLE_CASE)->alias('t1')
  479. ->leftJoin(TABLE_TESTRUN)->alias('t2')->on('t1.id = t2.case')
  480. ->where('t2.task')->eq($testTask)
  481. ->andWhere('t1.status')->ne('wait')
  482. ->beginIF($query)->andWhere($query)->fi()
  483. ->beginIF($linkedCases)->andWhere('t1.id')->notin($linkedCases)->fi()
  484. ->beginIF($this->lang->navGroup->testtask != 'qa')->andWhere('t1.project')->eq($this->session->project)->fi()
  485. ->page($pager)
  486. ->fetchAll('id', false);
  487. }
  488. /**
  489. * 按条件获取获取产品下的测试单。
  490. * Get testtasks under a product by condition.
  491. *
  492. * @param int $productID
  493. * @param int $testtaskID
  494. * @access public
  495. * @return array
  496. * @param int $testTaskID
  497. */
  498. public function getRelatedTestTasks($productID, $testTaskID)
  499. {
  500. $beginDate = $this->dao->select('begin')->from(TABLE_TESTTASK)->where('id')->eq($testTaskID)->fetch('begin');
  501. return $this->dao->select('id, name')->from(TABLE_TESTTASK)
  502. ->where('product')->eq($productID)
  503. ->andWhere('auto')->ne('unit')
  504. ->beginIF($beginDate)->andWhere('begin')->le($beginDate)->fi()
  505. ->andWhere('deleted')->eq('0')
  506. ->andWhere('id')->ne($testTaskID)
  507. ->orderBy('begin desc')
  508. ->fetchPairs();
  509. }
  510. /**
  511. * 按执行结果统计测试单中的用例。
  512. * Get report data of a testtask by execution results.
  513. *
  514. * @param int $taskID
  515. * @access public
  516. * @return array
  517. */
  518. public function getDataOfTestTaskPerRunResult($taskID)
  519. {
  520. $datas = $this->dao->select("t1.lastRunResult AS name, COUNT('t1.*') AS value")->from(TABLE_TESTRUN)->alias('t1')
  521. ->leftJoin(TABLE_CASE)->alias('t2')
  522. ->on('t1.case = t2.id')
  523. ->where('t1.task')->eq($taskID)
  524. ->andWhere('t2.deleted')->eq('0')
  525. ->groupBy('t1.lastRunResult')
  526. ->orderBy('value DESC')
  527. ->fetchAll('name');
  528. if(!$datas) return array();
  529. $this->app->loadLang('testcase');
  530. foreach($datas as $result => $data) $data->name = zget($this->lang->testcase->resultList, $result, $this->lang->testtask->unexecuted);
  531. return $datas;
  532. }
  533. /**
  534. * 按类型统计测试单中的用例。
  535. * Get report data of a testtask by case type.
  536. *
  537. * @param int $taskID
  538. * @access public
  539. * @return array
  540. */
  541. public function getDataOfTestTaskPerType($taskID)
  542. {
  543. $datas = $this->dao->select('t2.type AS name, COUNT(1) AS value')->from(TABLE_TESTRUN)->alias('t1')
  544. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
  545. ->where('t1.task')->eq($taskID)
  546. ->andWhere('t2.deleted')->eq('0')
  547. ->groupBy('t2.type')
  548. ->orderBy('value desc')
  549. ->fetchAll('name');
  550. if(!$datas) return array();
  551. foreach($datas as $result => $data) if(isset($this->lang->testcase->typeList[$result])) $data->name = $this->lang->testcase->typeList[$result];
  552. return $datas;
  553. }
  554. /**
  555. * 按模块统计测试单中的用例。
  556. * Get report data of a testtask by case module.
  557. *
  558. * @param int $taskID
  559. * @access public
  560. * @return array
  561. */
  562. public function getDataOfTestTaskPerModule($taskID)
  563. {
  564. $datas = $this->dao->select('t2.module AS name, COUNT(1) AS value')->from(TABLE_TESTRUN)->alias('t1')
  565. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
  566. ->where('t1.task')->eq($taskID)
  567. ->andWhere('t2.deleted')->eq('0')
  568. ->groupBy('t2.module')
  569. ->orderBy('value desc')
  570. ->fetchAll('name');
  571. if(!$datas) return array();
  572. $modules = $this->loadModel('tree')->getModulesName(array_keys($datas));
  573. foreach($datas as $moduleID => $data) $data->name = zget($modules, $moduleID, '/');
  574. return $datas;
  575. }
  576. /**
  577. * 按执行人统计测试单中的用例。
  578. * Get report data of a testtask by executor.
  579. *
  580. * @param int $taskID
  581. * @access public
  582. * @return array
  583. */
  584. public function getDataOfTestTaskPerRunner($taskID)
  585. {
  586. $datas = $this->dao->select('t1.lastRunner AS name, COUNT(1) AS value')->from(TABLE_TESTRUN)->alias('t1')
  587. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
  588. ->where('t1.task')->eq($taskID)
  589. ->andWhere('t2.deleted')->eq('0')
  590. ->groupBy('t1.lastRunner')
  591. ->orderBy('value DESC')
  592. ->fetchAll('name');
  593. if(!$datas) return array();
  594. $users = $this->loadModel('user')->getPairs('noclosed|noletter');
  595. foreach($datas as $result => $data) $data->name = $result ? zget($users, $result) : $this->lang->testtask->unexecuted;
  596. return $datas;
  597. }
  598. /**
  599. * 更新测试单。
  600. * Update a test task.
  601. *
  602. * @param object $task
  603. * @param object $oldTask
  604. * @access public
  605. * @return array|bool
  606. */
  607. public function update($task, $oldTask)
  608. {
  609. $this->dao->update(TABLE_TESTTASK)->data($task, 'deleteFiles,renameFiles,files')
  610. ->autoCheck()
  611. ->batchcheck($this->config->testtask->edit->requiredFields, 'notempty')
  612. ->checkIF($task->end != '', 'end', 'ge', $task->begin)
  613. ->checkFlow()
  614. ->where('id')->eq($task->id)
  615. ->exec();
  616. if(dao::isError()) return false;
  617. $this->loadModel('file')->processFileDiffsForObject('testtask', $oldTask, $task);
  618. $task = $this->file->replaceImgURL($task, 'desc');
  619. return common::createChanges($oldTask, $task);
  620. }
  621. /**
  622. * 开始一个测试单。
  623. * Start testtask.
  624. *
  625. * @param object $task
  626. * @access public
  627. * @return bool|array
  628. */
  629. public function start($task)
  630. {
  631. $taskID = (int)$task->id;
  632. $oldTask = $this->fetchByID($taskID);
  633. if(!$oldTask || !self::isClickable($oldTask, 'start')) return false;
  634. $this->dao->update(TABLE_TESTTASK)->data($task, 'comment,uid')
  635. ->autoCheck()
  636. ->checkFlow()
  637. ->where('id')->eq($taskID)
  638. ->exec();
  639. if(dao::isError()) return false;
  640. return common::createChanges($oldTask, $task);
  641. }
  642. /**
  643. * 关闭一个测试单。
  644. * Close a testtask.
  645. *
  646. * @param object $task
  647. * @access public
  648. * @return bool|array
  649. */
  650. public function close($task)
  651. {
  652. $taskID = (int)$task->id;
  653. $oldTask = $this->fetchByID($taskID);
  654. if(!$oldTask || !self::isClickable($oldTask, 'close')) return false;
  655. if($task->realFinishedDate <= $oldTask->begin) dao::$errors['realFinishedDate'][] = sprintf($this->lang->testtask->finishedDateLess, $oldTask->begin);
  656. if($task->realFinishedDate > date('Y-m-d 00:00:00', strtotime('+1 day'))) dao::$errors['realFinishedDate'][] = $this->lang->testtask->finishedDateMore;
  657. if(dao::isError()) return false;
  658. $this->dao->update(TABLE_TESTTASK)->data($task, 'comment,uid')
  659. ->autoCheck()
  660. ->checkFlow()
  661. ->where('id')->eq($taskID)
  662. ->exec();
  663. if(dao::isError()) return false;
  664. return common::createChanges($oldTask, $task);
  665. }
  666. /**
  667. * 阻塞一个测试单。
  668. * Block a testtask.
  669. *
  670. * @param object $task
  671. * @access public
  672. * @return bool|array
  673. */
  674. public function block($task)
  675. {
  676. $taskID = (int)$task->id;
  677. $oldTask = $this->fetchByID($taskID);
  678. if(!$oldTask || !self::isClickable($oldTask, 'block')) return false;
  679. $this->dao->update(TABLE_TESTTASK)->data($task, 'comment,uid')
  680. ->autoCheck()
  681. ->checkFlow()
  682. ->where('id')->eq($taskID)
  683. ->exec();
  684. if(dao::isError()) return false;
  685. return common::createChanges($oldTask, $task);
  686. }
  687. /**
  688. * 激活一个测试单。
  689. * Activate a testtask.
  690. *
  691. * @param object $task
  692. * @access public
  693. * @return bool|array
  694. */
  695. public function activate($task)
  696. {
  697. $taskID = (int)$task->id;
  698. $oldTask = $this->fetchByID($taskID);
  699. if(!$oldTask || !self::isClickable($oldTask, 'activate')) return false;
  700. $this->dao->update(TABLE_TESTTASK)->data($task, 'comment,uid')
  701. ->autoCheck()
  702. ->checkFlow()
  703. ->where('id')->eq($taskID)
  704. ->exec();
  705. if(dao::isError()) return false;
  706. return common::createChanges($oldTask, $task);
  707. }
  708. /**
  709. * 更新测试单状态。
  710. * Update testtask's status.
  711. *
  712. * @param int $task
  713. * @access public
  714. * @return bool
  715. * @param int $taskID
  716. */
  717. public function updateStatus($taskID)
  718. {
  719. if(empty($taskID)) return false;
  720. $oldTask = $this->fetchByID($taskID);
  721. if(empty($oldTask) || $oldTask->status == 'doing') return false;
  722. $task = new stdClass();
  723. $task->status = 'doing';
  724. if(empty($task->realBegan)) $task->realBegan = helper::today();
  725. $this->dao->update(TABLE_TESTTASK)->data($task)->autoCheck()->checkFlow()->where('id')->eq($taskID)->exec();
  726. if(dao::isError()) return false;
  727. $changes = common::createChanges($oldTask, $task);
  728. if($changes)
  729. {
  730. $actionID = $this->loadModel('action')->create('testtask', $taskID, 'syncByCase');
  731. $this->action->logHistory($actionID, $changes);
  732. }
  733. return !dao::isError();
  734. }
  735. /**
  736. * 关联用例到一个测试单。
  737. * Link cases to a testtask.
  738. *
  739. * @param int $taskID
  740. * @param string $type all|bystory|bysuite|bybuild|bybug
  741. * @param array $runs
  742. * @access public
  743. * @return bool
  744. */
  745. public function linkCase($taskID, $type, $runs)
  746. {
  747. if(!$runs) return false;
  748. $users = array();
  749. $caseIdList = array_unique(array_filter(array_map(function($run){return $run->case;}, $runs)));
  750. if($type == 'bybuild' && $caseIdList) $users = $this->dao->select('`case`, assignedTo')->from(TABLE_TESTRUN)->where('`case`')->in($caseIdList)->fetchPairs();
  751. $testtask = $this->dao->select('*')->from(TABLE_TESTTASK)->where('id')->eq($taskID)->fetch();
  752. if(!empty($testtask->execution))
  753. {
  754. $execution = $this->loadModel('execution')->fetchByID((int)$testtask->execution);
  755. $executionLastOrder = $this->dao->select('MAX(`order`) AS `order`')->from(TABLE_PROJECTCASE)->where('project')->eq((int)$execution->id)->fetch('order');
  756. $projectLastOrder = $this->dao->select('MAX(`order`) AS `order`')->from(TABLE_PROJECTCASE)->where('project')->eq((int)$execution->project)->fetch('order');
  757. }
  758. elseif(!empty($testtask->project))
  759. {
  760. $lastOrder = $this->dao->select('MAX(`order`) AS `order`')->from(TABLE_PROJECTCASE)->where('project')->eq((int)$testtask->project)->fetch('order');
  761. }
  762. $case = new stdclass();
  763. $case->product = $this->session->product;
  764. $case->version = 1;
  765. $this->loadModel('action');
  766. foreach($runs as $run)
  767. {
  768. $run->task = $taskID;
  769. $run->status = 'normal';
  770. $run->assignedTo = zget($users, $run->case, '');
  771. $this->dao->replace(TABLE_TESTRUN)->data($run)->exec();
  772. if(!empty($testtask->joint)) continue;
  773. /* 在项目或执行下关联用例到测试单时把用例关联到项目或执行。*/
  774. /* Associate the cases to the project or execution when associating the cases to the testtask under the project or execution. */
  775. if(!empty($testtask->execution))
  776. {
  777. $executionLastOrder++;
  778. $projectLastOrder++;
  779. $case->project = (int)$execution->id;
  780. $case->case = $run->case;
  781. $case->order = $executionLastOrder;
  782. $this->dao->replace(TABLE_PROJECTCASE)->data($case)->exec();
  783. $case->project = (int)$execution->project;
  784. $case->order = $projectLastOrder;
  785. $this->dao->replace(TABLE_PROJECTCASE)->data($case)->exec();
  786. }
  787. elseif(!empty($testtask->project))
  788. {
  789. $case->project = (int)$testtask->project;
  790. $case->case = $run->case;
  791. $case->order = ++$lastOrder;
  792. $this->dao->replace(TABLE_PROJECTCASE)->data($case)->exec();
  793. }
  794. $this->action->create('case', $run->case, 'linked2testtask', '', $taskID);
  795. }
  796. return !dao::isError();
  797. }
  798. /**
  799. * 从测试单移除一个用例。
  800. * Remove a case from a testtask.
  801. *
  802. * @param int $runID
  803. * @access public
  804. * @return bool
  805. */
  806. public function unlinkCase($runID)
  807. {
  808. $run = $this->dao->select('t1.task,t1.`case`,t2.story')->from(TABLE_TESTRUN)->alias('t1')
  809. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case=t2.id')
  810. ->where('t1.id')->eq($runID)
  811. ->fetch();
  812. if(!$run) return false;
  813. $linkedProjects = $run->story ? $this->dao->select('project')->from(TABLE_PROJECTSTORY)->where('story')->eq($run->story)->fetchPairs() : array();
  814. if($linkedProjects) $this->dao->delete()->from(TABLE_PROJECTCASE)->where('`case`')->eq($run->case)->andWhere('project')->notin($linkedProjects)->exec();
  815. $this->dao->delete()->from(TABLE_TESTRUN)->where('id')->eq($runID)->exec();
  816. $this->loadModel('action')->create('case' ,$run->case, 'unlinkedfromtesttask', '', $run->task);
  817. return !dao::isError();
  818. }
  819. /**
  820. * 批量从测试单移除用例。
  821. * Batch remove cases from a testtask.
  822. *
  823. * @param int $taskID
  824. * @param array $caseIdList
  825. * @access public
  826. * @return bool
  827. */
  828. public function batchUnlinkCases($taskID, $caseIdList)
  829. {
  830. if(!$taskID || !$caseIdList) return false;
  831. $cases = $this->dao->select('`case`')->from(TABLE_TESTRUN)->where('task')->eq($taskID)->andWhere('`case`')->in($caseIdList)->fetchPairs();
  832. if(!$cases) return false;
  833. $this->dao->delete()->from(TABLE_TESTRUN)->where('task')->eq($taskID)->andWhere('`case`')->in($cases)->exec();
  834. if(dao::isError()) return false;
  835. $this->loadModel('action');
  836. foreach($cases as $caseID) $this->action->create('case', $caseID, 'unlinkedfromtesttask', '', $taskID);
  837. return !dao::isError();
  838. }
  839. /**
  840. * 在测试单内指派用例。
  841. * Assign a case in a testtask.
  842. *
  843. * @param object $run
  844. * @param object $oldRun
  845. * @access public
  846. * @return array|bool
  847. */
  848. public function assignCase($run, $oldRun)
  849. {
  850. if(!empty($oldRun->assignedTo) && $run->assignedTo == $oldRun->assignedTo) return array();
  851. $this->dao->update(TABLE_TESTRUN)->data($run, 'comment,uid')->where('id')->eq($run->id)->exec();
  852. if(dao::isError()) return false;
  853. return common::createChanges($oldRun, $run);
  854. }
  855. /**
  856. * 批量指派一个测试单中的用例。
  857. * Batch assign cases in a testtask.
  858. *
  859. * @param int $taskID
  860. * @param string $account
  861. * @param array $caseIdList
  862. * @access public
  863. * @return bool
  864. */
  865. public function batchAssign($taskID, $account, $caseIdList)
  866. {
  867. if(!$taskID || !$account || !$caseIdList) return false;
  868. $cases = $this->dao->select('`case`')->from(TABLE_TESTRUN)->where('task')->eq($taskID)->andWhere('`case`')->in($caseIdList)->fetchPairs();
  869. if(!$cases) return false;
  870. $this->dao->update(TABLE_TESTRUN)->set('assignedTo')->eq($account)->where('task')->eq($taskID)->andWhere('`case`')->in($cases)->exec();
  871. if(dao::isError()) return false;
  872. $this->loadModel('action');
  873. foreach($cases as $caseID) $this->action->create('case', $caseID, 'assigned', '', $account);
  874. return !dao::isError();
  875. }
  876. /**
  877. * 给排序字段添加前缀。
  878. * Add a prefix to the sort field.
  879. *
  880. * @param string $orderBy
  881. * @access private
  882. * @return string
  883. */
  884. private function addPrefixToOrderBy($orderBy)
  885. {
  886. $specialFields = ',assignedTo,status,lastRunResult,lastRunner,lastRunDate,';
  887. $fieldToSort = strpos($orderBy, '_') ? substr($orderBy, 0, strpos($orderBy, '_')) : $orderBy;
  888. $orderBy = strpos($specialFields, ',' . $fieldToSort . ',') !== false ? ('t1.' . $orderBy) : ('t2.' . $orderBy);
  889. return $orderBy;
  890. }
  891. /**
  892. * 获取一个测试单关联的测试用例及相关需求。
  893. * Get cases associated with a testtask.
  894. *
  895. * @param int $taskID
  896. * @param array $modules
  897. * @param string $orderBy
  898. * @param object $pager
  899. * @access public
  900. * @return array
  901. */
  902. public function getRuns($taskID, $modules, $orderBy, $pager = null)
  903. {
  904. $orderBy = $this->addPrefixToOrderBy($orderBy);
  905. return $this->dao->select("t2.*, t1.*, t2.version AS caseVersion, COALESCE(t3.title, '') AS storyTitle, t2.status AS caseStatus, IF(t4.title IS NULL, t2.title, t4.title) AS title")->from(TABLE_TESTRUN)->alias('t1')
  906. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
  907. ->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story = t3.id')
  908. ->leftJoin(TABLE_CASESPEC)->alias('t4')->on('t1.`case` = t4.`case` AND t1.version = t4.version')
  909. ->where('t1.task')->eq($taskID)
  910. ->andWhere('t2.deleted')->eq('0')
  911. ->beginIF(!$this->app->user->admin)->andWhere('t2.product')->in($this->app->user->view->products)->fi()
  912. ->beginIF($modules)->andWhere('t2.module')->in($modules)->fi()
  913. ->orderBy($orderBy)
  914. ->page($pager)
  915. ->fetchAll('id', false);
  916. }
  917. /**
  918. * 根据一个测试套件获取一个测试单关联的测试用例。
  919. * Get cases associated with a testtask according by a test suite.
  920. *
  921. * @param int $taskID
  922. * @param int $suiteID
  923. * @param string $orderBy
  924. * @param object $pager
  925. * @access public
  926. * @return array
  927. */
  928. public function getRunsBySuite($taskID, $suiteID, $orderBy, $pager = null)
  929. {
  930. $orderBy = $this->addPrefixToOrderBy($orderBy);
  931. $cases = $this->loadModel('testsuite')->getLinkedCasePairs($suiteID);
  932. return $this->dao->select("t2.*, t1.*, COALESCE(t3.title, '') AS storyTitle, t2.status AS caseStatus, t2.version AS caseVersion, IF(t4.title IS NULL, t2.title, t4.title) AS title")->from(TABLE_TESTRUN)->alias('t1')
  933. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
  934. ->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story = t3.id')
  935. ->leftJoin(TABLE_CASESPEC)->alias('t4')->on('t1.case = t4.case AND t1.version = t4.version')
  936. ->where('t1.task')->eq($taskID)
  937. ->andWhere('t2.deleted')->eq('0')
  938. ->andWhere('t2.id')->in(array_keys($cases))
  939. ->orderBy($orderBy)
  940. ->page($pager)
  941. ->fetchAll('id', false);
  942. }
  943. /**
  944. * 按照测试单分组展示用例。
  945. * Group case run by suite.
  946. *
  947. * @param int $taskID
  948. * @param string $orderBy
  949. * @access public
  950. * @return array
  951. */
  952. public function getRunsForUnitCases($taskID, $orderBy = 't1.id_desc')
  953. {
  954. return $this->dao->select("t2.*, t1.*, t2.version AS caseVersion, COALESCE(t3.title, '') AS storyTitle, t2.status AS caseStatus, t4.suite, t5.name AS suiteTitle")->from(TABLE_TESTRUN)->alias('t1')
  955. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
  956. ->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story = t3.id')
  957. ->leftJoin(TABLE_SUITECASE)->alias('t4')->on('t1.case = t4.case')
  958. ->leftJoin(TABLE_TESTSUITE)->alias('t5')->on('t4.suite = t5.id')
  959. ->where('t1.task')->eq($taskID)
  960. ->andWhere('t2.deleted')->eq('0')
  961. ->orderBy($orderBy)
  962. ->fetchAll('id', false);
  963. }
  964. /**
  965. * 获取一个测试单中指派给一个用户的测试用例。
  966. * Get the cases assigned to a user among the cases associated with a testtask.
  967. *
  968. * @param int $taskID
  969. * @param string $user
  970. * @param array $modules
  971. * @param string $orderBy
  972. * @param object $pager
  973. * @access public
  974. * @return array
  975. */
  976. public function getUserRuns($taskID, $user, $modules = array(), $orderBy = 'id_desc', $pager = null)
  977. {
  978. $orderBy = $this->addPrefixToOrderBy($orderBy);
  979. return $this->dao->select("t2.*, t1.*, COALESCE(t3.title, '') AS storyTitle, t2.status AS caseStatus,t2.version AS caseVersion, IF(t4.title IS NULL, t2.title, t4.title) AS title")->from(TABLE_TESTRUN)->alias('t1')
  980. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
  981. ->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story = t3.id')
  982. ->leftJoin(TABLE_CASESPEC)->alias('t4')->on('t1.case = t4.case AND t1.version = t4.version')
  983. ->where('t1.task')->eq($taskID)
  984. ->andWhere('t1.assignedTo')->eq($user)
  985. ->andWhere('t2.deleted')->eq('0')
  986. ->beginIF($modules)->andWhere('t2.module')->in($modules)->fi()
  987. ->orderBy($orderBy)
  988. ->page($pager)
  989. ->fetchAll('id', false);
  990. }
  991. /**
  992. * 获取用例以及场景数据。
  993. * Get cases with scenes data.
  994. *
  995. * @param int|array $productID
  996. * @param array $runs
  997. * @access public
  998. * @return array
  999. */
  1000. public function getSceneCases($productID, $runs)
  1001. {
  1002. $scenes = $this->dao->select('*')->from(TABLE_SCENE)
  1003. ->where('deleted')->eq('0')
  1004. ->andWhere('product')->in($productID)
  1005. ->orderBy('grade_desc, sort_asc')
  1006. ->fetchAll('id', false);
  1007. $displayScenes = array();
  1008. foreach($runs as $run)
  1009. {
  1010. if(!empty($run->scene)) $displayScenes[] = $run->scene;
  1011. $run->parent = !empty($run->scene) ? 'scene-' . $scenes[$run->scene]->id : 0;
  1012. $run->isScene = false;
  1013. }
  1014. foreach($scenes as $scene)
  1015. {
  1016. foreach($displayScenes as $displayScene)
  1017. {
  1018. if(strpos($scene->path, $displayScene . ',') !== false)
  1019. {
  1020. $displayScenes += explode(',', trim($scene->path, ','));
  1021. }
  1022. }
  1023. }
  1024. $displayScenes = array_unique($displayScenes);
  1025. foreach($scenes as $id => $scene)
  1026. {
  1027. if(!in_array($id, $displayScenes)) unset($scenes[$id]);
  1028. $scene->parent = !empty($scene->parent) ? 'scene-' . $scene->parent : 0;
  1029. $scene->id = 'scene-' . $scene->id;
  1030. $scene->isScene = true;
  1031. }
  1032. return array($runs, $scenes);
  1033. }
  1034. /**
  1035. * 获取一个测试单关联的用例。
  1036. * Get cases associated with a testtask.
  1037. *
  1038. * @param int|array $productID
  1039. * @param string $browseType
  1040. * @param int $queryID
  1041. * @param int $moduleID
  1042. * @param string $sort
  1043. * @param object $pager
  1044. * @param object $task
  1045. * @access public
  1046. * @return array
  1047. */
  1048. public function getTaskCases($productID, $browseType, $queryID, $moduleID, $sort, $pager = null, $task = null)
  1049. {
  1050. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getCases();
  1051. $modules = $moduleID ? $this->loadModel('tree')->getAllChildId($moduleID) : array();
  1052. $browseType = ($browseType == 'bymodule' && $this->session->taskCaseBrowseType && $this->session->taskCaseBrowseType != 'bysearch') ? $this->session->taskCaseBrowseType : $browseType;
  1053. $browseType = strtolower($browseType);
  1054. if($browseType == 'bymodule' || $browseType == 'all') return $this->getRuns($task->id, $modules, $sort, $pager);
  1055. if($browseType == 'bysuite') return $this->getRunsBySuite($task->id, $queryID, $sort, $pager);
  1056. if($browseType == 'assignedtome') return $this->getUserRuns($task->id, $this->session->user->account, $modules, $sort, $pager);
  1057. if($browseType == 'bysearch')
  1058. {
  1059. if($this->session->testtaskQuery == false) $this->session->set('testtaskQuery', ' 1 = 1');
  1060. if($queryID)
  1061. {
  1062. $query = $this->loadModel('search')->getQuery($queryID);
  1063. if($query)
  1064. {
  1065. $this->session->set('testtaskQuery', $query->sql);
  1066. $this->session->set('testtaskForm', $query->form);
  1067. }
  1068. }
  1069. /* 预处理搜索表单生成的查询 SQL。*/
  1070. /* Preprocess the query SQL generated by the search form. */
  1071. $allProduct = "`product` = 'all'";
  1072. $caseQuery = $this->session->testtaskQuery;
  1073. $isQueryProduct = strpos($caseQuery, "`product` = '");
  1074. if(strpos($caseQuery, $allProduct) !== false) $caseQuery = str_replace($allProduct, '1', $caseQuery) . ' AND `product` ' . helper::dbIN($this->app->user->view->products);
  1075. $caseQuery = preg_replace('/`(\w+)`/', 't2.`$1`', $caseQuery);
  1076. $caseQuery = str_replace(array('t2.`assignedTo`', 't2.`lastRunner`', 't2.`lastRunDate`', 't2.`lastRunResult`'), array('t1.`assignedTo`', 't1.`lastRunner`', 't1.`lastRunDate`', 't1.`lastRunResult`'), $caseQuery);
  1077. $orderBy = $this->addPrefixToOrderBy($sort);
  1078. return $this->dao->select("t2.*, t1.*, COALESCE(t3.title, '') AS storyTitle, t2.status AS caseStatus, t2.version AS caseVersion, IF(t4.title IS NULL, t2.title, t4.title) AS title")->from(TABLE_TESTRUN)->alias('t1')
  1079. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
  1080. ->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story = t3.id')
  1081. ->leftJoin(TABLE_CASESPEC)->alias('t4')->on('t1.case = t4.case AND t1.version = t4.version')
  1082. ->where($caseQuery)
  1083. ->andWhere('t1.task')->eq($task->id)
  1084. ->andWhere('t2.deleted')->eq('0')
  1085. ->beginIF($isQueryProduct === false)->andWhere('t2.product')->in($productID)->fi()
  1086. ->beginIF($task->branch)->andWhere('t2.branch')->in("0,{$task->branch}")->fi()
  1087. ->orderBy($orderBy)
  1088. ->page($pager)
  1089. ->fetchAll('id', false);
  1090. }
  1091. return array();
  1092. }
  1093. /**
  1094. * 获取某人负责的测试单的键值对。
  1095. * Get the key-value pair of the testtask someone is responsible for.
  1096. *
  1097. * @param string $account
  1098. * @param int $limit
  1099. * @param string $status all|wait|doing|done|blocked
  1100. * @param array $skipProductIDList
  1101. * @param array $skipExecutionIDList
  1102. * @access public
  1103. * @return array
  1104. */
  1105. public function getUserTestTaskPairs($account, $limit = 0, $status = 'all', $skipProductIDList = array(), $skipExecutionIDList = array())
  1106. {
  1107. $stmt = $this->dao->select('t1.id, t1.name, t2.name AS execution')
  1108. ->from(TABLE_TESTTASK)->alias('t1')
  1109. ->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
  1110. ->leftJoin(TABLE_PRODUCT)->alias('t3')->on('t1.product=t3.id')
  1111. ->where('t1.owner')->eq($account)
  1112. ->andWhere('t1.deleted')->eq('0')
  1113. ->andWhere('t3.deleted')->eq('0')
  1114. ->beginIF($status != 'all')->andWhere('t1.status')->in($status)->fi()
  1115. ->beginIF(!empty($skipProductIDList))->andWhere('t1.product')->notin($skipProductIDList)->fi()
  1116. ->beginIF(!empty($skipExecutionIDList))->andWhere('t1.execution')->notin($skipExecutionIDList)->fi()
  1117. ->beginIF($limit)->limit($limit)->fi()
  1118. ->query();
  1119. $testtaskPairs = array();
  1120. while($testtask = $stmt->fetch())
  1121. {
  1122. if($testtask->execution) $testtask->execution .= " / ";
  1123. $testtaskPairs[$testtask->id] = $testtask->execution . $testtask->name;
  1124. }
  1125. return $testtaskPairs;
  1126. }
  1127. /**
  1128. * 获取执行结果信息。
  1129. * Get information of a testrun.
  1130. *
  1131. * @param int $runID
  1132. * @access public
  1133. * @return object|false
  1134. */
  1135. public function getRunById($runID)
  1136. {
  1137. $run = common::isTutorialMode() ? $this->loadModel('tutorial')->getRun() : $this->dao->findById($runID)->from(TABLE_TESTRUN)->fetch();
  1138. if(!$run) return false;
  1139. $run->case = $this->loadModel('testcase')->getById($run->case, $run->version);
  1140. return $run;
  1141. }
  1142. /**
  1143. * 根据用例 ID 分组获取关联的测试单。
  1144. * Get testtasks associated with cases by case ID.
  1145. *
  1146. * @param int|array $caseIDList
  1147. * @access public
  1148. * @return array
  1149. */
  1150. public function getGroupByCases($caseIDList)
  1151. {
  1152. return $this->dao->select('t1.case, t2.*, t3.branch')->from(TABLE_TESTRUN)->alias('t1')
  1153. ->leftJoin(TABLE_TESTTASK)->alias('t2')->on('t1.task=t2.id')
  1154. ->leftJoin(TABLE_BUILD)->alias('t3')->on('t2.build = t3.id')
  1155. ->where('t1.case')->in($caseIDList)
  1156. ->fetchGroup('case', 'id');
  1157. }
  1158. /**
  1159. * 初始化一条记录便于自动化测试完成后回填测试结果。
  1160. * Init a record for filling back the test result after the automated test is completed.
  1161. *
  1162. * @param int $runID
  1163. * @param int $caseID
  1164. * @param int $version
  1165. * @param int $nodeID
  1166. * @access public
  1167. * @return int|false
  1168. */
  1169. public function initResultForAutomatedTest($runID = 0, $caseID = 0, $version = 0, $nodeID = 0)
  1170. {
  1171. $result = new stdClass();
  1172. $result->run = $runID;
  1173. $result->case = $caseID;
  1174. $result->version = $version;
  1175. $result->node = $nodeID;
  1176. $result->date = helper::now();
  1177. $result->lastRunner = $this->app->user->account;
  1178. $this->dao->insert(TABLE_TESTRESULT)->data($result)->autoCheck()->exec();
  1179. if(dao::isError()) return false;
  1180. return $this->dao->lastInsertID();
  1181. }
  1182. /**
  1183. * 保存一个测试用例的执行结果。
  1184. * Save the execution results of a test case.
  1185. *
  1186. * @param int $runID
  1187. * @param int $caseID
  1188. * @param int $version
  1189. * @param array $stepResults
  1190. * @param int $deployID
  1191. * @access public
  1192. * @return bool|string
  1193. */
  1194. public function createResult($runID, $caseID, $version, $stepResults, $deployID = 0)
  1195. {
  1196. /* 根据测试用例步骤的执行结果获取测试用例的执行结果。*/
  1197. /* Get the execution results of the test case based on the execution results of the test case steps. */
  1198. $caseResult = 'pass';
  1199. foreach($stepResults as $stepResult)
  1200. {
  1201. if($stepResult->result == 'n/a' || $stepResult->result == 'pass') continue;
  1202. if(isset($this->lang->testcase->resultList[$stepResult->result])) $caseResult = $stepResult->result;
  1203. if($stepResult->result == 'fail') break;
  1204. }
  1205. /* 把用例步骤执行结果转换为数组类型以便于和历史数据保持一致。*/
  1206. /* Convert the execution results of the test case steps to array type for consistency with historical data. */
  1207. foreach($stepResults as $key => $stepResult) $stepResults[$key] = (array)$stepResult;
  1208. /* 保存测试用例的执行结果。*/
  1209. /* Save the execution results of the test case. */
  1210. $now = helper::now();
  1211. $result = new stdclass();
  1212. $result->run = $runID;
  1213. $result->case = $caseID;
  1214. $result->version = $version;
  1215. $result->caseResult = $caseResult;
  1216. $result->stepResults = serialize($stepResults);
  1217. $result->lastRunner = $this->app->user->account;
  1218. $result->date = $now;
  1219. $result->deploy = $deployID;
  1220. $this->dao->insert(TABLE_TESTRESULT)->data($result)->autoCheck()->exec();
  1221. if(dao::isError()) return false;
  1222. /* 把上传的文件关联到到执行结果的用例步骤中。*/
  1223. /* Associated the uploaded files to the test case steps of the execution results. */
  1224. $resultID = $this->dao->lastInsertID();
  1225. foreach($stepResults as $stepID => $stepResult) $this->loadModel('file')->saveUpload('stepResult', $resultID, $stepID, "files{$stepID}", "labels{$stepID}");
  1226. /* 更新测试用例的执行结果。*/
  1227. /* Update the execution results of the test case. */
  1228. $case = new stdclass();
  1229. $case->lastRunner = $this->app->user->account;
  1230. $case->lastRunDate = $now;
  1231. $case->lastRunResult = $caseResult;
  1232. $this->dao->update(TABLE_CASE)->data($case)->where('id')->eq($caseID)->exec();
  1233. if(dao::isError()) return false;
  1234. if($runID)
  1235. {
  1236. /* 更新测试单中测试用例的执行结果。*/
  1237. /* Update the execution results of the test case in testtask. */
  1238. $run = new stdclass();
  1239. $run->status = $caseResult == 'blocked' ? 'blocked' : 'normal';
  1240. $run->lastRunResult = $caseResult;
  1241. $run->lastRunner = $this->app->user->account;
  1242. $run->lastRunDate = $now;
  1243. $this->dao->update(TABLE_TESTRUN)->data($run)->where('id')->eq($runID)->exec();
  1244. }
  1245. $this->loadModel('score')->create('testtask', 'runCase', $runID);
  1246. return $caseResult;
  1247. }
  1248. /**
  1249. * 批量执行测试用例并记录测试结果。
  1250. * Batch run test cases and record the test results.
  1251. *
  1252. * @param array $cases
  1253. * @param string $runCaseType
  1254. * @param int $taskID
  1255. * @access public
  1256. * @return bool
  1257. */
  1258. public function batchRun($cases, $runCaseType = 'testcase', $taskID = 0)
  1259. {
  1260. $caseIdList = array_filter(array_keys($cases));
  1261. if(empty($caseIdList)) return false;
  1262. $runs = array();
  1263. if($runCaseType == 'testtask')
  1264. {
  1265. /* 如果是从测试单中批量执行测试用例,查询出测试用例和测试执行的键值对便于更新本次执行结果。*/
  1266. /* If batch run test cases from testtask, query the key-value pair of test cases and test execution for updating the execution results. */
  1267. $runs = $this->dao->select('`case`, id')->from(TABLE_TESTRUN)
  1268. ->where('`case`')->in($caseIdList)
  1269. ->beginIF($taskID)->andWhere('task')->eq($taskID)->fi()
  1270. ->fetchPairs();
  1271. }
  1272. $now = helper::now();
  1273. $result = new stdClass();
  1274. $result->lastRunner = $this->app->user->account;
  1275. $result->date = $now;
  1276. $case = new stdClass();
  1277. $case->lastRunner = $this->app->user->account;
  1278. $case->lastRunDate = $now;
  1279. $run = new stdclass();
  1280. $run->lastRunner = $this->app->user->account;
  1281. $run->lastRunDate = $now;
  1282. $this->loadModel('action');
  1283. foreach($cases as $caseID => $postCase)
  1284. {
  1285. $runID = zget($runs, $caseID, 0);
  1286. $postSteps = zget($postCase, 'steps', array());
  1287. $postReals = zget($postCase, 'reals', array());
  1288. $caseResult = $postCase->results ? $postCase->results : 'pass';
  1289. $stepResults = $this->processStepResults($caseIdList, $caseID, $caseResult, $postSteps, $postReals);
  1290. $result->run = $runID;
  1291. $result->case = $caseID;
  1292. $result->version = $postCase->version;
  1293. $result->caseResult = $caseResult;
  1294. $result->stepResults = serialize($stepResults);
  1295. $this->dao->insert(TABLE_TESTRESULT)->data($result)->autoCheck()->exec();
  1296. $case->lastRunResult = $caseResult;
  1297. $this->dao->update(TABLE_CASE)->data($case)->where('id')->eq($caseID)->exec();
  1298. $this->action->create('case', $caseID, 'run', '', $taskID . ',' . $caseResult);
  1299. if(!$runID) continue;
  1300. $run->lastRunResult = $caseResult;
  1301. $run->status = $caseResult == 'blocked' ? 'blocked' : 'normal';
  1302. $this->dao->update(TABLE_TESTRUN)->data($run)->where('id')->eq($runID)->exec();
  1303. if(dao::isError()) return false;
  1304. }
  1305. return true;
  1306. }
  1307. /**
  1308. * 处理测试用例步骤的执行结果。
  1309. * Process the execution results of the test case steps.
  1310. *
  1311. * @param array $caseIdList
  1312. * @param int $caseID
  1313. * @param string $caseResult
  1314. * @param array $postSteps
  1315. * @param array $postReals
  1316. * @access private
  1317. * @return array
  1318. */
  1319. private function processStepResults($caseIdList, $caseID, $caseResult, $postSteps, $postReals)
  1320. {
  1321. static $stepGroups = array();
  1322. if(empty($stepGroups))
  1323. {
  1324. $stepGroups = $this->dao->select('t1.id, t1.case')->from(TABLE_CASESTEP)->alias('t1')
  1325. ->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id AND t1.version = t2.version')
  1326. ->where('t1.case')->in($caseIdList)
  1327. ->andWhere('t1.type')->ne('group')
  1328. ->andWhere('t2.status')->ne('wait')
  1329. ->fetchGroup('case', 'id');
  1330. }
  1331. if(empty($stepGroups[$caseID]))
  1332. {
  1333. $results[0]['result'] = $caseResult;
  1334. $results[0]['real'] = $caseResult == 'pass' || empty($postReals) ? '' : $postReals[0];
  1335. return $results;
  1336. }
  1337. $results = array();
  1338. $dbSteps = array_keys($stepGroups[$caseID]);
  1339. foreach($dbSteps as $stepID)
  1340. {
  1341. $results[$stepID]['result'] = $caseResult == 'pass' ? $caseResult : $postSteps[$stepID];
  1342. $results[$stepID]['real'] = $caseResult == 'pass' ? '' : $postReals[$stepID] ?? '';
  1343. }
  1344. return $results;
  1345. }
  1346. /**
  1347. * 通过执行 ID 或用例 ID 获取用例的执行结果。
  1348. * Get results by runID or caseID
  1349. *
  1350. * @param int $runID
  1351. * @param int $caseID
  1352. * @param string $status all|done
  1353. * @param string $type all|fail
  1354. * @param int $deployID
  1355. * @access public
  1356. * @return void
  1357. */
  1358. public function getResults($runID, $caseID = 0, $status = 'all', $type = 'all', $deployID = 0)
  1359. {
  1360. if(common::isTutorialMode()) return $this->loadModel('tutorial')->getResults();
  1361. $results = $this->dao->select('*,`stepResults`,`ZTFResult`')->from(TABLE_TESTRESULT)
  1362. ->beginIF($runID > 0)->where('run')->eq($runID)->fi()
  1363. ->beginIF($runID <= 0)->where('`case`')->eq($caseID)->fi()
  1364. ->beginIF($status == 'done')->andWhere('caseResult')->ne('')->fi()
  1365. ->beginIF($type != 'all')->andWhere('caseResult')->eq($type)->fi()
  1366. ->beginIF($deployID)->andWhere('deploy')->eq($deployID)->fi()
  1367. ->orderBy('id desc')
  1368. ->fetchAll('id', false);
  1369. if(!$results) return array();
  1370. list($resultFiles, $stepFiles) = $this->getResultsFiles(array_keys($results));
  1371. $runIdList = $nodeIdList = $relatedVersions = array();
  1372. foreach($results as $result)
  1373. {
  1374. $runIdList[$result->run] = $result->run;
  1375. $nodeIdList[$result->node] = $result->node;
  1376. $relatedVersions[$result->version] = $result->version;
  1377. $runCaseID = $result->case;
  1378. }
  1379. $relatedSteps = $this->dao->select('*')->from(TABLE_CASESTEP)
  1380. ->where('`case`')->eq($runCaseID)
  1381. ->andWhere('version')->in($relatedVersions)
  1382. ->orderBy('id')
  1383. ->fetchGroup('version', 'id');
  1384. $runs = $this->dao->select('t1.id, t1.task, t2.build')->from(TABLE_TESTRUN)->alias('t1')
  1385. ->leftJoin(TABLE_TESTTASK)->alias('t2')->on('t1.task=t2.id')
  1386. ->where('t1.id')->in($runIdList)
  1387. ->fetchAll('id');
  1388. $nodes = $this->dao->select('id,name')->from(TABLE_ZAHOST)->where('id')->in($nodeIdList)->fetchPairs();
  1389. foreach($results as $resultID => $result)
  1390. {
  1391. $result->stepResults = $result->stepResults ? unserialize($result->stepResults) : array();
  1392. $result->build = !empty($runs[$result->run]->build) ? $runs[$result->run]->build : 0;
  1393. $result->task = !empty($runs[$result->run]->task) ? $runs[$result->run]->task : 0;
  1394. $result->nodeName = zget($nodes, $result->node, '');
  1395. $result->files = zget($resultFiles, $resultID, array()); //Get files of case result.
  1396. if(isset($relatedSteps[$result->version])) $result->stepResults = $this->processResultSteps($result, $relatedSteps[$result->version]);
  1397. if(!empty($result->ZTFResult)) $result->ZTFResult = $this->formatZtfLog($result->ZTFResult, $result->stepResults);
  1398. /* Get files of step result. */
  1399. if(!empty($result->stepResults))
  1400. {
  1401. foreach(array_keys($result->stepResults) as $stepID) $result->stepResults[$stepID]['files'] = isset($stepFiles[$resultID][$stepID]) ? $stepFiles[$resultID][$stepID] : array();
  1402. }
  1403. }
  1404. return $results;
  1405. }
  1406. /**
  1407. * 计算用例执行结果的步骤。
  1408. * Process steps of result.
  1409. *
  1410. * @param object $result
  1411. * @param array $relatedStep
  1412. * @access public
  1413. * @return array
  1414. */
  1415. public function processResultSteps($result, $relatedStep)
  1416. {
  1417. $this->loadModel('testcase');
  1418. $preGrade = 1;
  1419. $parentSteps = array();
  1420. $key = array(0, 0, 0);
  1421. foreach($relatedStep as $stepID => $step)
  1422. {
  1423. $parentSteps[$step->id] = $step->parent;
  1424. $grade = 1;
  1425. if(isset($parentSteps[$step->parent]))
  1426. {
  1427. $grade = isset($parentSteps[$parentSteps[$step->parent]]) ? 3 : 2;
  1428. }
  1429. if($grade > $preGrade)
  1430. {
  1431. $key[$grade - 1] = 1;
  1432. }
  1433. else
  1434. {
  1435. if($grade < $preGrade)
  1436. {
  1437. if($grade < 2) $key[1] = 0;
  1438. if($grade < 3) $key[2] = 0;
  1439. }
  1440. $key[$grade - 1] ++;
  1441. }
  1442. $name = implode('.', $key);
  1443. $name = str_replace('.0', '', $name);
  1444. $relatedStep[$stepID] = (array)$step;
  1445. $relatedStep[$stepID]['name'] = $name;
  1446. $relatedStep[$stepID]['grade'] = $grade;
  1447. $relatedStep[$stepID]['desc'] = html_entity_decode($relatedStep[$stepID]['desc']);
  1448. $relatedStep[$stepID]['expect'] = html_entity_decode($relatedStep[$stepID]['expect']);
  1449. if(isset($result->stepResults[$stepID]))
  1450. {
  1451. $relatedStep[$stepID]['result'] = $result->stepResults[$stepID]['result'];
  1452. $relatedStep[$stepID]['real'] = $result->stepResults[$stepID]['real'];
  1453. }
  1454. $preGrade = $grade;
  1455. }
  1456. return $relatedStep;
  1457. }
  1458. /**
  1459. * 获取结果的文件。
  1460. * Get files of the results.
  1461. *
  1462. * @param array $resultIdList
  1463. * @access public
  1464. * @return array
  1465. */
  1466. public function getResultsFiles($resultIdList)
  1467. {
  1468. $resultFiles = array();
  1469. $stepFiles = array();
  1470. $files = $this->dao->select('*')->from(TABLE_FILE)
  1471. ->where('objectType')->in('caseResult, stepResult')
  1472. ->andWhere('objectID')->in($resultIdList)
  1473. ->andWhere('extra')->ne('editor')
  1474. ->orderBy('id')
  1475. ->fetchAll('id', false);
  1476. $this->loadModel('file');
  1477. foreach($files as $file)
  1478. {
  1479. $this->file->setFileWebAndRealPaths($file);
  1480. if($file->objectType == 'caseResult')
  1481. {
  1482. $resultFiles[$file->objectID][$file->id] = $file;
  1483. }
  1484. elseif($file->objectType == 'stepResult')
  1485. {
  1486. $stepFiles[$file->objectID][(int)$file->extra][$file->id] = $file;
  1487. }
  1488. }
  1489. return array($resultFiles, $stepFiles);
  1490. }
  1491. /**
  1492. * 格式化 ztf 的执行日志。
  1493. * Format the execution log of ztf.
  1494. *
  1495. * @param string $result
  1496. * @param array $stepResults
  1497. * @access public
  1498. * @return string
  1499. */
  1500. public function formatZtfLog($result, $stepResults)
  1501. {
  1502. $logObj = json_decode($result);
  1503. $logs = empty($logObj->log) ? '' : $logObj->log;
  1504. if(empty($logs)) return '';
  1505. $logs = str_replace(array("\r", "\n", "\r\n"), "\n", $logs);
  1506. $logList = explode("\n", $logs);
  1507. $logHtml = '';
  1508. foreach($logList as $log)
  1509. {
  1510. $log = preg_replace("/^[\d\-:.\x20]+/", '', $log);
  1511. $log = trim($log);
  1512. if(empty($log)) continue;
  1513. $failHtml = ": <span class='result-testcase fail'>{$this->lang->testtask->fail}</span>";
  1514. $passHtml = ": <span class='result-testcase pass'>{$this->lang->testtask->pass}</span>";
  1515. $log = preg_replace(array("/:\x20失败/", "/:\x20fail/", "/:\x20成功/", "/:\x20pass/"), array($failHtml, $failHtml, $passHtml, $passHtml), $log);
  1516. $logHtml .= "<li>{$log}</li>";
  1517. }
  1518. if(!empty($stepResults))
  1519. {
  1520. $total = count($stepResults);
  1521. $failCount = $passCount = 0;
  1522. foreach($stepResults as $step)
  1523. {
  1524. if(!isset($step['result'])) continue;
  1525. if($step['result'] == 'pass')
  1526. {
  1527. $passCount++;
  1528. }
  1529. elseif($step['result'] == 'fail')
  1530. {
  1531. $failCount++;
  1532. }
  1533. }
  1534. $caseResult = $passCount ? 'pass':'fail';
  1535. $logHtml .= "<li class='result-testcase {$caseResult}'>" . sprintf($this->lang->testtask->stepSummary, $total, $passCount, $failCount) . '</li>';
  1536. }
  1537. return $logHtml;
  1538. }
  1539. /**
  1540. * 判断一个动作是否可以执行。
  1541. * Determine whether an action can be performed.
  1542. *
  1543. * @param object $testtask
  1544. * @param string $action
  1545. * @access public
  1546. * @return bool
  1547. */
  1548. public static function isClickable($testtask, $action)
  1549. {
  1550. $action = strtolower($action);
  1551. if(isset($testtask->status) && empty($testtask->rawStatus)) $testtask->rawStatus = $testtask->status;
  1552. if($action == 'start') return $testtask->rawStatus == 'wait';
  1553. if($action == 'block') return ($testtask->rawStatus == 'doing' || $testtask->rawStatus == 'wait');
  1554. if($action == 'activate') return ($testtask->rawStatus == 'blocked' || $testtask->rawStatus == 'done');
  1555. if($action == 'close') return $testtask->rawStatus != 'done';
  1556. if($action == 'ztfrun') return $testtask->auto == 'auto';
  1557. if($action == 'runcase') return (empty($testtask->lib) || !empty($testtask->product)) && isset($testtask->rawStatus) && $testtask->rawStatus != 'wait';
  1558. return true;
  1559. }
  1560. /**
  1561. * 获取一个测试单的收件人和抄送人。
  1562. * Get the recipient and cc of a testtask.
  1563. *
  1564. * @param object $testtask
  1565. * @access public
  1566. * @return bool|array
  1567. */
  1568. public function getToAndCcList($testtask)
  1569. {
  1570. /* Set toList and ccList. */
  1571. $toList = zget($testtask, 'owner', '') . ',' . zget($testtask, 'members', '') . ',';
  1572. $toList = trim($toList, ',');
  1573. $ccList = str_replace(' ', '', trim(zget($testtask, 'mailto', ''), ','));
  1574. if(empty($toList))
  1575. {
  1576. if(empty($ccList)) return false;
  1577. if(strpos($ccList, ',') === false)
  1578. {
  1579. $toList = $ccList;
  1580. $ccList = '';
  1581. }
  1582. else
  1583. {
  1584. $commaPos = strpos($ccList, ',');
  1585. $toList = substr($ccList, 0, $commaPos);
  1586. $ccList = substr($ccList, $commaPos + 1);
  1587. }
  1588. }
  1589. return array($toList, $ccList);
  1590. }
  1591. /**
  1592. * 导入单元测试结果。
  1593. * Import unit test results.
  1594. *
  1595. * @param object $task
  1596. * @access public
  1597. * @return bool|int
  1598. */
  1599. public function importUnitResult($task)
  1600. {
  1601. /* 获取上传的单元测试文件。*/
  1602. /* Get uploaded unit test file. */
  1603. $files = $this->loadModel('file')->getUpload('resultFile');
  1604. if(empty($files[0]))
  1605. {
  1606. dao::$errors['resultFile'][] = $this->lang->testtask->unitXMLFormat;
  1607. return false;
  1608. }
  1609. /* 使用 simplexml 读取文件。*/
  1610. /* Use simplexml to read file. */
  1611. $file = $files[0];
  1612. $filePath = $this->file->savePath . $this->file->getSaveName($file['pathname']);
  1613. move_uploaded_file($file['tmpname'], $filePath);
  1614. $parsedXML = simplexml_load_file($filePath, null, LIBXML_NOERROR);
  1615. if($parsedXML === false)
  1616. {
  1617. dao::$errors['resultFile'][] = $this->lang->testtask->cannotBeParsed;
  1618. return false;
  1619. }
  1620. /* 解析 xml 文件。*/
  1621. /* Parse xml file. */
  1622. $data = $this->parseXMLResult($parsedXML, $task->product, $task->frame);
  1623. if(empty($data['cases']) && $task->frame == 'cppunit') $data = $this->parseCppXMLResult($parsedXML, $task->product, $task->frame);
  1624. if(empty($data['cases']))
  1625. {
  1626. dao::$errors['resultFile'][] = $this->lang->testtask->noImportData;
  1627. return false;
  1628. }
  1629. /* 创建测试单。*/
  1630. /* Create test task. */
  1631. unset($task->frame);
  1632. $taskID = $this->create($task);
  1633. if(dao::isError()) return false;
  1634. unlink($filePath);
  1635. unset($_SESSION['resultFile']);
  1636. /* 导入单元测试结果文件中包含的数据。*/
  1637. /* Import data in the unit test results. */
  1638. $this->importDataOfUnitResult($taskID, $task->product, $data['suites'], $data['cases'], $data['results'], $data['suiteNames'], $data['caseTitles'], 'unit');
  1639. return $taskID;
  1640. }
  1641. /**
  1642. * 导入单元测试结果文件中包含的数据。
  1643. * Import data in the unit test results.
  1644. *
  1645. * @param int $taskID
  1646. * @param int $productID
  1647. * @param array $suites
  1648. * @param array $cases
  1649. * @param array $results
  1650. * @param array $suiteNames
  1651. * @param array $caseTitles
  1652. * @param string $auto unit|func
  1653. * @access public
  1654. * @return bool
  1655. */
  1656. public function importDataOfUnitResult($taskID, $productID, $suites, $cases, $results, $suiteNames, $caseTitles, $auto = 'unit')
  1657. {
  1658. $this->loadModel('action');
  1659. /* 初始化对象便于在循环中使用。*/
  1660. /* Initialize the object for use in loops. */
  1661. $suiteCase = new stdclass();
  1662. $testRun = new stdclass();
  1663. $testRun->task = $taskID;
  1664. $testRun->status = 'normal';
  1665. $existSuites = $suiteNames ? $this->getExistSuitesOfUnitResult($suiteNames, $productID, $auto) : array();
  1666. foreach($suites as $suiteIndex => $suite)
  1667. {
  1668. $suiteID = 0;
  1669. if($suite) $suiteID = isset($existSuites[$suite->name]) ? $existSuites[$suite->name] : $this->importSuiteOfUnitResult($suite);
  1670. $suiteCase->suite = $suiteID;
  1671. $importCases = zget($cases, $suiteIndex, array());
  1672. $existCases = !empty($caseTitles[$suiteIndex]) ? $this->getExistCasesOfUnitResult($caseTitles[$suiteIndex], $suiteID, $productID, $auto) : array();
  1673. foreach($importCases as $key => $case)
  1674. {
  1675. $caseID = $this->importCaseOfUnitResult($case, $existCases);
  1676. $runID = $this->importRunOfUnitResult($case, $caseID, $testRun);
  1677. if($suiteID) $this->linkImportedCaseToSuite($case, $caseID, $suiteCase);
  1678. $testresult = $results[$suiteIndex][$key];
  1679. $testresult->run = $runID;
  1680. $testresult->case = $caseID;
  1681. $testresult->version = isset($case->version) ? $case->version : 1;
  1682. $this->dao->insert(TABLE_TESTRESULT)->data($testresult)->exec();
  1683. }
  1684. }
  1685. return !dao::isError();
  1686. }
  1687. /**
  1688. * 导入单元测试结果文件中的测试套件。
  1689. * Import test suite in the unit test results.
  1690. *
  1691. * @param object $suite
  1692. * @access private
  1693. * @return int
  1694. */
  1695. private function importSuiteOfUnitResult($suite)
  1696. {
  1697. $this->dao->insert(TABLE_TESTSUITE)->data($suite)->exec();
  1698. $suiteID = $this->dao->lastInsertID();
  1699. $this->action->create('testsuite', $suiteID, 'opened');
  1700. return $suiteID;
  1701. }
  1702. /**
  1703. * 导入单元测试结果文件中的测试用例。
  1704. * Import test case in the unit test results.
  1705. *
  1706. * @param object $case
  1707. * @param array $existCases
  1708. * @access private
  1709. * @return int
  1710. */
  1711. private function importCaseOfUnitResult(&$case, $existCases)
  1712. {
  1713. if(!empty($case->id))
  1714. {
  1715. unset($case->steps);
  1716. $caseID = $case->id;
  1717. $oldCase = $this->dao->select('*')->from(TABLE_CASE)->where('id')->eq($caseID)->fetch();
  1718. $case->version = $oldCase->version;
  1719. $case->openedDate = $oldCase->openedDate;
  1720. $changes = common::createChanges($oldCase, $case);
  1721. if($changes)
  1722. {
  1723. $this->dao->update(TABLE_CASE)->data($case)->where('id')->eq($caseID)->exec();
  1724. $actionID = $this->action->create('case', $caseID, 'Edited');
  1725. $this->action->logHistory($actionID, $changes);
  1726. }
  1727. return $caseID;
  1728. }
  1729. if(!isset($existCases[$case->title]))
  1730. {
  1731. $this->dao->insert(TABLE_CASE)->data($case, 'steps')->exec();
  1732. $caseID = $this->dao->lastInsertID();
  1733. $steps = zget($case, 'steps', array());
  1734. foreach($steps as $step)
  1735. {
  1736. $step->case = $caseID;
  1737. $step->version = 1;
  1738. $this->dao->insert(TABLE_CASESTEP)->data($step)->exec();
  1739. }
  1740. $this->action->create('case', $caseID, 'Opened');
  1741. return $caseID;
  1742. }
  1743. return $existCases[$case->title];
  1744. }
  1745. /**
  1746. * 导入单元测试结果文件中的测试结果。
  1747. * Import test result in the unit test results.
  1748. *
  1749. * @param object $case
  1750. * @param int $caseID
  1751. * @param object $testRun
  1752. * @access private
  1753. * @return int
  1754. */
  1755. private function importRunOfUnitResult($case, $caseID, $testRun)
  1756. {
  1757. $testRun->case = $caseID;
  1758. $testRun->version = $case->version;
  1759. $testRun->lastRunner = $case->lastRunner;
  1760. $testRun->lastRunDate = $case->lastRunDate;
  1761. $testRun->lastRunResult = $case->lastRunResult;
  1762. $testRunInfo = $this->dao->select('id')->from(TABLE_TESTRUN)->where('case')->eq($caseID)->andWhere('task')->eq($testRun->task)->fetch();
  1763. if(isset($testRunInfo->id))
  1764. {
  1765. $this->dao->update(TABLE_TESTRUN)->data($testRun)->where('id')->eq($testRunInfo->id)->exec();
  1766. return $testRunInfo->id;
  1767. }
  1768. $this->dao->insert(TABLE_TESTRUN)->data($testRun)->exec();
  1769. return dao::isError() ? 0 : $this->dao->lastInsertID();
  1770. }
  1771. /**
  1772. * 将导入的测试用例关联到测试套件。
  1773. * Link imported case to suite.
  1774. *
  1775. * @param object $case
  1776. * @param int $caseID
  1777. * @param object $suiteCase
  1778. * @access private
  1779. * @return bool
  1780. */
  1781. private function linkImportedCaseToSuite($case, $caseID, $suiteCase)
  1782. {
  1783. $suiteCase->case = $caseID;
  1784. $suiteCase->version = $case->version;
  1785. $suiteCase->product = $case->product;
  1786. $this->dao->replace(TABLE_SUITECASE)->data($suiteCase)->exec();
  1787. return !dao::isError();
  1788. }
  1789. /**
  1790. * 获取单元测试结果文件中已存在的测试套件。
  1791. * Get exist suites in the unit test results.
  1792. *
  1793. * @param array $names
  1794. * @param int $productID
  1795. * @param string $auto
  1796. * @access public
  1797. * @return array
  1798. */
  1799. private function getExistSuitesOfUnitResult($names, $productID, $auto)
  1800. {
  1801. if(!$names) return array();
  1802. return $this->dao->select('name, id')->from(TABLE_TESTSUITE)
  1803. ->where('name')->in($names)
  1804. ->andWhere('product')->eq($productID)
  1805. ->andWhere('type')->eq($auto)
  1806. ->andWhere('deleted')->eq('0')
  1807. ->fetchPairs();
  1808. }
  1809. /**
  1810. * 获取单元测试结果文件中已存在的测试用例。
  1811. * Get exist cases in the unit test results.
  1812. *
  1813. * @param array $titles
  1814. * @param int $suiteID
  1815. * @param int $productID
  1816. * @param string $auto
  1817. * @access public
  1818. * @return array
  1819. */
  1820. private function getExistCasesOfUnitResult($titles, $suiteID, $productID, $auto)
  1821. {
  1822. if(!$titles) return array();
  1823. $this->dao->select('t1.title, t1.id')->from(TABLE_CASE)->alias('t1');
  1824. if($suiteID) $this->dao->leftJoin(TABLE_SUITECASE)->alias('t2')->on('t1.id=t2.case');
  1825. return $this->dao->where('t1.title')->in($titles)
  1826. ->andWhere('t1.product')->eq($productID)
  1827. ->beginIF($auto == 'unit')->andWhere('t1.auto')->eq($auto)->fi()
  1828. ->andWhere('t1.deleted')->eq('0')
  1829. ->orderBy('t1.id')
  1830. ->fetchPairs();
  1831. }
  1832. /**
  1833. * 解析 xml 文件中的 cppunit 的单元测试结果。
  1834. * Parse unit test result from cppunit xml.
  1835. *
  1836. * @param object $parsedXML
  1837. * @param int $productID
  1838. * @param string $frame
  1839. * @access public
  1840. * @return array
  1841. */
  1842. public function parseCppXMLResult($parsedXML, $productID, $frame)
  1843. {
  1844. /* Get testcase node. */
  1845. $failNodes = $parsedXML->xpath('FailedTests/FailedTest');
  1846. $passNodes = $parsedXML->xpath('SuccessfulTests/Test');
  1847. $matchNodes = array_merge($failNodes, $passNodes);
  1848. if(count($matchNodes) == 0) return array('suites' => array(), 'cases' => array(), 'results' => array(), 'suiteNames' => array(), 'caseTitles' => array());
  1849. /* Get cases and results by parsed node. */
  1850. $now = helper::now();
  1851. $cases = array();
  1852. $results = array();
  1853. $caseTitles = array();
  1854. $suiteIndex = 0;
  1855. $suites = array($suiteIndex => '');
  1856. foreach($matchNodes as $caseIndex => $matchNode)
  1857. {
  1858. $result = $this->initResult($now);
  1859. $result->duration = 0;
  1860. $result->xml = $matchNode->asXML();
  1861. if(isset($matchNode->Message))
  1862. {
  1863. $result->caseResult = 'fail';
  1864. $result->stepResults[0]['result'] = 'fail';
  1865. $result->stepResults[0]['real'] = (string)$matchNode->Message;
  1866. }
  1867. $result->stepResults = serialize($result->stepResults);
  1868. $case = $this->initCase($productID, (string)$matchNode->name, $now, 'unit', $frame ?: 'junit');
  1869. $case->lastRunResult = $result->caseResult;
  1870. $caseTitles[$suiteIndex][] = $case->title;
  1871. $cases[$suiteIndex][$caseIndex] = $case;
  1872. $results[$suiteIndex][$caseIndex] = $result;
  1873. }
  1874. return array('suites' => $suites, 'cases' => $cases, 'results' => $results, 'suiteNames' => array(), 'caseTitles' => $caseTitles);
  1875. }
  1876. /**
  1877. * 解析 xml 文件中的单元测试结果。
  1878. * Parse unit test result from xml.
  1879. *
  1880. * @param object $parsedXML
  1881. * @param int $productID
  1882. * @param string $frame
  1883. * @access public
  1884. * @return array
  1885. */
  1886. public function parseXMLResult($parsedXML, $productID, $frame)
  1887. {
  1888. /* Parse result xml. */
  1889. $rules = zget($this->config->testtask->unitResultRules, $frame, $this->config->testtask->unitResultRules->common);
  1890. /* Get testcase node. */
  1891. $matchPaths = $rules['path'];
  1892. $nameFields = $rules['name'];
  1893. $failure = $rules['failure'];
  1894. $skipped = $rules['skipped'];
  1895. $suiteField = $rules['suite'];
  1896. $aliasSuite = zget($rules, 'aliasSuite', array());
  1897. $aliasName = zget($rules, 'aliasName', array());
  1898. $matchNodes = array();
  1899. foreach($matchPaths as $matchPath)
  1900. {
  1901. $matchNodes = $parsedXML->xpath($matchPath);
  1902. if(count($matchNodes) != 0) break;
  1903. }
  1904. if(count($matchNodes) == 0) return array('suites' => array(), 'cases' => array(), 'results' => array(), 'suiteNames' => array(), 'caseTitles' => array());
  1905. $parentPath = '';
  1906. $caseNode = $matchPath;
  1907. $parentNodes = array($parsedXML);
  1908. if(strpos($matchPath, '/') !== false)
  1909. {
  1910. $explodedPath = explode('/', $matchPath);
  1911. $caseNode = array_pop($explodedPath);
  1912. $parentPath = implode('/', $explodedPath);
  1913. $parentNodes = $parsedXML->xpath($parentPath);
  1914. }
  1915. /* Get cases and results by parsed node. */
  1916. $now = helper::now();
  1917. $cases = array();
  1918. $results = array();
  1919. $suites = array();
  1920. $caseTitles = array();
  1921. $suiteNames = array();
  1922. foreach($parentNodes as $suiteIndex => $parentNode)
  1923. {
  1924. $caseNodes = $parentNode->xpath($caseNode);
  1925. $attributes = $parentNode->attributes();
  1926. $suite = '';
  1927. if(isset($attributes[$suiteField]))
  1928. {
  1929. $suite = $this->initSuite($productID, (string)$attributes[$suiteField], $now);
  1930. $suiteNames[] = $suite->name;
  1931. }
  1932. else
  1933. {
  1934. $attributes = $caseNodes[0]->attributes();
  1935. foreach($aliasSuite as $alias)
  1936. {
  1937. if(isset($attributes[$alias]))
  1938. {
  1939. $suite = $this->initSuite($productID, (string)$attributes[$alias], $now);
  1940. $suiteNames[] = $suite->name;
  1941. break;
  1942. }
  1943. }
  1944. }
  1945. $suites[$suiteIndex] = $suite;
  1946. foreach($caseNodes as $caseIndex => $matchNode)
  1947. {
  1948. $case = $this->initCase($productID, '', $now, 'unit', $frame ?: 'junit');
  1949. $attributes = $matchNode->attributes();
  1950. foreach($nameFields as $field)
  1951. {
  1952. if(!isset($attributes[$field])) continue;
  1953. $case->title .= (string)$attributes[$field] . ' ';
  1954. }
  1955. $case->title = trim($case->title);
  1956. if(empty($case->title))
  1957. {
  1958. foreach($aliasName as $field)
  1959. {
  1960. if(!isset($attributes[$field])) continue;
  1961. $case->title .= (string)$attributes[$field] . ' ';
  1962. }
  1963. $case->title = trim($case->title);
  1964. }
  1965. if(empty($case->title)) continue;
  1966. $result = $this->initResult($now);
  1967. $result->duration = isset($attributes['time']) ? (float)$attributes['time'] : 0;
  1968. $result->xml = $matchNode->asXML();
  1969. $result = $this->processResult($result, $matchNode, $failure, $skipped);
  1970. $result->stepResults = serialize($result->stepResults);
  1971. $case->lastRunResult = $result->caseResult;
  1972. $caseTitles[$suiteIndex][] = $case->title;
  1973. $cases[$suiteIndex][$caseIndex] = $case;
  1974. $results[$suiteIndex][$caseIndex] = $result;
  1975. }
  1976. }
  1977. return array('suites' => $suites, 'cases' => $cases, 'results' => $results, 'suiteNames' => $suiteNames, 'caseTitles' => $caseTitles);
  1978. }
  1979. /**
  1980. * 解析 ztf 的单元测试结果。
  1981. * Parse unit test result of ztf.
  1982. *
  1983. * @param array $caseResults
  1984. * @param string $frame
  1985. * @param int $productID
  1986. * @param int $jobID
  1987. * @param int $compileID
  1988. * @access public
  1989. * @return array
  1990. */
  1991. public function parseZTFUnitResult($caseResults, $frame, $productID, $jobID, $compileID)
  1992. {
  1993. $now = helper::now();
  1994. $cases = array();
  1995. $results = array();
  1996. $suites = array();
  1997. $caseTitles = array();
  1998. $suiteNames = array();
  1999. $suiteIndex = 0;
  2000. foreach($caseResults as $caseIndex => $caseResult)
  2001. {
  2002. $suite = '';
  2003. if(isset($caseResult->testSuite) and !isset($suiteNames[$caseResult->testSuite]))
  2004. {
  2005. $suite = $this->initSuite($productID, $caseResult->testSuite, $now);
  2006. $suiteNames[$suite->name] = $suite->name;
  2007. $suiteIndex ++;
  2008. }
  2009. if(!isset($suites[$suiteIndex])) $suites[$suiteIndex] = $suite;
  2010. $result = $this->initResult($now);
  2011. $result->job = $jobID;
  2012. $result->compile = $compileID;
  2013. $result->duration = zget($caseResult, 'duration', 0);
  2014. if(!empty($caseResult->failure))
  2015. {
  2016. $result->caseResult = 'fail';
  2017. $result->stepResults[0]['result'] = 'fail';
  2018. $result->stepResults[0]['real'] = zget($caseResult->failure, 'desc', '');
  2019. }
  2020. $result->stepResults = serialize($result->stepResults);
  2021. $case = $this->initCase($productID, '', $now, '', $frame);
  2022. $case->lastRunResult = $result->caseResult;
  2023. if(!empty($caseResult->id)) $case->id = $caseResult->id;
  2024. if(empty($caseResult->id)) $case->title = $caseResult->title;
  2025. if(empty($caseResult->id)) $case->auto = 'unit';
  2026. $caseTitles[$suiteIndex][] = $case->title;
  2027. $cases[$suiteIndex][$caseIndex] = $case;
  2028. $results[$suiteIndex][$caseIndex] = $result;
  2029. }
  2030. return array('suites' => $suites, 'cases' => $cases, 'results' => $results, 'suiteNames' => $suiteNames, 'caseTitles' => $caseTitles);
  2031. }
  2032. /**
  2033. * 解析 ztf 的功能测试结果。
  2034. * Parse function test result of ztf.
  2035. *
  2036. * @param array $caseResults
  2037. * @param string $frame
  2038. * @param int $productID
  2039. * @param int $jobID
  2040. * @param int $compileID
  2041. * @access public
  2042. * @return array
  2043. */
  2044. public function parseZTFFuncResult($caseResults, $frame, $productID, $jobID, $compileID)
  2045. {
  2046. $now = helper::now();
  2047. $cases = array();
  2048. $results = array();
  2049. $suites = array();
  2050. $caseTitles = array();
  2051. $suiteNames = array();
  2052. $suiteIndex = 0;
  2053. foreach($caseResults as $caseIndex => $caseResult)
  2054. {
  2055. $suite = '';
  2056. if(!isset($suites[$suiteIndex])) $suites[$suiteIndex] = $suite;
  2057. $steps = array();
  2058. $result = $this->initResult($now);
  2059. $result->job = $jobID;
  2060. $result->compile = $compileID;
  2061. if(!empty($caseResult->steps))
  2062. {
  2063. $result->stepResults = array();
  2064. $stepStatus = 'pass';
  2065. foreach($caseResult->steps as $i => $step)
  2066. {
  2067. if(!$step->status) $step->status = 'fail';
  2068. $result->stepResults[$i]['result'] = $step->status;
  2069. $result->stepResults[$i]['real'] = $step->checkPoints[0]->actual;
  2070. if($step->status == 'fail') $stepStatus = 'fail';
  2071. $caseStep = new stdclass();
  2072. $caseStep->type = 'step';
  2073. $caseStep->desc = $step->name;
  2074. $caseStep->expect = $step->checkPoints[0]->expect;
  2075. $steps[] = $caseStep;
  2076. }
  2077. $result->caseResult = $stepStatus;
  2078. }
  2079. $result->stepResults = serialize($result->stepResults);
  2080. $case = $this->initCase(0, $caseResult->title, $now, 'func', $frame, 'feature', 'feature');
  2081. $case->steps = $steps;
  2082. $case->lastRunResult = $result->caseResult;
  2083. if(isset($caseResult->id)) $case->id = $caseResult->id;
  2084. $case->product = $productID;
  2085. $caseTitles[$suiteIndex][] = $case->title;
  2086. $cases[$suiteIndex][$caseIndex] = $case;
  2087. $results[$suiteIndex][$caseIndex] = $result;
  2088. }
  2089. return array('suites' => $suites, 'cases' => $cases, 'results' => $results, 'suiteNames' => $suiteNames, 'caseTitles' => $caseTitles);
  2090. }
  2091. /**
  2092. * 初始化测试套件。
  2093. * Initialize the test suite.
  2094. *
  2095. * @param int $product
  2096. * @param string $name
  2097. * @param string $now
  2098. * @access private
  2099. * @return object
  2100. */
  2101. private function initSuite($product, $name, $now)
  2102. {
  2103. $suite = new stdclass();
  2104. $suite->product = $product;
  2105. $suite->name = $name;
  2106. $suite->type = 'unit';
  2107. $suite->addedBy = $this->app->user->account;
  2108. $suite->addedDate = $now;
  2109. return $suite;
  2110. }
  2111. /**
  2112. * 初始化测试用例。
  2113. * Initialize the test case.
  2114. *
  2115. * @param int $product
  2116. * @param string $title
  2117. * @param string $now
  2118. * @param string $auto
  2119. * @param string $frame
  2120. * @param string $type
  2121. * @param string $stage
  2122. * @access private
  2123. * @return object
  2124. */
  2125. private function initCase($product, $title, $now, $auto, $frame, $type = 'unit', $stage = 'unittest')
  2126. {
  2127. $case = new stdclass();
  2128. $case->product = $product;
  2129. $case->title = $title;
  2130. $case->pri = 3;
  2131. $case->type = $type;
  2132. $case->stage = $stage;
  2133. $case->status = 'normal';
  2134. $case->openedBy = $this->app->user->account;
  2135. $case->openedDate = $now;
  2136. $case->version = 1;
  2137. $case->auto = $auto;
  2138. $case->frame = $frame;
  2139. $case->lastRunner = $this->app->user->account;
  2140. $case->lastRunDate = $now;
  2141. return $case;
  2142. }
  2143. /**
  2144. * 初始化测试用例执行结果。
  2145. * Initialize the execution result of the test case.
  2146. *
  2147. * @param string $now
  2148. * @access private
  2149. * @return object
  2150. */
  2151. private function initResult($now)
  2152. {
  2153. $result = new stdclass();
  2154. $result->case = 0;
  2155. $result->version = 1;
  2156. $result->caseResult = 'pass';
  2157. $result->lastRunner = $this->app->user->account;
  2158. $result->date = $now;
  2159. $result->stepResults[0]['result'] = 'pass';
  2160. $result->stepResults[0]['real'] = '';
  2161. return $result;
  2162. }
  2163. /**
  2164. * 根据导入的 xml 文件内容处理测试用例的执行结果。
  2165. * Process the execution result of the test case according to the imported xml file content.
  2166. *
  2167. * @param object $result
  2168. * @param object $matchNode
  2169. * @param string $failure
  2170. * @param string $skipped
  2171. * @access private
  2172. * @return object
  2173. */
  2174. private function processResult($result, $matchNode, $failure, $skipped)
  2175. {
  2176. if(isset($matchNode->$failure))
  2177. {
  2178. $result->caseResult = 'fail';
  2179. $result->stepResults[0]['result'] = 'fail';
  2180. if(is_string($matchNode->$failure))
  2181. {
  2182. $result->stepResults[0]['real'] = (string)$matchNode->$failure;
  2183. return $result;
  2184. }
  2185. if(isset($matchNode->{$failure}[0]))
  2186. {
  2187. $result->stepResults[0]['real'] = (string)$matchNode->{$failure}[0];
  2188. return $result;
  2189. }
  2190. $failureAttrs = $matchNode->$failure->attributes();
  2191. $result->stepResults[0]['real'] = (string)$failureAttrs['message'];
  2192. return $result;
  2193. }
  2194. if(isset($matchNode->$skipped))
  2195. {
  2196. $result->caseResult = 'n/a';
  2197. $result->stepResults[0]['result'] = 'n/a';
  2198. $result->stepResults[0]['real'] = '';
  2199. return $result;
  2200. }
  2201. return $result;
  2202. }
  2203. /**
  2204. * 根据版本查询测试用例。
  2205. * Query test cases by version.
  2206. *
  2207. * @param int $buildID
  2208. * @access public
  2209. * @return object|false
  2210. */
  2211. public function getByBuild($buildID)
  2212. {
  2213. return $this->dao->select('*')->from(TABLE_TESTTASK)
  2214. ->where('build')->eq($buildID)
  2215. ->andWhere('deleted')->eq('0')
  2216. ->fetch();
  2217. }
  2218. /**
  2219. * 获取测试单的所属分支信息。
  2220. *
  2221. * @param object $task
  2222. * @param int $productID
  2223. * @access public
  2224. * @return array
  2225. */
  2226. public function getBranchesByTask($task, $productID = 0)
  2227. {
  2228. $this->app->loadLang('branch');
  2229. $branchPairs = array();
  2230. if(!empty($task->joint))
  2231. {
  2232. $branches = $this->dao->select('t2.id,t2.branch')->from(TABLE_TESTTASKPRODUCT)->alias('t1')
  2233. ->leftJoin(TABLE_BUILD)->alias('t2')->on('t1.build=t2.id')
  2234. ->where('t1.task')->eq($task->id)
  2235. ->beginIF(!empty($productID))->andWhere('t1.product')->eq($productID)->fi()
  2236. ->fetchPairs();
  2237. }
  2238. else
  2239. {
  2240. $branches = $this->dao->select('id,branch')->from(TABLE_BUILD)->where('id')->eq($task->build)->fetchPairs();
  2241. }
  2242. if(empty($branches)) return array(BRANCH_MAIN => $this->lang->branch->main);
  2243. $branchIdList = '';
  2244. foreach($branches as $branch) $branchIdList .= $branch . ',';
  2245. $branchIdList = trim($branchIdList, ',');
  2246. if(empty($branchIdList)) return array(BRANCH_MAIN => $this->lang->branch->main);
  2247. $branchPairs = $this->dao->select('id,name')->from(TABLE_BRANCH)->where('id')->in($branchIdList)->fetchPairs();
  2248. return arrayUnion(array(BRANCH_MAIN => $this->lang->branch->main), $branchPairs);
  2249. }
  2250. }