You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1148 lines
38 KiB

18 years ago
18 years ago
18 years ago
  1. <?php
  2. /* This is a script which generates simple PHPT test cases from the name of the function.
  3. * It works using the {{{ proto for the function PHP source code. The test cases that it generates
  4. * are simple, however you can also give it the name of a file with PHP code in and it will turn this
  5. * into the right format for a PHPT test.
  6. * This script will not generate expected output.
  7. * Further quidance on how to use it can be found on qa.php.net, or by using the -h command line option.
  8. */
  9. //read the command line input and parse it, do some basic checks to make sure that it's correct
  10. $opt = initialise_opt();
  11. $opt = parse_args($argv, $opt);
  12. check_source($opt['source_loc']);
  13. check_fname($opt['name']);
  14. check_testcase($opt['error_gen'], $opt['basic_gen'],$opt['variation_gen']);
  15. if ($opt['include_block'] != NULL) {
  16. check_file($opt['include_block']);
  17. }
  18. //Get a list of all the c funtions in the source tree
  19. $all_c = array();
  20. $c_file_count = 0;
  21. dirlist($opt['source_loc'], $c_file_count, $all_c);
  22. //Search the list of c functions for the function prototype, quit if can't find it or if the function is an alias
  23. $test_info = get_loc_proto($all_c, $opt['name'], $opt['source_loc']);
  24. if (!$test_info['found']) {
  25. echo "\nExiting: Unable to find implementation of {$opt['name']} in {$opt['source_loc']}\n";
  26. if ($test_info['falias']) {
  27. //But it may be aliased to something else
  28. echo "\n{$test_info['name']}() is an alias of {$test_info['alias']}() --- Write test cases for this instead \n";
  29. }
  30. exit();
  31. }
  32. if ($test_info['falias']) {
  33. //If this function is falias'd to another function tell the test case writer about them
  34. echo "\nNote: {$test_info['name']}() is an alias of {$test_info['alias']}() \n";
  35. }
  36. if ($test_info['error'] != NULL) {
  37. echo $test_info['error']."\n";
  38. exit();
  39. }
  40. $test_info['proto'] = $test_info['return_type']." ". $test_info['name']."(".$test_info['params'].")";
  41. //Set the test sections array - may want more in this later?
  42. $sections = array('--TEST--',
  43. '--FILE--',
  44. '--EXPECTF--'
  45. );
  46. //Parse the parameter list to get the information we need to build code
  47. $names = array();
  48. $types = array();
  49. $num_opt_args = 0;
  50. $num_args = 0;
  51. $parse_success = parse_params($test_info['params'], $names, $types, $num_args, $num_opt_args);
  52. if(!$parse_success) {
  53. echo "Unable to parse function parameter list: {$test_info['params']}. Will only generate template test\n";
  54. }
  55. if((!$parse_success) || ($opt['include_block'] != NULL)) {
  56. //EITHER
  57. // parse_params returns false (failed) so just generate template code
  58. // OR
  59. // If the user has given a file to import PHP code from - don't attempt to generate code
  60. //
  61. echo "\nGenerating test case for ".$test_info['name']."()\n\n";
  62. if($opt['basic_gen']) {
  63. $test_case = gen_template($test_info, $sections, 'basic', $opt['include_block']);
  64. }
  65. elseif ($opt['error_gen']) {
  66. $test_case = gen_template($test_info, $sections, 'error', $opt['include_block']);
  67. }
  68. elseif ($opt['variation_gen']) {
  69. $test_case = gen_template($test_info, $sections, 'variation', $opt['include_block']);
  70. }
  71. exit();
  72. }
  73. // parse params succeded - so set up the function arguments to be used in test generation
  74. $test_info['arg_c'] = $num_args;
  75. $test_info['optional_args'] = $num_opt_args;
  76. if ($num_args > 0) {
  77. $test_info['arg_det'] = array_combine($names, $types);
  78. }
  79. /* DEBUG HERE
  80. var_dump($test_info);
  81. exit();
  82. */
  83. // Ready to generate test cases as required
  84. echo "\nGenerating test case for ".$test_info['name']."()\n\n";
  85. $test_case = array();
  86. if($opt['basic_gen']) {
  87. $test_case = gen_basic_test($test_info, $sections, $test_case);
  88. }
  89. elseif ($opt['error_gen']) {
  90. $test_case = gen_error_test($test_info, $sections, $test_case);
  91. }
  92. elseif ($opt['variation_gen']) {
  93. //generates a number of tests
  94. gen_variation_test($test_info, $sections);
  95. }
  96. /*
  97. * END OF MAIN CODE SECTION
  98. */
  99. /*
  100. * Function to read the contents of a PHP into an array
  101. * Arguments:
  102. * File name => file name with PHP code in it
  103. * $test_case => test case array to be appended to
  104. * Returns:
  105. * $test_case
  106. */
  107. function read_include_file($file_name, $test_case) {
  108. $fp = fopen($file_name, "r");
  109. $code_block = file($file_name);
  110. fclose($fp);
  111. //strip PHP tags and blank lines from start of file
  112. foreach ($code_block as $line) {
  113. if (preg_match("/<\?php/", $line)){
  114. array_shift($code_block);
  115. break;
  116. }else if(preg_match("/^\s*$/",$line)) {
  117. array_shift($code_block);
  118. }else {
  119. break;
  120. }
  121. }
  122. //Strip PHP tags and blank lines from the end of the code
  123. $last = count($code_block) -1;
  124. for($j = 0; $j <= $last; $j++) {
  125. $i = $last - $j;
  126. $line = $code_block[$i];
  127. if(preg_match("/\?>/", $line)) {
  128. array_pop($code_block);
  129. break;
  130. }else if(preg_match("/^\s*$/",$line)) {
  131. array_pop($code_block);
  132. }else {
  133. break;
  134. }
  135. }
  136. // Append the lines of code to the test case array and return
  137. foreach($code_block as $line) {
  138. array_push($test_case, $line);
  139. }
  140. return $test_case;
  141. }
  142. /*
  143. * Generates basic functionality testcase and writes it out to a file.
  144. * Arguments:
  145. * $fn_det => array containing details of the function,
  146. * $sections => The test case sections (eg --TEST--) as an array
  147. * The test case code as arrays keyed by section.
  148. * Returns:
  149. * The test case code as an array of arrays, indexed by section
  150. */
  151. function gen_basic_test($fn_det, $sections, $test_case) {
  152. $name = $fn_det['name'];
  153. $proto = $fn_det['proto'];
  154. $desc = $fn_det['desc'];
  155. $source_file = $fn_det['source_file'];
  156. $arg_det = $fn_det['arg_det'];
  157. $arg_c = $fn_det['arg_c'];
  158. $optional_args = $fn_det['optional_args'];
  159. $alias = $fn_det['alias'];
  160. // get the test header
  161. $test_case = gen_test_header($name, $proto, $desc, $source_file, "basic functionality", NULL, $alias, $test_case);
  162. $test_case['--FILE--'] = gen_basic_test_code($name, $arg_det, $arg_c, $optional_args, $test_case['--FILE--']);
  163. //End the script
  164. $test_case = gen_test_trailer($test_case,'--EXPECTF--');
  165. write_file($test_case, $name, 'basic', $sections);
  166. return($test_case);
  167. }
  168. /*
  169. * Function to scan recursively down a directory structure looking for all c files.
  170. * Input:
  171. * $dir - string path of top level directory
  172. * &$c_file_count - reference to a count of the number of files - init to 0, is updated with count after code is run
  173. * &$all_c - reference to list of all c files. Initialise to array, will contain file list after code is run
  174. * Output:
  175. * $all_c (see above)
  176. * $c_file_count (see above)
  177. */
  178. function dirlist($dir, &$c_file_count, &$all_c)
  179. {
  180. $thisdir = dir($dir.'/'); //include the trailing slash
  181. while(($file = $thisdir->read()) !== false) {
  182. if ($file != '.' && $file != '..') {
  183. $path = $thisdir->path.$file;
  184. if(is_dir($path) == true) {
  185. dirlist($path , $c_file_count , $all_c);
  186. } else {
  187. if (preg_match("/\w+\.c$/", $file)) {
  188. $all_c[$c_file_count] = $path;
  189. $c_file_count++;
  190. }
  191. }
  192. }
  193. }
  194. return;
  195. }
  196. /*
  197. * Function takes parameter list extracted from the proto comment and retuns a list
  198. * of names and types
  199. * Arguments:
  200. * $param_list (string) = > list of arguments and types
  201. * $names => reference to an array of variable names
  202. * $types => reference to an array of variable
  203. * $nm_args => reference to number of function argumants
  204. * $num_opt_args => reference to number of optional arguments
  205. * Returns:
  206. * True if have been able to parse string $param_list, false if not.
  207. */
  208. function parse_params($param_list, &$names, &$types, &$num_args, &$num_opt_args) {
  209. $opt_args = false;
  210. $num_mand_args =0;
  211. $num_opt_args = 0;
  212. $num_args = 0;
  213. $opt_params = NULL;
  214. //remove all commas
  215. $param_list = preg_replace("/,/", "", $param_list);
  216. //deal with void
  217. if(preg_match("/\s*void\s*/", $param_list)) {
  218. return true;
  219. }
  220. // extract mandatory parameters and optional parameters
  221. if (preg_match("/^(.*?)\[\s*(.*)\]\s*(.*?)$/", $param_list, $matches)) {
  222. $param_list = $matches[1].$matches[3];
  223. $opt_params = $matches[2];
  224. // Extract nested optional parameters
  225. $temp_opt_params = $opt_params;
  226. while (preg_match("/(.+?)\s*\[\s*(.*)\]/",$temp_opt_params, $matches)) {
  227. $opt_params = $matches[1]." ".$matches[2];
  228. $temp_opt_params = $opt_params;
  229. }
  230. }
  231. // seperate parameter list into array of types and names
  232. if ($param_list != "") {
  233. $param_list = chop($param_list);
  234. $param_array = explode(" ", $param_list);
  235. $num_mand_args = count($param_array)/2;
  236. //check that this is an even number and quit if not, means we have failed to parse correctly
  237. if((round($num_mand_args) * 2) != count($param_array)) {return false;}
  238. $j = 0;
  239. for($i=0; $i<count($param_array); $i=$i+2) {
  240. $j = $i + 1;
  241. $types[$i] = $param_array[$i];
  242. // If the variable is a reference remove the & from the variable name
  243. $names[$i] = preg_replace("/&/", "", $param_array[$j]);
  244. }
  245. }
  246. //initialise optional arguments
  247. if ($opt_params != NULL) {
  248. $opt_array = explode(" ", $opt_params);
  249. $num_opt_args = count($opt_array)/2;
  250. //check that this is an even number and quit if not, means we have failed to parse correctly
  251. if((round($num_opt_args) * 2) != count($opt_array)) {return false;}
  252. $j = 0;
  253. for($i=0; $i<count($opt_array); $i=$i+2) {
  254. $j = $i + 1;
  255. array_push($types, $opt_array[$i]);
  256. // If the variable is a reference remove the & from the variable name
  257. array_push($names, preg_replace("/&/", "", $opt_array[$j]));
  258. }
  259. }
  260. $num_args = $num_mand_args + $num_opt_args;
  261. return true;
  262. }
  263. /*
  264. * Generates code for an array which contains invalid data types.
  265. * For example, if the variable being tested is of type "float" this code will
  266. * generate an array of data whose type does not include float
  267. *
  268. * Arguments:
  269. * $array_name => name of the array that should be returned
  270. * $var_type => data type of the argument
  271. * Array of code - will be appended to
  272. * Returns:
  273. * $code_block with appended lines of code to initialse the array of data
  274. */
  275. function gen_array_with_diff_values($var_type, $array_name, $code_block) {
  276. //generate the array with all different values, skip those of the same type as $var_type
  277. // integer values
  278. $variation_array['int'] = array(
  279. "'int 0' => 0",
  280. "'int 1' => 1",
  281. "'int 12345' => 12345",
  282. "'int -12345' => -2345"
  283. );
  284. // float values
  285. $variation_array['float'] = array(
  286. "'float 10.5' => 10.5",
  287. "'float -10.5' => -10.5",
  288. "'float 12.3456789000e10' => 12.3456789000e10",
  289. "'float -12.3456789000e10' => -12.3456789000e10",
  290. "'float .5' => .5"
  291. );
  292. // array values
  293. $variation_array['array'] = array(
  294. "'empty array' => array()",
  295. "'int indexed array' => \$index_array",
  296. "'associative array' => \$assoc_array",
  297. "'nested arrays' => array('foo', \$index_array, \$assoc_array)",
  298. );
  299. // null vlaues
  300. $variation_array['null'] = array(
  301. "'uppercase NULL' => NULL",
  302. "'lowercase null' => null"
  303. );
  304. // boolean values
  305. $variation_array['boolean'] = array(
  306. "'lowercase true' => true",
  307. "'lowercase false' =>false",
  308. "'uppercase TRUE' =>TRUE",
  309. "'uppercase FALSE' =>FALSE"
  310. );
  311. // empty data
  312. $variation_array['empty'] = array(
  313. "'empty string DQ' => \"\"",
  314. "'empty string SQ' => ''"
  315. );
  316. // string values
  317. $variation_array['string'] = array(
  318. "'string DQ' => \"string\"",
  319. "'string SQ' => 'string'",
  320. "'mixed case string' => \"sTrInG\"",
  321. "'heredoc' => \$heredoc"
  322. );
  323. // object data
  324. $variation_array['object'] = array(
  325. "'instance of classWithToString' => new classWithToString()",
  326. "'instance of classWithoutToString' => new classWithoutToString()"
  327. );
  328. // undefined variable
  329. $variation_array['undefined'] = array(
  330. "'undefined var' => @\$undefined_var"
  331. );
  332. // unset variable
  333. $variation_array['unset'] = array(
  334. "'unset var' => @\$unset_var"
  335. );
  336. //Write out the code block for the variation array
  337. $blank_line = "";
  338. array_push($code_block, "\$$array_name = array(");
  339. foreach ($variation_array as $type => $data) {
  340. if($type != $var_type) {
  341. array_push($code_block, $blank_line);
  342. $comment = " // $type data";
  343. array_push($code_block,$comment);
  344. foreach ($variation_array[$type] as $entry) {
  345. $line = " ".$entry.",";
  346. array_push($code_block, $line);
  347. }
  348. }
  349. }
  350. array_push($code_block, ");");
  351. return $code_block;
  352. }
  353. /*
  354. * Generate variation testcases and writes them to file(s)
  355. * 1) generate variation for each argument where different invalid argument values are passed
  356. * 2) generate a vartiation template
  357. * Arguments:
  358. * $fn_det => array containing details of the function,
  359. * $sections => list of test sections, eg '--TEST--', etc
  360. * Returns:
  361. * Nothing at the moment - should be tru for success/false for fail?
  362. *
  363. */
  364. function gen_variation_test($fn_det, $sections) {
  365. $name = $fn_det['name'];
  366. $proto = $fn_det['proto'];
  367. $desc = $fn_det['desc'];
  368. $source_file = $fn_det['source_file'];
  369. $arg_det = $fn_det['arg_det'];
  370. $arg_c = $fn_det['arg_c'];
  371. $optional_args = $fn_det['optional_args'];
  372. $alias = $fn_det['alias'];
  373. $test_case = array();
  374. $test_case = gen_template($fn_det, $sections, 'variation');
  375. // if the function has zero argument then quit here because we only need the template
  376. if($arg_c == 0) {
  377. return;
  378. }
  379. // generate a sequence of other tests which loop over each function arg trying different values
  380. $name_seq = 1;
  381. $arg_count = 1;
  382. if($arg_c > 0) {
  383. for($i = 0; $i < $arg_c; $i++) {
  384. //generate a different variation test case for each argument
  385. $test_case = array();
  386. $test_case = gen_test_header($name, $proto, $desc,
  387. $source_file, "usage variation","", $alias, $test_case);
  388. // add variation code
  389. $test_case['--FILE--'] = gen_variation_diff_arg_values_test($name, $arg_det, $arg_count, $test_case['--FILE--']);
  390. // end the script
  391. $test_case = gen_test_trailer($test_case, '--EXPECTF--');
  392. $tc_name = 'variation'.$name_seq;
  393. write_file($test_case, $name, $tc_name, $sections);
  394. $arg_count ++; // next time generate the code for next argument of the function;
  395. $name_seq ++; // next seqence number for variation test name
  396. }
  397. }
  398. }
  399. /*
  400. * Generate code for testcase header. The following test sections are added:
  401. * --TEST-- & --FILE--
  402. * Arguments:
  403. * $fn_name => name of the function
  404. * $proto => $fn_name function prototype
  405. * $desc => short description of $fn_name function
  406. * $source_file => location of the file that implements $fn_name function
  407. * $type_msg => Message to indicate what type of testing is being done : "error_conditions", "basic functionality", etc
  408. * $extra_msg => Additional message that will be printed to indicate what specifics are being tested in this file.
  409. * $alias => list any functions that are aliased to this
  410. * $test_sections => an array of arays of testcase code, keyed by section
  411. * Returns:
  412. * $test_sections
  413. */
  414. function gen_test_header($fn_name, $proto, $desc, $source_file, $type_msg, $extra_msg, $alias, $test_sections) {
  415. $msg = "$type_msg";
  416. if($extra_msg != NULL)
  417. $msg = "$msg - $extra_msg";
  418. $test_sections['--TEST--'] = array("Test $fn_name() function : $type_msg $extra_msg"
  419. );
  420. $test_sections['--FILE--'] = array ("<?php",
  421. "/* Prototype : $proto",
  422. " * Description: $desc",
  423. " * Source code: $source_file",
  424. " * Alias to functions: $alias",
  425. " */",
  426. "",
  427. "echo \"*** Testing $fn_name() : $type_msg ***\\n\";",
  428. ""
  429. );
  430. return $test_sections;
  431. }
  432. /*
  433. * Generate error testcase and writes it to a file
  434. * 1. Generates more than expected no. of argument case
  435. * 2. Generates less than expected no. of argument case
  436. * Arguments:
  437. * $fn_det => array containing details of the function
  438. * $sections => The test case sections (eg --TEST--) as amn array
  439. * $test_case => The test case code as arrays keyed by section.
  440. * Returns:
  441. * The test case code as an array of arrays, indexed by section
  442. */
  443. function gen_error_test($fn_det, $sections, $test_case) {
  444. $name = $fn_det['name'];
  445. $proto = $fn_det['proto'];
  446. $desc = $fn_det['desc'];
  447. $source_file = $fn_det['source_file'];
  448. $arg_det = $fn_det['arg_det'];
  449. $arg_c = $fn_det['arg_c'];
  450. $optional_args = $fn_det['optional_args'];
  451. $alias = $fn_det['alias'];
  452. // get the test header
  453. $test_case = gen_test_header($name, $proto, $desc, $source_file, "error conditions", NULL, $alias, $test_case);
  454. if($fn_det['arg_c'] == 0 ) {
  455. //If the function expects zero arguments generate a one arg test case and quit
  456. $test_case['--FILE--'] = gen_one_arg_code($name, "extra_arg", "int" , $test_case['--FILE--']);
  457. } else if (($fn_det['arg_c'] - $fn_det['optional_args']) == 1) {
  458. //If the function expects one argument generate a zero arg test case and two arg test case
  459. $test_case['--FILE--'] = gen_zero_arg_error_case($name, $test_case['--FILE--']);
  460. $test_case['--FILE--'] = gen_morethanexpected_arg_error_case($name, $arg_det, $test_case['--FILE--']);
  461. } else {
  462. $test_case['--FILE--'] = gen_morethanexpected_arg_error_case($name, $arg_det, $test_case['--FILE--']);
  463. $test_case['--FILE--'] = gen_lessthanexpected_arg_error_case($name, $arg_det, $arg_c, $optional_args, $test_case['--FILE--']);
  464. }
  465. // End the script
  466. $test_case = gen_test_trailer($test_case, '--EXPECTF--');
  467. write_file($test_case, $name, 'error', $sections);
  468. return($test_case);
  469. }
  470. /*
  471. * Add the final lines of the testcase, the default is set to be EXPECTF.
  472. * Arguments:
  473. * $test_case - An aray of arrays keyed by test section
  474. * $section_key - Type of EXPECT section, defaults to EXPECTF
  475. * Returns:
  476. * $test_case - completed test cases code as an array of arrays keyed by section
  477. */
  478. function gen_test_trailer($test_case, $section_key = "--EXPECTF--") {
  479. //Complete the --FILE-- section
  480. array_push($test_case['--FILE--'], "");
  481. array_push($test_case['--FILE--'], "?>\n===DONE===");
  482. //add a new key for the expect section
  483. $test_case[$section_key]=array();
  484. array_push($test_case[$section_key], "Expected output goes here");
  485. array_push($test_case[$section_key], "===DONE===");
  486. return $test_case;
  487. }
  488. /*
  489. * Writes test case code to a file
  490. * Arguments:
  491. * $test_case => Array of arrays of test sections, keyed by section
  492. * $function_name => Name of functio that tests are being generated for
  493. * $type => basic/error/variation
  494. * $test_sections => keys to $test_case
  495. * $seq = > sequence number, may be appended to file name
  496. * Returns:
  497. * Nothing at the moment - should be true/false depending on success
  498. */
  499. function write_file($test_case, $function_name, $type, $test_sections, $seq="") {
  500. $file_name = $function_name."_".$type.$seq.".phpt";
  501. $fp = fopen($file_name, 'w');
  502. foreach($test_sections as $section) {
  503. if(array_key_exists($section, $test_case)) {
  504. fwrite($fp, $section."\n");
  505. if(count($test_case[$section]) >0 ){
  506. foreach($test_case[$section] as $line_of_code) {
  507. fwrite($fp, $line_of_code."\n");
  508. }
  509. }
  510. }
  511. }
  512. fclose($fp);
  513. }
  514. /*
  515. * Generate code for testing different invalid values against an argument of the function
  516. * Arguments:
  517. * $fn_name => name of the function
  518. * $arg_det => details of the each argument, stored in an array in the form of 'nameofargument' => 'typeofargument'
  519. * $which_arg => a numeric value starting from 1 indicating the argument of the
  520. * function ( in $arg_det ) for which the case needs to be generated
  521. * $code_block => array of code which will be appended to
  522. * Returns:
  523. * $code_block
  524. */
  525. function gen_variation_diff_arg_values_test($fn_name, $arg_det, $which_arg, $code_block) {
  526. $names = array_keys($arg_det);
  527. $types = array_values($arg_det);
  528. $blank_line = "";
  529. // decrement the $which_arg so that its matches with the index of $types
  530. $which_arg--;
  531. //generate code to define error handler
  532. $code_block = add_error_handler($code_block);
  533. // generate code to initialise arguments that won't be substituted
  534. array_push($code_block, "// Initialise function arguments not being substituted (if any)");
  535. for($i = 0; $i < count($types); $i ++) {
  536. if ($i != $which_arg) { // do not generate initialization code for the argument which is being tested
  537. $i_stmt = get_variable_init_statement($types[$i], $names[$i]);
  538. array_push($code_block, $i_stmt);
  539. }
  540. }
  541. array_push($code_block, $blank_line);
  542. // generate code to unset a variable
  543. array_push($code_block, "//get an unset variable");
  544. array_push($code_block, "\$unset_var = 10;");
  545. array_push($code_block, "unset (\$unset_var);");
  546. array_push($code_block, $blank_line);
  547. //define some classes
  548. $code_block = define_classes($code_block);
  549. //add heredoc string
  550. array_push($code_block, "// heredoc string");
  551. array_push($code_block, "\$heredoc = <<<EOT");
  552. array_push($code_block, "hello world");
  553. array_push($code_block, "EOT;");
  554. array_push($code_block, $blank_line);
  555. //add arrays
  556. array_push($code_block, "// add arrays");
  557. array_push($code_block, "\$index_array = array (1, 2, 3);");
  558. array_push($code_block, "\$assoc_array = array ('one' => 1, 'two' => 2);");
  559. array_push($code_block, $blank_line);
  560. //generate code for an array of values to iterate over
  561. array_push($code_block, "//array of values to iterate over");
  562. $code_block = gen_array_with_diff_values($types[$which_arg], 'inputs', $code_block);
  563. //generate code for loop to iterate over array values
  564. array_push($code_block, $blank_line);
  565. array_push($code_block, "// loop through each element of the array for $names[$which_arg]");
  566. array_push($code_block, $blank_line);
  567. array_push($code_block, "foreach(\$inputs as \$key =>\$value) {");
  568. array_push($code_block, " echo \"\\n--\$key--\\n\";");
  569. // prepare the function call
  570. // use all arguments including the optional ones to construct a single function call
  571. $var_name = array();
  572. foreach ($names as $nm) {
  573. array_push($var_name, "$".$nm);
  574. }
  575. $var_name[$which_arg] = "\$value";
  576. $all_args = implode(", ", $var_name);
  577. array_push ($code_block, " var_dump( $fn_name($all_args) );");
  578. array_push ($code_block, "};");
  579. return $code_block;
  580. }
  581. /*
  582. * Generate code for testing more than expected no. of argument for error testcase
  583. * Arguments:
  584. * $fn_name => name of the function
  585. * $arg_det => details of the each argument, stored in an array in the form of 'nameofargument' => 'typeofargument'
  586. * $code_block => an array of code that will be appended to
  587. * Returns:
  588. * $code_block
  589. */
  590. function gen_morethanexpected_arg_error_case($fn_name, $arg_det ,$code_block) {
  591. array_push($code_block, "");
  592. array_push($code_block, "//Test $fn_name with one more than the expected number of arguments");
  593. array_push($code_block, "echo \"\\n-- Testing $fn_name() function with more than expected no. of arguments --\\n\";");
  594. $names = array_keys($arg_det);
  595. $types = array_values($arg_det);
  596. //Initialise expected arguments
  597. for($i = 0; $i < count($types); $i ++) {
  598. $i_stmt = get_variable_init_statement($types[$i], $names[$i]);
  599. array_push($code_block, $i_stmt);
  600. }
  601. // get the extra argument init statement
  602. $i_stmt = get_variable_init_statement("int", "extra_arg");
  603. array_push($code_block, $i_stmt);
  604. $var_name = array();
  605. foreach ($names as $nm) {
  606. array_push($var_name, "$".$nm);
  607. }
  608. $all_args = implode(", ", $var_name);
  609. array_push($code_block, "var_dump( $fn_name($all_args, \$extra_arg) );");
  610. return $code_block;
  611. }
  612. /*
  613. * Generate code for testing less than expected no. of mandatory arguments for error testcase
  614. * Arguments:
  615. * $fn_name => name of the function
  616. * $arg_det => details of the each argument, stored in an array in the form of 'nameofargument' => 'typeofargument'
  617. * $arg_c => total count of arguments that $fn_name takes
  618. * $optional_args => total count of optional arguments that $fn_name takes
  619. * $code_block => an array of code that will be appended to
  620. * Returns:
  621. * $code_block
  622. */
  623. function gen_lessthanexpected_arg_error_case($fn_name, $arg_det, $arg_c, $optional_args, $code_block) {
  624. $names = array_keys($arg_det);
  625. $types = array_values($arg_det);
  626. // check for no. of mandatory arguments
  627. // if there are no mandatory arguments - return
  628. // the code_block unchanged
  629. $mandatory_args = $arg_c - $optional_args;
  630. if($mandatory_args < 1) {
  631. return ($code_block);
  632. }
  633. //Discard optional arguments and last mandatory arg
  634. for ($i = 0; $i < $optional_args; $i++) {
  635. $discard_n = array_pop($names);
  636. $discard_v = array_pop($types);
  637. }
  638. $discard_n = array_pop($names);
  639. $discard_v = array_pop($types);
  640. array_push($code_block, "");
  641. array_push($code_block, "// Testing $fn_name with one less than the expected number of arguments");
  642. array_push($code_block, "echo \"\\n-- Testing $fn_name() function with less than expected no. of arguments --\\n\";");
  643. for($i = 0; $i < count($names); $i ++) {
  644. $i_stmt = get_variable_init_statement($types[$i], $names[$i]);
  645. array_push($code_block, $i_stmt);
  646. }
  647. $all_args = "";
  648. if ($mandatory_args > 1) {
  649. $var_name = array();
  650. foreach ($names as $nm) {
  651. array_push($var_name, "$".$nm);
  652. }
  653. $all_args = implode(", ", $var_name);
  654. }
  655. array_push($code_block, "var_dump( $fn_name($all_args) );");
  656. return $code_block;
  657. }
  658. /*
  659. * Generates code for initalizing a given variable with value of same type
  660. * Arguments:
  661. * $var_type => data type of variable
  662. * $var_name => name of the variable
  663. * Returns:
  664. * $code_block
  665. */
  666. function get_variable_init_statement( $var_type, $var_name ) {
  667. $code = "";
  668. if ($var_type == "int") {
  669. $code = "\$$var_name = 10;";
  670. } else if($var_type == "float") {
  671. $code = "\$$var_name = 10.5;";
  672. } else if($var_type == "array") {
  673. $code = "\$$var_name = array(1, 2);";
  674. } else if($var_type == "string") {
  675. $code = "\$$var_name = 'string_val';";
  676. } else if($var_type == "object") {
  677. $code = "\$$var_name = new stdclass();";
  678. } else if($var_type == 'bool' || $var_type == 'boolean') {
  679. $code = "\$$var_name = true;";
  680. } else if($var_type == 'mixed') {
  681. // take a guess at int
  682. $code = "\$$var_name = 1;";
  683. } else {
  684. $code = "\n//WARNING: Unable to initialise $var_name of type $var_type\n";
  685. }
  686. return $code;
  687. }
  688. /*
  689. * Generate code for function with one argument
  690. * Arguments:
  691. * $fn_name => name of the function
  692. * $arg_name => name of the argument
  693. * $arg_type => data type of the argument
  694. * $code_block => an array of code that will be appended to
  695. * Returns:
  696. * $code_block
  697. */
  698. function gen_one_arg_code($fn_name, $arg_name, $arg_type, $code_block) {
  699. //Initialse the argument
  700. $arg_one_init = get_variable_init_statement($arg_type, $arg_name);
  701. //push code onto the array $code_block
  702. array_push ($code_block, "// One argument");
  703. array_push ($code_block, "echo \"\\n-- Testing $fn_name() function with one argument --\\n\";");
  704. array_push ($code_block, "$arg_one_init;");
  705. array_push ($code_block, "var_dump( $fn_name(\$$arg_name) );");
  706. return $code_block;
  707. }
  708. /*
  709. * Generates code for basic functionality test. The generated code
  710. * will test the function with it's mandatory arguments and with all optional arguments.
  711. * Arguments:
  712. * $fn_name => name of the function
  713. * $arg_det => details of the each argument, stored in an array in the form of 'nameofargument' => 'typeofargument'
  714. * $arg_c => total count of arguments that $fn_name takes
  715. * $optional_args. $optional_args => total count of optional arguments that $fn_name takes
  716. * $code_block - an array of code that will be appended to
  717. * Returns:
  718. * $code_block with appends
  719. */
  720. function gen_basic_test_code($fn_name, $arg_det, $arg_c, $optional_args, $code_block) {
  721. if($arg_c == 0) {
  722. //Just generate Zero arg test case and return
  723. $code_block = gen_zero_arg_error_case($fn_name, $code_block);
  724. return $code_block;
  725. }
  726. $names = array_keys($arg_det);
  727. $types = array_values($arg_det);
  728. // prepare code to initialize all reqd. arguments
  729. array_push ($code_block, "");
  730. array_push ($code_block, "// Initialise all required variables");
  731. for($i = 0; $i < $arg_c; $i ++) {
  732. $i_stmt = get_variable_init_statement($types[$i], $names[$i]);
  733. array_push($code_block, $i_stmt);
  734. }
  735. // prepare the function calls
  736. // all arguments including the optional ones
  737. $var_name = array();
  738. foreach ($names as $nm) {
  739. array_push($var_name, "$".$nm);
  740. }
  741. $all_args = implode(", ", $var_name);
  742. array_push ($code_block, "");
  743. array_push ($code_block, "// Calling $fn_name() with all possible arguments");
  744. array_push ($code_block, "var_dump( $fn_name($all_args) );");
  745. //now remove the optional arguments and call the function with mandatory arguments only
  746. if ($optional_args != 0) {
  747. for ($i=0; $i < $optional_args; $i++) {
  748. $discard_n = array_pop($var_name);
  749. }
  750. $args = implode(", ", $var_name);
  751. array_push ($code_block, "");
  752. array_push ($code_block, "// Calling $fn_name() with mandatory arguments");
  753. array_push ($code_block, "var_dump( $fn_name($args) );");
  754. }
  755. return $code_block;
  756. }
  757. /*
  758. * Function to parse command line arguments
  759. * Returns:
  760. * $opt_array => array of options
  761. */
  762. function initialise_opt() {
  763. $opt=array();
  764. $opt['source_loc'] = NULL;
  765. $opt['name'] = NULL;
  766. $opt['error_gen'] = false;
  767. $opt['basic_gen'] = false;
  768. $opt['variation_gen'] = false;
  769. $opt['include_block'] = NULL;
  770. return $opt;
  771. }
  772. function parse_args ($arglist, $opt)
  773. {
  774. for($j = 1; $j<count($arglist); $j++) {
  775. switch ($arglist[$j])
  776. {
  777. case '-s':
  778. $j++;
  779. $opt['source_loc'] = $arglist[$j];
  780. break;
  781. case '-f':
  782. $j++;
  783. $opt['name'] = $arglist[$j];
  784. break;
  785. case '-e':
  786. $opt['error_gen'] = true;
  787. break;
  788. case '-b':
  789. $opt['basic_gen'] = true;
  790. break;
  791. case '-v':
  792. $opt['variation_gen'] = true;
  793. break;
  794. case '-i':
  795. $j++;
  796. $opt['include_block'] = $arglist[$j];
  797. break;
  798. case '-h':
  799. print_opts();
  800. break;
  801. default:
  802. echo "Command line option $arglist[$j] not recognised\n";
  803. print_opts();
  804. }
  805. }
  806. return $opt;
  807. }
  808. /*
  809. * Function to check that source directory given for PHP source actually conatins PHP source
  810. */
  811. function check_source($src)
  812. {
  813. $ext_loc = $src."/ext";
  814. if(!is_dir($ext_loc)) {
  815. echo "A PHP source location is required, $src does not appear to be a valid source location\n";
  816. print_opts();
  817. }
  818. }
  819. /*
  820. * Function to check that a file name exists
  821. */
  822. function check_file($f)
  823. {
  824. if(!is_file($f)) {
  825. echo "$f is not a valid file name \n";
  826. print_opts();
  827. }
  828. }
  829. /*
  830. * Function to check thet either a file name, a list of files or a function area has been supplied
  831. */
  832. function check_fname ($f)
  833. {
  834. if($f == NULL ) {
  835. echo "Require a function name \n";
  836. print_opts();
  837. }
  838. }
  839. /*
  840. * Function to check that the user has requested a type of test case
  841. */
  842. function check_testcase($e, $v, $b) {
  843. if(!$v && !$e && !$b) {
  844. echo "Need to specify which type of test to generate\n";
  845. print_opts();
  846. }
  847. }
  848. /*
  849. * Function to print out help text if any of the input data is incorrect
  850. */
  851. function print_opts() {
  852. echo "\nUsage:\n";
  853. echo " php generate_phpt.php -s <location_of_source_code> -f <function_name> -b|e|v [-i file]\n\n";
  854. echo "-s location_of_source_code ....... Top level directory of PHP source\n";
  855. echo "-f function_name ................. Name of PHP function, eg cos\n";
  856. echo "-b ............................... Generate basic tests\n";
  857. echo "-e ............................... Generate error tests\n";
  858. echo "-v ............................... Generate variation tests\n";
  859. echo "-i file_containing_include_block.. Block of PHP code to be included in the test case\n";
  860. echo "-h ............................... Print this message\n";
  861. exit;
  862. }
  863. /*
  864. * Generates a general testcase template and create the testcase file,
  865. * No code is added other than header and trailer
  866. * Arguments:
  867. * $fn_det => Array with function details
  868. * $sections => List of test sections eg '--TEST--', '--FILE--'..
  869. * $type => basic/error/variation
  870. * $php_file => name of file to import PHP code from
  871. * Returns:
  872. * $test_case => an array containing the complete test case
  873. */
  874. function gen_template($fn_det, $sections, $type, $php_file=NULL) {
  875. $name = $fn_det['name'];
  876. $proto = $fn_det['proto'];
  877. $desc = $fn_det['desc'];
  878. $source_file = $fn_det['source_file'];
  879. $alias = $fn_det['alias'];
  880. $test_case = array();
  881. // get the test header and write into the file
  882. $test_case = gen_test_header($name, $proto, $desc, $source_file, $type, "",$alias, $test_case);
  883. // write the message to indicate the start of addition of code in the template file
  884. if ($php_file == NULl) {
  885. $msg = "\n // add test code here \n";
  886. array_push($test_case['--FILE--'], $msg);
  887. }else{
  888. $test_case['--FILE--'] = read_include_file($php_file, $test_case['--FILE--']);
  889. }
  890. // end the script
  891. $test_case = gen_test_trailer($test_case);
  892. write_file($test_case, $name, $type, $sections);
  893. return ($test_case);
  894. }
  895. /*
  896. * Generate code for testing zero argument case for error testcase
  897. * Arguments:
  898. * $fn_name => name of the function
  899. * $code_block => array of code which will be appended to
  900. * Returns:
  901. * $code_block
  902. */
  903. function gen_zero_arg_error_case($fn_name, $code_block) {
  904. //push code onto the array $code_block
  905. array_push ($code_block, "// Zero arguments");
  906. array_push ($code_block, "echo \"\\n-- Testing $fn_name() function with Zero arguments --\\n\";");
  907. array_push ($code_block, "var_dump( $fn_name() );");
  908. return $code_block;
  909. }
  910. function get_loc_proto($all_c, $fname, $source) {
  911. //get location
  912. $test_info['name'] = $fname;
  913. $test_info['source_file'] = NULL;
  914. $test_info['return_type'] = NULL;
  915. $test_info['params'] = NULL;
  916. $test_info['falias'] = false;
  917. $test_info['found'] = false;
  918. $test_info['alias'] = NULL;
  919. $test_info['error'] = NULL;
  920. $escaped_source = preg_replace("/\\\/", "\\\\\\", $source);
  921. $escaped_source = preg_replace("/\//", "\\\/", $escaped_source);
  922. for ($i=0; $i<count($all_c); $i++)
  923. {
  924. $strings=file_get_contents(chop($all_c[$i]));
  925. if (preg_match ("/FUNCTION\($fname\)/",$strings))
  926. {
  927. //strip build specific part of the implementation file name
  928. preg_match("/$escaped_source\/(.*)$/", $all_c[$i], $tmp);
  929. $test_info['source_file'] = $tmp[1];
  930. //get prototype information
  931. if (preg_match("/\/\*\s+{{{\s*proto\s+(\w*)\s*$fname\(\s*(.*)\s*\)(\n|)\s*(.*?)(\*\/|\n)/", $strings, $matches)) {
  932. $test_info['return_type'] = $matches[1];
  933. $test_info['params'] = $matches[2];
  934. $test_info['desc'] = $matches[4];
  935. }
  936. else {
  937. $test_info['error'] = "\nFailed to parse prototype for $fname in $all_c[$i]".
  938. "\nEither the {{{proto comment is too hard to parse, or ".
  939. "\nthe real implementation is in an alias.\n";
  940. }
  941. $test_info['found'] = true;
  942. if ((preg_match ("/FALIAS\($fname,\s*(\w+),.*\)/",$strings, $alias_name))
  943. || (preg_match ("/FALIAS\((\w+),\s*$fname.*\)/",$strings, $alias_name))) {
  944. // There is another alias referred to in the same C source file. Make a note of it.
  945. $test_info['falias'] = true;
  946. if ( $test_info['alias'] != NULL) {
  947. $test_info['alias'] = $test_info['alias']." ".$alias_name[1];
  948. } else {
  949. $test_info['alias'] = $alias_name[1];
  950. }
  951. }
  952. }
  953. elseif ((preg_match ("/FALIAS\($fname,\s*(\w+),.*\)/",$strings, $alias_name))
  954. || (preg_match ("/FALIAS\((\w+),\s*$fname.*\)/",$strings, $alias_name))) {
  955. // There is an alias to the function in a different file from the main function definition - make a note of it
  956. $test_info['falias'] = true;
  957. if ( $test_info['alias'] != NULL) {
  958. $test_info['alias'] = $test_info['alias']." ".$alias_name[1];
  959. } else {
  960. $test_info['alias'] = $alias_name[1];
  961. }
  962. }
  963. //Some functions are in their own files and not declared using FUNTION/FALIAS.
  964. //If we haven't found either FUNCTION or FALIAS try just looking for the prototype
  965. elseif (preg_match ("/\/\*\s+{{{\s*proto\s+(\w*)\s*$fname\(\s*(.*)\s*\)(\n|)\s*(.*)\*\//", $strings, $matches)) {
  966. $test_info['return_type'] = $matches[1];
  967. $test_info['params'] = $matches[2];
  968. $test_info['desc'] = $matches[4];
  969. $test_info['found'] = true;
  970. preg_match("/$escaped_source\/(.*)$/", $all_c[$i], $tmp);
  971. $test_info['source_file']= $tmp[1];
  972. //break;
  973. }
  974. }
  975. return $test_info;
  976. }
  977. function add_error_handler($cb) {
  978. array_push($cb, "// Define error handler");
  979. array_push($cb, "function test_error_handler(\$err_no, \$err_msg, \$filename, \$linenum, \$vars) {");
  980. array_push($cb, " if (error_reporting() != 0) {");
  981. array_push($cb, " // report non-silenced errors");
  982. array_push($cb, " echo \"Error: \$err_no - \$err_msg, \$filename(\$linenum)\\n\";");
  983. array_push($cb, " }");
  984. array_push($cb, "}");
  985. array_push($cb, "set_error_handler('test_error_handler');");
  986. array_push($cb, "");
  987. return $cb;
  988. }
  989. function define_classes($cb) {
  990. array_push($cb,"// define some classes");
  991. array_push($cb,"class classWithToString");
  992. array_push($cb,"{");
  993. array_push($cb," public function __toString() {");
  994. array_push($cb," return \"Class A object\";");
  995. array_push($cb," }");
  996. array_push($cb,"}");
  997. array_push($cb,"");
  998. array_push($cb,"class classWithoutToString");
  999. array_push($cb,"{");
  1000. array_push($cb,"}");
  1001. array_push($cb,"");
  1002. return $cb;
  1003. }
  1004. ?>