Покрытие лжёт
Вы научитесь
Почему процент покрытия бывает проверкой, которая ничего не проверила, и как это распознать.
Процент покрытия — заявление о тестах, и заявления бывают пустыми. Виджет нашёл пустой вид: 11 spec отвергнуты на разборе, 0 заблокированы только прозой, и 23 сломаны нарочно как фикстуры — но где-то ещё в корпусе «тест», являющийся только прозой, проходит все проверки, ничего не исполняя. Приметы всегда одни: число без знаменателя, проход без исполнившегося ассерта, прогон, который не может упасть. Спрашивайте с каждого процента: что сдвинуло бы это число?
Попробуйте
В прогоне по прозе найдите разницу между «отвергнут на разборе» и «заблокирован только прозой»; затем напишите тест, чей проход не исполняет ни одного ассерта.

11 specs refused at parse, 0 blocked only by prose, 23 broken on purpose as fixtures.
specs/fpga/testbench/fifo_tb.t27
// FIFO testbench spec
// This spec exercises a small synchronous FIFO with a power-of-two depth.
// It provides write/read helpers, a combined transaction function,
// and a set of tests covering basic operation, edge cases, and invariants.
const DEPTH : u32 = 8;
struct Fifo {
mem : [DEPTH]u32,
head : u32,
tail : u32,
fill_count : u32,
}
var fifo : Fifo = Fifo {
mem = [0, 0, 0, 0, 0, 0, 0, 0],
head = 0,
tail = 0,
fill_count = 0,
};
var wr_en : bool = false;
var rd_en : bool = false;
var wr_data : u32 = 0;
var rd_data : u32 = 0;
fn reset() {
fifo.head = 0;
fifo.tail = 0;
fifo.fill_count = 0;
wr_en = false;
rd_en = false;
wr_data = 0;
rd_data = 0;
}
fn tick() {
if wr_en && !rd_en && !full() {
fifo.mem[fifo.tail] = wr_data;
fifo.tail = (fifo.tail + 1) % DEPTH;
fifo.fill_count = fifo.fill_count + 1;
} else if rd_en && !wr_en && !empty() {
rd_data = fifo.mem[fifo.head];
fifo.head = (fifo.head + 1) % DEPTH;
fifo.fill_count = fifo.fill_count - 1;
} else if wr_en && rd_en {
if !full() && !empty() {
rd_data = fifo.mem[fifo.head];
fifo.mem[fifo.tail] = wr_data;
fifo.head = (fifo.head + 1) % DEPTH;
fifo.tail = (fifo.tail + 1) % DEPTH;
} else if !full() && empty() {
fifo.mem[fifo.tail] = wr_data;
fifo.tail = (fifo.tail + 1) % DEPTH;
fifo.fill_count = fifo.fill_count + 1;
} else if full() && !empty() {
rd_data = fifo.mem[fifo.head];
fifo.head = (fifo.head + 1) % DEPTH;
fifo.fill_count = fifo.fill_count - 1;
}
}
wr_en = false;
rd_en = false;
}
fn full() -> bool {
return fifo.fill_count == DEPTH;
}
fn empty() -> bool {
return fifo.fill_count == 0;
}
fn write_word(data : u32) -> bool {
if full() {
return false;
}
wr_en = true;
wr_data = data;
tick();
wr_en = false;
return true;
}
fn read_word() -> u32 {
if empty() {
return 0xFFFF;
}
rd_en = true;
tick();
rd_en = false;
return rd_data;
}
fn on_comb(data: u32) -> bool {
return write_word(data);
}
test test_basic_write {
reset();
var ok = write_word(0xDEAD);
assert ok == true;
assert fifo.fill_count == 1;
}
test test_basic_read {
reset();
write_word(0xBEEF);
var data = read_word();
assert data == 0xBEEF;
assert fifo.fill_count == 0;
}
test test_full_after_writes {
reset();
var i = 0;
while i < DEPTH {
var ok = write_word(i);
assert ok == true;
i = i + 1;
}
assert full() == true;
var ok = write_word(0xFFFF);
assert ok == false;
}
test test_empty_after_read {
reset();
write_word(0x1234);
write_word(0x5678);
var d1 = read_word();
var d2 = read_word();
assert d1 == 0x1234;
assert d2 == 0x5678;
assert empty() == true;
}
test test_overflow_rejected {
reset();
var i = 0;
while i < DEPTH {
write_word(i);
i = i + 1;
}
var ok = write_word(0xABCD);
assert ok == false;
assert fifo.fill_count == DEPTH;
}
test test_underflow_returns_sentinel {
reset();
var data = read_word();
assert data == 0xFFFF;
assert fifo.fill_count == 0;
}
test test_overflow_then_underflow {
reset();
var i = 0;
while i < DEPTH {
write_word(i);
i = i + 1;
}
var extra = write_word(0xFFFF);
assert extra == false;
var j = 0;
while j < DEPTH {
var data = read_word();
assert data == j;
j = j + 1;
}
var sentinel = read_word();
assert sentinel == 0xFFFF;
}
test test_rapid_alternating {
reset();
var i = 0;
while i < 20 {
var ok = write_word(i);
assert ok == true;
var data = read_word();
assert data == i;
i = i + 1;
}
assert empty() == true;
}
test test_wrap_behavior {
reset();
var i = 0;
while i < 16 {
write_word(i);
i = i + 1;
}
var j = 0;
while j < 8 {
var data = read_word();
assert data == (j + 8);
j = j + 1;
}
assert fifo.fill_count == 8;
}
test test_simultaneous_rw {
reset();
write_word(0x42);
wr_en = true;
rd_en = true;
wr_data = 0x43;
tick();
wr_en = false;
rd_en = false;
invariant fill_count == 1;
}
test test_on_comb_writes_when_not_full {
reset();
var result = on_comb(0xAAAA);
assert result == true;
assert fifo.fill_count == 1;
assert fifo.mem[0] == 0xAAAA;
}
test test_on_comb_rejects_when_full {
reset();
var i = 0;
while i < DEPTH {
write_word(i);
i = i + 1;
}
var result = on_comb(0xBBBB);
assert result == false;
assert fifo.fill_count == DEPTH;
}Все уроки
Модуль 1 · Зачем проверять
Дизайны, которые компилируются и ошибаются; модель, которая выносит вердикт; план, записанный до кода.
Модуль 2 · Тестбенчи
Стимулы, проверки и вердикт, записанные как один spec рядом с дизайном, который они судят.
Модуль 3 · Временные диаграммы
Трасса каждого сигнала, прочитанная так, как её читает инженер по железу, и два прогона, сравнённые между собой.
Модуль 4 · Векторы соответствия
Случаи с ответом, записанным рядом, там, откуда их достанет компилятор.
Модуль 5 · Косимуляция
Spec, симулятор и плата сходятся в одном ответе на стенде Artix-7 XC7A200T, и что делать, когда не сходятся.
Модуль 6 · Покрытие
Чего коснулись тесты: строки, переключения, состояния — и что прячет это число.
Модуль 7 · Формальные методы
Ассерты, верные каждый такт; ограниченный поиск контрпримера; и почему доказательству нужна индукция.
Модуль 8 · Мутации
Ломайте дизайн нарочно и считайте, что заметили тесты.
Модуль 9 · Приёмка
Одна команда, все квитанции, чистый вердикт, который можно показать.