2022-02-23 15:58:18 +00:00
< ? php declare ( strict_types = 1 );
2012-03-29 13:34:57 +00:00
/*
* This file is part of Composer .
*
* ( c ) Nils Adermann < naderman @ naderman . de >
* Jordi Boggiano < j . boggiano @ seld . be >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Composer\Test\Downloader ;
2022-02-18 10:22:01 +00:00
use React\Promise\PromiseInterface ;
2012-03-29 13:34:57 +00:00
use Composer\Downloader\ZipDownloader ;
2017-03-14 22:43:48 +00:00
use Composer\Package\PackageInterface ;
2018-11-12 14:23:32 +00:00
use Composer\Test\TestCase ;
2015-12-14 14:35:27 +00:00
use Composer\Util\Filesystem ;
2018-10-31 11:44:54 +00:00
use Composer\Util\HttpDownloader ;
2019-01-17 16:12:33 +00:00
use Composer\Util\Loop ;
2012-03-29 13:34:57 +00:00
2016-01-21 12:01:55 +00:00
class ZipDownloaderTest extends TestCase
2012-03-29 13:34:57 +00:00
{
2021-10-30 08:21:50 +00:00
/** @var string */
2015-12-14 15:50:04 +00:00
private $testDir ;
2021-10-30 08:21:50 +00:00
/** @var \Composer\Util\HttpDownloader */
2019-01-07 15:22:41 +00:00
private $httpDownloader ;
2021-10-30 08:21:50 +00:00
/** @var \Composer\IO\IOInterface&\PHPUnit\Framework\MockObject\MockObject */
2018-11-12 14:23:32 +00:00
private $io ;
2021-10-30 08:21:50 +00:00
/** @var \Composer\Config&\PHPUnit\Framework\MockObject\MockObject */
2018-11-12 14:23:32 +00:00
private $config ;
2021-10-30 08:21:50 +00:00
/** @var \Composer\Package\PackageInterface&\PHPUnit\Framework\MockObject\MockObject */
2019-01-17 16:12:33 +00:00
private $package ;
2022-03-29 16:57:30 +00:00
/** @var string */
private $filename ;
2015-12-14 14:35:27 +00:00
2021-12-08 16:03:05 +00:00
public function setUp () : void
2012-03-29 13:34:57 +00:00
{
2022-05-11 14:05:35 +00:00
$this -> testDir = self :: getUniqueTmpDirectory ();
2018-04-12 08:24:56 +00:00
$this -> io = $this -> getMockBuilder ( 'Composer\IO\IOInterface' ) -> getMock ();
$this -> config = $this -> getMockBuilder ( 'Composer\Config' ) -> getMock ();
2018-10-31 11:44:54 +00:00
$dlConfig = $this -> getMockBuilder ( 'Composer\Config' ) -> getMock ();
$this -> httpDownloader = new HttpDownloader ( $this -> io , $dlConfig );
2019-01-17 16:12:33 +00:00
$this -> package = $this -> getMockBuilder ( 'Composer\Package\PackageInterface' ) -> getMock ();
2022-03-29 16:57:30 +00:00
$this -> filename = $this -> testDir . '/composer-test.zip' ;
file_put_contents ( $this -> filename , 'zip' );
2015-12-14 14:35:27 +00:00
}
2021-12-09 16:09:07 +00:00
protected function tearDown () : void
2015-12-14 14:35:27 +00:00
{
2021-12-09 16:09:07 +00:00
parent :: tearDown ();
2015-12-14 15:50:04 +00:00
$fs = new Filesystem ;
$fs -> removeDirectory ( $this -> testDir );
2017-03-30 07:24:48 +00:00
$this -> setPrivateProperty ( 'hasZipArchive' , null );
2017-03-14 22:43:48 +00:00
}
2021-10-30 08:21:50 +00:00
/**
* @ param mixed $value
* @ param ? \Composer\Test\Downloader\MockedZipDownloader $obj
*/
2022-02-22 15:47:09 +00:00
public function setPrivateProperty ( string $name , $value , $obj = null ) : void
2017-03-14 22:43:48 +00:00
{
$reflectionClass = new \ReflectionClass ( 'Composer\Downloader\ZipDownloader' );
$reflectedProperty = $reflectionClass -> getProperty ( $name );
$reflectedProperty -> setAccessible ( true );
2023-08-30 09:42:33 +00:00
$reflectedProperty -> setValue ( $obj , $value );
2012-03-29 13:34:57 +00:00
}
2022-02-18 09:38:54 +00:00
public function testErrorMessages () : void
2012-03-29 13:34:57 +00:00
{
2017-02-13 14:54:55 +00:00
if ( ! class_exists ( 'ZipArchive' )) {
$this -> markTestSkipped ( 'zip extension missing' );
}
2020-03-28 19:38:50 +00:00
$this -> config -> expects ( $this -> any ())
2017-03-14 22:43:48 +00:00
-> method ( 'get' )
-> with ( 'vendor-dir' )
-> will ( $this -> returnValue ( $this -> testDir ));
2019-01-17 16:12:33 +00:00
$this -> package -> expects ( $this -> any ())
2012-03-29 13:34:57 +00:00
-> method ( 'getDistUrl' )
2013-07-01 23:45:43 +00:00
-> will ( $this -> returnValue ( $distUrl = 'file://' . __FILE__ ))
;
2019-01-17 16:12:33 +00:00
$this -> package -> expects ( $this -> any ())
2013-07-01 23:45:43 +00:00
-> method ( 'getDistUrls' )
2022-08-17 12:20:07 +00:00
-> will ( $this -> returnValue ([ $distUrl ]))
2012-03-29 13:34:57 +00:00
;
2019-01-17 16:12:33 +00:00
$this -> package -> expects ( $this -> atLeastOnce ())
2014-05-07 16:25:28 +00:00
-> method ( 'getTransportOptions' )
2022-08-17 12:20:07 +00:00
-> will ( $this -> returnValue ([]))
2012-03-29 13:34:57 +00:00
;
2018-10-31 11:44:54 +00:00
$downloader = new ZipDownloader ( $this -> io , $this -> config , $this -> httpDownloader );
2017-03-14 22:43:48 +00:00
2012-03-29 13:34:57 +00:00
try {
2019-01-17 16:12:33 +00:00
$loop = new Loop ( $this -> httpDownloader );
2020-06-05 06:33:44 +00:00
$promise = $downloader -> download ( $this -> package , $path = sys_get_temp_dir () . '/composer-zip-test' );
2022-08-17 12:20:07 +00:00
$loop -> wait ([ $promise ]);
2019-01-17 16:12:33 +00:00
$downloader -> install ( $this -> package , $path );
2012-03-29 13:34:57 +00:00
$this -> fail ( 'Download of invalid zip files should throw an exception' );
2017-03-14 22:43:48 +00:00
} catch ( \Exception $e ) {
2024-05-29 21:12:06 +00:00
self :: assertStringContainsString ( 'is not a zip archive' , $e -> getMessage ());
2012-03-29 13:34:57 +00:00
}
}
2017-02-13 14:54:55 +00:00
2022-02-18 09:38:54 +00:00
public function testZipArchiveOnlyFailed () : void
2017-03-14 22:43:48 +00:00
{
2021-12-09 19:55:26 +00:00
self :: expectException ( 'RuntimeException' );
self :: expectExceptionMessage ( 'There was an error extracting the ZIP file' );
2017-12-19 10:07:09 +00:00
if ( ! class_exists ( 'ZipArchive' )) {
$this -> markTestSkipped ( 'zip extension missing' );
}
2017-03-30 07:24:48 +00:00
$this -> setPrivateProperty ( 'hasZipArchive' , true );
2018-10-31 11:44:54 +00:00
$downloader = new MockedZipDownloader ( $this -> io , $this -> config , $this -> httpDownloader );
2021-12-09 12:02:20 +00:00
$zipArchive = $this -> getMockBuilder ( 'ZipArchive' ) -> getMock ();
2021-12-08 21:06:17 +00:00
$zipArchive -> expects ( $this -> once ())
2017-03-14 22:43:48 +00:00
-> method ( 'open' )
-> will ( $this -> returnValue ( true ));
2021-12-08 21:06:17 +00:00
$zipArchive -> expects ( $this -> once ())
2017-03-14 22:43:48 +00:00
-> method ( 'extractTo' )
-> will ( $this -> returnValue ( false ));
$this -> setPrivateProperty ( 'zipArchiveObject' , $zipArchive , $downloader );
2022-03-29 16:57:30 +00:00
$promise = $downloader -> extract ( $this -> package , $this -> filename , 'vendor/dir' );
2020-06-05 12:05:19 +00:00
$this -> wait ( $promise );
2017-02-13 14:54:55 +00:00
}
2022-02-18 09:38:54 +00:00
public function testZipArchiveExtractOnlyFailed () : void
2017-04-08 18:47:46 +00:00
{
2021-12-09 19:55:26 +00:00
self :: expectException ( 'RuntimeException' );
self :: expectExceptionMessage ( 'The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems): Not a directory' );
2017-12-19 10:07:09 +00:00
if ( ! class_exists ( 'ZipArchive' )) {
$this -> markTestSkipped ( 'zip extension missing' );
}
2017-04-08 18:47:46 +00:00
$this -> setPrivateProperty ( 'hasZipArchive' , true );
2018-10-31 11:44:54 +00:00
$downloader = new MockedZipDownloader ( $this -> io , $this -> config , $this -> httpDownloader );
2018-04-12 08:24:56 +00:00
$zipArchive = $this -> getMockBuilder ( 'ZipArchive' ) -> getMock ();
2021-12-09 12:02:20 +00:00
$zipArchive -> expects ( $this -> once ())
2017-04-08 18:47:46 +00:00
-> method ( 'open' )
-> will ( $this -> returnValue ( true ));
2021-12-09 12:02:20 +00:00
$zipArchive -> expects ( $this -> once ())
2017-04-08 18:47:46 +00:00
-> method ( 'extractTo' )
-> will ( $this -> throwException ( new \ErrorException ( 'Not a directory' )));
$this -> setPrivateProperty ( 'zipArchiveObject' , $zipArchive , $downloader );
2022-03-29 16:57:30 +00:00
$promise = $downloader -> extract ( $this -> package , $this -> filename , 'vendor/dir' );
2020-06-05 12:05:19 +00:00
$this -> wait ( $promise );
2017-04-08 18:47:46 +00:00
}
2022-02-18 09:38:54 +00:00
public function testZipArchiveOnlyGood () : void
2017-03-14 22:43:48 +00:00
{
2017-12-19 10:07:09 +00:00
if ( ! class_exists ( 'ZipArchive' )) {
$this -> markTestSkipped ( 'zip extension missing' );
}
2017-03-30 07:24:48 +00:00
$this -> setPrivateProperty ( 'hasZipArchive' , true );
2018-10-31 11:44:54 +00:00
$downloader = new MockedZipDownloader ( $this -> io , $this -> config , $this -> httpDownloader );
2018-04-12 08:24:56 +00:00
$zipArchive = $this -> getMockBuilder ( 'ZipArchive' ) -> getMock ();
2021-12-09 12:02:20 +00:00
$zipArchive -> expects ( $this -> once ())
2017-03-14 22:43:48 +00:00
-> method ( 'open' )
-> will ( $this -> returnValue ( true ));
2021-12-09 12:02:20 +00:00
$zipArchive -> expects ( $this -> once ())
2017-03-14 22:43:48 +00:00
-> method ( 'extractTo' )
-> will ( $this -> returnValue ( true ));
2024-05-29 20:08:42 +00:00
$zipArchive -> expects ( $this -> once ())
-> method ( 'count' )
-> will ( $this -> returnValue ( 0 ));
2017-03-14 22:43:48 +00:00
$this -> setPrivateProperty ( 'zipArchiveObject' , $zipArchive , $downloader );
2022-03-29 16:57:30 +00:00
$promise = $downloader -> extract ( $this -> package , $this -> filename , 'vendor/dir' );
2020-06-05 12:05:19 +00:00
$this -> wait ( $promise );
2017-02-13 14:54:55 +00:00
}
2022-02-18 09:38:54 +00:00
public function testSystemUnzipOnlyFailed () : void
2017-03-14 22:43:48 +00:00
{
2021-12-09 19:55:26 +00:00
self :: expectException ( 'Exception' );
self :: expectExceptionMessage ( 'Failed to extract : (1) unzip' );
2020-06-05 07:07:40 +00:00
$this -> setPrivateProperty ( 'isWindows' , false );
2017-03-30 07:24:48 +00:00
$this -> setPrivateProperty ( 'hasZipArchive' , false );
2022-08-17 12:20:07 +00:00
$this -> setPrivateProperty ( 'unzipCommands' , [[ 'unzip' , 'unzip -qq %s -d %s' ]]);
2020-06-05 07:07:40 +00:00
$procMock = $this -> getMockBuilder ( 'Symfony\Component\Process\Process' ) -> disableOriginalConstructor () -> getMock ();
$procMock -> expects ( $this -> any ())
-> method ( 'getExitCode' )
-> will ( $this -> returnValue ( 1 ));
$procMock -> expects ( $this -> any ())
-> method ( 'isSuccessful' )
-> will ( $this -> returnValue ( false ));
$procMock -> expects ( $this -> any ())
-> method ( 'getErrorOutput' )
-> will ( $this -> returnValue ( 'output' ));
2018-04-12 08:24:56 +00:00
$processExecutor = $this -> getMockBuilder ( 'Composer\Util\ProcessExecutor' ) -> getMock ();
2021-12-09 12:02:20 +00:00
$processExecutor -> expects ( $this -> once ())
2020-06-05 07:07:40 +00:00
-> method ( 'executeAsync' )
-> will ( $this -> returnValue ( \React\Promise\resolve ( $procMock )));
2017-03-14 22:43:48 +00:00
2020-06-04 13:30:20 +00:00
$downloader = new MockedZipDownloader ( $this -> io , $this -> config , $this -> httpDownloader , null , null , null , $processExecutor );
2022-03-29 16:57:30 +00:00
$promise = $downloader -> extract ( $this -> package , $this -> filename , 'vendor/dir' );
2020-06-05 12:05:19 +00:00
$this -> wait ( $promise );
2017-02-13 14:54:55 +00:00
}
2022-02-18 09:38:54 +00:00
public function testSystemUnzipOnlyGood () : void
2017-03-14 22:43:48 +00:00
{
2020-06-05 07:07:40 +00:00
$this -> setPrivateProperty ( 'isWindows' , false );
2017-03-30 07:24:48 +00:00
$this -> setPrivateProperty ( 'hasZipArchive' , false );
2022-08-17 12:20:07 +00:00
$this -> setPrivateProperty ( 'unzipCommands' , [[ 'unzip' , 'unzip -qq %s -d %s' ]]);
2020-06-05 07:07:40 +00:00
$procMock = $this -> getMockBuilder ( 'Symfony\Component\Process\Process' ) -> disableOriginalConstructor () -> getMock ();
$procMock -> expects ( $this -> any ())
-> method ( 'getExitCode' )
-> will ( $this -> returnValue ( 0 ));
$procMock -> expects ( $this -> any ())
-> method ( 'isSuccessful' )
-> will ( $this -> returnValue ( true ));
$procMock -> expects ( $this -> any ())
-> method ( 'getErrorOutput' )
-> will ( $this -> returnValue ( 'output' ));
2018-04-12 08:24:56 +00:00
$processExecutor = $this -> getMockBuilder ( 'Composer\Util\ProcessExecutor' ) -> getMock ();
2021-12-09 12:02:20 +00:00
$processExecutor -> expects ( $this -> once ())
2020-06-05 07:07:40 +00:00
-> method ( 'executeAsync' )
-> will ( $this -> returnValue ( \React\Promise\resolve ( $procMock )));
2017-03-14 22:43:48 +00:00
2020-06-04 13:30:20 +00:00
$downloader = new MockedZipDownloader ( $this -> io , $this -> config , $this -> httpDownloader , null , null , null , $processExecutor );
2022-03-29 16:57:30 +00:00
$promise = $downloader -> extract ( $this -> package , $this -> filename , 'vendor/dir' );
2020-06-05 12:05:19 +00:00
$this -> wait ( $promise );
2017-02-13 14:54:55 +00:00
}
2022-02-18 09:38:54 +00:00
public function testNonWindowsFallbackGood () : void
2017-03-14 22:43:48 +00:00
{
2017-12-19 10:07:09 +00:00
if ( ! class_exists ( 'ZipArchive' )) {
$this -> markTestSkipped ( 'zip extension missing' );
}
2017-03-30 07:24:48 +00:00
$this -> setPrivateProperty ( 'isWindows' , false );
$this -> setPrivateProperty ( 'hasZipArchive' , true );
2017-03-14 22:43:48 +00:00
2020-06-05 07:07:40 +00:00
$procMock = $this -> getMockBuilder ( 'Symfony\Component\Process\Process' ) -> disableOriginalConstructor () -> getMock ();
$procMock -> expects ( $this -> any ())
-> method ( 'getExitCode' )
-> will ( $this -> returnValue ( 1 ));
$procMock -> expects ( $this -> any ())
-> method ( 'isSuccessful' )
-> will ( $this -> returnValue ( false ));
$procMock -> expects ( $this -> any ())
-> method ( 'getErrorOutput' )
-> will ( $this -> returnValue ( 'output' ));
2018-04-12 08:24:56 +00:00
$processExecutor = $this -> getMockBuilder ( 'Composer\Util\ProcessExecutor' ) -> getMock ();
2021-12-09 12:02:20 +00:00
$processExecutor -> expects ( $this -> once ())
2020-06-05 07:07:40 +00:00
-> method ( 'executeAsync' )
-> will ( $this -> returnValue ( \React\Promise\resolve ( $procMock )));
2017-03-14 22:43:48 +00:00
2018-04-12 08:24:56 +00:00
$zipArchive = $this -> getMockBuilder ( 'ZipArchive' ) -> getMock ();
2021-12-09 12:02:20 +00:00
$zipArchive -> expects ( $this -> once ())
2017-03-14 22:43:48 +00:00
-> method ( 'open' )
-> will ( $this -> returnValue ( true ));
2021-12-09 12:02:20 +00:00
$zipArchive -> expects ( $this -> once ())
2017-03-14 22:43:48 +00:00
-> method ( 'extractTo' )
-> will ( $this -> returnValue ( true ));
2024-05-29 20:08:42 +00:00
$zipArchive -> expects ( $this -> once ())
-> method ( 'count' )
-> will ( $this -> returnValue ( 0 ));
2017-03-14 22:43:48 +00:00
2020-06-04 13:30:20 +00:00
$downloader = new MockedZipDownloader ( $this -> io , $this -> config , $this -> httpDownloader , null , null , null , $processExecutor );
2017-03-14 22:43:48 +00:00
$this -> setPrivateProperty ( 'zipArchiveObject' , $zipArchive , $downloader );
2022-03-29 16:57:30 +00:00
$promise = $downloader -> extract ( $this -> package , $this -> filename , 'vendor/dir' );
2020-06-05 12:05:19 +00:00
$this -> wait ( $promise );
2017-02-13 14:54:55 +00:00
}
2022-02-18 09:38:54 +00:00
public function testNonWindowsFallbackFailed () : void
2017-03-14 22:43:48 +00:00
{
2021-12-09 19:55:26 +00:00
self :: expectException ( 'Exception' );
self :: expectExceptionMessage ( 'There was an error extracting the ZIP file' );
2017-12-19 10:07:09 +00:00
if ( ! class_exists ( 'ZipArchive' )) {
$this -> markTestSkipped ( 'zip extension missing' );
}
2017-03-30 07:24:48 +00:00
$this -> setPrivateProperty ( 'isWindows' , false );
$this -> setPrivateProperty ( 'hasZipArchive' , true );
2017-03-14 22:43:48 +00:00
2020-06-05 12:05:19 +00:00
$procMock = $this -> getMockBuilder ( 'Symfony\Component\Process\Process' ) -> disableOriginalConstructor () -> getMock ();
$procMock -> expects ( $this -> any ())
-> method ( 'getExitCode' )
-> will ( $this -> returnValue ( 1 ));
$procMock -> expects ( $this -> any ())
-> method ( 'isSuccessful' )
-> will ( $this -> returnValue ( false ));
$procMock -> expects ( $this -> any ())
-> method ( 'getErrorOutput' )
-> will ( $this -> returnValue ( 'output' ));
2018-04-12 08:24:56 +00:00
$processExecutor = $this -> getMockBuilder ( 'Composer\Util\ProcessExecutor' ) -> getMock ();
2021-12-09 12:02:20 +00:00
$processExecutor -> expects ( $this -> once ())
2020-06-05 12:05:19 +00:00
-> method ( 'executeAsync' )
-> will ( $this -> returnValue ( \React\Promise\resolve ( $procMock )));
2017-03-14 22:43:48 +00:00
2018-04-12 08:24:56 +00:00
$zipArchive = $this -> getMockBuilder ( 'ZipArchive' ) -> getMock ();
2021-12-09 12:02:20 +00:00
$zipArchive -> expects ( $this -> once ())
2017-03-14 22:43:48 +00:00
-> method ( 'open' )
-> will ( $this -> returnValue ( true ));
2021-12-09 12:02:20 +00:00
$zipArchive -> expects ( $this -> once ())
2017-03-14 22:43:48 +00:00
-> method ( 'extractTo' )
-> will ( $this -> returnValue ( false ));
2020-06-04 13:30:20 +00:00
$downloader = new MockedZipDownloader ( $this -> io , $this -> config , $this -> httpDownloader , null , null , null , $processExecutor );
2017-03-14 22:43:48 +00:00
$this -> setPrivateProperty ( 'zipArchiveObject' , $zipArchive , $downloader );
2022-03-29 16:57:30 +00:00
$promise = $downloader -> extract ( $this -> package , $this -> filename , 'vendor/dir' );
2020-06-05 12:05:19 +00:00
$this -> wait ( $promise );
}
2021-10-30 08:21:50 +00:00
/**
2023-07-20 10:52:28 +00:00
* @ param ? \React\Promise\PromiseInterface < mixed > $promise
2021-10-30 08:21:50 +00:00
*/
2022-02-18 09:38:54 +00:00
private function wait ( $promise ) : void
2020-06-05 12:05:19 +00:00
{
if ( null === $promise ) {
return ;
}
$e = null ;
2022-08-17 12:20:07 +00:00
$promise -> then ( static function () : void {
2020-06-05 12:05:19 +00:00
// noop
2022-08-17 12:20:07 +00:00
}, static function ( $ex ) use ( & $e ) : void {
2020-06-05 12:05:19 +00:00
$e = $ex ;
});
2023-07-20 10:52:28 +00:00
if ( $e !== null ) {
2020-06-05 12:05:19 +00:00
throw $e ;
}
2017-02-13 14:54:55 +00:00
}
2017-03-14 22:43:48 +00:00
}
2017-02-13 14:54:55 +00:00
2017-03-14 22:43:48 +00:00
class MockedZipDownloader extends ZipDownloader
{
2022-08-17 12:20:07 +00:00
public function download ( PackageInterface $package , $path , ? PackageInterface $prevPackage = null , bool $output = true ) : PromiseInterface
2017-03-14 22:43:48 +00:00
{
2022-03-18 08:20:42 +00:00
return \React\Promise\resolve ( null );
2017-02-13 14:54:55 +00:00
}
2022-08-17 12:20:07 +00:00
public function install ( PackageInterface $package , $path , bool $output = true ) : PromiseInterface
2019-01-17 16:12:33 +00:00
{
2022-03-18 08:20:42 +00:00
return \React\Promise\resolve ( null );
2019-01-17 16:12:33 +00:00
}
2022-02-22 21:10:52 +00:00
public function extract ( PackageInterface $package , $file , $path ) : PromiseInterface
2017-03-14 22:43:48 +00:00
{
2020-06-05 07:07:40 +00:00
return parent :: extract ( $package , $file , $path );
2017-02-13 14:54:55 +00:00
}
2012-03-29 13:34:57 +00:00
}