Сохрани адресацию отдельных регистров GAS 0x3 и выдели слотам тип 0x4 с маской Word0. Добавь единое C-ядро GAS_WATCH2, тонкие Python/JNI-порты и проверки общих эталонных байтов. Совместимость: тип 0x4 больше не обслуживает Modbus Coil. Потребителям PCAN_MSG_MODBUS_COIL и ProtoCanMsgToModbusCoil нужно перейти на GAS_SLOT и отдельный API слотов; регистровый GAS остаётся на 0x3.
371 lines
61 KiB
C
371 lines
61 KiB
C
/* Контракт GAS_WATCH2: атомарная подписка, самостоятельный снимок и проверка входящих пакетов. */
|
|
#include <stdint.h> /* Целочисленные типы фиксируют размеры полей и переполнение счётчиков протокола. */
|
|
#include <stdio.h> /* Диагностика сообщает конкретное нарушенное условие и строку теста. */
|
|
#include <string.h> /* Побайтовые сравнения проверяют эталонный формат и отсутствие частичных изменений. */
|
|
#include "set_gas_watch.h" /* Проверяем только публичный интерфейс общего ядра и его хостовые функции. */
|
|
|
|
static unsigned failures; /* Число нарушений накапливается, чтобы один запуск показал все независимые ошибки. */
|
|
static void check(int condition, const char *expression, int line) /* Обёртка сохраняет проверки даже при определённом NDEBUG. */
|
|
{ /* Начало общей диагностики проверяемого условия. */
|
|
if (!condition) { /* Записываем только нарушения, чтобы успешный прогон оставался коротким. */
|
|
printf("FAIL gas_watch:%d: %s\n", line, expression); /* Исходная строка позволяет быстро найти нарушенный контракт. */
|
|
++failures; /* Каждое отдельное нарушение делает итоговый код возврата ненулевым. */
|
|
} /* Завершение обработки одного нарушения. */
|
|
} /* Завершение функции диагностики. */
|
|
#define CHECK(condition) check((condition), #condition, __LINE__) /* Макрос сохраняет выражение и его исходную строку без зависимости от assert. */
|
|
|
|
typedef struct { /* Состояние обработчика позволяет отличить проверку карты от реального чтения. */
|
|
unsigned calls; /* Счётчик фиксирует любое обращение к провайдеру регистра. */
|
|
uint16_t rejected_offset; /* Смещение 0xFFFF означает, что ни один из двух тестовых регистров не отвергается. */
|
|
uint16_t base_value; /* Начальное значение добавляется к смещению, сохраняя различимость соседних каналов. */
|
|
} read_context_t; /* Один экземпляр контекста обслуживает вычисляемый регион карты. */
|
|
|
|
static pcan_gas_status_t read_value(const pcan_gas_region_t *region, uint16_t offset, uint16_t *value) /* Подменяем приборный провайдер воспроизводимым обработчиком. */
|
|
{ /* Начало имитации чтения одного 16-битного регистра. */
|
|
read_context_t *context = (read_context_t *)region->user; /* Контекст берём из публичного пользовательского поля GAS-региона. */
|
|
++context->calls; /* Считаем в том числе неудачные чтения, чтобы проверить продолжение после ошибки. */
|
|
*value = (uint16_t)(context->base_value + offset); /* Записываем ненулевой результат и при отказе: ядро обязано его обнулить. */
|
|
return offset == context->rejected_offset ? PCAN_GAS_REJECTED : PCAN_GAS_OK; /* Выбранное смещение имитирует недостоверное значение устройства. */
|
|
} /* Завершение подмены чтения. */
|
|
|
|
static pcan_gas_status_t write_value(const pcan_gas_region_t *region, uint16_t offset, uint16_t value) /* Провайдер записи создаёт корректный регион без возможности чтения. */
|
|
{ /* Начало обработчика, используемого только для описания карты. */
|
|
(void)region; /* Тесту достаточно наличия обработчика: состояние региона не изменяем. */
|
|
(void)offset; /* Адрес записи не нужен, потому что операции записи здесь не выполняются. */
|
|
(void)value; /* Значение записи не используется в проверках подписки только на чтение. */
|
|
return PCAN_GAS_OK; /* Наличие рабочего обработчика записи делает регион допустимым для общей карты GAS. */
|
|
} /* Завершение провайдера записи. */
|
|
|
|
static uint16_t storage[] = {123U, 65534U, 456U, 789U}; /* Значения включают отрицательное INT16 в исходном представлении 0xFFFE. */
|
|
static read_context_t context; /* Общий контекст даёт доступ к счётчику обращений из разных сценариев. */
|
|
static const pcan_gas_region_t regions[] = { /* Разрозненные адреса повторяют реальное сочетание памяти и вычисляемых регистров. */
|
|
{0x0000U, 4U, storage, NULL, NULL, PCAN_GAS_RDONLY, NULL, "memory"}, /* Первые четыре адреса доступны напрямую, без обработчиков. */
|
|
{0x0100U, 2U, NULL, read_value, NULL, PCAN_GAS_RDONLY, &context, "provider"}, /* Два вычисляемых канала позволяют отказать первому и успешно прочитать следующий. */
|
|
{0x0200U, 1U, storage, NULL, NULL, PCAN_GAS_WRONLY, NULL, "write-only"}, /* Явный запрет чтения должен отвергаться до сбора выборки. */
|
|
{0x0300U, 1U, NULL, NULL, write_value, 0U, NULL, "writer"}, /* Отсутствие провайдера чтения должно отвергаться и без флага WRONLY. */
|
|
{0xFF01U, 1U, storage + 2U, NULL, NULL, PCAN_GAS_RDONLY, NULL, "uptime"} /* Высокий адрес проверяет отсутствие требований к непрерывности списка. */
|
|
}; /* Конец отсортированной карты без пересечений. */
|
|
static const pcan_gas_map_t map = {regions, 5U}; /* Пять регионов образуют валидную карту для основных сценариев. */
|
|
static const uint16_t reference_addresses[] = {0x0000U, 0x0001U, 0xFF01U}; /* Порядок адресов совпадает с общим JSON-эталоном портов. */
|
|
static const uint8_t reference_config[] = { /* Эталон соответствует tests/fixtures/gas-watch-v2.json и little-endian полям. */
|
|
0x02U, 0x00U, 0x64U, 0x00U, 0x78U, 0x56U, 0x34U, 0x12U, 0x03U, 0x00U, 0x00U, 0x00U, /* Версия 2, период 100 мс, идентификатор 0x12345678, три адреса и нулевой резерв. */
|
|
0x00U, 0x00U, 0x01U, 0x00U, 0x01U, 0xFFU /* Адреса 0x0000, 0x0001 и 0xFF01 сохраняют исходный порядок запроса. */
|
|
}; /* Конец эталона запроса, который одновременно является точным ACK. */
|
|
static const uint8_t reference_sample[] = { /* Эталон фиксирует полный диапазон беззнаковых счётчиков и недостоверный третий канал. */
|
|
0x02U, 0x00U, 0x03U, 0x00U, 0x78U, 0x56U, 0x34U, 0x12U, /* Версия 2, три значения и идентификатор 0x12345678. */
|
|
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xF0U, 0xFFU, 0xFFU, 0xFFU, /* Номер 0xFFFFFFFF и приборное время 0xFFFFFFF0 мс сохраняются без знакового искажения. */
|
|
0x03U, 0x7BU, 0x00U, 0xFEU, 0xFFU, 0x00U, 0x00U /* Биты 0 и 1 отмечают достоверность значений 123 и 65534; недостоверное значение передаётся нулём. */
|
|
}; /* Конец эталонной выборки длиной 23 байта. */
|
|
|
|
static void reject_unchanged(set_gas_watch_t *watch, const pcan_gas_map_t *active_map, const uint8_t *payload, size_t size, int expected) /* Общая проверка атомарного отказа на стороне устройства. */
|
|
{ /* Начало вспомогательного сценария ошибочного применения конфигурации. */
|
|
set_gas_watch_t before = *watch; /* Сохраняем весь объект, включая адреса, счётчики и срок очередной выборки. */
|
|
unsigned calls = context.calls; /* Проверка доступности регистров не должна запускать приборные чтения. */
|
|
CHECK(set_gas_watch_apply(watch, active_map, payload, size) == expected); /* Код ошибки отличает формат, отсутствующий адрес, доступ и конфликт идентификатора. */
|
|
CHECK(memcmp(watch, &before, sizeof before) == 0); /* Ошибка любого адреса оставляет подписку целиком прежней. */
|
|
CHECK(context.calls == calls); /* Отклонение запроса не вызывает побочных действий провайдера. */
|
|
} /* Завершение атомарной проверки отказа. */
|
|
|
|
static void test_reference(void) /* Проверяем точные байты обеих сторон контракта на общей эталонной конфигурации. */
|
|
{ /* Начало сценария совместимости wire-формата. */
|
|
set_gas_watch_t watch; /* Устройство хранит подписку в памяти вызывающего приложения. */
|
|
set_gas_watch_sample_t sample; /* Выборка хранится отдельно и не зависит от последующей перенастройки. */
|
|
uint8_t output[SET_GAS_WATCH_SAMPLE_MAX]; /* Размер обеспечивает место для максимально разрешённого пакета. */
|
|
uint16_t values[3] = {0U, 0U, 0U}; /* Декодер получает ровно три ячейки для значений эталона. */
|
|
uint8_t valid[3] = {0U, 0U, 0U}; /* Признаки достоверности возвращаются отдельно от значений. */
|
|
uint32_t sequence = 0U; /* Выходной счётчик должен принять максимальное беззнаковое значение. */
|
|
uint32_t timestamp = 0U; /* Выходная отметка должна сохранить приборные миллисекунды. */
|
|
set_gas_watch_init(&watch); /* Пустое состояние явно инициализируется до первого применения. */
|
|
CHECK(!set_gas_watch_due(&watch, 0U)); /* До настройки устройство не считает сбор активным. */
|
|
CHECK(set_gas_watch_request(0x12345678UL, 100U, reference_addresses, 3U, output, sizeof output) == sizeof reference_config); /* Запрос содержит ровно заголовок 12 байт и три 16-битных адреса. */
|
|
CHECK(memcmp(output, reference_config, sizeof reference_config) == 0); /* Сравнение фиксирует порядок байтов, резерв и порядок адресов. */
|
|
CHECK(set_gas_watch_apply(&watch, &map, output, sizeof reference_config) == SET_GAS_WATCH_OK); /* Полный валидный список принимается одной транзакцией. */
|
|
CHECK(set_gas_watch_encode_ack(&watch, output, sizeof output) == sizeof reference_config); /* Подтверждение возвращает весь принятый список. */
|
|
CHECK(memcmp(output, reference_config, sizeof reference_config) == 0); /* ACK подтверждает адреса точно, а не только совпадение числа каналов. */
|
|
CHECK(set_gas_watch_ack(output, sizeof reference_config, 0x12345678UL, 100U, reference_addresses, 3U) == 1); /* Хост принимает точное подтверждение текущего запроса. */
|
|
CHECK(set_gas_watch_ack(output, sizeof reference_config, 0x12345679UL, 100U, reference_addresses, 3U) == 0); /* Подтверждение предыдущей конфигурации не подтверждает новую. */
|
|
CHECK(set_gas_watch_ack(output, sizeof reference_config, 0x12345678UL, 101U, reference_addresses, 3U) == 0); /* Подтверждённый период тоже является частью конфигурации. */
|
|
const uint16_t reordered[] = {0x0001U, 0x0000U, 0xFF01U}; /* Перестановка сохраняет размер и состав множества, но меняет смысл позиций. */
|
|
CHECK(set_gas_watch_ack(output, sizeof reference_config, 0x12345678UL, 100U, reordered, 3U) == 0); /* ACK обязан подтверждать порядок списка, а не только его множество. */
|
|
CHECK(set_gas_watch_ack(NULL, sizeof reference_config, 0x12345678UL, 100U, reference_addresses, 3U) == 0); /* Нулевой вход ACK безопасно отвергается до сравнения содержимого. */
|
|
CHECK(set_gas_watch_ack(output, sizeof reference_config - 1U, 0x12345678UL, 100U, reference_addresses, 3U) == 0); /* Усечённое подтверждение не подтверждает даже совпадающий префикс. */
|
|
memset(&sample, 0, sizeof sample); /* Обнуляем запасные каналы, чтобы эталон не зависел от содержимого стека. */
|
|
sample.config_id = 0x12345678UL; /* Идентификатор связывает выборку с конкретным списком адресов. */
|
|
sample.sequence = UINT32_MAX; /* Максимальный номер проверяет сохранение всех 32 бит. */
|
|
sample.timestamp_ms = 0xFFFFFFF0UL; /* Время в 16 мс до переполнения проверяет беззнаковую передачу. */
|
|
sample.count = 3U; /* В эталон включены два достоверных и один недостоверный канал. */
|
|
sample.values[0] = 123U; /* Первый регистр передаётся как обычный UINT16. */
|
|
sample.values[1] = 65534U; /* Второй регистр передаётся как сырое слово 0xFFFE без интерпретации типа GUI. */
|
|
sample.values[2] = 0xCAFEU; /* Ненулевое недостоверное значение обязано превратиться в ноль при сериализации. */
|
|
sample.valid[0] = 1U; /* Первый бит маски подтверждает первый канал. */
|
|
sample.valid[1] = 1U; /* Второй бит маски подтверждает второй канал. */
|
|
CHECK(set_gas_watch_encode_sample(&sample, output, sizeof output) == sizeof reference_sample); /* Размер включает заголовок 16 байт, один байт маски и шесть байт значений. */
|
|
CHECK(memcmp(output, reference_sample, sizeof reference_sample) == 0); /* Эталон проверяет каноническое обнуление и отсутствие лишних битов маски. */
|
|
CHECK(sample.values[2] == 0xCAFEU); /* Кодирование не изменяет исходный самостоятельный снимок. */
|
|
CHECK(set_gas_watch_decode(output, sizeof reference_sample, sample.config_id, 3U, &sequence, ×tamp, values, valid, 3U) == 3); /* Хост возвращает число успешно распознанных каналов. */
|
|
CHECK(sequence == UINT32_MAX && timestamp == 0xFFFFFFF0UL); /* Номер и время сохраняются при прохождении через ABI. */
|
|
CHECK(values[0] == 123U && values[1] == 65534U && values[2] == 0U); /* Значения возвращаются в порядке адресов, включая нулевое недостоверное. */
|
|
CHECK(valid[0] == 1U && valid[1] == 1U && valid[2] == 0U); /* Маска распаковывается в отдельные признаки каналов. */
|
|
} /* Завершение проверки эталонных байтов. */
|
|
|
|
static void test_atomic_apply(void) /* Проверяем отказ без частичной замены списка и работу идентификаторов конфигурации. */
|
|
{ /* Начало сценария транзакционной настройки. */
|
|
set_gas_watch_t watch; /* Состояние будет многократно получать ошибочные и корректные запросы. */
|
|
uint8_t payload[SET_GAS_WATCH_CONFIG_MAX + 1U]; /* Дополнительный байт позволяет проверить отказ от лишнего хвоста. */
|
|
uint16_t addresses[] = {0x0000U, 0x0100U}; /* Второй канал принадлежит провайдеру, который нельзя вызывать при настройке. */
|
|
size_t size = set_gas_watch_request(1U, 32U, addresses, 2U, payload, sizeof payload); /* Первая конфигурация запускает два разрозненных регистра с периодом 32 мс. */
|
|
set_gas_watch_init(&watch); /* В исходном состоянии нет принятого идентификатора. */
|
|
context.calls = 0U; /* Счётчик отделяет настройку от всех предыдущих сценариев. */
|
|
CHECK(set_gas_watch_apply(&watch, &map, payload, size) == SET_GAS_WATCH_OK); /* Валидный запрос устанавливает сразу весь список. */
|
|
CHECK(context.calls == 0U); /* Проверка наличия обработчика не должна читать устройство. */
|
|
watch.sequence = 77U; /* Ненулевой номер помогает заметить ошибочный сброс при повторе запроса. */
|
|
watch.next_ms = 12345U; /* Установленный срок должен сохраниться при идемпотентном повторе. */
|
|
watch.scheduled = 1U; /* Состояние уже выполнявшегося сбора не должно превращаться в новый старт. */
|
|
set_gas_watch_t before = watch; /* Копия фиксирует все поля до повторной доставки одного запроса. */
|
|
CHECK(set_gas_watch_apply(&watch, &map, payload, size) == SET_GAS_WATCH_OK); /* Повтор принятой конфигурации является успешным повтором подтверждения. */
|
|
CHECK(memcmp(&watch, &before, sizeof watch) == 0); /* Идемпотентный повтор не меняет номера, расписание и адреса. */
|
|
payload[2] = 33U; /* При прежнем идентификаторе изменяем период с 32 на 33 мс. */
|
|
reject_unchanged(&watch, &map, payload, size, SET_GAS_WATCH_CONFLICT); /* Один идентификатор не может обозначать две разные конфигурации. */
|
|
payload[2] = 32U; /* Восстанавливаем канонический период исходной конфигурации. */
|
|
payload[0] = 3U; /* Неподдерживаемая версия не может интерпретироваться как текущий формат. */
|
|
reject_unchanged(&watch, &map, payload, size, SET_GAS_WATCH_INVALID); /* Неверная версия оставляет рабочий сбор действующим. */
|
|
payload[0] = 2U; /* Возвращаем версию 2 перед проверкой следующего поля. */
|
|
payload[10] = 1U; /* Зарезервированное поле должно оставаться нулевым в версии 2. */
|
|
reject_unchanged(&watch, &map, payload, size, SET_GAS_WATCH_INVALID); /* Неизвестные расширения не принимаются молча. */
|
|
payload[10] = 0U; /* Убираем нарушение резерва для независимой проверки длины. */
|
|
reject_unchanged(&watch, &map, payload, size - 1U, SET_GAS_WATCH_INVALID); /* Оборванный адрес не приводит к усечённой подписке. */
|
|
reject_unchanged(&watch, &map, payload, size + 1U, SET_GAS_WATCH_INVALID); /* Лишний байт исключает неоднозначное принятие пакета. */
|
|
reject_unchanged(&watch, &map, payload, SET_GAS_WATCH_CONFIG_HEADER - 1U, SET_GAS_WATCH_INVALID); /* До чтения полей должен присутствовать весь фиксированный заголовок. */
|
|
reject_unchanged(&watch, &map, payload, sizeof payload, SET_GAS_WATCH_INVALID); /* Пакет больше 140 байт не может содержать допустимую конфигурацию версии 2. */
|
|
reject_unchanged(&watch, &map, NULL, size, SET_GAS_WATCH_INVALID); /* Нулевой указатель с ненулевой длиной обрабатывается безопасно. */
|
|
addresses[1] = 0x0042U; /* Адрес 0x0042 расположен в промежутке между регионами тестовой карты. */
|
|
size = set_gas_watch_request(2U, 32U, addresses, 2U, payload, sizeof payload); /* Новый идентификатор исключает смешение ошибки адреса с конфликтом номера. */
|
|
reject_unchanged(&watch, NULL, payload, size, SET_GAS_WATCH_INVALID); /* Без карты невозможно подтвердить доступность каналов нового списка. */
|
|
reject_unchanged(&watch, &map, payload, size, SET_GAS_WATCH_NO_REG); /* Отсутствующий регистр отвергает весь список. */
|
|
addresses[1] = 0x0200U; /* Этот адрес существует, но помечен как доступный только для записи. */
|
|
size = set_gas_watch_request(2U, 32U, addresses, 2U, payload, sizeof payload); /* Сохраняем корректный wire-формат для проверки прав доступа. */
|
|
reject_unchanged(&watch, &map, payload, size, SET_GAS_WATCH_ACCESS); /* Явный запрет чтения не превращается в постоянно недостоверный канал. */
|
|
addresses[1] = 0x0300U; /* У этого региона есть только провайдер записи без флага WRONLY. */
|
|
size = set_gas_watch_request(2U, 32U, addresses, 2U, payload, sizeof payload); /* Подписка проверяет реальную возможность чтения, а не один флаг. */
|
|
reject_unchanged(&watch, &map, payload, size, SET_GAS_WATCH_ACCESS); /* Отсутствие хранилища и провайдера чтения тоже означает запрет. */
|
|
addresses[1] = 0x0101U; /* Корректная замена сохраняет число каналов, но меняет второй адрес. */
|
|
size = set_gas_watch_request(2U, 32U, addresses, 2U, payload, sizeof payload); /* Новая конфигурация имеет отдельный идентификатор. */
|
|
CHECK(set_gas_watch_apply(&watch, &map, payload, size) == SET_GAS_WATCH_OK); /* Устройство принимает новый список целиком. */
|
|
CHECK(watch.config_id == 2U && watch.address[1] == 0x0101U); /* Равное число каналов не скрывает замену их содержимого. */
|
|
CHECK(watch.sequence == 0U && watch.scheduled == 0U); /* Новый список начинает отдельную последовательность и готов к немедленному сбору. */
|
|
size = set_gas_watch_request(1U, 32U, addresses, 2U, payload, sizeof payload); /* Возвращаем устаревший идентификатор с тем же числом каналов. */
|
|
reject_unchanged(&watch, &map, payload, size, SET_GAS_WATCH_CONFLICT); /* Задержавшийся запрос не откатывает устройство назад. */
|
|
size = set_gas_watch_request(0x80000002UL, 32U, addresses, 2U, payload, sizeof payload); /* Разность ровно половины диапазона uint32 неоднозначна для порядка номеров. */
|
|
reject_unchanged(&watch, &map, payload, size, SET_GAS_WATCH_CONFLICT); /* Граница 2^31 не признаётся более новой конфигурацией. */
|
|
payload[4] = 0U; /* Первые четыре байта идентификатора заменяем нулями для запрещённого номера. */
|
|
payload[5] = 0U; /* Убираем второй байт идентификатора, сохраняя длину и адреса. */
|
|
payload[6] = 0U; /* Убираем третий байт идентификатора без изменения других полей. */
|
|
payload[7] = 0U; /* Завершаем формирование зарезервированного идентификатора 0. */
|
|
reject_unchanged(&watch, &map, payload, size, SET_GAS_WATCH_INVALID); /* Нулевой номер запрещён и не используется как команда обхода порядка. */
|
|
size = set_gas_watch_request(3U, 32U, addresses, 2U, payload, sizeof payload); /* Создаём валидный пакет перед намеренным дублированием адреса на линии. */
|
|
payload[14] = payload[12]; /* Второй адрес получает младший байт первого адреса. */
|
|
payload[15] = payload[13]; /* Второй адрес получает старший байт первого адреса. */
|
|
reject_unchanged(&watch, &map, payload, size, SET_GAS_WATCH_INVALID); /* Дубликаты отвергаются и на устройстве, даже если хостовый builder их запрещает. */
|
|
pcan_gas_region_t overlap[] = {regions[0], regions[0]}; /* Перекрывающиеся регионы создают неоднозначное отображение GAS-адресов. */
|
|
pcan_gas_map_t invalid_map = {overlap, 2U}; /* Некорректная карта не должна обходить проверку через удачный первый поиск. */
|
|
size = set_gas_watch_request(3U, 32U, addresses, 2U, payload, sizeof payload); /* Убираем дубликат из запроса, оставляя единственным нарушением карту. */
|
|
reject_unchanged(&watch, &invalid_map, payload, size, SET_GAS_WATCH_INVALID); /* Применение проверяет целостность карты до принятия подписки. */
|
|
set_gas_watch_init(&watch); /* Новое состояние позволяет отдельно проверить перенос идентификатора через uint32. */
|
|
size = set_gas_watch_request(UINT32_MAX, 32U, addresses, 2U, payload, sizeof payload); /* Любой ненулевой номер разрешён для самой первой конфигурации. */
|
|
CHECK(set_gas_watch_apply(&watch, &map, payload, size) == SET_GAS_WATCH_OK); /* Максимальный номер не отвергается из-за его старшего знакового бита. */
|
|
size = set_gas_watch_request(1U, 32U, addresses, 2U, payload, sizeof payload); /* После максимального номера хост пропускает запрещённый ноль. */
|
|
CHECK(set_gas_watch_apply(&watch, &map, payload, size) == SET_GAS_WATCH_OK); /* Модульное сравнение распознаёт номер 1 как новый после 0xFFFFFFFF. */
|
|
} /* Завершение атомарных и порядковых проверок. */
|
|
|
|
static void test_capture_and_timing(void) /* Проверяем приборное время, единичное чтение и независимость уже полученной выборки. */
|
|
{ /* Начало сценария сбора и сериализации самостоятельного снимка. */
|
|
set_gas_watch_t watch; /* Подписка управляет периодом и последовательностью выборок. */
|
|
set_gas_watch_sample_t sample; /* Снимок принадлежит вызывающему приложению и переживает изменение подписки. */
|
|
uint16_t addresses[] = {0x0100U, 0x0101U, 0x0000U}; /* После отказавшего провайдера остаются ещё два канала для чтения. */
|
|
uint8_t payload[SET_GAS_WATCH_CONFIG_MAX]; /* Буфер переносит только конфигурацию адресов. */
|
|
uint8_t encoded[SET_GAS_WATCH_SAMPLE_MAX]; /* Первый результат кодирования сохраняется для проверки повторной передачи. */
|
|
uint8_t repeated[SET_GAS_WATCH_SAMPLE_MAX]; /* Повторное кодирование обязано совпадать даже после изменений карты. */
|
|
context.calls = 0U; /* Изолируем число приборных обращений текущего сценария. */
|
|
context.base_value = 1000U; /* Вычисляемые значения отличаются от памяти GAS. */
|
|
context.rejected_offset = 0U; /* Первый вычисляемый канал вернёт отказ уже после записи ненулевого результата. */
|
|
set_gas_watch_init(&watch); /* Начинаем без активной подписки и расписания. */
|
|
size_t size = set_gas_watch_request(1U, 32U, addresses, 3U, payload, sizeof payload); /* Три канала должны собираться одним снимком каждые 32 мс. */
|
|
CHECK(set_gas_watch_apply(&watch, &map, payload, size) == SET_GAS_WATCH_OK); /* Проверяем, что наличие провайдера достаточно независимо от его будущего результата. */
|
|
CHECK(context.calls == 0U); /* Настройка не вызывает провайдер даже для заведомо отказавшего чтения. */
|
|
CHECK(set_gas_watch_due(&watch, 0xFFFFFFF0UL)); /* Новый список готов к сбору сразу и около переполнения времени. */
|
|
CHECK(set_gas_watch_capture(&watch, &map, 0xFFFFFFF0UL, &sample)); /* Одна операция фиксирует все каналы и приборную отметку времени. */
|
|
CHECK(context.calls == 2U); /* Оба провайдера прочитаны ровно по одному разу, несмотря на отказ первого. */
|
|
CHECK(sample.config_id == 1U && sample.sequence == 0U && watch.sequence == 1U); /* Первая выборка имеет номер 0, после неё счётчик увеличивается один раз. */
|
|
CHECK(sample.timestamp_ms == 0xFFFFFFF0UL && sample.count == 3U); /* Метка относится ко всей выборке и не подменяется временем отправки. */
|
|
CHECK(sample.valid[0] == 0U && sample.values[0] == 0U); /* Неудачное чтение не выпускает записанное провайдером мусорное значение. */
|
|
CHECK(sample.valid[1] == 1U && sample.values[1] == 1001U); /* Отказ одного адреса не прерывает чтение следующего провайдера. */
|
|
CHECK(sample.valid[2] == 1U && sample.values[2] == storage[0]); /* Последний канал памяти остаётся достоверным после ошибки другого канала. */
|
|
CHECK(watch.scheduled == 1U && watch.next_ms == 16U); /* Прибавление 32 мс к 0xFFFFFFF0 корректно переносит срок в следующий оборот таймера. */
|
|
CHECK(!set_gas_watch_due(&watch, 0xFFFFFFF1UL)); /* Через 1 мс после выборки период ещё не истёк. */
|
|
CHECK(!set_gas_watch_due(&watch, 0U)); /* После переполнения до срока остаётся 16 мс. */
|
|
CHECK(!set_gas_watch_due(&watch, 15U)); /* В последнюю миллисекунду перед сроком чтение ещё не требуется. */
|
|
CHECK(set_gas_watch_due(&watch, 16U)); /* Равенство приборного времени сроку означает готовность новой выборки. */
|
|
watch.sequence = UINT32_MAX; /* Устанавливаем границу, которую нельзя дождаться перебором миллиардов выборок. */
|
|
CHECK(set_gas_watch_capture(&watch, &map, 0U, &sample)); /* Явный сбор разрешён и до очередного срока: due остаётся решением вызывающего кода. */
|
|
CHECK(sample.sequence == UINT32_MAX && watch.sequence == 0U); /* Последовательность переносится через uint32 без потери последнего номера. */
|
|
size_t encoded_size = set_gas_watch_encode_sample(&sample, encoded, sizeof encoded); /* Кодируем зафиксированный снимок до изменения окружения. */
|
|
unsigned calls = context.calls; /* Запоминаем количество чтений, чтобы выявить скрытое перечитывание при сериализации. */
|
|
storage[0] = 999U; /* Модель прибора меняется после фиксации снимка. */
|
|
context.base_value = 2000U; /* Вычисляемый провайдер тоже теперь вернул бы другие значения. */
|
|
size = set_gas_watch_request(2U, 100U, reference_addresses, 3U, payload, sizeof payload); /* Меняем подписку на другой список при сохранении числа каналов. */
|
|
CHECK(set_gas_watch_apply(&watch, &map, payload, size) == SET_GAS_WATCH_OK); /* Перенастройка должна быть независима от уже созданного пакета. */
|
|
CHECK(set_gas_watch_encode_sample(&sample, repeated, sizeof repeated) == encoded_size); /* Повторная передача использует прежний размер самостоятельного снимка. */
|
|
CHECK(memcmp(encoded, repeated, encoded_size) == 0); /* Ни значения карты, ни новые адреса не изменяют старую выборку. */
|
|
CHECK(context.calls == calls); /* Подтверждение настройки и повторное кодирование не читают приборные регистры. */
|
|
pcan_gas_region_t changed_regions[5]; /* Подмена карты моделирует исчезновение или изменение прав после успешной настройки. */
|
|
memcpy(changed_regions, regions, sizeof changed_regions); /* Остальные свойства регионов сохраняются, чтобы карта оставалась структурно корректной. */
|
|
changed_regions[0].flags = PCAN_GAS_WRONLY; /* Выбранные адреса 0 и 1 теперь запрещены для чтения. */
|
|
changed_regions[4].base = 0xFF02U; /* Ранее выбранный адрес 0xFF01 больше не покрывается картой. */
|
|
pcan_gas_map_t changed_map = {changed_regions, 5U}; /* Изменённая карта остаётся отсортированной и без перекрытий. */
|
|
CHECK(set_gas_watch_capture(&watch, &changed_map, 1U, &sample)); /* Потеря отдельных каналов не отменяет номер и время всей выборки. */
|
|
CHECK(sample.valid[0] == 0U && sample.valid[1] == 0U && sample.valid[2] == 0U); /* Изменение прав и исчезновение адреса явно отражаются недостоверностью. */
|
|
CHECK(sample.values[0] == 0U && sample.values[1] == 0U && sample.values[2] == 0U); /* Ни старые значения, ни указатели исчезнувших каналов не попадают в новый пакет. */
|
|
pcan_gas_map_t invalid_map = {NULL, 1U}; /* Ненулевое число регионов без массива является структурной ошибкой карты. */
|
|
set_gas_watch_t before_watch = watch; /* Ошибка всей карты не должна расходовать номер выборки или сдвигать расписание. */
|
|
set_gas_watch_sample_t before_sample = sample; /* Ошибка аргументов не должна повреждать уже полученный снимок. */
|
|
CHECK(!set_gas_watch_capture(&watch, &invalid_map, 2U, &sample)); /* Структурно неверная карта отличается от ошибки отдельного читаемого канала. */
|
|
CHECK(memcmp(&watch, &before_watch, sizeof watch) == 0); /* Состояние подписки полностью сохраняется при невозможности захвата. */
|
|
CHECK(memcmp(&sample, &before_sample, sizeof sample) == 0); /* Выходной снимок полностью сохраняется при невозможности захвата. */
|
|
size = set_gas_watch_request(3U, 0U, NULL, 0U, payload, sizeof payload); /* Остановка выражается пустым списком с периодом 0 мс и новым идентификатором. */
|
|
CHECK(size == SET_GAS_WATCH_CONFIG_HEADER); /* Пакет остановки состоит только из заголовка конфигурации. */
|
|
CHECK(set_gas_watch_apply(&watch, &map, payload, size) == SET_GAS_WATCH_OK); /* Остановка применяется такой же транзакцией, как активная конфигурация. */
|
|
set_gas_watch_sample_t before = sample; /* Сохраняем старую выборку перед попыткой собрать неактивный поток. */
|
|
CHECK(!set_gas_watch_due(&watch, UINT32_MAX)); /* Остановленный поток не становится активным из-за переполнения времени. */
|
|
CHECK(!set_gas_watch_capture(&watch, &map, 1U, &sample)); /* Пустая подписка не создаёт новый пакет. */
|
|
CHECK(memcmp(&sample, &before, sizeof sample) == 0); /* Неудачный сбор сохраняет уже готовую выборку в буфере вызывающего кода. */
|
|
storage[0] = 123U; /* Возвращаем общее значение эталона для независимости следующих сценариев. */
|
|
} /* Завершение проверки снимка и расписания. */
|
|
|
|
static void decode_rejected(const uint8_t *payload, size_t size, uint32_t config_id, size_t count, size_t capacity) /* Проверяем единый контракт отсутствия частичного результата на стороне хоста. */
|
|
{ /* Начало вспомогательной проверки отказа декодера. */
|
|
uint16_t values[SET_GAS_WATCH_MAX]; /* Полный буфер отделяет недостаточную объявленную ёмкость от реального выхода за память. */
|
|
uint8_t valid[SET_GAS_WATCH_MAX]; /* Маска хранится в собственном буфере с отличимым заполнителем. */
|
|
uint16_t before_values[SET_GAS_WATCH_MAX]; /* Копия выявляет изменение любого канала, включая запасные ячейки. */
|
|
uint8_t before_valid[SET_GAS_WATCH_MAX]; /* Копия маски выявляет раннюю запись до полной валидации payload. */
|
|
uint32_t sequence = 0x11223344UL; /* Отличимая исходная последовательность не должна меняться при отказе. */
|
|
uint32_t timestamp = 0x55667788UL; /* Отличимая исходная метка не должна меняться при отказе. */
|
|
memset(values, 0xA5, sizeof values); /* Все значения получают заполнитель, отличный от эталонного payload. */
|
|
memset(valid, 0x5A, sizeof valid); /* Признаки достоверности получают отдельный заполнитель. */
|
|
memcpy(before_values, values, sizeof values); /* Сохраняем массив значений для полного побайтового сравнения. */
|
|
memcpy(before_valid, valid, sizeof valid); /* Сохраняем все ячейки маски до вызова декодера. */
|
|
CHECK(set_gas_watch_decode(payload, size, config_id, count, &sequence, ×tamp, values, valid, capacity) == -1); /* Любая ошибка возвращает единый отрицательный результат. */
|
|
CHECK(sequence == 0x11223344UL && timestamp == 0x55667788UL); /* Скалярные выходы обновляются только после полного принятия пакета. */
|
|
CHECK(memcmp(values, before_values, sizeof values) == 0); /* Декодер не оставляет частично декодированные значения. */
|
|
CHECK(memcmp(valid, before_valid, sizeof valid) == 0); /* Декодер не оставляет частично распакованные признаки. */
|
|
} /* Завершение проверки транзакционного декодирования. */
|
|
|
|
static void test_malformed_and_limits(void) /* Проверяем границы буферов, максимальный список и канонический bitmap. */
|
|
{ /* Начало сценария валидации публичной границы данных. */
|
|
uint8_t payload[SET_GAS_WATCH_SAMPLE_MAX + 1U]; /* Дополнительный байт используется только для проверки лишнего хвоста. */
|
|
uint8_t before[sizeof payload]; /* Заполнитель позволяет доказать отсутствие записи при отказе builder. */
|
|
uint16_t duplicates[] = {1U, 1U}; /* Повтор одного адреса запрещён независимо от его реального существования в карте. */
|
|
memset(payload, 0xA5, sizeof payload); /* Начинаем с различимого содержимого выходного буфера. */
|
|
memcpy(before, payload, sizeof payload); /* Снимок буфера позволит выявить даже частично записанный заголовок. */
|
|
CHECK(set_gas_watch_request(0U, 100U, reference_addresses, 3U, payload, sizeof payload) == 0U); /* Нулевой идентификатор конфигурации запрещён хостовому builder. */
|
|
CHECK(set_gas_watch_request(1U, 0U, reference_addresses, 3U, payload, sizeof payload) == 0U); /* Ненулевой список требует ненулевого периода. */
|
|
CHECK(set_gas_watch_request(1U, 100U, NULL, 0U, payload, sizeof payload) == 0U); /* Пустой список требует нулевого периода и означает остановку. */
|
|
CHECK(set_gas_watch_request(1U, 100U, duplicates, 2U, payload, sizeof payload) == 0U); /* Дубликаты не должны выпускаться builder на линию. */
|
|
CHECK(set_gas_watch_request(1U, 100U, NULL, 3U, payload, sizeof payload) == 0U); /* Ненулевое количество требует доступного массива адресов. */
|
|
CHECK(set_gas_watch_request(1U, 100U, reference_addresses, 65U, payload, sizeof payload) == 0U); /* Проверка предела 64 выполняется до чтения указанного массива. */
|
|
CHECK(set_gas_watch_request(1U, 100U, reference_addresses, 3U, payload, sizeof reference_config - 1U) == 0U); /* Недостаточная ёмкость не допускает усечённого запроса. */
|
|
CHECK(set_gas_watch_request(1U, 100U, reference_addresses, 3U, NULL, sizeof payload) == 0U); /* Нулевой выходной указатель возвращает отказ без записи. */
|
|
CHECK(memcmp(payload, before, sizeof payload) == 0); /* Ни один ошибочный запрос не должен частично менять переданный буфер. */
|
|
memcpy(payload, reference_sample, sizeof reference_sample); /* Берём правильный пакет и независимо повреждаем отдельные части. */
|
|
decode_rejected(payload, sizeof reference_sample, 0x12345679UL, 3U, 3U); /* Задержавшаяся выборка прежнего списка отвергается даже при том же количестве каналов. */
|
|
decode_rejected(payload, sizeof reference_sample, 0x12345678UL, 2U, 3U); /* Число каналов должно совпадать с подтверждённой подпиской. */
|
|
decode_rejected(payload, sizeof reference_sample, 0x12345678UL, 3U, 2U); /* Недостаточное число выходных ячеек не оставляет частичного результата. */
|
|
decode_rejected(NULL, sizeof reference_sample, 0x12345678UL, 3U, 3U); /* Нулевой входной указатель обрабатывается как некорректный пакет. */
|
|
decode_rejected(payload, SET_GAS_WATCH_SAMPLE_HEADER - 1U, 0x12345678UL, 3U, 3U); /* Не хватает даже фиксированного заголовка выборки. */
|
|
decode_rejected(payload, sizeof reference_sample - 1U, 0x12345678UL, 3U, 3U); /* Оборванное последнее значение не может обновить первые каналы. */
|
|
decode_rejected(payload, sizeof reference_sample + 1U, 0x12345678UL, 3U, 3U); /* Избыточный хвост исключает неверную интерпретацию следующего сообщения. */
|
|
payload[0] = 3U; /* Изменяем версию при сохранении всех остальных эталонных полей. */
|
|
decode_rejected(payload, sizeof reference_sample, 0x12345678UL, 3U, 3U); /* Неподдерживаемый формат не обновляет выходы. */
|
|
payload[0] = 2U; /* Возвращаем действующую версию перед повреждением bitmap. */
|
|
payload[16] = 0x83U; /* Бит 7 находится за пределами трёх каналов и обязан быть нулевым. */
|
|
decode_rejected(payload, sizeof reference_sample, 0x12345678UL, 3U, 3U); /* Неиспользуемые биты маски проверяются для канонического представления. */
|
|
payload[16] = 3U; /* Восстанавливаем корректную маску двух достоверных каналов. */
|
|
payload[21] = 1U; /* Третий канал остаётся недостоверным, но его слово становится ненулевым. */
|
|
decode_rejected(payload, sizeof reference_sample, 0x12345678UL, 3U, 3U); /* Неканоническое ненулевое слово недостоверного канала отвергается. */
|
|
payload[21] = 0U; /* Восстанавливаем эталон перед проверкой чрезмерного количества. */
|
|
payload[2] = 65U; /* Заголовок требует больше максимальных 64 каналов. */
|
|
decode_rejected(payload, sizeof reference_sample, 0x12345678UL, 65U, 64U); /* Ограничение количества проверяется до обращения к выходным массивам. */
|
|
uint16_t addresses[SET_GAS_WATCH_MAX]; /* Максимальный список хранит уникальные адреса единого региона. */
|
|
uint16_t words[SET_GAS_WATCH_MAX]; /* Модель карты содержит достаточно реальных слов для всех 64 каналов. */
|
|
for (size_t i = 0U; i < SET_GAS_WATCH_MAX; ++i) { /* Заполняем все разрешённые позиции без фиксации размера в теле цикла. */
|
|
addresses[i] = (uint16_t)i; /* Диапазон 0..63 создаёт уникальный отсортированный список регистров. */
|
|
words[i] = (uint16_t)(1000U + i); /* Ненулевой возрастающий рисунок делает перестановки значений заметными. */
|
|
} /* Завершение подготовки максимального списка. */
|
|
pcan_gas_region_t full_region = {0U, SET_GAS_WATCH_MAX, words, NULL, NULL, PCAN_GAS_RDONLY, NULL, "full"}; /* Один читаемый регион покрывает все 64 адреса. */
|
|
pcan_gas_map_t full_map = {&full_region, 1U}; /* Карта максимального списка остаётся простой и однозначной. */
|
|
set_gas_watch_t watch; /* Состояние максимального потока проверяет размеры публичных структур. */
|
|
set_gas_watch_sample_t sample; /* Выборка должна вместить все значения и признаки без динамической памяти. */
|
|
set_gas_watch_init(&watch); /* Создаём пустое состояние для независимого граничного сценария. */
|
|
size_t config_size = set_gas_watch_request(1U, 1U, addresses, SET_GAS_WATCH_MAX, payload, sizeof payload); /* Минимальный ненулевой период 1 мс остаётся разрешённым. */
|
|
CHECK(config_size == SET_GAS_WATCH_CONFIG_MAX && config_size == 140U); /* Максимальная конфигурация занимает 12 + 64*2 байта. */
|
|
CHECK(set_gas_watch_apply(&watch, &full_map, payload, config_size) == SET_GAS_WATCH_OK); /* Устройство принимает ровно предельные 64 уникальных адреса. */
|
|
CHECK(set_gas_watch_capture(&watch, &full_map, 42U, &sample)); /* Максимальная выборка собирается за один вызов. */
|
|
size_t sample_size = set_gas_watch_encode_sample(&sample, payload, sizeof payload); /* Кодируем весь снимок с восемью байтами bitmap. */
|
|
CHECK(sample_size == SET_GAS_WATCH_SAMPLE_MAX && sample_size == 152U); /* Полный пакет занимает 16 + 8 + 64*2 байта. */
|
|
uint16_t decoded[SET_GAS_WATCH_MAX]; /* Выходные значения хоста имеют ровно предельную ёмкость. */
|
|
uint8_t valid[SET_GAS_WATCH_MAX]; /* Один признак на каждый из 64 каналов проверяет все восемь байтов bitmap. */
|
|
uint32_t sequence = UINT32_MAX; /* Отличимое исходное значение должно замениться нулём первой выборки. */
|
|
uint32_t timestamp = 0U; /* Метка должна получить приборное значение 42 мс. */
|
|
CHECK(set_gas_watch_decode(payload, sample_size, 1U, SET_GAS_WATCH_MAX, &sequence, ×tamp, decoded, valid, SET_GAS_WATCH_MAX) == (int)SET_GAS_WATCH_MAX); /* Декодер принимает предельный размер без усечения. */
|
|
CHECK(sequence == 0U && timestamp == 42U && memcmp(decoded, words, sizeof words) == 0); /* Все значения и скалярные поля максимального пакета сохраняются. */
|
|
for (size_t i = 0U; i < SET_GAS_WATCH_MAX; ++i) { /* Проверяем каждую позицию вместо одной средней маски. */
|
|
CHECK(valid[i] == 1U); /* Читаемый массив делает достоверными все 64 канала, включая последние биты bitmap. */
|
|
} /* Завершение проверки полного bitmap. */
|
|
CHECK(set_gas_watch_decode(payload, sample_size, 1U, SET_GAS_WATCH_MAX, NULL, ×tamp, decoded, valid, SET_GAS_WATCH_MAX) == (int)SET_GAS_WATCH_MAX); /* Вызывающий код может отказаться от получения необязательного счётчика последовательности. */
|
|
CHECK(set_gas_watch_decode(payload, sample_size, 1U, SET_GAS_WATCH_MAX, &sequence, NULL, decoded, valid, SET_GAS_WATCH_MAX) == (int)SET_GAS_WATCH_MAX); /* Вызывающий код может отказаться от получения необязательной отметки времени. */
|
|
CHECK(set_gas_watch_decode(payload, sample_size, 1U, SET_GAS_WATCH_MAX, &sequence, ×tamp, NULL, valid, SET_GAS_WATCH_MAX) == -1); /* Обязательный массив значений проверяется до записи результата. */
|
|
CHECK(set_gas_watch_decode(payload, sample_size, 1U, SET_GAS_WATCH_MAX, &sequence, ×tamp, decoded, NULL, SET_GAS_WATCH_MAX) == -1); /* Обязательный массив достоверности проверяется до записи результата. */
|
|
CHECK(set_gas_watch_encode_sample(&sample, NULL, sizeof payload) == 0U); /* Нулевой буфер сериализации приводит к безопасному отказу. */
|
|
CHECK(set_gas_watch_encode_sample(&sample, payload, sample_size - 1U) == 0U); /* Сериализация никогда не выпускает частичный пакет. */
|
|
CHECK(set_gas_watch_encode_sample(NULL, payload, sizeof payload) == 0U); /* Отсутствующий снимок нельзя заменить чтением какой-либо глобальной карты. */
|
|
CHECK(set_gas_watch_encode_ack(NULL, payload, sizeof payload) == 0U); /* Отсутствующая подписка не образует корректного подтверждения. */
|
|
CHECK(set_gas_watch_encode_ack(&watch, NULL, sizeof payload) == 0U); /* Нулевой буфер ACK безопасно отвергается. */
|
|
CHECK(set_gas_watch_encode_ack(&watch, payload, config_size - 1U) == 0U); /* Подтверждение не сокращается до одного заголовка из-за малого буфера. */
|
|
CHECK(set_gas_watch_apply(NULL, &full_map, payload, config_size) == SET_GAS_WATCH_INVALID); /* Пустой указатель состояния не должен разыменовываться. */
|
|
CHECK(!set_gas_watch_due(NULL, 1U)); /* Запрос расписания без состояния возвращает отсутствие готовности. */
|
|
CHECK(!set_gas_watch_capture(NULL, &full_map, 1U, &sample)); /* Сбор без состояния возвращает отказ. */
|
|
CHECK(!set_gas_watch_capture(&watch, NULL, 1U, &sample)); /* Сбор без карты не обращается к несуществующим провайдерам. */
|
|
CHECK(!set_gas_watch_capture(&watch, &full_map, 1U, NULL)); /* Сбор без выходного снимка не выполняется. */
|
|
set_gas_watch_init(NULL); /* Инициализация проверяется на безопасное поведение с нулевым указателем. */
|
|
sample.valid[0] = 0U; /* Отключаем первый канал, чтобы проверить младший бит первого байта bitmap. */
|
|
sample.valid[7] = 0U; /* Отключаем последний канал первого байта bitmap. */
|
|
sample.valid[8] = 0U; /* Отключаем первый канал второго байта bitmap, проверяя перенос границы восьми каналов. */
|
|
sample.valid[63] = 0U; /* Отключаем последний разрешённый канал в старшем бите восьмого байта. */
|
|
CHECK(set_gas_watch_encode_sample(&sample, payload, sizeof payload) == sample_size); /* Изменение достоверности сохраняет длину максимального пакета. */
|
|
CHECK(payload[16] == 0x7EU && payload[17] == 0xFEU && payload[23] == 0x7FU); /* Раскладка bitmap соответствует индексам 0, 7, 8 и 63 без смещения. */
|
|
CHECK(set_gas_watch_decode(payload, sample_size, 1U, SET_GAS_WATCH_MAX, &sequence, ×tamp, decoded, valid, SET_GAS_WATCH_MAX) == (int)SET_GAS_WATCH_MAX); /* Максимальный пакет с разреженной маской остаётся допустимым. */
|
|
CHECK(decoded[0] == 0U && decoded[7] == 0U && decoded[8] == 0U && decoded[63] == 0U); /* Недостоверные слова принудительно обнуляются на всех проверенных границах маски. */
|
|
CHECK(sample.values[0] == 1000U && sample.values[63] == 1063U); /* Каноническое обнуление относится к wire-пакету и не меняет исходный снимок. */
|
|
memcpy(before, payload, sizeof payload); /* Сохраняем полный выход перед ошибками модели сериализации. */
|
|
sample.valid[63] = 2U; /* Признак достоверности допускает только значения 0 и 1. */
|
|
CHECK(set_gas_watch_encode_sample(&sample, payload, sizeof payload) == 0U); /* Неверный признак в последнем канале проверяется до записи первого байта заголовка. */
|
|
CHECK(memcmp(payload, before, sizeof payload) == 0); /* Поздняя ошибка модели не оставляет частично записанный пакет. */
|
|
sample.valid[63] = 0U; /* Восстанавливаем двоичный признак перед проверкой неверного количества каналов. */
|
|
sample.count = SET_GAS_WATCH_MAX + 1U; /* Число 65 находится за пределами обоих встроенных массивов снимка. */
|
|
CHECK(set_gas_watch_encode_sample(&sample, payload, sizeof payload) == 0U); /* Проверка количества предотвращает выход за границу даже при достаточном output. */
|
|
CHECK(memcmp(payload, before, sizeof payload) == 0); /* Неверное количество тоже не меняет выходной буфер. */
|
|
} /* Завершение проверок некорректных входов и предельных размеров. */
|
|
|
|
int main(void) /* Один независимый исполняемый тест запускается общей системой CTest. */
|
|
{ /* Начало последовательного запуска контрактных сценариев. */
|
|
test_reference(); /* Эталонные байты фиксируют совместимость C, Python и JVM. */
|
|
test_atomic_apply(); /* Ошибки настройки и повторные запросы не должны повреждать подписку. */
|
|
test_capture_and_timing(); /* Снимок и приборный таймер проверяются отдельно от транспорта. */
|
|
test_malformed_and_limits(); /* Входные границы проверяются и на устройстве, и в хостовом ABI. */
|
|
if (failures != 0U) { /* Любое нарушение делает запуск неуспешным для CTest и CI. */
|
|
printf("GAS_WATCH2: %u failures\n", failures); /* Итог сообщает число обнаруженных нарушений контракта. */
|
|
return 1; /* Ненулевой код обозначает, что хотя бы одна контрактная проверка не прошла. */
|
|
} /* Завершение ветки ошибочного результата. */
|
|
puts("GAS_WATCH2 contract tests passed"); /* Краткая строка подтверждает завершение всех сценариев. */
|
|
return 0; /* Нулевой код передаёт общий успех системе сборки. */
|
|
} /* Завершение тестового исполняемого файла. */
|