Merge branch '4.4' into 5.1

* 4.4:
  Add tests on CacheDataCollector
  [ProxyManagerBridge] fix tests
  [ProxyManagerBridge] relax fixture in tests
  Fix circular detection with multiple paths
This commit is contained in:
Alexander M. Turek 2020-11-12 23:25:33 +01:00
commit b8afc7cba3
9 changed files with 284 additions and 6 deletions

View File

@ -76,7 +76,7 @@ class SunnyInterface_%s implements \ProxyManager\Proxy\VirtualProxyInterface, \S
$targetObject = $this->valueHolder%s;
$backtrace = debug_backtrace(false);
$backtrace = debug_backtrace(false%S);
trigger_error(
sprintf(
'Undefined property: %s::$%s in %s on line %s',
@ -115,8 +115,7 @@ class SunnyInterface_%s implements \ProxyManager\Proxy\VirtualProxyInterface, \S
$targetObject = $this->valueHolder%s;
unset($targetObject->$name);
return;
}
%a }
public function __clone()
{

View File

@ -0,0 +1,98 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Tests\Marshaller;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\TraceableAdapter;
use Symfony\Component\Cache\DataCollector\CacheDataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class CacheDataCollectorTest extends TestCase
{
private const INSTANCE_NAME = 'test';
public function testEmptyDataCollector()
{
$statistics = $this->getCacheDataCollectorStatisticsFromEvents([]);
$this->assertEquals($statistics[self::INSTANCE_NAME]['calls'], 0, 'calls');
$this->assertEquals($statistics[self::INSTANCE_NAME]['reads'], 0, 'reads');
$this->assertEquals($statistics[self::INSTANCE_NAME]['hits'], 0, 'hits');
$this->assertEquals($statistics[self::INSTANCE_NAME]['misses'], 0, 'misses');
$this->assertEquals($statistics[self::INSTANCE_NAME]['writes'], 0, 'writes');
}
public function testOneEventDataCollector()
{
$traceableAdapterEvent = new \stdClass();
$traceableAdapterEvent->name = 'getItem';
$traceableAdapterEvent->start = 0;
$traceableAdapterEvent->end = 0;
$traceableAdapterEvent->hits = 0;
$statistics = $this->getCacheDataCollectorStatisticsFromEvents([$traceableAdapterEvent]);
$this->assertEquals($statistics[self::INSTANCE_NAME]['calls'], 1, 'calls');
$this->assertEquals($statistics[self::INSTANCE_NAME]['reads'], 1, 'reads');
$this->assertEquals($statistics[self::INSTANCE_NAME]['hits'], 0, 'hits');
$this->assertEquals($statistics[self::INSTANCE_NAME]['misses'], 1, 'misses');
$this->assertEquals($statistics[self::INSTANCE_NAME]['writes'], 0, 'writes');
}
public function testHitedEventDataCollector()
{
$traceableAdapterEvent = new \stdClass();
$traceableAdapterEvent->name = 'hasItem';
$traceableAdapterEvent->start = 0;
$traceableAdapterEvent->end = 0;
$traceableAdapterEvent->hits = 1;
$traceableAdapterEvent->misses = 0;
$traceableAdapterEvent->result = ['foo' => false];
$statistics = $this->getCacheDataCollectorStatisticsFromEvents([$traceableAdapterEvent]);
$this->assertEquals($statistics[self::INSTANCE_NAME]['calls'], 1, 'calls');
$this->assertEquals($statistics[self::INSTANCE_NAME]['reads'], 1, 'reads');
$this->assertEquals($statistics[self::INSTANCE_NAME]['hits'], 1, 'hits');
$this->assertEquals($statistics[self::INSTANCE_NAME]['misses'], 0, 'misses');
$this->assertEquals($statistics[self::INSTANCE_NAME]['writes'], 0, 'writes');
}
public function testSavedEventDataCollector()
{
$traceableAdapterEvent = new \stdClass();
$traceableAdapterEvent->name = 'save';
$traceableAdapterEvent->start = 0;
$traceableAdapterEvent->end = 0;
$statistics = $this->getCacheDataCollectorStatisticsFromEvents([$traceableAdapterEvent]);
$this->assertEquals($statistics[self::INSTANCE_NAME]['calls'], 1, 'calls');
$this->assertEquals($statistics[self::INSTANCE_NAME]['reads'], 0, 'reads');
$this->assertEquals($statistics[self::INSTANCE_NAME]['hits'], 0, 'hits');
$this->assertEquals($statistics[self::INSTANCE_NAME]['misses'], 0, 'misses');
$this->assertEquals($statistics[self::INSTANCE_NAME]['writes'], 1, 'writes');
}
private function getCacheDataCollectorStatisticsFromEvents(array $traceableAdapterEvents)
{
$traceableAdapterMock = $this->createMock(TraceableAdapter::class);
$traceableAdapterMock->method('getCalls')->willReturn($traceableAdapterEvents);
$cacheDataCollector = new CacheDataCollector();
$cacheDataCollector->addInstance(self::INSTANCE_NAME, $traceableAdapterMock);
$cacheDataCollector->collect(new Request(), new Response());
return $cacheDataCollector->getStatistics();
}
}

View File

@ -38,6 +38,7 @@
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/filesystem": "^4.4|^5.0",
"symfony/http-kernel": "^4.4|^5.0",
"symfony/var-dumper": "^4.4|^5.0"
},
"conflict": {

View File

@ -433,14 +433,13 @@ EOF;
$this->singleUsePrivateIds = array_diff_key($this->singleUsePrivateIds, $this->circularReferences);
}
private function collectCircularReferences(string $sourceId, array $edges, array &$checkedNodes, array $path = [], bool $byConstructor = true): void
private function collectCircularReferences(string $sourceId, array $edges, array &$checkedNodes, array &$loops = [], array $path = [], bool $byConstructor = true): void
{
$path[$sourceId] = $byConstructor;
$checkedNodes[$sourceId] = true;
foreach ($edges as $edge) {
$node = $edge->getDestNode();
$id = $node->getId();
if (!($definition = $node->getValue()) instanceof Definition || $sourceId === $id || ($edge->isLazy() && ($this->proxyDumper ?? $this->getProxyDumper())->isProxyCandidate($definition)) || $edge->isWeak()) {
continue;
}
@ -448,9 +447,12 @@ EOF;
if (isset($path[$id])) {
$loop = null;
$loopByConstructor = $edge->isReferencedByConstructor();
$pathInLoop = [$id, []];
foreach ($path as $k => $pathByConstructor) {
if (null !== $loop) {
$loop[] = $k;
$pathInLoop[1][$k] = $pathByConstructor;
$loops[$k][] = &$pathInLoop;
$loopByConstructor = $loopByConstructor && $pathByConstructor;
} elseif ($k === $id) {
$loop = [];
@ -458,7 +460,39 @@ EOF;
}
$this->addCircularReferences($id, $loop, $loopByConstructor);
} elseif (!isset($checkedNodes[$id])) {
$this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $path, $edge->isReferencedByConstructor());
$this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $loops, $path, $edge->isReferencedByConstructor());
} elseif (isset($loops[$id])) {
// we already had detected loops for this edge
// let's check if we have a common ancestor in one of the detected loops
foreach ($loops[$id] as [$first, $loopPath]) {
if (!isset($path[$first])) {
continue;
}
// We have a common ancestor, let's fill the current path
$fillPath = null;
foreach ($loopPath as $k => $pathByConstructor) {
if (null !== $fillPath) {
$fillPath[$k] = $pathByConstructor;
} elseif ($k === $id) {
$fillPath = $path;
$fillPath[$k] = $pathByConstructor;
}
}
// we can now build the loop
$loop = null;
$loopByConstructor = $edge->isReferencedByConstructor();
foreach ($fillPath as $k => $pathByConstructor) {
if (null !== $loop) {
$loop[] = $k;
$loopByConstructor = $loopByConstructor && $pathByConstructor;
} elseif ($k === $first) {
$loop = [];
}
}
$this->addCircularReferences($first, $loop, true);
break;
}
}
}
unset($path[$sourceId]);

View File

@ -1397,6 +1397,9 @@ class ContainerBuilderTest extends TestCase
$container = include __DIR__.'/Fixtures/containers/container_almost_circular.php';
$container->compile();
$pA = $container->get('pA');
$this->assertEquals(new \stdClass(), $pA);
$logger = $container->get('monolog.logger');
$this->assertEquals(new \stdClass(), $logger->handler);

View File

@ -1061,6 +1061,9 @@ class PhpDumperTest extends TestCase
$container = new $container();
$pA = $container->get('pA');
$this->assertEquals(new \stdClass(), $pA);
$logger = $container->get('monolog.logger');
$this->assertEquals(new \stdClass(), $logger->handler);

View File

@ -9,6 +9,21 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\FooForCircularWithAddCa
$public = 'public' === $visibility;
$container = new ContainerBuilder();
// multiple path detection
$container->register('pA', 'stdClass')->setPublic(true)
->addArgument(new Reference('pB'))
->addArgument(new Reference('pC'));
$container->register('pB', 'stdClass')->setPublic($public)
->setProperty('d', new Reference('pD'));
$container->register('pC', 'stdClass')->setPublic($public)
->setLazy(true)
->setProperty('d', new Reference('pD'));
$container->register('pD', 'stdClass')->setPublic($public)
->addArgument(new Reference('pA'));
// monolog-like + handler that require monolog
$container->register('monolog.logger', 'stdClass')->setPublic(true)

View File

@ -37,6 +37,7 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Private extends Container
'manager2' => 'getManager2Service',
'manager3' => 'getManager3Service',
'monolog.logger' => 'getMonolog_LoggerService',
'pA' => 'getPAService',
'root' => 'getRootService',
'subscriber' => 'getSubscriberService',
];
@ -84,6 +85,9 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Private extends Container
'manager4' => true,
'monolog.logger_2' => true,
'multiuse1' => true,
'pB' => true,
'pC' => true,
'pD' => true,
'subscriber2' => true,
];
}
@ -371,6 +375,28 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Private extends Container
return $instance;
}
/**
* Gets the public 'pA' shared service.
*
* @return \stdClass
*/
protected function getPAService()
{
$a = new \stdClass();
$b = ($this->privates['pC'] ?? $this->getPCService());
if (isset($this->services['pA'])) {
return $this->services['pA'];
}
$this->services['pA'] = $instance = new \stdClass($a, $b);
$a->d = ($this->privates['pD'] ?? $this->getPDService());
return $instance;
}
/**
* Gets the public 'root' shared service.
*
@ -478,4 +504,34 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Private extends Container
return $instance;
}
/**
* Gets the private 'pC' shared service.
*
* @return \stdClass
*/
protected function getPCService($lazyLoad = true)
{
$this->privates['pC'] = $instance = new \stdClass();
$instance->d = ($this->privates['pD'] ?? $this->getPDService());
return $instance;
}
/**
* Gets the private 'pD' shared service.
*
* @return \stdClass
*/
protected function getPDService()
{
$a = ($this->services['pA'] ?? $this->getPAService());
if (isset($this->privates['pD'])) {
return $this->privates['pD'];
}
return $this->privates['pD'] = new \stdClass($a);
}
}

View File

@ -50,6 +50,10 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Public extends Container
'manager3' => 'getManager3Service',
'monolog.logger' => 'getMonolog_LoggerService',
'monolog.logger_2' => 'getMonolog_Logger2Service',
'pA' => 'getPAService',
'pB' => 'getPBService',
'pC' => 'getPCService',
'pD' => 'getPDService',
'root' => 'getRootService',
'subscriber' => 'getSubscriberService',
];
@ -559,6 +563,71 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Public extends Container
return $instance;
}
/**
* Gets the public 'pA' shared service.
*
* @return \stdClass
*/
protected function getPAService()
{
$a = ($this->services['pB'] ?? $this->getPBService());
if (isset($this->services['pA'])) {
return $this->services['pA'];
}
$b = ($this->services['pC'] ?? $this->getPCService());
if (isset($this->services['pA'])) {
return $this->services['pA'];
}
return $this->services['pA'] = new \stdClass($a, $b);
}
/**
* Gets the public 'pB' shared service.
*
* @return \stdClass
*/
protected function getPBService()
{
$this->services['pB'] = $instance = new \stdClass();
$instance->d = ($this->services['pD'] ?? $this->getPDService());
return $instance;
}
/**
* Gets the public 'pC' shared service.
*
* @return \stdClass
*/
protected function getPCService($lazyLoad = true)
{
$this->services['pC'] = $instance = new \stdClass();
$instance->d = ($this->services['pD'] ?? $this->getPDService());
return $instance;
}
/**
* Gets the public 'pD' shared service.
*
* @return \stdClass
*/
protected function getPDService()
{
$a = ($this->services['pA'] ?? $this->getPAService());
if (isset($this->services['pD'])) {
return $this->services['pD'];
}
return $this->services['pD'] = new \stdClass($a);
}
/**
* Gets the public 'root' shared service.
*