Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions components/Blueprints/Steps/class-importcontentstep.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,13 @@ private function importWxr( Runtime $runtime, array $content_definition, Tracker
$import_process = $runtime->create_php_sub_process(
$importer_script .
<<<'PHP'
<?php
run_content_import([
'mode' => 'wxr',
'execution_context_root' => getenv('EXECUTION_CONTEXT') ? getenv('EXECUTION_CONTEXT') : null,
'source' => json_decode(getenv('DATA_SOURCE_DEFINITION'), true),
// @TODO: Support arbitrary media URLs to enable fetching assets during import.
// 'media_url' => 'https://pd.w.org/'
]);
?>
PHP
,
array(
Expand Down
158 changes: 158 additions & 0 deletions components/Blueprints/Tests/Unit/RunnerCleanupTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

namespace WordPress\Blueprints\Tests\Unit;

use PHPUnit\Framework\TestCase;
use WordPress\Blueprints\DataReference\InlineFile;
use WordPress\Blueprints\Logger\NoopLogger;
use WordPress\Blueprints\Runner;
use WordPress\Blueprints\RunnerConfiguration;

use function WordPress\Filesystem\wp_join_unix_paths;
use function WordPress\Filesystem\wp_unix_sys_get_temp_dir;

class RunnerCleanupTest extends TestCase {
/**
* @var string[]
*/
private $paths_to_remove = array();

/**
* @after
*/
public function tearDown(): void {
unset( $GLOBALS['wp_filter']['blueprint.target_resolved'] );

foreach ( array_reverse( $this->paths_to_remove ) as $path ) {
$this->remove_directory( $path );
}
}

public function test_removes_temporary_workspace_after_successful_run() {
$logger = new RecordingLogger();
$runner = $this->create_runner_for_existing_site( $logger );

$runner->run();

$this->assertFalse( is_dir( $runner->runtime->get_temp_root() ) );
$this->assertSame( array(), $logger->warnings );
}

public function test_logs_cleanup_failure_without_failing_successful_run() {
if ( PHP_OS_FAMILY !== 'Windows' && function_exists( 'posix_geteuid' ) && 0 === posix_geteuid() ) {
$this->markTestSkipped( 'This test needs filesystem permissions to reject a temporary workspace cleanup.' );
}

$logger = new RecordingLogger();
$runner = $this->create_runner_for_existing_site( $logger );
$temp_root = null;
$unremovable_dir = null;
$unremovable_file = null;
$open_handle = null;

add_action(
'blueprint.target_resolved',
function () use ( $runner, &$temp_root, &$unremovable_dir, &$unremovable_file, &$open_handle ) {
$temp_root = $runner->runtime->get_temp_root();
$unremovable_dir = wp_join_unix_paths( $temp_root, 'unremovable' );
$unremovable_file = wp_join_unix_paths( $unremovable_dir, 'locked.txt' );

mkdir( $unremovable_dir );
file_put_contents( $unremovable_file, 'locked' );
$open_handle = fopen( $unremovable_file, 'r' );

if ( PHP_OS_FAMILY !== 'Windows' ) {
chmod( $unremovable_dir, 0555 );
}
}
);

try {
$runner->run();
} finally {
if ( $open_handle ) {
fclose( $open_handle );
}
if ( $unremovable_dir && is_dir( $unremovable_dir ) ) {
chmod( $unremovable_dir, 0777 );
}
if ( $temp_root ) {
$this->remove_directory( $temp_root );
}
}

$this->assertCount( 1, $logger->warnings );
$this->assertStringContainsString( 'Failed to remove temporary Blueprint workspace ', $logger->warnings[0] );
$this->assertStringContainsString( $temp_root, $logger->warnings[0] );
}

private function create_runner_for_existing_site( RecordingLogger $logger ) {
$site_root = wp_join_unix_paths( wp_unix_sys_get_temp_dir(), 'blueprint_cleanup_test_' . uniqid() );
$this->paths_to_remove[] = $site_root;

mkdir( wp_join_unix_paths( $site_root, 'wp-content/plugins/sqlite-database-integration' ), 0777, true );
file_put_contents(
wp_join_unix_paths( $site_root, 'wp-load.php' ),
<<<'STUBPHP'
<?php
define( 'WP_CONTENT_DIR', __DIR__ . '/wp-content' );
function is_blog_installed() {
return true;
}
STUBPHP
);
file_put_contents( wp_join_unix_paths( $site_root, 'wp-content/plugins/sqlite-database-integration/load.php' ), '<?php' );
file_put_contents( wp_join_unix_paths( $site_root, 'wp-content/db.php' ), '<?php' );

$config = ( new RunnerConfiguration() )
->set_blueprint( array( 'version' => 2 ) )
->set_execution_mode( Runner::EXECUTION_MODE_APPLY_TO_EXISTING_SITE )
->set_target_site_root( $site_root )
->set_target_site_url( 'http://example.com' )
->set_database_engine( 'sqlite' )
->set_logger( $logger )
->set_wp_cli_reference(
new InlineFile(
array(
'filename' => 'wp-cli.phar',
'content' => '',
)
)
);

return new Runner( $config );
}

private function remove_directory( $dir ) {
if ( ! is_dir( $dir ) ) {
return;
}

chmod( $dir, 0777 );
$objects = scandir( $dir );
foreach ( $objects as $object ) {
if ( '.' === $object || '..' === $object ) {
continue;
}

$path = $dir . DIRECTORY_SEPARATOR . $object;
if ( is_dir( $path ) ) {
$this->remove_directory( $path );
} else {
@unlink( $path );
}
}
@rmdir( $dir );
}
}

class RecordingLogger extends NoopLogger {
/**
* @var string[]
*/
public $warnings = array();

public function warning( $message, array $context = array() ): void {
$this->warnings[] = $message;
}
}
80 changes: 80 additions & 0 deletions components/Blueprints/Tests/Unit/Steps/ImportContentStepTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace WordPress\Blueprints\Tests\Unit\Steps;

use PHPUnit\Framework\TestCase;
use WordPress\Blueprints\DataReference\InlineFile;
use WordPress\Blueprints\Progress\Tracker;
use WordPress\Blueprints\Runtime;
use WordPress\Blueprints\Steps\ImportContentStep;
use WordPress\ByteStream\MemoryPipe;

class ImportContentStepTest extends TestCase {
public function test_wxr_import_appends_call_inside_existing_php_script() {
$runtime = new CapturingImportRuntime();
$source = new InlineFile(
array(
'filename' => 'content.xml',
'content' => '<rss />',
)
);
$step = new ImportContentStep(
array(
array(
'type' => 'wxr',
'source' => $source,
),
)
);

$step->run( $runtime, new Tracker() );

$importer_script = file_get_contents( __DIR__ . '/../../../Steps/scripts/import-content.php' );
$appended_code = substr( $runtime->captured_code, strlen( $importer_script ) );

$this->assertSame( 0, strpos( ltrim( $appended_code ), 'run_content_import([' ) );
$this->assertStringNotContainsString( '<?php', $appended_code );
$this->assertStringNotContainsString( '?>', $appended_code );
}
}

class CapturingImportRuntime extends Runtime {
/**
* @var string
*/
public $captured_code;

public function __construct() {
}

public function create_php_sub_process(
$code,
$env = null,
$input = null,
$timeout = 60
) {
$this->captured_code = $code;

return new SuccessfulImportProcess();
}

public function get_execution_context_root(): ?string {
return null;
}
}

class SuccessfulImportProcess {
public function start() {
}

public function getOutputStream( $pipe ) {
return new MemoryPipe( '{"type":"completion"}' . "\n" );
}

public function getExitCode() {
return 0;
}

public function stop() {
}
}
23 changes: 17 additions & 6 deletions components/Blueprints/class-runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
use WordPress\Blueprints\VersionStrings\WordPressVersion;
use WordPress\ByteStream\ReadStream\FileReadStream;
use WordPress\Filesystem\Filesystem;
use WordPress\Filesystem\FilesystemException;
use WordPress\Filesystem\InMemoryFilesystem;
use WordPress\Filesystem\LocalFilesystem;
use WordPress\HttpClient\ByteStream\RequestReadStream;
Expand Down Expand Up @@ -288,12 +289,22 @@ public function run(): void {
$progress->finish();
} finally {
// TODO: Optionally preserve workspace in case of error? Support resuming after error?
LocalFilesystem::create( $temp_root )->rmdir(
'/',
array(
'recursive' => true,
)
);
try {
LocalFilesystem::create( $temp_root )->rmdir(
'/',
array(
'recursive' => true,
)
);
} catch ( FilesystemException $exception ) {
$this->configuration->get_logger()->warning(
sprintf(
'Failed to remove temporary Blueprint workspace %s: %s',
$temp_root,
$exception->getMessage()
)
);
}
}
}

Expand Down
Loading