Subversion Repositories web.creative

Rev

Details | Last modification | View Log

Rev Author Line No. Line
1 mjordaan 1
<?php
2
/*
3
 * This file is part of PHPUnit.
4
 *
5
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
 
11
/**
12
 * Test helpers.
13
 */
14
class PHPUnit_Util_Test
15
{
16
    const REGEX_DATA_PROVIDER      = '/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/';
17
    const REGEX_TEST_WITH          = '/@testWith\s+/';
18
    const REGEX_EXPECTED_EXCEPTION = '(@expectedException\s+([:.\w\\\\x7f-\xff]+)(?:[\t ]+(\S*))?(?:[\t ]+(\S*))?\s*$)m';
19
    const REGEX_REQUIRES_VERSION   = '/@requires\s+(?P<name>PHP(?:Unit)?)\s+(?P<operator>[<>=!]{0,2})\s*(?P<version>[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m';
20
    const REGEX_REQUIRES_OS        = '/@requires\s+OS\s+(?P<value>.+?)[ \t]*\r?$/m';
21
    const REGEX_REQUIRES           = '/@requires\s+(?P<name>function|extension)\s+(?P<value>([^ ]+?))\s*(?P<operator>[<>=!]{0,2})\s*(?P<version>[\d\.-]+[\d\.]?)?[ \t]*\r?$/m';
22
 
23
    const UNKNOWN = -1;
24
    const SMALL   = 0;
25
    const MEDIUM  = 1;
26
    const LARGE   = 2;
27
 
28
    private static $annotationCache = [];
29
 
30
    private static $hookMethods = [];
31
 
32
    /**
33
     * @param PHPUnit_Framework_Test $test
34
     * @param bool                   $asString
35
     *
36
     * @return mixed
37
     */
38
    public static function describe(PHPUnit_Framework_Test $test, $asString = true)
39
    {
40
        if ($asString) {
41
            if ($test instanceof PHPUnit_Framework_SelfDescribing) {
42
                return $test->toString();
43
            } else {
44
                return get_class($test);
45
            }
46
        } else {
47
            if ($test instanceof PHPUnit_Framework_TestCase) {
48
                return [
49
                  get_class($test), $test->getName()
50
                ];
51
            } elseif ($test instanceof PHPUnit_Framework_SelfDescribing) {
52
                return ['', $test->toString()];
53
            } else {
54
                return ['', get_class($test)];
55
            }
56
        }
57
    }
58
 
59
    /**
60
     * @param string $className
61
     * @param string $methodName
62
     *
63
     * @return array|bool
64
     *
65
     * @throws PHPUnit_Framework_CodeCoverageException
66
     */
67
    public static function getLinesToBeCovered($className, $methodName)
68
    {
69
        $annotations = self::parseTestMethodAnnotations(
70
            $className,
71
            $methodName
72
        );
73
 
74
        if (isset($annotations['class']['coversNothing']) || isset($annotations['method']['coversNothing'])) {
75
            return false;
76
        }
77
 
78
        return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers');
79
    }
80
 
81
    /**
82
     * Returns lines of code specified with the @uses annotation.
83
     *
84
     * @param string $className
85
     * @param string $methodName
86
     *
87
     * @return array
88
     */
89
    public static function getLinesToBeUsed($className, $methodName)
90
    {
91
        return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses');
92
    }
93
 
94
    /**
95
     * @param string $className
96
     * @param string $methodName
97
     * @param string $mode
98
     *
99
     * @return array
100
     *
101
     * @throws PHPUnit_Framework_CodeCoverageException
102
     */
103
    private static function getLinesToBeCoveredOrUsed($className, $methodName, $mode)
104
    {
105
        $annotations = self::parseTestMethodAnnotations(
106
            $className,
107
            $methodName
108
        );
109
 
110
        $classShortcut = null;
111
 
112
        if (!empty($annotations['class'][$mode . 'DefaultClass'])) {
113
            if (count($annotations['class'][$mode . 'DefaultClass']) > 1) {
114
                throw new PHPUnit_Framework_CodeCoverageException(
115
                    sprintf(
116
                        'More than one @%sClass annotation in class or interface "%s".',
117
                        $mode,
118
                        $className
119
                    )
120
                );
121
            }
122
 
123
            $classShortcut = $annotations['class'][$mode . 'DefaultClass'][0];
124
        }
125
 
126
        $list = [];
127
 
128
        if (isset($annotations['class'][$mode])) {
129
            $list = $annotations['class'][$mode];
130
        }
131
 
132
        if (isset($annotations['method'][$mode])) {
133
            $list = array_merge($list, $annotations['method'][$mode]);
134
        }
135
 
136
        $codeList = [];
137
 
138
        foreach (array_unique($list) as $element) {
139
            if ($classShortcut && strncmp($element, '::', 2) === 0) {
140
                $element = $classShortcut . $element;
141
            }
142
 
143
            $element = preg_replace('/[\s()]+$/', '', $element);
144
            $element = explode(' ', $element);
145
            $element = $element[0];
146
 
147
            $codeList = array_merge(
148
                $codeList,
149
                self::resolveElementToReflectionObjects($element)
150
            );
151
        }
152
 
153
        return self::resolveReflectionObjectsToLines($codeList);
154
    }
155
 
156
    /**
157
     * Returns the requirements for a test.
158
     *
159
     * @param string $className
160
     * @param string $methodName
161
     *
162
     * @return array
163
     */
164
    public static function getRequirements($className, $methodName)
165
    {
166
        $reflector  = new ReflectionClass($className);
167
        $docComment = $reflector->getDocComment();
168
        $reflector  = new ReflectionMethod($className, $methodName);
169
        $docComment .= "\n" . $reflector->getDocComment();
170
        $requires   = [];
171
 
172
        if ($count = preg_match_all(self::REGEX_REQUIRES_OS, $docComment, $matches)) {
173
            $requires['OS'] = sprintf(
174
                '/%s/i',
175
                addcslashes($matches['value'][$count - 1], '/')
176
            );
177
        }
178
        if ($count = preg_match_all(self::REGEX_REQUIRES_VERSION, $docComment, $matches)) {
179
            for ($i = 0; $i < $count; $i++) {
180
                $requires[$matches['name'][$i]] = [
181
                    'version'  => $matches['version'][$i],
182
                    'operator' => $matches['operator'][$i]
183
                ];
184
            }
185
        }
186
 
187
        // https://bugs.php.net/bug.php?id=63055
188
        $matches = [];
189
 
190
        if ($count = preg_match_all(self::REGEX_REQUIRES, $docComment, $matches)) {
191
            for ($i = 0; $i < $count; $i++) {
192
                $name = $matches['name'][$i] . 's';
193
                if (!isset($requires[$name])) {
194
                    $requires[$name] = [];
195
                }
196
                $requires[$name][] = $matches['value'][$i];
197
                if (empty($matches['version'][$i]) || $name != 'extensions') {
198
                    continue;
199
                }
200
                $requires['extension_versions'][$matches['value'][$i]] = [
201
                    'version'  => $matches['version'][$i],
202
                    'operator' => $matches['operator'][$i]
203
                ];
204
            }
205
        }
206
 
207
        return $requires;
208
    }
209
 
210
    /**
211
     * Returns the missing requirements for a test.
212
     *
213
     * @param string $className
214
     * @param string $methodName
215
     *
216
     * @return array
217
     */
218
    public static function getMissingRequirements($className, $methodName)
219
    {
220
        $required = static::getRequirements($className, $methodName);
221
        $missing  = [];
222
 
223
        $operator = empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator'];
224
        if (!empty($required['PHP']) && !version_compare(PHP_VERSION, $required['PHP']['version'], $operator)) {
225
            $missing[] = sprintf('PHP %s %s is required.', $operator, $required['PHP']['version']);
226
        }
227
 
228
        if (!empty($required['PHPUnit'])) {
229
            $phpunitVersion = PHPUnit_Runner_Version::id();
230
 
231
            $operator = empty($required['PHPUnit']['operator']) ? '>=' : $required['PHPUnit']['operator'];
232
            if (!version_compare($phpunitVersion, $required['PHPUnit']['version'], $operator)) {
233
                $missing[] = sprintf('PHPUnit %s %s is required.', $operator, $required['PHPUnit']['version']);
234
            }
235
        }
236
 
237
        if (!empty($required['OS']) && !preg_match($required['OS'], PHP_OS)) {
238
            $missing[] = sprintf('Operating system matching %s is required.', $required['OS']);
239
        }
240
 
241
        if (!empty($required['functions'])) {
242
            foreach ($required['functions'] as $function) {
243
                $pieces = explode('::', $function);
244
                if (2 === count($pieces) && method_exists($pieces[0], $pieces[1])) {
245
                    continue;
246
                }
247
                if (function_exists($function)) {
248
                    continue;
249
                }
250
                $missing[] = sprintf('Function %s is required.', $function);
251
            }
252
        }
253
 
254
        if (!empty($required['extensions'])) {
255
            foreach ($required['extensions'] as $extension) {
256
                if (isset($required['extension_versions'][$extension])) {
257
                    continue;
258
                }
259
                if (!extension_loaded($extension)) {
260
                    $missing[] = sprintf('Extension %s is required.', $extension);
261
                }
262
            }
263
        }
264
 
265
        if (!empty($required['extension_versions'])) {
266
            foreach ($required['extension_versions'] as $extension => $required) {
267
                $actualVersion = phpversion($extension);
268
 
269
                $operator = empty($required['operator']) ? '>=' : $required['operator'];
270
                if (false === $actualVersion || !version_compare($actualVersion, $required['version'], $operator)) {
271
                    $missing[] = sprintf('Extension %s %s %s is required.', $extension, $operator, $required['version']);
272
                }
273
            }
274
        }
275
 
276
        return $missing;
277
    }
278
 
279
    /**
280
     * Returns the expected exception for a test.
281
     *
282
     * @param string $className
283
     * @param string $methodName
284
     *
285
     * @return array
286
     */
287
    public static function getExpectedException($className, $methodName)
288
    {
289
        $reflector  = new ReflectionMethod($className, $methodName);
290
        $docComment = $reflector->getDocComment();
291
        $docComment = substr($docComment, 3, -2);
292
 
293
        if (preg_match(self::REGEX_EXPECTED_EXCEPTION, $docComment, $matches)) {
294
            $annotations = self::parseTestMethodAnnotations(
295
                $className,
296
                $methodName
297
            );
298
 
299
            $class         = $matches[1];
300
            $code          = null;
301
            $message       = '';
302
            $messageRegExp = '';
303
 
304
            if (isset($matches[2])) {
305
                $message = trim($matches[2]);
306
            } elseif (isset($annotations['method']['expectedExceptionMessage'])) {
307
                $message = self::parseAnnotationContent(
308
                    $annotations['method']['expectedExceptionMessage'][0]
309
                );
310
            }
311
 
312
            if (isset($annotations['method']['expectedExceptionMessageRegExp'])) {
313
                $messageRegExp = self::parseAnnotationContent(
314
                    $annotations['method']['expectedExceptionMessageRegExp'][0]
315
                );
316
            }
317
 
318
            if (isset($matches[3])) {
319
                $code = $matches[3];
320
            } elseif (isset($annotations['method']['expectedExceptionCode'])) {
321
                $code = self::parseAnnotationContent(
322
                    $annotations['method']['expectedExceptionCode'][0]
323
                );
324
            }
325
 
326
            if (is_numeric($code)) {
327
                $code = (int) $code;
328
            } elseif (is_string($code) && defined($code)) {
329
                $code = (int) constant($code);
330
            }
331
 
332
            return [
333
              'class' => $class, 'code' => $code, 'message' => $message, 'message_regex' => $messageRegExp
334
            ];
335
        }
336
 
337
        return false;
338
    }
339
 
340
    /**
341
     * Parse annotation content to use constant/class constant values
342
     *
343
     * Constants are specified using a starting '@'. For example: @ClassName::CONST_NAME
344
     *
345
     * If the constant is not found the string is used as is to ensure maximum BC.
346
     *
347
     * @param string $message
348
     *
349
     * @return string
350
     */
351
    private static function parseAnnotationContent($message)
352
    {
353
        if (strpos($message, '::') !== false && count(explode('::', $message)) == 2) {
354
            if (defined($message)) {
355
                $message = constant($message);
356
            }
357
        }
358
 
359
        return $message;
360
    }
361
 
362
    /**
363
     * Returns the provided data for a method.
364
     *
365
     * @param string $className
366
     * @param string $methodName
367
     *
368
     * @return array When a data provider is specified and exists
369
     *         null  When no data provider is specified
370
     *
371
     * @throws PHPUnit_Framework_Exception
372
     */
373
    public static function getProvidedData($className, $methodName)
374
    {
375
        $reflector  = new ReflectionMethod($className, $methodName);
376
        $docComment = $reflector->getDocComment();
377
 
378
        $data = self::getDataFromDataProviderAnnotation($docComment, $className, $methodName);
379
 
380
        if ($data === null) {
381
            $data = self::getDataFromTestWithAnnotation($docComment);
382
        }
383
 
384
        if (is_array($data) && empty($data)) {
385
            throw new PHPUnit_Framework_SkippedTestError;
386
        }
387
 
388
        if ($data !== null) {
389
            foreach ($data as $key => $value) {
390
                if (!is_array($value)) {
391
                    throw new PHPUnit_Framework_Exception(
392
                        sprintf(
393
                            'Data set %s is invalid.',
394
                            is_int($key) ? '#' . $key : '"' . $key . '"'
395
                        )
396
                    );
397
                }
398
            }
399
        }
400
 
401
        return $data;
402
    }
403
 
404
    /**
405
     * Returns the provided data for a method.
406
     *
407
     * @param string $docComment
408
     * @param string $className
409
     * @param string $methodName
410
     *
411
     * @return array|Iterator when a data provider is specified and exists
412
     *                        null           when no data provider is specified
413
     *
414
     * @throws PHPUnit_Framework_Exception
415
     */
416
    private static function getDataFromDataProviderAnnotation($docComment, $className, $methodName)
417
    {
418
        if (preg_match_all(self::REGEX_DATA_PROVIDER, $docComment, $matches)) {
419
            $result = [];
420
 
421
            foreach ($matches[1] as $match) {
422
                $dataProviderMethodNameNamespace = explode('\\', $match);
423
                $leaf                            = explode('::', array_pop($dataProviderMethodNameNamespace));
424
                $dataProviderMethodName          = array_pop($leaf);
425
 
426
                if (!empty($dataProviderMethodNameNamespace)) {
427
                    $dataProviderMethodNameNamespace = implode('\\', $dataProviderMethodNameNamespace) . '\\';
428
                } else {
429
                    $dataProviderMethodNameNamespace = '';
430
                }
431
 
432
                if (!empty($leaf)) {
433
                    $dataProviderClassName = $dataProviderMethodNameNamespace . array_pop($leaf);
434
                } else {
435
                    $dataProviderClassName = $className;
436
                }
437
 
438
                $dataProviderClass  = new ReflectionClass($dataProviderClassName);
439
                $dataProviderMethod = $dataProviderClass->getMethod(
440
                    $dataProviderMethodName
441
                );
442
 
443
                if ($dataProviderMethod->isStatic()) {
444
                    $object = null;
445
                } else {
446
                    $object = $dataProviderClass->newInstance();
447
                }
448
 
449
                if ($dataProviderMethod->getNumberOfParameters() == 0) {
450
                    $data = $dataProviderMethod->invoke($object);
451
                } else {
452
                    $data = $dataProviderMethod->invoke($object, $methodName);
453
                }
454
 
455
                if ($data instanceof Iterator) {
456
                    $data = iterator_to_array($data);
457
                }
458
 
459
                if (is_array($data)) {
460
                    $result = array_merge($result, $data);
461
                } elseif ($data instanceof \Iterator) {
462
                    $data   = iterator_to_array($data);
463
                    $result = array_merge($result, $data);
464
                }
465
            }
466
 
467
            return $result;
468
        }
469
    }
470
 
471
    /**
472
     * @param string $docComment full docComment string
473
     *
474
     * @return array when @testWith annotation is defined
475
     *               null  when @testWith annotation is omitted
476
     *
477
     * @throws PHPUnit_Framework_Exception when @testWith annotation is defined but cannot be parsed
478
     */
479
    public static function getDataFromTestWithAnnotation($docComment)
480
    {
481
        $docComment = self::cleanUpMultiLineAnnotation($docComment);
482
 
483
        if (preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) {
484
            $offset            = strlen($matches[0][0]) + $matches[0][1];
485
            $annotationContent = substr($docComment, $offset);
486
            $data              = [];
487
 
488
            foreach (explode("\n", $annotationContent) as $candidateRow) {
489
                $candidateRow = trim($candidateRow);
490
 
491
                if ($candidateRow[0] !== '[') {
492
                    break;
493
                }
494
 
495
                $dataSet = json_decode($candidateRow, true);
496
 
497
                if (json_last_error() != JSON_ERROR_NONE) {
498
                    throw new PHPUnit_Framework_Exception(
499
                        'The dataset for the @testWith annotation cannot be parsed: ' . json_last_error_msg()
500
                    );
501
                }
502
 
503
                $data[] = $dataSet;
504
            }
505
 
506
            if (!$data) {
507
                throw new PHPUnit_Framework_Exception('The dataset for the @testWith annotation cannot be parsed.');
508
            }
509
 
510
            return $data;
511
        }
512
    }
513
 
514
    private static function cleanUpMultiLineAnnotation($docComment)
515
    {
516
        //removing initial '   * ' for docComment
517
        $docComment = preg_replace('/' . '\n' . '\s*' . '\*' . '\s?' . '/', "\n", $docComment);
518
        $docComment = substr($docComment, 0, -1);
519
        $docComment = rtrim($docComment, "\n");
520
 
521
        return $docComment;
522
    }
523
 
524
    /**
525
     * @param string $className
526
     * @param string $methodName
527
     *
528
     * @return array
529
     *
530
     * @throws ReflectionException
531
     */
532
    public static function parseTestMethodAnnotations($className, $methodName = '')
533
    {
534
        if (!isset(self::$annotationCache[$className])) {
535
            $class                             = new ReflectionClass($className);
536
            self::$annotationCache[$className] = self::parseAnnotations($class->getDocComment());
537
        }
538
 
539
        if (!empty($methodName) && !isset(self::$annotationCache[$className . '::' . $methodName])) {
540
            try {
541
                $method      = new ReflectionMethod($className, $methodName);
542
                $annotations = self::parseAnnotations($method->getDocComment());
543
            } catch (ReflectionException $e) {
544
                $annotations = [];
545
            }
546
            self::$annotationCache[$className . '::' . $methodName] = $annotations;
547
        }
548
 
549
        return [
550
          'class'  => self::$annotationCache[$className],
551
          'method' => !empty($methodName) ? self::$annotationCache[$className . '::' . $methodName] : []
552
        ];
553
    }
554
 
555
    /**
556
     * @param string $className
557
     * @param string $methodName
558
     *
559
     * @return array
560
     */
561
    public static function getInlineAnnotations($className, $methodName)
562
    {
563
        $method      = new ReflectionMethod($className, $methodName);
564
        $code        = file($method->getFileName());
565
        $lineNumber  = $method->getStartLine();
566
        $startLine   = $method->getStartLine() - 1;
567
        $endLine     = $method->getEndLine() - 1;
568
        $methodLines = array_slice($code, $startLine, $endLine - $startLine + 1);
569
        $annotations = [];
570
 
571
        foreach ($methodLines as $line) {
572
            if (preg_match('#/\*\*?\s*@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?\*/$#m', $line, $matches)) {
573
                $annotations[strtolower($matches['name'])] = [
574
                    'line'  => $lineNumber,
575
                    'value' => $matches['value']
576
                ];
577
            }
578
 
579
            $lineNumber++;
580
        }
581
 
582
        return $annotations;
583
    }
584
 
585
    /**
586
     * @param string $docblock
587
     *
588
     * @return array
589
     */
590
    private static function parseAnnotations($docblock)
591
    {
592
        $annotations = [];
593
        // Strip away the docblock header and footer to ease parsing of one line annotations
594
        $docblock = substr($docblock, 3, -2);
595
 
596
        if (preg_match_all('/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m', $docblock, $matches)) {
597
            $numMatches = count($matches[0]);
598
 
599
            for ($i = 0; $i < $numMatches; ++$i) {
600
                $annotations[$matches['name'][$i]][] = (string) $matches['value'][$i];
601
            }
602
        }
603
 
604
        return $annotations;
605
    }
606
 
607
    /**
608
     * Returns the backup settings for a test.
609
     *
610
     * @param string $className
611
     * @param string $methodName
612
     *
613
     * @return array
614
     */
615
    public static function getBackupSettings($className, $methodName)
616
    {
617
        return [
618
          'backupGlobals' => self::getBooleanAnnotationSetting(
619
              $className,
620
              $methodName,
621
              'backupGlobals'
622
          ),
623
          'backupStaticAttributes' => self::getBooleanAnnotationSetting(
624
              $className,
625
              $methodName,
626
              'backupStaticAttributes'
627
          )
628
        ];
629
    }
630
 
631
    /**
632
     * Returns the dependencies for a test class or method.
633
     *
634
     * @param string $className
635
     * @param string $methodName
636
     *
637
     * @return array
638
     */
639
    public static function getDependencies($className, $methodName)
640
    {
641
        $annotations = self::parseTestMethodAnnotations(
642
            $className,
643
            $methodName
644
        );
645
 
646
        $dependencies = [];
647
 
648
        if (isset($annotations['class']['depends'])) {
649
            $dependencies = $annotations['class']['depends'];
650
        }
651
 
652
        if (isset($annotations['method']['depends'])) {
653
            $dependencies = array_merge(
654
                $dependencies,
655
                $annotations['method']['depends']
656
            );
657
        }
658
 
659
        return array_unique($dependencies);
660
    }
661
 
662
    /**
663
     * Returns the error handler settings for a test.
664
     *
665
     * @param string $className
666
     * @param string $methodName
667
     *
668
     * @return bool
669
     */
670
    public static function getErrorHandlerSettings($className, $methodName)
671
    {
672
        return self::getBooleanAnnotationSetting(
673
            $className,
674
            $methodName,
675
            'errorHandler'
676
        );
677
    }
678
 
679
    /**
680
     * Returns the groups for a test class or method.
681
     *
682
     * @param string $className
683
     * @param string $methodName
684
     *
685
     * @return array
686
     */
687
    public static function getGroups($className, $methodName = '')
688
    {
689
        $annotations = self::parseTestMethodAnnotations(
690
            $className,
691
            $methodName
692
        );
693
 
694
        $groups = [];
695
 
696
        if (isset($annotations['method']['author'])) {
697
            $groups = $annotations['method']['author'];
698
        } elseif (isset($annotations['class']['author'])) {
699
            $groups = $annotations['class']['author'];
700
        }
701
 
702
        if (isset($annotations['class']['group'])) {
703
            $groups = array_merge($groups, $annotations['class']['group']);
704
        }
705
 
706
        if (isset($annotations['method']['group'])) {
707
            $groups = array_merge($groups, $annotations['method']['group']);
708
        }
709
 
710
        if (isset($annotations['class']['ticket'])) {
711
            $groups = array_merge($groups, $annotations['class']['ticket']);
712
        }
713
 
714
        if (isset($annotations['method']['ticket'])) {
715
            $groups = array_merge($groups, $annotations['method']['ticket']);
716
        }
717
 
718
        foreach (['method', 'class'] as $element) {
719
            foreach (['small', 'medium', 'large'] as $size) {
720
                if (isset($annotations[$element][$size])) {
721
                    $groups[] = $size;
722
                    break 2;
723
                }
724
            }
725
        }
726
 
727
        return array_unique($groups);
728
    }
729
 
730
    /**
731
     * Returns the size of the test.
732
     *
733
     * @param string $className
734
     * @param string $methodName
735
     *
736
     * @return int
737
     */
738
    public static function getSize($className, $methodName)
739
    {
740
        $groups = array_flip(self::getGroups($className, $methodName));
741
        $size   = self::UNKNOWN;
742
        $class  = new ReflectionClass($className);
743
 
744
        if (isset($groups['large']) ||
745
            (class_exists('PHPUnit_Extensions_Database_TestCase', false) &&
746
             $class->isSubclassOf('PHPUnit_Extensions_Database_TestCase'))) {
747
            $size = self::LARGE;
748
        } elseif (isset($groups['medium'])) {
749
            $size = self::MEDIUM;
750
        } elseif (isset($groups['small'])) {
751
            $size = self::SMALL;
752
        }
753
 
754
        return $size;
755
    }
756
 
757
    /**
758
     * Returns the tickets for a test class or method.
759
     *
760
     * @param string $className
761
     * @param string $methodName
762
     *
763
     * @return array
764
     */
765
    public static function getTickets($className, $methodName)
766
    {
767
        $annotations = self::parseTestMethodAnnotations(
768
            $className,
769
            $methodName
770
        );
771
 
772
        $tickets = [];
773
 
774
        if (isset($annotations['class']['ticket'])) {
775
            $tickets = $annotations['class']['ticket'];
776
        }
777
 
778
        if (isset($annotations['method']['ticket'])) {
779
            $tickets = array_merge($tickets, $annotations['method']['ticket']);
780
        }
781
 
782
        return array_unique($tickets);
783
    }
784
 
785
    /**
786
     * Returns the process isolation settings for a test.
787
     *
788
     * @param string $className
789
     * @param string $methodName
790
     *
791
     * @return bool
792
     */
793
    public static function getProcessIsolationSettings($className, $methodName)
794
    {
795
        $annotations = self::parseTestMethodAnnotations(
796
            $className,
797
            $methodName
798
        );
799
 
800
        if (isset($annotations['class']['runTestsInSeparateProcesses']) ||
801
            isset($annotations['method']['runInSeparateProcess'])) {
802
            return true;
803
        } else {
804
            return false;
805
        }
806
    }
807
 
808
    /**
809
     * Returns the preserve global state settings for a test.
810
     *
811
     * @param string $className
812
     * @param string $methodName
813
     *
814
     * @return bool
815
     */
816
    public static function getPreserveGlobalStateSettings($className, $methodName)
817
    {
818
        return self::getBooleanAnnotationSetting(
819
            $className,
820
            $methodName,
821
            'preserveGlobalState'
822
        );
823
    }
824
 
825
    /**
826
     * @param string $className
827
     *
828
     * @return array
829
     */
830
    public static function getHookMethods($className)
831
    {
832
        if (!class_exists($className, false)) {
833
            return self::emptyHookMethodsArray();
834
        }
835
 
836
        if (!isset(self::$hookMethods[$className])) {
837
            self::$hookMethods[$className] = self::emptyHookMethodsArray();
838
 
839
            try {
840
                $class = new ReflectionClass($className);
841
 
842
                foreach ($class->getMethods() as $method) {
843
                    if (self::isBeforeClassMethod($method)) {
844
                        self::$hookMethods[$className]['beforeClass'][] = $method->getName();
845
                    }
846
 
847
                    if (self::isBeforeMethod($method)) {
848
                        self::$hookMethods[$className]['before'][] = $method->getName();
849
                    }
850
 
851
                    if (self::isAfterMethod($method)) {
852
                        self::$hookMethods[$className]['after'][] = $method->getName();
853
                    }
854
 
855
                    if (self::isAfterClassMethod($method)) {
856
                        self::$hookMethods[$className]['afterClass'][] = $method->getName();
857
                    }
858
                }
859
            } catch (ReflectionException $e) {
860
            }
861
        }
862
 
863
        return self::$hookMethods[$className];
864
    }
865
 
866
    /**
867
     * @return array
868
     */
869
    private static function emptyHookMethodsArray()
870
    {
871
        return [
872
            'beforeClass' => ['setUpBeforeClass'],
873
            'before'      => ['setUp'],
874
            'after'       => ['tearDown'],
875
            'afterClass'  => ['tearDownAfterClass']
876
        ];
877
    }
878
 
879
    /**
880
     * @param string $className
881
     * @param string $methodName
882
     * @param string $settingName
883
     *
884
     * @return bool
885
     */
886
    private static function getBooleanAnnotationSetting($className, $methodName, $settingName)
887
    {
888
        $annotations = self::parseTestMethodAnnotations(
889
            $className,
890
            $methodName
891
        );
892
 
893
        $result = null;
894
 
895
        if (isset($annotations['class'][$settingName])) {
896
            if ($annotations['class'][$settingName][0] == 'enabled') {
897
                $result = true;
898
            } elseif ($annotations['class'][$settingName][0] == 'disabled') {
899
                $result = false;
900
            }
901
        }
902
 
903
        if (isset($annotations['method'][$settingName])) {
904
            if ($annotations['method'][$settingName][0] == 'enabled') {
905
                $result = true;
906
            } elseif ($annotations['method'][$settingName][0] == 'disabled') {
907
                $result = false;
908
            }
909
        }
910
 
911
        return $result;
912
    }
913
 
914
    /**
915
     * @param string $element
916
     *
917
     * @return array
918
     *
919
     * @throws PHPUnit_Framework_InvalidCoversTargetException
920
     */
921
    private static function resolveElementToReflectionObjects($element)
922
    {
923
        $codeToCoverList = [];
924
 
925
        if (strpos($element, '\\') !== false && function_exists($element)) {
926
            $codeToCoverList[] = new ReflectionFunction($element);
927
        } elseif (strpos($element, '::') !== false) {
928
            list($className, $methodName) = explode('::', $element);
929
 
930
            if (isset($methodName[0]) && $methodName[0] == '<') {
931
                $classes = [$className];
932
 
933
                foreach ($classes as $className) {
934
                    if (!class_exists($className) &&
935
                        !interface_exists($className) &&
936
                        !trait_exists($className)) {
937
                        throw new PHPUnit_Framework_InvalidCoversTargetException(
938
                            sprintf(
939
                                'Trying to @cover or @use not existing class or ' .
940
                                'interface "%s".',
941
                                $className
942
                            )
943
                        );
944
                    }
945
 
946
                    $class   = new ReflectionClass($className);
947
                    $methods = $class->getMethods();
948
                    $inverse = isset($methodName[1]) && $methodName[1] == '!';
949
 
950
                    if (strpos($methodName, 'protected')) {
951
                        $visibility = 'isProtected';
952
                    } elseif (strpos($methodName, 'private')) {
953
                        $visibility = 'isPrivate';
954
                    } elseif (strpos($methodName, 'public')) {
955
                        $visibility = 'isPublic';
956
                    }
957
 
958
                    foreach ($methods as $method) {
959
                        if ($inverse && !$method->$visibility()) {
960
                            $codeToCoverList[] = $method;
961
                        } elseif (!$inverse && $method->$visibility()) {
962
                            $codeToCoverList[] = $method;
963
                        }
964
                    }
965
                }
966
            } else {
967
                $classes = [$className];
968
 
969
                foreach ($classes as $className) {
970
                    if ($className == '' && function_exists($methodName)) {
971
                        $codeToCoverList[] = new ReflectionFunction(
972
                            $methodName
973
                        );
974
                    } else {
975
                        if (!((class_exists($className) ||
976
                               interface_exists($className) ||
977
                               trait_exists($className)) &&
978
                              method_exists($className, $methodName))) {
979
                            throw new PHPUnit_Framework_InvalidCoversTargetException(
980
                                sprintf(
981
                                    'Trying to @cover or @use not existing method "%s::%s".',
982
                                    $className,
983
                                    $methodName
984
                                )
985
                            );
986
                        }
987
 
988
                        $codeToCoverList[] = new ReflectionMethod(
989
                            $className,
990
                            $methodName
991
                        );
992
                    }
993
                }
994
            }
995
        } else {
996
            $extended = false;
997
 
998
            if (strpos($element, '<extended>') !== false) {
999
                $element  = str_replace('<extended>', '', $element);
1000
                $extended = true;
1001
            }
1002
 
1003
            $classes = [$element];
1004
 
1005
            if ($extended) {
1006
                $classes = array_merge(
1007
                    $classes,
1008
                    class_implements($element),
1009
                    class_parents($element)
1010
                );
1011
            }
1012
 
1013
            foreach ($classes as $className) {
1014
                if (!class_exists($className) &&
1015
                    !interface_exists($className) &&
1016
                    !trait_exists($className)) {
1017
                    throw new PHPUnit_Framework_InvalidCoversTargetException(
1018
                        sprintf(
1019
                            'Trying to @cover or @use not existing class or ' .
1020
                            'interface "%s".',
1021
                            $className
1022
                        )
1023
                    );
1024
                }
1025
 
1026
                $codeToCoverList[] = new ReflectionClass($className);
1027
            }
1028
        }
1029
 
1030
        return $codeToCoverList;
1031
    }
1032
 
1033
    /**
1034
     * @param array $reflectors
1035
     *
1036
     * @return array
1037
     */
1038
    private static function resolveReflectionObjectsToLines(array $reflectors)
1039
    {
1040
        $result = [];
1041
 
1042
        foreach ($reflectors as $reflector) {
1043
            $filename = $reflector->getFileName();
1044
 
1045
            if (!isset($result[$filename])) {
1046
                $result[$filename] = [];
1047
            }
1048
 
1049
            $result[$filename] = array_unique(
1050
                array_merge(
1051
                    $result[$filename],
1052
                    range($reflector->getStartLine(), $reflector->getEndLine())
1053
                )
1054
            );
1055
        }
1056
 
1057
        return $result;
1058
    }
1059
 
1060
    /**
1061
     * @param ReflectionMethod $method
1062
     *
1063
     * @return bool
1064
     */
1065
    private static function isBeforeClassMethod(ReflectionMethod $method)
1066
    {
1067
        return $method->isStatic() && strpos($method->getDocComment(), '@beforeClass') !== false;
1068
    }
1069
 
1070
    /**
1071
     * @param ReflectionMethod $method
1072
     *
1073
     * @return bool
1074
     */
1075
    private static function isBeforeMethod(ReflectionMethod $method)
1076
    {
1077
        return preg_match('/@before\b/', $method->getDocComment());
1078
    }
1079
 
1080
    /**
1081
     * @param ReflectionMethod $method
1082
     *
1083
     * @return bool
1084
     */
1085
    private static function isAfterClassMethod(ReflectionMethod $method)
1086
    {
1087
        return $method->isStatic() && strpos($method->getDocComment(), '@afterClass') !== false;
1088
    }
1089
 
1090
    /**
1091
     * @param ReflectionMethod $method
1092
     *
1093
     * @return bool
1094
     */
1095
    private static function isAfterMethod(ReflectionMethod $method)
1096
    {
1097
        return preg_match('/@after\b/', $method->getDocComment());
1098
    }
1099
}