Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
50.00% covered (danger)
50.00%
44 / 88
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
TestFileReader
50.00% covered (danger)
50.00%
44 / 88
0.00% covered (danger)
0.00%
0 / 2
118.12
0.00% covered (danger)
0.00%
0 / 1
 read
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 __construct
57.14% covered (warning)
57.14%
44 / 77
0.00% covered (danger)
0.00%
0 / 1
74.20
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\ParserTests;
5
6class TestFileReader {
7    /** @var array File-level options and requirements for these parser tests */
8    public $fileOptions = [];
9
10    /** @var Test[] */
11    public $testCases = [];
12
13    /** @var Article[] */
14    public $articles = [];
15
16    /**
17     * @var ?string Path to known failures file, or null if does not exist
18     *   or is not readable.
19     */
20    public $knownFailuresPath;
21
22    /**
23     * Read and parse a parserTest file.
24     * @param string $testFilePath The parserTest file to read
25     * @param ?callable(string) $warnFunc An optional function to use to
26     *   report the use of deprecated test section names
27     * @param ?callable(string):string $normalizeFunc An optional function
28     *   to use to normalize article titles for uniqueness testing
29     * @param ?string $knownFailuresInfix qualifier for the known failures file
30     *   (usually "standalone" to distinguish from the failures from the default
31     *    integrated test run)
32     * @return TestFileReader
33     */
34    public static function read(
35        string $testFilePath,
36        ?callable $warnFunc = null,
37        ?callable $normalizeFunc = null,
38        ?string $knownFailuresInfix = null
39    ): TestFileReader {
40        $info = pathinfo( $testFilePath );
41        $knownFailuresPath = $info['dirname'] . '/' . $info['filename'] .
42            ( $knownFailuresInfix ? "-$knownFailuresInfix" : '' ) .
43            '-knownFailures.json';
44        $reader = new self(
45            $testFilePath,
46            $knownFailuresPath,
47            $warnFunc,
48            $normalizeFunc
49        );
50        return $reader;
51    }
52
53    /**
54     * @param string $testFilePath The parserTest file to read
55     * @param ?string $knownFailuresPath The known failures file to read
56     *   (or null, if there is no readable known failures file)
57     * @param ?callable(string) $warnFunc An optional function to use to
58     *   report the use of deprecated test section names
59     * @param ?callable(string):string $normalizeFunc An optional function
60     *   to use to normalize article titles for uniqueness testing
61     */
62    private function __construct(
63        string $testFilePath, ?string $knownFailuresPath,
64        ?callable $warnFunc = null, ?callable $normalizeFunc = null
65    ) {
66        $this->knownFailuresPath = $knownFailuresPath && is_readable( $knownFailuresPath ) ?
67            $knownFailuresPath : null;
68        $parsedTests = Grammar::load( $testFilePath );
69        // Start off with any comments before `!! format`
70        $rawTestItems = $parsedTests[0];
71        $testFormat = $parsedTests[1];
72        if ( $testFormat != null ) {
73            // If `!!format` was present, existing comments applied to the
74            // format declaration, not the first item.
75            $rawTestItems = [];
76        }
77
78        // Add any comments after `!! format`
79        array_splice( $rawTestItems, count( $rawTestItems ), 0, $parsedTests[2] );
80        if ( $parsedTests[3] == null ) {
81            $this->fileOptions = [];
82        } else {
83            $this->fileOptions = $parsedTests[3]['text'];
84            // If `!!options` was present, existing comments applied to the
85            // file options, not the first item.
86            $rawTestItems = [];
87        }
88
89        // Add the rest of the comments and items appearing after `!!options`
90        array_splice( $rawTestItems, count( $rawTestItems ), 0, $parsedTests[4] );
91
92        if ( $testFormat !== null ) {
93            if ( isset( $this->fileOptions['version'] ) ) {
94                ( new Item( $parsedTests[3] ) )->error( 'Duplicate version specification' );
95            } else {
96                $this->fileOptions['version'] = $testFormat['text'];
97            }
98        }
99        $this->fileOptions['version'] ??= '1';
100
101        $knownFailures = $this->knownFailuresPath !== null ?
102            json_decode( file_get_contents( $knownFailuresPath ), true ) :
103            null;
104
105        $testNames = [];
106        $articleTitles = [];
107
108        $lastComment = '';
109        foreach ( $rawTestItems as $item ) {
110            if ( $item['type'] === 'article' ) {
111                $art = new Article( $item, $lastComment );
112                $key = $normalizeFunc ? $normalizeFunc( $art->title ) : $art->title;
113                if ( isset( $articleTitles[$key] ) ) {
114                    $art->error( 'Duplicate article', $art->title );
115                }
116                $articleTitles[$key] = true;
117                $this->articles[] = $art;
118                $lastComment = '';
119            } elseif ( $item['type'] === 'test' ) {
120                $test = new Test(
121                    $item,
122                    $knownFailures[$item['testName']] ?? [],
123                    $lastComment,
124                    $warnFunc
125                );
126                if ( isset( $testNames[$test->testName] ) ) {
127                    $test->error( 'Duplicate test name', $test->testName );
128                }
129                $testNames[$test->testName] = true;
130                $this->testCases[] = $test;
131                $lastComment = '';
132            } elseif ( $item['type'] === 'comment' ) {
133                $lastComment .= $item['text'];
134            } elseif ( $item['type'] === 'hooks' ) {
135                foreach ( explode( "\n", $item['text'] ) as $line ) {
136                    $this->fileOptions['requirements'][] = [
137                        'type' => 'hook',
138                        'name' => trim( $line ),
139                    ];
140                }
141                $lastComment = '';
142            } elseif ( $item['type'] === 'functionhooks' ) {
143                foreach ( explode( "\n", $item['text'] ) as $line ) {
144                    $this->fileOptions['requirements'][] = [
145                        'type' => 'functionHook',
146                        'name' => trim( $line ),
147                    ];
148                }
149                $lastComment = '';
150            } elseif ( $item['type'] === 'line' ) {
151                if ( !empty( trim( $item['text'] ) ) ) {
152                    ( new Item( $item ) )->error( 'Invalid line', $item['text'] );
153                }
154            } else {
155                ( new Item( $item ) )->error( 'Unknown item type', $item['type'] );
156            }
157        }
158        // Convenience function to expand 'requirements'
159        if ( isset( $this->fileOptions['requirements'] ) ) {
160            if ( !is_array( $this->fileOptions['requirements'] ) ) {
161                $this->fileOptions['requirements'] = [
162                    $this->fileOptions['requirements']
163                ];
164            }
165            foreach ( $this->fileOptions['requirements'] as &$item ) {
166                if ( is_string( $item ) ) {
167                    $item = [
168                        'type' => 'hook',
169                        'name' => "$item",
170                    ];
171                }
172            }
173            unset( $item );
174        }
175    }
176}