Completed
Push — add/changelog-tooling ( 3f93f3...c37726 )
by
unknown
250:15 queued 241:25
created

UtilsTest   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 323
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 2

Importance

Changes 0
Metric Value
dl 0
loc 323
rs 10
c 0
b 0
f 0
wmc 15
lcom 0
cbo 2

7 Methods

Rating   Name   Duplication   Size   Complexity  
A test_error_clear_last() 0 9 1
A testRunCommand() 0 18 2
B provideRunCommand() 0 62 1
A testRunCommand_timeout() 0 12 2
A testLoadChangeFile() 0 23 3
B provideLoadChangeFile() 0 119 1
A testLoadChangeFile_badFile() 0 33 5
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 );
0 ignored issues
show
Bug introduced by
The property fileLine does not seem to exist in RuntimeException.

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.

Loading history...
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;
0 ignored issues
show
Bug introduced by
The property fileLine does not seem to exist in RuntimeException.

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.

Loading history...
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 );
0 ignored issues
show
Bug introduced by
The property fileLine does not seem to exist in RuntimeException.

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.

Loading history...
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