test.class.inc.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. <?php
  2. // Copyright (C) 2010-2017 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * Core automated tests - basics
  20. *
  21. * @copyright Copyright (C) 2010-2017 Combodo SARL
  22. * @license http://opensource.org/licenses/AGPL-3.0
  23. */
  24. require_once(APPROOT.'/core/coreexception.class.inc.php');
  25. require_once(APPROOT.'/core/attributedef.class.inc.php');
  26. require_once(APPROOT.'/core/filterdef.class.inc.php');
  27. require_once(APPROOT.'/core/stimulus.class.inc.php');
  28. require_once(APPROOT.'/core/MyHelpers.class.inc.php');
  29. require_once(APPROOT.'/core/oql/expression.class.inc.php');
  30. require_once(APPROOT.'/core/cmdbsource.class.inc.php');
  31. require_once(APPROOT.'/core/sqlquery.class.inc.php');
  32. require_once(APPROOT.'/core/sqlobjectquery.class.inc.php');
  33. require_once(APPROOT.'/core/sqlunionquery.class.inc.php');
  34. require_once(APPROOT.'/core/log.class.inc.php');
  35. require_once(APPROOT.'/core/kpi.class.inc.php');
  36. require_once(APPROOT.'/core/dbobject.class.php');
  37. require_once(APPROOT.'/core/dbsearch.class.php');
  38. require_once(APPROOT.'/core/dbobjectset.class.php');
  39. require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
  40. require_once(APPROOT.'/core/userrights.class.inc.php');
  41. require_once(APPROOT.'/webservices/webservices.class.inc.php');
  42. // Just to differentiate programmatically triggered exceptions and other kind of errors (usefull?)
  43. class UnitTestException extends Exception
  44. {}
  45. /**
  46. * Improved display of the backtrace
  47. *
  48. * @package iTopORM
  49. */
  50. class ExceptionFromError extends Exception
  51. {
  52. public function getTraceAsHtml()
  53. {
  54. $aBackTrace = $this->getTrace();
  55. return MyHelpers::get_callstack_html(0, $this->getTrace());
  56. // return "<pre>\n".$this->getTraceAsString()."</pre>\n";
  57. }
  58. }
  59. /**
  60. * Test handler API and basic helpers
  61. *
  62. * @package iTopORM
  63. */
  64. abstract class TestHandler
  65. {
  66. protected $m_aSuccesses;
  67. protected $m_aWarnings;
  68. protected $m_aErrors;
  69. protected $m_sOutput;
  70. public function __construct()
  71. {
  72. $this->m_aSuccesses = array();
  73. $this->m_aWarnings = array();
  74. $this->m_aErrors = array();
  75. }
  76. static public function GetName() {return "fooname";}
  77. static public function GetDescription(){return "foodesc";}
  78. protected function DoPrepare() {return true;}
  79. abstract protected function DoExecute();
  80. protected function DoCleanup() {return true;}
  81. protected static function DumpVariable($var)
  82. {
  83. echo "<pre class=\"vardump\">\n";
  84. print_r($var);
  85. echo "</pre>\n";
  86. }
  87. protected function ReportSuccess($sMessage, $sSubtestId = '')
  88. {
  89. $this->m_aSuccesses[] = $sMessage;
  90. }
  91. protected function ReportWarning($sMessage, $sSubtestId = '')
  92. {
  93. $this->m_aWarnings[] = $sMessage;
  94. }
  95. protected function ReportError($sMessage, $sSubtestId = '')
  96. {
  97. $this->m_aErrors[] = $sMessage;
  98. }
  99. public function GetResults()
  100. {
  101. return $this->m_aSuccesses;
  102. }
  103. public function GetWarnings()
  104. {
  105. return $this->m_aWarnings;
  106. }
  107. public function GetErrors()
  108. {
  109. return $this->m_aErrors;
  110. }
  111. public function GetOutput()
  112. {
  113. return $this->m_sOutput;
  114. }
  115. public function error_handler($errno, $errstr, $errfile, $errline)
  116. {
  117. // Note: return false to call the default handler (stop the program if an error)
  118. if ($errstr == 'assert()') $errno = E_USER_ERROR;
  119. switch ($errno)
  120. {
  121. case E_USER_ERROR:
  122. case E_WARNING: //(assertion failed)
  123. $this->ReportError("$errfile@$errline - $errstr");
  124. break;
  125. case E_USER_WARNING:
  126. $this->ReportWarning("$errfile@$errline - $errstr");
  127. break;
  128. case E_USER_NOTICE:
  129. $this->ReportWarning("$errfile@$errline - $errstr");
  130. break;
  131. default:
  132. $this->ReportWarning("$errfile@$errline - Unknown error type: [$errno] $errstr");
  133. echo "Unknown error type: [$errno] $errstr in $errfile at $errline<br />\n";
  134. break;
  135. }
  136. return true; // do not call the default handler
  137. }
  138. public function Execute()
  139. {
  140. ob_start();
  141. set_error_handler(array($this, 'error_handler'));
  142. try
  143. {
  144. $this->DoPrepare();
  145. $this->DoExecute();
  146. }
  147. catch (ExceptionFromError $e)
  148. {
  149. $this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml());
  150. }
  151. catch (CoreException $e)
  152. {
  153. //$this->ReportError($e->getMessage());
  154. //$this->ReportError($e->__tostring());
  155. $this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml());
  156. }
  157. catch (Exception $e)
  158. {
  159. //$this->ReportError($e->getMessage());
  160. //$this->ReportError($e->__tostring());
  161. $this->ReportError('class '.get_class($e).' --- '.$e->getMessage().' - '.$e->getTraceAsString());
  162. }
  163. restore_error_handler();
  164. $this->m_sOutput = ob_get_clean();
  165. return (count($this->GetErrors()) == 0);
  166. }
  167. static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = 'admin', $sOptionnalHeaders = null)
  168. {
  169. $aDataAndAuth = $aData;
  170. // To be changed to use basic authentication
  171. $aDataAndAuth['operation'] = 'login';
  172. $aDataAndAuth['auth_user'] = $sLogin;
  173. $aDataAndAuth['auth_pwd'] = $sPassword;
  174. $sHost = $_SERVER['HTTP_HOST'];
  175. $sRawPath = $_SERVER['SCRIPT_NAME'];
  176. $sPath = dirname($sRawPath);
  177. $sUrl = "http://$sHost/$sPath/$sRelativeUrl";
  178. return self::DoPostRequest($sUrl, $aDataAndAuth, $sOptionnalHeaders);
  179. }
  180. // Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl
  181. // originaly named after do_post_request
  182. // Partially adapted to our coding conventions
  183. static protected function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null)
  184. {
  185. // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request.
  186. $sData = http_build_query($aData);
  187. $aParams = array('http' => array(
  188. 'method' => 'POST',
  189. 'content' => $sData,
  190. 'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n",
  191. ));
  192. if ($sOptionnalHeaders !== null)
  193. {
  194. $aParams['http']['header'] .= $sOptionnalHeaders;
  195. }
  196. $ctx = stream_context_create($aParams);
  197. $fp = @fopen($sUrl, 'rb', false, $ctx);
  198. if (!$fp)
  199. {
  200. global $php_errormsg;
  201. if (isset($php_errormsg))
  202. {
  203. throw new Exception("Problem with $sUrl, $php_errormsg");
  204. }
  205. else
  206. {
  207. throw new Exception("Problem with $sUrl");
  208. }
  209. }
  210. $response = @stream_get_contents($fp);
  211. if ($response === false)
  212. {
  213. throw new Exception("Problem reading data from $sUrl, $php_errormsg");
  214. }
  215. return $response;
  216. }
  217. }
  218. /**
  219. * Test to execute a piece of code (checks if an error occurs)
  220. *
  221. * @package iTopORM
  222. */
  223. abstract class TestFunction extends TestHandler
  224. {
  225. // simply overload DoExecute (temporary)
  226. }
  227. /**
  228. * Test to execute a piece of code (checks if an error occurs)
  229. *
  230. * @package iTopORM
  231. */
  232. abstract class TestWebServices extends TestHandler
  233. {
  234. }
  235. /**
  236. * Test to execute a piece of code (checks if an error occurs)
  237. *
  238. * @package iTopORM
  239. */
  240. abstract class TestSoapWebService extends TestHandler
  241. {
  242. // simply overload DoExecute (temporary)
  243. function __construct()
  244. {
  245. parent::__construct();
  246. }
  247. }
  248. /**
  249. * Test to check that a function outputs some values depending on its input
  250. *
  251. * @package iTopORM
  252. */
  253. abstract class TestFunctionInOut extends TestFunction
  254. {
  255. // abstract static public function GetCallSpec(); // parameters to call_user_func
  256. // abstract static public function GetInOut(); // array of input => output
  257. protected function DoExecute()
  258. {
  259. $aTests = $this->GetInOut();
  260. if (is_array($aTests))
  261. {
  262. foreach ($aTests as $iTestId => $aTest)
  263. {
  264. $ret = call_user_func_array($this->GetCallSpec(), $aTest['args']);
  265. if ($ret != $aTest['output'])
  266. {
  267. // Note: to be improved to cope with non string parameters
  268. $this->ReportError("Found '$ret' while expecting '".$aTest['output']."'", $iTestId);
  269. }
  270. else
  271. {
  272. $this->ReportSuccess("Found the expected output '$ret'", $iTestId);
  273. }
  274. }
  275. }
  276. else
  277. {
  278. $ret = call_user_func($this->GetCallSpec());
  279. $this->ReportSuccess('Finished successfully');
  280. }
  281. }
  282. }
  283. /**
  284. * Test to check an URL (Searches for Error/Warning/Etc keywords)
  285. *
  286. * @package iTopORM
  287. */
  288. abstract class TestUrl extends TestHandler
  289. {
  290. // abstract static public function GetUrl();
  291. // abstract static public function GetErrorKeywords();
  292. // abstract static public function GetWarningKeywords();
  293. protected function DoExecute()
  294. {
  295. return true;
  296. }
  297. }
  298. /**
  299. * Test to check a user management module
  300. *
  301. * @package iTopORM
  302. */
  303. abstract class TestUserRights extends TestHandler
  304. {
  305. protected function DoExecute()
  306. {
  307. return true;
  308. }
  309. }
  310. /**
  311. * Test to execute a scenario on a given DB
  312. *
  313. * @package iTopORM
  314. */
  315. abstract class TestScenarioOnDB extends TestHandler
  316. {
  317. // abstract static public function GetDBHost();
  318. // abstract static public function GetDBUser();
  319. // abstract static public function GetDBPwd();
  320. // abstract static public function GetDBName();
  321. protected function DoPrepare()
  322. {
  323. $sDBHost = $this->GetDBHost();
  324. $sDBUser = $this->GetDBUser();
  325. $sDBPwd = $this->GetDBPwd();
  326. $sDBName = $this->GetDBName();
  327. CMDBSource::Init($sDBHost, $sDBUser, $sDBPwd);
  328. CMDBSource::SetCharacterSet();
  329. if (CMDBSource::IsDB($sDBName))
  330. {
  331. CMDBSource::DropDB($sDBName);
  332. }
  333. CMDBSource::CreateDB($sDBName);
  334. }
  335. protected function DoCleanup()
  336. {
  337. // CMDBSource::DropDB($this->GetDBName());
  338. }
  339. }
  340. /**
  341. * Test to use a business model on a given DB
  342. *
  343. * @package iTopORM
  344. */
  345. abstract class TestBizModel extends TestHandler
  346. {
  347. // abstract static public function GetDBSubName();
  348. // abstract static public function GetBusinessModelFile();
  349. // abstract static public function GetConfigFile();
  350. static public function GetConfigFile() {return 'conf/production/config-itop.php';}
  351. protected function DoPrepare()
  352. {
  353. $sConfigFile = APPROOT.$this->GetConfigFile();
  354. MetaModel::Startup($sConfigFile);
  355. // #@# Temporary disabled by Romain
  356. // MetaModel::CheckDefinitions();
  357. // something here to create records... but that's another story
  358. }
  359. protected $m_oChange;
  360. protected function GetCurrentChange()
  361. {
  362. if (!isset($this->m_oChange))
  363. {
  364. new CMDBChange();
  365. $oMyChange = MetaModel::NewObject("CMDBChange");
  366. $oMyChange->Set("date", time());
  367. $oMyChange->Set("userinfo", "Someone doing some tests");
  368. $iChangeId = $oMyChange->DBInsertNoReload();
  369. $this->m_oChange = $oMyChange;
  370. }
  371. return $this->m_oChange;
  372. }
  373. protected function ObjectToDB($oNew, $bReload = false)
  374. {
  375. // list($bRes, $aIssues) = $oNew->CheckToWrite();
  376. // if (!$bRes)
  377. // {
  378. // throw new CoreException('Could not create object, unexpected values', array('issues' => $aIssues));
  379. // }
  380. if ($oNew instanceof CMDBObject)
  381. {
  382. if (!isset($this->m_oChange))
  383. {
  384. new CMDBChange();
  385. $oMyChange = MetaModel::NewObject("CMDBChange");
  386. $oMyChange->Set("date", time());
  387. $oMyChange->Set("userinfo", "Someone doing some tests");
  388. $iChangeId = $oMyChange->DBInsertNoReload();
  389. $this->m_oChange = $oMyChange;
  390. }
  391. $oChange = $this->GetCurrentChange();
  392. if ($bReload)
  393. {
  394. $iId = $oNew->DBInsertTracked($oChange);
  395. }
  396. else
  397. {
  398. $iId = $oNew->DBInsertTrackedNoReload($oChange);
  399. }
  400. }
  401. else
  402. {
  403. if ($bReload)
  404. {
  405. $iId = $oNew->DBInsert();
  406. }
  407. else
  408. {
  409. $iId = $oNew->DBInsertNoReload();
  410. }
  411. }
  412. return $iId;
  413. }
  414. protected function UpdateObjectInDB($oObject)
  415. {
  416. if ($oObject instanceof CMDBObject)
  417. {
  418. $oChange = $this->GetCurrentChange();
  419. $oObject->DBUpdateTracked($oChange);
  420. }
  421. else
  422. {
  423. $oObject->DBUpdate();
  424. }
  425. }
  426. protected function ResetDB()
  427. {
  428. if (MetaModel::DBExists(false))
  429. {
  430. MetaModel::DBDrop();
  431. }
  432. MetaModel::DBCreate();
  433. }
  434. static protected function show_list($oObjectSet)
  435. {
  436. $oObjectSet->Rewind();
  437. $aData = array();
  438. while ($oItem = $oObjectSet->Fetch())
  439. {
  440. $aValues = array();
  441. foreach(MetaModel::GetAttributesList(get_class($oItem)) as $sAttCode)
  442. {
  443. $aValues[$sAttCode] = $oItem->GetAsHTML($sAttCode);
  444. }
  445. //echo $oItem->GetKey()." => ".implode(", ", $aValues)."</br>\n";
  446. $aData[] = $aValues;
  447. }
  448. echo MyHelpers::make_table_from_assoc_array($aData);
  449. }
  450. static protected function search_and_show_list(DBSearch $oMyFilter)
  451. {
  452. $oObjSet = new CMDBObjectSet($oMyFilter);
  453. echo $oMyFilter->ToOQL()."' - Found ".$oObjSet->Count()." items.</br>\n";
  454. self::show_list($oObjSet);
  455. }
  456. static protected function search_and_show_list_from_oql($sOQL)
  457. {
  458. echo $sOQL."...<br/>\n";
  459. $oNewFilter = DBObjectSearch::FromOQL($sOQL);
  460. self::search_and_show_list($oNewFilter);
  461. }
  462. }
  463. /**
  464. * Test to execute a scenario common to any business model (tries to build all the possible queries, etc.)
  465. *
  466. * @package iTopORM
  467. */
  468. abstract class TestBizModelGeneric extends TestBizModel
  469. {
  470. static public function GetName()
  471. {
  472. return 'Full test on a given business model';
  473. }
  474. static public function GetDescription()
  475. {
  476. return 'Systematic tests: gets each and every existing class and tries every attribute, search filters, etc.';
  477. }
  478. protected function DoPrepare()
  479. {
  480. parent::DoPrepare();
  481. if (!MetaModel::DBExists(false))
  482. {
  483. MetaModel::DBCreate();
  484. }
  485. // something here to create records... but that's another story
  486. }
  487. protected function DoExecute()
  488. {
  489. foreach(MetaModel::GetClasses() as $sClassName)
  490. {
  491. if (MetaModel::HasTable($sClassName)) continue;
  492. $oNobody = MetaModel::GetObject($sClassName, 123);
  493. $oBaby = new $sClassName;
  494. $oFilter = new DBObjectSearch($sClassName);
  495. // Challenge reversibility of OQL / filter object
  496. //
  497. $sExpr1 = $oFilter->ToOQL();
  498. $oNewFilter = DBObjectSearch::FromOQL($sExpr1);
  499. $sExpr2 = $oNewFilter->ToOQL();
  500. if ($sExpr1 != $sExpr2)
  501. {
  502. $this->ReportError("Found two different OQL expression out of the (same?) filter: <em>$sExpr1</em> != <em>$sExpr2</em>");
  503. }
  504. // Use the filter (perform the query)
  505. //
  506. $oSet = new CMDBObjectSet($oFilter);
  507. $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName");
  508. }
  509. return true;
  510. }
  511. }
  512. ?>