123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578 |
- <?php
- /*
- * This file is part of composer/semver.
- *
- * (c) Composer <https://github.com/composer>
- *
- * For the full copyright and license information, please view
- * the LICENSE file that was distributed with this source code.
- */
- namespace Composer\Semver\Constraint;
- use Composer\Semver\VersionParser;
- use PHPUnit\Framework\TestCase;
- use Composer\Semver\Intervals;
- class MultiConstraintTest extends TestCase
- {
- /**
- * @var Constraint
- */
- protected $versionRequireStart;
- /**
- * @var Constraint
- */
- protected $versionRequireEnd;
- protected function setUp()
- {
- $this->versionRequireStart = new Constraint('>', '1.0');
- $this->versionRequireEnd = new Constraint('<', '1.2');
- }
- public function testIsConjunctive()
- {
- $multiConstraint = new MultiConstraint(array($this->versionRequireStart, $this->versionRequireEnd), true);
- $this->assertTrue($multiConstraint->isConjunctive());
- $this->assertFalse($multiConstraint->isDisjunctive());
- }
- public function testIsDisjunctive()
- {
- $multiConstraint = new MultiConstraint(array($this->versionRequireStart, $this->versionRequireEnd), false);
- $this->assertFalse($multiConstraint->isConjunctive());
- $this->assertTrue($multiConstraint->isDisjunctive());
- }
- public function testMultiVersionMatchSucceeds()
- {
- $versionProvide = new Constraint('==', '1.1');
- $multiRequire = new MultiConstraint(array($this->versionRequireStart, $this->versionRequireEnd));
- $this->assertTrue($multiRequire->matches($versionProvide));
- $this->assertTrue($versionProvide->matches($multiRequire));
- $this->assertTrue($this->matchCompiled($multiRequire, '==', '1.1'));
- $this->assertTrue(Intervals::haveIntersections($multiRequire, $versionProvide));
- $this->assertTrue(Intervals::compactConstraint($multiRequire)->matches(Intervals::compactConstraint($versionProvide)));
- $this->assertTrue(Intervals::compactConstraint($versionProvide)->matches(Intervals::compactConstraint($multiRequire)));
- }
- public function testMultiVersionProvidedMatchSucceeds()
- {
- $versionProvideStart = new Constraint('>=', '1.1');
- $versionProvideEnd = new Constraint('<', '2.0');
- $multiRequire = new MultiConstraint(array($this->versionRequireStart, $this->versionRequireEnd));
- $multiProvide = new MultiConstraint(array($versionProvideStart, $versionProvideEnd));
- $this->assertTrue($multiRequire->matches($multiProvide));
- $this->assertTrue($multiProvide->matches($multiRequire));
- $this->assertTrue(Intervals::haveIntersections($multiRequire, $multiProvide));
- $this->assertTrue(Intervals::compactConstraint($multiRequire)->matches(Intervals::compactConstraint($multiProvide)));
- $this->assertTrue(Intervals::compactConstraint($multiProvide)->matches(Intervals::compactConstraint($multiRequire)));
- }
- public function testMultiVersionMatchSucceedsInsideForeachLoop()
- {
- $versionProvideStart = new Constraint('>', '1.0');
- $versionProvideEnd = new Constraint('<', '1.2');
- $multiRequire = new MultiConstraint(array($this->versionRequireStart, $this->versionRequireEnd), false);
- $multiProvide = new MultiConstraint(array($versionProvideStart, $versionProvideEnd), false);
- $this->assertTrue($multiRequire->matches($multiProvide));
- $this->assertTrue($multiProvide->matches($multiRequire));
- $this->assertTrue(Intervals::haveIntersections($multiRequire, $multiProvide));
- $this->assertTrue(Intervals::compactConstraint($multiRequire)->matches(Intervals::compactConstraint($multiProvide)));
- $this->assertTrue(Intervals::compactConstraint($multiProvide)->matches(Intervals::compactConstraint($multiRequire)));
- }
- public function testConjunctiveMatchesDisjunctiveFalse()
- {
- $versionProvideStart = new Constraint('<', '1.0');
- $versionProvideEnd = new Constraint('>', '2.0');
- $multiRequire = new MultiConstraint(array($this->versionRequireStart, $this->versionRequireEnd), true);
- $multiProvide = new MultiConstraint(array($versionProvideStart, $versionProvideEnd), false);
- $this->assertFalse($multiRequire->matches($multiProvide));
- $this->assertFalse($multiProvide->matches($multiRequire));
- $this->assertFalse(Intervals::haveIntersections($multiRequire, $multiProvide));
- $this->assertFalse(Intervals::compactConstraint($multiRequire)->matches(Intervals::compactConstraint($multiProvide)));
- $this->assertFalse(Intervals::compactConstraint($multiProvide)->matches(Intervals::compactConstraint($multiRequire)));
- }
- public function testMultiVersionMatchFails()
- {
- $versionProvide = new Constraint('==', '1.2');
- $multiRequire = new MultiConstraint(array($this->versionRequireStart, $this->versionRequireEnd));
- $this->assertFalse($multiRequire->matches($versionProvide));
- $this->assertFalse($versionProvide->matches($multiRequire));
- $this->assertFalse($this->matchCompiled($multiRequire, '==', '1.2'));
- $this->assertFalse(Intervals::haveIntersections($multiRequire, $versionProvide));
- $this->assertFalse(Intervals::compactConstraint($multiRequire)->matches(Intervals::compactConstraint($versionProvide)));
- $this->assertFalse(Intervals::compactConstraint($versionProvide)->matches(Intervals::compactConstraint($multiRequire)));
- }
- public function testGetPrettyString()
- {
- $multiConstraint = new MultiConstraint(array($this->versionRequireStart, $this->versionRequireEnd));
- $expectedString = 'pretty-string';
- $multiConstraint->setPrettyString($expectedString);
- $result = $multiConstraint->getPrettyString();
- $this->assertSame($expectedString, $result);
- $expectedString = '[> 1.0 < 1.2]';
- $multiConstraint->setPrettyString(null);
- $result = $multiConstraint->getPrettyString();
- $this->assertSame($expectedString, $result);
- }
- /**
- * @dataProvider bounds
- *
- * @param array<ConstraintInterface> $constraints
- * @param bool $conjunctive
- * @param Bound $expectedLower
- * @param Bound $expectedUpper
- */
- public function testBounds(array $constraints, $conjunctive, Bound $expectedLower, Bound $expectedUpper)
- {
- $constraint = new MultiConstraint($constraints, $conjunctive);
- $this->assertEquals($expectedLower, $constraint->getLowerBound(), 'Expected lower bound does not match');
- $this->assertEquals($expectedUpper, $constraint->getUpperBound(), 'Expected upper bound does not match');
- }
- /**
- * @return array<mixed>
- */
- public function bounds()
- {
- return array(
- 'all equal' => array(
- array(
- new Constraint('==', '1.0.0.0'),
- new Constraint('==', '1.0.0.0'),
- ),
- true,
- new Bound('1.0.0.0', true),
- new Bound('1.0.0.0', true),
- ),
- '">" should take precedence ">=" for lower bound when conjunctive' => array(
- array(
- new Constraint('>', '1.0.0.0'),
- new Constraint('>=', '1.0.0.0'),
- new Constraint('>', '1.0.0.0'),
- ),
- true,
- new Bound('1.0.0.0', false),
- Bound::positiveInfinity(),
- ),
- '">=" should take precedence ">" for lower bound when disjunctive' => array(
- array(
- new Constraint('>', '1.0.0.0'),
- new Constraint('>=', '1.0.0.0'),
- new Constraint('>', '1.0.0.0'),
- ),
- false,
- new Bound('1.0.0.0', true),
- Bound::positiveInfinity(),
- ),
- 'Bounds should be limited when conjunctive' => array(
- array(
- new Constraint('>=', '7.0.0.0'),
- new Constraint('<', '8.0.0.0'),
- ),
- true,
- new Bound('7.0.0.0', true),
- new Bound('8.0.0.0', false),
- ),
- 'Bounds should be unlimited when disjunctive' => array(
- array(
- new Constraint('>=', '7.0.0.0'),
- new Constraint('<', '8.0.0.0'),
- ),
- false,
- Bound::zero(),
- Bound::positiveInfinity(),
- ),
- );
- }
- /**
- * @dataProvider boundsIntegration
- *
- * @param string $constraints
- * @param Bound $expectedLower
- * @param Bound $expectedUpper
- */
- public function testBoundsIntegrationWithVersionParser($constraints, Bound $expectedLower, Bound $expectedUpper)
- {
- $versionParser = new VersionParser();
- $constraint = $versionParser->parseConstraints($constraints);
- $this->assertEquals($expectedLower, $constraint->getLowerBound(), 'Expected lower bound does not match');
- $this->assertEquals($expectedUpper, $constraint->getUpperBound(), 'Expected upper bound does not match');
- }
- /**
- * @return array<mixed>
- */
- public function boundsIntegration()
- {
- return array(
- '^7.0' => array(
- '^7.0',
- new Bound('7.0.0.0-dev', true),
- new Bound('8.0.0.0-dev', false),
- ),
- '^7.2' => array(
- '^7.2',
- new Bound('7.2.0.0-dev', true),
- new Bound('8.0.0.0-dev', false),
- ),
- '7.4.*' => array(
- '7.4.*',
- new Bound('7.4.0.0-dev', true),
- new Bound('7.5.0.0-dev', false),
- ),
- '7.2.* || 7.4.*' => array(
- '7.2.* || 7.4.*',
- new Bound('7.2.0.0-dev', true),
- new Bound('7.5.0.0-dev', false),
- ),
- );
- }
- public function testMultipleMultiConstraintsMerging()
- {
- $versionParser = new VersionParser();
- $strConstraints = array(
- '^7.0',
- '^7.2',
- '7.4.*',
- '7.2.* || 7.4.*',
- );
- $constraints = array();
- foreach ($strConstraints as $str) {
- $constraints[] = $versionParser->parseConstraints($str);
- }
- $constraint = new MultiConstraint($constraints);
- $this->assertEquals(new Bound('7.4.0.0-dev', true), $constraint->getLowerBound(), 'Expected lower bound does not match');
- $this->assertEquals(new Bound('7.5.0.0-dev', false), $constraint->getUpperBound(), 'Expected upper bound does not match');
- }
- public function testMultipleMultiConstraintsMergingWithGaps()
- {
- $versionParser = new VersionParser();
- $constraint = new MultiConstraint(array(
- $versionParser->parseConstraints('^7.1.15 || ^7.2.3'),
- $versionParser->parseConstraints('^7.2.2'),
- ));
- $this->assertEquals(new Bound('7.2.2.0-dev', true), $constraint->getLowerBound(), 'Expected lower bound does not match');
- $this->assertEquals(new Bound('8.0.0.0-dev', false), $constraint->getUpperBound(), 'Expected upper bound does not match');
- }
- public function testCreatesMatchAllConstraintIfNoneGiven()
- {
- $this->assertInstanceOf('Composer\Semver\Constraint\MatchAllConstraint', MultiConstraint::create(array()));
- }
- public function testMatchAllConstraintWithinConjunctiveMultiConstraint()
- {
- $this->assertSame('[>= 2.5.0.0-dev <= 3.0.0.0-dev *]', (string) MultiConstraint::create(
- array(new Constraint('>=', '2.5.0.0-dev'), new Constraint('<=', '3.0.0.0-dev'), new MatchAllConstraint())
- ));
- }
- public function testMatchAllConstraintWithinDisjunctiveMultiConstraint()
- {
- $this->assertSame('[>= 2.5.0.0-dev || *]', (string) MultiConstraint::create(
- array(new Constraint('>=', '2.5.0.0-dev'), new MatchAllConstraint()), false
- ));
- }
- /**
- * @dataProvider multiConstraintOptimizations
- *
- * @param string $constraints
- */
- public function testMultiConstraintOptimizations($constraints, ConstraintInterface $expectedConstraint)
- {
- // We're using the version parser here because that uses MultiConstraint::create() internally and
- // thus tests our optimizations. It's just easier to write complex multi constraint instances
- // using the string notation.
- $parser = new VersionParser();
- $this->assertSame((string) $expectedConstraint, (string) $parser->parseConstraints($constraints));
- }
- /**
- * @return array<mixed>
- */
- public function multiConstraintOptimizations()
- {
- return array(
- 'Test collapses contiguous' => array(
- '^2.5 || ^3.0',
- new MultiConstraint(
- array(
- new Constraint('>=', '2.5.0.0-dev'),
- new Constraint('<', '4.0.0.0-dev'),
- ),
- true // conjunctive
- ),
- ),
- 'Test collapses multiple contiguous' => array(
- '^2.5 || ^3.0 || ^4.0',
- new MultiConstraint(
- array(
- new Constraint('>=', '2.5.0.0-dev'),
- new Constraint('<', '5.0.0.0-dev'),
- ),
- true // conjunctive
- ),
- ),
- 'Test does not collapse when one side is more complex' => array(
- '~2.5.9 || ~2.6, >=2.6.2',
- new MultiConstraint(
- array(
- new MultiConstraint(
- array(
- new Constraint('>=', '2.5.9.0-dev'),
- new Constraint('<', '2.6.0.0-dev'),
- ),
- true // conjunctive
- ),
- new MultiConstraint(
- array(
- new Constraint('>=', '2.6.0.0-dev'),
- new Constraint('<', '3.0.0.0-dev'),
- new Constraint('>=', '2.6.2.0-dev'),
- ),
- true // conjunctive
- ),
- ),
- false
- )
- ),
- 'Test does not collapse multiple contiguous with other constraint but collapses the end' => array(
- '^1.0 || ^2.0 !=2.0.1 || ^3.0 || ^4.0',
- new MultiConstraint(
- array(
- new MultiConstraint(
- array(
- new Constraint('>=', '1.0.0.0-dev'),
- new Constraint('<', '2.0.0.0-dev'),
- ),
- true // conjunctive
- ),
- new MultiConstraint(
- array(
- new Constraint('>=', '2.0.0.0-dev'),
- new Constraint('<', '3.0.0.0-dev'),
- new Constraint('!=', '2.0.1.0'),
- ),
- true // conjunctive
- ),
- new MultiConstraint(
- array(
- new Constraint('>=', '3.0.0.0-dev'),
- new Constraint('<', '5.0.0.0-dev'),
- ),
- true // conjunctive
- ),
- ),
- false
- )
- ),
- 'Test does not collapse multiple contiguous with multiple other constraint' => array(
- '^1.0 != 1.0.1 || ^2.0 !=2.0.1 || ^3.0 || ^4.0 != 4.0.1',
- new MultiConstraint(
- array(
- new MultiConstraint(
- array(
- new Constraint('>=', '1.0.0.0-dev'),
- new Constraint('<', '2.0.0.0-dev'),
- new Constraint('!=', '1.0.1.0'),
- ),
- true // conjunctive
- ),
- new MultiConstraint(
- array(
- new Constraint('>=', '2.0.0.0-dev'),
- new Constraint('<', '3.0.0.0-dev'),
- new Constraint('!=', '2.0.1.0'),
- ),
- true // conjunctive
- ),
- new MultiConstraint(
- array(
- new Constraint('>=', '3.0.0.0-dev'),
- new Constraint('<', '4.0.0.0-dev'),
- ),
- true // conjunctive
- ),
- new MultiConstraint(
- array(
- new Constraint('>=', '4.0.0.0-dev'),
- new Constraint('<', '5.0.0.0-dev'),
- new Constraint('!=', '4.0.1.0'),
- ),
- true // conjunctive
- ),
- ),
- false
- )
- ),
- 'Test does not collapse if contiguous range and other constraints also apply' => array(
- '~0.1 || ~1.0 !=1.0.1',
- new MultiConstraint(
- array(
- new MultiConstraint(
- array(
- new Constraint('>=', '0.1.0.0-dev'),
- new Constraint('<', '1.0.0.0-dev'),
- ),
- true // conjunctive
- ),
- new MultiConstraint(
- array(
- new Constraint('>=', '1.0.0.0-dev'),
- new Constraint('<', '2.0.0.0-dev'),
- new Constraint('!=', '1.0.1.0'),
- ),
- true // conjunctive
- ),
- ),
- false
- )
- ),
- 'Parse caret constraints must not collapse if non contiguous range' => array(
- '^0.2 || ^1.0',
- new MultiConstraint(
- array(
- new MultiConstraint(
- array(
- new Constraint('>=', '0.2.0.0-dev'),
- new Constraint('<', '0.3.0.0-dev'),
- )
- ),
- new MultiConstraint(
- array(
- new Constraint('>=', '1.0.0.0-dev'),
- new Constraint('<', '2.0.0.0-dev'),
- )
- ),
- ),
- false // disjunctive
- ),
- ),
- 'Must not collapse if not contiguous range but collapse following constraints' => array(
- '^0.1 || ^1.0 || ^2.0',
- new MultiConstraint(
- array(
- new MultiConstraint(
- array(
- new Constraint('>=', '0.1.0.0-dev'),
- new Constraint('<', '0.2.0.0-dev'),
- )
- ),
- new MultiConstraint(
- array(
- new Constraint('>=', '1.0.0.0-dev'),
- new Constraint('<', '3.0.0.0-dev'),
- )
- ),
- ),
- false // disjunctive
- ),
- ),
- 'Must not collapse other constraint not in range' => array(
- '^1.0 || 2.1 || ^3.0',
- new MultiConstraint(
- array(
- new MultiConstraint(
- array(
- new Constraint('>=', '1.0.0.0-dev'),
- new Constraint('<', '2.0.0.0-dev'),
- )
- ),
- new Constraint('=', '2.1.0.0'),
- new MultiConstraint(
- array(
- new Constraint('>=', '3.0.0.0-dev'),
- new Constraint('<', '4.0.0.0-dev'),
- )
- ),
- ),
- false // disjunctive
- ),
- ),
- );
- }
- public function testMultiConstraintNotconjunctiveFillWithFalse()
- {
- $versionProvide = new Constraint('==', '1.1');
- $multiRequire = new MultiConstraint(array(
- new Constraint('>', 'dev-foo'), // always false
- new Constraint('>', 'dev-bar'), // always false
- ), false);
- $this->assertFalse($multiRequire->matches($versionProvide));
- $this->assertFalse($versionProvide->matches($multiRequire));
- $this->assertFalse($this->matchCompiled($multiRequire, '==', '1.1'));
- $this->assertFalse(Intervals::haveIntersections($multiRequire, $versionProvide));
- }
- public function testMultiConstraintConjunctiveFillWithTrue()
- {
- $versionProvide = new Constraint('!=', '1.1');
- $multiRequire = new MultiConstraint(array(
- new Constraint('!=', 'dev-foo'), // always true
- new Constraint('!=', 'dev-bar'), // always true
- ), true);
- $this->assertTrue($multiRequire->matches($versionProvide));
- $this->assertTrue($versionProvide->matches($multiRequire));
- $this->assertTrue($this->matchCompiled($multiRequire, '!=', '1.1'));
- $this->assertTrue(Intervals::haveIntersections($multiRequire, $versionProvide));
- }
- /**
- * @param Constraint::STR_OP_* $operator
- * @param string $version
- * @return bool
- */
- private function matchCompiled(ConstraintInterface $constraint, $operator, $version)
- {
- $map = array(
- '=' => Constraint::OP_EQ,
- '==' => Constraint::OP_EQ,
- '<' => Constraint::OP_LT,
- '<=' => Constraint::OP_LE,
- '>' => Constraint::OP_GT,
- '>=' => Constraint::OP_GE,
- '<>' => Constraint::OP_NE,
- '!=' => Constraint::OP_NE,
- );
- $code = $constraint->compile($map[$operator]);
- $v = $version;
- $b = 'dev-' === substr($v, 0, 4);
- return eval("return $code;");
- }
- }
|