Merge pull request #3450 from nicolas-grekas/prefer-lowest-stable
add --prefer-lowest and --prefer-stable to update commandpull/2264/merge
commit
901fd838f3
|
@ -140,7 +140,9 @@ php composer.phar update vendor/*
|
||||||
* **--lock:** Only updates the lock file hash to suppress warning about the
|
* **--lock:** Only updates the lock file hash to suppress warning about the
|
||||||
lock file being out of date.
|
lock file being out of date.
|
||||||
* **--with-dependencies** Add also all dependencies of whitelisted packages to the whitelist.
|
* **--with-dependencies** Add also all dependencies of whitelisted packages to the whitelist.
|
||||||
So all packages with their dependencies are updated recursively.
|
* **--prefer-stable:** Prefer stable versions of dependencies.
|
||||||
|
* **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal
|
||||||
|
versions of requirements, generally used with `--prefer-stable`.
|
||||||
|
|
||||||
## require
|
## require
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,8 @@ class UpdateCommand extends Command
|
||||||
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
|
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
|
||||||
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
|
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
|
||||||
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
|
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
|
||||||
|
new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'),
|
||||||
|
new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'),
|
||||||
))
|
))
|
||||||
->setHelp(<<<EOT
|
->setHelp(<<<EOT
|
||||||
The <info>update</info> command reads the composer.json file from the
|
The <info>update</info> command reads the composer.json file from the
|
||||||
|
@ -121,6 +123,8 @@ EOT
|
||||||
->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages'))
|
->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages'))
|
||||||
->setWhitelistDependencies($input->getOption('with-dependencies'))
|
->setWhitelistDependencies($input->getOption('with-dependencies'))
|
||||||
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
|
||||||
|
->setPreferStable($input->getOption('prefer-stable'))
|
||||||
|
->setPreferLowest($input->getOption('prefer-lowest'))
|
||||||
;
|
;
|
||||||
|
|
||||||
if ($input->getOption('no-plugins')) {
|
if ($input->getOption('no-plugins')) {
|
||||||
|
|
|
@ -24,10 +24,12 @@ use Composer\Package\LinkConstraint\VersionConstraint;
|
||||||
class DefaultPolicy implements PolicyInterface
|
class DefaultPolicy implements PolicyInterface
|
||||||
{
|
{
|
||||||
private $preferStable;
|
private $preferStable;
|
||||||
|
private $preferLowest;
|
||||||
|
|
||||||
public function __construct($preferStable = false)
|
public function __construct($preferStable = false, $preferLowest = false)
|
||||||
{
|
{
|
||||||
$this->preferStable = $preferStable;
|
$this->preferStable = $preferStable;
|
||||||
|
$this->preferLowest = $preferLowest;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function versionCompare(PackageInterface $a, PackageInterface $b, $operator)
|
public function versionCompare(PackageInterface $a, PackageInterface $b, $operator)
|
||||||
|
@ -195,6 +197,7 @@ class DefaultPolicy implements PolicyInterface
|
||||||
|
|
||||||
protected function pruneToBestVersion(Pool $pool, $literals)
|
protected function pruneToBestVersion(Pool $pool, $literals)
|
||||||
{
|
{
|
||||||
|
$operator = $this->preferLowest ? '<' : '>';
|
||||||
$bestLiterals = array($literals[0]);
|
$bestLiterals = array($literals[0]);
|
||||||
$bestPackage = $pool->literalToPackage($literals[0]);
|
$bestPackage = $pool->literalToPackage($literals[0]);
|
||||||
foreach ($literals as $i => $literal) {
|
foreach ($literals as $i => $literal) {
|
||||||
|
@ -204,7 +207,7 @@ class DefaultPolicy implements PolicyInterface
|
||||||
|
|
||||||
$package = $pool->literalToPackage($literal);
|
$package = $pool->literalToPackage($literal);
|
||||||
|
|
||||||
if ($this->versionCompare($package, $bestPackage, '>')) {
|
if ($this->versionCompare($package, $bestPackage, $operator)) {
|
||||||
$bestPackage = $package;
|
$bestPackage = $package;
|
||||||
$bestLiterals = array($literal);
|
$bestLiterals = array($literal);
|
||||||
} elseif ($this->versionCompare($package, $bestPackage, '==')) {
|
} elseif ($this->versionCompare($package, $bestPackage, '==')) {
|
||||||
|
|
|
@ -107,6 +107,8 @@ class Installer
|
||||||
protected $update = false;
|
protected $update = false;
|
||||||
protected $runScripts = true;
|
protected $runScripts = true;
|
||||||
protected $ignorePlatformReqs = false;
|
protected $ignorePlatformReqs = false;
|
||||||
|
protected $preferStable = false;
|
||||||
|
protected $preferLowest = false;
|
||||||
/**
|
/**
|
||||||
* Array of package names/globs flagged for update
|
* Array of package names/globs flagged for update
|
||||||
*
|
*
|
||||||
|
@ -307,7 +309,8 @@ class Installer
|
||||||
$aliases,
|
$aliases,
|
||||||
$this->package->getMinimumStability(),
|
$this->package->getMinimumStability(),
|
||||||
$this->package->getStabilityFlags(),
|
$this->package->getStabilityFlags(),
|
||||||
$this->package->getPreferStable()
|
$this->preferStable || $this->package->getPreferStable(),
|
||||||
|
$this->preferLowest
|
||||||
);
|
);
|
||||||
if ($updatedLock) {
|
if ($updatedLock) {
|
||||||
$this->io->write('<info>Writing lock file</info>');
|
$this->io->write('<info>Writing lock file</info>');
|
||||||
|
@ -694,16 +697,21 @@ class Installer
|
||||||
private function createPolicy()
|
private function createPolicy()
|
||||||
{
|
{
|
||||||
$preferStable = null;
|
$preferStable = null;
|
||||||
|
$preferLowest = null;
|
||||||
if (!$this->update && $this->locker->isLocked()) {
|
if (!$this->update && $this->locker->isLocked()) {
|
||||||
$preferStable = $this->locker->getPreferStable();
|
$preferStable = $this->locker->getPreferStable();
|
||||||
|
$preferLowest = $this->locker->getPreferLowest();
|
||||||
}
|
}
|
||||||
// old lock file without prefer stable will return null
|
// old lock file without prefer stable/lowest will return null
|
||||||
// so in this case we use the composer.json info
|
// so in this case we use the composer.json info
|
||||||
if (null === $preferStable) {
|
if (null === $preferStable) {
|
||||||
$preferStable = $this->package->getPreferStable();
|
$preferStable = $this->preferStable || $this->package->getPreferStable();
|
||||||
|
}
|
||||||
|
if (null === $preferLowest) {
|
||||||
|
$preferLowest = $this->preferLowest;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DefaultPolicy($preferStable);
|
return new DefaultPolicy($preferStable, $preferLowest);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createRequest(Pool $pool, RootPackageInterface $rootPackage, PlatformRepository $platformRepo)
|
private function createRequest(Pool $pool, RootPackageInterface $rootPackage, PlatformRepository $platformRepo)
|
||||||
|
@ -1244,6 +1252,32 @@ class Installer
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should packages be prefered in a stable version when updating?
|
||||||
|
*
|
||||||
|
* @param boolean $preferStable
|
||||||
|
* @return Installer
|
||||||
|
*/
|
||||||
|
public function setPreferStable($preferStable = true)
|
||||||
|
{
|
||||||
|
$this->preferStable = (boolean) $preferStable;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should packages be prefered in a lowest version when updating?
|
||||||
|
*
|
||||||
|
* @param boolean $preferLowest
|
||||||
|
* @return Installer
|
||||||
|
*/
|
||||||
|
public function setPreferLowest($preferLowest = true)
|
||||||
|
{
|
||||||
|
$this->preferLowest = (boolean) $preferLowest;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disables plugins.
|
* Disables plugins.
|
||||||
*
|
*
|
||||||
|
|
|
@ -182,6 +182,15 @@ class Locker
|
||||||
return isset($lockData['prefer-stable']) ? $lockData['prefer-stable'] : null;
|
return isset($lockData['prefer-stable']) ? $lockData['prefer-stable'] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPreferLowest()
|
||||||
|
{
|
||||||
|
$lockData = $this->getLockData();
|
||||||
|
|
||||||
|
// return null if not set to allow caller logic to choose the
|
||||||
|
// right behavior since old lock files have no prefer-lowest
|
||||||
|
return isset($lockData['prefer-lowest']) ? $lockData['prefer-lowest'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
public function getAliases()
|
public function getAliases()
|
||||||
{
|
{
|
||||||
$lockData = $this->getLockData();
|
$lockData = $this->getLockData();
|
||||||
|
@ -213,10 +222,11 @@ class Locker
|
||||||
* @param string $minimumStability
|
* @param string $minimumStability
|
||||||
* @param array $stabilityFlags
|
* @param array $stabilityFlags
|
||||||
* @param bool $preferStable
|
* @param bool $preferStable
|
||||||
|
* @param bool $preferLowest
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable)
|
public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable, $preferLowest)
|
||||||
{
|
{
|
||||||
$lock = array(
|
$lock = array(
|
||||||
'_readme' => array('This file locks the dependencies of your project to a known state',
|
'_readme' => array('This file locks the dependencies of your project to a known state',
|
||||||
|
@ -229,6 +239,7 @@ class Locker
|
||||||
'minimum-stability' => $minimumStability,
|
'minimum-stability' => $minimumStability,
|
||||||
'stability-flags' => $stabilityFlags,
|
'stability-flags' => $stabilityFlags,
|
||||||
'prefer-stable' => $preferStable,
|
'prefer-stable' => $preferStable,
|
||||||
|
'prefer-lowest' => $preferLowest,
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($aliases as $package => $versions) {
|
foreach ($aliases as $package => $versions) {
|
||||||
|
|
|
@ -247,4 +247,20 @@ class DefaultPolicyTest extends TestCase
|
||||||
|
|
||||||
return $map;
|
return $map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSelectLowest()
|
||||||
|
{
|
||||||
|
$policy = new DefaultPolicy(false, true);
|
||||||
|
|
||||||
|
$this->repo->addPackage($packageA1 = $this->getPackage('A', '1.0'));
|
||||||
|
$this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0'));
|
||||||
|
$this->pool->addRepository($this->repo);
|
||||||
|
|
||||||
|
$literals = array($packageA1->getId(), $packageA2->getId());
|
||||||
|
$expected = array($packageA1->getId());
|
||||||
|
|
||||||
|
$selected = $policy->selectPreferedPackages($this->pool, array(), $literals);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $selected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ install --prefer-dist
|
||||||
"a/a": 20
|
"a/a": 20
|
||||||
},
|
},
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": []
|
"platform-dev": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,8 @@ Requirements from the composer file are not installed if the lock file is presen
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"stability-flags": [],
|
"stability-flags": [],
|
||||||
"prefer-stable": false
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false
|
||||||
}
|
}
|
||||||
--RUN--
|
--RUN--
|
||||||
install
|
install
|
||||||
|
|
|
@ -33,7 +33,8 @@ Installing an old alias that doesn't exist anymore from a lock is possible
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"stability-flags": [],
|
"stability-flags": [],
|
||||||
"prefer-stable": false
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false
|
||||||
}
|
}
|
||||||
--RUN--
|
--RUN--
|
||||||
install
|
install
|
||||||
|
|
|
@ -36,6 +36,7 @@ Partial update from lock file should apply lock file and downgrade unstable pack
|
||||||
"b/unstable": 15
|
"b/unstable": 15
|
||||||
},
|
},
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": []
|
"platform-dev": []
|
||||||
}
|
}
|
||||||
|
@ -59,6 +60,7 @@ update c/uptodate
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"stability-flags": [],
|
"stability-flags": [],
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": []
|
"platform-dev": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ Partial update from lock file should update everything to the state of the lock,
|
||||||
"b/unstable": 15
|
"b/unstable": 15
|
||||||
},
|
},
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": []
|
"platform-dev": []
|
||||||
}
|
}
|
||||||
|
@ -59,6 +60,7 @@ update b/unstable
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"stability-flags": [],
|
"stability-flags": [],
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": []
|
"platform-dev": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ update b/unstable
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"stability-flags": [],
|
"stability-flags": [],
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": []
|
"platform-dev": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,8 @@ Update aliased package does not mess up the lock file
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"stability-flags": [],
|
"stability-flags": [],
|
||||||
"prefer-stable": false
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false
|
||||||
}
|
}
|
||||||
--INSTALLED--
|
--INSTALLED--
|
||||||
[
|
[
|
||||||
|
@ -66,6 +67,7 @@ update
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"stability-flags": [],
|
"stability-flags": [],
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": []
|
"platform-dev": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
--TEST--
|
||||||
|
Updates packages to their lowest stable version
|
||||||
|
--COMPOSER--
|
||||||
|
{
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "package",
|
||||||
|
"package": [
|
||||||
|
{ "name": "a/a", "version": "1.0.0-rc1" },
|
||||||
|
{ "name": "a/a", "version": "1.0.1" },
|
||||||
|
{ "name": "a/a", "version": "1.1.0" },
|
||||||
|
|
||||||
|
{ "name": "a/b", "version": "1.0.0" },
|
||||||
|
{ "name": "a/b", "version": "1.0.1" },
|
||||||
|
{ "name": "a/b", "version": "2.0.0" },
|
||||||
|
|
||||||
|
{ "name": "a/c", "version": "1.0.0" },
|
||||||
|
{ "name": "a/c", "version": "2.0.0" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"a/a": "~1.0@dev",
|
||||||
|
"a/c": "2.*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"a/b": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--INSTALLED--
|
||||||
|
[
|
||||||
|
{ "name": "a/a", "version": "1.0.0-rc1" },
|
||||||
|
{ "name": "a/c", "version": "2.0.0" },
|
||||||
|
{ "name": "a/b", "version": "1.0.1" }
|
||||||
|
]
|
||||||
|
--RUN--
|
||||||
|
update --prefer-lowest --prefer-stable
|
||||||
|
--EXPECT--
|
||||||
|
Updating a/a (1.0.0-rc1) to a/a (1.0.1)
|
||||||
|
Updating a/b (1.0.1) to a/b (1.0.0)
|
|
@ -32,7 +32,8 @@ Limited update takes rules from lock if available, and not from the installed re
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"stability-flags": [],
|
"stability-flags": [],
|
||||||
"prefer-stable": false
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false
|
||||||
}
|
}
|
||||||
--INSTALLED--
|
--INSTALLED--
|
||||||
[
|
[
|
||||||
|
|
|
@ -20,7 +20,8 @@ Installing locked dev packages should remove old dependencies
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"stability-flags": [],
|
"stability-flags": [],
|
||||||
"prefer-stable": false
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false
|
||||||
}
|
}
|
||||||
--INSTALLED--
|
--INSTALLED--
|
||||||
[
|
[
|
||||||
|
|
|
@ -32,7 +32,8 @@ Updating a dev package for new reference updates the url and reference
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"stability-flags": {"a/a":20},
|
"stability-flags": {"a/a":20},
|
||||||
"prefer-stable": false
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false
|
||||||
}
|
}
|
||||||
--INSTALLED--
|
--INSTALLED--
|
||||||
[
|
[
|
||||||
|
@ -59,6 +60,7 @@ update
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"stability-flags": {"a/a":20},
|
"stability-flags": {"a/a":20},
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": []
|
"platform-dev": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,6 +217,8 @@ class InstallerTest extends TestCase
|
||||||
->setDryRun($input->getOption('dry-run'))
|
->setDryRun($input->getOption('dry-run'))
|
||||||
->setUpdateWhitelist($input->getArgument('packages'))
|
->setUpdateWhitelist($input->getArgument('packages'))
|
||||||
->setWhitelistDependencies($input->getOption('with-dependencies'))
|
->setWhitelistDependencies($input->getOption('with-dependencies'))
|
||||||
|
->setPreferStable($input->getOption('prefer-stable'))
|
||||||
|
->setPreferLowest($input->getOption('prefer-lowest'))
|
||||||
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'));
|
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'));
|
||||||
|
|
||||||
return $installer->run();
|
return $installer->run();
|
||||||
|
|
|
@ -135,9 +135,10 @@ class LockerTest extends \PHPUnit_Framework_TestCase
|
||||||
'platform' => array(),
|
'platform' => array(),
|
||||||
'platform-dev' => array(),
|
'platform-dev' => array(),
|
||||||
'prefer-stable' => false,
|
'prefer-stable' => false,
|
||||||
|
'prefer-lowest' => false,
|
||||||
));
|
));
|
||||||
|
|
||||||
$locker->setLockData(array($package1, $package2), array(), array(), array(), array(), 'dev', array(), false);
|
$locker->setLockData(array($package1, $package2), array(), array(), array(), array(), 'dev', array(), false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLockBadPackages()
|
public function testLockBadPackages()
|
||||||
|
@ -156,7 +157,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
|
||||||
|
|
||||||
$this->setExpectedException('LogicException');
|
$this->setExpectedException('LogicException');
|
||||||
|
|
||||||
$locker->setLockData(array($package1), array(), array(), array(), array(), 'dev', array(), false);
|
$locker->setLockData(array($package1), array(), array(), array(), array(), 'dev', array(), false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIsFresh()
|
public function testIsFresh()
|
||||||
|
|
Loading…
Reference in New Issue