diff --git a/features/reinstall-extensions.feature b/features/reinstall-extensions.feature new file mode 100644 index 00000000..f5b643be --- /dev/null +++ b/features/reinstall-extensions.feature @@ -0,0 +1,25 @@ +Feature: Extensions can be re-installed with PIE, but only if needed + + # pie download && pie install + Example: Installing a previously downloaded extension should install the extension + Given an extension was previously downloaded but not built + When I run a command to install an extension + Then the extension should have been installed and enabled + + # pie build && pie install + Example: Installing a previously built extension should install the extension + Given an extension was previously built but not installed + When I run a command to install an extension + Then the extension should have been installed and enabled + + # pie install && pie install + Example: Re-installing an existing extension is a no-op + Given an extension was previously installed and enabled + When I run a command to install an extension + Then the extension should not have been re-installed + + # pie install && pie install --force + Example: Forcefully re-installing an existing extension should re-install the extension + Given an extension was previously installed and enabled + When I run a command to forcefully install an extension + Then the extension should have been installed and enabled diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 7a81d09b..19da6192 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -240,18 +240,6 @@ parameters: count: 2 path: test/integration/Command/InstallCommandTest.php - - - message: '#^Call to function assert\(\) with true will always evaluate to true\.$#' - identifier: function.alreadyNarrowedType - count: 1 - path: test/integration/Command/InstallCommandTest.php - - - - message: '#^Call to function is_string\(\) with non\-empty\-string will always evaluate to true\.$#' - identifier: function.alreadyNarrowedType - count: 1 - path: test/integration/Command/InstallCommandTest.php - - message: '#^Call to function assert\(\) with true will always evaluate to true\.$#' identifier: function.alreadyNarrowedType diff --git a/src/Command/ShowCommand.php b/src/Command/ShowCommand.php index c291dc6a..ec457f09 100644 --- a/src/Command/ShowCommand.php +++ b/src/Command/ShowCommand.php @@ -8,7 +8,6 @@ use Composer\IO\NullIO; use Php\Pie\ComposerIntegration\PieComposerFactory; use Php\Pie\ComposerIntegration\PieComposerRequest; -use Php\Pie\ComposerIntegration\PieInstalledJsonMetadataKeys; use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal; use Php\Pie\DependencyResolver\Package; use Php\Pie\DependencyResolver\RequestedPackageAndVersion; @@ -33,7 +32,6 @@ use function rtrim; use function sprintf; -/** @phpstan-import-type PieMetadata from PieInstalledJsonMetadataKeys */ #[AsCommand( name: 'show', description: 'List the installed modules and their versions.', diff --git a/src/ComposerIntegration/AddInstalledJsonMetadata.php b/src/ComposerIntegration/AddInstalledJsonMetadata.php new file mode 100644 index 00000000..b829d2e0 --- /dev/null +++ b/src/ComposerIntegration/AddInstalledJsonMetadata.php @@ -0,0 +1,130 @@ +addPieMetadata( + $composer, + $composerPackage, + InstalledJsonMetadata::KEY_TARGET_PLATFORM_PHP_PATH, + $composerRequest->targetPlatform->phpBinaryPath->phpBinaryPath, + ); + $this->addPieMetadata( + $composer, + $composerPackage, + InstalledJsonMetadata::KEY_TARGET_PLATFORM_PHP_CONFIG_PATH, + $composerRequest->targetPlatform->phpBinaryPath->phpConfigPath(), + ); + $this->addPieMetadata( + $composer, + $composerPackage, + InstalledJsonMetadata::KEY_TARGET_PLATFORM_PHP_VERSION, + $composerRequest->targetPlatform->phpBinaryPath->version(), + ); + $this->addPieMetadata( + $composer, + $composerPackage, + InstalledJsonMetadata::KEY_TARGET_PLATFORM_PHP_THREAD_SAFETY, + $composerRequest->targetPlatform->threadSafety->name, + ); + $this->addPieMetadata( + $composer, + $composerPackage, + InstalledJsonMetadata::KEY_TARGET_PLATFORM_PHP_WINDOWS_COMPILER, + $composerRequest->targetPlatform->windowsCompiler?->name, + ); + $this->addPieMetadata( + $composer, + $composerPackage, + InstalledJsonMetadata::KEY_TARGET_PLATFORM_ARCHITECTURE, + $composerRequest->targetPlatform->architecture->name, + ); + } + + public function addBuildMetadata( + PartialComposer $composer, + PieComposerRequest $composerRequest, + CompletePackageInterface $composerPackage, + BinaryFile $builtBinary, + ): void { + $this->addPieMetadata( + $composer, + $composerPackage, + InstalledJsonMetadata::KEY_CONFIGURE_OPTIONS, + implode(' ', $composerRequest->configureOptions), + ); + + $this->addPieMetadata( + $composer, + $composerPackage, + InstalledJsonMetadata::KEY_PHPIZE_BINARY, + $composerRequest->targetPlatform->phpizePath->phpizeBinaryPath ?? null, + ); + + $this->addPieMetadata( + $composer, + $composerPackage, + InstalledJsonMetadata::KEY_BUILT_BINARY, + $builtBinary->filePath, + ); + + $this->addPieMetadata( + $composer, + $composerPackage, + InstalledJsonMetadata::KEY_BINARY_CHECKSUM, + $builtBinary->checksum, + ); + } + + public function addInstallMetadata( + PartialComposer $composer, + CompletePackageInterface $composerPackage, + BinaryFile $installedBinary, + ): void { + $this->addPieMetadata( + $composer, + $composerPackage, + InstalledJsonMetadata::KEY_INSTALLED_BINARY, + $installedBinary->filePath, + ); + } + + /** @param InstalledJsonMetadata::KEY_* $key */ + private function addPieMetadata( + PartialComposer $composer, + CompletePackageInterface $composerPackage, + string $key, + string|null $value, + ): void { + $localRepositoryPackage = $composer + ->getRepositoryManager() + ->getLocalRepository() + ->findPackages($composerPackage->getName())[0]; + + if ($localRepositoryPackage instanceof CompleteAliasPackage) { + $localRepositoryPackage = $localRepositoryPackage->getAliasOf(); + } + + Assert::methodExists($localRepositoryPackage, 'setExtra'); + + $localRepositoryPackage->setExtra(array_merge($localRepositoryPackage->getExtra(), [$key => $value])); + } +} diff --git a/src/ComposerIntegration/ComposerIntegrationHandler.php b/src/ComposerIntegration/ComposerIntegrationHandler.php index 6a13b6b3..6557487f 100644 --- a/src/ComposerIntegration/ComposerIntegrationHandler.php +++ b/src/ComposerIntegration/ComposerIntegrationHandler.php @@ -7,13 +7,20 @@ use Composer\Composer; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\Installer; +use Composer\IO\IOInterface; +use Composer\Package\CompleteAliasPackage; +use Composer\Package\CompletePackageInterface; use Php\Pie\DependencyResolver\Package; use Php\Pie\DependencyResolver\RequestedPackageAndVersion; +use Php\Pie\ExtensionName; use Php\Pie\Platform; use Php\Pie\Platform\TargetPlatform; +use Php\Pie\Util\Emoji; use Psr\Container\ContainerInterface; +use function assert; use function file_exists; +use function sprintf; /** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ class ComposerIntegrationHandler @@ -63,9 +70,61 @@ public function runInstall( // Refresh the Composer instance so it re-reads the updated pie.json $composer = PieComposerFactory::recreatePieComposer($this->container, $composer); - // Removing the package from the local repository will trick Composer into "re-installing" it :) - foreach ($composer->getRepositoryManager()->getLocalRepository()->findPackages($requestedPackageAndVersion->package) as $pkg) { - $composer->getRepositoryManager()->getLocalRepository()->removePackage($pkg); + foreach ($composer->getRepositoryManager()->getLocalRepository()->getPackages() as $localRepoPackage) { + $extName = ExtensionName::determineFromComposerPackage($localRepoPackage); + + if ($localRepoPackage instanceof CompleteAliasPackage) { + $localRepoPackage = $localRepoPackage->getAliasOf(); + } + + assert($localRepoPackage instanceof CompletePackageInterface); + $piePackage = Package::fromComposerCompletePackage($localRepoPackage); + $installedJsonMetadata = $piePackage->installedJsonMetadata(); + $status = $piePackage->verifyPackageStatus($targetPlatform); + + $this->arrayCollectionIo->write(sprintf( + 'Install status %s (%s) status=%s', + $localRepoPackage->getName(), + $extName->name(), + $status->description(), + ), verbosity: IOInterface::VERY_VERBOSE); + + if ($status->isVerified() && ! $forceInstallPackageVersion) { + $this->arrayCollectionIo->write(sprintf( + '%s PIE package %s (%s) is already installed and verified.', + Emoji::GREEN_CHECKMARK, + $localRepoPackage->getName(), + $extName->name(), + ), verbosity: IOInterface::QUIET); + continue; + } + + if (! $installedJsonMetadata->isInstalled() && $installedJsonMetadata->isBuilt()) { + $this->arrayCollectionIo->write(sprintf( + '%s PIE package %s (%s) was previously built but not installed.', + Emoji::INFO, + $localRepoPackage->getName(), + $extName->name(), + ), verbosity: IOInterface::VERBOSE); + } + + if (! $installedJsonMetadata->isInstalled() && ! $installedJsonMetadata->isBuilt() && $installedJsonMetadata->isDownloaded()) { + $this->arrayCollectionIo->write(sprintf( + '%s PIE package %s (%s) was previously downloaded but not built.', + Emoji::INFO, + $localRepoPackage->getName(), + $extName->name(), + ), verbosity: IOInterface::VERBOSE); + } + + $this->arrayCollectionIo->write(sprintf( + '%s Package status of %s (%s) is not yet verified, adding to install candidates: %s', + Emoji::WARNING, + $localRepoPackage->getName(), + $extName->name(), + $status->description(), + )); + $composer->getRepositoryManager()->getLocalRepository()->removePackage($localRepoPackage); } $composerInstaller = PieComposerInstaller::createWithPhpBinary( diff --git a/src/ComposerIntegration/InstallAndBuildProcess.php b/src/ComposerIntegration/InstallAndBuildProcess.php index 69985a78..45a97ab3 100644 --- a/src/ComposerIntegration/InstallAndBuildProcess.php +++ b/src/ComposerIntegration/InstallAndBuildProcess.php @@ -20,7 +20,7 @@ class InstallAndBuildProcess public function __construct( private readonly Build $pieBuild, private readonly Install $pieInstall, - private readonly InstalledJsonMetadata $installedJsonMetadata, + private readonly AddInstalledJsonMetadata $addInstalledJsonMetadata, private readonly PlaceholderReplacer $placeholderReplacer, ) { } @@ -50,7 +50,7 @@ public function __invoke( $downloadedPackage, ); - $this->installedJsonMetadata->addDownloadMetadata( + $this->addInstalledJsonMetadata->addDownloadMetadata( $composer, $composerRequest, $composerPackage, @@ -65,7 +65,7 @@ public function __invoke( $io, ); - $this->installedJsonMetadata->addBuildMetadata( + $this->addInstalledJsonMetadata->addBuildMetadata( $composer, $composerRequest, $composerPackage, @@ -77,7 +77,7 @@ public function __invoke( return; } - $this->installedJsonMetadata->addInstallMetadata( + $this->addInstalledJsonMetadata->addInstallMetadata( $composer, $composerPackage, ($this->pieInstall)( diff --git a/src/ComposerIntegration/InstalledJsonMetadata.php b/src/ComposerIntegration/InstalledJsonMetadata.php index 77a35fb5..35d81159 100644 --- a/src/ComposerIntegration/InstalledJsonMetadata.php +++ b/src/ComposerIntegration/InstalledJsonMetadata.php @@ -5,120 +5,182 @@ namespace Php\Pie\ComposerIntegration; use Composer\Package\CompletePackageInterface; -use Composer\PartialComposer; -use Php\Pie\ComposerIntegration\PieInstalledJsonMetadataKeys as MetadataKey; -use Php\Pie\File\BinaryFile; -use Webmozart\Assert\Assert; -use function array_merge; -use function implode; +use function array_key_exists; +use function is_string; -/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ -class InstalledJsonMetadata +/** + * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks + * + * @phpstan-type PieMetadata = array{ + * pie-target-platform-php-path?: non-empty-string, + * pie-target-platform-php-config-path?: non-empty-string, + * pie-target-platform-php-version?: non-empty-string, + * pie-target-platform-php-thread-safety?: non-empty-string, + * pie-target-platform-php-windows-compiler?: non-empty-string, + * pie-target-platform-architecture?: non-empty-string, + * pie-configure-options?: non-empty-string, + * pie-built-binary?: non-empty-string, + * pie-installed-binary-checksum?: non-empty-string, + * pie-installed-binary?: non-empty-string, + * pie-phpize-binary?: non-empty-string, + * } + */ +final class InstalledJsonMetadata { - public function addDownloadMetadata( - PartialComposer $composer, - PieComposerRequest $composerRequest, - CompletePackageInterface $composerPackage, - ): void { - $this->addPieMetadata( - $composer, - $composerPackage, - MetadataKey::TargetPlatformPhpPath, - $composerRequest->targetPlatform->phpBinaryPath->phpBinaryPath, - ); - $this->addPieMetadata( - $composer, - $composerPackage, - MetadataKey::TargetPlatformPhpConfigPath, - $composerRequest->targetPlatform->phpBinaryPath->phpConfigPath(), - ); - $this->addPieMetadata( - $composer, - $composerPackage, - MetadataKey::TargetPlatformPhpVersion, - $composerRequest->targetPlatform->phpBinaryPath->version(), - ); - $this->addPieMetadata( - $composer, - $composerPackage, - MetadataKey::TargetPlatformPhpThreadSafety, - $composerRequest->targetPlatform->threadSafety->name, - ); - $this->addPieMetadata( - $composer, - $composerPackage, - MetadataKey::TargetPlatformPhpWindowsCompiler, - $composerRequest->targetPlatform->windowsCompiler?->name, - ); - $this->addPieMetadata( - $composer, - $composerPackage, - MetadataKey::TargetPlatformArchitecture, - $composerRequest->targetPlatform->architecture->name, - ); - } - - public function addBuildMetadata( - PartialComposer $composer, - PieComposerRequest $composerRequest, - CompletePackageInterface $composerPackage, - BinaryFile $builtBinary, - ): void { - $this->addPieMetadata( - $composer, - $composerPackage, - MetadataKey::ConfigureOptions, - implode(' ', $composerRequest->configureOptions), - ); - - $this->addPieMetadata( - $composer, - $composerPackage, - MetadataKey::PhpizeBinary, - $composerRequest->targetPlatform->phpizePath->phpizeBinaryPath ?? null, - ); - - $this->addPieMetadata( - $composer, - $composerPackage, - MetadataKey::BuiltBinary, - $builtBinary->filePath, - ); - - $this->addPieMetadata( - $composer, - $composerPackage, - MetadataKey::BinaryChecksum, - $builtBinary->checksum, - ); - } - - public function addInstallMetadata( - PartialComposer $composer, - CompletePackageInterface $composerPackage, - BinaryFile $installedBinary, - ): void { - $this->addPieMetadata( - $composer, - $composerPackage, - MetadataKey::InstalledBinary, - $installedBinary->filePath, - ); - } - - private function addPieMetadata( - PartialComposer $composer, - CompletePackageInterface $composerPackage, - MetadataKey $key, - string|null $value, - ): void { - $localRepositoryPackage = $composer - ->getRepositoryManager() - ->getLocalRepository() - ->findPackages($composerPackage->getName())[0]; - Assert::methodExists($localRepositoryPackage, 'setExtra'); - - $localRepositoryPackage->setExtra(array_merge($localRepositoryPackage->getExtra(), [$key->value => $value])); + public const KEY_TARGET_PLATFORM_PHP_PATH = 'pie-target-platform-php-path'; + public const KEY_TARGET_PLATFORM_PHP_CONFIG_PATH = 'pie-target-platform-php-config-path'; + public const KEY_TARGET_PLATFORM_PHP_VERSION = 'pie-target-platform-php-version'; + public const KEY_TARGET_PLATFORM_PHP_THREAD_SAFETY = 'pie-target-platform-php-thread-safety'; + public const KEY_TARGET_PLATFORM_PHP_WINDOWS_COMPILER = 'pie-target-platform-php-windows-compiler'; + public const KEY_TARGET_PLATFORM_ARCHITECTURE = 'pie-target-platform-architecture'; + public const KEY_CONFIGURE_OPTIONS = 'pie-configure-options'; + public const KEY_BUILT_BINARY = 'pie-built-binary'; + public const KEY_BINARY_CHECKSUM = 'pie-installed-binary-checksum'; + public const KEY_INSTALLED_BINARY = 'pie-installed-binary'; + public const KEY_PHPIZE_BINARY = 'pie-phpize-binary'; + + private const ALL_KEYS = [ + self::KEY_TARGET_PLATFORM_PHP_PATH, + self::KEY_TARGET_PLATFORM_PHP_CONFIG_PATH, + self::KEY_TARGET_PLATFORM_PHP_VERSION, + self::KEY_TARGET_PLATFORM_PHP_THREAD_SAFETY, + self::KEY_TARGET_PLATFORM_PHP_WINDOWS_COMPILER, + self::KEY_TARGET_PLATFORM_ARCHITECTURE, + self::KEY_CONFIGURE_OPTIONS, + self::KEY_BUILT_BINARY, + self::KEY_BINARY_CHECKSUM, + self::KEY_INSTALLED_BINARY, + self::KEY_PHPIZE_BINARY, + ]; + + /** @param PieMetadata $values */ + private function __construct(private readonly array $values) + { + } + + /** @param PieMetadata $values */ + public static function fromArray(array $values): self + { + return new self($values); + } + + public static function fromComposerPackage(CompletePackageInterface $composerPackage): self + { + $composerPackageExtras = $composerPackage->getExtra(); + + $onlyPieExtras = []; + + foreach (self::ALL_KEYS as $key) { + if ( + ! array_key_exists($key, $composerPackageExtras) + || ! is_string($composerPackageExtras[$key]) + || $composerPackageExtras[$key] === '' + ) { + continue; + } + + $onlyPieExtras[$key] = $composerPackageExtras[$key]; + } + + return new self($onlyPieExtras); + } + + /** @return PieMetadata */ + public function all(): array + { + return $this->values; + } + + /** @return non-empty-string|null */ + private function nonEmptyStringOrNull(string $key): string|null + { + return array_key_exists($key, $this->values) + ? $this->values[$key] + : null; + } + + /** @return non-empty-string|null */ + public function targetPlatformPhpPath(): string|null + { + return $this->nonEmptyStringOrNull(self::KEY_TARGET_PLATFORM_PHP_PATH); + } + + /** @return non-empty-string|null */ + public function targetPlatformPhpConfigPath(): string|null + { + return $this->nonEmptyStringOrNull(self::KEY_TARGET_PLATFORM_PHP_CONFIG_PATH); + } + + /** @return non-empty-string|null */ + public function targetPlatformPhpVersion(): string|null + { + return $this->nonEmptyStringOrNull(self::KEY_TARGET_PLATFORM_PHP_VERSION); + } + + /** @return non-empty-string|null */ + public function targetPlatformPhpThreadSafety(): string|null + { + return $this->nonEmptyStringOrNull(self::KEY_TARGET_PLATFORM_PHP_THREAD_SAFETY); + } + + /** @return non-empty-string|null */ + public function targetPlatformPhpWindowsCompiler(): string|null + { + return $this->nonEmptyStringOrNull(self::KEY_TARGET_PLATFORM_PHP_WINDOWS_COMPILER); + } + + /** @return non-empty-string|null */ + public function targetPlatformArchitecture(): string|null + { + return $this->nonEmptyStringOrNull(self::KEY_TARGET_PLATFORM_ARCHITECTURE); + } + + /** @return non-empty-string|null */ + public function configureOptions(): string|null + { + return $this->nonEmptyStringOrNull(self::KEY_CONFIGURE_OPTIONS); + } + + /** @return non-empty-string|null */ + public function builtBinary(): string|null + { + return $this->nonEmptyStringOrNull(self::KEY_BUILT_BINARY); + } + + /** @return non-empty-string|null */ + public function binaryChecksum(): string|null + { + return $this->nonEmptyStringOrNull(self::KEY_BINARY_CHECKSUM); + } + + /** @return non-empty-string|null */ + public function installedBinary(): string|null + { + return $this->nonEmptyStringOrNull(self::KEY_INSTALLED_BINARY); + } + + /** @return non-empty-string|null */ + public function phpizeBinary(): string|null + { + return $this->nonEmptyStringOrNull(self::KEY_PHPIZE_BINARY); + } + + /** Has this package been downloaded, according to the metadata? (note: does not verifiy it is STILL downloaded - especially if vendor cleanup happened!) */ + public function isDownloaded(): bool + { + return $this->targetPlatformPhpVersion() !== null; + } + + /** Has this package been built, according to the metadata? (note: does not verify it is STILL built) */ + public function isBuilt(): bool + { + return $this->isDownloaded() && $this->builtBinary() !== null; + } + + /** Has this package been installed, according to the metadata (note: not verify it is STILL installed/verified) */ + public function isInstalled(): bool + { + return $this->isBuilt() && $this->installedBinary() !== null; } } diff --git a/src/ComposerIntegration/PieInstalledJsonMetadataKeys.php b/src/ComposerIntegration/PieInstalledJsonMetadataKeys.php deleted file mode 100644 index e93210f8..00000000 --- a/src/ComposerIntegration/PieInstalledJsonMetadataKeys.php +++ /dev/null @@ -1,65 +0,0 @@ -getExtra(); - - $onlyPieExtras = []; - - foreach (array_column(self::cases(), 'value') as $pieMetadataKey) { - if ( - ! array_key_exists($pieMetadataKey, $composerPackageExtras) - || ! is_string($composerPackageExtras[$pieMetadataKey]) - || $composerPackageExtras[$pieMetadataKey] === '' - ) { - continue; - } - - $onlyPieExtras[$pieMetadataKey] = $composerPackageExtras[$pieMetadataKey]; - } - - return $onlyPieExtras; - } -} diff --git a/src/ComposerIntegration/UninstallProcess.php b/src/ComposerIntegration/UninstallProcess.php index cce50f73..80b10489 100644 --- a/src/ComposerIntegration/UninstallProcess.php +++ b/src/ComposerIntegration/UninstallProcess.php @@ -33,7 +33,15 @@ public function __invoke( $piePackage = Package::fromComposerCompletePackage($composerPackage); - $affectedIniFiles = ($this->removeIniEntry)($piePackage, $targetPlatform, $io); + $status = $piePackage->verifyPackageStatus($composerRequest->targetPlatform); + + if ($status->isVerified()) { + $io->write(sprintf('👋 Removed extension: %s', ($this->uninstall)($targetPlatform, $piePackage)->filePath)); + } else { + $io->writeError(sprintf('Did not remove extension file: %s', $status->description())); + } + + $affectedIniFiles = ($this->removeIniEntry)($piePackage, $composerRequest->targetPlatform, $io); if (count($affectedIniFiles) === 1) { $io->write( @@ -52,7 +60,5 @@ public function __invoke( ); array_walk($affectedIniFiles, static fn (string $ini) => $io->write(' - ' . $ini)); } - - $io->write(sprintf('👋 Removed extension: %s', ($this->uninstall)($targetPlatform, $piePackage)->filePath)); } } diff --git a/src/DependencyResolver/Package.php b/src/DependencyResolver/Package.php index 513b3ffb..6a33ca24 100644 --- a/src/DependencyResolver/Package.php +++ b/src/DependencyResolver/Package.php @@ -6,7 +6,7 @@ use Composer\Package\CompletePackageInterface; use InvalidArgumentException; -use Php\Pie\ComposerIntegration\PieInstalledJsonMetadataKeys; +use Php\Pie\ComposerIntegration\InstalledJsonMetadata; use Php\Pie\ConfigureOption; use Php\Pie\Downloading\DownloadUrlMethod; use Php\Pie\ExtensionName; @@ -56,6 +56,7 @@ final class Package private bool $supportNts = true; /** @var non-empty-list|null */ private array|null $supportedDownloadUrlMethods = null; + private readonly InstalledJsonMetadata $installedJsonMetadata; public function __construct( private readonly CompletePackageInterface $composerPackage, @@ -65,6 +66,7 @@ public function __construct( private readonly string $version, private readonly string|null $downloadUrl, ) { + $this->installedJsonMetadata = InstalledJsonMetadata::fromComposerPackage($this->composerPackage); } public static function fromComposerCompletePackage(CompletePackageInterface $completePackage): self @@ -269,6 +271,11 @@ public function supportedDownloadUrlMethods(): array|null return $this->supportedDownloadUrlMethods; } + public function installedJsonMetadata(): InstalledJsonMetadata + { + return $this->installedJsonMetadata; + } + public function verifyPackageStatus(TargetPlatform $targetPlatform): PackageVerificationStatus { $extensionPath = $targetPlatform->phpBinaryPath->extensionPath(); @@ -282,9 +289,8 @@ public function verifyPackageStatus(TargetPlatform $targetPlatform): PackageVeri return PackageVerificationStatus::ActualBinaryNotFound; } - $installedJsonMetadata = PieInstalledJsonMetadataKeys::pieMetadataFromComposerPackage($this->composerPackage()); - $pieExpectedBinaryPath = array_key_exists(PieInstalledJsonMetadataKeys::InstalledBinary->value, $installedJsonMetadata) ? $installedJsonMetadata[PieInstalledJsonMetadataKeys::InstalledBinary->value] : null; - $pieExpectedChecksum = array_key_exists(PieInstalledJsonMetadataKeys::BinaryChecksum->value, $installedJsonMetadata) ? $installedJsonMetadata[PieInstalledJsonMetadataKeys::BinaryChecksum->value] : null; + $pieExpectedBinaryPath = $this->installedJsonMetadata()->installedBinary(); + $pieExpectedChecksum = $this->installedJsonMetadata()->binaryChecksum(); if ($pieExpectedBinaryPath === null) { return PackageVerificationStatus::InstalledBinaryMetadataMissing; diff --git a/src/Installing/PackageMetadataMissing.php b/src/Installing/PackageMetadataMissing.php index 50c5ff7a..1509e3d1 100644 --- a/src/Installing/PackageMetadataMissing.php +++ b/src/Installing/PackageMetadataMissing.php @@ -4,6 +4,7 @@ namespace Php\Pie\Installing; +use Php\Pie\ComposerIntegration\InstalledJsonMetadata; use Php\Pie\DependencyResolver\Package; use RuntimeException; @@ -15,13 +16,10 @@ class PackageMetadataMissing extends RuntimeException { - /** - * @param array $actualMetadata - * @param list $wantedKeys - */ - public static function duringUninstall(Package $package, array $actualMetadata, array $wantedKeys): self + /** @param list $wantedKeys */ + public static function duringUninstall(Package $package, InstalledJsonMetadata $actualMetadata, array $wantedKeys): self { - $missingKeys = array_diff($wantedKeys, array_keys($actualMetadata)); + $missingKeys = array_diff($wantedKeys, array_keys($actualMetadata->all())); return new self(sprintf( 'PIE metadata was missing for package %s. Missing metadata key%s: %s', diff --git a/src/Installing/UninstallUsingUnlink.php b/src/Installing/UninstallUsingUnlink.php index 23b8abfd..80369d0e 100644 --- a/src/Installing/UninstallUsingUnlink.php +++ b/src/Installing/UninstallUsingUnlink.php @@ -5,7 +5,7 @@ namespace Php\Pie\Installing; use Composer\Util\Platform as ComposerPlatform; -use Php\Pie\ComposerIntegration\PieInstalledJsonMetadataKeys; +use Php\Pie\ComposerIntegration\InstalledJsonMetadata; use Php\Pie\DependencyResolver\Package; use Php\Pie\File\BinaryFile; use Php\Pie\File\FailedToUnlinkFile; @@ -16,7 +16,6 @@ use Php\Pie\Util\Process; use RuntimeException; -use function array_key_exists; use function file_exists; use function is_writable; use function sprintf; @@ -28,18 +27,17 @@ class UninstallUsingUnlink implements Uninstall { public function __invoke(TargetPlatform $targetPlatform, Package $package): BinaryFile { - $pieMetadata = PieInstalledJsonMetadataKeys::pieMetadataFromComposerPackage($package->composerPackage()); + $pieMetadata = $package->installedJsonMetadata(); + $pieExpectedBinaryPath = $pieMetadata->installedBinary(); + $pieExpectedChecksum = $pieMetadata->binaryChecksum(); - if ( - ! array_key_exists(PieInstalledJsonMetadataKeys::InstalledBinary->value, $pieMetadata) - || ! array_key_exists(PieInstalledJsonMetadataKeys::BinaryChecksum->value, $pieMetadata) - ) { + if ($pieExpectedBinaryPath === null || $pieExpectedChecksum === null) { throw PackageMetadataMissing::duringUninstall( $package, $pieMetadata, [ - PieInstalledJsonMetadataKeys::InstalledBinary->value, - PieInstalledJsonMetadataKeys::BinaryChecksum->value, + InstalledJsonMetadata::KEY_INSTALLED_BINARY, + InstalledJsonMetadata::KEY_BINARY_CHECKSUM, ], ); } @@ -52,19 +50,15 @@ public function __invoke(TargetPlatform $targetPlatform, Package $package): Bina . ($targetPlatform->operatingSystem === OperatingSystem::Windows ? 'php_' : '') . $package->extensionName()->name() . ($targetPlatform->operatingSystem === OperatingSystem::Windows ? '.dll' : '.so'); - if ($extensionPathByConvention !== $pieMetadata[PieInstalledJsonMetadataKeys::InstalledBinary->value]) { + if ($extensionPathByConvention !== $pieExpectedBinaryPath) { throw new RuntimeException(sprintf( 'Stored metadata path "%s" did not match expected path "%s"', - $pieMetadata[PieInstalledJsonMetadataKeys::InstalledBinary->value], + $pieExpectedBinaryPath, $extensionPathByConvention, )); } - $expectedBinaryFile = new BinaryFile( - $pieMetadata[PieInstalledJsonMetadataKeys::InstalledBinary->value], - $pieMetadata[PieInstalledJsonMetadataKeys::BinaryChecksum->value], - ); - + $expectedBinaryFile = new BinaryFile($pieExpectedBinaryPath, $pieExpectedChecksum); $expectedBinaryFile->verify(); // If the target directory isn't writable, or a .so file already exists and isn't writable, try to use sudo diff --git a/src/Util/Emoji.php b/src/Util/Emoji.php index 8e980019..7c160266 100644 --- a/src/Util/Emoji.php +++ b/src/Util/Emoji.php @@ -11,4 +11,5 @@ final class Emoji public const WARNING = 'âš ī¸ '; public const PROHIBITED = 'đŸšĢ'; public const CROSS = '❌'; + public const INFO = 'â„šī¸ '; } diff --git a/src/Util/PackageVerificationStatus.php b/src/Util/PackageVerificationStatus.php index d0048194..272d8646 100644 --- a/src/Util/PackageVerificationStatus.php +++ b/src/Util/PackageVerificationStatus.php @@ -24,4 +24,9 @@ public function description(): string self::InstalledBinaryPathDoesNotMatchActualBinaryPath => Emoji::WARNING . ' - binary path mismatch', }; } + + public function isVerified(): bool + { + return $this === self::Verified; + } } diff --git a/test/behaviour/CliContext.php b/test/behaviour/CliContext.php index 2620c476..fb8b1b59 100644 --- a/test/behaviour/CliContext.php +++ b/test/behaviour/CliContext.php @@ -5,16 +5,18 @@ namespace Php\PieBehaviourTest; use Behat\Behat\Context\Context; +use Behat\Hook\AfterScenario; use Behat\Step\Given; use Behat\Step\Then; use Behat\Step\When; use Composer\Util\Platform; +use Safe\Exceptions\PcreException; use Symfony\Component\Process\Process; use Webmozart\Assert\Assert; use function array_merge; -use function assert; use function Safe\copy; +use function Safe\preg_match_all; use function Safe\realpath; use function sprintf; use function str_contains; @@ -28,12 +30,32 @@ class CliContext implements Context private string|null $errorOutput = null; private int|null $exitCode = null; /** @var list */ - private array $phpArguments = []; - private string $theExtension = 'example_pie_extension'; + private array $phpArguments = []; + private string $theExtension = 'example_pie_extension'; + /** @var non-empty-string */ private string $thePackage = 'asgrim/example-pie-extension'; private string|null $workingDirectory = null; + /** @throws PcreException */ + #[AfterScenario] + public function removeInstalledExtensions(): void + { + $this->runPieCommand(['show']); + if (! preg_match_all('#([a-zA-Z0-9-_]+/[a-zA-Z0-9-_]+):#', (string) $this->output, $installedExtensionPackageNames)) { + return; + } + + foreach ($installedExtensionPackageNames[1] as $extensionPackageName) { + if ($extensionPackageName === 'xdebug/xdebug') { + continue; + } + + $this->runPieCommand(['uninstall', $extensionPackageName]); + } + } + #[When('I run a command to download the latest version of an extension')] + #[Given('an extension was previously downloaded but not built')] public function iRunACommandToDownloadTheLatestVersionOfAnExtension(): void { $this->runPieCommand(['download', 'asgrim/example-pie-extension']); @@ -104,6 +126,7 @@ public function versionOfTheExtensionShouldHaveBeen(string $version): void } #[When('I run a command to build an extension')] + #[Given('an extension was previously built but not installed')] public function iRunACommandToBuildAnExtension(): void { $this->runPieCommand(['build', 'asgrim/example-pie-extension']); @@ -170,6 +193,14 @@ public function iRunACommandToInstallAnExtension(): void $this->runPieCommand(['install', $this->thePackage]); } + #[When('I run a command to forcefully install an extension')] + public function iRunACommandToForcefullyInstallAnExtension(): void + { + $this->theExtension = 'example_pie_extension'; + $this->thePackage = 'asgrim/example-pie-extension'; + $this->runPieCommand(['install', '--force', $this->thePackage]); + } + #[When('I run a command to install an extension without enabling it')] public function iRunACommandToInstallAnExtensionWithoutEnabling(): void { @@ -181,7 +212,6 @@ public function iRunACommandToInstallAnExtensionWithoutEnabling(): void #[When('I run a command to uninstall an extension')] public function iRunACommandToUninstallAnExtension(): void { - assert($this->thePackage !== ''); $this->runPieCommand(['uninstall', $this->thePackage]); } @@ -245,6 +275,20 @@ public function theExtensionShouldHaveBeenInstalledAndEnabled(): void Assert::same($isExtEnabled, 'yes'); } + #[Then('the extension should not have been re-installed')] + public function theExtensionShouldNotHaveBeenReinstalled(): void + { + $this->assertCommandSuccessful(); + + Assert::contains($this->output, 'PIE package asgrim/example-pie-extension (example_pie_extension) is already installed and verified.'); + + $isExtEnabled = (new Process([self::PHP_BINARY, '-r', 'echo extension_loaded("' . $this->theExtension . '")?"yes":"no";'])) + ->mustRun() + ->getOutput(); + + Assert::same($isExtEnabled, 'yes'); + } + #[Given('I have an invalid extension installed')] public function iHaveAnInvalidExtensionInstalled(): void { diff --git a/test/integration/Command/BuildCommandTest.php b/test/integration/Command/BuildCommandTest.php index 342e81ee..6db411a1 100644 --- a/test/integration/Command/BuildCommandTest.php +++ b/test/integration/Command/BuildCommandTest.php @@ -8,12 +8,11 @@ use Php\Pie\Command\BuildCommand; use Php\Pie\Container; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; use function str_contains; #[CoversClass(BuildCommand::class)] -class BuildCommandTest extends TestCase +class BuildCommandTest extends IsolatedWorkingDirectoryTestCase { private const TEST_PACKAGE = 'asgrim/example-pie-extension'; @@ -21,6 +20,8 @@ class BuildCommandTest extends TestCase public function setUp(): void { + parent::setUp(); + $this->commandTester = new CommandTester(Container::testFactory()->get(BuildCommand::class)); } diff --git a/test/integration/Command/DownloadCommandTest.php b/test/integration/Command/DownloadCommandTest.php index fb580fe8..7ce923c6 100644 --- a/test/integration/Command/DownloadCommandTest.php +++ b/test/integration/Command/DownloadCommandTest.php @@ -11,7 +11,6 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily; use PHPUnit\Framework\Attributes\RequiresPhp; -use PHPUnit\Framework\TestCase; use function array_combine; use function array_map; @@ -21,7 +20,7 @@ use const PHP_VERSION_ID; #[CoversClass(DownloadCommand::class)] -class DownloadCommandTest extends TestCase +class DownloadCommandTest extends IsolatedWorkingDirectoryTestCase { private const TEST_PACKAGE_LATEST = '2.0.9'; private const TEST_PACKAGE = 'asgrim/example-pie-extension'; @@ -30,6 +29,8 @@ class DownloadCommandTest extends TestCase public function setUp(): void { + parent::setUp(); + $this->commandTester = new CommandTester(Container::testFactory()->get(DownloadCommand::class)); } diff --git a/test/integration/Command/InfoCommandTest.php b/test/integration/Command/InfoCommandTest.php index a0f520d8..5871f1f3 100644 --- a/test/integration/Command/InfoCommandTest.php +++ b/test/integration/Command/InfoCommandTest.php @@ -7,15 +7,16 @@ use Php\Pie\Command\InfoCommand; use Php\Pie\Container; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; #[CoversClass(InfoCommand::class)] -final class InfoCommandTest extends TestCase +final class InfoCommandTest extends IsolatedWorkingDirectoryTestCase { private CommandTester $commandTester; public function setUp(): void { + parent::setUp(); + $this->commandTester = new CommandTester(Container::testFactory()->get(InfoCommand::class)); } diff --git a/test/integration/Command/InstallCommandTest.php b/test/integration/Command/InstallCommandTest.php index 9ba63c84..631325bc 100644 --- a/test/integration/Command/InstallCommandTest.php +++ b/test/integration/Command/InstallCommandTest.php @@ -10,7 +10,6 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily; -use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Process\Process; @@ -19,26 +18,40 @@ use function array_key_exists; use function array_map; use function array_unshift; -use function assert; use function file_exists; use function is_executable; -use function is_file; -use function is_string; use function is_writable; use function Safe\preg_match; #[CoversClass(InstallCommand::class)] -class InstallCommandTest extends TestCase +class InstallCommandTest extends IsolatedWorkingDirectoryTestCase { private const TEST_PACKAGE = 'asgrim/example-pie-extension'; private CommandTester $commandTester; + private string|null $lastInstalledBinary = null; public function setUp(): void { + parent::setUp(); + $this->commandTester = new CommandTester(Container::testFactory()->get(InstallCommand::class)); } + protected function tearDown(): void + { + if ($this->lastInstalledBinary !== null && file_exists($this->lastInstalledBinary)) { + $rmCommand = ['rm', $this->lastInstalledBinary]; + if (! is_writable($this->lastInstalledBinary)) { + array_unshift($rmCommand, 'sudo'); + } + + (new Process($rmCommand))->run(); + } + + parent::tearDown(); + } + /** @return array */ public static function phpPathProvider(): array { @@ -87,27 +100,17 @@ public function testInstallCommandWillInstallCompatibleExtensionNonWindows(strin $this->commandTester->assertCommandIsSuccessful(); $outputString = $this->commandTester->getDisplay(); - self::assertStringContainsString('Install complete: ', $outputString); - self::assertStringContainsString('You must now add "extension=example_pie_extension" to your php.ini', $outputString); if ( - ! preg_match('#^Install complete: (.*)$#m', $outputString, $matches) - || ! array_key_exists(1, $matches) - || $matches[1] === '' - || ! file_exists($matches[1]) - || ! is_file($matches[1]) + preg_match('#^Install complete: (.*)$#m', $outputString, $matches) + && array_key_exists(1, $matches) + && $matches[1] !== '' ) { - return; + $this->lastInstalledBinary = $matches[1]; } - $fileToRemove = $matches[1]; - assert(is_string($fileToRemove)); - $rmCommand = ['rm', $fileToRemove]; - if (! is_writable($fileToRemove)) { - array_unshift($rmCommand, 'sudo'); - } - - (new Process($rmCommand))->mustRun(); + self::assertStringContainsString('Install complete: ', $outputString); + self::assertStringContainsString('You must now add "extension=example_pie_extension" to your php.ini', $outputString); } #[RequiresOperatingSystemFamily('Windows')] @@ -121,19 +124,16 @@ public function testInstallCommandWillInstallCompatibleExtensionWindows(): void $this->commandTester->assertCommandIsSuccessful(); $outputString = $this->commandTester->getDisplay(); - self::assertStringContainsString('Copied DLL to: ', $outputString); - self::assertStringContainsString('You must now add "extension=example_pie_extension" to your php.ini', $outputString); if ( - ! preg_match('#^Copied DLL to: (.*)$#m', $outputString, $matches) - || ! array_key_exists(1, $matches) - || $matches[1] === '' - || ! file_exists($matches[1]) - || ! is_file($matches[1]) + preg_match('#^Copied DLL to: (.*)$#m', $outputString, $matches) + && array_key_exists(1, $matches) + && $matches[1] !== '' ) { - return; + $this->lastInstalledBinary = $matches[1]; } - (new Process(['rm', $matches[1]]))->mustRun(); + self::assertStringContainsString('Copied DLL to: ', $outputString); + self::assertStringContainsString('You must now add "extension=example_pie_extension" to your php.ini', $outputString); } } diff --git a/test/integration/Command/IsolatedWorkingDirectoryTestCase.php b/test/integration/Command/IsolatedWorkingDirectoryTestCase.php new file mode 100644 index 00000000..e3d7dec8 --- /dev/null +++ b/test/integration/Command/IsolatedWorkingDirectoryTestCase.php @@ -0,0 +1,33 @@ +tempPieDir = sys_get_temp_dir() . '/pie-test-' . uniqid(); + putenv('PIE_WORKING_DIRECTORY=' . $this->tempPieDir); + } + + protected function tearDown(): void + { + if (file_exists($this->tempPieDir)) { + (new Process(['rm', '-rf', $this->tempPieDir]))->run(); + } + + putenv('PIE_WORKING_DIRECTORY'); + } +} diff --git a/test/integration/Command/RepositoryManagementCommandsTest.php b/test/integration/Command/RepositoryManagementCommandsTest.php index b93c411e..3e31148b 100644 --- a/test/integration/Command/RepositoryManagementCommandsTest.php +++ b/test/integration/Command/RepositoryManagementCommandsTest.php @@ -9,7 +9,6 @@ use Php\Pie\Command\RepositoryRemoveCommand; use Php\Pie\Container; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\TestCase; use function array_filter; use function array_map; @@ -23,7 +22,7 @@ #[CoversClass(RepositoryListCommand::class)] #[CoversClass(RepositoryAddCommand::class)] #[CoversClass(RepositoryRemoveCommand::class)] -final class RepositoryManagementCommandsTest extends TestCase +final class RepositoryManagementCommandsTest extends IsolatedWorkingDirectoryTestCase { private const EXAMPLE_PATH_REPOSITORY_URL = __DIR__; private const EXAMPLE_VCS_REPOSITORY_URL = 'https://github.com/asgrim/example-pie-extension'; @@ -35,6 +34,8 @@ final class RepositoryManagementCommandsTest extends TestCase public function setUp(): void { + parent::setUp(); + $this->listCommand = new CommandTester(Container::testFactory()->get(RepositoryListCommand::class)); $this->addCommand = new CommandTester(Container::testFactory()->get(RepositoryAddCommand::class)); $this->removeCommand = new CommandTester(Container::testFactory()->get(RepositoryRemoveCommand::class)); diff --git a/test/unit/ComposerIntegration/InstalledJsonMetadataTest.php b/test/unit/ComposerIntegration/AddInstalledJsonMetadataTest.php similarity index 93% rename from test/unit/ComposerIntegration/InstalledJsonMetadataTest.php rename to test/unit/ComposerIntegration/AddInstalledJsonMetadataTest.php index 20234dff..111f2324 100644 --- a/test/unit/ComposerIntegration/InstalledJsonMetadataTest.php +++ b/test/unit/ComposerIntegration/AddInstalledJsonMetadataTest.php @@ -10,7 +10,7 @@ use Composer\Package\CompletePackageInterface; use Composer\Repository\InstalledArrayRepository; use Composer\Repository\RepositoryManager; -use Php\Pie\ComposerIntegration\InstalledJsonMetadata; +use Php\Pie\ComposerIntegration\AddInstalledJsonMetadata; use Php\Pie\ComposerIntegration\PieComposerRequest; use Php\Pie\ComposerIntegration\PieOperation; use Php\Pie\DependencyResolver\RequestedPackageAndVersion; @@ -26,8 +26,8 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -#[CoversClass(InstalledJsonMetadata::class)] -final class InstalledJsonMetadataTest extends TestCase +#[CoversClass(AddInstalledJsonMetadata::class)] +final class AddInstalledJsonMetadataTest extends TestCase { private function mockComposerInstalledRepositoryWith(CompletePackageInterface $package): Composer&MockObject { @@ -48,7 +48,7 @@ public function testMetadataForDownloads(): void $phpBinary = PhpBinaryPath::fromCurrentProcess(); - (new InstalledJsonMetadata())->addDownloadMetadata( + (new AddInstalledJsonMetadata())->addDownloadMetadata( $this->mockComposerInstalledRepositoryWith($package), new PieComposerRequest( $this->createMock(IOInterface::class), @@ -87,7 +87,7 @@ public function testMetadataForBuilds(): void { $package = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3'); - (new InstalledJsonMetadata())->addBuildMetadata( + (new AddInstalledJsonMetadata())->addBuildMetadata( $this->mockComposerInstalledRepositoryWith($package), new PieComposerRequest( $this->createMock(IOInterface::class), @@ -125,7 +125,7 @@ public function testMetadataForInstalls(): void { $package = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3'); - (new InstalledJsonMetadata())->addInstallMetadata( + (new AddInstalledJsonMetadata())->addInstallMetadata( $this->mockComposerInstalledRepositoryWith($package), clone $package, new BinaryFile('/path/to/installed', 'ignore'), diff --git a/test/unit/ComposerIntegration/InstallAndBuildProcessTest.php b/test/unit/ComposerIntegration/InstallAndBuildProcessTest.php index 6318ba6e..be713a05 100644 --- a/test/unit/ComposerIntegration/InstallAndBuildProcessTest.php +++ b/test/unit/ComposerIntegration/InstallAndBuildProcessTest.php @@ -9,8 +9,8 @@ use Composer\PartialComposer; use Php\Pie\Building\Build; use Php\Pie\Building\PlaceholderReplacer; +use Php\Pie\ComposerIntegration\AddInstalledJsonMetadata; use Php\Pie\ComposerIntegration\InstallAndBuildProcess; -use Php\Pie\ComposerIntegration\InstalledJsonMetadata; use Php\Pie\ComposerIntegration\PieComposerRequest; use Php\Pie\ComposerIntegration\PieOperation; use Php\Pie\DependencyResolver\RequestedPackageAndVersion; @@ -31,7 +31,7 @@ final class InstallAndBuildProcessTest extends TestCase { private Build&MockObject $pieBuild; private Install&MockObject $pieInstall; - private InstalledJsonMetadata&MockObject $installedJsonMetadata; + private AddInstalledJsonMetadata&MockObject $addInstalledJsonMetadata; private InstallAndBuildProcess $installAndBuildProcess; @@ -39,14 +39,14 @@ public function setUp(): void { parent::setUp(); - $this->pieBuild = $this->createMock(Build::class); - $this->pieInstall = $this->createMock(Install::class); - $this->installedJsonMetadata = $this->createMock(InstalledJsonMetadata::class); + $this->pieBuild = $this->createMock(Build::class); + $this->pieInstall = $this->createMock(Install::class); + $this->addInstalledJsonMetadata = $this->createMock(AddInstalledJsonMetadata::class); $this->installAndBuildProcess = new InstallAndBuildProcess( $this->pieBuild, $this->pieInstall, - $this->installedJsonMetadata, + $this->addInstalledJsonMetadata, $this->createMock(PlaceholderReplacer::class), ); } @@ -74,11 +74,11 @@ public function testDownloadWithoutBuildAndInstall(): void $composerPackage = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3'); $installPath = '/path/to/install'; - $this->installedJsonMetadata->expects(self::once())->method('addDownloadMetadata'); + $this->addInstalledJsonMetadata->expects(self::once())->method('addDownloadMetadata'); - $this->installedJsonMetadata->expects(self::never())->method('addBuildMetadata'); + $this->addInstalledJsonMetadata->expects(self::never())->method('addBuildMetadata'); - $this->installedJsonMetadata->expects(self::never())->method('addInstallMetadata'); + $this->addInstalledJsonMetadata->expects(self::never())->method('addInstallMetadata'); $this->pieBuild->expects(self::never())->method('__invoke'); @@ -115,11 +115,11 @@ public function testDownloadAndBuildWithoutInstall(): void $composerPackage = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3'); $installPath = '/path/to/install'; - $this->installedJsonMetadata->expects(self::once())->method('addDownloadMetadata'); + $this->addInstalledJsonMetadata->expects(self::once())->method('addDownloadMetadata'); - $this->installedJsonMetadata->expects(self::once())->method('addBuildMetadata'); + $this->addInstalledJsonMetadata->expects(self::once())->method('addBuildMetadata'); - $this->installedJsonMetadata->expects(self::never())->method('addInstallMetadata'); + $this->addInstalledJsonMetadata->expects(self::never())->method('addInstallMetadata'); $this->pieBuild ->expects(self::once()) @@ -159,11 +159,11 @@ public function testDownloadBuildAndInstall(): void $composerPackage = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3'); $installPath = '/path/to/install'; - $this->installedJsonMetadata->expects(self::once())->method('addDownloadMetadata'); + $this->addInstalledJsonMetadata->expects(self::once())->method('addDownloadMetadata'); - $this->installedJsonMetadata->expects(self::once())->method('addBuildMetadata'); + $this->addInstalledJsonMetadata->expects(self::once())->method('addBuildMetadata'); - $this->installedJsonMetadata->expects(self::once())->method('addInstallMetadata'); + $this->addInstalledJsonMetadata->expects(self::once())->method('addInstallMetadata'); $this->pieBuild ->expects(self::once()) diff --git a/test/unit/ComposerIntegration/PieInstalledJsonMetadataKeysTest.php b/test/unit/ComposerIntegration/PieInstalledJsonMetadataKeysTest.php deleted file mode 100644 index e689d09e..00000000 --- a/test/unit/ComposerIntegration/PieInstalledJsonMetadataKeysTest.php +++ /dev/null @@ -1,44 +0,0 @@ -createMock(CompletePackageInterface::class); - $composerPackage->expects(self::once()) - ->method('getExtra') - ->willReturn([]); - - self::assertSame([], PieInstalledJsonMetadataKeys::pieMetadataFromComposerPackage($composerPackage)); - } - - public function testPieMetadataFromComposerPackageWithPopulatedExtra(): void - { - $composerPackage = $this->createMock(CompletePackageInterface::class); - $composerPackage->expects(self::once()) - ->method('getExtra') - ->willReturn([ - PieInstalledJsonMetadataKeys::InstalledBinary->value => '/path/to/some/file', - PieInstalledJsonMetadataKeys::BinaryChecksum->value => 'some-checksum-value', - 'something else' => 'hopefully this does not make it in', - ]); - - self::assertEqualsCanonicalizing( - [ - PieInstalledJsonMetadataKeys::InstalledBinary->value => '/path/to/some/file', - PieInstalledJsonMetadataKeys::BinaryChecksum->value => 'some-checksum-value', - ], - PieInstalledJsonMetadataKeys::pieMetadataFromComposerPackage($composerPackage), - ); - } -} diff --git a/test/unit/Installing/PackageMetadataMissingTest.php b/test/unit/Installing/PackageMetadataMissingTest.php index 5fdc5a78..bb282980 100644 --- a/test/unit/Installing/PackageMetadataMissingTest.php +++ b/test/unit/Installing/PackageMetadataMissingTest.php @@ -5,6 +5,7 @@ namespace Php\PieUnitTest\Installing; use Composer\Package\CompletePackageInterface; +use Php\Pie\ComposerIntegration\InstalledJsonMetadata; use Php\Pie\DependencyResolver\Package; use Php\Pie\ExtensionName; use Php\Pie\ExtensionType; @@ -28,10 +29,10 @@ public function testDuringUninstall(): void $exception = PackageMetadataMissing::duringUninstall( $package, - [ + InstalledJsonMetadata::fromArray([ 'a' => 'something', 'b' => 'something else', - ], + ]), ['b', 'c', 'd'], ); diff --git a/test/unit/Installing/UninstallUsingUnlinkTest.php b/test/unit/Installing/UninstallUsingUnlinkTest.php index 66d4c8cb..f81ebd3e 100644 --- a/test/unit/Installing/UninstallUsingUnlinkTest.php +++ b/test/unit/Installing/UninstallUsingUnlinkTest.php @@ -6,7 +6,7 @@ use Composer\Package\CompletePackageInterface; use Composer\Util\Filesystem; -use Php\Pie\ComposerIntegration\PieInstalledJsonMetadataKeys; +use Php\Pie\ComposerIntegration\InstalledJsonMetadata; use Php\Pie\DependencyResolver\Package; use Php\Pie\ExtensionName; use Php\Pie\ExtensionType; @@ -100,8 +100,8 @@ public function testBinaryFileIsRemoved(): void $composerPackage ->method('getExtra') ->willReturn([ - PieInstalledJsonMetadataKeys::InstalledBinary->value => $extensionFile, - PieInstalledJsonMetadataKeys::BinaryChecksum->value => $testHash, + InstalledJsonMetadata::KEY_INSTALLED_BINARY => $extensionFile, + InstalledJsonMetadata::KEY_BINARY_CHECKSUM => $testHash, ]); $package = new Package( @@ -148,8 +148,8 @@ public function testBinaryFileIsRemovedOnWindows(): void $composerPackage ->method('getExtra') ->willReturn([ - PieInstalledJsonMetadataKeys::InstalledBinary->value => $extensionFile, - PieInstalledJsonMetadataKeys::BinaryChecksum->value => $testHash, + InstalledJsonMetadata::KEY_INSTALLED_BINARY => $extensionFile, + InstalledJsonMetadata::KEY_BINARY_CHECKSUM => $testHash, ]); $package = new Package( @@ -200,8 +200,8 @@ public function testBinaryFileIsRemovedWhenInstallRootUsed(): void $composerPackage ->method('getExtra') ->willReturn([ - PieInstalledJsonMetadataKeys::InstalledBinary->value => $extensionFile, - PieInstalledJsonMetadataKeys::BinaryChecksum->value => $testHash, + InstalledJsonMetadata::KEY_INSTALLED_BINARY => $extensionFile, + InstalledJsonMetadata::KEY_BINARY_CHECKSUM => $testHash, ]); $package = new Package( @@ -251,8 +251,8 @@ public function testExtensionPathInMetadataNotMatchingConventionWillThrowExcepti $composerPackage ->method('getExtra') ->willReturn([ - PieInstalledJsonMetadataKeys::InstalledBinary->value => $extensionFile, - PieInstalledJsonMetadataKeys::BinaryChecksum->value => $testHash, + InstalledJsonMetadata::KEY_INSTALLED_BINARY => $extensionFile, + InstalledJsonMetadata::KEY_BINARY_CHECKSUM => $testHash, ]); $package = new Package(