Рецепт: воспроизведение по seed
Фальсифицированное property сообщает seed. Эта страница проходит три способа им воспользоваться и то, как PROPERTY_VERBOSE закрывает разрыв между «какой seed» и «что реально происходило в этом прогоне».
Почему seed воспроизводится точно
Random (тип, из которого тянет каждый генератор) оборачивает object-scoped \Random\Randomizer (MT19937), независимый от глобального состояния mt_rand PHP. Два экземпляра Random, построенные с одним seed, выдают идентичную последовательность draw независимо от того, что ещё в процессе вызывало random-функции между ними — именно это делает сообщённый seed надёжным ключом реплея, а не приблизительной подсказкой. См. Random.
1. Зафиксировать seed в атрибуте
Самый прямой путь, когда падение уже найдено локально:
#[Property(runs: 200, seed: 918273645)]
public function everyIntIsEven(int $n): void
{
Assert::true($n % 2 === 0);
}С зафиксированным seed это property всегда генерирует одну и ту же последовательность — полезно, пока вы активно отлаживаете конкретное падение, но не забудьте убрать его перед коммитом, если только вы не фиксируете регрессию намеренно (для этого лучше подходят явные примеры или корпус регрессий — оба спроектированы, чтобы сохраняться, тогда как зафиксированный в атрибуте seed легко забыть и оставить, тихо сузив property до одного входа навсегда).
2. Переопределить через переменную окружения, без правки кода
PROPERTY_SEED задаёт seed для каждого property, чей атрибут не фиксирует свой — зафиксированный seed: всё равно побеждает переменную окружения:
docker run --rm -v "$PWD":/app -w /app -e PROPERTY_SEED=918273645 \
composer:2 vendor/bin/testoУдобно в CI, когда ночной прогон сообщает seed и вы хотите реплеить его без правки тестового файла, или при воспроизведении локально без оставленного диффа.
3. Смотреть вживую через PROPERTY_VERBOSE
Seed реплеит входы; PROPERTY_VERBOSE показывает, что property с ними делало — сгенерированные аргументы каждого прогона плюс по строке на каждый принятый шаг shrink при падении:
docker run --rm -v "$PWD":/app -w /app -e PROPERTY_SEED=918273645 \
-e PROPERTY_VERBOSE=1 composer:2 vendor/bin/testorun 1: n=412
run 2: n=847
shrink step 1: n=847 -> 424
shrink step 2: n=424 -> 213
shrink step 3: n=213 -> 107
shrink step 4: n=107 -> 1Это инструмент для «шринкнутое значение выглядит странно — как шринкер туда пришёл» или «property часто отбрасывает попытки — какие входы отсеиваются до того, как я вообще увижу падение». Он намеренно шумный (каждый прогон, не только падающий), так что используйте его вместе с зафиксированным/env seed, а не над неограниченным полным сьютом.
Seed живёт только в рамках одной мажорной версии
Два Random(seed: 42) сегодня совпадают, но воспроизводимость гарантируется внутри одной мажорной версии, а не через релиз, сдвигающий последовательность генерации (новое правило boundary-bias, изменённый порядок draw). Не полагайтесь на голый #[Property(seed: ...)] как единственную защиту в долгоживущем regression-сьюте — см. Корпус регрессий про механизм (PROPERTY_DB), который уже учитывает это через SEQUENCE_EPOCH, и явные примеры — фиксация конкретного входа независимо от какого-либо генератора или seed вообще.