Issues (300)

src/Block/Group.php (1 issue)

Checks if the types of returned expressions are compatible with the documented types.

Best Practice Bug Major
1
<?php
2
namespace Kahlan\Block;
3
4
use Kahlan\Block;
5
use Closure;
6
use Exception;
7
use Throwable;
8
use Kahlan\Suite;
9
use Kahlan\Scope\Group as Scope;
10
11
class Group extends Block
12
{
13
    /**
14
     * The each callbacks.
15
     *
16
     * @var array
17
     */
18
    protected $_callbacks = [
19
        'beforeAll'  => [],
20
        'afterAll'   => [],
21
        'beforeEach' => [],
22
        'afterEach'  => [],
23
    ];
24
25
    /**
26
     * Indicates if the group has been loaded or not.
27
     *
28
     * @var boolean
29
     */
30
    protected $_loaded = false;
31
32
    /**
33
     * The children array.
34
     *
35
     * @var Group[]|Specification[]
36
     */
37
    protected $_children = [];
38
39
    /**
40
     * Group statistics.
41
     *
42
     * @var array
43
     */
44
    protected $_stats = null;
45
46
    /**
47
     * Group state.
48
     *
49
     * @var array
50
     */
51
    protected $_enabled = true;
52
53
    /**
54
     * The Constructor.
55
     *
56
     * @param array $config The Group config array. Options are:
57
     *                      -`'name'`    _string_ : the type of the suite.
58
     */
59
    public function __construct($config = [])
60 121
    {
61
        parent::__construct($config);
62 121
63 121
        $this->_scope = new Scope(['block' => $this]);
64
        $this->_closure = $this->_bindScope($this->_closure);
65
    }
66
67
    /**
68
     * Gets children.
69
     *
70
     * @return array The array of children instances.
71
     */
72
    public function children()
73 48
    {
74
        return $this->_children;
75
    }
76
77
    /**
78
     * Builds the group stats.
79
     *
80
     * @return array The group stats.
81
     */
82
    public function stats()
83
    {
84
        if ($this->_stats !== null) {
85
            return $this->_stats;
86
        }
87 40
88
        Suite::push($this);
89
90 40
        $builder = function ($block) {
91 40
            $block->load();
92 40
            $normal = 0;
93 40
            $inactive = 0;
94 40
            $focused = 0;
95
            $excluded = 0;
96
97
            foreach ($block->children() as $child) {
98 4
                if ($block->excluded()) {
99
                    $child->type('exclude');
100
                }
101 38
                if ($child instanceof Group) {
102
                    $result = $child->stats();
103 6
                    if ($child->focused() && !$result['focused']) {
104 6
                        $focused += $result['normal'];
105 6
                        $excluded += $result['excluded'];
106
                        $child->broadcastFocus();
107 38
                    } elseif (!$child->enabled()) {
108
                        $inactive += $result['normal'];
109
                        $focused += $result['focused'];
110
                        $excluded += $result['excluded'];
111 38
                    } else {
112 38
                        $normal += $result['normal'];
113 38
                        $focused += $result['focused'];
114
                        $excluded += $result['excluded'];
115
                    }
116
                } else {
117
                    switch ($child->type()) {
118 6
                        case 'exclude':
119 6
                            $excluded++;
120
                            break;
121 12
                        case 'focus':
122 12
                            $focused++;
123
                            break;
124 36
                        default:
125 36
                            $normal++;
126
                            break;
127
                    }
128
                }
129 40
            }
130
            return compact('normal', 'inactive', 'focused', 'excluded');
131
        };
132
133 40
        try {
134
            $stats = $builder($this);
135 2
        } catch (Throwable $exception) {
136 2
            $this->log()->type('errored');
137
            $this->log()->exception($exception);
138
139
            $stats = [
140
                'normal' => 0,
141
                'focused' => 0,
142 2
                'excluded' => 0
143
            ];
144
        }
145 40
146 40
        Suite::pop();
147
        return $stats;
148
    }
149
150
    /**
151
     * Splits the specs in different partitions and only enable one.
152
     *
153
     * @param integer $index The partition index to enable.
154
     * @param integer $total The total of partitions.
155
     */
156
    public function partition($index, $total)
157 38
    {
158 38
        $index = (integer) $index;
159
        $total = (integer) $total;
160 38
        if (!$index || !$total || $index > $total) {
161
            throw new Exception("Invalid partition parameters: {$index}/{$total}");
162
        }
163 38
164 38
        $groups = [];
165 38
        $partitions = [];
166
        $partitionsTotal = [];
167 38
168 38
        for ($i = 0; $i < $total; $i++) {
169 38
            $partitions[$i] = [];
170
            $partitionsTotal[$i] = 0;
171
        }
172 38
173
        $children = $this->children();
174
175 38
        foreach ($children as $key => $child) {
176 38
            $groups[$key] = $child->stats()['normal'];
177
            $child->enabled(false);
178 38
        }
179
        asort($groups);
180
181 38
        foreach ($groups as $key => $value) {
182 38
            $i = array_search(min($partitionsTotal), $partitionsTotal);
183 38
            $partitions[$i][] = $key;
184
            $partitionsTotal[$i] += $groups[$key];
185
        }
186
187 38
        foreach ($partitions[$index - 1] as $key) {
188
            $children[$key]->enabled(true);
189
        }
190
    }
191
192
    /**
193
     * Set/get the enable value.
194
     *
195
     * @param  string $enable The enable value.
196
     * @return mixed
197
     */
198
    public function enabled($enable = null)
199
    {
200 1265
        if (!func_num_args()) {
201
            return $this->_enabled;
202 38
        }
203 38
        $this->_enabled = $enable;
204
        return $this;
205
    }
206
207
    /* Adds a group/class related spec.
208
     *
209
     * @param  string  $message Description message.
210
     * @param  Closure $closure A test case closure.
211
     *
212
     * @return Group
213
     */
214
    public function describe($message, $closure, $timeout = null, $type = 'normal')
215 44
    {
216 44
        $suite = $this->suite();
217 44
        $parent = $this;
218 44
        $timeout = $timeout ?? $this->timeout();
219
        $group = new Group(compact('message', 'closure', 'suite', 'parent', 'timeout', 'type'));
220 44
221
        return $this->_children[] = $group;
222
    }
223
224
    /**
225
     * Adds a context related spec.
226
     *
227
     * @param  string  $message Description message.
228
     * @param  Closure $closure A test case closure.
229
     * @param  null    $timeout
230
     * @param  string  $type
231
     *
232
     * @return Group
233
     */
234
    public function context($message, $closure, $timeout = null, $type = 'normal')
235 6
    {
236
        return $this->describe($message, $closure, $timeout, $type);
237
    }
238
239
    /**
240
     * Adds a spec.
241
     *
242
     * @param  string|Closure $message Description message or a test closure.
243
     * @param  Closure        $closure A test case closure.
244
     * @param  string         $type    The type.
245
     *
246
     * @return Specification
247
     */
248
    public function it($message, $closure = null, $timeout = null, $type = 'normal')
249 38
    {
250 38
        $suite = $this->suite();
251 38
        $parent = $this;
252 38
        $timeout = $timeout ?? $this->timeout();
253 38
        $spec = new Specification(compact('message', 'closure', 'suite', 'parent', 'timeout', 'type'));
254
        $this->_children[] = $spec;
255 38
256
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Kahlan\Block\Group which is incompatible with the documented return type Kahlan\Block\Specification.
Loading history...
257
    }
258
259
    /**
260
     * Executed before tests.
261
     *
262
     * @param  Closure $closure A closure
263
     *
264
     * @return self
265
     */
266
    public function beforeAll($closure)
267 8
    {
268 8
        $this->_callbacks['beforeAll'][] = $this->_bindScope($closure);
269
        return $this;
270
    }
271
272
    /**
273
     * Executed after tests.
274
     *
275
     * @param  Closure $closure A closure
276
     *
277
     * @return self
278
     */
279
    public function afterAll($closure)
280 8
    {
281 8
        array_unshift($this->_callbacks['afterAll'], $this->_bindScope($closure));
282
        return $this;
283
    }
284
285
    /**
286
     * Executed before each tests.
287
     *
288
     * @param  Closure $closure A closure
289
     *
290
     * @return self
291
     */
292
    public function beforeEach($closure)
293 10
    {
294 10
        $this->_callbacks['beforeEach'][] = $this->_bindScope($closure);
295
        return $this;
296
    }
297
298
    /**
299
     * Executed after each tests.
300
     *
301
     * @param  Closure $closure A closure
302
     *
303
     * @return self
304
     */
305
    public function afterEach($closure)
306 10
    {
307 10
        array_unshift($this->_callbacks['afterEach'], $this->_bindScope($closure));
308
        return $this;
309
    }
310
311
    /**
312
     * Load the group.
313
     */
314
    public function load()
315
    {
316 40
        if ($this->_loaded) {
317
            return;
318 40
        }
319
        $this->_loaded = true;
320
        if (!$closure = $this->closure()) {
321
            return;
322 40
        }
323
        return $this->_suite->runBlock($this, $closure, 'group');
324
    }
325
326
    /**
327
     * Group execution helper.
328
     */
329
    protected function _execute()
330
    {
331
        if (!$this->enabled() && !$this->focused()) {
332
            return;
333
        }
334
        foreach ($this->_children as $child) {
335 2
            if ($this->suite()->failfast()) {
336
                break;
337 1283
            }
338
            $this->_passed = $child->process() && $this->_passed;
339
        }
340
    }
341
342
    /**
343
     * Start group execution helper.
344
     */
345
    protected function _blockStart()
346
    {
347
        if (!$this->enabled()) {
348
            return;
349 452
        }
350 721
        $this->report('suiteStart', $this);
351
        $this->runCallbacks('beforeAll', false);
352
    }
353
354
    /**
355
     * End group block execution helper.
356
     */
357
    protected function _blockEnd($runAfterAll = true)
358
    {
359
        if (!$this->enabled()) {
360
            return;
361
        }
362
        if ($runAfterAll) {
363 831
            try {
364
                $this->runCallbacks('afterAll', false);
365 2
            } catch (Throwable $exception) {
366
                $this->_exception($exception);
367
            }
368
        }
369 833
370
        $this->suite()->autoclear();
371 833
372
        $type = $this->log()->type();
373 6
        if ($type === 'failed' || $type === 'errored') {
374 6
            $this->_passed = false;
375 6
            $this->suite()->failure();
376
            $this->summary()->log($this->log());
377
        }
378 833
379
        $this->report('suiteEnd', $this);
380
    }
381
382
    /**
383
     * Runs a callback.
384
     *
385
     * @param string $name The name of the callback (i.e `'beforeEach'` or `'afterEach'`).
386
     */
387
    public function runCallbacks($name, $recursive = true)
388 970
    {
389
        $instances = $recursive ? $this->parents(true) : [$this];
390 875
        if (strncmp($name, 'after', 5) === 0) {
391
            $instances = array_reverse($instances);
392
        }
393
        foreach ($instances as $instance) {
394 480
            foreach ($instance->_callbacks[$name] as $closure) {
395
                $this->_suite->runBlock($this, $closure, $name);
396
            }
397
        }
398
    }
399
400
    /**
401
     * Gets callbacks.
402
     *
403
     * @param  string $type The type of callbacks to get.
404
     *
405
     * @return array        The array callbacks instances.
406
     */
407
    public function callbacks($type)
408 8
    {
409
        return $this->_callbacks[$type] ?? [];
410
    }
411
412
    /**
413
     * Apply focus downward to the leaf.
414
     */
415
    public function broadcastFocus()
416
    {
417
        foreach ($this->_children as $child) {
418 2
            if ($child->type() !== 'normal') {
419
                continue;
420 6
            }
421
            $child->type('focus');
422 4
            if ($child instanceof Group) {
423
                $child->broadcastFocus();
424
            }
425
        }
426
    }
427
428
}
429