test.class.inc.php 13 KB

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