Test::restoreErrorHandler()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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