diff --git a/phpda.yml.dist b/phpda.yml.dist index ef447191..412017e0 100644 --- a/phpda.yml.dist +++ b/phpda.yml.dist @@ -7,6 +7,7 @@ target: './phpda.svg' # namespaceFilter: 'Fully\Qualified\Class\Name\To\NamespaceFilter' # referenceValidator: 'Fully\Qualified\Class\Name\To\ReferenceValidator' groupLength: 1 +#detectCycles: false visitor: - PhpDA\Parser\Visitor\TagCollector - PhpDA\Parser\Visitor\SuperglobalCollector diff --git a/src/Command/Config.php b/src/Command/Config.php index 418d6dba..c8506de0 100644 --- a/src/Command/Config.php +++ b/src/Command/Config.php @@ -59,6 +59,9 @@ class Config /** @var int */ private $groupLength = 0; + /** @var bool */ + private $detectCycles = true; + /** @var array */ private $visitor = []; @@ -236,6 +239,19 @@ public function getGroupLength() return (int) $this->groupLength; } + /** + * @return bool + * @throws \InvalidArgumentException + */ + public function getDetectCycles() + { + if (!is_bool($this->detectCycles)) { + throw new \InvalidArgumentException('Config for detectCycles must be a boolean'); + } + + return (bool) $this->detectCycles; + } + /** * @return string|null * @throws \InvalidArgumentException diff --git a/src/Command/Strategy/AbstractStrategy.php b/src/Command/Strategy/AbstractStrategy.php index ea40e8b4..b5ba2e80 100644 --- a/src/Command/Strategy/AbstractStrategy.php +++ b/src/Command/Strategy/AbstractStrategy.php @@ -260,6 +260,7 @@ private function createGraph() $graphBuilder->setLogEntries($this->getAnalyzer()->getLogger()->getEntries()); $graphBuilder->setLayout($layout); $graphBuilder->setGroupLength($this->getConfig()->getGroupLength()); + $graphBuilder->setDetectCycles($this->getConfig()->getDetectCycles()); $graphBuilder->setAnalysisCollection($this->getAnalyzer()->getAnalysisCollection()); if ($referenceValidator = $this->loadReferenceValidator()) { diff --git a/src/Layout/Builder.php b/src/Layout/Builder.php index 68eade3d..d5a550cb 100644 --- a/src/Layout/Builder.php +++ b/src/Layout/Builder.php @@ -34,6 +34,7 @@ use PhpDA\Entity\Location; use PhpDA\Layout\Helper\CycleDetector; use PhpDA\Layout\Helper\GroupGenerator; +use PhpDA\Plugin\ConfigurableInterface; use PhpDA\Reference\ValidatorInterface; use PhpParser\Node\Name; use Symfony\Component\Finder\SplFileInfo; @@ -52,10 +53,13 @@ class Builder implements BuilderInterface /** @var GroupGenerator */ private $groupGenerator; + /** @var bool */ + private $detectCyclesEnabled; + /** @var array */ private $logEntries = []; - /** @var CycleDetector */ + /** @var CycleDetector|null */ private $cycleDetector; /** @var AnalysisCollection */ @@ -135,6 +139,14 @@ public function setGroupLength($groupLength) $this->groupGenerator->setGroupLength($groupLength); } + /** + * @param bool $detectCycles + */ + public function setDetectCycles($detectCycles) + { + $this->detectCyclesEnabled = $detectCycles; + } + /** * @param LayoutInterface $layout */ @@ -146,7 +158,11 @@ public function setLayout(LayoutInterface $layout) public function create() { $this->createDependencies(); - $this->detectCycles(); + + if ( $this->detectCyclesEnabled ) { + $this->detectCycles(); + } + $this->bindLayoutTo($this->getGraph(), $this->layout->getGraph(), 'graphviz.graph.'); $this->getGraph()->setAttribute('graphviz.groups', $this->groupGenerator->getGroups()); $this->getGraph()->setAttribute('graphviz.groupLayout', $this->layout->getGroup()); diff --git a/src/Layout/BuilderInterface.php b/src/Layout/BuilderInterface.php index 3e0517c1..3e014340 100644 --- a/src/Layout/BuilderInterface.php +++ b/src/Layout/BuilderInterface.php @@ -43,6 +43,11 @@ public function setLogEntries(array $entries); */ public function setGroupLength($groupLength); + /** + * @param bool $detectCycles + */ + public function setDetectCycles($detectCycles); + /** * @param LayoutInterface $layout */ diff --git a/src/Writer/Extractor/Graph.php b/src/Writer/Extractor/Graph.php index 9efc03ce..a269ef22 100644 --- a/src/Writer/Extractor/Graph.php +++ b/src/Writer/Extractor/Graph.php @@ -39,12 +39,16 @@ public function extract(FhacultyGraph $graph) $this->data = [ 'edges' => [], 'vertices' => [], - 'cycles' => $this->extractEntities($graph->getAttribute('cycles', [])), 'groups' => $graph->getAttribute('graphviz.groups', []), 'log' => $graph->getAttribute('logEntries', []), 'label' => $graph->getAttribute('graphviz.graph.label'), ]; + $cycles = $graph->getAttribute('cycles'); + if ( $cycles !== null ) { + $this->data['cylces'] = $this->extractEntities($cycles); + } + $edges = $graph->getEdges(); foreach ($edges as $edge) { /** @var Directed $edge */ diff --git a/tests/_data/json/expectation/inheritance.json b/tests/_data/json/expectation/inheritance.json index c521f42e..c27f96f0 100644 --- a/tests/_data/json/expectation/inheritance.json +++ b/tests/_data/json/expectation/inheritance.json @@ -195,7 +195,7 @@ "location": { "file": "\/app\/tests\/_data\/svg\/config\/..\/..\/..\/..\/src\/Writer\/Extractor\/Graph.php", "startLine": 32, - "endline": 127, + "endline": 131, "isComment": false }, "group": 0 diff --git a/tests/_support/_generated/FunctionalTesterActions.php b/tests/_support/_generated/FunctionalTesterActions.php index 54d2e745..12337dfc 100644 --- a/tests/_support/_generated/FunctionalTesterActions.php +++ b/tests/_support/_generated/FunctionalTesterActions.php @@ -1,13 +1,10 @@ -assertEquals(5, $element->getChildrenCount()); + * ``` + * + * Floating-point example: + * ```php + * assertEquals(0.3, $calculator->add(0.1, 0.2), 'Calculator should add the two numbers correctly.', 0.01); + * ``` * * @param $expected * @param $actual * @param string $message + * @param float $delta * @see \Codeception\Module\Asserts::assertEquals() */ - public function assertEquals($expected, $actual, $message = null) { + public function assertEquals($expected, $actual, $message = null, $delta = null) { return $this->getScenario()->runStep(new \Codeception\Step\Action('assertEquals', func_get_args())); } @@ -57,14 +69,29 @@ public function assertEquals($expected, $actual, $message = null) { /** * [!] Method is generated. Documentation taken from corresponding module. * - * Checks that two variables are not equal + * Checks that two variables are not equal. If you're comparing floating-point values, + * you can specify the optional "delta" parameter which dictates how great of a precision + * error are you willing to tolerate in order to consider the two values not equal. + * + * Regular example: + * ```php + * assertNotEquals(0, $element->getChildrenCount()); + * ``` + * + * Floating-point example: + * ```php + * assertNotEquals(0.4, $calculator->add(0.1, 0.2), 'Calculator should add the two numbers correctly.', 0.01); + * ``` * * @param $expected * @param $actual * @param string $message + * @param float $delta * @see \Codeception\Module\Asserts::assertNotEquals() */ - public function assertNotEquals($expected, $actual, $message = null) { + public function assertNotEquals($expected, $actual, $message = null, $delta = null) { return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotEquals', func_get_args())); } @@ -77,7 +104,6 @@ public function assertNotEquals($expected, $actual, $message = null) { * @param $expected * @param $actual * @param string $message - * @return mixed|void * @see \Codeception\Module\Asserts::assertSame() */ public function assertSame($expected, $actual, $message = null) { @@ -220,6 +246,36 @@ public function assertNotRegExp($pattern, $string, $message = null) { } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that a string starts with the given prefix. + * + * @param string $prefix + * @param string $string + * @param string $message + * @see \Codeception\Module\Asserts::assertStringStartsWith() + */ + public function assertStringStartsWith($prefix, $string, $message = null) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringStartsWith', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that a string doesn't start with the given prefix. + * + * @param string $prefix + * @param string $string + * @param string $message + * @see \Codeception\Module\Asserts::assertStringStartsNotWith() + */ + public function assertStringStartsNotWith($prefix, $string, $message = null) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringStartsNotWith', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -290,6 +346,20 @@ public function assertTrue($condition, $message = null) { } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the condition is NOT true (everything but true) + * + * @param $condition + * @param string $message + * @see \Codeception\Module\Asserts::assertNotTrue() + */ + public function assertNotTrue($condition, $message = null) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotTrue', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -304,6 +374,20 @@ public function assertFalse($condition, $message = null) { } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the condition is NOT false (everything but false) + * + * @param $condition + * @param string $message + * @see \Codeception\Module\Asserts::assertNotFalse() + */ + public function assertNotFalse($condition, $message = null) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotFalse', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -396,6 +480,22 @@ public function assertArrayNotHasKey($key, $actual, $description = null) { } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that array contains subset. + * + * @param array $subset + * @param array $array + * @param bool $strict + * @param string $message + * @see \Codeception\Module\Asserts::assertArraySubset() + */ + public function assertArraySubset($subset, $array, $strict = null, $message = null) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertArraySubset', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -488,9 +588,45 @@ public function fail($message) { * * @param $exception string or \Exception * @param $callback + * + * @deprecated Use expectThrowable instead * @see \Codeception\Module\Asserts::expectException() */ public function expectException($exception, $callback) { return $this->getScenario()->runStep(new \Codeception\Step\Action('expectException', func_get_args())); } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Handles and checks throwables (Exceptions/Errors) called inside the callback function. + * Either throwable class name or throwable instance should be provided. + * + * ```php + * expectThrowable(MyThrowable::class, function() { + * $this->doSomethingBad(); + * }); + * + * $I->expectThrowable(new MyException(), function() { + * $this->doSomethingBad(); + * }); + * ``` + * If you want to check message or throwable code, you can pass them with throwable instance: + * ```php + * expectThrowable(new MyError("Don't do bad things"), function() { + * $this->doSomethingBad(); + * }); + * ``` + * + * @param $throwable string or \Throwable + * @param $callback + * @see \Codeception\Module\Asserts::expectThrowable() + */ + public function expectThrowable($throwable, $callback) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('expectThrowable', func_get_args())); + } } diff --git a/tests/_support/_generated/UnitTesterActions.php b/tests/_support/_generated/UnitTesterActions.php index 79e4aa0d..ffddb862 100644 --- a/tests/_support/_generated/UnitTesterActions.php +++ b/tests/_support/_generated/UnitTesterActions.php @@ -1,13 +1,10 @@ -assertEquals(5, $element->getChildrenCount()); + * ``` + * + * Floating-point example: + * ```php + * assertEquals(0.3, $calculator->add(0.1, 0.2), 'Calculator should add the two numbers correctly.', 0.01); + * ``` * * @param $expected * @param $actual * @param string $message + * @param float $delta * @see \Codeception\Module\Asserts::assertEquals() */ - public function assertEquals($expected, $actual, $message = null) { + public function assertEquals($expected, $actual, $message = null, $delta = null) { return $this->getScenario()->runStep(new \Codeception\Step\Action('assertEquals', func_get_args())); } @@ -34,14 +46,29 @@ public function assertEquals($expected, $actual, $message = null) { /** * [!] Method is generated. Documentation taken from corresponding module. * - * Checks that two variables are not equal + * Checks that two variables are not equal. If you're comparing floating-point values, + * you can specify the optional "delta" parameter which dictates how great of a precision + * error are you willing to tolerate in order to consider the two values not equal. + * + * Regular example: + * ```php + * assertNotEquals(0, $element->getChildrenCount()); + * ``` + * + * Floating-point example: + * ```php + * assertNotEquals(0.4, $calculator->add(0.1, 0.2), 'Calculator should add the two numbers correctly.', 0.01); + * ``` * * @param $expected * @param $actual * @param string $message + * @param float $delta * @see \Codeception\Module\Asserts::assertNotEquals() */ - public function assertNotEquals($expected, $actual, $message = null) { + public function assertNotEquals($expected, $actual, $message = null, $delta = null) { return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotEquals', func_get_args())); } @@ -54,7 +81,6 @@ public function assertNotEquals($expected, $actual, $message = null) { * @param $expected * @param $actual * @param string $message - * @return mixed|void * @see \Codeception\Module\Asserts::assertSame() */ public function assertSame($expected, $actual, $message = null) { @@ -197,6 +223,36 @@ public function assertNotRegExp($pattern, $string, $message = null) { } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that a string starts with the given prefix. + * + * @param string $prefix + * @param string $string + * @param string $message + * @see \Codeception\Module\Asserts::assertStringStartsWith() + */ + public function assertStringStartsWith($prefix, $string, $message = null) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringStartsWith', func_get_args())); + } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that a string doesn't start with the given prefix. + * + * @param string $prefix + * @param string $string + * @param string $message + * @see \Codeception\Module\Asserts::assertStringStartsNotWith() + */ + public function assertStringStartsNotWith($prefix, $string, $message = null) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringStartsNotWith', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -267,6 +323,20 @@ public function assertTrue($condition, $message = null) { } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the condition is NOT true (everything but true) + * + * @param $condition + * @param string $message + * @see \Codeception\Module\Asserts::assertNotTrue() + */ + public function assertNotTrue($condition, $message = null) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotTrue', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -281,6 +351,20 @@ public function assertFalse($condition, $message = null) { } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the condition is NOT false (everything but false) + * + * @param $condition + * @param string $message + * @see \Codeception\Module\Asserts::assertNotFalse() + */ + public function assertNotFalse($condition, $message = null) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotFalse', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -373,6 +457,22 @@ public function assertArrayNotHasKey($key, $actual, $description = null) { } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that array contains subset. + * + * @param array $subset + * @param array $array + * @param bool $strict + * @param string $message + * @see \Codeception\Module\Asserts::assertArraySubset() + */ + public function assertArraySubset($subset, $array, $strict = null, $message = null) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertArraySubset', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -465,9 +565,45 @@ public function fail($message) { * * @param $exception string or \Exception * @param $callback + * + * @deprecated Use expectThrowable instead * @see \Codeception\Module\Asserts::expectException() */ public function expectException($exception, $callback) { return $this->getScenario()->runStep(new \Codeception\Step\Action('expectException', func_get_args())); } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Handles and checks throwables (Exceptions/Errors) called inside the callback function. + * Either throwable class name or throwable instance should be provided. + * + * ```php + * expectThrowable(MyThrowable::class, function() { + * $this->doSomethingBad(); + * }); + * + * $I->expectThrowable(new MyException(), function() { + * $this->doSomethingBad(); + * }); + * ``` + * If you want to check message or throwable code, you can pass them with throwable instance: + * ```php + * expectThrowable(new MyError("Don't do bad things"), function() { + * $this->doSomethingBad(); + * }); + * ``` + * + * @param $throwable string or \Throwable + * @param $callback + * @see \Codeception\Module\Asserts::expectThrowable() + */ + public function expectThrowable($throwable, $callback) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('expectThrowable', func_get_args())); + } } diff --git a/tests/functional/JsonCest.php b/tests/functional/JsonCest.php index fced979d..3d3b99a2 100644 --- a/tests/functional/JsonCest.php +++ b/tests/functional/JsonCest.php @@ -74,6 +74,7 @@ private function readResultFrom($jsonFile) unset($result['label']); + ksort( $result ); return $result; } diff --git a/tests/unit/PhpDATest/Command/ConfigTest.php b/tests/unit/PhpDATest/Command/ConfigTest.php index 0b8c4e2d..3c5ea337 100644 --- a/tests/unit/PhpDATest/Command/ConfigTest.php +++ b/tests/unit/PhpDATest/Command/ConfigTest.php @@ -39,6 +39,7 @@ public function testBasic() 'target' => 'myTarget', 'filePattern' => 'myFilePattern', 'groupLength' => 4, + 'detectCycles' => false, 'visitor' => array('foo', 'baz'), 'visitorOptions' => array('bar'), 'referenceValidator' => 'myValidator', @@ -62,6 +63,7 @@ public function testOptionalConfigs() $this->assertSame(array(), $config->getVisitorOptions()); $this->assertSame('usage', $config->getMode()); $this->assertSame(0, $config->getGroupLength()); + $this->assertSame(true, $config->getDetectCycles()); $this->assertNull($config->getReferenceValidator()); $this->assertNull($config->getNamespaceFilter()); } @@ -137,6 +139,14 @@ public function testNumericGroupLength() $this->assertSame(1, $config->getGroupLength()); } + public function testInvalidDetectCycles() + { + $this->setExpectedException('InvalidArgumentException'); + $config = new Config(array('detectCycles' => 'test')); + + $config->getDetectCycles(); + } + public function testInvalidVisitor() { $this->setExpectedException('InvalidArgumentException'); diff --git a/tests/unit/PhpDATest/Layout/BuilderTest.php b/tests/unit/PhpDATest/Layout/BuilderTest.php index 7db1aa1f..10711957 100644 --- a/tests/unit/PhpDATest/Layout/BuilderTest.php +++ b/tests/unit/PhpDATest/Layout/BuilderTest.php @@ -95,6 +95,14 @@ public function testDelegatingGroupLengthMutatorToGroupGenerator() $this->fixture->setGroupLength(4); } + public function testDetectCyclesMutator() + { + $this->fixture->setDetectCycles(false); + $this->cycleDetector->shouldNotReceive('inspect'); + $this->cycleDetector->shouldNotReceive('getCycles'); + $this->fixture->create(); + } + public function testFluentInterfaceForCreating() { $this->cycleDetector->shouldReceive('getCycledEdges')->andReturn(array()); diff --git a/tests/unit/PhpDATest/Writer/Extractor/GraphTest.php b/tests/unit/PhpDATest/Writer/Extractor/GraphTest.php index 218d61ba..ecf4a901 100644 --- a/tests/unit/PhpDATest/Writer/Extractor/GraphTest.php +++ b/tests/unit/PhpDATest/Writer/Extractor/GraphTest.php @@ -65,10 +65,10 @@ public function testExtraction() 'group' => 'group', ), ), - 'cycles' => array('cycle'), 'groups' => array('groups'), 'log' => array('logentries'), 'label' => 'label', + 'cycles' => array('cycle'), ); $edges = \Mockery::mock('Fhaculty\Graph\Set\Edges'); @@ -105,11 +105,39 @@ public function testExtraction() $graph->shouldReceive('getAttribute')->with('graphviz.groups', array())->andReturn($expected['groups']); $graph->shouldReceive('getAttribute')->with('graphviz.graph.label')->andReturn($expected['label']); - $graph->shouldReceive('getAttribute')->with('cycles', array())->andReturn(array($cycle)); + $graph->shouldReceive('getAttribute')->with('cycles')->andReturn(array($cycle)); $graph->shouldReceive('getAttribute')->with('logEntries', array())->andReturn(array('logentries')); $graph->shouldReceive('getEdges')->once()->andReturn(array($edge)); $this->assertSame($expected, $this->fixture->extract($graph)); } + + public function testExtractionWithoutCycles() + { + $expected = array( + 'edges' => array(), + 'vertices' => array(), + 'groups' => array(), + 'log' => array('logentries'), + 'label' => 'label', + ); + + $edges = \Mockery::mock('Fhaculty\Graph\Set\Edges'); + $edges->shouldReceive('count')->andReturn(0); + + $cycle = \Mockery::mock('PhpDA\Entity\Cycle'); + $cycle->shouldNotReceive('toArray'); + + $graph = \Mockery::mock('Fhaculty\Graph\Graph'); + + $graph->shouldReceive('getAttribute')->with('graphviz.groups', array())->andReturn($expected['groups']); + $graph->shouldReceive('getAttribute')->with('graphviz.graph.label')->andReturn($expected['label']); + $graph->shouldReceive('getAttribute')->with('cycles')->andReturn(null); + $graph->shouldReceive('getAttribute')->with('logEntries', array())->andReturn(array('logentries')); + + $graph->shouldReceive('getEdges')->once()->andReturn(array()); + + $this->assertSame($expected, $this->fixture->extract($graph)); + } }