Skip to content
Open
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: 6 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,12 @@ jobs:
echo "$OUTPUT"
../bashunit assert contains 'Composer packages changed (test/logger); re-analysing the files depending on them and the files with errors.' "$OUTPUT"
../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT"
- script: |
cd e2e/result-cache-deterministic-order
composer install
../../bin/phpstan analyse
OUTPUT=$(php assert-sorted.php tmp/resultCache.php 2>&1) || { echo "$OUTPUT"; exit 1; }
echo "$OUTPUT"
- script: |
cd e2e/result-cache-path-repository
composer install
Expand Down
2 changes: 2 additions & 0 deletions e2e/result-cache-deterministic-order/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/vendor
/composer.lock
104 changes: 104 additions & 0 deletions e2e/result-cache-deterministic-order/assert-sorted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php declare(strict_types = 1);

// Fails if an entry section of the result cache, or the packages of a file, are not sorted.

$path = $argv[1] ?? null;
if ($path === null || !is_file($path)) {
fwrite(STDERR, sprintf("Result cache %s does not exist.\n", $path ?? '<missing argument>'));
exit(1);
}

$handle = fopen($path, 'r');
if ($handle === false) {
fwrite(STDERR, sprintf("Cannot open %s.\n", $path));
exit(1);
}

fgets($handle); // the PHP prefix line that keeps the file inert when included

$unsorted = [];
$sections = 0;
$packageLists = [];
while (($header = fgets($handle)) !== false) {
$header = rtrim($header, "\n");
if ($header === '') {
continue;
}

$parts = explode(' ', $header, 2);
if (count($parts) !== 2) {
fwrite(STDERR, sprintf("Malformed frame header \"%s\".\n", $header));
exit(1);
}

[$name, $size] = $parts;
if (!str_ends_with($name, '*')) {
// a single value, or (with #) payloads described by the index frame before them
if ((int) $size > 0) {
fread($handle, (int) $size);
}

continue;
}

$name = substr($name, 0, -1);
$keys = [];
for ($i = 0; $i < (int) $size; $i++) {
[$keyLength, $valueLength] = array_map('intval', explode(' ', rtrim((string) fgets($handle), "\n"), 2));
$key = unserialize((string) fread($handle, $keyLength));
$value = unserialize((string) fread($handle, $valueLength));
$keys[] = $key;
if ($name !== 'packageDependencies') {
continue;
}

$packageLists[$key] = $value;
}

$sections++;
$sorted = $keys;
sort($sorted);
if ($keys === $sorted) {
continue;
}

$unsorted[$name] = $keys;
}

fclose($handle);

if ($sections === 0) {
fwrite(STDERR, "The result cache has no entry sections, so nothing was checked.\n");
exit(1);
}

if ($packageLists === []) {
fwrite(STDERR, "The result cache recorded no package dependencies, so nothing was checked.\n");
exit(1);
}

$problems = [];
foreach ($unsorted as $name => $keys) {
$problems[] = sprintf('Section "%s" is not in key order: %s', $name, implode(', ', array_map(static fn (int|string $key): string => basename((string) $key), $keys)));
}

foreach ($packageLists as $file => $packages) {
$sortedPackages = $packages;
sort($sortedPackages, SORT_STRING);
if ($packages === $sortedPackages) {
continue;
}

$problems[] = sprintf('The packages of %s are not in order: %s', basename($file), implode(', ', $packages));
}

if ($problems !== []) {
fwrite(STDERR, implode("\n", $problems) . "\n");
exit(1);
}

echo sprintf(
"All %d entry sections of the result cache are in key order, and so are the packages of all %d files that have package dependencies.\n",
$sections,
count($packageLists),
);
26 changes: 26 additions & 0 deletions e2e/result-cache-deterministic-order/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "phpstan/result-cache-deterministic-order-e2e",
"repositories": [
{
"type": "path",
"url": "./logger",
"options": {
"symlink": false
}
},
{
"type": "path",
"url": "./mailer",
"options": {
"symlink": false
}
}
],
"require": {
"test/logger": "*",
"test/mailer": "*"
},
"autoload": {
"classmap": ["src"]
}
}
1 change: 1 addition & 0 deletions e2e/result-cache-deterministic-order/logger/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "name": "test/logger", "version": "1.0.0", "autoload": { "classmap": ["src"] } }
12 changes: 12 additions & 0 deletions e2e/result-cache-deterministic-order/logger/src/Logger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Test\Logger;

class Logger
{

public function info(string $message): void
{
}

}
1 change: 1 addition & 0 deletions e2e/result-cache-deterministic-order/mailer/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "name": "test/mailer", "version": "1.0.0", "autoload": { "classmap": ["src"] } }
12 changes: 12 additions & 0 deletions e2e/result-cache-deterministic-order/mailer/src/Mailer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Test\Mailer;

class Mailer
{

public function send(string $message): void
{
}

}
10 changes: 10 additions & 0 deletions e2e/result-cache-deterministic-order/phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
parameters:
level: 5
tmpDir: tmp
paths:
- src
parallel:
# two jobs, so the files reach the main process interleaved
jobSize: 2
maximumNumberOfProcesses: 4
minimumNumberOfJobsPerProcess: 1
22 changes: 22 additions & 0 deletions e2e/result-cache-deterministic-order/src/F1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace ResultCacheDeterministicOrderE2E;

use Test\Logger\Logger;
use Test\Mailer\Mailer;

// without the sort, this file records test/mailer before test/logger
class F1
{

public function __construct(private Logger $logger, private Mailer $mailer)
{
}

public function doFoo(): void
{
$this->logger->info('hello');
$this->mailer->send('hello');
}

}
22 changes: 22 additions & 0 deletions e2e/result-cache-deterministic-order/src/F2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace ResultCacheDeterministicOrderE2E;

use Test\Logger\Logger;
use Test\Mailer\Mailer;

// without the sort, this file records test/mailer before test/logger
class F2
{

public function __construct(private Logger $logger, private Mailer $mailer)
{
}

public function doFoo(): void
{
$this->logger->info('hello');
$this->mailer->send('hello');
}

}
22 changes: 22 additions & 0 deletions e2e/result-cache-deterministic-order/src/F3.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace ResultCacheDeterministicOrderE2E;

use Test\Logger\Logger;
use Test\Mailer\Mailer;

// without the sort, this file records test/mailer before test/logger
class F3
{

public function __construct(private Logger $logger, private Mailer $mailer)
{
}

public function doFoo(): void
{
$this->logger->info('hello');
$this->mailer->send('hello');
}

}
22 changes: 22 additions & 0 deletions e2e/result-cache-deterministic-order/src/F4.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace ResultCacheDeterministicOrderE2E;

use Test\Logger\Logger;
use Test\Mailer\Mailer;

// without the sort, this file records test/mailer before test/logger
class F4
{

public function __construct(private Logger $logger, private Mailer $mailer)
{
}

public function doFoo(): void
{
$this->logger->info('hello');
$this->mailer->send('hello');
}

}
2 changes: 2 additions & 0 deletions e2e/result-cache-deterministic-order/tmp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.*
5 changes: 5 additions & 0 deletions src/Analyser/ResultCache/ResultCacheManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -2032,6 +2032,11 @@ private function save(

ksort($exportedNodes);
ksort($collectedDataRuleResults);
ksort($packageDependencies);
foreach ($packageDependencies as $file => $packages) {
sort($packages);
$packageDependencies[$file] = $packages;
}

// The only point where the StubFilesExtensions may run: the analysis is over, bootstrapFiles
// have been executed, so the extensions can rely on them. restore() reads these hashes back
Expand Down
Loading