Completed
Push — renovate/major-react-monorepo ( 880c2b...a6f86c )
by
unknown
355:09 queued 345:32
created

Acceptance_Test_Case::set_up()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Base class file for all acceptance tests.
4
 *
5
 * @package automattic/jetpack-autoloader
6
 */
7
8
use Automattic\Jetpack\Autoloader\jpCurrent\Path_Processor;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Path_Processor.

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...
9
use Automattic\Jetpack\Autoloader\jpCurrent\Plugins_Handler;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Plugins_Handler.

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
 * Class Acceptance_Test_Case.
14
 */
15
abstract class Acceptance_Test_Case extends TestCase {
16
17
	/**
18
	 * A constant for identifying the current plugin installed as an mu-plugin in the tests.
19
	 */
20
	const CURRENT_MU = Test_Plugin_Factory::CURRENT . 'mu';
21
22
	/**
23
	 * An array containing the versions and paths of all of the autoloaders we have installed for the test class.
24
	 *
25
	 * @var string[]
26
	 */
27
	private $installed_autoloaders;
28
29
	/**
30
	 * An array containing the versions and paths of autoloaders that have been symlinked.
31
	 *
32
	 * @var string[]
33
	 */
34
	private $symlinked_autoloaders;
35
36
	/**
37
	 * Setup runs before each test.
38
	 *
39
	 * @before
40
	 */
41
	public function set_up() {
42
		// Ensure that the current autoloader is always installed.
43
		$this->installed_autoloaders = array( Test_Plugin_Factory::CURRENT => TEST_PLUGIN_DIR );
0 ignored issues
show
Documentation Bug introduced by
It seems like array(\Test_Plugin_Facto...ENT => TEST_PLUGIN_DIR) of type array is incompatible with the declared type array<integer,string> of property $installed_autoloaders.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
44
		$this->symlinked_autoloaders = array();
45
46
		// We need to install the current plugin as an mu-plugin in many tests.
47
		$this->install_autoloaders( self::CURRENT_MU );
48
	}
49
50
	/**
51
	 * Teardown runs after each test.
52
	 *
53
	 * @after
54
	 */
55
	public function tear_down() {
56
		// Erase all of the directory symlinks since we're done with them.
57
		foreach ( $this->symlinked_autoloaders as $dir ) {
58
            // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
59
			@unlink( $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...
60
		}
61
	}
62
63
	/**
64
	 * Installs the given autoloader or autoloaders so that they can be used by tests.
65
	 *
66
	 * @param string|string[] $version_or_versions The version or array of versions of the autoloader to install.
67
	 *                                             A suffix of 'mu' designates that the plugin should be an mu-plugin.
68
	 */
69
	protected function install_autoloaders( $version_or_versions ) {
70
		if ( ! is_array( $version_or_versions ) ) {
71
			$version_or_versions = array( $version_or_versions );
72
		}
73
74
		foreach ( $version_or_versions as $version ) {
75
			if ( isset( $this->installed_autoloaders[ $version ] ) ) {
76
				$this->fail( 'The plugin has already been installed.' );
77
			}
78
79
			// A suffix of 'mu' means that the plugin should be installed to mu-plugins.
80
			$is_mu_plugin = 'mu' === substr( $version, -2 );
81
82
			$path                                    = Test_Plugin_Factory::create_test_plugin(
83
				$is_mu_plugin,
84
				$is_mu_plugin ? substr( $version, 0, -2 ) : $version
85
			)->make();
86
			$this->installed_autoloaders[ $version ] = $path;
87
		}
88
	}
89
90
	/**
91
	 * Installs a symlink to a plugin version.
92
	 *
93
	 * @param string $version      The version of the autoloader we want to symlink to.
94
	 * @param string $is_mu_plugin Whether or not the symlink should be an mu-plugin.
95
	 * @param string $symlink_key  The key for the symlink in the installed plugin list.
96
	 */
97
	protected function install_autoloader_symlink( $version, $is_mu_plugin, $symlink_key ) {
98
		if ( isset( $this->symlinked_autoloaders[ $symlink_key ] ) ) {
99
			$this->fail( 'The symlink has already been installed.' );
100
		}
101
102
		// The location of the symlink depends on whether it's an mu-plugin or not.
103
		if ( $is_mu_plugin ) {
104
			$symlink_dir = WPMU_PLUGIN_DIR . DIRECTORY_SEPARATOR . $symlink_key;
105
		} else {
106
			$symlink_dir = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $symlink_key;
107
		}
108
109
		// Create the symlink to the plugin's version.
110
		$plugin_dir = $this->get_autoloader_path( $version );
111
112
        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
113
		@symlink( $plugin_dir, $symlink_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...
114
115
		// Record it as installed but also as symlinked so that it can be cleaned up.
116
		$this->installed_autoloaders[ $symlink_key ] = $symlink_dir;
117
		$this->symlinked_autoloaders[ $symlink_key ] = $symlink_dir;
118
	}
119
120
	/**
121
	 * Fetches the path for an installed autoloader.
122
	 *
123
	 * @param string $version The version of autoloader we want a path to.
124
	 * @return string The path to the autoloader.
125
	 */
126
	protected function get_autoloader_path( $version ) {
127
		if ( ! isset( $this->installed_autoloaders[ $version ] ) ) {
128
			$this->fail( "The $version autoloader has not been installed." );
129
		}
130
131
		return $this->installed_autoloaders[ $version ];
132
	}
133
134
	/**
135
	 * Loads an autoloader and tracks whether or not a reset occurred.
136
	 *
137
	 * @param string $version The version of the autoloader we want to load.
138
	 */
139
	protected function load_plugin_autoloader( $version ) {
140
		$plugin_dir = $this->get_autoloader_path( $version );
141
142
		// We're going to store a value in the classmap to detect when a reset has occurred after loading the autoloader.
143
		// This isn't perfect (it won't catch successive resets from a new autoloader discovering newer autoloaders) but
144
		// it will at least catch the most common reset scenarios that we can build assertions on.
145
		global $jetpack_packages_classmap;
146
		$reset_count = isset( $jetpack_packages_classmap ) ? $jetpack_packages_classmap['reset_count'] : null;
147
148
		require_once $plugin_dir . '/vendor/autoload_packages.php';
149
150
		// Since the classmap was not erased we can assume no reset occurred.
151
		if ( isset( $jetpack_packages_classmap['reset_count'] ) ) {
152
			return;
153
		}
154
155
		// Since we can assume after every load we set the count we know a null value
156
		// means this was the first time the autoloader was executed.
157
		if ( ! isset( $reset_count ) ) {
158
			$jetpack_packages_classmap['reset_count'] = 0;
159
		} else {
160
			$jetpack_packages_classmap['reset_count'] = $reset_count + 1;
161
		}
162
	}
163
164
	/**
165
	 * Executes all of the given autoloader versions and triggers a shutdown.
166
	 *
167
	 * Note: This method sorts all of the mu-plugins to the front of the array to replicate WordPress' loading order.
168
	 *
169
	 * @param string[] $versions             The array of versions to execute in the order they should be loaded.
170
	 * @param bool     $after_plugins_loaded Whether or not the shutdown should be after the plugins_loaded action.
171
	 */
172
	protected function execute_autoloader_chain( $versions, $after_plugins_loaded ) {
173
		// Place all of the mu-plugins at the front of the array to replicate WordPress' load order.
174
		// Take care not to affect the order in any other way so that the caller can define the
175
		// rest of the load order semantics. This functionality is mostly to prevent accidents.
176
		$mu_versions = array_filter(
177
			$versions,
178
			function ( $version ) {
179
				return 'mu' === substr( $version, -2 );
180
			}
181
		);
182
183
		foreach ( $mu_versions as $version ) {
184
			$this->load_plugin_autoloader( $version );
185
		}
186
		foreach ( $versions as $key => $version ) {
187
			// We've already loaded these!
188
			if ( isset( $mu_versions[ $key ] ) ) {
189
				continue;
190
			}
191
192
			$this->load_plugin_autoloader( $version );
193
		}
194
195
		$this->trigger_shutdown( $after_plugins_loaded );
196
	}
197
198
	/**
199
	 * Adds an autoloader plugin to the activated list.
200
	 *
201
	 * @param string $version  The version of autoloader that we want to activate.
202
	 * @param bool   $sitewide Indicates whether or not the plugin should be site active.
203
	 */
204
	protected function activate_autoloader( $version, $sitewide = false ) {
205
		$plugin_dir = $this->get_autoloader_path( $version );
206
207
		if ( false !== strpos( $plugin_dir, 'mu-plugins' ) ) {
208
			$this->fail( 'Plugins in mu-plugins cannot be activated.' );
209
		}
210
211
		// The slug is the last segment of the path in WordPress plugin slug format.
212
		$slug = basename( $plugin_dir );
213
		$slug = "$slug/$slug.php";
214
215
		// Retrieve the list from the appropriate option.
216
		if ( $sitewide ) {
217
			$active_plugins = get_site_option( 'active_sitewide_plugins' );
218
		} else {
219
			$active_plugins = get_option( 'active_plugins' );
220
		}
221
222
		if ( ! $active_plugins ) {
223
			$active_plugins = array();
224
		}
225
226
		if ( in_array( $slug, $active_plugins, true ) ) {
227
			return;
228
		}
229
230
		$active_plugins[] = $slug;
231
232
		// Make sure to set the list back to the appropriate option.
233
		if ( $sitewide ) {
234
			add_test_site_option( 'active_sitewide_plugins', $active_plugins );
235
		} else {
236
			add_test_option( 'active_plugins', $active_plugins );
237
		}
238
	}
239
240
	/**
241
	 * Triggers a shutdown action for the autoloader.
242
	 *
243
	 * @param bool $after_plugins_loaded Whether or not we should execute 'plugins_loaded' before shutting down.
244
	 */
245
	protected function trigger_shutdown( $after_plugins_loaded ) {
246
		if ( $after_plugins_loaded ) {
247
			do_action( 'plugins_loaded' );
248
		}
249
250
		do_action( 'shutdown' );
251
	}
252
253
	/**
254
	 * Erases the autoloader cache.
255
	 */
256
	protected function erase_cache() {
257
		set_transient( Plugins_Handler::TRANSIENT_KEY, array() );
258
	}
259
260
	/**
261
	 * Adds a version or array of versions to the autoloader cache.
262
	 *
263
	 * @param string|string[] $version_or_versions The version or array of versions of the autoloader that we want to cache.
264
	 */
265
	protected function cache_plugin( $version_or_versions ) {
266
		if ( ! is_array( $version_or_versions ) ) {
267
			$version_or_versions = array( $version_or_versions );
268
		}
269
270
		// Use the path processor so we can replicate the real cache.
271
		$processor = new Path_Processor();
272
273
		$plugins = array();
274
		foreach ( $version_or_versions as $version ) {
275
			$plugin    = $this->get_autoloader_path( $version );
276
			$plugins[] = $processor->tokenize_path_constants( $plugin );
277
		}
278
279
		$transient = get_transient( Plugins_Handler::TRANSIENT_KEY );
280
		if ( empty( $transient ) ) {
281
			$transient = array();
282
		}
283
284
		$transient = array_merge( $transient, $plugins );
285
286
		// The cache is always sorted.
287
		sort( $transient );
288
289
		// Store the cache now that we've added the plugin or plugins.
290
		set_transient( Plugins_Handler::TRANSIENT_KEY, $transient );
291
	}
292
293
	/**
294
	 * Asserts that the autoloader was reset a given number of times.
295
	 *
296
	 * @param int $count The number of resets we expect.
297
	 */
298
	protected function assertAutoloaderResetCount( $count ) {
299
		global $jetpack_packages_classmap;
300
		$reset_count = isset( $jetpack_packages_classmap ) ? $jetpack_packages_classmap['reset_count'] : 0;
301
		$this->assertEquals( $count, $reset_count, 'The number of autoloader resets did not match what was expected.' );
302
	}
303
304
	/**
305
	 * Asserts that the autoloader has been initialized to a specific version.
306
	 *
307
	 * @param string $version The version of the autoloader we expect.
308
	 */
309
	protected function assertAutoloaderVersion( $version ) {
310
		if ( Test_Plugin_Factory::CURRENT === $version || self::CURRENT_MU === $version ) {
311
			$version = Test_Plugin_Factory::VERSION_CURRENT;
312
		}
313
314
		global $jetpack_autoloader_latest_version;
315
		$this->assertEquals( $version, $jetpack_autoloader_latest_version, 'The version of the autoloader did not match what was expected.' );
316
	}
317
318
	/**
319
	 * Asserts that the autoloader is able to provide the given class.
320
	 *
321
	 * @param string $fqn The fully qualified name of the class we want to load.
322
	 */
323
	protected function assertAutoloaderProvidesClass( $fqn ) {
324
		global $jetpack_autoloader_latest_version;
325
		if ( ! isset( $jetpack_autoloader_latest_version ) ) {
326
			$this->fail( 'There is no autoloader loaded to check.' );
327
		}
328
329
		// We're going to check for v1, < v2.4, and >= v2.4 autoloaders directly.
330
		// This is prefereable to trying to load the class because it controls
331
		// for other autoloaders that may have been registered by mistake.
332
		global $jetpack_packages_classes; // v1 used this global.
333
		global $jetpack_packages_classmap; // v2.0 - v2.3 used only the classmap.
334
		global $jetpack_autoloader_loader; // v2.4 introduced the loader with PSR-4 support.
335
336
		$file = null;
337
		if ( isset( $jetpack_autoloader_loader ) ) {
338
			$file = $jetpack_autoloader_loader->find_class_file( $fqn );
339
		} elseif ( isset( $jetpack_packages_classmap[ $fqn ] ) ) {
340
			$file = $jetpack_packages_classmap[ $fqn ];
341
		} elseif ( isset( $jetpack_packages_classes[ $fqn ] ) ) {
342
			$file = $jetpack_packages_classes[ $fqn ];
343
		}
344
345
		$this->assertNotNull( $file, "The autoloader did not provide the '$fqn' class." );
346
	}
347
348
	/**
349
	 * Asserts that the autoloader did not find an unknown plugin.
350
	 *
351
	 * @param string $version The version of autoloader we expect to be known.
352
	 */
353
	protected function assertAutoloaderNotFoundUnknown( $version ) {
354
		$plugin = $this->get_autoloader_path( $version );
355
356
		global $jetpack_autoloader_activating_plugins_paths;
357
		$this->assertNotContains( $plugin, $jetpack_autoloader_activating_plugins_paths, 'The autoloader registered the plugin as unknown.' );
358
	}
359
360
	/**
361
	 * Asserts that the autoloader found an unknown pugin.
362
	 *
363
	 * @param string $version The version of autoloader we expect to be unknown.
364
	 */
365
	protected function assertAutoloaderFoundUnknown( $version ) {
366
		$plugin = $this->get_autoloader_path( $version );
367
368
		global $jetpack_autoloader_activating_plugins_paths;
369
		$this->assertContains( $plugin, $jetpack_autoloader_activating_plugins_paths, 'The autoloader did not register the plugin as unknown.' );
370
	}
371
372
	/**
373
	 * Asserts that the autoloader cache is empty.
374
	 */
375
	protected function assertAutoloaderCacheEmpty() {
376
		$transient = get_transient( Plugins_Handler::TRANSIENT_KEY );
377
		if ( empty( $transient ) ) {
378
			$transient = array();
379
		}
380
381
		$this->assertEmpty( $transient, 'The autoloader cache was not empty.' );
382
	}
383
384
	/**
385
	 * Asserts that the autoloader cache only contains the given version or array of version.
386
	 *
387
	 * @param string|string[] $version_or_versions The version or array of versions that we expect.
388
	 */
389
	protected function assertAutoloaderCacheEquals( $version_or_versions ) {
390
		if ( ! is_array( $version_or_versions ) ) {
391
			$version_or_versions = array( $version_or_versions );
392
		}
393
394
		// Use the path processor so we can replicate the real cache.
395
		$processor = new Path_Processor();
396
397
		$plugins = array();
398
		foreach ( $version_or_versions as $version ) {
399
			$plugin    = $this->get_autoloader_path( $version );
400
			$plugins[] = $processor->tokenize_path_constants( $plugin );
401
		}
402
403
		// The autoloader cache is always sorted.
404
		sort( $plugins );
405
406
		$transient = get_transient( Plugins_Handler::TRANSIENT_KEY );
407
		if ( empty( $transient ) ) {
408
			$transient = array();
409
		}
410
411
		$this->assertEquals( $plugins, $transient, 'The autoloader cache did not match what was expected.' );
412
	}
413
}
414