Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.10% covered (success)
93.10%
27 / 29
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
ReportPublisher
93.10% covered (success)
93.10%
27 / 29
50.00% covered (danger)
50.00%
1 / 2
12.05
0.00% covered (danger)
0.00%
0 / 1
 publish
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 aggregate
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
11
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\WptHarness;
6
7/**
8 * Serialises a `TestResult[]` ledger into the published artefacts:
9 *
10 *   - `var/wpt/report.json`   — machine-readable, full per-test rows
11 *   - `var/wpt/summary.md`    — human-readable summary (counts per
12 *                               bucket, top failure clusters, deltas
13 *                               vs prior run)
14 *   - `docs/site/standards/spec-coverage/wpt-report.md` — published
15 *                                                        dashboard
16 *
17 * Phase 4A.5 implements all three writers. The JSON ledger is the
18 * input to Phase 4A.6 (PR comment delta) and to the spec inventory
19 * regenerator (`composer spec-status`).
20 */
21final class ReportPublisher
22{
23    /**
24     * @param list<TestResult> $results
25     */
26    public function publish(array $results, string $outputDir): void
27    {
28        unset($results, $outputDir);
29        throw new \RuntimeException('4A.5 not yet implemented');
30    }
31
32    /**
33     * Aggregate the bucket counts for a result set. Used by both
34     * publish() and the summary writer.
35     *
36     * @param list<TestResult> $results
37     * @return array{
38     *     pass: int,
39     *     fail: int,
40     *     outOfScope: int,
41     *     pendingSubstrate: int,
42     *     skipped: int,
43     *     harnessError: int,
44     *     blankPass: int,
45     *     inScopeTotal: int,
46     *     inScopePassRate: float
47     * }
48     */
49    public static function aggregate(array $results): array
50    {
51        $counts = [
52            'pass' => 0,
53            'fail' => 0,
54            'outOfScope' => 0,
55            'pendingSubstrate' => 0,
56            'skipped' => 0,
57            'harnessError' => 0,
58        ];
59        // Passes with nothing drawn on either side. A subset of
60        // `pass`, NOT a bucket of its own: a handful of fixtures
61        // legitimately render nothing, so this is reported as
62        // evidence-quality metadata and never as a verdict.
63        $blankPass = 0;
64        foreach ($results as $result) {
65            $counts[match ($result->status) {
66                TestStatus::Pass => 'pass',
67                TestStatus::Fail => 'fail',
68                TestStatus::OutOfScope => 'outOfScope',
69                TestStatus::PendingSubstrate => 'pendingSubstrate',
70                TestStatus::Skipped => 'skipped',
71                TestStatus::HarnessError => 'harnessError',
72            }]++;
73            if ($result->status === TestStatus::Pass && $result->bothRendersSolid) {
74                $blankPass++;
75            }
76        }
77        $inScopeTotal = $counts['pass'] + $counts['fail'];
78        // PHP `int / int` returns int when divisible — force float so
79        // the rate is always a float in [0.0, 1.0].
80        $inScopePassRate = $inScopeTotal > 0 ? (float) $counts['pass'] / $inScopeTotal : 0.0;
81        return [
82            ...$counts,
83            'blankPass' => $blankPass,
84            'inScopeTotal' => $inScopeTotal,
85            'inScopePassRate' => $inScopePassRate,
86        ];
87    }
88}