From 7e1ef19a5a544af35a3b063652659637c711fb2a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 30 Jul 2020 14:53:46 +0200 Subject: [PATCH] Expand library version checking capabilities (closes #9093) --- src/Composer/Platform/HhvmDetector.php | 59 + src/Composer/Platform/Runtime.php | 94 ++ .../Repository/PlatformRepository.php | 426 ++++-- src/Composer/Util/Version.php | 104 ++ .../Test/Platform/HhvmDetectorTest.php | 97 ++ .../Repository/PlatformRepositoryTest.php | 1196 ++++++++++++++++- tests/Composer/Test/Util/VersionTest.php | 131 ++ 7 files changed, 1958 insertions(+), 149 deletions(-) create mode 100644 src/Composer/Platform/HhvmDetector.php create mode 100644 src/Composer/Platform/Runtime.php create mode 100644 src/Composer/Util/Version.php create mode 100644 tests/Composer/Test/Platform/HhvmDetectorTest.php create mode 100644 tests/Composer/Test/Util/VersionTest.php diff --git a/src/Composer/Platform/HhvmDetector.php b/src/Composer/Platform/HhvmDetector.php new file mode 100644 index 000000000..a0524e758 --- /dev/null +++ b/src/Composer/Platform/HhvmDetector.php @@ -0,0 +1,59 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Platform; + +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Process\ExecutableFinder; + +class HhvmDetector +{ + private static $hhvmVersion; + private $executableFinder; + private $processExecutor; + + public function __construct(ExecutableFinder $executableFinder = null, ProcessExecutor $processExecutor = null) { + + $this->executableFinder = $executableFinder; + $this->processExecutor = $processExecutor; + } + + public function reset() + { + self::$hhvmVersion = null; + } + + public function getVersion() { + if (null !== self::$hhvmVersion) { + return self::$hhvmVersion ?: null; + } + + self::$hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null; + if (self::$hhvmVersion === null && !Platform::isWindows()) { + self::$hhvmVersion = false; + $this->executableFinder = $this->executableFinder ?: new ExecutableFinder(); + $hhvmPath = $this->executableFinder->find('hhvm'); + if ($hhvmPath !== null) { + $this->processExecutor = $this->processExecutor ?: new ProcessExecutor(); + $exitCode = $this->processExecutor->execute( + ProcessExecutor::escape($hhvmPath). + ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', + self::$hhvmVersion + ); + if ($exitCode !== 0) { + self::$hhvmVersion = false; + } + } + } + return self::$hhvmVersion; + } +} diff --git a/src/Composer/Platform/Runtime.php b/src/Composer/Platform/Runtime.php new file mode 100644 index 000000000..baf450df1 --- /dev/null +++ b/src/Composer/Platform/Runtime.php @@ -0,0 +1,94 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Platform; + +class Runtime +{ + /** + * @param string $constant + * @param class-string $class + * @return bool + */ + public function hasConstant($constant, $class = null) { + return defined(ltrim($class.'::'.$constant, ':')); + } + + /** + * @param bool $constant + * @param class-string $class + * @return mixed + */ + public function getConstant($constant, $class = null) { + return constant(ltrim($class.'::'.$constant, ':')); + } + + /** + * @param callable $callable + * @param array $arguments + * @return mixed + */ + public function invoke($callable, array $arguments = array()) { + return call_user_func_array($callable, $arguments); + } + + /** + * @param class-string $class + * @return bool + */ + public function hasClass($class) { + return class_exists($class, false); + } + + /** + * @param class-string $class + * @param array $arguments + * @return object + */ + public function construct($class, array $arguments = array()) { + if (empty($arguments)) { + return new $class; + } + + $refl = new \ReflectionClass($class); + return $refl->newInstanceArgs($arguments); + } + + /** @return string[] */ + public function getExtensions() + { + return get_loaded_extensions(); + } + + /** + * @param string $extension + * @return string + */ + public function getExtensionVersion($extension) + { + return phpversion($extension); + } + + /** + * @param string $extension + * @return string + */ + public function getExtensionInfo($extension) + { + $reflector = new \ReflectionExtension($extension); + + ob_start(); + $reflector->info(); + + return ob_get_clean(); + } +} diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index c88604a3b..899b177cf 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -14,14 +14,16 @@ namespace Composer\Repository; use Composer\Composer; use Composer\Package\CompletePackage; +use Composer\Package\Link; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; +use Composer\Platform\HhvmDetector; +use Composer\Platform\Runtime; use Composer\Plugin\PluginInterface; -use Composer\Util\ProcessExecutor; +use Composer\Semver\Constraint\Constraint; use Composer\Util\Silencer; -use Composer\Util\Platform; +use Composer\Util\Version; use Composer\XdebugHandler\XdebugHandler; -use Symfony\Component\Process\ExecutableFinder; /** * @author Jordi Boggiano @@ -30,7 +32,9 @@ class PlatformRepository extends ArrayRepository { const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer-(?:plugin|runtime)-api)$}iD'; - private static $hhvmVersion; + /** + * @var VersionParser + */ private $versionParser; /** @@ -42,11 +46,13 @@ class PlatformRepository extends ArrayRepository */ private $overrides = array(); - private $process; + private $runtime; + private $hhvmDetector; - public function __construct(array $packages = array(), array $overrides = array(), ProcessExecutor $process = null) + public function __construct(array $packages = array(), array $overrides = array(), Runtime $runtime = null, HhvmDetector $hhvmDetector = null) { - $this->process = $process; + $this->runtime = $runtime ?: new Runtime(); + $this->hhvmDetector = $hhvmDetector ?: new HhvmDetector(); foreach ($overrides as $name => $version) { $this->overrides[strtolower($name)] = array('name' => $name, 'version' => $version); } @@ -88,10 +94,10 @@ class PlatformRepository extends ArrayRepository $this->addPackage($composerRuntimeApi); try { - $prettyVersion = PHP_VERSION; + $prettyVersion = $this->runtime->getConstant('PHP_VERSION'); $version = $this->versionParser->normalize($prettyVersion); } catch (\UnexpectedValueException $e) { - $prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', PHP_VERSION); + $prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', $this->runtime->getConstant('PHP_VERSION')); $version = $this->versionParser->normalize($prettyVersion); } @@ -99,19 +105,19 @@ class PlatformRepository extends ArrayRepository $php->setDescription('The PHP interpreter'); $this->addPackage($php); - if (PHP_DEBUG) { + if ($this->runtime->getConstant('PHP_DEBUG')) { $phpdebug = new CompletePackage('php-debug', $version, $prettyVersion); $phpdebug->setDescription('The PHP interpreter, with debugging symbols'); $this->addPackage($phpdebug); } - if (defined('PHP_ZTS') && PHP_ZTS) { + if ($this->runtime->hasConstant('PHP_ZTS') && $this->runtime->getConstant('PHP_ZTS')) { $phpzts = new CompletePackage('php-zts', $version, $prettyVersion); $phpzts->setDescription('The PHP interpreter, with Zend Thread Safety'); $this->addPackage($phpzts); } - if (PHP_INT_SIZE === 8) { + if ($this->runtime->getConstant('PHP_INT_SIZE') === 8) { $php64 = new CompletePackage('php-64bit', $version, $prettyVersion); $php64->setDescription('The PHP interpreter, 64bit'); $this->addPackage($php64); @@ -119,13 +125,13 @@ class PlatformRepository extends ArrayRepository // The AF_INET6 constant is only defined if ext-sockets is available but // IPv6 support might still be available. - if (defined('AF_INET6') || Silencer::call('inet_pton', '::') !== false) { + if ($this->runtime->hasConstant('AF_INET6') || Silencer::call(array($this->runtime, 'invoke'), array('inet_pton', '::')) !== false) { $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion); $phpIpv6->setDescription('The PHP interpreter, with IPv6 support'); $this->addPackage($phpIpv6); } - $loadedExtensions = get_loaded_extensions(); + $loadedExtensions = $this->runtime->getExtensions(); // Extensions scanning foreach ($loadedExtensions as $name) { @@ -133,9 +139,7 @@ class PlatformRepository extends ArrayRepository continue; } - $reflExt = new \ReflectionExtension($name); - $prettyVersion = $reflExt->getVersion(); - $this->addExtension($name, $prettyVersion); + $this->addExtension($name, $this->runtime->getExtensionVersion($name)); } // Check for Xdebug in a restarted process @@ -147,112 +151,317 @@ class PlatformRepository extends ArrayRepository // Doing it this way to know that functions or constants exist before // relying on them. foreach ($loadedExtensions as $name) { - $prettyVersion = null; - $description = 'The '.$name.' PHP library'; switch ($name) { + case 'amqp': + $info = $this->runtime->getExtensionInfo($name); + + // librabbitmq version => 0.9.0 + if (preg_match('/^librabbitmq version => (?.+)$/m', $info, $librabbitmqMatches)) { + $this->addLibrary($name.'-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version'); + } + + // AMQP protocol version => 0-9-1 + if (preg_match('/^AMQP protocol version => (?.+)$/m', $info, $protocolMatches)) { + $this->addLibrary($name.'-protocol', str_replace('-', '.', $protocolMatches['version']), 'AMQP protocol version'); + } + break; + + case 'bz2': + $info = $this->runtime->getExtensionInfo($name); + + // BZip2 Version => 1.0.6, 6-Sept-2010 + if (preg_match('/^BZip2 Version => (?.*),/m', $info, $matches)) { + $this->addLibrary($name, $matches['version']); + } + break; + case 'curl': - $curlVersion = curl_version(); - $prettyVersion = $curlVersion['version']; + $curlVersion = $this->runtime->invoke('curl_version'); + $this->addLibrary($name, $curlVersion['version']); + + $info = $this->runtime->getExtensionInfo($name); + + // SSL Version => OpenSSL/1.0.1t + if (preg_match('{^SSL Version => (?[^/]+)/(?.+)$}m', $info, $sslMatches)) { + $library = strtolower($sslMatches['library']); + if ($library === 'openssl') { + $parsedVersion = Version::parseOpenssl($sslMatches['version'], $isFips); + $this->addLibrary($name.'-openssl'.($isFips ? '-fips': ''), $parsedVersion, 'curl OpenSSL version ('.$parsedVersion.')', array(), $isFips ? array('curl-openssl'): array()); + } else { + $this->addLibrary($name.'-'.$library, $sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', array('curl-openssl')); + } + } + + // libSSH Version => libssh2/1.4.3 + if (preg_match('{^libSSH Version => (?[^/]+)/(?.+?)(?:/.*)?$}m', $info, $sshMatches)) { + $this->addLibrary($name.'-'.strtolower($sshMatches['library']), $sshMatches['version'], 'curl '.$sshMatches['library'].' version'); + } + + // ZLib Version => 1.2.8 + if (preg_match('{^ZLib Version => (?.+)$}m', $info, $zlibMatches)) { + $this->addLibrary($name.'-zlib', $zlibMatches['version'], 'curl zlib version'); + } break; - case 'iconv': - $prettyVersion = ICONV_VERSION; + case 'date': + $info = $this->runtime->getExtensionInfo($name); + + // timelib version => 2018.03 + if (preg_match('/^timelib version => (?.+)$/m', $info, $timelibMatches)) { + $this->addLibrary($name.'-timelib', $timelibMatches['version'], 'date timelib version'); + } + + // Timezone Database => internal + if (preg_match('/^Timezone Database => (?internal|external)$/m', $info, $zoneinfoSourceMatches)) { + $external = $zoneinfoSourceMatches['source'] === 'external'; + if (preg_match('/^"Olson" Timezone Database Version => (?.+?)(\.system)?$/m', $info, $zoneinfoMatches)) { + // If the timezonedb is provided by ext/timezonedb, register that version as a replacement + if ($external && in_array('timezonedb', $loadedExtensions, true)) { + $this->addLibrary('timezonedb-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', array($name.'-zoneinfo')); + } else { + $this->addLibrary($name.'-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date'); + } + } + } break; - case 'intl': - $name = 'ICU'; - if (defined('INTL_ICU_VERSION')) { - $prettyVersion = INTL_ICU_VERSION; - } else { - $reflector = new \ReflectionExtension('intl'); + case 'fileinfo': + $info = $this->runtime->getExtensionInfo($name); - ob_start(); - $reflector->info(); - $output = ob_get_clean(); + // libmagic => 537 + if (preg_match('/^^libmagic => (?.+)$/m', $info, $magicMatches)) { + $this->addLibrary($name.'-libmagic', $magicMatches['version'], 'fileinfo libmagic version'); + } + break; - preg_match('/^ICU version => (.*)$/m', $output, $matches); - $prettyVersion = $matches[1]; + case 'gd': + $this->addLibrary($name, $this->runtime->getConstant('GD_VERSION')); + + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^libJPEG Version => (?.+?)(?: compatible)?$/m', $info, $libjpegMatches)) { + $this->addLibrary($name.'-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd'); + } + + if (preg_match('/^libPNG Version => (?.+)$/m', $info, $libpngMatches)) { + $this->addLibrary($name.'-libpng', $libpngMatches['version'], 'libpng version for gd'); + } + + if (preg_match('/^FreeType Version => (?.+)$/m', $info, $freetypeMatches)) { + $this->addLibrary($name.'-freetype', $freetypeMatches['version'], 'freetype version for gd'); + } + + if (preg_match('/^libXpm Version => (?\d+)$/m', $info, $libxpmMatches)) { + $this->addLibrary($name.'-libxpm', Version::convertLibxpmVersionId($libxpmMatches['versionId']), 'libxpm version for gd'); } break; + case 'gmp': + $this->addLibrary($name, $this->runtime->getConstant('GMP_VERSION')); + break; + + case 'iconv': + $this->addLibrary($name, $this->runtime->getConstant('ICONV_VERSION')); + break; + + case 'intl': + $info = $this->runtime->getExtensionInfo($name); + + $description = 'The ICU unicode and globalization support library'; + // Truthy check is for testing only so we can make the condition fail + if ($this->runtime->hasConstant('INTL_ICU_VERSION')) { + $this->addLibrary('icu', $this->runtime->getConstant('INTL_ICU_VERSION'), $description); + } elseif (preg_match('/^ICU version => (?.+)$/m', $info, $matches)) { + $this->addLibrary('icu', $matches['version'], $description); + } + + // ICU TZData version => 2019c + if (preg_match('/^ICU TZData version => (?.*)$/m', $info, $zoneinfoMatches)) { + $this->addLibrary('icu-zoneinfo', Version::parseZoneinfoVersion($zoneinfoMatches['version']), 'zoneinfo ("Olson") database for icu'); + } + + # Add a separate version for the CLDR library version + if ($this->runtime->hasClass('ResourceBundle')) { + $cldrVersion = $this->runtime->invoke(array('ResourceBundle', 'create'), array('root', 'ICUDATA', false))->get('Version'); + $this->addLibrary('icu-cldr', $cldrVersion, 'ICU CLDR project version'); + } + + if ($this->runtime->hasClass('IntlChar')) { + $this->addLibrary('icu-unicode', implode('.', array_slice($this->runtime->invoke(array('IntlChar', 'getUnicodeVersion')), 0, 3)), 'ICU unicode version'); + } + break; + case 'imagick': - $imagick = new \Imagick(); - $imageMagickVersion = $imagick->getVersion(); + $imageMagickVersion = $this->runtime->construct('Imagick')->getVersion(); // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org - preg_match('/^ImageMagick ([\d.]+)(?:-(\d+))?/', $imageMagickVersion['versionString'], $matches); - if (isset($matches[2])) { - $prettyVersion = "{$matches[1]}.{$matches[2]}"; - } else { - $prettyVersion = $matches[1]; + preg_match('/^ImageMagick (?[\d.]+)(?:-(?\d+))?/', $imageMagickVersion['versionString'], $matches); + $version = $matches['version']; + if (isset($matches['patch'])) { + $version .= '.'.$matches['patch']; + } + + $this->addLibrary($name.'-imagemagick', $version, null, array('imagick')); + break; + + case 'ldap': + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^Vendor Version => (?\d+)$/m', $info, $matches) && preg_match('/^Vendor Name => (?.+)$/m', $info, $vendorMatches)) { + $this->addLibrary($name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId($matches['versionId']), $vendorMatches['vendor'].' version of ldap'); } break; case 'libxml': - $prettyVersion = LIBXML_DOTTED_VERSION; + // ext/dom, ext/simplexml, ext/xmlreader and ext/xmlwriter use the same libxml as the ext/libxml + $libxmlProvides = array_map(function($extension) { + return $extension . '-libxml'; + }, array_intersect($loadedExtensions, array('dom', 'simplexml', 'xml', 'xmlreader', 'xmlwriter'))); + $this->addLibrary($name, $this->runtime->getConstant('LIBXML_DOTTED_VERSION'), 'libxml library version', array(), $libxmlProvides); + + break; + + case 'mbstring': + $info = $this->runtime->getExtensionInfo($name); + + // libmbfl version => 1.3.2 + if (preg_match('/^libmbfl version => (?.+)$/m', $info, $libmbflMatches)) { + $this->addLibrary($name.'-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version'); + } + + if ($this->runtime->hasConstant('MB_ONIGURUMA_VERSION')) { + $this->addLibrary($name.'-oniguruma', $this->runtime->getConstant('MB_ONIGURUMA_VERSION'), 'mbstring oniguruma version'); + + // Multibyte regex (oniguruma) version => 5.9.5 + // oniguruma version => 6.9.0 + } elseif (preg_match('/^(?:oniguruma|Multibyte regex \(oniguruma\)) version => (?.+)$/m', $info, $onigurumaMatches)) { + $this->addLibrary($name.'-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version'); + } + + break; + + case 'memcached': + $info = $this->runtime->getExtensionInfo($name); + + // libmemcached version => 1.0.18 + if (preg_match('/^libmemcached version => (?.+)$/m', $info, $matches)) { + $this->addLibrary($name.'-libmemcached', $matches['version'], 'libmemcached version'); + } break; case 'openssl': - $prettyVersion = preg_replace_callback('{^(?:OpenSSL|LibreSSL)?\s*([0-9.]+)([a-z]*).*}i', function ($match) { - if (empty($match[2])) { - return $match[1]; - } - - // OpenSSL versions add another letter when they reach Z. - // e.g. OpenSSL 0.9.8zh 3 Dec 2015 - - if (!preg_match('{^z*[a-z]$}', $match[2])) { - // 0.9.8abc is garbage - return 0; - } - - $len = strlen($match[2]); - $patchVersion = ($len - 1) * 26; // All Z - $patchVersion += ord($match[2][$len - 1]) - 96; - - return $match[1].'.'.$patchVersion; - }, OPENSSL_VERSION_TEXT); - - $description = OPENSSL_VERSION_TEXT; + // OpenSSL 1.1.1g 21 Apr 2020 + if (preg_match('{^(?:OpenSSL|LibreSSL)?\s*(?\S+)}i', $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), $matches)) { + $parsedVersion = Version::parseOpenssl($matches['version'], $isFips); + $this->addLibrary($name.($isFips ? '-fips' : ''), $parsedVersion, $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), array(), $isFips ? array($name) : array()); + } break; case 'pcre': - $prettyVersion = preg_replace('{^(\S+).*}', '$1', PCRE_VERSION); + $this->addLibrary($name, preg_replace('{^(\S+).*}', '$1', $this->runtime->getConstant('PCRE_VERSION'))); + + $info = $this->runtime->getExtensionInfo($name); + + // PCRE Unicode Version => 12.1.0 + if (preg_match('/^PCRE Unicode Version => (?.+)$/m', $info, $pcreUnicodeMatches)) { + $this->addLibrary($name.'-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support'); + } + break; - case 'uuid': - $prettyVersion = phpversion('uuid'); + case 'mysqlnd': + case 'pdo_mysql': + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^(?:Client API version|Version) => mysqlnd (?.+?) /mi', $info, $matches)) { + $this->addLibrary($name.'-mysqlnd', $matches['version'], 'mysqlnd library version for '.$name); + } + break; + + case 'mongodb': + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^libmongoc bundled version => (?.+)$/m', $info, $libmongocMatches)) { + $this->addLibrary($name.'-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb'); + } + + if (preg_match('/^libbson bundled version => (?.+)$/m', $info, $libbsonMatches)) { + $this->addLibrary($name.'-libbson', $libbsonMatches['version'], 'libbson version of mongodb'); + } + break; + + case 'pgsql': + case 'pdo_pgsql': + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^PostgreSQL\(libpq\) Version => (?.*)$/m', $info, $matches)) { + $this->addLibrary($name.'-libpq', $matches['version'], 'libpq for '.$name); + } + break; + + case 'libsodium': + case 'sodium': + $this->addLibrary('libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION')); + break; + + case 'sqlite3': + case 'pdo_sqlite': + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^SQLite Library => (?.+)$/m', $info, $matches)) { + $this->addLibrary($name.'-sqlite', $matches['version']); + } + break; + + case 'ssh2': + $info = $this->runtime->getExtensionInfo($name); + + if (preg_match('/^libssh2 version => (?.+)$/m', $info, $matches)) { + $this->addLibrary($name.'-libssh2', $matches['version']); + } break; case 'xsl': - $prettyVersion = LIBXSLT_DOTTED_VERSION; + $this->addLibrary('libxslt', $this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, array('xsl')); + + $info = $this->runtime->getExtensionInfo('xsl'); + if (preg_match('/^libxslt compiled against libxml Version => (?.+)$/m', $info, $matches)) { + $this->addLibrary('libxslt-libxml', $matches['version'], 'libxml version libxslt is compiled against'); + } + break; + + case 'yaml': + $info = $this->runtime->getExtensionInfo('yaml'); + + if (preg_match('/^LibYAML Version => (?.+)$/m', $info, $matches)) { + $this->addLibrary($name.'-libyaml', $matches['version'], 'libyaml version of yaml'); + } break; case 'zip': - if (defined('ZipArchive::LIBZIP_VERSION')) { - $prettyVersion = \ZipArchive::LIBZIP_VERSION; - } else { - continue 2; + if ($this->runtime->hasConstant('LIBZIP_VERSION', 'ZipArchive')) { + $this->addLibrary($name.'-libzip', $this->runtime->getConstant('LIBZIP_VERSION','ZipArchive'), null, array('zip')); } + break; + + case 'zlib': + if ($this->runtime->hasConstant('ZLIB_VERSION')) { + $this->addLibrary($name, $this->runtime->getConstant('ZLIB_VERSION')); + + // Linked Version => 1.2.8 + } elseif (preg_match('/^Linked Version => (?.+)$/m', $this->runtime->getExtensionInfo($name), $matches)) { + $this->addLibrary($name, $matches['version']); + } + break; default: - // None handled extensions have no special cases, skip - continue 2; + break; } - - try { - $version = $this->versionParser->normalize($prettyVersion); - } catch (\UnexpectedValueException $e) { - continue; - } - - $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); - $lib->setDescription($description); - $this->addPackage($lib); } - if ($hhvmVersion = self::getHHVMVersion($this->process)) { + $hhvmVersion = $this->hhvmDetector->getVersion(); + if ($hhvmVersion) { try { $prettyVersion = $hhvmVersion; $version = $this->versionParser->normalize($prettyVersion); @@ -337,6 +546,11 @@ class PlatformRepository extends ArrayRepository $packageName = $this->buildPackageName($name); $ext = new CompletePackage($packageName, $version, $prettyVersion); $ext->setDescription('The '.$name.' PHP extension'.$extraDescription); + + if ($name === 'uuid') { + $ext->setReplaces(array(new Link('ext-uuid', 'lib-uuid', new Constraint('=', $version)))); + } + $this->addPackage($ext); } @@ -349,28 +563,34 @@ class PlatformRepository extends ArrayRepository return 'ext-' . str_replace(' ', '-', $name); } - private static function getHHVMVersion(ProcessExecutor $process = null) + /** + * @param string $name + * @param string $prettyVersion + * @param string|null $description + * @param string[] $replaces + * @param string[] $provides + */ + private function addLibrary($name, $prettyVersion, $description = null, array $replaces = array(), array $provides = array()) { - if (null !== self::$hhvmVersion) { - return self::$hhvmVersion ?: null; + try { + $version = $this->versionParser->normalize($prettyVersion); + } catch (\UnexpectedValueException $e) { + return; } - self::$hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null; - if (self::$hhvmVersion === null && !Platform::isWindows()) { - self::$hhvmVersion = false; - $finder = new ExecutableFinder(); - $hhvmPath = $finder->find('hhvm'); - if ($hhvmPath !== null) { - $process = $process ?: new ProcessExecutor(); - $exitCode = $process->execute( - ProcessExecutor::escape($hhvmPath). - ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', - self::$hhvmVersion - ); - if ($exitCode !== 0) { - self::$hhvmVersion = false; - } - } + if ($description === null) { + $description = 'The '.$name.' library'; } + + $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion); + $lib->setDescription($description); + + $links = function ($alias) use ($name, $version) { + return new Link('lib-'.$name, 'lib-'.$alias, new Constraint('=', $version)); + }; + $lib->setReplaces(array_map($links, $replaces)); + $lib->setProvides(array_map($links, $provides)); + + $this->addPackage($lib); } } diff --git a/src/Composer/Util/Version.php b/src/Composer/Util/Version.php new file mode 100644 index 000000000..1907816c0 --- /dev/null +++ b/src/Composer/Util/Version.php @@ -0,0 +1,104 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * @author Lars Strojny + */ +class Version +{ + /** + * @param string $opensslVersion + * @param bool $isFips + * @return string|null + */ + public static function parseOpenssl($opensslVersion, &$isFips) + { + $isFips = false; + + if (!preg_match('/^(?[0-9.]+)(?[a-z]{0,2})?(?(?:-?(?:dev|pre|alpha|beta|rc|fips)[\d]*)*)?$/', $opensslVersion, $matches)) { + return null; + } + + $isFips = strpos($matches['suffix'], 'fips') !== false; + $suffix = strtr('-'.ltrim($matches['suffix'], '-'), array('-fips' => '', '-pre' => '-alpha')); + $patch = self::convertAlphaVersionToIntVersion($matches['patch']); + + return rtrim($matches['version'].'.'.$patch.$suffix, '-'); + } + + /** + * @param string $libjpegVersion + * @return string|null + */ + public static function parseLibjpeg($libjpegVersion) + { + if (!preg_match('/^(?\d+)(?[a-z]*)$/', $libjpegVersion, $matches)) { + return null; + } + + return $matches['major'].'.'.self::convertAlphaVersionToIntVersion($matches['minor']); + } + + /** + * @param string $zoneinfoVersion + * @return string|null + */ + public static function parseZoneinfoVersion($zoneinfoVersion) + { + if (!preg_match('/^(?\d{4})(?[a-z]*)$/', $zoneinfoVersion, $matches)) { + return null; + } + + return $matches['year'].'.'.self::convertAlphaVersionToIntVersion($matches['revision']); + } + + /** + * "" => 0, "a" => 1, "zg" => 33 + * + * @param string $alpha + * @return int + */ + private static function convertAlphaVersionToIntVersion($alpha) + { + return strlen($alpha) * (-ord('a')+1) + array_sum(array_map('ord', str_split($alpha))); + } + + /** + * @param int $versionId + * @return string + */ + public static function convertLibxpmVersionId($versionId) + { + return self::convertVersionId($versionId, 100); + } + + /** + * @param int $versionId + * @return string + */ + public static function convertOpenldapVersionId($versionId) + { + return self::convertVersionId($versionId, 100); + } + + private static function convertVersionId($versionId, $base) + { + return sprintf( + '%d.%d.%d', + $versionId / ($base * $base), + ($versionId / $base) % $base, + $versionId % $base + ); + } +} diff --git a/tests/Composer/Test/Platform/HhvmDetectorTest.php b/tests/Composer/Test/Platform/HhvmDetectorTest.php new file mode 100644 index 000000000..3e7e63228 --- /dev/null +++ b/tests/Composer/Test/Platform/HhvmDetectorTest.php @@ -0,0 +1,97 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Platform; + +use Composer\Platform\HhvmDetector; +use Composer\Test\TestCase; +use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; +use Symfony\Component\Process\ExecutableFinder; + +class HhvmDetectorTest extends TestCase +{ + private $hhvmDetector; + + protected function setUp() + { + $this->hhvmDetector = new HhvmDetector(); + $this->hhvmDetector->reset(); + } + + public function testHHVMVersionWhenExecutingInHHVM() { + if (!defined('HHVM_VERSION_ID')) { + self::markTestSkipped('Not running with HHVM'); + return; + } + $version = $this->hhvmDetector->getVersion(); + self::assertSame(self::versionIdToVersion(), $version); + } + + public function testHHVMVersionWhenExecutingInPHP() { + if (defined('HHVM_VERSION_ID')) { + self::markTestSkipped('Running with HHVM'); + return; + } + if (PHP_VERSION_ID < 50400) { + self::markTestSkipped('Test only works on PHP 5.4+'); + return; + } + if (Platform::isWindows()) { + self::markTestSkipped('Test does not run on Windows'); + return; + } + $finder = new ExecutableFinder(); + $hhvm = $finder->find('hhvm'); + if ($hhvm === null) { + self::markTestSkipped('HHVM is not installed'); + } + + $detectedVersion = $this->hhvmDetector->getVersion(); + self::assertNotNull($detectedVersion, 'Failed to detect HHVM version'); + + $process = new ProcessExecutor(); + $exitCode = $process->execute( + ProcessExecutor::escape($hhvm). + ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', + $version + ); + self::assertSame(0, $exitCode); + + self::assertSame(self::getVersionParser()->normalize($version), self::getVersionParser()->normalize($detectedVersion)); + } + + /** @runInSeparateProcess */ + public function testHHVMVersionWhenRunningInHHVMWithMockedConstant() + { + if (!defined('HHVM_VERSION_ID')) { + define('HHVM_VERSION', '2.2.1'); + define('HHVM_VERSION_ID', 20201); + } + $version = $this->hhvmDetector->getVersion(); + self::assertSame(self::getVersionParser()->normalize(self::versionIdToVersion()), self::getVersionParser()->normalize($version)); + } + + private static function versionIdToVersion() + { + if (!defined('HHVM_VERSION_ID')) { + return null; + } + + return sprintf( + '%d.%d.%d', + HHVM_VERSION_ID / 10000, + (HHVM_VERSION_ID / 100) % 100, + HHVM_VERSION_ID % 100 + ); + } +} diff --git a/tests/Composer/Test/Repository/PlatformRepositoryTest.php b/tests/Composer/Test/Repository/PlatformRepositoryTest.php index 519aadb31..d90fcbc73 100644 --- a/tests/Composer/Test/Repository/PlatformRepositoryTest.php +++ b/tests/Composer/Test/Repository/PlatformRepositoryTest.php @@ -12,65 +12,1169 @@ namespace Composer\Test\Repository; +use Composer\Package\Package; use Composer\Repository\PlatformRepository; use Composer\Test\TestCase; -use Composer\Util\ProcessExecutor; -use Composer\Package\Version\VersionParser; -use Composer\Util\Platform; -use Symfony\Component\Process\ExecutableFinder; +use PHPUnit\Framework\Assert; class PlatformRepositoryTest extends TestCase { - public function testHHVMVersionWhenExecutingInHHVM() + public function testHhvmPackage() { - if (!defined('HHVM_VERSION_ID')) { - $this->markTestSkipped('Not running with HHVM'); - return; - } - $repository = new PlatformRepository(); - $package = $repository->findPackage('hhvm', '*'); - $this->assertNotNull($package, 'failed to find HHVM package'); - $this->assertSame( - sprintf('%d.%d.%d', - HHVM_VERSION_ID / 10000, - (HHVM_VERSION_ID / 100) % 100, - HHVM_VERSION_ID % 100 + $hhvmDetector = $this->getMockBuilder('Composer\Platform\HhvmDetector')->getMock(); + $platformRepository = new PlatformRepository(array(), array(), null, $hhvmDetector); + + $hhvmDetector + ->method('getVersion') + ->willReturn('2.1.0'); + + $hhvm = $platformRepository->findPackage('hhvm', '*'); + self::assertNotNull($hhvm, 'hhvm found'); + + self::assertSame('2.1.0', $hhvm->getPrettyVersion()); + } + + public function getPhpFlavorTestCases() + { + return array( + array( + array( + 'PHP_VERSION' => '7.1.33', + ), + array( + 'php' => '7.1.33' + ) ), - $package->getPrettyVersion() + array( + array( + 'PHP_VERSION' => '7.2.31-1+ubuntu16.04.1+deb.sury.org+1', + 'PHP_DEBUG' => true, + ), + array( + 'php' => '7.2.31', + 'php-debug' => '7.2.31', + ), + ), + array( + array( + 'PHP_VERSION' => '7.2.31-1+ubuntu16.04.1+deb.sury.org+1', + 'PHP_ZTS' => true, + ), + array( + 'php' => '7.2.31', + 'php-zts' => '7.2.31', + ), + ), + array( + array( + 'PHP_VERSION' => '7.2.31-1+ubuntu16.04.1+deb.sury.org+1', + 'PHP_INT_SIZE' => 8, + ), + array( + 'php' => '7.2.31', + 'php-64bit' => '7.2.31', + ), + ), + array( + array( + 'PHP_VERSION' => '7.2.31-1+ubuntu16.04.1+deb.sury.org+1', + 'AF_INET6' => 30, + ), + array( + 'php' => '7.2.31', + 'php-ipv6' => '7.2.31', + ), + ), + array( + array( + 'PHP_VERSION' => '7.2.31-1+ubuntu16.04.1+deb.sury.org+1', + ), + array( + 'php' => '7.2.31', + 'php-ipv6' => '7.2.31', + ), + array( + array('inet_pton', array('::'), ''), + ) + ) ); } - public function testHHVMVersionWhenExecutingInPHP() + /** @dataProvider getPhpFlavorTestCases */ + public function testPhpVersion(array $constants, array $packages, array $functions = array()) { - if (defined('HHVM_VERSION_ID')) { - $this->markTestSkipped('Running with HHVM'); - return; - } - if (PHP_VERSION_ID < 50400) { - $this->markTestSkipped('Test only works on PHP 5.4+'); - return; - } - if (Platform::isWindows()) { - $this->markTestSkipped('Test does not run on Windows'); - return; - } - $finder = new ExecutableFinder(); - $hhvm = $finder->find('hhvm'); - if ($hhvm === null) { - $this->markTestSkipped('HHVM is not installed'); - } - $repository = new PlatformRepository(array(), array()); - $package = $repository->findPackage('hhvm', '*'); - $this->assertNotNull($package, 'failed to find HHVM package'); + $runtime = $this->getMockBuilder('Composer\Platform\Runtime')->getMock(); + $runtime + ->method('getExtensions') + ->willReturn(array()); + $runtime + ->method('hasConstant') + ->willReturnMap( + array_map(function($constant) { + return array($constant, null, true); + }, array_keys($constants)) + ); + $runtime + ->method('getConstant') + ->willReturnMap( + array_map(function($constant, $value) { + return array($constant, null, $value); + }, array_keys($constants), array_values($constants)) + ); + $runtime + ->method('invoke') + ->willReturnMap($functions); - $process = new ProcessExecutor(); - $exitCode = $process->execute( - ProcessExecutor::escape($hhvm). - ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null', - $version + $repository = new PlatformRepository(array(), array(), $runtime); + foreach ($packages as $packageName => $version) { + $package = $repository->findPackage($packageName, '*'); + self::assertNotNull($package, sprintf('Expected to find package "%s"', $packageName)); + self::assertSame($version, $package->getPrettyVersion(), sprintf('Expected package "%s" version to be %s, got %s', $packageName, $version, $package->getPrettyVersion())); + } + } + + public static function getLibraryTestCases() + { + return array( + 'amqp' => array( + 'amqp', + ' + +amqp + +Version => 1.9.4 +Revision => release +Compiled => Nov 19 2019 @ 08:44:26 +AMQP protocol version => 0-9-1 +librabbitmq version => 0.9.0 +Default max channels per connection => 256 +Default max frame size => 131072 +Default heartbeats interval => 0', + array( + 'lib-amqp-protocol' => '0.9.1', + 'lib-amqp-librabbitmq' => '0.9.0', + ) + ), + 'bz2' => array( + 'bz2', + ' +bz2 + +BZip2 Support => Enabled +Stream Wrapper support => compress.bzip2:// +Stream Filter support => bzip2.decompress, bzip2.compress +BZip2 Version => 1.0.5, 6-Sept-2010', + array('lib-bz2' => '1.0.5') + ), + 'curl' => array( + 'curl', + ' +curl + +cURL support => enabled +cURL Information => 7.38.0 +Age => 3 +Features +AsynchDNS => Yes +CharConv => No +Debug => No +GSS-Negotiate => No +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => Yes +SSL => Yes +SSPI => No +TLS-SRP => Yes +HTTP2 => No +GSSAPI => Yes +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtmp, rtsp, scp, sftp, smtp, smtps, telnet, tftp +Host => x86_64-pc-linux-gnu +SSL Version => OpenSSL/1.0.1t +ZLib Version => 1.2.8 +libSSH Version => libssh2/1.4.3 + +Directive => Local Value => Master Value +curl.cainfo => no value => no value', + array( + 'lib-curl' => '2.0.0', + 'lib-curl-openssl' => '1.0.1.20', + 'lib-curl-zlib' => '1.2.8', + 'lib-curl-libssh2' => '1.4.3', + ), + array(array('curl_version', array(), array('version' => '2.0.0'))) + ), + + 'curl: OpenSSL fips version' => array( + 'curl', + ' +curl + +cURL support => enabled +cURL Information => 7.38.0 +Age => 3 +Features +AsynchDNS => Yes +CharConv => No +Debug => No +GSS-Negotiate => No +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => Yes +SSL => Yes +SSPI => No +TLS-SRP => Yes +HTTP2 => No +GSSAPI => Yes +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtmp, rtsp, scp, sftp, smtp, smtps, telnet, tftp +Host => x86_64-pc-linux-gnu +SSL Version => OpenSSL/1.0.1t-fips +ZLib Version => 1.2.8 +libSSH Version => libssh2/1.4.3 + +Directive => Local Value => Master Value +curl.cainfo => no value => no value', + array( + 'lib-curl' => '2.0.0', + 'lib-curl-openssl-fips' => array('1.0.1.20', array(), array('lib-curl-openssl')), + 'lib-curl-zlib' => '1.2.8', + 'lib-curl-libssh2' => '1.4.3', + ), + array(array('curl_version', array(), array('version' => '2.0.0'))) + ), + 'curl: gnutls' => array( + 'curl', + ' +curl + +cURL support => enabled +cURL Information => 7.22.0 +Age => 3 +Features +AsynchDNS => No +CharConv => No +Debug => No +GSS-Negotiate => Yes +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => No +SSL => Yes +SSPI => No +TLS-SRP => Yes +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, pop3, pop3s, rtmp, rtsp, smtp, smtps, telnet, tftp +Host => x86_64-pc-linux-gnu +SSL Version => GnuTLS/2.12.14 +ZLib Version => 1.2.3.4', + array( + 'lib-curl' => '7.22.0', + 'lib-curl-zlib' => '1.2.3.4', + 'lib-curl-gnutls' => array('2.12.14', array('lib-curl-openssl')), + ), + array(array('curl_version', array(), array('version' => '7.22.0'))) + ), + 'curl: NSS' => array( + 'curl', + ' +curl + +cURL support => enabled +cURL Information => 7.24.0 +Age => 3 +Features +AsynchDNS => Yes +Debug => No +GSS-Negotiate => Yes +IDN => Yes +IPv6 => Yes +Largefile => Yes +NTLM => Yes +SPNEGO => No +SSL => Yes +SSPI => No +krb4 => No +libz => Yes +CharConv => No +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtsp, scp, sftp, smtp, smtps, telnet, tftp +Host => x86_64-redhat-linux-gnu +SSL Version => NSS/3.13.3.0 +ZLib Version => 1.2.5 +libSSH Version => libssh2/1.4.1', + array( + 'lib-curl' => '7.24.0', + 'lib-curl-nss' => array('3.13.3.0', array('lib-curl-openssl')), + 'lib-curl-zlib' => '1.2.5', + 'lib-curl-libssh2' => '1.4.1', + ), + array(array('curl_version', array(), array('version' => '7.24.0'))) + ), + 'curl: libssh not libssh2' => array( + 'curl', + ' + +curl + +cURL support => enabled +cURL Information => 7.68.0 +Age => 5 +Features +AsynchDNS => Yes +CharConv => No +Debug => No +GSS-Negotiate => No +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => Yes +SSL => Yes +SSPI => No +TLS-SRP => Yes +HTTP2 => Yes +GSSAPI => Yes +KERBEROS5 => Yes +UNIX_SOCKETS => Yes +PSL => Yes +HTTPS_PROXY => Yes +MULTI_SSL => No +BROTLI => Yes +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtmp, rtsp, scp, sftp, smb, smbs, smtp, smtps, telnet, tftp +Host => x86_64-pc-linux-gnu +SSL Version => OpenSSL/1.1.1g +ZLib Version => 1.2.11 +libSSH Version => libssh/0.9.3/openssl/zlib', + array( + 'lib-curl' => '7.68.0', + 'lib-curl-openssl' => '1.1.1.7', + 'lib-curl-zlib' => '1.2.11', + 'lib-curl-libssh' => '0.9.3', + ), + array(array('curl_version', array(), array('version' => '7.68.0'))), + ), + 'date' => array( + 'date', + ' +date + +date/time support => enabled +timelib version => 2018.03 +"Olson" Timezone Database Version => 2020.1 +Timezone Database => external +Default timezone => Europe/Berlin', + array( + 'lib-date-timelib' => '2018.03', + 'lib-date-zoneinfo' => '2020.1', + ) + ), + 'date: before timelib was extracted' => array( + 'date', + ' +date + +date/time support => enabled +"Olson" Timezone Database Version => 2013.2 +Timezone Database => internal +Default timezone => Europe/Amsterdam', + array( + 'lib-date-zoneinfo' => '2013.2', + 'lib-date-timelib' => false, + ) + ), + 'date: internal zoneinfo' => array( + array('date', 'timezonedb'), + ' +date + +date/time support => enabled +"Olson" Timezone Database Version => 2020.1 +Timezone Database => internal +Default timezone => UTC', + array('lib-date-zoneinfo' => '2020.1') + ), + 'date: external zoneinfo' => array( + array('date', 'timezonedb'), + ' +date + +date/time support => enabled +"Olson" Timezone Database Version => 2020.1 +Timezone Database => external +Default timezone => UTC', + array('lib-timezonedb-zoneinfo' => array('2020.1', array('lib-date-zoneinfo'))) + ), + 'date: zoneinfo 0.system' => array( + 'date', + ' + + +date/time support => enabled +timelib version => 2018.03 +"Olson" Timezone Database Version => 0.system +Timezone Database => internal +Default timezone => Europe/Berlin + +Directive => Local Value => Master Value +date.timezone => no value => no value +date.default_latitude => 31.7667 => 31.7667 +date.default_longitude => 35.2333 => 35.2333 +date.sunset_zenith => 90.583333 => 90.583333 +date.sunrise_zenith => 90.583333 => 90.583333', + array( + 'lib-date-zoneinfo' => '0', + 'lib-date-timelib' => '2018.03', + ) + ), + 'fileinfo' => array( + 'fileinfo', + ' +fileinfo + +fileinfo support => enabled +libmagic => 537', + array('lib-fileinfo-libmagic' => '537') + ), + 'gd' => array( + 'gd', + ' +gd + +GD Support => enabled +GD Version => bundled (2.1.0 compatible) +FreeType Support => enabled +FreeType Linkage => with freetype +FreeType Version => 2.10.0 +GIF Read Support => enabled +GIF Create Support => enabled +JPEG Support => enabled +libJPEG Version => 9 compatible +PNG Support => enabled +libPNG Version => 1.6.34 +WBMP Support => enabled +XBM Support => enabled +WebP Support => enabled + +Directive => Local Value => Master Value +gd.jpeg_ignore_warning => 1 => 1', + array( + 'lib-gd' => '1.2.3', + 'lib-gd-freetype' => '2.10.0', + 'lib-gd-libjpeg' => '9.0', + 'lib-gd-libpng' => '1.6.34', + ), + array(), + array(array('GD_VERSION', null, '1.2.3')) + ), + 'gd: libjpeg version variation' => array( + 'gd', + ' +gd + +GD Support => enabled +GD Version => bundled (2.1.0 compatible) +FreeType Support => enabled +FreeType Linkage => with freetype +FreeType Version => 2.9.1 +GIF Read Support => enabled +GIF Create Support => enabled +JPEG Support => enabled +libJPEG Version => 6b +PNG Support => enabled +libPNG Version => 1.6.35 +WBMP Support => enabled +XBM Support => enabled +WebP Support => enabled + +Directive => Local Value => Master Value +gd.jpeg_ignore_warning => 1 => 1', + array( + 'lib-gd' => '1.2.3', + 'lib-gd-freetype' => '2.9.1', + 'lib-gd-libjpeg' => '6.2', + 'lib-gd-libpng' => '1.6.35', + ), + array(), + array(array('GD_VERSION', null, '1.2.3')) + ), + 'gd: libxpm' => array( + 'gd', + ' +gd + +GD Support => enabled +GD headers Version => 2.2.5 +GD library Version => 2.2.5 +FreeType Support => enabled +FreeType Linkage => with freetype +FreeType Version => 2.6.3 +GIF Read Support => enabled +GIF Create Support => enabled +JPEG Support => enabled +libJPEG Version => 6b +PNG Support => enabled +libPNG Version => 1.6.28 +WBMP Support => enabled +XPM Support => enabled +libXpm Version => 30411 +XBM Support => enabled +WebP Support => enabled + +Directive => Local Value => Master Value +gd.jpeg_ignore_warning => 1 => 1', + array( + 'lib-gd' => '2.2.5', + 'lib-gd-freetype' => '2.6.3', + 'lib-gd-libjpeg' => '6.2', + 'lib-gd-libpng' => '1.6.28', + 'lib-gd-libxpm' => '3.4.11', + ), + array(), + array(array('GD_VERSION', null, '2.2.5')) + ), + 'iconv' => array( + 'iconv', + null, + array('lib-iconv' => '1.2.4'), + array(), + array(array('ICONV_VERSION', null, '1.2.4')) + ), + 'gmp' => array( + 'gmp', + null, + array('lib-gmp' => '6.1.0'), + array(), + array(array('GMP_VERSION', null, '6.1.0')) + ), + 'intl' => array( + 'intl', + ' +intl + +Internationalization support => enabled +ICU version => 57.1 +ICU Data version => 57.1 +ICU TZData version => 2016b +ICU Unicode version => 8.0 + +Directive => Local Value => Master Value +intl.default_locale => no value => no value +intl.error_level => 0 => 0 +intl.use_exceptions => 0 => 0', + array( + 'lib-icu' => '100', + 'lib-icu-cldr' => ResourceBundleStub::STUB_VERSION, + 'lib-icu-unicode' => '7.0.0', + 'lib-icu-zoneinfo' => '2016.2', + ), + array( + array(array('ResourceBundle', 'create'), array('root', 'ICUDATA', false), new ResourceBundleStub()), + array(array('IntlChar', 'getUnicodeVersion'), array(), array(7, 0, 0, 0)), + ), + array(array('INTL_ICU_VERSION', null, '100')), + array( + array('ResourceBundle'), + array('IntlChar'), + ) + ), + 'intl: INTL_ICU_VERSION not defined' => array( + 'intl', + ' +intl + +Internationalization support => enabled +version => 1.1.0 +ICU version => 57.1 +ICU Data version => 57.1', + array('lib-icu' => '57.1'), + ), + 'imagick: 6.x' => array( + 'imagick', + null, + array('lib-imagick-imagemagick' => array('6.2.9', array('lib-imagick'))), + array(), + array(), + array(array('Imagick', array(), new ImagickStub('ImageMagick 6.2.9 Q16 x86_64 2018-05-18 http://www.imagemagick.org'))) + ), + 'imagick: 7.x' => array( + 'imagick', + null, + array('lib-imagick-imagemagick' => array('7.0.8.34', array('lib-imagick'))), + array(), + array(), + array(array('Imagick', array(), new ImagickStub('ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org'))) + ), + 'ldap' => array( + 'ldap', + ' +ldap + +LDAP Support => enabled +RCS Version => $Id: 5f1913de8e05a346da913956f81e0c0d8991c7cb $ +Total Links => 0/unlimited +API Version => 3001 +Vendor Name => OpenLDAP +Vendor Version => 20450 +SASL Support => Enabled + +Directive => Local Value => Master Value +ldap.max_links => Unlimited => Unlimited', + array('lib-ldap-openldap' => '2.4.50') + ), + 'libxml' => array( + 'libxml', + null, + array('lib-libxml' => '2.1.5'), + array(), + array(array('LIBXML_DOTTED_VERSION', null, '2.1.5')) + ), + 'libxml: related extensions' => array( + array('libxml', 'dom', 'simplexml', 'xml', 'xmlreader', 'xmlwriter'), + null, + array('lib-libxml' => array('2.1.5', array(), array('lib-dom-libxml', 'lib-simplexml-libxml', 'lib-xml-libxml', 'lib-xmlreader-libxml', 'lib-xmlwriter-libxml'))), + array(), + array(array('LIBXML_DOTTED_VERSION', null, '2.1.5')) + ), + 'mbstring' => array( + 'mbstring', + ' +mbstring + +Multibyte Support => enabled +Multibyte string engine => libmbfl +HTTP input encoding translation => disabled +libmbfl version => 1.3.2 + +mbstring extension makes use of "streamable kanji code filter and converter", which is distributed under the GNU Lesser General Public License version 2.1. + +Multibyte (japanese) regex support => enabled +Multibyte regex (oniguruma) version => 6.1.3', + array( + 'lib-mbstring-libmbfl' => '1.3.2', + 'lib-mbstring-oniguruma' => '7.0.0', + ), + array(), + array(array('MB_ONIGURUMA_VERSION', null, '7.0.0')) + ), + 'mbstring: no MB_ONIGURUMA constant' => array( + 'mbstring', + ' +mbstring + +Multibyte Support => enabled +Multibyte string engine => libmbfl +HTTP input encoding translation => disabled +libmbfl version => 1.3.2 + +mbstring extension makes use of "streamable kanji code filter and converter", which is distributed under the GNU Lesser General Public License version 2.1. + +Multibyte (japanese) regex support => enabled +Multibyte regex (oniguruma) version => 6.1.3', + array( + 'lib-mbstring-libmbfl' => '1.3.2', + 'lib-mbstring-oniguruma' => '6.1.3', + ) + ), + 'mbstring: no MB_ONIGURUMA constant <7.40' => array( + 'mbstring', + ' +mbstring + +Multibyte Support => enabled +Multibyte string engine => libmbfl +HTTP input encoding translation => disabled +libmbfl version => 1.3.2 +oniguruma version => 6.9.4 + +mbstring extension makes use of "streamable kanji code filter and converter", which is distributed under the GNU Lesser General Public License version 2.1. + +Multibyte (japanese) regex support => enabled +Multibyte regex (oniguruma) backtrack check => On', + array( + 'lib-mbstring-libmbfl' => '1.3.2', + 'lib-mbstring-oniguruma' => '6.9.4', + ), + ), + 'memcached' => array( + 'memcached', + ' +memcached + +memcached support => enabled +Version => 3.1.5 +libmemcached version => 1.0.18 +SASL support => yes +Session support => yes +igbinary support => yes +json support => yes +msgpack support => yes', + array('lib-memcached-libmemcached' => '1.0.18') + ), + 'openssl' => array( + 'openssl', + null, + array('lib-openssl' => '1.1.1.7'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 1.1.1g 21 Apr 2020')) + ), + 'openssl: two letters suffix' => array( + 'openssl', + null, + array('lib-openssl' => '0.9.8.33'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 0.9.8zg 21 Apr 2020')) + ), + 'openssl: pre release is treated as alpha' => array( + 'openssl', + null, + array('lib-openssl' => '1.1.1.7-alpha1'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 1.1.1g-pre1 21 Apr 2020')) + ), + 'openssl: beta release' => array( + 'openssl', + null, + array('lib-openssl' => '1.1.1.7-beta2'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 1.1.1g-beta2 21 Apr 2020')) + ), + 'openssl: alpha release' => array( + 'openssl', + null, + array('lib-openssl' => '1.1.1.7-alpha4'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 1.1.1g-alpha4 21 Apr 2020')) + ), + 'openssl: rc release' => array( + 'openssl', + null, + array('lib-openssl' => '1.1.1.7-rc2'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 1.1.1g-rc2 21 Apr 2020')) + ), + 'openssl: fips' => array( + 'openssl', + null, + array('lib-openssl-fips' => array('1.1.1.7', array(), array('lib-openssl'))), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'OpenSSL 1.1.1g-fips 21 Apr 2020')) + ), + 'openssl: LibreSSL' => array( + 'openssl', + null, + array('lib-openssl' => '2.0.1.0'), + array(), + array(array('OPENSSL_VERSION_TEXT', null, 'LibreSSL 2.0.1')) + ), + 'mysqlnd' => array( + 'mysqlnd', + ' + mysqlnd + +mysqlnd => enabled +Version => mysqlnd 5.0.11-dev - 20150407 - $Id: 38fea24f2847fa7519001be390c98ae0acafe387 $ +Compression => supported +core SSL => supported +extended SSL => supported +Command buffer size => 4096 +Read buffer size => 32768 +Read timeout => 31536000 +Collecting statistics => Yes +Collecting memory statistics => Yes +Tracing => n/a +Loaded plugins => mysqlnd,debug_trace,auth_plugin_mysql_native_password,auth_plugin_mysql_clear_password,auth_plugin_sha256_password +API Extensions => pdo_mysql,mysqli', + array('lib-mysqlnd-mysqlnd' => '5.0.11-dev') + ), + 'pdo_mysql' => array( + 'pdo_mysql', + ' + pdo_mysql + +PDO Driver for MySQL => enabled +Client API version => mysqlnd 5.0.10-dev - 20150407 - $Id: 38fea24f2847fa7519001be390c98ae0acafe387 $ + +Directive => Local Value => Master Value +pdo_mysql.default_socket => /tmp/mysql.sock => /tmp/mysql.sock', + array('lib-pdo_mysql-mysqlnd' => '5.0.10-dev') + ), + 'mongodb' => array( + 'mongodb', + ' + mongodb + +MongoDB support => enabled +MongoDB extension version => 1.6.1 +MongoDB extension stability => stable +libbson bundled version => 1.15.2 +libmongoc bundled version => 1.15.2 +libmongoc SSL => enabled +libmongoc SSL library => OpenSSL +libmongoc crypto => enabled +libmongoc crypto library => libcrypto +libmongoc crypto system profile => disabled +libmongoc SASL => disabled +libmongoc ICU => enabled +libmongoc compression => enabled +libmongoc compression snappy => disabled +libmongoc compression zlib => enabled + +Directive => Local Value => Master Value +mongodb.debug => no value => no value', + array( + 'lib-mongodb-libmongoc' => '1.15.2', + 'lib-mongodb-libbson' => '1.15.2', + ) + ), + 'pcre' => array( + 'pcre', + ' +pcre + +PCRE (Perl Compatible Regular Expressions) Support => enabled +PCRE Library Version => 10.33 2019-04-16 +PCRE Unicode Version => 11.0.0 +PCRE JIT Support => enabled +PCRE JIT Target => x86 64bit (little endian + unaligned)', + array( + 'lib-pcre' => '10.33', + 'lib-pcre-unicode' => '11.0.0', + ), + array(), + array(array('PCRE_VERSION', null, '10.33 2019-04-16')) + ), + 'pcre: no unicode version included' => array( + 'pcre', + ' +pcre + +PCRE (Perl Compatible Regular Expressions) Support => enabled +PCRE Library Version => 8.38 2015-11-23 + +Directive => Local Value => Master Value +pcre.backtrack_limit => 1000000 => 1000000 +pcre.recursion_limit => 100000 => 100000 + ', + array( + 'lib-pcre' => '8.38', + ), + array(), + array(array('PCRE_VERSION', null, '8.38 2015-11-23')) + ), + 'pgsql' => array( + 'pgsql', + ' +pgsql + +PostgreSQL Support => enabled +PostgreSQL(libpq) Version => 12.2 +PostgreSQL(libpq) => PostgreSQL 12.3 on x86_64-apple-darwin18.7.0, compiled by Apple clang version 11.0.0 (clang-1100.0.33.17), 64-bit +Multibyte character support => enabled +SSL support => enabled +Active Persistent Links => 0 +Active Links => 0 + +Directive => Local Value => Master Value +pgsql.allow_persistent => On => On +pgsql.max_persistent => Unlimited => Unlimited +pgsql.max_links => Unlimited => Unlimited +pgsql.auto_reset_persistent => Off => Off +pgsql.ignore_notice => Off => Off +pgsql.log_notice => Off => Off', + array('lib-pgsql-libpq' => '12.2') + ), + 'pdo_pgsql' => array( + 'pdo_pgsql', + ' + pdo_pgsql + +PDO Driver for PostgreSQL => enabled +PostgreSQL(libpq) Version => 12.1 +Module version => 7.1.33 +Revision => $Id: 9c5f356c77143981d2e905e276e439501fe0f419 $', + array('lib-pdo_pgsql-libpq' => '12.1') + ), + 'libsodium' => array( + 'libsodium', + null, + array('lib-libsodium' => '1.0.17'), + array(), + array(array('SODIUM_LIBRARY_VERSION', null, '1.0.17')) + ), + 'libsodium: different extension name' => array( + 'sodium', + null, + array('lib-libsodium' => '1.0.15'), + array(), + array(array('SODIUM_LIBRARY_VERSION', null, '1.0.15')) + ), + 'pdo_sqlite' => array( + 'pdo_sqlite', + ' +pdo_sqlite + +PDO Driver for SQLite 3.x => enabled +SQLite Library => 3.32.3 + ', + array('lib-pdo_sqlite-sqlite' => '3.32.3') + ), + 'sqlite3' => array( + 'sqlite3', + ' +sqlite3 + +SQLite3 support => enabled +SQLite3 module version => 7.1.33 +SQLite Library => 3.31.0 + +Directive => Local Value => Master Value +sqlite3.extension_dir => no value => no value +sqlite3.defensive => 1 => 1', + array('lib-sqlite3-sqlite' => '3.31.0') + ), + 'ssh2' => array( + 'ssh2', + ' +ssh2 + +SSH2 support => enabled +extension version => 1.2 +libssh2 version => 1.8.0 +banner => SSH-2.0-libssh2_1.8.0', + array('lib-ssh2-libssh2' => '1.8.0') + ), + 'yaml' => array( + 'yaml', + ' + yaml + +LibYAML Support => enabled +Module Version => 2.0.2 +LibYAML Version => 0.2.2 + +Directive => Local Value => Master Value +yaml.decode_binary => 0 => 0 +yaml.decode_timestamp => 0 => 0 +yaml.decode_php => 0 => 0 +yaml.output_canonical => 0 => 0 +yaml.output_indent => 2 => 2 +yaml.output_width => 80 => 80', + array('lib-yaml-libyaml' => '0.2.2') + ), + 'xsl' => array( + 'xsl', + ' +xsl + +XSL => enabled +libxslt Version => 1.1.33 +libxslt compiled against libxml Version => 2.9.8 +EXSLT => enabled +libexslt Version => 1.1.29', + array( + 'lib-libxslt' => array('1.1.29', array('lib-xsl')), + 'lib-libxslt-libxml' => '2.9.8', + ), + array(), + array(array('LIBXSLT_DOTTED_VERSION', null, '1.1.29')) + ), + 'zip' => array( + 'zip', + null, + array('lib-zip-libzip' => array('1.5.0', array('lib-zip'))), + array(), + array(array('LIBZIP_VERSION', 'ZipArchive', '1.5.0')), + ), + 'zlib' => array( + 'zlib', + null, + array('lib-zlib' => '1.2.10'), + array(), + array(array('ZLIB_VERSION', null, '1.2.10')), + ), + 'zlib: no constant present' => array( + 'zlib', + ' +zlib + +ZLib Support => enabled +Stream Wrapper => compress.zlib:// +Stream Filter => zlib.inflate, zlib.deflate +Compiled Version => 1.2.8 +Linked Version => 1.2.11', + array('lib-zlib' => '1.2.11'), + ), ); - $parser = new VersionParser; + } - $this->assertSame($parser->normalize($version), $package->getVersion()); + /** + * @dataProvider getLibraryTestCases + * + * @param string|string[] $extensions + * @param string|null $info + * @param array $expectations + * @param array $functions + * @param array $constants + * @param array $classes + */ + public function testLibraryInformation( + $extensions, + $info, + array $expectations, + array $functions = array(), + array $constants = array(), + array $classDefinitions = array() + ) + { + $extensions = (array)$extensions; + + $extensionVersion = '100.200.300'; + + $runtime = $this->getMockBuilder('Composer\Platform\Runtime')->getMock(); + $runtime + ->method('getExtensions') + ->willReturn($extensions); + + + $runtime + ->method('getExtensionVersion') + ->willReturnMap( + array_map(function($extension) use ($extensionVersion) { + return array($extension, $extensionVersion); + }, $extensions) + ); + + $runtime + ->method('getExtensionInfo') + ->willReturnMap( + array_map(function ($extension) use ($info) { + return array($extension, $info); + }, $extensions) + ); + + $runtime + ->method('invoke') + ->willReturnMap($functions); + + $constants[] = array('PHP_VERSION', null, '7.1.0'); + $runtime + ->method('hasConstant') + ->willReturnMap( + array_map( + function ($constantDefintion) { return array($constantDefintion[0], $constantDefintion[1], true); }, + $constants + ) + ); + $runtime + ->method('getConstant') + ->willReturnMap($constants); + + $runtime + ->method('hasClass') + ->willReturnMap( + array_map( + function ($classDefinition) { return array($classDefinition[0], true); }, + $classDefinitions + ) + ); + $runtime + ->method('construct') + ->willReturnMap($classDefinitions); + + $platformRepository = new PlatformRepository(array(), array(), $runtime); + + $expectations = array_map(function ($expectation) { + return array_replace(array(null, array(), array()), (array) $expectation); + }, $expectations); + + $libraries = array_map( + function ($package) { + return $package['name']; + }, array_filter( + $platformRepository->search('lib', PlatformRepository::SEARCH_NAME), + function ($package) { + return strpos($package['name'], 'lib-') === 0; + } + ) + ); + $expectedLibraries = array_merge(array_keys(array_filter($expectations,function($expectation) { return $expectation[0] !== false; }))); + self::assertCount(count(array_filter($expectedLibraries)), $libraries, sprintf('Expected: %s, got %s', var_export($expectedLibraries, true), var_export($libraries, true))); + + $expectations = array_merge($expectations, array_combine(array_map(function($extension) { + return 'ext-'.$extension; + }, $extensions), array_fill(0, count($extensions), array($extensionVersion, array(), array())))); + + foreach ($expectations as $packageName => $expectation) { + list($expectedVersion, $expectedReplaces, $expectedProvides) = $expectation; + + $package = $platformRepository->findPackage($packageName, '*'); + if ($expectedVersion === false) { + self::assertNull($package, sprintf('Expected to not find package "%s"', $packageName)); + } else { + self::assertNotNull($package, sprintf('Expected to find package "%s"', $packageName)); + self::assertSame($expectedVersion, $package->getPrettyVersion(), sprintf('Expected version %s for %s', $expectedVersion, $packageName)); + $this->assertPackageLinks('replaces', $expectedReplaces, $package, $package->getReplaces()); + $this->assertPackageLinks('provides', $expectedProvides, $package, $package->getProvides()); + } + } + } + + private function assertPackageLinks($context, array $expectedLinks, Package $sourcePackage, array $links) + { + self::assertCount(count($expectedLinks), $links, sprintf('%s: expected package count to match', $context)); + + foreach ($links as $link) { + self::assertSame($sourcePackage->getName(), $link->getSource()); + self::assertContains($link->getTarget(), $expectedLinks, sprintf('%s: package %s not in %s', $context, $link->getTarget(), var_export($expectedLinks, true))); + self::assertTrue($link->getConstraint()->matches($this->getVersionConstraint('=', $sourcePackage->getVersion()))); + } + } +} + +class ResourceBundleStub { + const STUB_VERSION = '32.0.1'; + + public static function create($locale, $bundleName, $fallback) { + Assert::assertSame(3, func_num_args()); + Assert::assertSame('root', $locale); + Assert::assertSame('ICUDATA', $bundleName); + Assert::assertFalse($fallback); + + return new self(); + } + + public function get($field) { + Assert::assertSame(1, func_num_args()); + Assert::assertSame('Version', $field); + + return self::STUB_VERSION; + } +} + +class ImagickStub { + private $versionString; + + public function __construct($versionString) { + $this->versionString = $versionString; + } + + public function getVersion() { + Assert::assertSame(0, func_num_args()); + + return array('versionString' => $this->versionString); } } diff --git a/tests/Composer/Test/Util/VersionTest.php b/tests/Composer/Test/Util/VersionTest.php new file mode 100644 index 000000000..907e1e472 --- /dev/null +++ b/tests/Composer/Test/Util/VersionTest.php @@ -0,0 +1,131 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Util; + +use Composer\Util\Version; +use Composer\Test\TestCase; + +/** + * @author Lars Strojny + */ +class VersionTest extends TestCase +{ + /** + * Create normalized test data set + * + * 1) Clone OpenSSL repository + * 2) git log --pretty=%h --all -- crypto/opensslv.h include/openssl/opensslv.h | while read hash ; do (git show $hash:crypto/opensslv.h; git show $hash:include/openssl/opensslv.h) | grep "define OPENSSL_VERSION_TEXT" ; done > versions.txt + * 3) cat versions.txt | awk -F "OpenSSL " '{print $2}' | awk -F " " '{print $1}' | sed -e "s:\([0-9]*\.[0-9]*\.[0-9]*\):1.2.3:g" -e "s:1\.2\.3[a-z]\(-.*\)\{0,1\}$:1.2.3a\1:g" -e "s:1\.2\.3[a-z]\{2\}\(-.*\)\{0,1\}$:1.2.3zh\1:g" -e "s:beta[0-9]:beta3:g" -e "s:pre[0-9]*:pre2:g" | sort | uniq + */ + public static function getOpenSslVersions() + { + return array( + // Generated + array('1.2.3', '1.2.3.0'), + array('1.2.3-beta3', '1.2.3.0-beta3'), + array('1.2.3-beta3-dev', '1.2.3.0-beta3-dev'), + array('1.2.3-beta3-fips', '1.2.3.0-beta3', true), + array('1.2.3-beta3-fips-dev', '1.2.3.0-beta3-dev', true), + array('1.2.3-dev', '1.2.3.0-dev'), + array('1.2.3-fips', '1.2.3.0', true), + array('1.2.3-fips-beta3', '1.2.3.0-beta3', true), + array('1.2.3-fips-beta3-dev', '1.2.3.0-beta3-dev', true), + array('1.2.3-fips-dev', '1.2.3.0-dev', true), + array('1.2.3-pre2', '1.2.3.0-alpha2'), + array('1.2.3-pre2-dev', '1.2.3.0-alpha2-dev'), + array('1.2.3-pre2-fips', '1.2.3.0-alpha2', true), + array('1.2.3-pre2-fips-dev', '1.2.3.0-alpha2-dev', true), + array('1.2.3a', '1.2.3.1'), + array('1.2.3a-beta3','1.2.3.1-beta3'), + array('1.2.3a-beta3-dev', '1.2.3.1-beta3-dev'), + array('1.2.3a-dev', '1.2.3.1-dev'), + array('1.2.3a-dev-fips', '1.2.3.1-dev', true), + array('1.2.3a-fips', '1.2.3.1', true), + array('1.2.3a-fips-beta3', '1.2.3.1-beta3', true), + array('1.2.3a-fips-dev', '1.2.3.1-dev', true), + array('1.2.3beta3', '1.2.3.0-beta3'), + array('1.2.3beta3-dev', '1.2.3.0-beta3-dev'), + array('1.2.3zh', '1.2.3.34'), + array('1.2.3zh-dev', '1.2.3.34-dev'), + array('1.2.3zh-fips', '1.2.3.34',true), + array('1.2.3zh-fips-dev', '1.2.3.34-dev', true), + // Additional cases + array('1.2.3zh-fips-rc3', '1.2.3.34-rc3', true, '1.2.3.34-RC3'), + array('1.2.3zh-alpha10-fips', '1.2.3.34-alpha10', true), + // Check that alphabetical patch levels overflow correctly + array('1.2.3', '1.2.3.0'), + array('1.2.3a', '1.2.3.1'), + array('1.2.3z', '1.2.3.26'), + array('1.2.3za', '1.2.3.27'), + array('1.2.3zy', '1.2.3.51'), + array('1.2.3zz', '1.2.3.52'), + ); + } + + /** + * @dataProvider getOpenSslVersions + * @param string $input + * @param string $parsedVersion + * @param bool $fipsExpected + * @param string|null $normalizedVersion + */ + public function testParseOpensslVersions($input, $parsedVersion, $fipsExpected = false, $normalizedVersion = null) + { + self::assertSame($parsedVersion, Version::parseOpenssl($input, $isFips)); + self::assertSame($fipsExpected, $isFips); + + $normalizedVersion = $normalizedVersion ? $normalizedVersion : $parsedVersion; + self::assertSame($normalizedVersion, $this->getVersionParser()->normalize($parsedVersion)); + } + + public function getLibJpegVersions() + { + return array( + array('9', '9.0'), + array('9a', '9.1'), + array('9b', '9.2'), + // Never seen in the wild, just for overflow correctness + array('9za', '9.27'), + ); + } + + /** + * @dataProvider getLibJpegVersions + * @param string $input + * @param string $parsedVersion + */ + public function testParseLibjpegVersion($input, $parsedVersion) + { + self::assertSame($parsedVersion, Version::parseLibjpeg($input)); + } + + public function getZoneinfoVersions() + { + return array( + array('2019c', '2019.3'), + array('2020a', '2020.1'), + // Never happened so far but fixate overflow behavior + array('2020za', '2020.27'), + ); + } + + /** + * @dataProvider getZoneinfoVersions + * @param string $input + * @param string $parsedVersion + */ + public function testParseZoneinfoVersion($input, $parsedVersion) + { + self::assertSame($parsedVersion, Version::parseZoneinfoVersion($input)); + } +}