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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 0.5.1 — 2026-08-20

- `PROPERTY_DB` with a non-`redis` URI scheme is now a configuration error
instead of a directory named after the scheme. Only an exact `redis://`
prefix was recognised, so a `rediss://` typo — or any other scheme — fell
through to `FilesystemCorpus` and silently wrote the corpus to a directory
nobody reads, exactly the "silent fall back to the filesystem" the design
forbids. Scheme matching is now case-insensitive (`Redis://` is a shared
corpus) and the error names the scheme but not the DSN, which may carry
credentials. A path with no scheme is unchanged.

## 0.5.0 — 2026-08-16

- Added fluent `auto()`: a generator is derived from the property closure's
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,9 @@ PROPERTY_DB=redis://redis:6379/suite-a: vendor/bin/phpunit # shared server, o
A directory remembers a counterexample for whoever owns it — in CI, a machine
deleted when the job ends. The Redis form is the same corpus, in the same
document, shared. It needs `ext-redis` or `predis/predis`; neither installed is
an error rather than a silent fall back to the filesystem.
an error rather than a silent fall back to the filesystem. A `PROPERTY_DB` with
any other scheme — a `rediss://` typo, another backend — is likewise an error,
never a directory named after the scheme.

The corpus format is exactly the one `rasuvaeff/property-testing` 2.8 wrote —
a corpus recorded under Testo (or under 2.x) replays here and vice versa. On
Expand Down
4 changes: 3 additions & 1 deletion README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,9 @@ PROPERTY_DB=redis://redis:6379/suite-a: vendor/bin/phpunit # общий се
Каталог помнит контрпример для того, кто им владеет, — в CI это машина,
которую удаляют вместе с job'ом. Redis-форма — тот же корпус в том же
документе, но общий. Нужен `ext-redis` или `predis/predis`; отсутствие обоих —
ошибка, а не тихий откат на файловую систему.
ошибка, а не тихий откат на файловую систему. `PROPERTY_DB` с любой другой
схемой — опечатка `rediss://`, другой бэкенд — тоже ошибка, а не каталог с
именем схемы.

Формат корпуса — ровно тот, что писал `rasuvaeff/property-testing` 2.8:
корпус, записанный под Testo (или под 2.x), реплеится здесь, и наоборот. При
Expand Down
19 changes: 17 additions & 2 deletions src/PhpUnit/CorpusFromEnv.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@
*/
final class CorpusFromEnv
{
private const string SCHEME = 'redis://';
/**
* A leading URI scheme: `redis` in `redis://host`. A value without one is
* a directory path; a value with a scheme that is not `redis` is a typo
* (`rediss://`, `Redis://`) or the wrong backend — never a directory.
*/
private const string SCHEME_PATTERN = '#^([a-zA-Z][a-zA-Z0-9+.\-]*)://#';

private function __construct()
{
Expand All @@ -48,10 +53,20 @@ public static function resolve(): ?Corpus
return null;
}

if (!str_starts_with($dsn, self::SCHEME)) {
if (preg_match(self::SCHEME_PATTERN, $dsn, $matches) !== 1) {
return new FilesystemCorpus($dsn);
}

if (strtolower($matches[1]) !== 'redis') {
// Not a directory: a suite told to share its corpus, quietly
// writing to a directory nobody reads, is worse than one that
// stops. The DSN itself is not echoed — it may carry credentials.
throw new \InvalidArgumentException(sprintf(
'PROPERTY_DB uses an unsupported scheme "%s://"; use redis:// for a shared corpus or a plain directory path for a local one',
$matches[1],
));
}

$parsed = RedisDsn::parse($dsn);

return new RedisCorpus(self::client($parsed, $dsn), $parsed->prefix);
Expand Down
36 changes: 36 additions & 0 deletions tests/CorpusFromEnvTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,40 @@ public function testAnUnusableDsnSurfacesAsAConfigurationError(): void
$restore();
}
}

/**
* A scheme other than `redis` — a `rediss://` typo, the wrong backend — is
* a configuration error, never a directory named `rediss:`. The message
* names the scheme but not the DSN, which may carry credentials.
*/
public function testAMistypedSchemeErrorsInsteadOfWritingToADirectory(): void
{
$restore = Env::set('PROPERTY_DB', 'rediss://127.0.0.1:6379/suite');

try {
CorpusFromEnv::resolve();

self::fail('expected an InvalidArgumentException');
} catch (\InvalidArgumentException $e) {
self::assertStringContainsString('unsupported scheme "rediss://"', $e->getMessage());
self::assertStringNotContainsString('127.0.0.1', $e->getMessage());
} finally {
$restore();
}
}

/**
* URI schemes are case-insensitive, so `Redis://` is still a shared corpus,
* not a directory the previous exact-string check would have created.
*/
public function testTheRedisSchemeIsMatchedCaseInsensitively(): void
{
$restore = Env::set('PROPERTY_DB', 'Redis://127.0.0.1:6399/suite:');

try {
self::assertInstanceOf(RedisCorpus::class, CorpusFromEnv::resolve());
} finally {
$restore();
}
}
}
Loading