Projet

Général

Profil

Paste
Télécharger (21,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / scripts / run-tests.sh @ b4adf10d

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
// 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 b4adf10d Assos Assos
      $test_list = array();
423
      foreach ($args['test_names'] as $test_class) {
424
        if (class_exists($test_class)) {
425
          $test_list[] = $test_class;
426
        }
427
        else {
428
          $groups = simpletest_test_get_all();
429
          $all_classes = array();
430
          foreach ($groups as $group) {
431
            $all_classes = array_merge($all_classes, array_keys($group));
432
          }
433
          simpletest_script_print_error('Test class not found: ' . $test_class);
434
          simpletest_script_print_alternatives($test_class, $all_classes, 6);
435
          exit(1);
436 85ad3d82 Assos Assos
        }
437
      }
438
    }
439
    elseif ($args['file']) {
440
      $files = array();
441
      foreach ($args['test_names'] as $file) {
442
        $files[drupal_realpath($file)] = 1;
443
      }
444
445
      // Check for valid class names.
446
      foreach ($all_tests as $class_name) {
447
        $refclass = new ReflectionClass($class_name);
448
        $file = $refclass->getFileName();
449
        if (isset($files[$file])) {
450
          $test_list[] = $class_name;
451
        }
452
      }
453
    }
454
    else {
455
      // Check for valid group names and get all valid classes in group.
456
      foreach ($args['test_names'] as $group_name) {
457
        if (isset($groups[$group_name])) {
458 b4adf10d Assos Assos
          $test_list = array_merge($test_list, array_keys($groups[$group_name]));
459
        }
460
        else {
461
          simpletest_script_print_error('Test group not found: ' . $group_name);
462
          simpletest_script_print_alternatives($group_name, array_keys($groups));
463
          exit(1);
464 85ad3d82 Assos Assos
        }
465
      }
466
    }
467
  }
468
469
  if (empty($test_list)) {
470
    simpletest_script_print_error('No valid tests were specified.');
471
    exit;
472
  }
473
  return $test_list;
474
}
475
476
/**
477
 * Initialize the reporter.
478
 */
479
function simpletest_script_reporter_init() {
480
  global $args, $all_tests, $test_list, $results_map;
481
482
  $results_map = array(
483
    'pass' => 'Pass',
484
    'fail' => 'Fail',
485
    'exception' => 'Exception'
486
  );
487
488
  echo "\n";
489
  echo "Drupal test run\n";
490
  echo "---------------\n";
491
  echo "\n";
492
493
  // Tell the user about what tests are to be run.
494
  if ($args['all']) {
495
    echo "All tests will run.\n\n";
496
  }
497
  else {
498
    echo "Tests to be run:\n";
499
    foreach ($test_list as $class_name) {
500
      $info = call_user_func(array($class_name, 'getInfo'));
501
      echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n";
502
    }
503
    echo "\n";
504
  }
505
506
  echo "Test run started:\n";
507
  echo " " . format_date($_SERVER['REQUEST_TIME'], 'long') . "\n";
508
  timer_start('run-tests');
509
  echo "\n";
510
511
  echo "Test summary\n";
512
  echo "------------\n";
513
  echo "\n";
514
}
515
516
/**
517
 * Display jUnit XML test results.
518
 */
519
function simpletest_script_reporter_write_xml_results() {
520
  global $args, $test_id, $results_map;
521
522
  $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id));
523
524
  $test_class = '';
525
  $xml_files = array();
526
527
  foreach ($results as $result) {
528
    if (isset($results_map[$result->status])) {
529
      if ($result->test_class != $test_class) {
530
        // We've moved onto a new class, so write the last classes results to a file:
531
        if (isset($xml_files[$test_class])) {
532
          file_put_contents($args['xml'] . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML());
533
          unset($xml_files[$test_class]);
534
        }
535
        $test_class = $result->test_class;
536
        if (!isset($xml_files[$test_class])) {
537
          $doc = new DomDocument('1.0');
538
          $root = $doc->createElement('testsuite');
539
          $root = $doc->appendChild($root);
540
          $xml_files[$test_class] = array('doc' => $doc, 'suite' => $root);
541
        }
542
      }
543
544
      // For convenience:
545
      $dom_document = &$xml_files[$test_class]['doc'];
546
547
      // Create the XML element for this test case:
548
      $case = $dom_document->createElement('testcase');
549
      $case->setAttribute('classname', $test_class);
550
      list($class, $name) = explode('->', $result->function, 2);
551
      $case->setAttribute('name', $name);
552
553
      // Passes get no further attention, but failures and exceptions get to add more detail:
554
      if ($result->status == 'fail') {
555
        $fail = $dom_document->createElement('failure');
556
        $fail->setAttribute('type', 'failure');
557
        $fail->setAttribute('message', $result->message_group);
558
        $text = $dom_document->createTextNode($result->message);
559
        $fail->appendChild($text);
560
        $case->appendChild($fail);
561
      }
562
      elseif ($result->status == 'exception') {
563
        // In the case of an exception the $result->function may not be a class
564
        // method so we record the full function name:
565
        $case->setAttribute('name', $result->function);
566
567
        $fail = $dom_document->createElement('error');
568
        $fail->setAttribute('type', 'exception');
569
        $fail->setAttribute('message', $result->message_group);
570
        $full_message = $result->message . "\n\nline: " . $result->line . "\nfile: " . $result->file;
571
        $text = $dom_document->createTextNode($full_message);
572
        $fail->appendChild($text);
573
        $case->appendChild($fail);
574
      }
575
      // Append the test case XML to the test suite:
576
      $xml_files[$test_class]['suite']->appendChild($case);
577
    }
578
  }
579
  // The last test case hasn't been saved to a file yet, so do that now:
580
  if (isset($xml_files[$test_class])) {
581
    file_put_contents($args['xml'] . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML());
582
    unset($xml_files[$test_class]);
583
  }
584
}
585
586
/**
587
 * Stop the test timer.
588
 */
589
function simpletest_script_reporter_timer_stop() {
590
  echo "\n";
591
  $end = timer_stop('run-tests');
592
  echo "Test run duration: " . format_interval($end['time'] / 1000);
593
  echo "\n\n";
594
}
595
596
/**
597
 * Display test results.
598
 */
599
function simpletest_script_reporter_display_results() {
600
  global $args, $test_id, $results_map;
601
602
  if ($args['verbose']) {
603
    // Report results.
604
    echo "Detailed test results\n";
605
    echo "---------------------\n";
606
607
    $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id));
608
    $test_class = '';
609
    foreach ($results as $result) {
610
      if (isset($results_map[$result->status])) {
611
        if ($result->test_class != $test_class) {
612
          // Display test class every time results are for new test class.
613
          echo "\n\n---- $result->test_class ----\n\n\n";
614
          $test_class = $result->test_class;
615
616
          // Print table header.
617
          echo "Status    Group      Filename          Line Function                            \n";
618
          echo "--------------------------------------------------------------------------------\n";
619
        }
620
621
        simpletest_script_format_result($result);
622
      }
623
    }
624
  }
625
}
626
627
/**
628
 * Format the result so that it fits within the default 80 character
629
 * terminal size.
630
 *
631
 * @param $result The result object to format.
632
 */
633
function simpletest_script_format_result($result) {
634
  global $results_map, $color;
635
636
  $summary = sprintf("%-9.9s %-10.10s %-17.17s %4.4s %-35.35s\n",
637
    $results_map[$result->status], $result->message_group, basename($result->file), $result->line, $result->function);
638
639
  simpletest_script_print($summary, simpletest_script_color_code($result->status));
640
641
  $lines = explode("\n", wordwrap(trim(strip_tags($result->message)), 76));
642
  foreach ($lines as $line) {
643
    echo "    $line\n";
644
  }
645
}
646
647
/**
648
 * Print error message prefixed with "  ERROR: " and displayed in fail color
649
 * if color output is enabled.
650
 *
651
 * @param $message The message to print.
652
 */
653
function simpletest_script_print_error($message) {
654
  simpletest_script_print("  ERROR: $message\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
655
}
656
657
/**
658
 * Print a message to the console, if color is enabled then the specified
659
 * color code will be used.
660
 *
661
 * @param $message The message to print.
662
 * @param $color_code The color code to use for coloring.
663
 */
664
function simpletest_script_print($message, $color_code) {
665
  global $args;
666
  if ($args['color']) {
667
    echo "\033[" . $color_code . "m" . $message . "\033[0m";
668
  }
669
  else {
670
    echo $message;
671
  }
672
}
673
674
/**
675
 * Get the color code associated with the specified status.
676
 *
677
 * @param $status The status string to get code for.
678
 * @return Color code.
679
 */
680
function simpletest_script_color_code($status) {
681
  switch ($status) {
682
    case 'pass':
683
      return SIMPLETEST_SCRIPT_COLOR_PASS;
684
    case 'fail':
685
      return SIMPLETEST_SCRIPT_COLOR_FAIL;
686
    case 'exception':
687
      return SIMPLETEST_SCRIPT_COLOR_EXCEPTION;
688
  }
689
  return 0; // Default formatting.
690
}
691 b4adf10d Assos Assos
692
/**
693
 * Prints alternative test names.
694
 *
695
 * Searches the provided array of string values for close matches based on the
696
 * Levenshtein algorithm.
697
 *
698
 * @see http://php.net/manual/en/function.levenshtein.php
699
 *
700
 * @param string $string
701
 *   A string to test.
702
 * @param array $array
703
 *   A list of strings to search.
704
 * @param int $degree
705
 *   The matching strictness. Higher values return fewer matches. A value of
706
 *   4 means that the function will return strings from $array if the candidate
707
 *   string in $array would be identical to $string by changing 1/4 or fewer of
708
 *   its characters.
709
 */
710
function simpletest_script_print_alternatives($string, $array, $degree = 4) {
711
  $alternatives = array();
712
  foreach ($array as $item) {
713
    $lev = levenshtein($string, $item);
714
    if ($lev <= strlen($item) / $degree || FALSE !== strpos($string, $item)) {
715
      $alternatives[] = $item;
716
    }
717
  }
718
  if (!empty($alternatives)) {
719
    simpletest_script_print("  Did you mean?\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
720
    foreach ($alternatives as $alternative) {
721
      simpletest_script_print("  - $alternative\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
722
    }
723
  }
724
}