Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion tests/EndToEnd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ A script file declares `namespace Firehed\PhpLsp\Tests\EndToEnd;` and names type
Run `composer install` in it before the suite.
Each script runs against a throwaway copy, which becomes the server's working directory.
- `capabilities` is what the client declares in `initialize`.
- `Open` and `Type` are what a person does in the editor.
Declare watched-file support in any script that changes files on disk.
- `Open`, `Type`, and `Close` are what a person does in the editor.
Open only files a person would have open; the server finds the rest on its own.
- `Copy` and `Delete` change the disk as another program would, and the server is not told.
`Copy` puts a prepared variant in place; variants live outside the autoload paths, such as `DiskChange/` in the fixture project.
- `ReportChanges` is the client's file watcher reporting what changed, in one notification.
- `Definition` and `Complete` are requests.
Each decodes the answer, failing on a malformed one, and checks it against `expect:`: one expectation, or a list that must all hold.
- Definition expectations are `LandsOn(file, line, column)`, where the column is optional, and `NoAnswer()`.
Expand Down
44 changes: 44 additions & 0 deletions tests/EndToEnd/Session/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Firehed\PhpLsp\Tests\EndToEnd\Client\LspClient;
use Firehed\PhpLsp\Tests\EndToEnd\Client\ServerMessage;
use Firehed\PhpLsp\Tests\EndToEnd\Marker\MarkerInterface;
use Firehed\PhpLsp\Tests\EndToEnd\Step\FileChange;
use PHPUnit\Framework\Assert;
use stdClass;

Expand Down Expand Up @@ -54,6 +55,34 @@ public function ask(Feature $feature, string $file, MarkerInterface $at): Server
return $response;
}

public function close(string $file): void
{
assert(array_key_exists($file, $this->buffers), "Not open: {$file}");
unset($this->buffers[$file], $this->versions[$file]);

$this->client->notify('textDocument/didClose', (object) [
'textDocument' => ['uri' => $this->uri($file)],
]);
}

/**
* Writes a file on disk as another process would. Nothing is sent.
*/
public function copy(string $from, string $to): void
{
$copied = copy("{$this->projectRoot}/{$from}", "{$this->projectRoot}/{$to}");
assert($copied, "Cannot copy {$from} to {$to}");
}

/**
* Deletes a file on disk as another process would. Nothing is sent.
*/
public function delete(string $file): void
{
$deleted = unlink("{$this->projectRoot}/{$file}");
assert($deleted, "Cannot delete {$file}");
}

/**
* @return ServerMessage The response to `shutdown`.
*/
Expand Down Expand Up @@ -91,6 +120,21 @@ public function open(string $file): void
]);
}

/**
* Reports changes on disk, as the client's file watcher would.
*
* @param list<FileChange> $changes
*/
public function reportChanges(array $changes): void
{
$this->client->notify('workspace/didChangeWatchedFiles', (object) [
'changes' => array_map(
fn (FileChange $change): array => ['uri' => $this->uri($change->file), 'type' => $change->type->value],
$changes,
),
]);
}

/**
* @return ServerMessage The response to `initialize`.
*/
Expand Down
22 changes: 22 additions & 0 deletions tests/EndToEnd/Step/Close.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd\Step;

use Firehed\PhpLsp\Tests\EndToEnd\Session\Session;

final readonly class Close implements StepInterface
{
/**
* @param string $file Path relative to the project root. It must be open.
*/
public function __construct(private string $file)
{
}

public function run(Session $session): void
{
$session->close($this->file);
}
}
29 changes: 29 additions & 0 deletions tests/EndToEnd/Step/Copy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd\Step;

use Firehed\PhpLsp\Tests\EndToEnd\Session\Session;

/**
* Replaces or creates a file on disk with a prepared variant, as a checkout
* or a save in another program would. The server is not told.
*/
final readonly class Copy implements StepInterface
{
/**
* @param string $from Path relative to the project root.
* @param string $to Path relative to the project root.
*/
public function __construct(
private string $from,
private string $to,
) {
}

public function run(Session $session): void
{
$session->copy($this->from, $this->to);
}
}
25 changes: 25 additions & 0 deletions tests/EndToEnd/Step/Delete.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd\Step;

use Firehed\PhpLsp\Tests\EndToEnd\Session\Session;

/**
* Deletes a file on disk, as another program would. The server is not told.
*/
final readonly class Delete implements StepInterface
{
/**
* @param string $file Path relative to the project root.
*/
public function __construct(private string $file)
{
}

public function run(Session $session): void
{
$session->delete($this->file);
}
}
20 changes: 20 additions & 0 deletions tests/EndToEnd/Step/FileChange.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd\Step;

/**
* [LSP] workspace/didChangeWatchedFiles, FileEvent.
*/
final readonly class FileChange
{
/**
* @param string $file Path relative to the project root.
*/
public function __construct(
public string $file,
public FileChangeType $type,
) {
}
}
15 changes: 15 additions & 0 deletions tests/EndToEnd/Step/FileChangeType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd\Step;

/**
* [LSP] workspace/didChangeWatchedFiles, FileChangeType.
*/
enum FileChangeType: int
{
case Created = 1;
case Changed = 2;
case Deleted = 3;
}
26 changes: 26 additions & 0 deletions tests/EndToEnd/Step/ReportChanges.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd\Step;

use Firehed\PhpLsp\Tests\EndToEnd\Session\Session;

/**
* The client's file watcher reporting what changed on disk, in one
* notification.
*/
final readonly class ReportChanges implements StepInterface
{
/**
* @param list<FileChange> $changes
*/
public function __construct(private array $changes)
{
}

public function run(Session $session): void
{
$session->reportChanges($this->changes);
}
}
22 changes: 22 additions & 0 deletions tests/EndToEnd/scripts/disk-autoload-files-edit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd;

$consumer = 'src/DiskChange/Consumer.php';
$helpers = 'AutoloadFiles/helpers.php';
$helper = new Marker\SymbolMarker('helper_added');

// Functions in an `autoload.files` entry are found through an index of that
// file, which an edit must refresh.
return new Session\Script(
project: 'tests/Fixtures',
steps: [
new Step\Definition($consumer, $helper, expect: new Expectation\NoAnswer()),
new Step\Copy('DiskChange/helpers.with-added.php', $helpers),
new Step\ReportChanges([new Step\FileChange($helpers, Step\FileChangeType::Changed)]),
new Step\Definition($consumer, $helper, expect: new Expectation\LandsOn($helpers, line: 11)),
],
capabilities: new Session\ClientCapabilities(watchedFilesDynamicRegistration: true),
);
29 changes: 29 additions & 0 deletions tests/EndToEnd/scripts/disk-checkout.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd;

$consumer = 'src/DiskChange/Consumer.php';
$widget = 'src/DiskChange/Widget.php';
$gadget = 'src/DiskChange/Gadget.php';
$greet = new Marker\SymbolMarker('widget_greet');
$spin = new Marker\SymbolMarker('gadget_spin');

// A checkout changes several files, reported together.
return new Session\Script(
project: 'tests/Fixtures',
steps: [
new Step\Definition($consumer, $greet, expect: new Expectation\NoAnswer()),
new Step\Definition($consumer, $spin, expect: new Expectation\NoAnswer()),
new Step\Copy('DiskChange/Widget.with-greet.php', $widget),
new Step\Copy('DiskChange/Gadget.with-spin.php', $gadget),
new Step\ReportChanges([
new Step\FileChange($widget, Step\FileChangeType::Changed),
new Step\FileChange($gadget, Step\FileChangeType::Changed),
]),
new Step\Definition($consumer, $greet, expect: new Expectation\LandsOn($widget, line: 12)),
new Step\Definition($consumer, $spin, expect: new Expectation\LandsOn($gadget, line: 12)),
],
capabilities: new Session\ClientCapabilities(watchedFilesDynamicRegistration: true),
);
25 changes: 25 additions & 0 deletions tests/EndToEnd/scripts/disk-close-rereads.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd;

$consumer = 'src/DiskChange/Consumer.php';
$widget = 'src/DiskChange/Widget.php';
$greet = new Marker\SymbolMarker('widget_greet');

// While a file is open its buffer wins over the disk ([LSP]
// textDocument/didOpen). Closing it means the disk is read again, not the
// answer from before it was opened.
return new Session\Script(
project: 'tests/Fixtures',
steps: [
new Step\Definition($consumer, $greet, expect: new Expectation\NoAnswer()),
new Step\Open($widget),
new Step\Copy('DiskChange/Widget.with-greet.php', $widget),
new Step\Definition($consumer, $greet, expect: new Expectation\NoAnswer()),
new Step\Close($widget),
new Step\Definition($consumer, $greet, expect: new Expectation\LandsOn($widget, line: 12)),
],
capabilities: new Session\ClientCapabilities(watchedFilesDynamicRegistration: true),
);
21 changes: 21 additions & 0 deletions tests/EndToEnd/scripts/disk-created-after-miss.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd;

$consumer = 'src/DiskChange/Consumer.php';
$late = 'src/DiskChange/Late.php';
$newLate = new Marker\SymbolMarker('new_late');

// A class that was missing is not remembered as missing once it is created.
return new Session\Script(
project: 'tests/Fixtures',
steps: [
new Step\Definition($consumer, $newLate, expect: new Expectation\NoAnswer()),
new Step\Copy('DiskChange/Late.php', $late),
new Step\ReportChanges([new Step\FileChange($late, Step\FileChangeType::Created)]),
new Step\Definition($consumer, $newLate, expect: new Expectation\LandsOn($late, line: 11)),
],
capabilities: new Session\ClientCapabilities(watchedFilesDynamicRegistration: true),
);
20 changes: 20 additions & 0 deletions tests/EndToEnd/scripts/disk-deletion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd;

$consumer = 'src/DiskChange/Consumer.php';
$widget = 'src/DiskChange/Widget.php';
$newWidget = new Marker\SymbolMarker('new_widget');

return new Session\Script(
project: 'tests/Fixtures',
steps: [
new Step\Definition($consumer, $newWidget, expect: new Expectation\LandsOn($widget, line: 11)),
new Step\Delete($widget),
new Step\ReportChanges([new Step\FileChange($widget, Step\FileChangeType::Deleted)]),
new Step\Definition($consumer, $newWidget, expect: new Expectation\NoAnswer()),
],
capabilities: new Session\ClientCapabilities(watchedFilesDynamicRegistration: true),
);
21 changes: 21 additions & 0 deletions tests/EndToEnd/scripts/disk-reported-edit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd;

$consumer = 'src/DiskChange/Consumer.php';
$widget = 'src/DiskChange/Widget.php';
$greet = new Marker\SymbolMarker('widget_greet');

// Another program edits a file the editor never opened.
return new Session\Script(
project: 'tests/Fixtures',
steps: [
new Step\Definition($consumer, $greet, expect: new Expectation\NoAnswer()),
new Step\Copy('DiskChange/Widget.with-greet.php', $widget),
new Step\ReportChanges([new Step\FileChange($widget, Step\FileChangeType::Changed)]),
new Step\Definition($consumer, $greet, expect: new Expectation\LandsOn($widget, line: 12)),
],
capabilities: new Session\ClientCapabilities(watchedFilesDynamicRegistration: true),
);
20 changes: 20 additions & 0 deletions tests/EndToEnd/scripts/disk-unreported-autoload-files-edit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Firehed\PhpLsp\Tests\EndToEnd;

$consumer = 'src/DiskChange/Consumer.php';
$helper = new Marker\SymbolMarker('helper_added');

// The index of `autoload.files` entries is rebuilt only when the editor
// reports a change, so an unreported edit is not seen.
return new Session\Script(
project: 'tests/Fixtures',
steps: [
new Step\Definition($consumer, $helper, expect: new Expectation\NoAnswer()),
new Step\Copy('DiskChange/helpers.with-added.php', 'AutoloadFiles/helpers.php'),
new Step\Definition($consumer, $helper, expect: new Expectation\NoAnswer()),
],
capabilities: new Session\ClientCapabilities(watchedFilesDynamicRegistration: true),
);
Loading
Loading