|
1
|
|
|
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName |
|
2
|
|
|
/** |
|
3
|
|
|
* Tests for the changelogger utils. |
|
4
|
|
|
* |
|
5
|
|
|
* @package automattic/jetpack-changelogger |
|
6
|
|
|
*/ |
|
7
|
|
|
|
|
8
|
|
|
// phpcs:disable WordPress.NamingConventions.ValidVariableName, WordPress.WP.AlternativeFunctions |
|
9
|
|
|
|
|
10
|
|
|
namespace Automattic\Jetpack\Changelogger\Tests; |
|
11
|
|
|
|
|
12
|
|
|
use Automattic\Jetpack\Changelogger\Utils; |
|
13
|
|
|
use Symfony\Component\Console\Helper\DebugFormatterHelper; |
|
14
|
|
|
use Symfony\Component\Console\Output\BufferedOutput; |
|
15
|
|
|
use Symfony\Component\Process\Exception\ProcessTimedOutException; |
|
16
|
|
|
use Symfony\Component\Process\ExecutableFinder; |
|
17
|
|
|
use Symfony\Component\Process\Process; |
|
18
|
|
|
use function Wikimedia\quietCall; |
|
19
|
|
|
|
|
20
|
|
|
/** |
|
21
|
|
|
* Tests for the changelogger utils. |
|
22
|
|
|
* |
|
23
|
|
|
* @covers \Automattic\Jetpack\Changelogger\Utils |
|
24
|
|
|
*/ |
|
25
|
|
|
class UtilsTest extends TestCase { |
|
26
|
|
|
use \Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames; |
|
27
|
|
|
use \Yoast\PHPUnitPolyfills\Polyfills\ExpectException; |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* Test error_clear_last. |
|
31
|
|
|
*/ |
|
32
|
|
|
public function test_error_clear_last() { |
|
33
|
|
|
quietCall( 'trigger_error', 'Test', E_USER_NOTICE ); |
|
34
|
|
|
$err = error_get_last(); |
|
35
|
|
|
$this->assertSame( 'Test', $err['message'] ); |
|
36
|
|
|
|
|
37
|
|
|
Utils::error_clear_last(); |
|
38
|
|
|
$err = error_get_last(); |
|
39
|
|
|
$this->assertTrue( empty( $err['message'] ) ); |
|
40
|
|
|
} |
|
41
|
|
|
|
|
42
|
|
|
/** |
|
43
|
|
|
* Test runCommand. |
|
44
|
|
|
* |
|
45
|
|
|
* @dataProvider provideRunCommand |
|
46
|
|
|
* @param string $cmd Bash command string. |
|
47
|
|
|
* @param array $options Options for `runCommand()`. |
|
48
|
|
|
* @param int $expectExitCode Expected exit code. |
|
49
|
|
|
* @param string $expectStdout Expected output from the command. |
|
50
|
|
|
* @param string $expectStderr Expected output from the command. |
|
51
|
|
|
* @param string $expectOutput Expected output to the console. |
|
52
|
|
|
* @param int $verbosity Output buffer verbosity. |
|
53
|
|
|
*/ |
|
54
|
|
|
public function testRunCommand( $cmd, $options, $expectExitCode, $expectStdout, $expectStderr, $expectOutput, $verbosity = BufferedOutput::VERBOSITY_DEBUG ) { |
|
55
|
|
|
$sh = ( new ExecutableFinder() )->find( 'sh' ); |
|
56
|
|
|
if ( ! $sh ) { |
|
57
|
|
|
$this->markTestSkipped( 'This test requires a Posix shell' ); |
|
58
|
|
|
} |
|
59
|
|
|
|
|
60
|
|
|
$expectOutput = strtr( $expectOutput, array( '{SHELL}' => $sh ) ); |
|
61
|
|
|
|
|
62
|
|
|
$output = new BufferedOutput(); |
|
63
|
|
|
$output->setVerbosity( $verbosity ); |
|
64
|
|
|
$helper = new DebugFormatterHelper(); |
|
65
|
|
|
$ret = Utils::runCommand( array( $sh, '-c', $cmd ), $output, $helper, $options ); |
|
66
|
|
|
$this->assertInstanceOf( Process::class, $ret ); |
|
67
|
|
|
$this->assertSame( $expectExitCode, $ret->getExitCode() ); |
|
68
|
|
|
$this->assertSame( $expectStdout, $ret->getOutput() ); |
|
69
|
|
|
$this->assertSame( $expectStderr, $ret->getErrorOutput() ); |
|
70
|
|
|
$this->assertSame( $expectOutput, $output->fetch() ); |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
|
|
/** |
|
74
|
|
|
* Data provider for testRunCommand. |
|
75
|
|
|
*/ |
|
76
|
|
|
public function provideRunCommand() { |
|
77
|
|
|
$tmp = sys_get_temp_dir(); |
|
78
|
|
|
|
|
79
|
|
|
return array( |
|
80
|
|
|
'true' => array( |
|
81
|
|
|
'true', |
|
82
|
|
|
array(), |
|
83
|
|
|
0, |
|
84
|
|
|
'', |
|
85
|
|
|
'', |
|
86
|
|
|
" RUN '{SHELL}' '-c' 'true'\n\n", |
|
87
|
|
|
), |
|
88
|
|
|
'false' => array( |
|
89
|
|
|
'false', |
|
90
|
|
|
array(), |
|
91
|
|
|
1, |
|
92
|
|
|
'', |
|
93
|
|
|
'', |
|
94
|
|
|
" RUN '{SHELL}' '-c' 'false'\n\n", |
|
95
|
|
|
), |
|
96
|
|
|
'true, non-debug verbosity' => array( |
|
97
|
|
|
'true', |
|
98
|
|
|
array(), |
|
99
|
|
|
0, |
|
100
|
|
|
'', |
|
101
|
|
|
'', |
|
102
|
|
|
'', |
|
103
|
|
|
BufferedOutput::VERBOSITY_VERY_VERBOSE, |
|
104
|
|
|
), |
|
105
|
|
|
'With cwd' => array( |
|
106
|
|
|
'pwd', |
|
107
|
|
|
array( |
|
108
|
|
|
'cwd' => $tmp, |
|
109
|
|
|
), |
|
110
|
|
|
0, |
|
111
|
|
|
"$tmp\n", |
|
112
|
|
|
'', |
|
113
|
|
|
" RUN '{SHELL}' '-c' 'pwd'\n\n OUT $tmp\n OUT \n", |
|
114
|
|
|
), |
|
115
|
|
|
'With env' => array( |
|
116
|
|
|
'echo "$FOO" >&2', |
|
117
|
|
|
array( |
|
118
|
|
|
'env' => array( 'FOO' => 'FOOBAR' ), |
|
119
|
|
|
), |
|
120
|
|
|
0, |
|
121
|
|
|
'', |
|
122
|
|
|
"FOOBAR\n", |
|
123
|
|
|
" RUN '{SHELL}' '-c' 'echo \"\$FOO\" >&2'\n\n ERR FOOBAR\n ERR \n", |
|
124
|
|
|
), |
|
125
|
|
|
'With input' => array( |
|
126
|
|
|
'while IFS= read X; do echo "{{$X}}"; done', |
|
127
|
|
|
array( |
|
128
|
|
|
'input' => "A\nB\nC\n", |
|
129
|
|
|
), |
|
130
|
|
|
0, |
|
131
|
|
|
"{{A}}\n{{B}}\n{{C}}\n", |
|
132
|
|
|
'', |
|
133
|
|
|
'', |
|
134
|
|
|
BufferedOutput::VERBOSITY_NORMAL, |
|
135
|
|
|
), |
|
136
|
|
|
); |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
/** |
|
140
|
|
|
* Test runCommand with a timeout. |
|
141
|
|
|
*/ |
|
142
|
|
|
public function testRunCommand_timeout() { |
|
143
|
|
|
$sleep = ( new ExecutableFinder() )->find( 'sleep' ); |
|
144
|
|
|
if ( ! $sleep ) { |
|
145
|
|
|
$this->markTestSkipped( 'This test requires a "sleep" command' ); |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
$output = new BufferedOutput(); |
|
149
|
|
|
$output->setVerbosity( BufferedOutput::VERBOSITY_DEBUG ); |
|
150
|
|
|
$helper = new DebugFormatterHelper(); |
|
151
|
|
|
$this->expectException( ProcessTimedOutException::class ); |
|
152
|
|
|
Utils::runCommand( array( $sleep, '1' ), $output, $helper, array( 'timeout' => 0.1 ) ); |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* Test loadChangeFile. |
|
157
|
|
|
* |
|
158
|
|
|
* @dataProvider provideLoadChangeFile |
|
159
|
|
|
* @param string $contents File contents. |
|
160
|
|
|
* @param array|\RuntimeException $expect Expected output. |
|
161
|
|
|
* @param array $expectDiagnostics Expected diagnostics. |
|
162
|
|
|
*/ |
|
163
|
|
|
public function testLoadChangeFile( $contents, $expect, $expectDiagnostics = array() ) { |
|
164
|
|
|
$temp = tempnam( sys_get_temp_dir(), 'phpunit-testLoadChangeFile-' ); |
|
165
|
|
|
try { |
|
166
|
|
|
file_put_contents( $temp, $contents ); |
|
167
|
|
|
if ( ! $expect instanceof \RuntimeException ) { |
|
168
|
|
|
$diagnostics = null; // Make phpcs happy. |
|
169
|
|
|
$this->assertSame( $expect, Utils::loadChangeFile( $temp, $diagnostics ) ); |
|
170
|
|
|
$this->assertSame( $expectDiagnostics, $diagnostics ); |
|
171
|
|
|
} else { |
|
172
|
|
|
try { |
|
173
|
|
|
Utils::loadChangeFile( $temp ); |
|
174
|
|
|
$this->fail( 'Expcected exception not thrown' ); |
|
175
|
|
|
} catch ( \RuntimeException $ex ) { |
|
176
|
|
|
$this->assertInstanceOf( get_class( $expect ), $ex ); |
|
177
|
|
|
$this->assertMatchesRegularExpression( $expect->getMessage(), $ex->getMessage() ); |
|
178
|
|
|
$this->assertObjectHasAttribute( 'fileLine', $ex ); |
|
179
|
|
|
$this->assertSame( $expect->fileLine, $ex->fileLine ); |
|
|
|
|
|
|
180
|
|
|
} |
|
181
|
|
|
} |
|
182
|
|
|
} finally { |
|
183
|
|
|
unlink( $temp ); |
|
184
|
|
|
} |
|
185
|
|
|
} |
|
186
|
|
|
|
|
187
|
|
|
/** |
|
188
|
|
|
* Data provider for testLoadChangeFile. |
|
189
|
|
|
*/ |
|
190
|
|
|
public function provideLoadChangeFile() { |
|
191
|
|
|
$ex = function ( $msg, $line ) { |
|
192
|
|
|
$ret = new \RuntimeException( $msg ); |
|
193
|
|
|
$ret->fileLine = $line; |
|
|
|
|
|
|
194
|
|
|
return $ret; |
|
195
|
|
|
}; |
|
196
|
|
|
return array( |
|
197
|
|
|
'Normal file' => array( |
|
198
|
|
|
"Foo: bar baz\nQuux: XXX\n\nEntry\n", |
|
199
|
|
|
array( |
|
200
|
|
|
'Foo' => 'bar baz', |
|
201
|
|
|
'Quux' => 'XXX', |
|
202
|
|
|
'' => 'Entry', |
|
203
|
|
|
), |
|
204
|
|
|
array( |
|
205
|
|
|
'warnings' => array(), |
|
206
|
|
|
'lines' => array( |
|
207
|
|
|
'Foo' => 1, |
|
208
|
|
|
'Quux' => 2, |
|
209
|
|
|
'' => 4, |
|
210
|
|
|
), |
|
211
|
|
|
), |
|
212
|
|
|
), |
|
213
|
|
|
'File with no entry' => array( |
|
214
|
|
|
"Foo: bar baz\nQuux: XXX\n\n\n\n", |
|
215
|
|
|
array( |
|
216
|
|
|
'Foo' => 'bar baz', |
|
217
|
|
|
'Quux' => 'XXX', |
|
218
|
|
|
'' => '', |
|
219
|
|
|
), |
|
220
|
|
|
array( |
|
221
|
|
|
'warnings' => array(), |
|
222
|
|
|
'lines' => array( |
|
223
|
|
|
'Foo' => 1, |
|
224
|
|
|
'Quux' => 2, |
|
225
|
|
|
'' => 6, |
|
226
|
|
|
), |
|
227
|
|
|
), |
|
228
|
|
|
), |
|
229
|
|
|
'Trimmed file with no entry' => array( |
|
230
|
|
|
"Foo: bar baz\nQuux: XXX", |
|
231
|
|
|
array( |
|
232
|
|
|
'Foo' => 'bar baz', |
|
233
|
|
|
'Quux' => 'XXX', |
|
234
|
|
|
'' => '', |
|
235
|
|
|
), |
|
236
|
|
|
array( |
|
237
|
|
|
'warnings' => array(), |
|
238
|
|
|
'lines' => array( |
|
239
|
|
|
'Foo' => 1, |
|
240
|
|
|
'Quux' => 2, |
|
241
|
|
|
'' => 2, |
|
242
|
|
|
), |
|
243
|
|
|
), |
|
244
|
|
|
), |
|
245
|
|
|
'File with no headers' => array( |
|
246
|
|
|
"\nEntry\n", |
|
247
|
|
|
array( |
|
248
|
|
|
'' => 'Entry', |
|
249
|
|
|
), |
|
250
|
|
|
array( |
|
251
|
|
|
'warnings' => array(), |
|
252
|
|
|
'lines' => array( |
|
253
|
|
|
'' => 2, |
|
254
|
|
|
), |
|
255
|
|
|
), |
|
256
|
|
|
), |
|
257
|
|
|
'Empty file' => array( |
|
258
|
|
|
'', |
|
259
|
|
|
array( |
|
260
|
|
|
'' => '', |
|
261
|
|
|
), |
|
262
|
|
|
array( |
|
263
|
|
|
'warnings' => array(), |
|
264
|
|
|
'lines' => array( |
|
265
|
|
|
'' => 1, |
|
266
|
|
|
), |
|
267
|
|
|
), |
|
268
|
|
|
), |
|
269
|
|
|
'File with wrapped header' => array( |
|
270
|
|
|
"Foo: bar\n baz\n \n ok?\n\nThis is a multiline\nentry.\n", |
|
271
|
|
|
array( |
|
272
|
|
|
'Foo' => 'bar baz ok?', |
|
273
|
|
|
'' => "This is a multiline\nentry.", |
|
274
|
|
|
), |
|
275
|
|
|
array( |
|
276
|
|
|
'warnings' => array(), |
|
277
|
|
|
'lines' => array( |
|
278
|
|
|
'Foo' => 1, |
|
279
|
|
|
'' => 6, |
|
280
|
|
|
), |
|
281
|
|
|
), |
|
282
|
|
|
), |
|
283
|
|
|
'File with duplicate headers' => array( |
|
284
|
|
|
"Foo: A\nFoo: B\nBar:\nFoo: C\nBar: X\n\nEntry\n", |
|
285
|
|
|
array( |
|
286
|
|
|
'Foo' => 'A', |
|
287
|
|
|
'Bar' => '', |
|
288
|
|
|
'' => 'Entry', |
|
289
|
|
|
), |
|
290
|
|
|
array( |
|
291
|
|
|
'warnings' => array( |
|
292
|
|
|
array( 'Duplicate header "Foo", previously seen on line 1.', 2 ), |
|
293
|
|
|
array( 'Duplicate header "Foo", previously seen on line 1.', 4 ), |
|
294
|
|
|
array( 'Duplicate header "Bar", previously seen on line 3.', 5 ), |
|
295
|
|
|
), |
|
296
|
|
|
'lines' => array( |
|
297
|
|
|
'Foo' => 1, |
|
298
|
|
|
'Bar' => 3, |
|
299
|
|
|
'' => 7, |
|
300
|
|
|
), |
|
301
|
|
|
), |
|
302
|
|
|
), |
|
303
|
|
|
'Invalid header' => array( |
|
304
|
|
|
"Foo: bar\nWrapped: A\n B\n C\nEntry.\n", |
|
305
|
|
|
$ex( '/^Invalid header.$/', 5 ), |
|
306
|
|
|
), |
|
307
|
|
|
); |
|
308
|
|
|
} |
|
309
|
|
|
|
|
310
|
|
|
/** |
|
311
|
|
|
* Test "bad filename" paths in loadChangeFile. |
|
312
|
|
|
*/ |
|
313
|
|
|
public function testLoadChangeFile_badFile() { |
|
314
|
|
|
try { |
|
315
|
|
|
Utils::loadChangeFile( 'doesnotexist/reallydoesnotexist.txt' ); |
|
316
|
|
|
$this->fail( 'Expected exception not thrown' ); |
|
317
|
|
|
} catch ( \RuntimeException $ex ) { |
|
318
|
|
|
$this->assertSame( 'File does not exist.', $ex->getMessage() ); |
|
319
|
|
|
$this->assertNull( $ex->fileLine ); |
|
|
|
|
|
|
320
|
|
|
} |
|
321
|
|
|
try { |
|
322
|
|
|
Utils::loadChangeFile( '.' ); |
|
323
|
|
|
$this->fail( 'Expected exception not thrown' ); |
|
324
|
|
|
} catch ( \RuntimeException $ex ) { |
|
325
|
|
|
$this->assertSame( 'Expected a file, got dir.', $ex->getMessage() ); |
|
326
|
|
|
$this->assertNull( $ex->fileLine ); |
|
327
|
|
|
} |
|
328
|
|
|
|
|
329
|
|
|
// Try to create an unreadable file. May fail if tests are running as root. |
|
330
|
|
|
$temp = tempnam( sys_get_temp_dir(), 'phpunit-testLoadChangeFile-' ); |
|
331
|
|
|
try { |
|
332
|
|
|
chmod( $temp, 0000 ); |
|
333
|
|
|
if ( ! is_readable( $temp ) ) { |
|
334
|
|
|
try { |
|
335
|
|
|
Utils::loadChangeFile( $temp ); |
|
336
|
|
|
$this->fail( 'Expected exception not thrown' ); |
|
337
|
|
|
} catch ( \RuntimeException $ex ) { |
|
338
|
|
|
$this->assertSame( 'File is not readable.', $ex->getMessage() ); |
|
339
|
|
|
$this->assertNull( $ex->fileLine ); |
|
340
|
|
|
} |
|
341
|
|
|
} |
|
342
|
|
|
} finally { |
|
343
|
|
|
unlink( $temp ); |
|
344
|
|
|
} |
|
345
|
|
|
} |
|
346
|
|
|
|
|
347
|
|
|
} |
|
348
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.