Projet

Général

Profil

Paste
Télécharger (24,9 ko) Statistiques
| Branche: | Révision:

root / drupal7 / scripts / run-tests.sh @ 6b24a280

1 85ad3d82 Assos Assos
<?php
2
/**
3
 * @file
4
 * This script runs Drupal tests from command line.
5
 */
6
7
define('SIMPLETEST_SCRIPT_COLOR_PASS', 32); // Green.
8
define('SIMPLETEST_SCRIPT_COLOR_FAIL', 31); // Red.
9
define('SIMPLETEST_SCRIPT_COLOR_EXCEPTION', 33); // Brown.
10
11 b0dc3a2e Julien Enselme
define('SIMPLETEST_SCRIPT_EXIT_SUCCESS', 0);
12
define('SIMPLETEST_SCRIPT_EXIT_FAILURE', 1);
13
define('SIMPLETEST_SCRIPT_EXIT_EXCEPTION', 2);
14
15 85ad3d82 Assos Assos
// Set defaults and get overrides.
16
list($args, $count) = simpletest_script_parse_args();
17
18
if ($args['help'] || $count == 0) {
19
  simpletest_script_help();
20 b0dc3a2e Julien Enselme
  exit(($count == 0) ? SIMPLETEST_SCRIPT_EXIT_FAILURE : SIMPLETEST_SCRIPT_EXIT_SUCCESS);
21 85ad3d82 Assos Assos
}
22
23
if ($args['execute-test']) {
24
  // Masquerade as Apache for running tests.
25
  simpletest_script_init("Apache");
26
  simpletest_script_run_one_test($args['test-id'], $args['execute-test']);
27
}
28
else {
29
  // Run administrative functions as CLI.
30
  simpletest_script_init(NULL);
31
}
32
33
// Bootstrap to perform initial validation or other operations.
34
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
35
if (!module_exists('simpletest')) {
36
  simpletest_script_print_error("The simpletest module must be enabled before this script can run.");
37 b0dc3a2e Julien Enselme
  exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
38 85ad3d82 Assos Assos
}
39
40
if ($args['clean']) {
41
  // Clean up left-over times and directories.
42
  simpletest_clean_environment();
43
  echo "\nEnvironment cleaned.\n";
44
45
  // Get the status messages and print them.
46
  $messages = array_pop(drupal_get_messages('status'));
47
  foreach ($messages as $text) {
48
    echo " - " . $text . "\n";
49
  }
50 b0dc3a2e Julien Enselme
  exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS);
51 85ad3d82 Assos Assos
}
52
53
// Load SimpleTest files.
54
$groups = simpletest_test_get_all();
55
$all_tests = array();
56
foreach ($groups as $group => $tests) {
57
  $all_tests = array_merge($all_tests, array_keys($tests));
58
}
59
$test_list = array();
60
61
if ($args['list']) {
62
  // Display all available tests.
63
  echo "\nAvailable test groups & classes\n";
64
  echo   "-------------------------------\n\n";
65
  foreach ($groups as $group => $tests) {
66
    echo $group . "\n";
67
    foreach ($tests as $class => $info) {
68
      echo " - " . $info['name'] . ' (' . $class . ')' . "\n";
69
    }
70
  }
71 b0dc3a2e Julien Enselme
  exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS);
72 85ad3d82 Assos Assos
}
73
74
$test_list = simpletest_script_get_test_list();
75
76
// Try to allocate unlimited time to run the tests.
77
drupal_set_time_limit(0);
78
79
simpletest_script_reporter_init();
80
81
// Setup database for test results.
82
$test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute();
83
84
// Execute tests.
85 b0dc3a2e Julien Enselme
$status = simpletest_script_execute_batch($test_id, simpletest_script_get_test_list());
86 85ad3d82 Assos Assos
87
// Retrieve the last database prefix used for testing and the last test class
88
// that was run from. Use the information to read the lgo file in case any
89
// fatal errors caused the test to crash.
90
list($last_prefix, $last_test_class) = simpletest_last_test_get($test_id);
91
simpletest_log_read($test_id, $last_prefix, $last_test_class);
92
93
// Stop the timer.
94
simpletest_script_reporter_timer_stop();
95
96
// Display results before database is cleared.
97
simpletest_script_reporter_display_results();
98
99
if ($args['xml']) {
100
  simpletest_script_reporter_write_xml_results();
101
}
102
103
// Cleanup our test results.
104
simpletest_clean_results_table($test_id);
105
106
// Test complete, exit.
107 b0dc3a2e Julien Enselme
exit($status);
108 85ad3d82 Assos Assos
109
/**
110
 * Print help text.
111
 */
112
function simpletest_script_help() {
113
  global $args;
114
115
  echo <<<EOF
116
117
Run Drupal tests from the shell.
118
119
Usage:        {$args['script']} [OPTIONS] <tests>
120
Example:      {$args['script']} Profile
121
122
All arguments are long options.
123
124
  --help      Print this page.
125
126
  --list      Display all available test groups.
127
128
  --clean     Cleans up database tables or directories from previous, failed,
129
              tests and then exits (no tests are run).
130
131
  --url       Immediately precedes a URL to set the host and path. You will
132
              need this parameter if Drupal is in a subdirectory on your
133
              localhost and you have not set \$base_url in settings.php. Tests
134
              can be run under SSL by including https:// in the URL.
135
136
  --php       The absolute path to the PHP executable. Usually not needed.
137
138
  --concurrency [num]
139
140
              Run tests in parallel, up to [num] tests at a time.
141
142
  --all       Run all available tests.
143
144
  --class     Run tests identified by specific class names, instead of group names.
145
146
  --file      Run tests identified by specific file names, instead of group names.
147
              Specify the path and the extension (i.e. 'modules/user/user.test').
148
149 b0dc3a2e Julien Enselme
  --directory Run all tests found within the specified file directory.
150
151 85ad3d82 Assos Assos
  --xml       <path>
152
153
              If provided, test results will be written as xml files to this path.
154
155
  --color     Output text format results with color highlighting.
156
157
  --verbose   Output detailed assertion messages in addition to summary.
158
159
  <test1>[,<test2>[,<test3> ...]]
160
161
              One or more tests to be run. By default, these are interpreted
162
              as the names of test groups as shown at
163
              ?q=admin/config/development/testing.
164
              These group names typically correspond to module names like "User"
165
              or "Profile" or "System", but there is also a group "XML-RPC".
166
              If --class is specified then these are interpreted as the names of
167
              specific test classes whose test methods will be run. Tests must
168
              be separated by commas. Ignored if --all is specified.
169
170
To run this script you will normally invoke it from the root directory of your
171
Drupal installation as the webserver user (differs per configuration), or root:
172
173
sudo -u [wwwrun|www-data|etc] php ./scripts/{$args['script']}
174
  --url http://example.com/ --all
175
sudo -u [wwwrun|www-data|etc] php ./scripts/{$args['script']}
176
  --url http://example.com/ --class BlockTestCase
177
\n
178
EOF;
179
}
180
181
/**
182
 * Parse execution argument and ensure that all are valid.
183
 *
184
 * @return The list of arguments.
185
 */
186
function simpletest_script_parse_args() {
187
  // Set default values.
188
  $args = array(
189
    'script' => '',
190
    'help' => FALSE,
191
    'list' => FALSE,
192
    'clean' => FALSE,
193
    'url' => '',
194
    'php' => '',
195
    'concurrency' => 1,
196
    'all' => FALSE,
197
    'class' => FALSE,
198
    'file' => FALSE,
199 b0dc3a2e Julien Enselme
    'directory' => '',
200 85ad3d82 Assos Assos
    'color' => FALSE,
201
    'verbose' => FALSE,
202
    'test_names' => array(),
203
    // Used internally.
204
    'test-id' => 0,
205
    'execute-test' => '',
206
    'xml' => '',
207
  );
208
209
  // Override with set values.
210
  $args['script'] = basename(array_shift($_SERVER['argv']));
211
212
  $count = 0;
213
  while ($arg = array_shift($_SERVER['argv'])) {
214
    if (preg_match('/--(\S+)/', $arg, $matches)) {
215
      // Argument found.
216
      if (array_key_exists($matches[1], $args)) {
217
        // Argument found in list.
218
        $previous_arg = $matches[1];
219
        if (is_bool($args[$previous_arg])) {
220
          $args[$matches[1]] = TRUE;
221
        }
222
        else {
223
          $args[$matches[1]] = array_shift($_SERVER['argv']);
224
        }
225
        // Clear extraneous values.
226
        $args['test_names'] = array();
227
        $count++;
228
      }
229
      else {
230
        // Argument not found in list.
231
        simpletest_script_print_error("Unknown argument '$arg'.");
232 b0dc3a2e Julien Enselme
        exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
233 85ad3d82 Assos Assos
      }
234
    }
235
    else {
236
      // Values found without an argument should be test names.
237
      $args['test_names'] += explode(',', $arg);
238
      $count++;
239
    }
240
  }
241
242
  // Validate the concurrency argument
243
  if (!is_numeric($args['concurrency']) || $args['concurrency'] <= 0) {
244
    simpletest_script_print_error("--concurrency must be a strictly positive integer.");
245 b0dc3a2e Julien Enselme
    exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
246 85ad3d82 Assos Assos
  }
247
248
  return array($args, $count);
249
}
250
251
/**
252
 * Initialize script variables and perform general setup requirements.
253
 */
254
function simpletest_script_init($server_software) {
255
  global $args, $php;
256
257
  $host = 'localhost';
258
  $path = '';
259
  // Determine location of php command automatically, unless a command line argument is supplied.
260
  if (!empty($args['php'])) {
261
    $php = $args['php'];
262
  }
263
  elseif ($php_env = getenv('_')) {
264
    // '_' is an environment variable set by the shell. It contains the command that was executed.
265
    $php = $php_env;
266
  }
267
  elseif ($sudo = getenv('SUDO_COMMAND')) {
268
    // 'SUDO_COMMAND' is an environment variable set by the sudo program.
269
    // Extract only the PHP interpreter, not the rest of the command.
270
    list($php, ) = explode(' ', $sudo, 2);
271
  }
272
  else {
273
    simpletest_script_print_error('Unable to automatically determine the path to the PHP interpreter. Supply the --php command line argument.');
274
    simpletest_script_help();
275 b0dc3a2e Julien Enselme
    exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
276 85ad3d82 Assos Assos
  }
277
278
  // Get URL from arguments.
279
  if (!empty($args['url'])) {
280
    $parsed_url = parse_url($args['url']);
281
    $host = $parsed_url['host'] . (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
282
    $path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
283
284
    // If the passed URL schema is 'https' then setup the $_SERVER variables
285
    // properly so that testing will run under HTTPS.
286
    if ($parsed_url['scheme'] == 'https') {
287
      $_SERVER['HTTPS'] = 'on';
288
    }
289
  }
290
291
  $_SERVER['HTTP_HOST'] = $host;
292
  $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
293
  $_SERVER['SERVER_ADDR'] = '127.0.0.1';
294
  $_SERVER['SERVER_SOFTWARE'] = $server_software;
295
  $_SERVER['SERVER_NAME'] = 'localhost';
296
  $_SERVER['REQUEST_URI'] = $path .'/';
297
  $_SERVER['REQUEST_METHOD'] = 'GET';
298
  $_SERVER['SCRIPT_NAME'] = $path .'/index.php';
299
  $_SERVER['PHP_SELF'] = $path .'/index.php';
300
  $_SERVER['HTTP_USER_AGENT'] = 'Drupal command line';
301
302
  if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
303
    // Ensure that any and all environment variables are changed to https://.
304
    foreach ($_SERVER as $key => $value) {
305
      $_SERVER[$key] = str_replace('http://', 'https://', $_SERVER[$key]);
306
    }
307
  }
308
309
  chdir(realpath(dirname(__FILE__) . '/..'));
310
  define('DRUPAL_ROOT', getcwd());
311
  require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
312
}
313
314
/**
315
 * Execute a batch of tests.
316
 */
317
function simpletest_script_execute_batch($test_id, $test_classes) {
318
  global $args;
319
320 b0dc3a2e Julien Enselme
  $total_status = SIMPLETEST_SCRIPT_EXIT_SUCCESS;
321
322 85ad3d82 Assos Assos
  // Multi-process execution.
323
  $children = array();
324
  while (!empty($test_classes) || !empty($children)) {
325
    while (count($children) < $args['concurrency']) {
326
      if (empty($test_classes)) {
327
        break;
328
      }
329
330
      // Fork a child process.
331
      $test_class = array_shift($test_classes);
332
      $command = simpletest_script_command($test_id, $test_class);
333
      $process = proc_open($command, array(), $pipes, NULL, NULL, array('bypass_shell' => TRUE));
334
335
      if (!is_resource($process)) {
336
        echo "Unable to fork test process. Aborting.\n";
337 b0dc3a2e Julien Enselme
        exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
338 85ad3d82 Assos Assos
      }
339
340
      // Register our new child.
341
      $children[] = array(
342
        'process' => $process,
343
        'class' => $test_class,
344
        'pipes' => $pipes,
345
      );
346
    }
347
348
    // Wait for children every 200ms.
349
    usleep(200000);
350
351
    // Check if some children finished.
352
    foreach ($children as $cid => $child) {
353
      $status = proc_get_status($child['process']);
354
      if (empty($status['running'])) {
355
        // The child exited, unregister it.
356
        proc_close($child['process']);
357 b0dc3a2e Julien Enselme
        if ($status['exitcode'] == SIMPLETEST_SCRIPT_EXIT_FAILURE) {
358
          if ($status['exitcode'] > $total_status) {
359
            $total_status = $status['exitcode'];
360
          }
361
        }
362
        elseif ($status['exitcode']) {
363
          $total_status = $status['exitcode'];
364 85ad3d82 Assos Assos
          echo 'FATAL ' . $test_class . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').' . "\n";
365
        }
366 b0dc3a2e Julien Enselme
367
        // Remove this child.
368 85ad3d82 Assos Assos
        unset($children[$cid]);
369
      }
370
    }
371
  }
372 b0dc3a2e Julien Enselme
  return $total_status;
373 85ad3d82 Assos Assos
}
374
375
/**
376
 * Bootstrap Drupal and run a single test.
377
 */
378
function simpletest_script_run_one_test($test_id, $test_class) {
379
  try {
380
    // Bootstrap Drupal.
381
    drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
382
383
    simpletest_classloader_register();
384
385
    $test = new $test_class($test_id);
386
    $test->run();
387
    $info = $test->getInfo();
388
389
    $had_fails = (isset($test->results['#fail']) && $test->results['#fail'] > 0);
390
    $had_exceptions = (isset($test->results['#exception']) && $test->results['#exception'] > 0);
391
    $status = ($had_fails || $had_exceptions ? 'fail' : 'pass');
392
    simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->results) . "\n", simpletest_script_color_code($status));
393
394
    // Finished, kill this runner.
395 b0dc3a2e Julien Enselme
    if ($had_fails || $had_exceptions) {
396
      exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
397
    }
398
    exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS);
399 85ad3d82 Assos Assos
  }
400
  catch (Exception $e) {
401
    echo (string) $e;
402 b0dc3a2e Julien Enselme
    exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
403 85ad3d82 Assos Assos
  }
404
}
405
406
/**
407
 * Return a command used to run a test in a separate process.
408
 *
409
 * @param $test_id
410
 *  The current test ID.
411
 * @param $test_class
412
 *  The name of the test class to run.
413
 */
414
function simpletest_script_command($test_id, $test_class) {
415
  global $args, $php;
416
417
  $command = escapeshellarg($php) . ' ' . escapeshellarg('./scripts/' . $args['script']) . ' --url ' . escapeshellarg($args['url']);
418
  if ($args['color']) {
419
    $command .= ' --color';
420
  }
421
  $command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test " . escapeshellarg($test_class);
422
  return $command;
423
}
424
425
/**
426
 * Get list of tests based on arguments. If --all specified then
427
 * returns all available tests, otherwise reads list of tests.
428
 *
429
 * Will print error and exit if no valid tests were found.
430
 *
431
 * @return List of tests.
432
 */
433
function simpletest_script_get_test_list() {
434
  global $args, $all_tests, $groups;
435
436
  $test_list = array();
437
  if ($args['all']) {
438
    $test_list = $all_tests;
439
  }
440
  else {
441
    if ($args['class']) {
442
      // Check for valid class names.
443 b4adf10d Assos Assos
      $test_list = array();
444
      foreach ($args['test_names'] as $test_class) {
445
        if (class_exists($test_class)) {
446
          $test_list[] = $test_class;
447
        }
448
        else {
449
          $groups = simpletest_test_get_all();
450
          $all_classes = array();
451
          foreach ($groups as $group) {
452
            $all_classes = array_merge($all_classes, array_keys($group));
453
          }
454
          simpletest_script_print_error('Test class not found: ' . $test_class);
455
          simpletest_script_print_alternatives($test_class, $all_classes, 6);
456 b0dc3a2e Julien Enselme
          exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
457 85ad3d82 Assos Assos
        }
458
      }
459
    }
460
    elseif ($args['file']) {
461
      $files = array();
462
      foreach ($args['test_names'] as $file) {
463
        $files[drupal_realpath($file)] = 1;
464
      }
465
466
      // Check for valid class names.
467
      foreach ($all_tests as $class_name) {
468
        $refclass = new ReflectionClass($class_name);
469
        $file = $refclass->getFileName();
470
        if (isset($files[$file])) {
471
          $test_list[] = $class_name;
472
        }
473
      }
474
    }
475 b0dc3a2e Julien Enselme
    elseif ($args['directory']) {
476
      // Extract test case class names from specified directory.
477
      // Find all tests in the PSR-X structure; Drupal\$extension\Tests\*.php
478
      // Since we do not want to hard-code too many structural file/directory
479
      // assumptions about PSR-0/4 files and directories, we check for the
480
      // minimal conditions only; i.e., a '*.php' file that has '/Tests/' in
481
      // its path.
482
      // Ignore anything from third party vendors, and ignore template files used in tests.
483
      // And any api.php files.
484
      $ignore = array('nomask' => '/vendor|\.tpl\.php|\.api\.php/');
485
      $files = array();
486
      if ($args['directory'][0] === '/') {
487
        $directory = $args['directory'];
488
      }
489
      else {
490
        $directory = DRUPAL_ROOT . "/" . $args['directory'];
491
      }
492
      $file_list = file_scan_directory($directory, '/\.php|\.test$/', $ignore);
493
      foreach ($file_list as $file) {
494
        // '/Tests/' can be contained anywhere in the file's path (there can be
495
        // sub-directories below /Tests), but must be contained literally.
496
        // Case-insensitive to match all Simpletest and PHPUnit tests:
497
        //   ./lib/Drupal/foo/Tests/Bar/Baz.php
498
        //   ./foo/src/Tests/Bar/Baz.php
499
        //   ./foo/tests/Drupal/foo/Tests/FooTest.php
500
        //   ./foo/tests/src/FooTest.php
501
        // $file->filename doesn't give us a directory, so we use $file->uri
502
        // Strip the drupal root directory and trailing slash off the URI
503
        $filename = substr($file->uri, strlen(DRUPAL_ROOT)+1);
504
        if (stripos($filename, '/Tests/')) {
505
          $files[drupal_realpath($filename)] = 1;
506
        } else if (stripos($filename, '.test')){
507
          $files[drupal_realpath($filename)] = 1;
508
        }
509
      }
510
511
      // Check for valid class names.
512
      foreach ($all_tests as $class_name) {
513
        $refclass = new ReflectionClass($class_name);
514
        $classfile = $refclass->getFileName();
515
        if (isset($files[$classfile])) {
516
          $test_list[] = $class_name;
517
        }
518
      }
519
    }
520 85ad3d82 Assos Assos
    else {
521
      // Check for valid group names and get all valid classes in group.
522
      foreach ($args['test_names'] as $group_name) {
523
        if (isset($groups[$group_name])) {
524 b4adf10d Assos Assos
          $test_list = array_merge($test_list, array_keys($groups[$group_name]));
525
        }
526
        else {
527
          simpletest_script_print_error('Test group not found: ' . $group_name);
528
          simpletest_script_print_alternatives($group_name, array_keys($groups));
529 b0dc3a2e Julien Enselme
          exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
530 85ad3d82 Assos Assos
        }
531
      }
532
    }
533
  }
534
535
  if (empty($test_list)) {
536
    simpletest_script_print_error('No valid tests were specified.');
537 b0dc3a2e Julien Enselme
    exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
538 85ad3d82 Assos Assos
  }
539
  return $test_list;
540
}
541
542
/**
543
 * Initialize the reporter.
544
 */
545
function simpletest_script_reporter_init() {
546
  global $args, $all_tests, $test_list, $results_map;
547
548
  $results_map = array(
549
    'pass' => 'Pass',
550
    'fail' => 'Fail',
551
    'exception' => 'Exception'
552
  );
553
554
  echo "\n";
555
  echo "Drupal test run\n";
556
  echo "---------------\n";
557
  echo "\n";
558
559
  // Tell the user about what tests are to be run.
560
  if ($args['all']) {
561
    echo "All tests will run.\n\n";
562
  }
563
  else {
564
    echo "Tests to be run:\n";
565
    foreach ($test_list as $class_name) {
566
      $info = call_user_func(array($class_name, 'getInfo'));
567
      echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n";
568
    }
569
    echo "\n";
570
  }
571
572
  echo "Test run started:\n";
573
  echo " " . format_date($_SERVER['REQUEST_TIME'], 'long') . "\n";
574
  timer_start('run-tests');
575
  echo "\n";
576
577
  echo "Test summary\n";
578
  echo "------------\n";
579
  echo "\n";
580
}
581
582
/**
583
 * Display jUnit XML test results.
584
 */
585
function simpletest_script_reporter_write_xml_results() {
586
  global $args, $test_id, $results_map;
587
588
  $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id));
589
590
  $test_class = '';
591
  $xml_files = array();
592
593
  foreach ($results as $result) {
594
    if (isset($results_map[$result->status])) {
595
      if ($result->test_class != $test_class) {
596
        // We've moved onto a new class, so write the last classes results to a file:
597
        if (isset($xml_files[$test_class])) {
598
          file_put_contents($args['xml'] . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML());
599
          unset($xml_files[$test_class]);
600
        }
601
        $test_class = $result->test_class;
602
        if (!isset($xml_files[$test_class])) {
603
          $doc = new DomDocument('1.0');
604
          $root = $doc->createElement('testsuite');
605
          $root = $doc->appendChild($root);
606
          $xml_files[$test_class] = array('doc' => $doc, 'suite' => $root);
607
        }
608
      }
609
610
      // For convenience:
611
      $dom_document = &$xml_files[$test_class]['doc'];
612
613
      // Create the XML element for this test case:
614
      $case = $dom_document->createElement('testcase');
615
      $case->setAttribute('classname', $test_class);
616
      list($class, $name) = explode('->', $result->function, 2);
617
      $case->setAttribute('name', $name);
618
619
      // Passes get no further attention, but failures and exceptions get to add more detail:
620
      if ($result->status == 'fail') {
621
        $fail = $dom_document->createElement('failure');
622
        $fail->setAttribute('type', 'failure');
623
        $fail->setAttribute('message', $result->message_group);
624
        $text = $dom_document->createTextNode($result->message);
625
        $fail->appendChild($text);
626
        $case->appendChild($fail);
627
      }
628
      elseif ($result->status == 'exception') {
629
        // In the case of an exception the $result->function may not be a class
630
        // method so we record the full function name:
631
        $case->setAttribute('name', $result->function);
632
633
        $fail = $dom_document->createElement('error');
634
        $fail->setAttribute('type', 'exception');
635
        $fail->setAttribute('message', $result->message_group);
636
        $full_message = $result->message . "\n\nline: " . $result->line . "\nfile: " . $result->file;
637
        $text = $dom_document->createTextNode($full_message);
638
        $fail->appendChild($text);
639
        $case->appendChild($fail);
640
      }
641
      // Append the test case XML to the test suite:
642
      $xml_files[$test_class]['suite']->appendChild($case);
643
    }
644
  }
645
  // The last test case hasn't been saved to a file yet, so do that now:
646
  if (isset($xml_files[$test_class])) {
647
    file_put_contents($args['xml'] . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML());
648
    unset($xml_files[$test_class]);
649
  }
650
}
651
652
/**
653
 * Stop the test timer.
654
 */
655
function simpletest_script_reporter_timer_stop() {
656
  echo "\n";
657
  $end = timer_stop('run-tests');
658
  echo "Test run duration: " . format_interval($end['time'] / 1000);
659
  echo "\n\n";
660
}
661
662
/**
663
 * Display test results.
664
 */
665
function simpletest_script_reporter_display_results() {
666
  global $args, $test_id, $results_map;
667
668
  if ($args['verbose']) {
669
    // Report results.
670
    echo "Detailed test results\n";
671
    echo "---------------------\n";
672
673
    $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id));
674
    $test_class = '';
675
    foreach ($results as $result) {
676
      if (isset($results_map[$result->status])) {
677
        if ($result->test_class != $test_class) {
678
          // Display test class every time results are for new test class.
679
          echo "\n\n---- $result->test_class ----\n\n\n";
680
          $test_class = $result->test_class;
681
682
          // Print table header.
683
          echo "Status    Group      Filename          Line Function                            \n";
684
          echo "--------------------------------------------------------------------------------\n";
685
        }
686
687
        simpletest_script_format_result($result);
688
      }
689
    }
690
  }
691
}
692
693
/**
694
 * Format the result so that it fits within the default 80 character
695
 * terminal size.
696
 *
697
 * @param $result The result object to format.
698
 */
699
function simpletest_script_format_result($result) {
700
  global $results_map, $color;
701
702
  $summary = sprintf("%-9.9s %-10.10s %-17.17s %4.4s %-35.35s\n",
703
    $results_map[$result->status], $result->message_group, basename($result->file), $result->line, $result->function);
704
705
  simpletest_script_print($summary, simpletest_script_color_code($result->status));
706
707
  $lines = explode("\n", wordwrap(trim(strip_tags($result->message)), 76));
708
  foreach ($lines as $line) {
709
    echo "    $line\n";
710
  }
711
}
712
713
/**
714
 * Print error message prefixed with "  ERROR: " and displayed in fail color
715
 * if color output is enabled.
716
 *
717
 * @param $message The message to print.
718
 */
719
function simpletest_script_print_error($message) {
720
  simpletest_script_print("  ERROR: $message\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
721
}
722
723
/**
724
 * Print a message to the console, if color is enabled then the specified
725
 * color code will be used.
726
 *
727
 * @param $message The message to print.
728
 * @param $color_code The color code to use for coloring.
729
 */
730
function simpletest_script_print($message, $color_code) {
731
  global $args;
732 6b24a280 Assos Assos
  if (!empty($args['color'])) {
733 85ad3d82 Assos Assos
    echo "\033[" . $color_code . "m" . $message . "\033[0m";
734
  }
735
  else {
736
    echo $message;
737
  }
738
}
739
740
/**
741
 * Get the color code associated with the specified status.
742
 *
743
 * @param $status The status string to get code for.
744
 * @return Color code.
745
 */
746
function simpletest_script_color_code($status) {
747
  switch ($status) {
748
    case 'pass':
749
      return SIMPLETEST_SCRIPT_COLOR_PASS;
750
    case 'fail':
751
      return SIMPLETEST_SCRIPT_COLOR_FAIL;
752
    case 'exception':
753
      return SIMPLETEST_SCRIPT_COLOR_EXCEPTION;
754
  }
755
  return 0; // Default formatting.
756
}
757 b4adf10d Assos Assos
758
/**
759
 * Prints alternative test names.
760
 *
761
 * Searches the provided array of string values for close matches based on the
762
 * Levenshtein algorithm.
763
 *
764
 * @see http://php.net/manual/en/function.levenshtein.php
765
 *
766
 * @param string $string
767
 *   A string to test.
768
 * @param array $array
769
 *   A list of strings to search.
770
 * @param int $degree
771
 *   The matching strictness. Higher values return fewer matches. A value of
772
 *   4 means that the function will return strings from $array if the candidate
773
 *   string in $array would be identical to $string by changing 1/4 or fewer of
774
 *   its characters.
775
 */
776
function simpletest_script_print_alternatives($string, $array, $degree = 4) {
777
  $alternatives = array();
778
  foreach ($array as $item) {
779
    $lev = levenshtein($string, $item);
780
    if ($lev <= strlen($item) / $degree || FALSE !== strpos($string, $item)) {
781
      $alternatives[] = $item;
782
    }
783
  }
784
  if (!empty($alternatives)) {
785
    simpletest_script_print("  Did you mean?\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
786
    foreach ($alternatives as $alternative) {
787
      simpletest_script_print("  - $alternative\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
788
    }
789
  }
790
}