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.

1098 lines
36 KiB

18 years ago
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'] = "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. "0",
  280. "1",
  281. "12345",
  282. "-2345"
  283. );
  284. // float values
  285. $variation_array['float'] = array(
  286. "10.5",
  287. "-10.5",
  288. "10.1234567e10",
  289. "10.7654321E-10",
  290. ".5"
  291. );
  292. // array values
  293. $variation_array['array'] = array(
  294. "array()",
  295. "array(0)",
  296. "array(1)",
  297. "array(1, 2)",
  298. "array('color' => 'red', 'item' => 'pen')"
  299. );
  300. // null vlaues
  301. $variation_array['null'] = array(
  302. "NULL",
  303. "null"
  304. );
  305. // boolean values
  306. $variation_array['boolean'] = array(
  307. "true",
  308. "false",
  309. "TRUE",
  310. "FALSE"
  311. );
  312. // empty string
  313. $variation_array['empty'] = array(
  314. "\"\"",
  315. "''",
  316. );
  317. // string values
  318. $variation_array['string'] = array(
  319. "\"string\"",
  320. "'string'",
  321. );
  322. // objects
  323. $variation_array['object'] = array(
  324. "new stdclass()"
  325. );
  326. // undefined variable
  327. $variation_array['undefined'] = array(
  328. '$undefined_var'
  329. );
  330. // unset variable
  331. $variation_array['unset'] = array(
  332. '$unset_var'
  333. );
  334. //Write out the code block for the variation array
  335. $blank_line = "";
  336. array_push($code_block, "\$$array_name = array(");
  337. foreach ($variation_array as $type => $data) {
  338. if($type != $var_type) {
  339. array_push($code_block, $blank_line);
  340. $comment = " // $type data";
  341. array_push($code_block,$comment);
  342. foreach ($variation_array[$type] as $entry) {
  343. $line = " ".$entry.",";
  344. array_push($code_block, $line);
  345. }
  346. }
  347. }
  348. array_push($code_block, ");");
  349. return $code_block;
  350. }
  351. /*
  352. * Generate variation testcases and writes them to file(s)
  353. * 1) generate variation for each argument where different invalid argument values are passed
  354. * 2) generate a vartiation template
  355. * Arguments:
  356. * $fn_det => array containing details of the function,
  357. * $sections => list of test sections, eg '--TEST--', etc
  358. * Returns:
  359. * Nothing at the moment - should be tru for success/false for fail?
  360. *
  361. */
  362. function gen_variation_test($fn_det, $sections) {
  363. $name = $fn_det['name'];
  364. $proto = $fn_det['proto'];
  365. $desc = $fn_det['desc'];
  366. $source_file = $fn_det['source_file'];
  367. $arg_det = $fn_det['arg_det'];
  368. $arg_c = $fn_det['arg_c'];
  369. $optional_args = $fn_det['optional_args'];
  370. $alias = $fn_det['alias'];
  371. $test_case = array();
  372. $test_case = gen_template($fn_det, $sections, 'variation');
  373. // if the function has zero argument then quit here because we only need the template
  374. if($arg_c == 0) {
  375. return;
  376. }
  377. // generate a sequence of other tests which loop over each function arg trying different values
  378. $name_seq = 1;
  379. $arg_count = 1;
  380. if($arg_c > 0) {
  381. for($i = 0; $i < $arg_c; $i++) {
  382. //generate a different variation test case for each argument
  383. $test_case = array();
  384. $test_case = gen_test_header($name, $proto, $desc,
  385. $source_file, "usage variations", " - <type here specifics of this variation>", $alias, $test_case);
  386. // add variation code
  387. $test_case['--FILE--'] = gen_variation_diff_arg_values_test($name, $arg_det, $arg_count, $test_case['--FILE--']);
  388. // end the script
  389. $test_case = gen_test_trailer($test_case, '--EXPECTF--');
  390. $tc_name = 'variation'.$name_seq;
  391. write_file($test_case, $name, $tc_name, $sections);
  392. $arg_count ++; // next time generate the code for next argument of the function;
  393. $name_seq ++; // next seqence number for variation test name
  394. }
  395. }
  396. }
  397. /*
  398. * Generate code for testcase header. The following test sections are added:
  399. * --TEST-- & --FILE--
  400. * Arguments:
  401. * $fn_name => name of the function
  402. * $proto => $fn_name function prototype
  403. * $desc => short description of $fn_name function
  404. * $source_file => location of the file that implements $fn_name function
  405. * $type_msg => Message to indicate what type of testing is being done : "error_conditions", "basic functionality", etc
  406. * $extra_msg => Additional message that will be printed to indicate what specifics are being tested in this file.
  407. * $alias => list any functions that are aliased to this
  408. * $test_sections => an array of arays of testcase code, keyed by section
  409. * Returns:
  410. * $test_sections
  411. */
  412. function gen_test_header($fn_name, $proto, $desc, $source_file, $type_msg, $extra_msg, $alias, $test_sections) {
  413. $msg = "$type_msg";
  414. if($extra_msg != NULL)
  415. $msg = "$msg - $extra_msg";
  416. $test_sections['--TEST--'] = array("Test $fn_name() function : $type_msg $extra_msg"
  417. );
  418. $test_sections['--FILE--'] = array ("<?php",
  419. "/* Prototype : $proto",
  420. " * Description: $desc",
  421. " * Source code: $source_file",
  422. " * Alias to functions: $alias",
  423. " */",
  424. "",
  425. "/*",
  426. " * add a comment here to say what the test is supposed to do",
  427. " */",
  428. "",
  429. "echo \"*** Testing $fn_name() : $type_msg ***\\n\";",
  430. ""
  431. );
  432. return $test_sections;
  433. }
  434. /*
  435. * Generate error testcase and writes it to a file
  436. * 1. Generates more than expected no. of argument case
  437. * 2. Generates less than expected no. of argument case
  438. * Arguments:
  439. * $fn_det => array containing details of the function
  440. * $sections => The test case sections (eg --TEST--) as amn array
  441. * $test_case => The test case code as arrays keyed by section.
  442. * Returns:
  443. * The test case code as an array of arrays, indexed by section
  444. */
  445. function gen_error_test($fn_det, $sections, $test_case) {
  446. $name = $fn_det['name'];
  447. $proto = $fn_det['proto'];
  448. $desc = $fn_det['desc'];
  449. $source_file = $fn_det['source_file'];
  450. $arg_det = $fn_det['arg_det'];
  451. $arg_c = $fn_det['arg_c'];
  452. $optional_args = $fn_det['optional_args'];
  453. $alias = $fn_det['alias'];
  454. // get the test header
  455. $test_case = gen_test_header($name, $proto, $desc, $source_file, "error conditions", NULL, $alias, $test_case);
  456. if($fn_det['arg_c'] == 0 ) {
  457. //If the function expects zero arguments generate a one arg test case and quit
  458. $test_case['--FILE--'] = gen_one_arg_code($name, "extra_arg", "int" , $test_case['--FILE--']);
  459. } else if (($fn_det['arg_c'] - $fn_det['optional_args']) == 1) {
  460. //If the function expects one argument generate a zero arg test case and two arg test case
  461. $test_case['--FILE--'] = gen_zero_arg_error_case($name, $test_case['--FILE--']);
  462. $test_case['--FILE--'] = gen_morethanexpected_arg_error_case($name, $arg_det, $test_case['--FILE--']);
  463. } else {
  464. $test_case['--FILE--'] = gen_morethanexpected_arg_error_case($name, $arg_det, $test_case['--FILE--']);
  465. $test_case['--FILE--'] = gen_lessthanexpected_arg_error_case($name, $arg_det, $arg_c, $optional_args, $test_case['--FILE--']);
  466. }
  467. // End the script
  468. $test_case = gen_test_trailer($test_case, '--EXPECTF--');
  469. write_file($test_case, $name, 'error', $sections);
  470. return($test_case);
  471. }
  472. /*
  473. * Add the final lines of the testcase, the default is set to be EXPECTF.
  474. * Arguments:
  475. * $test_case - An aray of arrays keyed by test section
  476. * $section_key - Type of EXPECT section, defaults to EXPECTF
  477. * Returns:
  478. * $test_case - completed test cases code as an array of arrays keyed by section
  479. */
  480. function gen_test_trailer($test_case, $section_key = "--EXPECTF--") {
  481. //Complete the --FILE-- section
  482. array_push($test_case['--FILE--'], "");
  483. array_push($test_case['--FILE--'], "echo \"Done\";", "?>");
  484. //add a new key for the expect section
  485. $test_case[$section_key]=array();
  486. array_push($test_case[$section_key], "Expected output goes here");
  487. array_push($test_case[$section_key], "Done");
  488. return $test_case;
  489. }
  490. /*
  491. * Writes test case code to a file
  492. * Arguments:
  493. * $test_case => Array of arrays of test sections, keyed by section
  494. * $function_name => Name of functio that tests are being generated for
  495. * $type => basic/error/variation
  496. * $test_sections => keys to $test_case
  497. * $seq = > sequence number, may be appended to file name
  498. * Returns:
  499. * Nothing at the moment - should be true/false depending on success
  500. */
  501. function write_file($test_case, $function_name, $type, $test_sections, $seq="") {
  502. $file_name = $function_name."_".$type.$seq.".phpt";
  503. $fp = fopen($file_name, 'w');
  504. foreach($test_sections as $section) {
  505. if(array_key_exists($section, $test_case)) {
  506. fwrite($fp, $section."\n");
  507. if(count($test_case[$section]) >0 ){
  508. foreach($test_case[$section] as $line_of_code) {
  509. fwrite($fp, $line_of_code."\n");
  510. }
  511. }
  512. }
  513. }
  514. fclose($fp);
  515. }
  516. /*
  517. * Generate code for testing different invalid values against an argument of the function
  518. * Arguments:
  519. * $fn_name => name of the function
  520. * $arg_det => details of the each argument, stored in an array in the form of 'nameofargument' => 'typeofargument'
  521. * $which_arg => a numeric value starting from 1 indicating the argument of the
  522. * function ( in $arg_det ) for which the case needs to be generated
  523. * $code_block => array of code which will be appended to
  524. * Returns:
  525. * $code_block
  526. */
  527. function gen_variation_diff_arg_values_test($fn_name, $arg_det, $which_arg, $code_block) {
  528. $names = array_keys($arg_det);
  529. $types = array_values($arg_det);
  530. $blank_line = "";
  531. // decrement the $which_arg so that its matches with the index of $types
  532. $which_arg--;
  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. //generate code for an array of values to iterate over
  548. array_push($code_block, "//array of values to iterate over");
  549. $code_block = gen_array_with_diff_values($types[$which_arg], 'values', $code_block);
  550. //generate code for loop to iterate over array values
  551. array_push($code_block, $blank_line);
  552. array_push($code_block, "// loop through each element of the array for $names[$which_arg]");
  553. array_push($code_block, $blank_line);
  554. array_push($code_block, "foreach(\$values as \$value) {");
  555. array_push($code_block, " echo \"\\nArg value \$value \\n\";");
  556. // prepare the function call
  557. // use all arguments including the optional ones to construct a single function call
  558. $var_name = array();
  559. foreach ($names as $nm) {
  560. array_push($var_name, "$".$nm);
  561. }
  562. $var_name[$which_arg] = "\$value";
  563. $all_args = implode(", ", $var_name);
  564. array_push ($code_block, " var_dump( $fn_name($all_args) );");
  565. array_push ($code_block, "};");
  566. return $code_block;
  567. }
  568. /*
  569. * Generate code for testing more than expected no. of argument for error testcase
  570. * Arguments:
  571. * $fn_name => name of the function
  572. * $arg_det => details of the each argument, stored in an array in the form of 'nameofargument' => 'typeofargument'
  573. * $code_block => an array of code that will be appended to
  574. * Returns:
  575. * $code_block
  576. */
  577. function gen_morethanexpected_arg_error_case($fn_name, $arg_det ,$code_block) {
  578. array_push($code_block, "");
  579. array_push($code_block, "//Test $fn_name with one more than the expected number of arguments");
  580. array_push($code_block, "echo \"\\n-- Testing $fn_name() function with more than expected no. of arguments --\\n\";");
  581. $names = array_keys($arg_det);
  582. $types = array_values($arg_det);
  583. //Initialise expected arguments
  584. for($i = 0; $i < count($types); $i ++) {
  585. $i_stmt = get_variable_init_statement($types[$i], $names[$i]);
  586. array_push($code_block, $i_stmt);
  587. }
  588. // get the extra argument init statement
  589. $i_stmt = get_variable_init_statement("int", "extra_arg");
  590. array_push($code_block, $i_stmt);
  591. $var_name = array();
  592. foreach ($names as $nm) {
  593. array_push($var_name, "$".$nm);
  594. }
  595. $all_args = implode(", ", $var_name);
  596. array_push($code_block, "var_dump( $fn_name($all_args, \$extra_arg) );");
  597. return $code_block;
  598. }
  599. /*
  600. * Generate code for testing less than expected no. of mandatory arguments for error testcase
  601. * Arguments:
  602. * $fn_name => name of the function
  603. * $arg_det => details of the each argument, stored in an array in the form of 'nameofargument' => 'typeofargument'
  604. * $arg_c => total count of arguments that $fn_name takes
  605. * $optional_args => total count of optional arguments that $fn_name takes
  606. * $code_block => an array of code that will be appended to
  607. * Returns:
  608. * $code_block
  609. */
  610. function gen_lessthanexpected_arg_error_case($fn_name, $arg_det, $arg_c, $optional_args, $code_block) {
  611. $names = array_keys($arg_det);
  612. $types = array_values($arg_det);
  613. // check for no. of mandatory arguments
  614. // if there are no mandatory arguments - return
  615. // the code_block unchanged
  616. $mandatory_args = $arg_c - $optional_args;
  617. if($mandatory_args < 1) {
  618. return ($code_block);
  619. }
  620. //Discard optional arguments and last mandatory arg
  621. for ($i = 0; $i < $optional_args; $i++) {
  622. $discard_n = array_pop($names);
  623. $discard_v = array_pop($types);
  624. }
  625. $discard_n = array_pop($names);
  626. $discard_v = array_pop($types);
  627. array_push($code_block, "");
  628. array_push($code_block, "// Testing $fn_name with one less than the expected number of arguments");
  629. array_push($code_block, "echo \"\\n-- Testing $fn_name() function with less than expected no. of arguments --\\n\";");
  630. for($i = 0; $i < count($names); $i ++) {
  631. $i_stmt = get_variable_init_statement($types[$i], $names[$i]);
  632. array_push($code_block, $i_stmt);
  633. }
  634. $all_args = "";
  635. if ($mandatory_args > 1) {
  636. $var_name = array();
  637. foreach ($names as $nm) {
  638. array_push($var_name, "$".$nm);
  639. }
  640. $all_args = implode(", ", $var_name);
  641. }
  642. array_push($code_block, "var_dump( $fn_name($all_args) );");
  643. return $code_block;
  644. }
  645. /*
  646. * Generates code for initalizing a given variable with value of same type
  647. * Arguments:
  648. * $var_type => data type of variable
  649. * $var_name => name of the variable
  650. * Returns:
  651. * $code_block
  652. */
  653. function get_variable_init_statement( $var_type, $var_name ) {
  654. $code = "";
  655. if ($var_type == "int") {
  656. $code = "\$$var_name = 10;";
  657. } else if($var_type == "float") {
  658. $code = "\$$var_name = 10.5;";
  659. } else if($var_type == "array") {
  660. $code = "\$$var_name = array(1, 2);";
  661. } else if($var_type == "string") {
  662. $code = "\$$var_name = 'string_val';";
  663. } else if($var_type == "object") {
  664. $code = "\$$var_name = new stdclass();";
  665. } else if($var_type == 'bool' || $var_type == 'boolean') {
  666. $code = "\$$var_name = true;";
  667. } else if($var_type == 'mixed') {
  668. // take a guess at int
  669. $code = "\$$var_name = 1;";
  670. } else {
  671. $code = "\n//WARNING: Unable to initialise $var_name of type $var_type\n";
  672. }
  673. return $code;
  674. }
  675. /*
  676. * Generate code for function with one argument
  677. * Arguments:
  678. * $fn_name => name of the function
  679. * $arg_name => name of the argument
  680. * $arg_type => data type of the argument
  681. * $code_block => an array of code that will be appended to
  682. * Returns:
  683. * $code_block
  684. */
  685. function gen_one_arg_code($fn_name, $arg_name, $arg_type, $code_block) {
  686. //Initialse the argument
  687. $arg_one_init = get_variable_init_statement($arg_type, $arg_name);
  688. //push code onto the array $code_block
  689. array_push ($code_block, "// One argument");
  690. array_push ($code_block, "echo \"\\n-- Testing $fn_name() function with one argument --\\n\";");
  691. array_push ($code_block, "$arg_one_init;");
  692. array_push ($code_block, "var_dump( $fn_name(\$$arg_name) );");
  693. return $code_block;
  694. }
  695. /*
  696. * Generates code for basic functionality test. The generated code
  697. * will test the function with it's mandatory arguments and with all optional arguments.
  698. * Arguments:
  699. * $fn_name => name of the function
  700. * $arg_det => details of the each argument, stored in an array in the form of 'nameofargument' => 'typeofargument'
  701. * $arg_c => total count of arguments that $fn_name takes
  702. * $optional_args. $optional_args => total count of optional arguments that $fn_name takes
  703. * $code_block - an array of code that will be appended to
  704. * Returns:
  705. * $code_block with appends
  706. */
  707. function gen_basic_test_code($fn_name, $arg_det, $arg_c, $optional_args, $code_block) {
  708. if($arg_c == 0) {
  709. //Just generate Zero arg test case and return
  710. $code_block = gen_zero_arg_error_case($fn_name, $code_block);
  711. return $code_block;
  712. }
  713. $names = array_keys($arg_det);
  714. $types = array_values($arg_det);
  715. // prepare code to initialize all reqd. arguments
  716. array_push ($code_block, "");
  717. array_push ($code_block, "// Initialise all required variables");
  718. for($i = 0; $i < $arg_c; $i ++) {
  719. $i_stmt = get_variable_init_statement($types[$i], $names[$i]);
  720. array_push($code_block, $i_stmt);
  721. }
  722. // prepare the function calls
  723. // all arguments including the optional ones
  724. $var_name = array();
  725. foreach ($names as $nm) {
  726. array_push($var_name, "$".$nm);
  727. }
  728. $all_args = implode(", ", $var_name);
  729. array_push ($code_block, "");
  730. array_push ($code_block, "// Calling $fn_name() with all possible arguments");
  731. array_push ($code_block, "var_dump( $fn_name($all_args) );");
  732. //now remove the optional arguments and call the function with mandatory arguments only
  733. if ($optional_args != 0) {
  734. for ($i=0; $i < $optional_args; $i++) {
  735. $discard_n = array_pop($var_name);
  736. }
  737. $args = implode(", ", $var_name);
  738. array_push ($code_block, "");
  739. array_push ($code_block, "// Calling $fn_name() with mandatory arguments");
  740. array_push ($code_block, "var_dump( $fn_name($args) );");
  741. }
  742. return $code_block;
  743. }
  744. /*
  745. * Function to parse command line arguments
  746. * Returns:
  747. * $opt_array => array of options
  748. */
  749. function initialise_opt() {
  750. $opt=array();
  751. $opt['source_loc'] = NULL;
  752. $opt['name'] = NULL;
  753. $opt['error_gen'] = false;
  754. $opt['basic_gen'] = false;
  755. $opt['variation_gen'] = false;
  756. $opt['include_block'] = NULL;
  757. return $opt;
  758. }
  759. function parse_args ($arglist, $opt)
  760. {
  761. for($j = 1; $j<count($arglist); $j++) {
  762. switch ($arglist[$j])
  763. {
  764. case '-s':
  765. $j++;
  766. $opt['source_loc'] = $arglist[$j];
  767. break;
  768. case '-f':
  769. $j++;
  770. $opt['name'] = $arglist[$j];
  771. break;
  772. case '-e':
  773. $opt['error_gen'] = true;
  774. break;
  775. case '-b':
  776. $opt['basic_gen'] = true;
  777. break;
  778. case '-v':
  779. $opt['variation_gen'] = true;
  780. break;
  781. case '-i':
  782. $j++;
  783. $opt['include_block'] = $arglist[$j];
  784. break;
  785. case '-h':
  786. print_opts();
  787. break;
  788. default:
  789. echo "Command line option $arglist[$j] not recognised\n";
  790. print_opts();
  791. }
  792. }
  793. return $opt;
  794. }
  795. /*
  796. * Function to check that source directory given for PHP source actually conatins PHP source
  797. */
  798. function check_source($src)
  799. {
  800. $ext_loc = $src."/ext";
  801. if(!is_dir($ext_loc)) {
  802. echo "A PHP source location is required, $src does not appear to be a valid source location\n";
  803. print_opts();
  804. }
  805. }
  806. /*
  807. * Function to check that a file name exists
  808. */
  809. function check_file($f)
  810. {
  811. if(!is_file($f)) {
  812. echo "$f is not a valid file name \n";
  813. print_opts();
  814. }
  815. }
  816. /*
  817. * Function to check thet either a file name, a list of files or a function area has been supplied
  818. */
  819. function check_fname ($f)
  820. {
  821. if($f == NULL ) {
  822. echo "Require a function name \n";
  823. print_opts();
  824. }
  825. }
  826. /*
  827. * Function to check that the user has requested a type of test case
  828. */
  829. function check_testcase($e, $v, $b) {
  830. if(!$v && !$e && !$b) {
  831. echo "Need to specify which type of test to generate\n";
  832. print_opts();
  833. }
  834. }
  835. /*
  836. * Function to print out help text if any of the input data is incorrect
  837. */
  838. function print_opts() {
  839. echo "\nUsage:\n";
  840. echo " php generate_phpt.php -s <location_of_source_code> -f <function_name> -b|e|v [-i file]\n\n";
  841. echo "-s location_of_source_code ....... Top level directory of PHP source\n";
  842. echo "-f function_name ................. Name of PHP function, eg cos\n";
  843. echo "-b ............................... Generate basic tests\n";
  844. echo "-e ............................... Generate error tests\n";
  845. echo "-v ............................... Generate variation tests\n";
  846. echo "-i file_containing_include_block.. Block of PHP code to be included in the test case\n";
  847. echo "-h ............................... Print this message\n";
  848. exit;
  849. }
  850. /*
  851. * Generates a general testcase template and create the testcase file,
  852. * No code is added other than header and trailer
  853. * Arguments:
  854. * $fn_det => Array with function details
  855. * $sections => List of test sections eg '--TEST--', '--FILE--'..
  856. * $type => basic/error/variation
  857. * $php_file => name of file to import PHP code from
  858. * Returns:
  859. * $test_case => an array containing the complete test case
  860. */
  861. function gen_template($fn_det, $sections, $type, $php_file=NULL) {
  862. $name = $fn_det['name'];
  863. $proto = $fn_det['proto'];
  864. $desc = $fn_det['desc'];
  865. $source_file = $fn_det['source_file'];
  866. $alias = $fn_det['alias'];
  867. $test_case = array();
  868. // get the test header and write into the file
  869. $test_case = gen_test_header($name, $proto, $desc, $source_file, $type, "",$alias, $test_case);
  870. // write the message to indicate the start of addition of code in the template file
  871. if ($php_file == NULl) {
  872. $msg = "\n // add test code here \n";
  873. array_push($test_case['--FILE--'], $msg);
  874. }else{
  875. $test_case['--FILE--'] = read_include_file($php_file, $test_case['--FILE--']);
  876. }
  877. // end the script
  878. $test_case = gen_test_trailer($test_case);
  879. write_file($test_case, $name, $type, $sections);
  880. return ($test_case);
  881. }
  882. /*
  883. * Generate code for testing zero argument case for error testcase
  884. * Arguments:
  885. * $fn_name => name of the function
  886. * $code_block => array of code which will be appended to
  887. * Returns:
  888. * $code_block
  889. */
  890. function gen_zero_arg_error_case($fn_name, $code_block) {
  891. //push code onto the array $code_block
  892. array_push ($code_block, "// Zero arguments");
  893. array_push ($code_block, "echo \"\\n-- Testing $fn_name() function with Zero arguments --\\n\";");
  894. array_push ($code_block, "var_dump( $fn_name() );");
  895. return $code_block;
  896. }
  897. function get_loc_proto($all_c, $fname, $source) {
  898. //get location
  899. $test_info['name'] = $fname;
  900. $test_info['source_file'] = NULL;
  901. $test_info['return_type'] = NULL;
  902. $test_info['params'] = NULL;
  903. $test_info['falias'] = false;
  904. $test_info['found'] = false;
  905. $test_info['alias'] = NULL;
  906. $test_info['error'] = NULL;
  907. $escaped_source = preg_replace("/\\\/", "\\\\\\", $source);
  908. $escaped_source = preg_replace("/\//", "\\\/", $escaped_source);
  909. for ($i=0; $i<count($all_c); $i++)
  910. {
  911. $strings=file_get_contents(chop($all_c[$i]));
  912. if (preg_match ("/FUNCTION\($fname\)/",$strings))
  913. {
  914. //strip build specific part of the implementation file name
  915. preg_match("/$escaped_source\/(.*)$/", $all_c[$i], $tmp);
  916. $test_info['source_file'] = $tmp[1];
  917. //get prototype information
  918. if (preg_match("/\/\*\s+{{{\s*proto\s+(\w*)\s*$fname\(\s*(.*)\s*\)(\n|)\s*(.*?)(\*\/|\n)/", $strings, $matches)) {
  919. $test_info['return_type'] = $matches[1];
  920. $test_info['params'] = $matches[2];
  921. $test_info['desc'] = $matches[4];
  922. }
  923. else {
  924. $test_info['error'] = "\nFailed to parse prototype for $fname in $all_c[$i]".
  925. "\nEither the {{{proto comment is too hard to parse, or ".
  926. "\nthe real implementation is in an alias.\n";
  927. }
  928. $test_info['found'] = true;
  929. if ((preg_match ("/FALIAS\($fname,\s*(\w+),.*\)/",$strings, $alias_name))
  930. || (preg_match ("/FALIAS\((\w+),\s*$fname.*\)/",$strings, $alias_name))) {
  931. // There is another alias referred to in the same C source file. Make a note of it.
  932. $test_info['falias'] = true;
  933. if ( $test_info['alias'] != NULL) {
  934. $test_info['alias'] = $test_info['alias']." ".$alias_name[1];
  935. } else {
  936. $test_info['alias'] = $alias_name[1];
  937. }
  938. }
  939. }
  940. elseif ((preg_match ("/FALIAS\($fname,\s*(\w+),.*\)/",$strings, $alias_name))
  941. || (preg_match ("/FALIAS\((\w+),\s*$fname.*\)/",$strings, $alias_name))) {
  942. // There is an alias to the function in a different file from the main function definition - make a note of it
  943. $test_info['falias'] = true;
  944. if ( $test_info['alias'] != NULL) {
  945. $test_info['alias'] = $test_info['alias']." ".$alias_name[1];
  946. } else {
  947. $test_info['alias'] = $alias_name[1];
  948. }
  949. }
  950. //Some functions are in their own files and not declared using FUNTION/FALIAS.
  951. //If we haven't found either FUNCTION or FALIAS try just looking for the prototype
  952. elseif (preg_match ("/\/\*\s+{{{\s*proto\s+(\w*)\s*$fname\(\s*(.*)\s*\)(\n|)\s*(.*)\*\//", $strings, $matches)) {
  953. $test_info['return_type'] = $matches[1];
  954. $test_info['params'] = $matches[2];
  955. $test_info['desc'] = $matches[4];
  956. $test_info['found'] = true;
  957. preg_match("/$escaped_source\/(.*)$/", $all_c[$i], $tmp);
  958. $test_info['source_file']= $tmp[1];
  959. //break;
  960. }
  961. }
  962. return $test_info;
  963. }
  964. ?>