Completed
Push — add/anchor-fm-badge-insertion ( 367060...0b7190 )
by
unknown
180:57 queued 171:53
created

Test_Autoloader_Scenarios::tear_down()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
1
<?php // phpcs:ignore WordPress.Files.FileName
2
/**
3
 * Integration test suite for the full autoloader.
4
 *
5
 * @package automattic/jetpack-autoloader
6
 */
7
8
use Automattic\Jetpack\Autoloader\AutoloadFileWriter;
9
use Jetpack\AutoloaderTestData\Plugin\Test;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Test.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use PHPUnit\Framework\TestCase;
11
12
/**
13
 * Test suite class for testing the autoloader in different deployment configurations.
14
 *
15
 * @runTestsInSeparateProcesses Ensure each test has a fresh process to work with, replicating real requests.
16
 * @preserveGlobalState disabled
17
 */
18
class Test_Autoloader_Scenarios extends TestCase {
19
20
	/**
21
	 * Indicates whether or not the autoloader has been reset by a load operation.
22
	 *
23
	 * @var bool
24
	 */
25
	private $autoloader_reset;
26
27
	/**
28
	 * Setup runs before each test.
29
	 *
30
	 * @before
31
	 */
32
	public function set_up() {
33
		// We need to make sure there's an autoloader containing the current files for testing.
34
		$this->generate_autoloader( 'plugin_current' );
35
		$this->generate_autoloader( 'plugin_newer' );
36
	}
37
38
	/**
39
	 * Teardown runs after each test.
40
	 *
41
	 * @after
42
	 */
43
	public function tear_down() {
44
		cleanup_test_wordpress_data();
45
46
		// Make sure all of the tests have no cache file.
47
		// phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
48
		@unlink( TEST_DATA_PATH . '/cache/jetpack-autoloader-' . Plugins_Handler::TRANSIENT_KEY . '.json' );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
49
		@rmdir( TEST_DATA_PATH . '/cache' );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
50
		// phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
51
	}
52
53
	/**
54
	 * Tests that the autoloader works as expected.
55
	 */
56
	public function test_autoloader_init() {
57
		$this->activate_plugin( 'plugin_current' );
58
59
		$this->load_autoloader( 'plugin_current' );
60
61
		$this->assertAutoloaderVersion( '2.6.0.0' );
62
63
		$this->shutdown_autoloader( true );
64
		$this->assertAutoloaderCache( array( 'plugin_current' ) );
65
	}
66
67
	/**
68
	 * Tests that the autoloader does not initialize twice.
69
	 */
70
	public function test_autoloader_init_once() {
71
		$this->activate_plugin( 'plugin_current' );
72
73
		$this->load_autoloader( 'plugin_current' );
74
		$this->load_autoloader( 'plugin_current' );
75
76
		$this->assertFalse( $this->autoloader_reset );
77
		$this->assertAutoloaderVersion( '2.6.0.0' );
78
79
		$this->shutdown_autoloader( true );
80
		$this->assertAutoloaderCache( array( 'plugin_current' ) );
81
	}
82
83
	/**
84
	 * Tests that the autoloader loads the latest when loading an older one first.
85
	 */
86 View Code Duplication
	public function test_autoloader_loads_latest() {
87
		$this->activate_plugin( 'plugin_current' );
88
		$this->activate_plugin( 'plugin_newer' );
89
90
		$this->load_autoloader( 'plugin_current' );
91
		$this->load_autoloader( 'plugin_newer' );
92
93
		$this->assertFalse( $this->autoloader_reset );
94
		$this->assertAutoloaderVersion( '2.7.0.0' );
95
96
		$this->shutdown_autoloader( true );
97
		$this->assertAutoloaderCache( array( 'plugin_current', 'plugin_newer' ) );
98
	}
99
100
	/**
101
	 * Tests that the autoloader does not conflict with a v1 autoloader.
102
	 */
103
	public function test_autoloader_overrides_v1() {
104
		$this->activate_plugin( 'plugin_v1' );
105
		$this->activate_plugin( 'plugin_current' );
106
107
		$this->load_autoloader( 'plugin_v1' );
108
		$this->load_autoloader( 'plugin_current' );
109
110
		$this->assertTrue( $this->autoloader_reset );
111
		$this->assertAutoloaderVersion( '2.6.0.0' );
112
113
		$this->shutdown_autoloader( true );
114
		$this->assertAutoloaderCache( array( 'plugin_current' ) );
115
	}
116
117
	/**
118
	 * Tests that the autoloader is not reset when an older V2 initializes after the latest.
119
	 */
120 View Code Duplication
	public function test_autoloader_not_reset_by_older_v2() {
121
		$this->activate_plugin( 'plugin_current' );
122
		$this->activate_plugin( 'plugin_v2_2_0' );
123
124
		$this->load_autoloader( 'plugin_current' );
125
		$this->load_autoloader( 'plugin_v2_2_0' );
126
127
		$this->assertFalse( $this->autoloader_reset );
128
		$this->assertAutoloaderVersion( '2.6.0.0' );
129
130
		$this->shutdown_autoloader( true );
131
		$this->assertAutoloaderCache( array( 'plugin_current', 'plugin_v2_2_0' ) );
132
	}
133
134
	/**
135
	 * Tests that the autoloader resets when an unknown plugin is encountered, and that it does not
136
	 * reset a second time once the unknown plugin has been recorded.
137
	 */
138
	public function test_autoloader_resets_when_unknown_plugin_is_encountered() {
139
		$this->activate_plugin( 'plugin_current' );
140
141
		$this->load_autoloader( 'plugin_current' );
142
		$this->load_autoloader( 'plugin_newer' );
143
144
		$this->assertTrue( $this->autoloader_reset );
145
		$this->assertAutoloaderVersion( '2.7.0.0' );
146
147
		$this->shutdown_autoloader( true );
148
		$this->assertAutoloaderCache( array( 'plugin_current', 'plugin_newer' ) );
149
	}
150
151
	/**
152
	 * Tests that the autoloader uses the cache to avoid resetting when an known plugin is encountered.
153
	 */
154 View Code Duplication
	public function test_autoloader_uses_cache_to_avoid_resets() {
155
		$this->activate_plugin( 'plugin_current' );
156
157
		// Write the plugins to the cache so that the autoloader will see them.
158
		$this->cache_plugins( array( 'plugin_current', 'plugin_newer' ) );
159
160
		$this->load_autoloader( 'plugin_current' );
161
		$this->load_autoloader( 'plugin_newer' );
162
163
		$this->assertFalse( $this->autoloader_reset );
164
		$this->assertAutoloaderVersion( '2.7.0.0' );
165
166
		$this->shutdown_autoloader( true );
167
		$this->assertAutoloaderCache( array( 'plugin_current', 'plugin_newer' ) );
168
	}
169
170
	/**
171
	 * Tests that the autoloader updates the cache.
172
	 */
173
	public function test_autoloader_updates_cache() {
174
		$this->activate_plugin( 'plugin_current' );
175
176
		// Write an empty cache so we can make sure it was updated.
177
		$this->cache_plugins( array() );
178
179
		$this->load_autoloader( 'plugin_current' );
180
		$this->shutdown_autoloader( true );
181
182
		$this->assertAutoloaderVersion( '2.6.0.0' );
183
		$this->assertAutoloaderCache( array( 'plugin_current' ) );
184
	}
185
186
	/**
187
	 * Tests that the autoloader does not update the cache if it has not changed.
188
	 */
189
	public function test_autoloader_does_not_update_unchanged_cache() {
190
		$this->activate_plugin( 'plugin_current' );
191
192
		// Write a cache that we can use when loading the autoloader.
193
		$this->cache_plugins( array( 'plugin_current' ) );
194
195
		$this->load_autoloader( 'plugin_current' );
196
197
		// Erase the cache and then shut the autoloader down.
198
		// It shouldn't update the transient since the cached plugins changed.
199
		$this->cache_plugins( array() );
200
201
		$this->shutdown_autoloader( true );
202
203
		$this->assertAutoloaderVersion( '2.6.0.0' );
204
		$this->assertAutoloaderCache( array() );
205
	}
206
207
	/**
208
	 * Tests that the autoloader empties the cache if shutdown happens before plugins_loaded.
209
	 */
210
	public function test_autoloader_empties_cache_on_early_shutdown() {
211
		$this->activate_plugin( 'plugin_current' );
212
213
		// Write a cache that we can use when loading the autoloader.
214
		$this->cache_plugins( array( 'plugin_current' ) );
215
216
		$this->load_autoloader( 'plugin_current' );
217
218
		// Make sure to shutdown prematurely so that the cache will be erased instead of saved.
219
		$this->shutdown_autoloader( false );
220
221
		$this->assertAutoloaderVersion( '2.6.0.0' );
222
		$this->assertAutoloaderCache( array() );
223
	}
224
225
	/**
226
	 * Generates a new autoloader from the current source files for the "plugin_current" plugin.
227
	 *
228
	 * @param string $plugin The plugin to generate the autoloader for.
229
	 */
230
	private function generate_autoloader( $plugin ) {
231
		// phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
232
233
		$autoload_dir = TEST_DATA_PATH . '/plugins/' . $plugin . '/vendor/jetpack-autoloader';
234
235
		// Erase the existing autoloader files if they exist.
236
		@mkdir( $autoload_dir );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
237
		$files = scandir( $autoload_dir );
238
		foreach ( $files as $file ) {
239
			@unlink( $autoload_dir . '/' . $file );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
240
		}
241
		@unlink( $autoload_dir . '/../autoload_packages.php' );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
242
243
		// Copy the autoloader files to the plugin directory.
244
		$suffix = md5( uniqid( '', true ) );
245
		AutoloadFileWriter::copyAutoloaderFiles( null, $autoload_dir, $suffix );
246
247
		// phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
248
	}
249
250
	/**
251
	 * "Activate" a plugin so that the autoloader can detect it.
252
	 *
253
	 * @param string $plugin The plugin we want to activate.
254
	 */
255
	private function activate_plugin( $plugin ) {
256
		$active_plugins = get_option( 'active_plugins' );
257
		if ( ! $active_plugins ) {
258
			$active_plugins = array();
259
		}
260
261
		$active_plugins[] = $plugin . '/' . $plugin . '.php';
262
263
		add_test_option( 'active_plugins', $active_plugins );
264
	}
265
266
	/**
267
	 * Loads the given autoloader and initializes it.
268
	 *
269
	 * @param string $plugin The plugin to load the autoloader from.
270
	 */
271
	private function load_autoloader( $plugin ) {
272
		// We're going to use changes in the hooks to detect if the autoloader has been reset.
273
		global $test_filters;
274
		$temp = $test_filters;
275
276
		require TEST_DATA_PATH . '/plugins/' . $plugin . '/vendor/autoload_packages.php';
277
278
		// The first time the autoloader is loaded we didn't reset.
279
		if ( ! isset( $this->autoloader_reset ) ) {
280
			$this->autoloader_reset = false;
281
		} else {
282
			$this->autoloader_reset = $temp !== $test_filters;
283
		}
284
	}
285
286
	/**
287
	 * Writes the plugins to the cache so that they can be read by the autoloader.
288
	 *
289
	 * @param string[] $plugins The plugins to cache.
290
	 */
291
	private function cache_plugins( $plugins ) {
292
		$paths = array();
293
		foreach ( $plugins as $plugin ) {
294
			$paths[] = '{{WP_PLUGIN_DIR}}/' . $plugin;
295
		}
296
297
		// The cached plugins are always sorted!
298
		sort( $paths );
299
300
		set_transient( Plugins_Handler::TRANSIENT_KEY, $paths );
301
	}
302
303
	/**
304
	 * Runs the autoloader's shutdown action.
305
	 *
306
	 * @param bool $plugins_loaded Indicates whether or not the plugins_loaded action should have fired.
307
	 */
308
	private function shutdown_autoloader( $plugins_loaded = true ) {
309
		if ( $plugins_loaded ) {
310
			do_action( 'plugins_loaded' );
311
		}
312
313
		do_action( 'shutdown' );
314
	}
315
316
	/**
317
	 * Asserts that the latest autoloader version is the one given.
318
	 *
319
	 * @param string $version The version to check.
320
	 */
321
	private function assertAutoloaderVersion( $version ) {
322
		$this->assertTrue( class_exists( Test::class ) );
323
		$this->assertEquals( $version, Test::VERSION, 'The class version is incorrect.' );
324
325
		global $jetpack_autoloader_latest_version;
326
		$this->assertEquals( $version, $jetpack_autoloader_latest_version, 'The autoloader version is incorrect.' );
327
	}
328
329
	/**
330
	 * Asserts that the autoloader cache contains the plugins given.
331
	 *
332
	 * @param array $plugins The plugins to check the cache for.
333
	 */
334
	private function assertAutoloaderCache( $plugins ) {
335
		$paths = array();
336
		foreach ( $plugins as $plugin ) {
337
			$paths[] = '{{WP_PLUGIN_DIR}}/' . $plugin;
338
		}
339
340
		// The cached plugins are always sorted!
341
		sort( $paths );
342
343
		$this->assertTrue(
344
			test_has_transient( Plugins_Handler::TRANSIENT_KEY, $paths ),
345
			'The autoloader cache does not match'
346
		);
347
	}
348
}
349