test.drush.inc

  1. 6.x commands/core/test.drush.inc
  2. 4.x commands/core/test.drush.inc
  3. 5.x commands/core/test.drush.inc

Simpletest module drush integration.

Functions

Namesort ascending Description
test_test_run_complete Command argument complete callback.
test_drush_command Implementation of hook_drush_command().
simpletest_drush_test_groups Retrieve all test groups and sanitize their names to make them command-line friendly.
simpletest_drush_run_test Run a single test and display any failure messages.
drush_test_xml_results Write test results in jUnit XML format.
drush_test_run_validate
drush_test_run_class Run a test class via drush_invoke_process()
drush_test_run Test-run command callback.
drush_test_list
drush_test_get_all_tests
drush_test_clean

File

commands/core/test.drush.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Simpletest module drush integration.
  5. */
  6. /**
  7. * Implementation of hook_drush_command().
  8. */
  9. function test_drush_command() {
  10. $items = array();
  11. $items['test-run'] = array(
  12. 'description' => "Run tests. Note that you must use the --uri option.",
  13. 'arguments' => array(
  14. 'targets' => 'A test class, a test group. If omitted, a list of test classes and test groups is presented. Delimit multiple targets using commas.',
  15. ),
  16. 'examples' => array(
  17. 'test-run' => 'List all available classes and groups.',
  18. 'sudo -u apache test-run --all' => 'Run all available tests. Avoid permission related failures by running as web server user.',
  19. 'test-run XMLRPCBasicTestCase' => 'Run one test class.',
  20. 'test-run XML-RPC' => 'Run all classes in a XML-RPC group.',
  21. 'test-run XML-RPC,Filter' => 'Run all tests from multiple groups/classes.',
  22. 'test-run XMLRPCBasicTestCase --methods="testListMethods, testInvalidMessageParsing"' => 'Run particular methods in the specified class or group.',
  23. ),
  24. 'options' => array(
  25. 'all' => 'Run all available tests',
  26. 'methods' => 'A comma delimited list of methods that should be run within the test class. Defaults to all methods.',
  27. 'dirty' => 'Skip cleanup of temporary tables and files. Helpful for reading debug() messages and other post-mortem forensics.',
  28. 'xml' => 'Output verbose test results to a specified directory using the JUnit test reporting format. Useful for integrating with Jenkins.'
  29. ),
  30. 'outputformat' => array(
  31. 'default' => 'table',
  32. 'field-labels' => array(
  33. 'group' => 'Group',
  34. 'class' => 'Class',
  35. 'name' => 'Name',
  36. ),
  37. 'output-data-type' => 'format-table',
  38. ),
  39. // If you DRUSH_BOOTSTRAP_DRUPAL_LOGIN, you fall victim to http://drupal.org/node/974768. We'd like
  40. // to not bootstrap at all but simpletest uses Drupal to discover test classes,
  41. // cache the lists of tests, file_prepare_directory(), variable lookup like
  42. // httpauth creds, copy pre-built registry table from testing side, etc.
  43. 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
  44. 'drupal dependencies' => array('simpletest'),
  45. );
  46. $items['test-clean'] = array(
  47. 'description' => "Clean temporary tables and files.",
  48. 'drupal dependencies' => array('simpletest'),
  49. );
  50. return $items;
  51. }
  52. /**
  53. * Command argument complete callback.
  54. *
  55. * @return
  56. * Array of test classes and groups.
  57. */
  58. function test_test_run_complete() {
  59. if (drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_FULL) == DRUSH_BOOTSTRAP_DRUPAL_FULL && module_exists('simpletest')) {
  60. // Retrieve all tests and groups.
  61. list($groups, $all_tests) = drush_test_get_all_tests();
  62. return array('values' => array_keys($groups) + $all_tests);
  63. }
  64. }
  65. // Command callback
  66. function drush_test_clean() {
  67. return simpletest_clean_environment();
  68. }
  69. // Validate hook
  70. function drush_test_run_validate($specs = NULL) {
  71. if (!drush_get_option('uri')) {
  72. // No longer needed?
  73. // return drush_set_error(dt("You must specify this site's URL using the --uri parameter."));
  74. }
  75. }
  76. /**
  77. * Test-run command callback.
  78. *
  79. * @specs
  80. * A comma delimited string of test classes or group names.
  81. */
  82. function drush_test_run($specs = NULL) {
  83. if (drush_drupal_major_version() > 7) {
  84. cache()->delete('simpletest');
  85. }
  86. else {
  87. cache_clear_all('simpletest', 'cache');
  88. }
  89. // Retrieve all tests and groups.
  90. list($groups, $all_tests) = drush_test_get_all_tests();
  91. if (drush_get_option('all')) {
  92. // Run all tests.
  93. foreach (array_keys($groups) as $group) {
  94. foreach (array_keys($groups[$group]) as $class) {
  95. drush_invoke_process('@self', 'test-run', array($class));
  96. }
  97. }
  98. return;
  99. }
  100. elseif (empty($specs)) {
  101. // No argument so list all groups/classes.
  102. return drush_test_list($groups);
  103. }
  104. elseif (strpos($specs, ',') === FALSE && in_array($specs, $all_tests)) {
  105. // We got one, valid class. Good, now run it.
  106. simpletest_drush_run_test($specs);
  107. if (!drush_get_option('dirty')) {
  108. simpletest_clean_environment();
  109. }
  110. return drush_get_error() === DRUSH_SUCCESS;
  111. }
  112. // See if we got a list of specs.
  113. foreach (explode(',', $specs) as $spec) {
  114. $spec = trim($spec);
  115. if (in_array($spec, $all_tests)) {
  116. // Specific test class specified.
  117. drush_test_run_class($spec);
  118. }
  119. if (isset($groups[$spec])) {
  120. // Specific group specified.
  121. foreach (array_keys($groups[$spec]) as $class) {
  122. drush_test_run_class($class);
  123. }
  124. }
  125. }
  126. }
  127. /**
  128. * Run a test class via drush_invoke_process()
  129. * @param string $class
  130. * The test class to run.
  131. *
  132. * @return
  133. * If the command could not be completed successfully, FALSE.
  134. * If the command was completed, this will return an associative
  135. * array containing the results of the API call.
  136. * @see drush_backend_get_result()
  137. */
  138. function drush_test_run_class($class) {
  139. $backend_options = array('output-label' => "$class ", 'integrate' => TRUE, 'output' => TRUE);
  140. $return = drush_invoke_process('@self', 'test-run', array($class), drush_redispatch_get_options(), $backend_options);
  141. return $return;
  142. }
  143. /**
  144. * Run a single test and display any failure messages.
  145. */
  146. function simpletest_drush_run_test($class) {
  147. drush_log(dt('Starting test @test.', array('@test' => $class)), 'ok');
  148. if (drush_drupal_major_version() >= 7) {
  149. $test_id = db_insert('simpletest_test_id')
  150. ->useDefaults(array('test_id'))
  151. ->execute();
  152. }
  153. else {
  154. db_query('INSERT INTO {simpletest_test_id} (test_id) VALUES (default)');
  155. $test_id = db_last_insert_id('simpletest_test_id', 'test_id');
  156. }
  157. $test = new $class($test_id);
  158. if ($methods_string = drush_get_option('methods')) {
  159. foreach (explode(',', $methods_string) as $method) {
  160. $methods[] = trim($method);
  161. }
  162. $test->run($methods);
  163. }
  164. else {
  165. $test->run();
  166. }
  167. $info = $test->getInfo();
  168. $status = ((isset($test->results['#fail']) && $test->results['#fail'] > 0)
  169. || (isset($test->results['#exception']) && $test->results['#exception'] > 0) ? 'error' : 'ok');
  170. drush_log($info['name'] . ' ' . _simpletest_format_summary_line($test->results), $status);
  171. if ($dir = drush_get_option('xml')) {
  172. drush_test_xml_results($test_id, $dir, $info);
  173. }
  174. if ($status === 'error') {
  175. if (drush_drupal_major_version() >= 7) {
  176. $args = array(':test_id' => $test_id);
  177. $result = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id AND status IN ('exception', 'fail') ORDER BY test_class, message_id", $args);
  178. foreach ($result as $record) {
  179. drush_set_error('DRUSH_TEST_FAIL', dt("Test !function failed: !message in !file on line !line", array(
  180. '!function' => $record->function,
  181. '!message' => $record->message,
  182. '!file' => $record->file,
  183. '!line' => $record->line,
  184. )));
  185. }
  186. }
  187. else {
  188. $result = db_query("SELECT * FROM {simpletest} WHERE test_id = %d AND status IN ('exception', 'fail') ORDER BY test_class, message_id", $test_id);
  189. while ($row = db_fetch_object($result)) {
  190. drush_set_error('DRUSH_TEST_FAIL', dt("Test !function failed: !message in !file on line !line", array(
  191. '!function' => $row->function,
  192. '!message' => $row->message,
  193. '!file' => $row->file,
  194. '!line' => $row->line,
  195. )));
  196. }
  197. }
  198. }
  199. }
  200. /**
  201. * Retrieve all test groups and sanitize their names to make them command-line
  202. * friendly.
  203. */
  204. function simpletest_drush_test_groups($tests) {
  205. $groups = array();
  206. foreach (simpletest_categorize_tests($tests) as $name => $group) {
  207. $sanitized = strtr($name, array(' ' => ''));
  208. $groups[$sanitized] = $group;
  209. }
  210. return $groups;
  211. }
  212. // Print a listing of all available tests
  213. function drush_test_list($groups) {
  214. foreach ($groups as $group_name => $group_tests) {
  215. foreach ($group_tests as $test_class => $test_info) {
  216. $rows[] = array('group' => $group_name, 'class' => $test_class, 'name' => $test_info['name']);
  217. }
  218. }
  219. return $rows;
  220. }
  221. function drush_test_get_all_tests() {
  222. if (function_exists('simpletest_get_all_tests')) {
  223. $all_tests = simpletest_get_all_tests();
  224. $groups = simpletest_drush_test_groups($all_tests);
  225. }
  226. else {
  227. $groups = simpletest_test_get_all();
  228. $all_tests = array();
  229. foreach ($groups as $group) {
  230. $all_tests = array_merge($all_tests, array_keys($group));
  231. }
  232. }
  233. return array($groups, $all_tests);
  234. }
  235. /**
  236. * Write test results in jUnit XML format.
  237. */
  238. function drush_test_xml_results($test_id, $dir, $info) {
  239. $dir = is_string($dir) ? $dir : '.';
  240. // Get an array of test result objects from the database.
  241. if (drush_drupal_major_version() >= 7) {
  242. $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id));
  243. }
  244. else {
  245. $result = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $test_id);
  246. $results = array();
  247. while ($row = db_fetch_object($result)) {
  248. $results[] = $row;
  249. }
  250. }
  251. // Collect and aggregate the data from simpletest.
  252. $test_suites = array();
  253. foreach ($results as $result) {
  254. // Create test_suite object.
  255. // Formatting name so it becomes "group.name" without any other dots. Jenkins uses string splitting on dots to gather info.
  256. $test_suite_name = str_replace('.', '', $info['group']) . '.' . str_replace('.', '', $info['name']);
  257. if (!isset($test_suites[$test_suite_name])) {
  258. $test_suite = new stdClass();
  259. $test_suite->name = $test_suite_name;
  260. $test_suite->test_cases = array();
  261. $test_suites[$test_suite_name] = $test_suite;
  262. }
  263. else {
  264. $test_suite = $test_suites[$test_suite_name];
  265. }
  266. // Create test_case object.
  267. list(, $test_case_name) = explode('->', $result->function, 2);
  268. if (empty($test_case_name)) {
  269. // There is no '->' present on static function calls. Use the whole string in those cases.
  270. $test_case_name = $result->function;
  271. }
  272. $test_case_name = str_replace('.', '', $test_case_name); // Remove those dots Jenkins loves so much.
  273. if (!isset($test_suite->test_cases[$test_case_name])) {
  274. $test_case = new stdClass();
  275. $test_case->name = $test_case_name;
  276. $test_case->failure_message = '';
  277. $test_case->error_message = '';
  278. $test_case->system_out = '';
  279. $test_case->system_err = '';
  280. $test_suite->test_cases[$test_case_name] = $test_case;
  281. }
  282. else {
  283. $test_case = $test_suite->test_cases[$test_case_name];
  284. }
  285. // Prepare message.
  286. $status = str_pad($result->status . ':', 10);
  287. $message = strip_tags($result->message, '<a>'); // Jenkins encodes the output so don't use any tags.
  288. $message = preg_replace('/<a.*?href="([^"]+)".*?>(.*?)<\/a>/', '$1 $2', $message); // Jenkins will turn urls into clickable links.
  289. $message = $status . ' [' . $result->message_group . '] ' . $message . ' [' . basename($result->file) . ':' . $result->line . "]\n";
  290. // Everything is logged in system_out.
  291. $test_case->system_out .= $message;
  292. // Failures go to failures.
  293. if ($result->status == 'fail') {
  294. $test_case->failure_message .= $message;
  295. }
  296. // Exceptions go both to errors and system_err.
  297. if ($result->status == 'exception') {
  298. $test_case->error_message .= $message;
  299. $test_case->system_err .= $message;
  300. }
  301. }
  302. // Build an XML document from our results.
  303. $xml = new DOMDocument('1.0', 'UTF-8');
  304. foreach ($test_suites as $test_suite) {
  305. $test_suite_element = $xml->createElement('testsuite');
  306. $test_suite_element->setAttribute('name', $test_suite->name);
  307. foreach ($test_suite->test_cases as $test_case) {
  308. $test_case_element = $xml->createElement('testcase');
  309. $test_case_element->setAttribute('name', $test_case->name);
  310. if (!empty($test_case->failure_message)) {
  311. $failure_element = $xml->createElement('failure');
  312. $failure_element->setAttribute('message', $test_case->failure_message);
  313. $test_case_element->appendChild($failure_element);
  314. }
  315. if (!empty($test_case->error_message)) {
  316. $error_element = $xml->createElement('error');
  317. $error_element->setAttribute('message', $test_case->error_message);
  318. $test_case_element->appendChild($error_element);
  319. }
  320. if (!empty($test_case->system_out)) {
  321. $system_out_element = $xml->createElement('system-out');
  322. $system_out_element->appendChild($xml->createTextNode($test_case->system_out));
  323. $test_case_element->appendChild($system_out_element);
  324. }
  325. if (!empty($test_case->system_err)) {
  326. $system_err_element = $xml->createElement('system-err');
  327. $system_err_element->appendChild($xml->createTextNode($test_case->system_err));
  328. $test_case_element->appendChild($system_err_element);
  329. }
  330. $test_suite_element->appendChild($test_case_element);
  331. }
  332. $xml->appendChild($test_suite_element);
  333. }
  334. // Save to disk.
  335. file_put_contents($dir . '/testsuite-' . $test_id . '.xml', $xml->saveXML());
  336. }