Skip to content

Commit b78238f

Browse files
committed
Add integration test case for formatting
1 parent 8074481 commit b78238f

File tree

3 files changed

+256
-0
lines changed

3 files changed

+256
-0
lines changed

lib/Test/ChannelStream.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace Phpactor\LanguageServer\Test;
4+
5+
use Amp\ByteStream\ClosedException;
6+
use Amp\ByteStream\IteratorStream;
7+
use Amp\ByteStream\PendingReadError;
8+
use Amp\ByteStream\StreamException;
9+
use Amp\Emitter;
10+
use Amp\Promise;
11+
use Phpactor\LanguageServer\Core\Server\Stream\DuplexStream;
12+
13+
final class ChannelStream implements DuplexStream
14+
{
15+
private Emitter $emitter;
16+
17+
private IteratorStream $stream;
18+
19+
public function __construct()
20+
{
21+
$this->emitter = new Emitter();
22+
$this->stream = new IteratorStream($this->emitter->iterate());
23+
}
24+
25+
/**
26+
* Reads data from the stream.
27+
*
28+
* @return Promise Resolves with a string when new data is available or `null` if the stream has closed.
29+
*
30+
* @psalm-return Promise<string|null>
31+
*
32+
* @throws PendingReadError Thrown if another read operation is still pending.
33+
*/
34+
public function read(): Promise
35+
{
36+
return $this->stream->read();
37+
}
38+
39+
/**
40+
* Writes data to the stream.
41+
*
42+
* @param string $data Bytes to write.
43+
*
44+
* @return Promise Succeeds once the data has been successfully written to the stream.
45+
*
46+
* @throws ClosedException If the stream has already been closed.
47+
* @throws StreamException If writing to the stream fails.
48+
*/
49+
public function write(string $data): Promise
50+
{
51+
return $this->emitter->emit($data);
52+
}
53+
54+
/**
55+
* Marks the stream as no longer writable. Optionally writes a final data chunk before. Note that this is not the
56+
* same as forcefully closing the stream. This method waits for all pending writes to complete before closing the
57+
* stream. Socket streams implementing this interface should only close the writable side of the stream.
58+
*
59+
* @param string $finalData Bytes to write.
60+
*
61+
* @return Promise Succeeds once the data has been successfully written to the stream.
62+
*
63+
* @throws ClosedException If the stream has already been closed.
64+
* @throws StreamException If writing to the stream fails.
65+
*/
66+
public function end(string $finalData = ''): Promise
67+
{
68+
$promise = $this->emitter->emit($finalData);
69+
$promise->onResolve(function (): void {
70+
$this->emitter->complete();
71+
});
72+
return $promise;
73+
}
74+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace Phpactor\LanguageServer\Tests\Integration\Middleware\Handlers;
4+
5+
use Amp\Promise;
6+
use Generator;
7+
use Phpactor\LanguageServer\Adapter\Psr\AggregateEventDispatcher;
8+
use Phpactor\LanguageServer\Core\Handler\Handlers;
9+
use Phpactor\LanguageServer\Core\Rpc\RawMessage;
10+
use Phpactor\LanguageServer\Core\Server\Parser\LspMessageReader;
11+
use Phpactor\LanguageServer\Core\Server\Stream\DuplexStream;
12+
use Phpactor\LanguageServer\Core\Server\Transmitter\LspMessageFormatter;
13+
use Phpactor\LanguageServer\Core\Workspace\Workspace;
14+
use Phpactor\LanguageServer\Handler\TextDocument\FormattingHandler;
15+
use Phpactor\LanguageServer\Handler\TextDocument\TextDocumentHandler;
16+
use Phpactor\LanguageServer\Listener\WorkspaceListener;
17+
use Phpactor\LanguageServer\Test\ProtocolFactory;
18+
use Phpactor\LanguageServer\Tests\Unit\Handler\TextDocument\TestFormatter;
19+
use Phpactor\LanguageServer\Core\Rpc\RequestMessage;
20+
use PHPUnit\Framework\Assert;
21+
22+
class FormattingHandlersTest extends HandlersTestCase
23+
{
24+
/**
25+
* @return Generator<Promise<string>>
26+
*/
27+
public function testDispatchesFormattingRequest(): Generator
28+
{
29+
[$server, $stream] = yield $this->startServer($this->createHandlers());
30+
31+
$formatter = new LspMessageFormatter();
32+
$reader = new LspMessageReader($stream);
33+
34+
$response = yield $this->dispatchRequest(
35+
$stream,
36+
$formatter,
37+
$reader,
38+
new RequestMessage(1, 'textDocument/didOpen', [
39+
'textDocument' => [
40+
'uri' => 'file://foobar',
41+
'languageId' => 'php',
42+
'version' => 1,
43+
'text' => 'barfoo',
44+
],
45+
]),
46+
);
47+
48+
Assert::assertArrayNotHasKey('error', $response->body());
49+
50+
$response = yield $this->dispatchRequest(
51+
$stream,
52+
$formatter,
53+
$reader,
54+
new RequestMessage(1, 'textDocument/formatting', [
55+
'textDocument' => [
56+
'uri' => 'file://foobar',
57+
],
58+
'options' => [
59+
'tabSize' => 4,
60+
'insertSpaces' => true,
61+
],
62+
]),
63+
);
64+
65+
Assert::assertArrayNotHasKey('error', $response->body());
66+
67+
$server->shutdown();
68+
}
69+
70+
/**
71+
* @return Promise<RawMessage>
72+
*/
73+
protected function dispatchRequest(
74+
DuplexStream $stream,
75+
LspMessageFormatter $formatter,
76+
LspMessageReader $reader,
77+
RequestMessage $request,
78+
): Promise {
79+
return \Amp\call(function () use ($request, $stream, $formatter, $reader) {
80+
$message = $formatter->format($request);
81+
yield $stream->write($message);
82+
return yield $reader->wait();
83+
});
84+
}
85+
86+
private function createHandlers(): Handlers
87+
{
88+
$workspace = new Workspace();
89+
$formatter = new TestFormatter(
90+
ProtocolFactory::textEdit(0, 0, 0, 0, 'Hello'),
91+
);
92+
93+
$dispatcher = new AggregateEventDispatcher(
94+
new WorkspaceListener($workspace),
95+
);
96+
97+
return new Handlers(
98+
new TextDocumentHandler($dispatcher),
99+
new FormattingHandler($workspace, $formatter),
100+
);
101+
}
102+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
namespace Phpactor\LanguageServer\Tests\Integration\Middleware\Handlers;
4+
5+
use Amp\Loop;
6+
use Amp\PHPUnit\AsyncTestCase;
7+
use Amp\Promise;
8+
use Phpactor\LanguageServer\Core\Dispatcher\ArgumentResolver\ChainArgumentResolver;
9+
use Phpactor\LanguageServer\Core\Dispatcher\ArgumentResolver\LanguageSeverProtocolParamsResolver;
10+
use Phpactor\LanguageServer\Core\Dispatcher\ArgumentResolver\PassThroughArgumentResolver;
11+
use Phpactor\LanguageServer\Core\Dispatcher\Dispatcher\MiddlewareDispatcher;
12+
use Phpactor\LanguageServer\Core\Dispatcher\DispatcherFactory;
13+
use Phpactor\LanguageServer\Core\Handler\HandlerMethodRunner;
14+
use Phpactor\LanguageServer\Core\Handler\Handlers;
15+
use Phpactor\LanguageServer\Middleware\CancellationMiddleware;
16+
use Phpactor\LanguageServer\Middleware\ErrorHandlingMiddleware;
17+
use Phpactor\LanguageServer\Middleware\HandlerMiddleware;
18+
use Phpactor\LanguageServer\Test\ChannelStream;
19+
use Phpactor\LanguageServer\Core\Dispatcher\Factory\ClosureDispatcherFactory;
20+
use Phpactor\LanguageServer\Core\Server\Initializer\PredefinedInitializer;
21+
use Phpactor\LanguageServer\Core\Server\LanguageServer;
22+
use Phpactor\LanguageServer\Core\Server\StreamProvider\ResourceStreamProvider;
23+
use Phpactor\LanguageServer\Core\Server\Stream\ResourceDuplexStream;
24+
use Psr\Log\LoggerInterface;
25+
26+
abstract class HandlersTestCase extends AsyncTestCase
27+
{
28+
/**
29+
* @param Handlers $handlers The handlers to be registered with the server
30+
* @param int $lifetime The server will be shut down after this amount of time in milliseconds
31+
* @return Promise<array{LanguageServer, ChannelStream}>
32+
*/
33+
protected function startServer(Handlers $handlers, int $lifetime = 10): Promise
34+
{
35+
return \Amp\call(function () use ($handlers, $lifetime) {
36+
$requests = new ChannelStream();
37+
$responses = new ChannelStream();
38+
39+
$serverStream = new ResourceDuplexStream($requests, $responses);
40+
$clientStream = new ResourceDuplexStream($responses, $requests);
41+
42+
$logger = $this->createMock(LoggerInterface::class);
43+
$server = new LanguageServer(
44+
$this->createDispatcherFactory($handlers),
45+
$logger,
46+
new ResourceStreamProvider($serverStream, $logger),
47+
new PredefinedInitializer()
48+
);
49+
50+
Loop::delay($lifetime, function () use ($server) {
51+
yield $server->shutdown();
52+
});
53+
54+
yield $server->start();
55+
56+
return [$server, $clientStream];
57+
});
58+
}
59+
60+
private function createDispatcherFactory(Handlers $handlers): DispatcherFactory
61+
{
62+
$logger = $this->createMock(LoggerInterface::class);
63+
64+
$runner = new HandlerMethodRunner(
65+
$handlers,
66+
new ChainArgumentResolver(
67+
new LanguageSeverProtocolParamsResolver(),
68+
new PassThroughArgumentResolver(),
69+
),
70+
);
71+
72+
return new ClosureDispatcherFactory(
73+
fn () => new MiddlewareDispatcher(
74+
new ErrorHandlingMiddleware($logger),
75+
new CancellationMiddleware($runner),
76+
new HandlerMiddleware($runner),
77+
),
78+
);
79+
}
80+
}

0 commit comments

Comments
 (0)