Completed
Pull Request — master (#177)
by Erin
01:57
created

Test::run()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 11
rs 9.4285
cc 2
eloc 7
nc 2
nop 1
1
<?php
2
3
namespace Peridot\Core;
4
5
use ErrorException;
6
use Exception;
7
use Throwable;
8
9
/**
10
 * The main test fixture for Peridot.
11
 *
12
 * @package Peridot\Core
13
 */
14
class Test extends AbstractTest
15
{
16
    /**
17
     * @param string $description
18
     * @param callable $definition
19
     */
20
    public function __construct($description, callable $definition = null)
21
    {
22
        if ($definition === null) {
23
            $this->pending = true;
24
            $definition = function () {
25
                //noop
26
            };
27
        }
28
        parent::__construct($description, $definition);
29
    }
30
31
    /**
32
     * Execute the test along with any setup and tear down functions.
33
     *
34
     * @param  TestResult $result
35
     * @return void
36
     */
37
    public function run(TestResult $result)
38
    {
39
        $result->startTest($this);
40
41
        if ($this->getPending()) {
42
            $result->pendTest($this);
43
            return;
44
        }
45
        $this->executeTest($result);
46
        $result->endTest($this);
47
    }
48
49
    /**
50
     * Attempt to execute setup functions and run the test definition
51
     *
52
     * @param TestResult $result
53
     */
54
    protected function executeTest(TestResult $result)
55
    {
56
        $action = ['passTest', $this];
57
        $handler = $this->handleErrors($result, $action);
58
        try {
59
            $this->runSetup();
60
            call_user_func_array($this->getDefinition(), $this->getDefinitionArguments());
61
        } catch (Throwable $e) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
62
            if ('passTest' === $action[0]) {
63
                $action = ['failTest', $this, $e];
64
            }
65
        } catch (Exception $e) {
66
            if ('passTest' === $action[0]) {
67
                $action = ['failTest', $this, $e];
68
            }
69
        }
70
        $this->runTearDown($result, $action);
71
        $this->restoreErrorHandler($handler);
72
    }
73
74
    /**
75
     * Excecute the test's setup functions
76
     */
77
    protected function runSetup()
78
    {
79
        $this->forEachNodeTopDown(function (TestInterface $node) {
80
            $setups = $node->getSetupFunctions();
81
            foreach ($setups as $setup) {
82
                $setup();
83
            }
84
        });
85
    }
86
87
    /**
88
     * Run the tests tear down methods and have the result
89
     * perform the method indicated by $action
90
     *
91
     * @param TestResult $result
92
     * @param array $action
93
     */
94
    protected function runTearDown(TestResult $result, $action)
95
    {
96
        $this->forEachNodeBottomUp(function (TestInterface $test) use ($result, &$action) {
97
            $tearDowns = $test->getTearDownFunctions();
98
            foreach ($tearDowns as $tearDown) {
99
                try {
100
                    $tearDown();
101
                } catch (Throwable $e) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
102
                    if ('passTest' === $action[0]) {
103
                        $action = ['failTest', $this, $e];
104
                    }
105
                } catch (Exception $e) {
106
                    if ('passTest' === $action[0]) {
107
                        $action = ['failTest', $this, $e];
108
                    }
109
                }
110
            }
111
        });
112
        call_user_func_array([$result, $action[0]], array_slice($action, 1));
113
    }
114
115
    /**
116
     * Set an error handler to handle errors within the test
117
     *
118
     * @param TestResult $result
119
     * @param array      &$action
120
     *
121
     * @return callable|null
122
     */
123
    protected function handleErrors(TestResult $result, &$action)
124
    {
125
        $handler = null;
126
        $handler = set_error_handler(function ($severity, $message, $path, $line) use ($result, &$action, &$handler) {
127
            // if there is an existing error handler, call it and record the result
128
            $isHandled = $handler && false !== $handler($severity, $message, $path, $line);
129
130
            if (!$isHandled) {
131
                $result->getEventEmitter()->emit('error', [$severity, $message, $path, $line]);
132
133
                // honor the error reporting configuration - this also takes care of the error control operator (@)
134
                $errorReporting = error_reporting();
135
                $shouldHandle = $severity === ($severity & $errorReporting);
136
137
                if ($shouldHandle && 'passTest' === $action[0]) {
138
                    $action = ['failTest', $this, new ErrorException($message, 0, $severity, $path, $line)];
139
                }
140
            }
141
        });
142
143
        return $handler;
144
    }
145
146
    /**
147
     * Restore the previous error handler
148
     *
149
     * @param callable|null $handler
150
     */
151
    protected function restoreErrorHandler($handler)
152
    {
153
        if ($handler) {
154
            set_error_handler($handler);
155
        } else {
156
            // unfortunately, we can't pass null until PHP 5.5
157
            set_error_handler(function () { return false; });
158
        }
159
    }
160
}
161