Skip to content

Commit b893a16

Browse files
authored
Merge pull request #175 from fbourigault/profiling-functional-tests
Profiling might not work with CachePlugin
2 parents 7447f5a + 2066376 commit b893a16

File tree

4 files changed

+237
-11
lines changed

4 files changed

+237
-11
lines changed

Collector/ProfilePlugin.php

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,27 +65,105 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
6565

6666
// wrap the next callback to profile the plugin request changes
6767
$wrappedNext = function (RequestInterface $request) use ($next, $profile) {
68-
$profile->setRequest($this->formatter->formatRequest($request));
68+
$this->onOutgoingRequest($request, $profile);
6969

7070
return $next($request);
7171
};
7272

7373
// wrap the first callback to profile the plugin request changes
7474
$wrappedFirst = function (RequestInterface $request) use ($first, $profile) {
75-
$profile->setRequest($this->formatter->formatRequest($request));
75+
$this->onOutgoingRequest($request, $profile);
7676

7777
return $first($request);
7878
};
7979

80-
return $this->plugin->handleRequest($request, $wrappedNext, $wrappedFirst)->then(function (ResponseInterface $response) use ($profile) {
81-
$profile->setResponse($this->formatter->formatResponse($response));
80+
try {
81+
$promise = $this->plugin->handleRequest($request, $wrappedNext, $wrappedFirst);
82+
} catch (Exception $e) {
83+
$this->onException($request, $profile, $e, $stack);
84+
85+
throw $e;
86+
}
87+
88+
return $promise->then(function (ResponseInterface $response) use ($profile, $request, $stack) {
89+
$this->onOutgoingResponse($response, $profile, $request, $stack);
8290

8391
return $response;
84-
}, function (Exception $exception) use ($profile) {
85-
$profile->setFailed(true);
86-
$profile->setResponse($this->formatter->formatException($exception));
92+
}, function (Exception $exception) use ($profile, $request, $stack) {
93+
$this->onException($request, $profile, $exception, $stack);
8794

8895
throw $exception;
8996
});
9097
}
98+
99+
/**
100+
* @param RequestInterface $request
101+
* @param Profile $profile
102+
* @param Exception $exception
103+
* @param Stack $stack
104+
*/
105+
private function onException(
106+
RequestInterface $request,
107+
Profile $profile,
108+
Exception $exception,
109+
Stack $stack = null
110+
) {
111+
$profile->setFailed(true);
112+
$profile->setResponse($this->formatter->formatException($exception));
113+
$this->collectRequestInformation($request, $stack);
114+
}
115+
116+
/**
117+
* @param RequestInterface $request
118+
* @param Profile $profile
119+
*/
120+
private function onOutgoingRequest(RequestInterface $request, Profile $profile)
121+
{
122+
$profile->setRequest($this->formatter->formatRequest($request));
123+
}
124+
125+
/**
126+
* @param ResponseInterface $response
127+
* @param Profile $profile
128+
* @param RequestInterface $request
129+
* @param Stack $stack
130+
*/
131+
private function onOutgoingResponse(ResponseInterface $response, Profile $profile, RequestInterface $request, Stack $stack = null)
132+
{
133+
$profile->setResponse($this->formatter->formatResponse($response));
134+
$this->collectRequestInformation($request, $stack);
135+
}
136+
137+
/**
138+
* Collect request information when not already done by the HTTP client. This happens when using the CachePlugin
139+
* and the cache is hit without re-validation.
140+
*
141+
* @param RequestInterface $request
142+
* @param Stack|null $stack
143+
*/
144+
private function collectRequestInformation(RequestInterface $request, Stack $stack = null)
145+
{
146+
if (null === $stack) {
147+
return;
148+
}
149+
150+
if (empty($stack->getRequestTarget())) {
151+
$stack->setRequestTarget($request->getRequestTarget());
152+
}
153+
if (empty($stack->getRequestMethod())) {
154+
$stack->setRequestMethod($request->getMethod());
155+
}
156+
if (empty($stack->getRequestScheme())) {
157+
$stack->setRequestScheme($request->getUri()->getScheme());
158+
}
159+
if (empty($stack->getRequestHost())) {
160+
$stack->setRequestHost($request->getUri()->getHost());
161+
}
162+
if (empty($stack->getClientRequest())) {
163+
$stack->setClientRequest($this->formatter->formatRequest($request));
164+
}
165+
if (empty($stack->getCurlCommand())) {
166+
$stack->setCurlCommand($this->formatter->formatAsCurlCommand($request));
167+
}
168+
}
91169
}

Tests/Functional/ProfilingTest.php

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
3+
namespace Http\HttplugBundle\Tests\Functional;
4+
5+
use GuzzleHttp\Psr7\Request;
6+
use Http\Client\Common\Plugin;
7+
use Http\Client\Common\PluginClient;
8+
use Http\Discovery\StreamFactoryDiscovery;
9+
use Http\Discovery\UriFactoryDiscovery;
10+
use Http\HttplugBundle\Collector\Collector;
11+
use Http\HttplugBundle\Collector\Formatter;
12+
use Http\HttplugBundle\Collector\ProfileClient;
13+
use Http\HttplugBundle\Collector\ProfilePlugin;
14+
use Http\HttplugBundle\Collector\StackPlugin;
15+
use Http\Message\Formatter\CurlCommandFormatter;
16+
use Http\Message\Formatter\FullHttpMessageFormatter;
17+
use Http\Mock\Client;
18+
use Psr\Http\Message\RequestInterface;
19+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
20+
use Symfony\Component\Stopwatch\Stopwatch;
21+
22+
class ProfilingTest extends \PHPUnit_Framework_TestCase
23+
{
24+
/**
25+
* @var Collector
26+
*/
27+
private $collector;
28+
29+
/**
30+
* @var Formatter
31+
*/
32+
private $formatter;
33+
34+
/**
35+
* @var Stopwatch
36+
*/
37+
private $stopwatch;
38+
39+
public function setUp()
40+
{
41+
$this->collector = new Collector([]);
42+
$this->formatter = new Formatter(new FullHttpMessageFormatter(), new CurlCommandFormatter());
43+
$this->stopwatch = new Stopwatch();
44+
}
45+
46+
public function testProfilingWithCachePlugin()
47+
{
48+
$client = $this->createClient([
49+
new Plugin\CachePlugin(new ArrayAdapter(), StreamFactoryDiscovery::find(), [
50+
'respect_response_cache_directives' => [],
51+
'default_ttl' => 86400,
52+
]),
53+
]);
54+
55+
$client->sendRequest(new Request('GET', 'https://example.com'));
56+
$client->sendRequest(new Request('GET', 'https://example.com'));
57+
58+
$this->assertCount(2, $this->collector->getStacks());
59+
$stack = $this->collector->getStacks()[1];
60+
$this->assertEquals('GET', $stack->getRequestMethod());
61+
$this->assertEquals('https', $stack->getRequestScheme());
62+
$this->assertEquals('/', $stack->getRequestTarget());
63+
$this->assertEquals('example.com', $stack->getRequestHost());
64+
}
65+
66+
public function testProfilingWhenPluginThrowException()
67+
{
68+
$client = $this->createClient([
69+
new ExceptionThrowerPlugin(),
70+
]);
71+
72+
$this->setExpectedException(\Exception::class);
73+
74+
try {
75+
$client->sendRequest(new Request('GET', 'https://example.com'));
76+
} finally {
77+
$this->assertCount(1, $this->collector->getStacks());
78+
$stack = $this->collector->getStacks()[0];
79+
$this->assertEquals('GET', $stack->getRequestMethod());
80+
$this->assertEquals('https', $stack->getRequestScheme());
81+
$this->assertEquals('/', $stack->getRequestTarget());
82+
$this->assertEquals('example.com', $stack->getRequestHost());
83+
}
84+
}
85+
86+
public function testProfiling()
87+
{
88+
$client = $this->createClient([
89+
new Plugin\AddHostPlugin(UriFactoryDiscovery::find()->createUri('https://example.com')),
90+
new Plugin\RedirectPlugin(),
91+
new Plugin\RetryPlugin(),
92+
]);
93+
94+
$client->sendRequest(new Request('GET', '/'));
95+
96+
$this->assertCount(1, $this->collector->getStacks());
97+
$stack = $this->collector->getStacks()[0];
98+
$this->assertCount(3, $stack->getProfiles());
99+
$this->assertEquals('GET', $stack->getRequestMethod());
100+
$this->assertEquals('https', $stack->getRequestScheme());
101+
$this->assertEquals('/', $stack->getRequestTarget());
102+
$this->assertEquals('example.com', $stack->getRequestHost());
103+
}
104+
105+
private function createClient(array $plugins, $clientName = 'Acme', array $clientOptions = [])
106+
{
107+
$plugins = array_map(function (Plugin $plugin) {
108+
return new ProfilePlugin($plugin, $this->collector, $this->formatter, get_class($plugin));
109+
}, $plugins);
110+
111+
array_unshift($plugins, new StackPlugin($this->collector, $this->formatter, $clientName));
112+
113+
$client = new Client();
114+
$client = new ProfileClient($client, $this->collector, $this->formatter, $this->stopwatch);
115+
$client = new PluginClient($client, $plugins, $clientOptions);
116+
117+
return $client;
118+
}
119+
}
120+
121+
class ExceptionThrowerPlugin implements Plugin
122+
{
123+
/**
124+
* {@inheritdoc}
125+
*/
126+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
127+
{
128+
throw new \Exception();
129+
}
130+
}

Tests/Unit/Collector/ProfilePluginTest.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
use Http\HttplugBundle\Collector\ProfilePlugin;
1212
use Http\HttplugBundle\Collector\Stack;
1313
use Http\Promise\FulfilledPromise;
14+
use Http\Promise\Promise;
15+
use Http\Promise\RejectedPromise;
1416
use Psr\Http\Message\RequestInterface;
1517
use Psr\Http\Message\ResponseInterface;
1618

@@ -36,6 +38,11 @@ class ProfilePluginTest extends \PHPUnit_Framework_TestCase
3638
*/
3739
private $response;
3840

41+
/**
42+
* @var Promise
43+
*/
44+
private $fulfilledPromise;
45+
3946
/**
4047
* @var Stack
4148
*/
@@ -46,6 +53,11 @@ class ProfilePluginTest extends \PHPUnit_Framework_TestCase
4653
*/
4754
private $exception;
4855

56+
/**
57+
* @var Promise
58+
*/
59+
private $rejectedPromise;
60+
4961
/**
5062
* @var Formatter
5163
*/
@@ -62,8 +74,10 @@ public function setUp()
6274
$this->collector = $this->getMockBuilder(Collector::class)->disableOriginalConstructor()->getMock();
6375
$this->request = new Request('GET', '/');
6476
$this->response = new Response();
77+
$this->fulfilledPromise = new FulfilledPromise($this->response);
6578
$this->currentStack = new Stack('default', 'FormattedRequest');
6679
$this->exception = new TransferException();
80+
$this->rejectedPromise = new RejectedPromise($this->exception);
6781
$this->formatter = $this->getMockBuilder(Formatter::class)->disableOriginalConstructor()->getMock();
6882

6983
$this->collector
@@ -74,9 +88,7 @@ public function setUp()
7488
$this->plugin
7589
->method('handleRequest')
7690
->willReturnCallback(function ($request, $next, $first) {
77-
$next($request);
78-
79-
return new FulfilledPromise($this->response);
91+
return $next($request);
8092
})
8193
;
8294

@@ -115,13 +127,15 @@ public function testCallDecoratedPlugin()
115127
;
116128

117129
$this->subject->handleRequest($this->request, function () {
130+
return $this->fulfilledPromise;
118131
}, function () {
119132
});
120133
}
121134

122135
public function testProfileIsInitialized()
123136
{
124137
$this->subject->handleRequest($this->request, function () {
138+
return $this->fulfilledPromise;
125139
}, function () {
126140
});
127141

@@ -133,6 +147,7 @@ public function testProfileIsInitialized()
133147
public function testCollectRequestInformations()
134148
{
135149
$this->subject->handleRequest($this->request, function () {
150+
return $this->fulfilledPromise;
136151
}, function () {
137152
});
138153

@@ -143,6 +158,7 @@ public function testCollectRequestInformations()
143158
public function testOnFulfilled()
144159
{
145160
$promise = $this->subject->handleRequest($this->request, function () {
161+
return $this->fulfilledPromise;
146162
}, function () {
147163
});
148164

@@ -156,7 +172,7 @@ public function testOnRejected()
156172
$this->setExpectedException(TransferException::class);
157173

158174
$promise = $this->subject->handleRequest($this->request, function () {
159-
throw new TransferException();
175+
return $this->rejectedPromise;
160176
}, function () {
161177
});
162178

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,13 @@
3737
"php-http/guzzle6-adapter": "^1.1.1",
3838
"php-http/react-adapter": "^0.2.1",
3939
"php-http/buzz-adapter": "^0.3",
40+
"php-http/mock-client": "^1.0",
4041
"symfony/phpunit-bridge": "^3.2",
4142
"symfony/twig-bundle": "^2.8 || ^3.0",
4243
"symfony/twig-bridge": "^2.8 || ^3.0",
4344
"symfony/web-profiler-bundle": "^2.8 || ^3.0",
4445
"symfony/finder": "^2.7 || ^3.0",
46+
"symfony/cache": "^3.1",
4547
"polishsymfonycommunity/symfony-mocker-container": "^1.0",
4648
"matthiasnoback/symfony-dependency-injection-test": "^1.0"
4749
},

0 commit comments

Comments
 (0)