diff --git a/packages/reprint-client/src/lib/pull/class-remote-index-reader.php b/packages/reprint-client/src/lib/pull/class-remote-index-reader.php index 97992ad2c..4ee8006c0 100644 --- a/packages/reprint-client/src/lib/pull/class-remote-index-reader.php +++ b/packages/reprint-client/src/lib/pull/class-remote-index-reader.php @@ -148,8 +148,8 @@ public function open(): void * @type int $size Size in bytes. * @type string $type `file`, `dir`, or `link`. * } - * @throws RuntimeException When a non-blank line is not a decodable index - * entry. + * @throws RuntimeException When the index cannot be read or a non-blank + * line is not a decodable entry. * @throws InvalidArgumentException When the decoded path is not a valid * remote absolute path. */ @@ -164,6 +164,11 @@ public function next_entry(): ?array } return self::decode_index_line($remote_index_json_line); } + if (!feof($this->remote_index_file_handle)) { + throw new RuntimeException( + "Failed to read the remote index file: {$this->remote_index_path}" + ); + } return null; } diff --git a/tests/Import/RemoteIndexReadFailureStream.php b/tests/Import/RemoteIndexReadFailureStream.php new file mode 100644 index 000000000..85fc10fab --- /dev/null +++ b/tests/Import/RemoteIndexReadFailureStream.php @@ -0,0 +1,47 @@ + 0100644, 'size' => 1]; + } + + /** @return array{mode:int,size:int} */ + public function url_stat(string $path, int $flags): array + { + unset($path, $flags); + return ['mode' => 0100644, 'size' => 1]; + } +} diff --git a/tests/Import/RemoteIndexReaderTest.php b/tests/Import/RemoteIndexReaderTest.php index 3dc5d6b67..f347278c9 100644 --- a/tests/Import/RemoteIndexReaderTest.php +++ b/tests/Import/RemoteIndexReaderTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; require_once __DIR__ . '/../../packages/reprint-client/bin/reprint-client'; +require_once __DIR__ . '/RemoteIndexReadFailureStream.php'; final class RemoteIndexReaderTest extends TestCase { @@ -147,6 +148,31 @@ public function testLineDecoderRejectsABlankRecord(): void \RemoteIndexReader::decode_index_line("\n"); } + public function testReadFailureDoesNotLookLikeEndOfFile(): void + { + $scheme = 'failingremoteindex'; + $this->assertTrue( + stream_wrapper_register( + $scheme, + RemoteIndexReadFailureStream::class + ) + ); + $reader = new \RemoteIndexReader($scheme . '://index'); + try { + $reader->open(); + $reader->next_entry(); + $this->fail('Expected the remote index read failure to throw.'); + } catch (\RuntimeException $exception) { + $this->assertStringContainsString( + 'Failed to read the remote index file', + $exception->getMessage() + ); + } finally { + $reader->close(); + stream_wrapper_unregister($scheme); + } + } + private function indexLine( string $path, int $ctime,