1 <?php
2
3 namespace Alo\Test;
4
5 use Alo\Exception\TesterException as TE;
6 use Alo\Statics\Format as F;
7
8 if (!defined('GEN_START')) {
9 http_response_code(404);
10 die();
11 }
12
13 14 15 16 17 18
19 abstract class AbstractTester {
20
21 22 23 24 25
26 protected $queue;
27
28 29 30 31 32
33 protected $results;
34
35 36 37 38 39
40 const P_NAME = 'name';
41
42 43 44 45 46
47 const P_ARGS = 'args';
48
49 50 51 52 53
54 const P_OUTCOME = 'outcome';
55
56 57 58 59 60
61 const P_TYPE = 'type';
62
63 64 65 66 67
68 const P_DEFINITION = 'definition';
69
70 71 72 73 74
75 const P_PASSED = 'passed';
76
77 78 79 80 81
82 const T_OUTPUT = 'output';
83
84 85 86 87 88
89 const T_RETURN = 'return';
90
91 92 93 94 95
96 const O_NOT_CALLABLE = 'Not callable';
97
98 99 100 101 102
103 const P_TEST_START = 'start';
104
105 106 107 108 109
110 const P_TEST_END = 'end';
111
112 113 114 115 116
117 const P_TEST_RUNTIME = 'runtime';
118
119 120 121 122 123
124 function __construct() {
125 $this->queue = $this->results = [];
126 }
127
128 129 130 131 132 133
134 function runTests() {
135 foreach ($this->queue as $test) {
136 $this->runTest($test);
137 }
138
139 return $this->results;
140 }
141
142 143 144 145 146 147 148
149 protected function runTest(array $test) {
150 $callable = $this->getCallable($test[self::P_DEFINITION][self::P_NAME]);
151
152 $add = [
153 self::P_DEFINITION => $test[self::P_DEFINITION],
154 self::P_TEST_START => microtime(true)
155 ];
156
157 if (!is_callable($callable)) {
158 $add[self::P_PASSED] = false;
159 $add[self::P_OUTCOME] = self::O_NOT_CALLABLE;
160 $add[self::P_TEST_END] = microtime(true);
161 } else {
162 ob_start();
163 $call = call_user_func_array($callable, $test[self::P_DEFINITION][self::P_ARGS]);
164 $ob = ob_get_clean();
165 $add[self::P_TEST_END] = microtime(true);
166
167 $add[self::P_OUTCOME] = $test[self::P_TYPE] == self::T_OUTPUT ? $ob : $call;
168 $add[self::P_PASSED] = $add[self::P_OUTCOME] == $test[self::P_DEFINITION][self::P_OUTCOME];
169 }
170
171 $add[self::P_TEST_RUNTIME] = $add[self::P_TEST_END] - $add[self::P_TEST_START];
172 $this->results[] = $add;
173
174 return $this;
175 }
176
177 178 179 180 181 182 183
184 abstract protected function getCallable($name);
185
186 187 188 189 190 191 192 193 194 195 196
197 protected function addGenericTest($type, $name, $outcome, array $args = []) {
198 if (!is_string($name)) {
199 throw new TE('The name must be a string!', TE::E_NAME_INVALID);
200 } else {
201 $this->queue[] = [
202 self::P_TYPE => $type,
203 self::P_DEFINITION => [
204 self::P_NAME => $name,
205 self::P_ARGS => $args,
206 self::P_OUTCOME => $outcome
207 ]
208 ];
209 }
210
211 return $this;
212 }
213
214 215 216 217 218 219 220 221 222
223 function addOutputTest($name, $outcome, array $args = []) {
224 return $this->addGenericTest(self::T_OUTPUT, $name, $outcome, $args);
225 }
226
227 228 229 230 231 232 233 234 235
236 function addReturnTest($name, $outcome, array $args = []) {
237 return $this->addGenericTest(self::T_RETURN, $name, $outcome, $args);
238 }
239
240 241 242 243 244
245 function getQueue() {
246 return $this->queue;
247 }
248
249 250 251 252 253 254
255 function getResults() {
256 return $this->results;
257 }
258
259 260 261 262 263 264
265 function toPlaintextString() {
266 $ret = "";
267
268 foreach ($this->results as $r) {
269 $ret .= $r[self::P_DEFINITION][self::P_NAME] . "\n"
270 . "\tArgs:\t\t\t" . F::scalarOutput(array_shift($r[self::P_DEFINITION][self::P_ARGS])) . "\n";
271
272 foreach ($r[self::P_DEFINITION][self::P_ARGS] as $arg) {
273 $ret .= "\t\t\t\t" . F::scalarOutput($arg) . "\n";
274 }
275
276 $ret .= "\tExpected outcome:\t" . F::scalarOutput($r[self::P_DEFINITION][self::P_OUTCOME]) . "\n"
277 . "\tOutcome:\t\t" . F::scalarOutput($r[self::P_OUTCOME]) . "\n"
278 . "\tResult:\t\t\t" . ($r[self::P_PASSED] ? 'PASSED' : 'FAILED') . "\n"
279 . "\tStart time:\t\t" . \timestamp_precise($r[self::P_TEST_START]) . "\n"
280 . "\tEnd time:\t\t" . \timestamp_precise($r[self::P_TEST_END]) . "\n"
281 . "\tRuntime:\t\t" . (($r[self::P_TEST_END] - $r[self::P_TEST_START]) * 1000) . "ms\n\n";
282
283 }
284
285 return $ret;
286 }
287
288 289 290 291 292 293
294 function __toString() {
295 $ret = '<table border="1" style="background:#000;color:#fff" cellpadding="2">'
296 . '<thead>'
297 . '<tr>'
298 . '<th>Tested item</th>'
299 . '<th>Item args</th>'
300 . '<th>Expected outcome</th>'
301 . '<th>Outcome</th>'
302 . '<th>Result</th>'
303 . '<th>Start time</th>'
304 . '<th>End time</th>'
305 . '<th>Runtime</th>'
306 . '</tr>'
307 . '</thead>'
308 . '<tbody>';
309
310 foreach ($this->results as $r) {
311 foreach ($r[self::P_DEFINITION][self::P_ARGS] as &$a) {
312 $a = '<span style="color:gold">' . F::scalarOutput($a) . '</span>';
313 }
314
315 $ret .= '<tr>'
316 . '<td>' . $r[self::P_DEFINITION][self::P_NAME] . '</td>'
317 . '<td><ol style="margin:0;"><li>' . implode('</li><li>', $r[self::P_DEFINITION][self::P_ARGS]) . '</li></ol></td>'
318 . '<td><pre>' . F::scalarOutput($r[self::P_DEFINITION][self::P_OUTCOME]) . '</pre></td>'
319 . '<td><pre>' . F::scalarOutput($r[self::P_OUTCOME]) . '</pre></td>'
320 . '<td style="color:';
321
322 if ($r[self::P_PASSED]) {
323 $ret .= 'lime">PASSED';
324 } else {
325 $ret .= 'red">FAILED';
326 }
327
328 $ret .= '</td>'
329 . '<td>' . \timestamp_precise($r[self::P_TEST_START]) . '</td>'
330 . '<td>' . \timestamp_precise($r[self::P_TEST_END]) . '</td>'
331 . '<td>' . (($r[self::P_TEST_END] - $r[self::P_TEST_START]) * 1000) . 'ms</td>'
332 . '</tr>';
333 }
334
335 return $ret . '</tbody></table>';
336 }
337 }