Перейти к основному содержимому

<assert> — конфиг проверяет свой собственный вывод

Когда нужно: форма данных важна тому, кто их получит, и вы хотите, чтобы запуск остановился, а не отдал файл, который незаметно «поехал».

Утверждение описывает свойство, которым готовый запуск обязан обладать. Держится — ничего не происходит. Не держится — запуск останавливается вашей же фразой, до того как записана хоть одна строка.

<assert that="Tracked == 700" says="у каждого отправленного заказа должен быть трек-номер"/>

Живёт в <env>, рядом с <uniq> и <distinct>: как и они, это высказывание о том, чему обязан удовлетворять весь запуск, а не о том, чем является одна колонка.

Их два. that= читают один раз, по числам за весь запуск. each= отвечают на каждой строке — см. Каждая строка ниже.

Что стоит утверждать

Не то, что конфиг и так написал. Вы написали percent="70" и утверждаете 70 процентов — вы проверили, что TDC умеет считать.

Ценность в том, чего конфиг не говорит. Здесь фильтр и условие накладываются друг на друга, и доля, доехавшая до файла, нигде в тексте не написана:

<tdc>
<env count="1000" seed="orders" local="en">
<sequence name="Status"><gen type="text" value="shipped,pending" percent="70,30"/></sequence>
<sequence name="Tracking" parent="Status.shipped">
<gen type="regex" value="[A-Z]{2}[0-9]{9}" if="Status == 'shipped' && _count % 4 != 0"/>
</sequence>
<sequence name="Tracked"><gen type="stat" of="Tracking" op="count"/></sequence>

<assert that="Tracked == 700" says="every shipped order should carry a tracking number"/>
</env>
<block>
<line><data>${{Status}},${{Tracking}}</data></line>
</block>
</tdc>
./run orders.tdc
tdcv2: assert failed: every shipped order should carry a tracking number
Tracked == 700   with Tracked = 522

Больше ничто в TDC про этот конфиг мнения не имеет. Он разбирается, проходит проверку, запускается — и 178 отправленных заказов выходят с пустым трек-номером. Ровно ради такого сбоя утверждение и существует.

Код возврата — 1, поэтому CI на нём останавливается.

Кратко

АтрибутОбязателенЧто делает
thatодно из двухУсловие по значениям за весь запуск, читается один раз, на языке if=
eachодно из двухУсловие, которому обязана удовлетворять каждая строка, на том же языке
saysдаФраза, которую получит читатель, когда условие не сойдётся

says= обязателен в любом случае. Утверждение, которое срабатывает, показывая одно лишь выражение, заставляет читателя месяцы спустя разбираться в CI-логе, ради чего оно было написано. that= и each= нельзя написать на одном теге: их проверяют разное число раз, и читатель не смог бы понять, которое из двух описывает фраза в says=.

Флага нет. Утверждение работает потому, что оно написано: проверка, которую нужно не забыть включить, — это проверка, которую никто не запускал, на конфиге, который выглядит проверенным.

Откуда берутся числа

that= читает колонки, и читать обычно стоит <gen type="stat"> — одно число на весь запуск.

<env count="500" seed="clinic" local="en">
<sequence name="Visit"><gen type="date" from="2026-01-01" to="2026-06-30" format="YYYY-MM-DD"/></sequence>
<sequence name="Follow"><gen type="date" of="Visit" plus="7..30d" format="YYYY-MM-DD"/></sequence>
<sequence name="Ward"><gen type="text" value="A,B,C" percent="50,30,20"/></sequence>

<sequence name="Rows"><gen type="stat" of="Visit" op="count"/></sequence>
<sequence name="Wards"><gen type="stat" of="Ward" op="count"/></sequence>

<assert that="Rows == _total" says="у каждой строки есть дата визита"/>
<assert that="Wards == _total" says="у каждой строки есть отделение"/>
</env>

_total — число строк, и это единственная встроенная величина, которую утверждению разрешено читать.

Правило, которое не даёт себя обмануть

Каждое имя в that= должно быть одинаковым на всех строках. Колонка stat такова по построению. Колонка text с одним значением — по факту. Вытянутая колонка — нет, и её отвергают:

<sequence name="Amount"><gen type="number" value="1..500"/></sequence>
<assert that="Amount > 0" says="every amount is positive"/>
./run amounts.tdc
tdcv2: assert ("Amount > 0"): "Amount" is not the same on every row, so this would have checked the first row and called the run verified. A whole-run assertion reads whole-run values: give it a <gen type="stat" of="Amount" op="…"/> column, or _total. To state it of every row instead, write each= rather than that=.

Без этого правила that="Amount > 0" прочитало бы нулевую строку и отчиталось об одной строке из пятисот — проверка прошла потому, что почти не смотрела, но носит значок «проверено». Это ровно та болезнь, ради которой вся эта возможность и сделана.

Колонку, которую фильтр parent= оставляет пустой на части строк, отвергают по той же причине: у запуска нет для неё одного значения, и условие сравнивало бы с тем, что случайно оказалось в нулевой строке. Сверните её через op="count".

Каждая строка — each=

Отказ выше — не тупик, а указатель. «Каждая сумма положительна» — настоящее утверждение, и место ему в each=:

<sequence name="Amount"><gen type="number" value="1..500"/></sequence>
<assert each="Amount > 0" says="каждая сумма положительна"/>

Условие отвечают на каждой строке, на том же языке, что и if=, и по тем же именам — колонки этой строки плюс встроенные значения. Учить нечего, и колонку stat заранее строить не нужно.

Когда строка его нарушает, запуск останавливается на ней и говорит, на какой:

<sequence name="Fee"><gen type="number" value="-3..20"/></sequence>
<assert each="Fee >= 0" says="комиссия никогда не бывает отрицательной"/>
./run fees.tdc (20 строк)
tdcv2: assert failed on row 16: комиссия никогда не бывает отрицательной
Fee >= 0   with Fee = -3

Код выхода — 1, как и у that=.

Останавливается на первой сбойной строке, а не считает их все. На потоковом движке строки до неё уже на диске, так что «проверить весь файл, а потом отчитаться» — обещание, которое не всякий движок мог бы дать честно; а запуск, чьи данные уже неверны, не станет вернее от того, что мы узнаем, насколько. Названная строка всегда первая: строки проверяются по порядку, каждая — перед тем, как её запишут.

Из-за этого конфиг с each= работает в один поток. Воркеры берут по диапазону строк и каждый остановился бы на своей первой сбойной, так что читателю показали бы ту, до которой первым добрался чей-то поток, — разное число на одном и том же конфиге и сиде. Явный --jobs скажет об этом и продолжит в одном потоке.

Больше никаких последствий для движков нет: в отличие от that=, построчное утверждение не требует колонки stat, поэтому конфиг с ним по-прежнему стримится. См. большие выгрузки.

Чего пока нет

  • Проверки контрольной цифры. Вот это ловушка: её посчитал <compute>, и пересчёт утверждает лишь то, что один и тот же код согласен сам с собой.

Движки

Утверждение с that= читает колонки stat, а stat и так отправляет конфиг на движок в памяти, — своих последствий оно не добавляет. Утверждение с each= тоже не добавляет, если не считать одного потока. См. большие выгрузки.

См. также