From ac57adcc990a24eec86d5c4c15ef0c30b3a8ace7 Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 30 Nov 2022 22:31:13 +0700 Subject: [PATCH] Test zip handler --- src/BinariesInstaller.php | 29 +++ src/GlobCleaner.php | 2 +- src/Handler/ArchiveHandler.php | 21 +- src/Handler/BaseHandler.php | 40 ++-- src/Handler/FileHandler.php | 2 +- tests/Unit/GlobCleanerTest.php | 12 +- tests/Unit/Handler/ArchiveHandlerTestCase.php | 28 +++ tests/Unit/Handler/BaseHandlerTestCase.php | 187 ++++++++++++++++++ tests/Unit/Handler/ZipHandlerTest.php | 24 +++ 9 files changed, 307 insertions(+), 38 deletions(-) create mode 100644 src/BinariesInstaller.php create mode 100644 tests/Unit/Handler/ArchiveHandlerTestCase.php create mode 100644 tests/Unit/Handler/BaseHandlerTestCase.php create mode 100644 tests/Unit/Handler/ZipHandlerTest.php diff --git a/src/BinariesInstaller.php b/src/BinariesInstaller.php new file mode 100644 index 0000000..7e4fe56 --- /dev/null +++ b/src/BinariesInstaller.php @@ -0,0 +1,29 @@ +getBinaries() as $bin) { + $path = $baseDir.\DIRECTORY_SEPARATOR.$bin; + if (Platform::isWindows() || (method_exists(Platform::class, 'isWindowsSubsystemForLinux') ? Platform::isWindowsSubsystemForLinux() : false)) { + $proxy = $path.'.bat'; + if (file_exists($proxy)) { + $io->writeError(' Skipped installation of bin '.$bin.'.bat proxy for package '.$subpackage->getName().': a .bat proxy was already installed'); + } else { + $caller = BinaryInstaller::determineBinaryCaller($path); + file_put_contents($proxy, '@'.$caller.' "%~dp0'.ProcessExecutor::escape(basename($proxy, '.bat')).'" %*'); + } + } else { + chmod($path, 0777 ^ umask()); + } + } + } +} diff --git a/src/GlobCleaner.php b/src/GlobCleaner.php index 6b6326e..2c6cfc5 100644 --- a/src/GlobCleaner.php +++ b/src/GlobCleaner.php @@ -8,7 +8,7 @@ class GlobCleaner { - public static function clean(string $baseDir, array $ignores): void + public function clean(string $baseDir, array $ignores): void { if (empty($ignores)) { return; diff --git a/src/Handler/ArchiveHandler.php b/src/Handler/ArchiveHandler.php index 5812b5a..64dd6d4 100644 --- a/src/Handler/ArchiveHandler.php +++ b/src/Handler/ArchiveHandler.php @@ -4,10 +4,25 @@ use Composer\Composer; use Composer\IO\IOInterface; +use Composer\Package\PackageInterface; +use LastCall\DownloadsPlugin\BinariesInstaller; use LastCall\DownloadsPlugin\GlobCleaner; abstract class ArchiveHandler extends BaseHandler { + protected ?GlobCleaner $cleaner = null; + + public function __construct( + PackageInterface $parent, + string $parentPath, + array $extraFile, + ?BinariesInstaller $binariesInstaller = null, + ?GlobCleaner $cleaner = null + ) { + parent::__construct($parent, $parentPath, $extraFile, $binariesInstaller); + $this->cleaner = $cleaner ?? new GlobCleaner(); + } + public function getTrackingFile(): string { $file = basename($this->extraFile['id']).'-'.md5($this->extraFile['id']).'.json'; @@ -38,7 +53,7 @@ protected function getChecksumData(): array private function findIgnores(): array { if (isset($this->extraFile['ignore']) && !\is_array($this->extraFile['ignore'])) { - throw new \UnexpectedValueException(sprintf('Attribute "ignore" of extra file "%s" defined in package "%s" must be array, "%s" given.', $this->extraFile['id'], $this->parent->getId(), get_debug_type($this->extraFile['ignore']))); + throw new \UnexpectedValueException(sprintf('Attribute "ignore" of extra file "%s" defined in package "%s" must be array, "%s" given.', $this->extraFile['id'], $this->parent->getName(), get_debug_type($this->extraFile['ignore']))); } return $this->extraFile['ignore'] ?? []; @@ -58,13 +73,13 @@ protected function download(Composer $composer, IOInterface $io): void } else { $downloadManager->download($this->getSubpackage(), $targetPath); } - GlobCleaner::clean($targetPath, $this->findIgnores()); + $this->cleaner->clean($targetPath, $this->findIgnores()); } protected function getBinaries(): array { if (isset($this->extraFile['executable']) && !\is_array($this->extraFile['executable'])) { - throw new \UnexpectedValueException(sprintf('Attribute "executable" of extra file "%s" defined in package "%s" must be array, "%s" given.', $this->extraFile['id'], $this->parent->getId(), get_debug_type($this->extraFile['executable']))); + throw new \UnexpectedValueException(sprintf('Attribute "executable" of extra file "%s" defined in package "%s" must be array, "%s" given.', $this->extraFile['id'], $this->parent->getName(), get_debug_type($this->extraFile['executable']))); } return $this->extraFile['executable'] ?? []; diff --git a/src/Handler/BaseHandler.php b/src/Handler/BaseHandler.php index 76d698b..22236de 100644 --- a/src/Handler/BaseHandler.php +++ b/src/Handler/BaseHandler.php @@ -9,13 +9,11 @@ namespace LastCall\DownloadsPlugin\Handler; use Composer\Composer; -use Composer\Installer\BinaryInstaller; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Package\RootPackageInterface; use Composer\Package\Version\VersionParser; -use Composer\Util\Platform; -use Composer\Util\ProcessExecutor; +use LastCall\DownloadsPlugin\BinariesInstaller; use LastCall\DownloadsPlugin\Subpackage; abstract class BaseHandler implements HandlerInterface @@ -23,10 +21,16 @@ abstract class BaseHandler implements HandlerInterface public const FAKE_VERSION = 'dev-master'; public const DOT_DIR = '.composer-downloads'; - protected ?Subpackage $subpackage = null; + private ?Subpackage $subpackage = null; + private BinariesInstaller $binariesInstaller; - public function __construct(protected PackageInterface $parent, protected string $parentPath, protected array $extraFile) - { + public function __construct( + protected PackageInterface $parent, + protected string $parentPath, + protected array $extraFile, + ?BinariesInstaller $binariesInstaller = null + ) { + $this->binariesInstaller = $binariesInstaller ?? new BinariesInstaller(); } public function getSubpackage(): Subpackage @@ -60,12 +64,11 @@ private function createSubpackage(): Subpackage $parent, $extraFile['id'], $extraFile['url'], - null, + $this->getDistType(), $extraFile['path'], $version, $prettyVersion ); - $package->setDistType($this->getDistType()); $package->setBinaries($this->getBinaries()); return $package; @@ -117,27 +120,8 @@ protected function isComposerV2(): bool public function install(Composer $composer, IOInterface $io): void { $this->download($composer, $io); - $this->markExecutable($io); + $this->binariesInstaller->install($this->getSubpackage(), $this->parentPath, $io); } abstract protected function download(Composer $composer, IOInterface $io): void; - - private function markExecutable(IOInterface $io): void - { - $subpackage = $this->getSubpackage(); - foreach ($subpackage->getBinaries() as $bin) { - $path = $this->parentPath.\DIRECTORY_SEPARATOR.$bin; - if (Platform::isWindows() || (method_exists(Platform::class, 'isWindowsSubsystemForLinux') ? Platform::isWindowsSubsystemForLinux() : false)) { - $proxy = $path.'.bat'; - if (file_exists($proxy)) { - $io->writeError(' Skipped installation of bin '.$bin.'.bat proxy for package '.$subpackage->getName().': a .bat proxy was already installed'); - } else { - $caller = BinaryInstaller::determineBinaryCaller($path); - file_put_contents($proxy, '@'.$caller.' "%~dp0'.ProcessExecutor::escape(basename($proxy, '.bat')).'" %*'); - } - } else { - chmod($path, 0777 ^ umask()); - } - } - } } diff --git a/src/Handler/FileHandler.php b/src/Handler/FileHandler.php index be02615..6599b39 100644 --- a/src/Handler/FileHandler.php +++ b/src/Handler/FileHandler.php @@ -71,7 +71,7 @@ protected function getDistType(): string protected function getBinaries(): array { if (isset($this->extraFile['executable']) && !\is_bool($this->extraFile['executable'])) { - throw new \UnexpectedValueException(sprintf('Attribute "executable" of extra file "%s" defined in package "%s" must be boolean, "%s" given.', $this->extraFile['id'], $this->parent->getId(), get_debug_type($this->extraFile['executable']))); + throw new \UnexpectedValueException(sprintf('Attribute "executable" of extra file "%s" defined in package "%s" must be boolean, "%s" given.', $this->extraFile['id'], $this->parent->getName(), get_debug_type($this->extraFile['executable']))); } return empty($this->extraFile['executable']) ? [] : [$this->extraFile['path']]; diff --git a/tests/Unit/GlobCleanerTest.php b/tests/Unit/GlobCleanerTest.php index 73bcd29..acc7a45 100644 --- a/tests/Unit/GlobCleanerTest.php +++ b/tests/Unit/GlobCleanerTest.php @@ -33,10 +33,12 @@ class GlobCleanerTest extends TestCase '/file2.bat' => self::FILE, ]; - protected ?FileSystem $fs = null; + private GlobCleaner $cleaner; + private ?FileSystem $fs = null; protected function setUp(): void { + $this->cleaner = new GlobCleaner(); $this->fs = new FileSystem(); // Keep virtual file system alive during test $this->createFilesAndDirectories(); } @@ -48,25 +50,25 @@ protected function tearDown(): void public function testEmptyIgnore(): void { - GlobCleaner::clean($this->fs->path('/dir1'), []); + $this->cleaner->clean($this->fs->path('/dir1'), []); $this->assertRemaining(array_keys(self::FILES_AND_DIRECTORIES)); } public function testNotEmptyIgnores(): void { - GlobCleaner::clean($this->fs->path('/dir1'), [ + $this->cleaner->clean($this->fs->path('/dir1'), [ 'file*', '!/dir11/file2.xls', '/dir12', ]); - GlobCleaner::clean($this->fs->path('/dir2'), [ + $this->cleaner->clean($this->fs->path('/dir2'), [ '/dir21', '/dir22', '/dir22/.composer-downloads', '/dir22/.composer-downloads/*', '/dir22/.composer-downloads/file1.json', ]); - GlobCleaner::clean($this->fs->path('/dir3'), [ + $this->cleaner->clean($this->fs->path('/dir3'), [ '*', ]); $this->assertRemaining([ diff --git a/tests/Unit/Handler/ArchiveHandlerTestCase.php b/tests/Unit/Handler/ArchiveHandlerTestCase.php new file mode 100644 index 0000000..ab33390 --- /dev/null +++ b/tests/Unit/Handler/ArchiveHandlerTestCase.php @@ -0,0 +1,28 @@ + 'value'], 'stdClass'], + ]; + } +} diff --git a/tests/Unit/Handler/BaseHandlerTestCase.php b/tests/Unit/Handler/BaseHandlerTestCase.php new file mode 100644 index 0000000..aff5f62 --- /dev/null +++ b/tests/Unit/Handler/BaseHandlerTestCase.php @@ -0,0 +1,187 @@ +composer = $this->createMock(Composer::class); + $this->io = $this->createMock(IOInterface::class); + $this->downloadManager = $this->createMock(DownloadManager::class); + $this->cleaner = $this->createMock(GlobCleaner::class); + $this->binariesInstaller = $this->createMock(BinariesInstaller::class); + $this->parent = $this->createMock(PackageInterface::class); + $this->extraFile = [ + 'id' => $this->id, + 'url' => $this->url, + 'path' => $this->path, + ]; + $this->targetPath = $this->parentPath.\DIRECTORY_SEPARATOR.$this->path; + } + + public function testGetSubpackageWithExplicitVersion(): void + { + $handler = $this->createHandler($this->parent, $this->parentPath, $this->extraFile + ['version' => '1.2.3']); + $version = version_compare(Composer::RUNTIME_API_VERSION, '2.0.0') >= 0 ? 'dev-master' : '9999999-dev'; + $expectSubpackage = new Subpackage($this->parent, $this->id, $this->url, $this->getDistType(), $this->path, $version, '1.2.3'); + $this->assertEquals($expectSubpackage, $handler->getSubpackage()); + $this->assertSame([], $handler->getSubpackage()->getBinaries()); + } + + public function testGetSubpackageFromRootPackage(): void + { + $rootPackage = $this->createMock(RootPackage::class); + $handler = $this->createHandler($rootPackage, $this->parentPath, $this->extraFile); + $version = version_compare(Composer::RUNTIME_API_VERSION, '2.0.0') >= 0 ? 'dev-master' : '9999999-dev'; + $expectSubpackage = new Subpackage($rootPackage, $this->id, $this->url, $this->getDistType(), $this->path, $version, 'dev-master'); + $this->assertEquals($expectSubpackage, $handler->getSubpackage()); + $this->assertSame([], $handler->getSubpackage()->getBinaries()); + } + + public function testGetSubpackageFromNormalPackage(): void + { + $this->parent->expects($this->once())->method('getVersion')->willReturn('1.2.3.0'); + $this->parent->expects($this->once())->method('getPrettyVersion')->willReturn('v1.2.3'); + $handler = $this->createHandler($this->parent, $this->parentPath, $this->extraFile); + $expectSubpackage = new Subpackage($this->parent, $this->id, $this->url, $this->getDistType(), $this->path, '1.2.3.0', 'v1.2.3'); + $this->assertEquals($expectSubpackage, $handler->getSubpackage()); + $this->assertSame([], $handler->getSubpackage()->getBinaries()); + } + + abstract public function getBinariesTests(): array; + + /** + * @dataProvider getBinariesTests + */ + public function testSubpackageBinaries(mixed $executable, array $expectedBinaries): void + { + $handler = $this->createHandler($this->parent, $this->parentPath, $this->extraFile + ['executable' => $executable]); + $this->assertSame($expectedBinaries, $handler->getSubpackage()->getBinaries()); + } + + abstract public function getInvalidBinariesTests(): array; + + /** + * @dataProvider getInvalidBinariesTests + */ + public function testSubpackageInvalidBinaries(mixed $executable, string $type): void + { + $parentName = 'vendor/parent-package'; + $this->parent->expects($this->exactly(2))->method('getName')->willReturn($parentName); + $handler = $this->createHandler($this->parent, $this->parentPath, $this->extraFile + ['executable' => $executable]); + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage(sprintf('Attribute "executable" of extra file "%s" defined in package "%s" must be array, "%s" given.', $this->id, $parentName, $type)); + $handler->getSubpackage(); + } + + public function testGetTrackingData(): void + { + $parentName = 'vendor/parent-package'; + $this->parent->expects($this->once())->method('getName')->willReturn($parentName); + $handler = $this->createHandler($this->parent, $this->parentPath, $this->extraFile + ['ignore' => $this->ignore]); + $this->assertSame([ + 'ignore' => $this->ignore, + 'name' => "{$parentName}:{$this->id}", + 'url' => $this->url, + 'checksum' => $this->getChecksum(), + ], $handler->getTrackingData()); + } + + public function testGetChecksum(): void + { + $handler = $this->createHandler($this->parent, $this->parentPath, $this->extraFile + ['ignore' => $this->ignore]); + $this->assertSame($this->getChecksum(), $handler->getChecksum()); + } + + public function testGetTargetPath(): void + { + $handler = $this->createHandler($this->parent, $this->parentPath, $this->extraFile); + $this->assertSame($this->targetPath, $handler->getTargetPath()); + } + + public function testInstall(): void + { + $this->composer->expects($this->once())->method('getDownloadManager')->willReturn($this->downloadManager); + $isComposerV2 = version_compare(Composer::RUNTIME_API_VERSION, '2.0.0') >= 0; + if ($isComposerV2) { + $downloadPromise = $this->createMock(PromiseInterface::class); + $installPromise = $this->createMock(PromiseInterface::class); + $this->downloadManager + ->expects($this->once()) + ->method('download') + ->with($this->isInstanceOf(Subpackage::class), $this->targetPath) + ->willReturn($downloadPromise); + $this->downloadManager + ->expects($this->once()) + ->method('install') + ->with($this->isInstanceOf(Subpackage::class), $this->targetPath) + ->willReturn($installPromise); + $loop = $this->createMock(Loop::class); + $loop + ->expects($this->exactly(2)) + ->method('wait') + ->withConsecutive( + [[$downloadPromise]], + [[$installPromise]] + ); + $this->composer->expects($this->exactly(2))->method('getLoop')->willReturn($loop); + } else { + $this->downloadManager + ->expects($this->once()) + ->method('download') + ->with($this->isInstanceOf(Subpackage::class), $this->targetPath); + } + $this->binariesInstaller + ->expects($this->once()) + ->method('install') + ->with($this->isInstanceOf(Subpackage::class), $this->parentPath, $this->io); + $this->cleaner->expects($this->once())->method('clean')->with($this->targetPath, $this->ignore); + $handler = $this->createHandler($this->parent, $this->parentPath, $this->extraFile + ['ignore' => $this->ignore]); + $handler->install($this->composer, $this->io); + } + + abstract protected function getHandlerClass(): string; + + protected function getDistType(): string + { + return 'file'; + } + + abstract protected function getChecksum(): string; + + private function createHandler(PackageInterface $parent, string $parentPath, array $extraFile): HandlerInterface + { + $class = $this->getHandlerClass(); + + return new $class($parent, $parentPath, $extraFile, $this->binariesInstaller, $this->cleaner); + } +} diff --git a/tests/Unit/Handler/ZipHandlerTest.php b/tests/Unit/Handler/ZipHandlerTest.php new file mode 100644 index 0000000..8d6f8ff --- /dev/null +++ b/tests/Unit/Handler/ZipHandlerTest.php @@ -0,0 +1,24 @@ +