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
34 changes: 34 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,40 @@ jobs:
echo "$OUTPUT"
../bashunit -a contains 'Result cache restored. 0 files will be reanalysed.' "$OUTPUT"
../bashunit -a not_contains 'metadata do not match' "$OUTPUT"
- script: |
cd e2e/result-cache-truncated
../../bin/phpstan -vvv
# Cut inside the first section's payload: the cache is damaged, not merely stale, so
# it has to be discarded rather than read as far as it goes.
php truncate.php
OUTPUT=$(../../bin/phpstan -vvv 2>&1)
echo "$OUTPUT"
../bashunit -a contains 'an error occurred while loading the cache file' "$OUTPUT"
../bashunit -a contains 'Result cache is saved.' "$OUTPUT"
# And once more inside a section that is read lazily, which is a different code path.
php truncate-lazy.php
OUTPUT=$(../../bin/phpstan -vvv 2>&1)
echo "$OUTPUT"
../bashunit -a contains 'is truncated at entry' "$OUTPUT"
OUTPUT=$(../../bin/phpstan -vvv 2>&1)
echo "$OUTPUT"
../bashunit -a contains 'Result cache restored.' "$OUTPUT"
- script: |
cd e2e/result-cache-stale-objects
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan -vvv")
echo "$OUTPUT"
# Renaming a property inside the cache file is what a cache written by a PHPStan whose
# classes have changed since looks like: the payload does not carry the property the class
# now declares, so reconstructing the object leaves it uninitialized and reading it throws.
php rename-property.php
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan -vvv")
echo "$OUTPUT"
../bashunit -a contains 'could not be read back' "$OUTPUT"
../bashunit -a contains 'Result cache is saved.' "$OUTPUT"
# The cache written in its place is usable again.
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan -vvv")
echo "$OUTPUT"
../bashunit -a contains 'Result cache restored.' "$OUTPUT"
- script: |
cd e2e/bug-14514
composer install
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ e2e/bashunit
/e2e/result-cache-atomic-save/tmp
/e2e/result-cache-moved-tmpdir/tmp-a
/e2e/result-cache-moved-tmpdir/tmp-b
/e2e/result-cache-stale-objects/tmp
/e2e/result-cache-truncated/tmp
5 changes: 5 additions & 0 deletions e2e/result-cache-stale-objects/phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
parameters:
level: 8
tmpDir: tmp
paths:
- src
25 changes: 25 additions & 0 deletions e2e/result-cache-stale-objects/rename-property.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php declare(strict_types = 1);

// A cache written by a PHPStan whose classes have changed since cannot be reconstructed: the payload
// does not carry the property the class now declares, so the object comes back with it uninitialized
// and reading it throws. Renaming one inside the cache file is that shape without needing two
// PHPStan versions installed, and keeping the byte length identical leaves the rest of the payload
// valid - the frame headers count bytes.
$file = __DIR__ . '/tmp/resultCache.php';
$contents = file_get_contents($file);
if ($contents === false) {
throw new RuntimeException('No result cache at ' . $file);
}

// Error::$message, read by transformPaths() while the cached errors are absolutized.
$property = "\0PHPStan\\Analyser\\Error\0message";
if (substr_count($contents, $property) !== 1) {
throw new RuntimeException('Expected exactly one cached Error carrying that property.');
}

$renamed = substr($property, 0, -strlen('message')) . 'messagf';
if (strlen($renamed) !== strlen($property)) {
throw new RuntimeException('The replacement has to keep the byte length.');
}

file_put_contents($file, str_replace($property, $renamed, $contents));
13 changes: 13 additions & 0 deletions e2e/result-cache-stale-objects/src/Foo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php declare(strict_types = 1);

namespace ResultCacheE2EStaleObjects;

class Foo
{

public function doFoo(): int
{
return 'not an int';
}

}
2 changes: 2 additions & 0 deletions e2e/result-cache-stale-objects/tmp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
5 changes: 5 additions & 0 deletions e2e/result-cache-truncated/phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
parameters:
level: 8
tmpDir: tmp
paths:
- src
13 changes: 13 additions & 0 deletions e2e/result-cache-truncated/src/Bar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php declare(strict_types = 1);

namespace TestResultCacheTruncated;

class Bar
{

public function doBar(): string
{
return 'bar';
}

}
13 changes: 13 additions & 0 deletions e2e/result-cache-truncated/src/Foo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php declare(strict_types = 1);

namespace TestResultCacheTruncated;

class Foo
{

public function doFoo(Bar $bar): string
{
return $bar->doBar();
}

}
13 changes: 13 additions & 0 deletions e2e/result-cache-truncated/truncate-lazy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php declare(strict_types = 1);

// The same shape as truncate.php, but cutting far enough in to land inside one of the sections
// restore() reads lazily. Those are walked rather than decoded when the cache is opened, and
// fseek() past the end of a file succeeds, so only comparing the position with the file size
// catches this - otherwise the section would be handed out as a callback pointing past the end.
$file = __DIR__ . '/tmp/resultCache.php';
$contents = file_get_contents($file);
if ($contents === false) {
throw new RuntimeException('No result cache at ' . $file);
}

file_put_contents($file, substr($contents, 0, (int) (strlen($contents) * 0.9)));
13 changes: 13 additions & 0 deletions e2e/result-cache-truncated/truncate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php declare(strict_types = 1);

// A cache file that is this format but incomplete: a CI cache artifact that was archived or
// restored half way, a disk that filled up under something else, a copy that was interrupted.
// Cutting inside the first section's payload is the shape that would otherwise be read as a
// half-populated cache rather than a damaged one.
$file = __DIR__ . '/tmp/resultCache.php';
$contents = file_get_contents($file);
if ($contents === false) {
throw new RuntimeException('No result cache at ' . $file);
}

file_put_contents($file, substr($contents, 0, 200));
Loading
Loading