Skip to content

Рецепт: воспроизведение по seed

Фальсифицированное property сообщает seed. Эта страница проходит три способа им воспользоваться и то, как PROPERTY_VERBOSE закрывает разрыв между «какой seed» и «что реально происходило в этом прогоне».

Почему seed воспроизводится точно

Random (тип, из которого тянет каждый генератор) оборачивает object-scoped \Random\Randomizer (MT19937), независимый от глобального состояния mt_rand PHP. Два экземпляра Random, построенные с одним seed, выдают идентичную последовательность draw независимо от того, что ещё в процессе вызывало random-функции между ними — именно это делает сообщённый seed надёжным ключом реплея, а не приблизительной подсказкой. См. Random.

1. Зафиксировать seed в атрибуте

Самый прямой путь, когда падение уже найдено локально:

php
#[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: всё равно побеждает переменную окружения:

bash
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 при падении:

bash
docker run --rm -v "$PWD":/app -w /app -e PROPERTY_SEED=918273645 \
  -e PROPERTY_VERBOSE=1 composer:2 vendor/bin/testo
run 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 вообще.