Projet

Général

Profil

Paste
Télécharger (20 ko) Statistiques
| Branche: | Révision:

root / htmltest / scripts / run-tests.sh @ 6d91f38e

1
<?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
// Set defaults and get overrides.
12
list($args, $count) = simpletest_script_parse_args();
13

    
14
if ($args['help'] || $count == 0) {
15
  simpletest_script_help();
16
  exit;
17
}
18

    
19
if ($args['execute-test']) {
20
  // Masquerade as Apache for running tests.
21
  simpletest_script_init("Apache");
22
  simpletest_script_run_one_test($args['test-id'], $args['execute-test']);
23
}
24
else {
25
  // Run administrative functions as CLI.
26
  simpletest_script_init(NULL);
27
}
28

    
29
// Bootstrap to perform initial validation or other operations.
30
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
31
if (!module_exists('simpletest')) {
32
  simpletest_script_print_error("The simpletest module must be enabled before this script can run.");
33
  exit;
34
}
35

    
36
if ($args['clean']) {
37
  // Clean up left-over times and directories.
38
  simpletest_clean_environment();
39
  echo "\nEnvironment cleaned.\n";
40

    
41
  // Get the status messages and print them.
42
  $messages = array_pop(drupal_get_messages('status'));
43
  foreach ($messages as $text) {
44
    echo " - " . $text . "\n";
45
  }
46
  exit;
47
}
48

    
49
// Load SimpleTest files.
50
$groups = simpletest_test_get_all();
51
$all_tests = array();
52
foreach ($groups as $group => $tests) {
53
  $all_tests = array_merge($all_tests, array_keys($tests));
54
}
55
$test_list = array();
56

    
57
if ($args['list']) {
58
  // Display all available tests.
59
  echo "\nAvailable test groups & classes\n";
60
  echo   "-------------------------------\n\n";
61
  foreach ($groups as $group => $tests) {
62
    echo $group . "\n";
63
    foreach ($tests as $class => $info) {
64
      echo " - " . $info['name'] . ' (' . $class . ')' . "\n";
65
    }
66
  }
67
  exit;
68
}
69

    
70
$test_list = simpletest_script_get_test_list();
71

    
72
// Try to allocate unlimited time to run the tests.
73
drupal_set_time_limit(0);
74

    
75
simpletest_script_reporter_init();
76

    
77
// Setup database for test results.
78
$test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute();
79

    
80
// Execute tests.
81
simpletest_script_execute_batch($test_id, simpletest_script_get_test_list());
82

    
83
// Retrieve the last database prefix used for testing and the last test class
84
// that was run from. Use the information to read the lgo file in case any
85
// fatal errors caused the test to crash.
86
list($last_prefix, $last_test_class) = simpletest_last_test_get($test_id);
87
simpletest_log_read($test_id, $last_prefix, $last_test_class);
88

    
89
// Stop the timer.
90
simpletest_script_reporter_timer_stop();
91

    
92
// Display results before database is cleared.
93
simpletest_script_reporter_display_results();
94

    
95
if ($args['xml']) {
96
  simpletest_script_reporter_write_xml_results();
97
}
98

    
99
// Cleanup our test results.
100
simpletest_clean_results_table($test_id);
101

    
102
// Test complete, exit.
103
exit;
104

    
105
/**
106
 * Print help text.
107
 */
108
function simpletest_script_help() {
109
  global $args;
110

    
111
  echo <<<EOF
112

    
113
Run Drupal tests from the shell.
114

    
115
Usage:        {$args['script']} [OPTIONS] <tests>
116
Example:      {$args['script']} Profile
117

    
118
All arguments are long options.
119

    
120
  --help      Print this page.
121

    
122
  --list      Display all available test groups.
123

    
124
  --clean     Cleans up database tables or directories from previous, failed,
125
              tests and then exits (no tests are run).
126

    
127
  --url       Immediately precedes a URL to set the host and path. You will
128
              need this parameter if Drupal is in a subdirectory on your
129
              localhost and you have not set \$base_url in settings.php. Tests
130
              can be run under SSL by including https:// in the URL.
131

    
132
  --php       The absolute path to the PHP executable. Usually not needed.
133

    
134
  --concurrency [num]
135

    
136
              Run tests in parallel, up to [num] tests at a time.
137

    
138
  --all       Run all available tests.
139

    
140
  --class     Run tests identified by specific class names, instead of group names.
141

    
142
  --file      Run tests identified by specific file names, instead of group names.
143
              Specify the path and the extension (i.e. 'modules/user/user.test').
144

    
145
  --xml       <path>
146

    
147
              If provided, test results will be written as xml files to this path.
148

    
149
  --color     Output text format results with color highlighting.
150

    
151
  --verbose   Output detailed assertion messages in addition to summary.
152

    
153
  <test1>[,<test2>[,<test3> ...]]
154

    
155
              One or more tests to be run. By default, these are interpreted
156
              as the names of test groups as shown at
157
              ?q=admin/config/development/testing.
158
              These group names typically correspond to module names like "User"
159
              or "Profile" or "System", but there is also a group "XML-RPC".
160
              If --class is specified then these are interpreted as the names of
161
              specific test classes whose test methods will be run. Tests must
162
              be separated by commas. Ignored if --all is specified.
163

    
164
To run this script you will normally invoke it from the root directory of your
165
Drupal installation as the webserver user (differs per configuration), or root:
166

    
167
sudo -u [wwwrun|www-data|etc] php ./scripts/{$args['script']}
168
  --url http://example.com/ --all
169
sudo -u [wwwrun|www-data|etc] php ./scripts/{$args['script']}
170
  --url http://example.com/ --class BlockTestCase
171
\n
172
EOF;
173
}
174

    
175
/**
176
 * Parse execution argument and ensure that all are valid.
177
 *
178
 * @return The list of arguments.
179
 */
180
function simpletest_script_parse_args() {
181
  // Set default values.
182
  $args = array(
183
    'script' => '',
184
    'help' => FALSE,
185
    'list' => FALSE,
186
    'clean' => FALSE,
187
    'url' => '',
188
    'php' => '',
189
    'concurrency' => 1,
190
    'all' => FALSE,
191
    'class' => FALSE,
192
    'file' => FALSE,
193
    'color' => FALSE,
194
    'verbose' => FALSE,
195
    'test_names' => array(),
196
    // Used internally.
197
    'test-id' => 0,
198
    'execute-test' => '',
199
    'xml' => '',
200
  );
201

    
202
  // Override with set values.
203
  $args['script'] = basename(array_shift($_SERVER['argv']));
204

    
205
  $count = 0;
206
  while ($arg = array_shift($_SERVER['argv'])) {
207
    if (preg_match('/--(\S+)/', $arg, $matches)) {
208
      // Argument found.
209
      if (array_key_exists($matches[1], $args)) {
210
        // Argument found in list.
211
        $previous_arg = $matches[1];
212
        if (is_bool($args[$previous_arg])) {
213
          $args[$matches[1]] = TRUE;
214
        }
215
        else {
216
          $args[$matches[1]] = array_shift($_SERVER['argv']);
217
        }
218
        // Clear extraneous values.
219
        $args['test_names'] = array();
220
        $count++;
221
      }
222
      else {
223
        // Argument not found in list.
224
        simpletest_script_print_error("Unknown argument '$arg'.");
225
        exit;
226
      }
227
    }
228
    else {
229
      // Values found without an argument should be test names.
230
      $args['test_names'] += explode(',', $arg);
231
      $count++;
232
    }
233
  }
234

    
235
  // Validate the concurrency argument
236
  if (!is_numeric($args['concurrency']) || $args['concurrency'] <= 0) {
237
    simpletest_script_print_error("--concurrency must be a strictly positive integer.");
238
    exit;
239
  }
240

    
241
  return array($args, $count);
242
}
243

    
244
/**
245
 * Initialize script variables and perform general setup requirements.
246
 */
247
function simpletest_script_init($server_software) {
248
  global $args, $php;
249

    
250
  $host = 'localhost';
251
  $path = '';
252
  // Determine location of php command automatically, unless a command line argument is supplied.
253
  if (!empty($args['php'])) {
254
    $php = $args['php'];
255
  }
256
  elseif ($php_env = getenv('_')) {
257
    // '_' is an environment variable set by the shell. It contains the command that was executed.
258
    $php = $php_env;
259
  }
260
  elseif ($sudo = getenv('SUDO_COMMAND')) {
261
    // 'SUDO_COMMAND' is an environment variable set by the sudo program.
262
    // Extract only the PHP interpreter, not the rest of the command.
263
    list($php, ) = explode(' ', $sudo, 2);
264
  }
265
  else {
266
    simpletest_script_print_error('Unable to automatically determine the path to the PHP interpreter. Supply the --php command line argument.');
267
    simpletest_script_help();
268
    exit();
269
  }
270

    
271
  // Get URL from arguments.
272
  if (!empty($args['url'])) {
273
    $parsed_url = parse_url($args['url']);
274
    $host = $parsed_url['host'] . (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
275
    $path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
276

    
277
    // If the passed URL schema is 'https' then setup the $_SERVER variables
278
    // properly so that testing will run under HTTPS.
279
    if ($parsed_url['scheme'] == 'https') {
280
      $_SERVER['HTTPS'] = 'on';
281
    }
282
  }
283

    
284
  $_SERVER['HTTP_HOST'] = $host;
285
  $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
286
  $_SERVER['SERVER_ADDR'] = '127.0.0.1';
287
  $_SERVER['SERVER_SOFTWARE'] = $server_software;
288
  $_SERVER['SERVER_NAME'] = 'localhost';
289
  $_SERVER['REQUEST_URI'] = $path .'/';
290
  $_SERVER['REQUEST_METHOD'] = 'GET';
291
  $_SERVER['SCRIPT_NAME'] = $path .'/index.php';
292
  $_SERVER['PHP_SELF'] = $path .'/index.php';
293
  $_SERVER['HTTP_USER_AGENT'] = 'Drupal command line';
294

    
295
  if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
296
    // Ensure that any and all environment variables are changed to https://.
297
    foreach ($_SERVER as $key => $value) {
298
      $_SERVER[$key] = str_replace('http://', 'https://', $_SERVER[$key]);
299
    }
300
  }
301

    
302
  chdir(realpath(dirname(__FILE__) . '/..'));
303
  define('DRUPAL_ROOT', getcwd());
304
  require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
305
}
306

    
307
/**
308
 * Execute a batch of tests.
309
 */
310
function simpletest_script_execute_batch($test_id, $test_classes) {
311
  global $args;
312

    
313
  // Multi-process execution.
314
  $children = array();
315
  while (!empty($test_classes) || !empty($children)) {
316
    while (count($children) < $args['concurrency']) {
317
      if (empty($test_classes)) {
318
        break;
319
      }
320

    
321
      // Fork a child process.
322
      $test_class = array_shift($test_classes);
323
      $command = simpletest_script_command($test_id, $test_class);
324
      $process = proc_open($command, array(), $pipes, NULL, NULL, array('bypass_shell' => TRUE));
325

    
326
      if (!is_resource($process)) {
327
        echo "Unable to fork test process. Aborting.\n";
328
        exit;
329
      }
330

    
331
      // Register our new child.
332
      $children[] = array(
333
        'process' => $process,
334
        'class' => $test_class,
335
        'pipes' => $pipes,
336
      );
337
    }
338

    
339
    // Wait for children every 200ms.
340
    usleep(200000);
341

    
342
    // Check if some children finished.
343
    foreach ($children as $cid => $child) {
344
      $status = proc_get_status($child['process']);
345
      if (empty($status['running'])) {
346
        // The child exited, unregister it.
347
        proc_close($child['process']);
348
        if ($status['exitcode']) {
349
          echo 'FATAL ' . $test_class . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').' . "\n";
350
        }
351
        unset($children[$cid]);
352
      }
353
    }
354
  }
355
}
356

    
357
/**
358
 * Bootstrap Drupal and run a single test.
359
 */
360
function simpletest_script_run_one_test($test_id, $test_class) {
361
  try {
362
    // Bootstrap Drupal.
363
    drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
364

    
365
    simpletest_classloader_register();
366

    
367
    $test = new $test_class($test_id);
368
    $test->run();
369
    $info = $test->getInfo();
370

    
371
    $had_fails = (isset($test->results['#fail']) && $test->results['#fail'] > 0);
372
    $had_exceptions = (isset($test->results['#exception']) && $test->results['#exception'] > 0);
373
    $status = ($had_fails || $had_exceptions ? 'fail' : 'pass');
374
    simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->results) . "\n", simpletest_script_color_code($status));
375

    
376
    // Finished, kill this runner.
377
    exit(0);
378
  }
379
  catch (Exception $e) {
380
    echo (string) $e;
381
    exit(1);
382
  }
383
}
384

    
385
/**
386
 * Return a command used to run a test in a separate process.
387
 *
388
 * @param $test_id
389
 *  The current test ID.
390
 * @param $test_class
391
 *  The name of the test class to run.
392
 */
393
function simpletest_script_command($test_id, $test_class) {
394
  global $args, $php;
395

    
396
  $command = escapeshellarg($php) . ' ' . escapeshellarg('./scripts/' . $args['script']) . ' --url ' . escapeshellarg($args['url']);
397
  if ($args['color']) {
398
    $command .= ' --color';
399
  }
400
  $command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test " . escapeshellarg($test_class);
401
  return $command;
402
}
403

    
404
/**
405
 * Get list of tests based on arguments. If --all specified then
406
 * returns all available tests, otherwise reads list of tests.
407
 *
408
 * Will print error and exit if no valid tests were found.
409
 *
410
 * @return List of tests.
411
 */
412
function simpletest_script_get_test_list() {
413
  global $args, $all_tests, $groups;
414

    
415
  $test_list = array();
416
  if ($args['all']) {
417
    $test_list = $all_tests;
418
  }
419
  else {
420
    if ($args['class']) {
421
      // Check for valid class names.
422
      foreach ($args['test_names'] as $class_name) {
423
        if (in_array($class_name, $all_tests)) {
424
          $test_list[] = $class_name;
425
        }
426
      }
427
    }
428
    elseif ($args['file']) {
429
      $files = array();
430
      foreach ($args['test_names'] as $file) {
431
        $files[drupal_realpath($file)] = 1;
432
      }
433

    
434
      // Check for valid class names.
435
      foreach ($all_tests as $class_name) {
436
        $refclass = new ReflectionClass($class_name);
437
        $file = $refclass->getFileName();
438
        if (isset($files[$file])) {
439
          $test_list[] = $class_name;
440
        }
441
      }
442
    }
443
    else {
444
      // Check for valid group names and get all valid classes in group.
445
      foreach ($args['test_names'] as $group_name) {
446
        if (isset($groups[$group_name])) {
447
          foreach ($groups[$group_name] as $class_name => $info) {
448
            $test_list[] = $class_name;
449
          }
450
        }
451
      }
452
    }
453
  }
454

    
455
  if (empty($test_list)) {
456
    simpletest_script_print_error('No valid tests were specified.');
457
    exit;
458
  }
459
  return $test_list;
460
}
461

    
462
/**
463
 * Initialize the reporter.
464
 */
465
function simpletest_script_reporter_init() {
466
  global $args, $all_tests, $test_list, $results_map;
467

    
468
  $results_map = array(
469
    'pass' => 'Pass',
470
    'fail' => 'Fail',
471
    'exception' => 'Exception'
472
  );
473

    
474
  echo "\n";
475
  echo "Drupal test run\n";
476
  echo "---------------\n";
477
  echo "\n";
478

    
479
  // Tell the user about what tests are to be run.
480
  if ($args['all']) {
481
    echo "All tests will run.\n\n";
482
  }
483
  else {
484
    echo "Tests to be run:\n";
485
    foreach ($test_list as $class_name) {
486
      $info = call_user_func(array($class_name, 'getInfo'));
487
      echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n";
488
    }
489
    echo "\n";
490
  }
491

    
492
  echo "Test run started:\n";
493
  echo " " . format_date($_SERVER['REQUEST_TIME'], 'long') . "\n";
494
  timer_start('run-tests');
495
  echo "\n";
496

    
497
  echo "Test summary\n";
498
  echo "------------\n";
499
  echo "\n";
500
}
501

    
502
/**
503
 * Display jUnit XML test results.
504
 */
505
function simpletest_script_reporter_write_xml_results() {
506
  global $args, $test_id, $results_map;
507

    
508
  $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id));
509

    
510
  $test_class = '';
511
  $xml_files = array();
512

    
513
  foreach ($results as $result) {
514
    if (isset($results_map[$result->status])) {
515
      if ($result->test_class != $test_class) {
516
        // We've moved onto a new class, so write the last classes results to a file:
517
        if (isset($xml_files[$test_class])) {
518
          file_put_contents($args['xml'] . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML());
519
          unset($xml_files[$test_class]);
520
        }
521
        $test_class = $result->test_class;
522
        if (!isset($xml_files[$test_class])) {
523
          $doc = new DomDocument('1.0');
524
          $root = $doc->createElement('testsuite');
525
          $root = $doc->appendChild($root);
526
          $xml_files[$test_class] = array('doc' => $doc, 'suite' => $root);
527
        }
528
      }
529

    
530
      // For convenience:
531
      $dom_document = &$xml_files[$test_class]['doc'];
532

    
533
      // Create the XML element for this test case:
534
      $case = $dom_document->createElement('testcase');
535
      $case->setAttribute('classname', $test_class);
536
      list($class, $name) = explode('->', $result->function, 2);
537
      $case->setAttribute('name', $name);
538

    
539
      // Passes get no further attention, but failures and exceptions get to add more detail:
540
      if ($result->status == 'fail') {
541
        $fail = $dom_document->createElement('failure');
542
        $fail->setAttribute('type', 'failure');
543
        $fail->setAttribute('message', $result->message_group);
544
        $text = $dom_document->createTextNode($result->message);
545
        $fail->appendChild($text);
546
        $case->appendChild($fail);
547
      }
548
      elseif ($result->status == 'exception') {
549
        // In the case of an exception the $result->function may not be a class
550
        // method so we record the full function name:
551
        $case->setAttribute('name', $result->function);
552

    
553
        $fail = $dom_document->createElement('error');
554
        $fail->setAttribute('type', 'exception');
555
        $fail->setAttribute('message', $result->message_group);
556
        $full_message = $result->message . "\n\nline: " . $result->line . "\nfile: " . $result->file;
557
        $text = $dom_document->createTextNode($full_message);
558
        $fail->appendChild($text);
559
        $case->appendChild($fail);
560
      }
561
      // Append the test case XML to the test suite:
562
      $xml_files[$test_class]['suite']->appendChild($case);
563
    }
564
  }
565
  // The last test case hasn't been saved to a file yet, so do that now:
566
  if (isset($xml_files[$test_class])) {
567
    file_put_contents($args['xml'] . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML());
568
    unset($xml_files[$test_class]);
569
  }
570
}
571

    
572
/**
573
 * Stop the test timer.
574
 */
575
function simpletest_script_reporter_timer_stop() {
576
  echo "\n";
577
  $end = timer_stop('run-tests');
578
  echo "Test run duration: " . format_interval($end['time'] / 1000);
579
  echo "\n\n";
580
}
581

    
582
/**
583
 * Display test results.
584
 */
585
function simpletest_script_reporter_display_results() {
586
  global $args, $test_id, $results_map;
587

    
588
  if ($args['verbose']) {
589
    // Report results.
590
    echo "Detailed test results\n";
591
    echo "---------------------\n";
592

    
593
    $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id));
594
    $test_class = '';
595
    foreach ($results as $result) {
596
      if (isset($results_map[$result->status])) {
597
        if ($result->test_class != $test_class) {
598
          // Display test class every time results are for new test class.
599
          echo "\n\n---- $result->test_class ----\n\n\n";
600
          $test_class = $result->test_class;
601

    
602
          // Print table header.
603
          echo "Status    Group      Filename          Line Function                            \n";
604
          echo "--------------------------------------------------------------------------------\n";
605
        }
606

    
607
        simpletest_script_format_result($result);
608
      }
609
    }
610
  }
611
}
612

    
613
/**
614
 * Format the result so that it fits within the default 80 character
615
 * terminal size.
616
 *
617
 * @param $result The result object to format.
618
 */
619
function simpletest_script_format_result($result) {
620
  global $results_map, $color;
621

    
622
  $summary = sprintf("%-9.9s %-10.10s %-17.17s %4.4s %-35.35s\n",
623
    $results_map[$result->status], $result->message_group, basename($result->file), $result->line, $result->function);
624

    
625
  simpletest_script_print($summary, simpletest_script_color_code($result->status));
626

    
627
  $lines = explode("\n", wordwrap(trim(strip_tags($result->message)), 76));
628
  foreach ($lines as $line) {
629
    echo "    $line\n";
630
  }
631
}
632

    
633
/**
634
 * Print error message prefixed with "  ERROR: " and displayed in fail color
635
 * if color output is enabled.
636
 *
637
 * @param $message The message to print.
638
 */
639
function simpletest_script_print_error($message) {
640
  simpletest_script_print("  ERROR: $message\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
641
}
642

    
643
/**
644
 * Print a message to the console, if color is enabled then the specified
645
 * color code will be used.
646
 *
647
 * @param $message The message to print.
648
 * @param $color_code The color code to use for coloring.
649
 */
650
function simpletest_script_print($message, $color_code) {
651
  global $args;
652
  if ($args['color']) {
653
    echo "\033[" . $color_code . "m" . $message . "\033[0m";
654
  }
655
  else {
656
    echo $message;
657
  }
658
}
659

    
660
/**
661
 * Get the color code associated with the specified status.
662
 *
663
 * @param $status The status string to get code for.
664
 * @return Color code.
665
 */
666
function simpletest_script_color_code($status) {
667
  switch ($status) {
668
    case 'pass':
669
      return SIMPLETEST_SCRIPT_COLOR_PASS;
670
    case 'fail':
671
      return SIMPLETEST_SCRIPT_COLOR_FAIL;
672
    case 'exception':
673
      return SIMPLETEST_SCRIPT_COLOR_EXCEPTION;
674
  }
675
  return 0; // Default formatting.
676
}