schneespur/vendor/league/csv/src/StreamTest.php
Michael ee3dbba6cc Initial release v1.0.0
Schneespur — Open-source winter service documentation software (PWA + Admin).
GPS tracking via OwnTracks, weather data, photo evidence, and legally
compliant service records for winter maintenance operators.

License: AGPL-3.0-or-later
2026-05-17 13:33:51 +00:00

308 lines
8.3 KiB
PHP

<?php
/**
* League.Csv (https://csv.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Csv;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
use SplFileObject;
use TypeError;
use function feof;
use function fopen;
use function fputcsv;
use function fread;
use function fseek;
use function ftell;
use function fwrite;
use function in_array;
use function stream_context_create;
use function stream_context_get_options;
use function stream_get_wrappers;
use function stream_wrapper_register;
use function stream_wrapper_unregister;
use const STREAM_FILTER_READ;
#[Group('csv')]
final class StreamTest extends TestCase
{
protected function setUp(): void
{
stream_wrapper_register(StreamWrapper::PROTOCOL, StreamWrapper::class);
}
protected function tearDown(): void
{
stream_wrapper_unregister(StreamWrapper::PROTOCOL);
}
public function testCloningIsForbidden(): void
{
$this->expectException(UnavailableStream::class);
clone Stream::from(STDOUT);
}
public function testCreateStreamWithInvalidParameter(): void
{
$this->expectException(TypeError::class);
Stream::createFromResource(__DIR__.'/../test_files/foo.csv'); /* @phpstan-ignore-line */
}
public function testCreateStreamWithWrongResourceType(): void
{
$this->expectException(TypeError::class);
/** @var resource $resource */
$resource = stream_filter_append(STDOUT, 'string.rot13', STREAM_FILTER_WRITE);
Stream::from($resource);
}
public function testCreateStreamFromPath(): void
{
$path = 'no/such/file.csv';
$this->expectException(UnavailableStream::class);
$this->expectExceptionMessage('`'.$path.'`: failed to open stream: No such file or directory');
Stream::from($path);
}
public function testCreateStreamFromPathWithContext(): void
{
/** @var resource $fp */
$fp = fopen('php://temp', 'r+');
$expected = [
['john', 'doe', 'john.doe@example.com'],
['john', 'doe', 'john.doe@example.com'],
];
foreach ($expected as $row) {
fputcsv($fp, $row, escape: '');
}
$stream = Stream::from(
StreamWrapper::PROTOCOL.'://stream',
'r+',
stream_context_create([StreamWrapper::PROTOCOL => ['stream' => $fp]])
);
$stream->setFlags(SplFileObject::READ_AHEAD | SplFileObject::READ_CSV);
$stream->rewind();
self::assertIsArray($stream->current());
}
#[DataProvider('fputcsvProvider')]
public function testfputcsv(string $delimiter, string $enclosure, string $escape): void
{
$this->expectException(InvalidArgument::class);
$stream = Stream::from(STDOUT);
$stream->fputcsv(['john', 'doe', 'john.doe@example.com'], $delimiter, $enclosure, $escape);
}
public static function fputcsvProvider(): array
{
return [
'wrong delimiter' => ['toto', '"', '\\'],
'wrong enclosure' => [',', 'é', '\\'],
'wrong escape' => [',', '"', 'à'],
];
}
public function testVarDump(): void
{
$stream = Stream::from(STDOUT);
$debugInfo = $stream->__debugInfo();
self::assertArrayHasKey('delimiter', $debugInfo);
self::assertArrayHasKey('enclosure', $debugInfo);
self::assertArrayHasKey('escape', $debugInfo);
self::assertArrayHasKey('stream_filters', $debugInfo);
}
public function testSeekThrowsException(): void
{
$this->expectException(InvalidArgument::class);
$stream = Stream::from(STDOUT);
$stream->seek(-1);
}
public function testFSeekThrowsExceptionOnNonSeakableResource(): void
{
$this->expectException(UnavailableFeature::class);
$stream = Stream::from(STDOUT);
$stream->fputcsv(['foo', 'bar'], escape: '');
$stream->fseek(-1);
}
public function testSeek(): void
{
$doc = Stream::from(__DIR__.'/../test_files/prenoms.csv');
$doc->setCsvControl(';');
$doc->setFlags(SplFileObject::READ_CSV);
$doc->seek(1);
self::assertSame(['Aaron', '55', 'M', '2004'], $doc->current());
}
public function testSeekToPositionZero(): void
{
$doc = Stream::fromString();
$doc->seek(0);
self::assertSame(0, $doc->key());
}
public function testRewindThrowsException(): void
{
$this->expectException(UnavailableFeature::class);
$stream = Stream::from(STDIN);
$stream->rewind();
}
public function testCreateStreamWithNonSeekableStream(): void
{
$this->expectException(UnavailableFeature::class);
$stream = Stream::from(STDIN);
$stream->seek(3);
}
public function testCsvControl(): void
{
$doc = Stream::fromString('foo,bar');
self::assertSame([',', '"', '\\'], $doc->getCsvControl());
$expected = [';', '|', '"'];
$doc->setCsvControl(...$expected);
self::assertSame($expected, $doc->getCsvControl());
$this->expectException(InvalidArgument::class);
$doc->setCsvControl(...['foo']);
}
public function testCsvControlAcceptsEmptyEscapeString(): void
{
$doc = Stream::fromString();
$expected = [';', '|', ''];
$doc->setCsvControl(...$expected);
self::assertSame($expected, $doc->getCsvControl());
}
public function testAppendStreamFilterThrowsException(): void
{
$filtername = 'foo.bar';
$this->expectException(InvalidArgument::class);
$this->expectExceptionMessage('unable to locate filter `'.$filtername.'`');
$stream = Stream::from('php://temp', 'r+');
$stream->appendFilter($filtername, STREAM_FILTER_READ);
}
public function testIterateOverLines(): void
{
$text = <<<TEXT
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Duis nec sapien felis, ac sodales nisl.
Nulla vitae magna vitae purus aliquet consequat.
TEXT;
$newText = '';
$file = Stream::fromString($text);
$file->setMaxLineLen(20);
foreach ($file as $line) {
$newText .= $line."\n";
}
self::assertStringContainsString('Lorem ipsum dolor s', $newText);
self::assertSame(20, $file->getMaxLineLen());
}
}
final class StreamWrapper
{
public const PROTOCOL = 'leaguetest';
/**
* @var resource
*/
public $context;
/**
* @var resource
*/
private $stream;
public static function register(): void
{
if (!in_array(self::PROTOCOL, stream_get_wrappers(), true)) {
stream_wrapper_register(self::PROTOCOL, __CLASS__);
}
}
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path = null): bool
{
$options = stream_context_get_options($this->context);
if (!isset($options[self::PROTOCOL]['stream'])) {
return false;
}
$this->stream = $options[self::PROTOCOL]['stream'];
return true;
}
/**
* @param int<1, max> $count
*/
public function stream_read(int $count): string|false
{
return fread($this->stream, $count);
}
public function stream_write(string $data): int|false
{
return fwrite($this->stream, $data);
}
public function stream_tell(): int|false
{
return ftell($this->stream);
}
public function stream_eof(): bool
{
return feof($this->stream);
}
public function stream_seek(int $offset, int $whence): bool
{
fseek($this->stream, $whence);
return true;
}
public function stream_stat(): array
{
return [
'dev' => 0,
'ino' => 0,
'mode' => 33206,
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => 0,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => 0,
'blocks' => 0,
];
}
}